添加管理

This commit is contained in:
齐斌 2025-09-23 10:04:22 +08:00
parent 968b91a69d
commit 4eaaa6d4bc
18 changed files with 9095 additions and 0 deletions

View File

@ -72,7 +72,72 @@ const commonRoute = [
},
component: () => import("@/views/artworkreturn/index.vue"),
},
{
path: "/posimanage/create",
name: "PosiManageCre",
meta: {
title: "岗位管理",
},
component: () => import("../views/manage/posimanage/create"),
},
{
path: "/orgmanage",
name: "OrgManage",
meta: {
title: "组织管理",
},
component: () => import("../views/manage/orgmanage/index"),
},
{
path: "/posimanage",
name: "PosiManage",
meta: {
title: "岗位管理",
},
component: () => import("../views/manage/posimanage/index"),
},
{
path: "/posirecordlog",
name: "PosiRecordLog",
meta: {
title: "岗位操作记录",
},
component: () => import("../views/manage/posirecordlog/index"),
},
{
path: "/peomanage",
name: "PeoManage",
meta: {
title: "人员管理",
},
component: () => import("../views/manage/peomanage/index"),
},
{
path: "/peomanage/personnelManage",
name: "personnelManage",
meta: {
title: "人员管理",
},
component: () => import("../views/manage/peomanage/personnelManage"),
},
{
path: "/peomanage/create",
name: "PeoManageCre",
meta: {
title: "人员管理",
},
component: () => import("../views/manage/peomanage/create"),
},
{
path: "/peomanage/editPeo",
name: "PeoManageEdi",
meta: {
title: "人员管理",
},
component: () => import("../views/manage/peomanage/editPeo"),
},
];
export default commonRoute;

View File

@ -0,0 +1,430 @@
<template>
<div class="row"
style="padding: 35px">
<div class="col-3 fl-pa-md"
style="
background: #fff;
box-shadow: rgba(188, 188, 188, 0.18) 0px 3px 6px 1px;
">
<div class="row font-16"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5">
<div class="fl-py-sm"
style="border-bottom: 4px solid #764cf6">
组织架构
</div>
</div>
<div class="row"
style="height: 76vh; overflow: auto">
<fl-tree :data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
:config="{
actions: ['edit', 'add', 'subtraction'],
addShow: '[%=level%]!==4',
}"
@triggerTreeAction="handleTreeAction"
@triggerTreeClick="handleTreeClick"></fl-tree>
</div>
</div>
<div class="col-9 row fl-px-md">
<div style="background: #fff"
class="fl-pa-md">
<div class="col-12 row font-16"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5">
<div class="fl-py-sm"
style="border-bottom: 4px solid #764cf6">
{{ state.treeSelectData.title || "平台开发管理项目组" }}所有人员
</div>
</div>
<div class="col-12 row fl-mt-sm">
<fln-table :config="state.tableConfig"
:refreshCount="state.tableConfig.refreshCount"
:fatherData="state.treeSelectData">
</fln-table>
</div>
</div>
</div>
</div>
<n-modal v-model:show="state.dialogAddTree"
style="width: 600px"
:mask-closable="false"
preset="card">
<template #header>
<div class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2">
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
添加组织
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div class="fl-my-lg font-20">
<n-input v-model:value="state.dialogAddTreeData"
placeholder="请输入组织名称" />
</div>
<div class="col-12 row justify-center">
<n-button tertiary
style="width: 161px; background: #764cf6; color: #fff"
class="fl-mr-md"
@click="handleDialogAddTreeSave">保存</n-button>
<n-button tertiary
style="width: 161px; background: #eeeaf7; color: #774ff6"
@click="state.dialogAddTree = false">返回</n-button>
</div>
</div>
</n-modal>
<n-modal v-model:show="state.dialogSubtractionTree"
style="width: 600px"
:mask-closable="false"
preset="card">
<template #header>
<div class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2">
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
删除组织
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div class="font-20"
style="margin: 115px 0">
是否确认删除该组织及其子组织如有
</div>
<div class="col-12 row justify-center">
<n-button tertiary
style="width: 161px; background: #764cf6; color: #fff"
class="fl-mr-md"
@click="handleDialogSubtractionTreeSave">确定</n-button>
<n-button tertiary
style="width: 161px; background: #eeeaf7; color: #774ff6"
@click="state.dialogSubtractionTree = false">返回</n-button>
</div>
</div>
</n-modal>
</template>
<script setup>
//
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flTree from "@/components/flnlayout/tree/flnindex.vue";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
} from "vue";
import { Local } from "@/utils/storage.js";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter } from "vue-router";
const router = useRouter();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const state = reactive({
dialogData: {},
dialogAddTree: false,
dialogAddTreeData: "",
dialogSubtractionTree: false,
treeSelectData: {},
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
btnLoading: false,
selectedRows: [],
tableConfig: {
requestbysf: true,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "user/v2/list",
params: [
{
label: "departmentID",
field: "key",
},
],
},
searchConfig: [
{
type: "text",
label: "姓名",
field: "nickName",
class: "col-4",
placeholder: "请输入姓名",
},
{
type: "text",
label: "手机号",
field: "telNum",
class: "col-4",
placeholder: "请输入手机号",
},
{
type: "text",
label: "岗位",
field: "positionName",
class: "col-4",
placeholder: "请输入岗位",
},
{
type: "text",
label: "员工工号",
field: "jobNum",
class: "col-4",
placeholder: "请输入员工工号",
},
],
columns: [
{
title: "员工工号",
field: "jobNum",
},
{
title: "姓名",
field: "nickName",
},
{
width: 120,
title: "手机号",
field: "telNum",
},
{
width: 360,
title: "岗位",
align: 'left',
field: "nowPositions",
type: "tags",
tagConfig: {
tagField: "name",
tagBgColorField: "color",
showLength: 3,
},
},
],
},
});
onBeforeMount(() => {
let treeSelectData = Local.get("orgmanage_treeSelectData");
if (treeSelectData) {
state.treeSelectData = treeSelectData;
state.clickKey = treeSelectData.key;
}
getTreeData();
});
onMounted(() => { });
const getTreeData = () => {
let url = "/department/v2/tree/my";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
if (
state.clickKey === data[0].key &&
!state.expandedKeys.includes(data[0].key)
) {
state.expandedKeys.push(data[0].key);
}
if (!state.expandedKeys.includes(state.clickKey)) {
state.expandedKeys.push(state.clickKey);
}
if (data.length === 1 && !state.expandedKeys.includes(data[0].key)) {
state.expandedKeys.push(data[0].key);
}
state.treeRefreshCount++;
state.tableConfig.refreshCount++;
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const handleCreatePosi = () => {
console.log("handleCreatePosi");
};
const handleTreeAction = ({ type, val }) => {
state.dialogData = val;
if (type === "add") {
state.clickKey = val.key;
state.treeSelectData = val;
state.tableConfig.refreshCount++;
state.dialogAddTreeData = "";
state.dialogAddTree = true;
}
if (type === "subtraction") {
state.dialogSubtractionTree = true;
}
if (type === "save") {
let url = "/department/v2/update";
let params = {
name: val.title,
ID: val.key,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
getTreeData();
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
},
() => {
processError("操作失败!");
}
);
}
if (type === "cancel") {
getTreeData();
}
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
Local.set("orgmanage_treeSelectData", tree);
state.tableConfig.refreshCount++;
};
// const handleTreeDefaultClick = () => {
// state.treeSelectData = state.treeData[0];
// state.clickKey = state.treeData[0].key;
// Local.remove("orgmanage_treeSelectData");
// state.tableConfig.refreshCount++;
// };
const handleCloseModal = () => {
// emits("triggerCloseModal");
};
//
const handleDialogAddTreeSave = () => {
let url = "/department/v2/create";
let params = {
name: state.dialogAddTreeData,
pid: state.dialogData.key,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
if (Array.isArray(res.data.levelPathList)) {
state.expandedKeys = [];
res.data.levelPathList.forEach((treeid) => {
if (!state.expandedKeys.includes(treeid)) {
state.expandedKeys.push(treeid);
}
});
}
getTreeData();
state.dialogAddTree = false;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.dialogAddTree = false;
},
() => {
state.dialogAddTree = false;
processError("操作失败!");
}
);
};
//
const handleDialogSubtractionTreeSave = () => {
let url = "/department/v2/remove";
let params = {
ID: state.dialogData.key,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
getTreeData();
state.dialogSubtractionTree = false;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.dialogSubtractionTree = false;
},
() => {
state.dialogSubtractionTree = false;
processError("操作失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.label = item.name;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
}
</style>

View File

@ -0,0 +1,893 @@
<template>
<div class="row" style="padding: 35px; margin-bottom: 80px">
<div
class="col-12 row"
style="
background: #ffffff;
border-radius: 3px;
padding: 17px 20px;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
"
>
<div
class="col-12 row font-20"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5"
>
<div
v-if="state.pageType === 'create'"
class="fl-py-sm"
style="border-bottom: 4px solid #764cf6"
>
新增
</div>
<div
v-if="state.pageType === 'edit'"
class="fl-py-sm"
style="border-bottom: 4px solid #764cf6"
>
修改
</div>
<div
v-if="state.pageType === 'view'"
class="fl-py-sm"
style="border-bottom: 4px solid #764cf6"
>
查看
</div>
</div>
<div class="col-12 row">
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.nickName"
:config="{
label: '姓名:',
field: 'nickName',
placeholder: '请输入姓名',
}"
:disable="calcPageDisable()"
@triggerValBlur="handleValBlur"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.telNum"
:config="{
label: '手机号:',
field: 'telNum',
placeholder: '请输入手机号',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.mailAccount"
:config="{
label: '邮箱:',
field: 'mailAccount',
placeholder: '自动生成',
}"
:disable="true"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="date"
:val="state.formData.enterDate"
:config="{
label: '入职时间:',
field: 'enterDate',
placeholder: '请输入入职时间',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="select"
:val="state.formData.status"
:config="{
placeholder: '请输入状态',
label: '状态:',
field: 'status',
options: [
{
label: '有效',
value: 'notactive',
},
{
label: '无效',
value: 'left',
},
],
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.jobNum"
:config="{
label: '员工工号:',
field: 'jobNum',
placeholder: '请输入员工工号',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row col-3">
<fln-form-item
type="upload"
title="图片上传"
:config="{
limit4096: true,
label: '员工近照:',
field: 'recentImg',
placeholder: '请输入员工近照',
style: 'width:80px',
}"
:val="state.formData.recentImg"
:otherParams="{
source: 'artwork',
mask: '',
type: 'image',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row col-3">
<div class="col-12 row items-center">
<fln-form-item
type="upload"
title="图片上传"
:config="{
limit4096: true,
label: '员工头像:',
field: 'avatar',
placeholder: '请输入员工近照',
style: 'width:80px',
}"
:val="state.formData.avatar"
:otherParams="{
source: 'artwork',
mask: '',
type: 'image',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
>
<template #uploadRight v-if="!calcPageDisable()">
<div class="row" style="width: calc(100% - 84px)">
<img
v-for="(item, index) in state.defaultImgList"
:src="item + '?x-oss-process=image/resize,w_158,h_158'"
:key="index"
class="img-avatar"
:class="
state.defaultImgSelect == index ? 'border-active' : ''
"
@click="handleSetImg(item, index)"
/>
</div> </template
></fln-form-item>
</div>
</div>
</div>
</div>
<div
class="col-12 row fl-mt-md"
style="
background: #ffffff;
border-radius: 3px;
padding: 17px 20px;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
"
>
<div
v-if="
Array.isArray(state.depPositions) &&
state.depPositions.filter((item) => item.positions?.length > 0)
.length > 0
"
class="col-12 row"
>
<div
class="col-12 fl-mt-md row items-center fl-pb-xs"
style="min-height: 40px"
>
<div
class="col-12 row justify-between items-center fl-px-md"
style="
min-height: 40px;
background: #46299dff;
border-radius: 3px 3px 0 0;
"
>
<div class="color-white">设置所属部门及岗位</div>
</div>
<div
style="border: 1px solid #c3c3c3; border-radius: 0 0 3px 3px"
class="col-12 row fl-pb-xs"
>
<div
class="col-12 row"
style="padding: 11px 0; background: #dfd7f2"
>
<div class="col-3 text-center">所属部门</div>
<div class="col-6 text-left">所属岗位</div>
</div>
<template v-for="(row, rowIdx) in state.depPositions">
<div
class="col-12 row fl-py-sm"
v-if="row.positions?.length > 0"
:style="
rowIdx !== state.depPositions.length - 1
? 'border-bottom:1px solid #c3c3c3'
: ''
"
:key="rowIdx"
>
<div class="col-3 row items-center justify-center">
{{ row.name }}
</div>
<div class="col-6 row items-center">
<div
v-for="(tag, idx) in row.positions"
:key="idx"
class="fl-mr-sm fl-px-sm fl-py-xs row items-center fl-mb-xs"
style="border: solid 1px #ded7f1; border-radius: 3px"
:style="{
borderColor: tag.color,
color: tag.color || '#fff',
}"
>
<n-popover :show-arrow="true">
<template #trigger>
{{ tag.name }}
</template>
{{ tag.name }}
</n-popover>
<close-circle-outlined
v-if="!calcPageDisable()"
class="fl-ml-md"
@click="handleClearPage(rowIdx, tag)"
/>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
<div class="col-12 row">
<div
class="col-3 row fl-px-md"
style="overflow: auto; box-shadow: 0 3px 6px 1px #bcbcbc2e"
>
<fln-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fln-tree>
</div>
<div class="col-9 row fl-pl-md" style="align-content: flex-start">
<fln-table
class="artwork-table"
:config="state.tableConfig"
:defaultSelectedRows="state.tableDefaultPositions"
:clearSelectRowKey="state.tableClearKey"
:fatherData="state.treeSelectData"
:fatherParams="state.tableSearchData"
:refreshCount="state.tableConfig.refreshCount"
@triggerRowSelect="handleTableRowSelect"
@triggerRowSelectAll="handleTableCheckAll"
>
<template #table-header>
<span style="color: #fd0000" class="fl-mx-sm">*</span>
请至少勾选一个岗位</template
>
</fln-table>
</div>
</div>
</div>
</div>
<div
class="width-100 row fl-pt-md fl-pb-lg"
style="position: fixed; bottom: 0px; background: #f0f0f5"
>
<div class="col-10 row justify-center">
<n-button
tertiary
class="fl-mr-md"
style="width: 161px; background: #c7c7c9ff; color: #ffffffff"
@click="handleBack"
>返回</n-button
>
<n-button
tertiary
v-if="state.pageType === 'edit'"
style="width: 161px; height: 34px; background: #46299dff; color: #fff"
@click="handleSave"
>保存</n-button
>
<n-button
tertiary
v-if="state.pageType === 'create'"
style="width: 161px; height: 34px; background: #46299dff; color: #fff"
@click="handleCreate"
>保存</n-button
>
</div>
</div>
</template>
<script setup>
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flnFormItem from "@/components/flnlayout/form/flnformItem.vue";
import flnTree from "@/components/flnlayout/tree/flnindex.vue";
import { CloseCircleOutlined } from "@ant-design/icons-vue";
import {
ref,
reactive,
onBeforeMount,
onActivated,
onMounted,
getCurrentInstance,
computed,
nextTick,
watch,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const state = reactive({
tableClearKey: null,
treeSelectData: {},
tableDefaultPositions: [],
depPositions: [],
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
formData: {
nickName: null,
telNum: null,
status: "notactive",
positionName: null,
jobNum: null,
mailAccount: null,
enterDate: null,
recentImg: null,
avatar: null,
},
tableSearchData: {},
selectedRows: [],
tableConfig: {
hasSelection: true,
requestbysf: true,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "/position/v2/list",
params: [
{
label: "departmentID",
field: "key",
},
],
},
columns: [
{
title: "岗位ID",
field: "ID",
},
{
width: 100,
align: "left",
title: "岗位名",
field: "name",
type: "sfBgColor",
bgConfig: {
bgColorField: "color",
color: "#fff",
},
},
{
type: "tooltip",
align: "left",
width: 120,
title: "岗位描述",
field: "remark",
},
{
width: 360,
align: "left",
title: "页面权限",
field: "menuAuths",
type: "tags",
tagConfig: {
showLength: 3,
bgColor: "#fff",
borderRadius: "14px",
style: "border-radius: 14px",
},
},
],
},
defaultImgList: [
"https://cdns.fontree.cn/fonchain-main/prod/runtime/image/avart/0/d5f416d5-0b95-4b2c-82c4-6218d4017d17.jpg",
"https://cdns.fontree.cn/fonchain-main/prod/runtime/image/avart/0/012a339a-53d2-43f7-ae47-84d444d5ca6f.jpg",
"https://cdns.fontree.cn/fonchain-main/prod/runtime/file/avart/11/48ae18e2-37a4-4eab-bd03-5fe1099892c3.jpg",
"https://cdns.fontree.cn/fonchain-main/prod/runtime/file/avart/11/0a53fd8a-951e-4a8e-8d71-0198dad5e7ae.jpg",
],
defaultImgSelect: null,
btnLoading: false,
pageType: "create",
});
watch(
() => state.clickKey,
(newVal, oldVal) => {
state.tableDefaultPositions =
state.depPositions.filter((item) => item.ID === state.clickKey).length > 0
? state.depPositions.filter((item) => item.ID === state.clickKey)[0]
.positions
: [];
},
{ deep: true }
);
onBeforeMount(() => {
getTreeData();
if (route.query.type) {
state.pageType = route.query.type;
}
if (route.query.type === "view") {
state.tableConfig.hasSelection = false;
}
});
onActivated(() => {
getTreeData();
if (route.query.type) {
state.pageType = route.query.type;
}
if (route.query.type === "view") {
state.tableConfig.hasSelection = false;
}
});
onMounted(() => {});
const getPageData = () => {
if (route.query.ID) {
let url = "/user/v2/detail";
let params = {
ID: Number(route.query.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
state.formData = {
nickName: res.data.nickName,
telNum: res.data.telNum,
status: res.data.status,
positionName: res.data.positionName,
jobNum: res.data.jobNum,
mailAccount: res.data.mailAccount,
enterDate: res.data.enterDate,
recentImg: res.data.recentImg,
avatar: res.data.avatar,
};
state.depPositions = res.data.depPositions || [];
state.tableDefaultPositions =
state.depPositions.filter((item) => item.ID === state.clickKey)
.length > 0
? state.depPositions.filter(
(item) => item.ID === state.clickKey
)[0].positions
: [];
state.depPositions.forEach((item) => {
item.isLeader = item.isLeader || false;
});
} else {
processError(res.msg || "获取数据失败!");
}
},
() => {
processError("获取数据失败!");
},
() => {
processError("获取数据失败!");
}
);
}
};
const getTreeData = () => {
let url = "/department/v2/tree/all";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
if (!state.expandedKeys.includes(data[0].key)) {
state.expandedKeys.push(data[0].key);
}
state.treeRefreshCount++;
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
state.tableConfig.refreshCount++;
if (route.query.ID) {
getPageData();
}
} else {
processError("获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const handleValChange = ({ val, config }) => {
state.formData[config.field] = val;
};
const handleValBlur = ({ val, config }) => {
if (config.field === "nickName" && state.formData.nickName) {
let url = "/user/mail/account";
let params = {
nickName: val,
ID: route.query.ID ? Number(route.query.ID) : null,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.formData.mailAccount = res.data.mailAccount;
} else {
processError("生成邮箱错误:" + res.msg);
state.formData.mailAccount = "";
}
},
() => {},
() => {}
);
}
};
const handleSearch = () => {
state.tableSearchData = JSON.parse(JSON.stringify(state.formData));
};
const handleTableRowSelect = ({ record, selected, selectedRows }) => {
if (
state.depPositions.filter((item) => item.ID === state.clickKey).length === 0
) {
state.depPositions.push({
isLeader: false,
ID: state.clickKey,
name: state.treeSelectData.title,
positions: selectedRows.map((item) => {
return {
ID: item.ID,
name: item.name,
color: item.color,
};
}),
});
} else {
state.depPositions.forEach((item) => {
if (item.ID === state.clickKey) {
if (selected) {
//
item.positions = item.positions.filter(
(depItem) => depItem.ID !== record.ID
);
item.positions = item.positions.concat([
{
ID: record.ID,
name: record.name,
color: record.color,
},
]);
} else {
item.positions = item.positions.filter(
(depItem) => depItem.ID !== record.ID
);
}
}
});
}
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
state.selectedRows = selectedRows;
};
const handleTableCheckAll = ({ selectedRowKeys, selectedRows }) => {
if (
state.depPositions.filter((item) => item.ID === state.clickKey).length === 0
) {
state.depPositions.push({
isLeader: false,
ID: state.clickKey,
name: state.treeSelectData.title,
positions: selectedRows.map((item) => {
return {
ID: item.ID,
name: item.name,
color: item.color,
};
}),
});
} else {
state.depPositions.forEach((item) => {
if (item.ID === state.clickKey) {
item.positions = selectedRows.map((item) => {
return {
ID: item.ID,
name: item.name,
color: item.color,
};
});
}
});
}
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
state.selectedRows = selectedRows;
};
const handleDialogSave = () => {
state.dialogDel = false;
};
const handleDialogBack = () => {
state.dialogDel = false;
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
state.tableConfig.refreshCount++;
};
const calcPageDisable = () => {
return state.pageType === "view";
};
const handleSetImg = (item, index) => {
state.formData.avatar = "";
nextTick(() => {
state.formData.avatar = item;
state.defaultImgSelect = index;
});
};
const handleCreate = () => {
if (
state.formData.jobNum === null ||
state.formData.jobNum === "" ||
state.formData.jobNum === undefined
) {
processError("请输入员工工号");
return;
}
state.btnLoading = true;
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
let url = "/user/v2/create";
let params = {
depPositions: state.depPositions,
};
Object.assign(params, state.formData);
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
processSuccess("操作成功!");
router.go(-1);
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
const handleSave = () => {
if (
state.formData.jobNum === null ||
state.formData.jobNum === "" ||
state.formData.jobNum === undefined
) {
processError("请输入员工工号");
return;
}
state.btnLoading = true;
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
let url = "/user/v2/update";
let params = {
ID: Number(route.query.ID),
depPositions: state.depPositions,
};
Object.assign(params, state.formData);
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
processSuccess("操作成功!");
getPageData();
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
const handleLink = (path) => {
router.push(path);
};
const handleBack = () => {
router.go(-1);
};
const calcDepPositions = () => {
for (let key in state.depPositions) {
let data = state.depPositions[key];
if (!data || JSON.stringify(data) === "[]") {
delete state.depPositions[key];
}
}
};
const handleClearPage = (idx, clearItem) => {
state.depPositions[idx].positions.forEach((item, index) => {
if (item.name === clearItem.name) {
state.depPositions[idx].positions.splice(index, 1);
}
if (state.depPositions[idx].positions.length === 0) {
state.depPositions.splice(idx, 1);
}
});
state.tableClearKey = clearItem.ID;
};
const calcSfBgColorWidth = (val) => {
if (val) {
return val.length * 15 + "px !important";
}
return null;
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.avatar-uploader > .ant-upload) {
background: #fafafcff;
}
:deep(.ant-image) {
height: 100%;
width: 100%;
border-radius: 50%;
}
:deep(.ant-image-img) {
object-fit: cover !important;
border-radius: 50%;
}
:deep(.ant-image-mask) {
border-radius: 50%;
}
:deep(.ant-upload) {
color: #c1b2e5;
}
:deep(.ant-select) {
color: #000;
}
:deep(.ant-select-selector .ant-select-selection-placeholder) {
color: #c3c3c3 !important;
&::placeholder {
color: #c3c3c3 !important;
}
}
:deep(.ant-select:not(.ant-select-customize-input) .ant-select-selector) {
background: #fff;
}
}
.img-avatar {
width: 40px;
height: 40px;
margin: 3px;
cursor: pointer;
}
.border-active {
border: 2px solid #764cf6;
}
</style>

View File

@ -0,0 +1,893 @@
<template>
<div class="row" style="padding: 35px; margin-bottom: 80px">
<div
class="col-12 row"
style="
background: #ffffff;
border-radius: 3px;
padding: 17px 20px;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
"
>
<div
class="col-12 row font-20"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5"
>
<div
v-if="state.pageType === 'create'"
class="fl-py-sm"
style="border-bottom: 4px solid #764cf6"
>
新增
</div>
<div
v-if="state.pageType === 'edit'"
class="fl-py-sm"
style="border-bottom: 4px solid #764cf6"
>
修改
</div>
<div
v-if="state.pageType === 'view'"
class="fl-py-sm"
style="border-bottom: 4px solid #764cf6"
>
查看
</div>
</div>
<div class="col-12 row">
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.nickName"
:config="{
label: '姓名:',
field: 'nickName',
placeholder: '请输入姓名',
}"
:disable="calcPageDisable()"
@triggerValBlur="handleValBlur"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.telNum"
:config="{
label: '手机号:',
field: 'telNum',
placeholder: '请输入手机号',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.mailAccount"
:config="{
label: '邮箱:',
field: 'mailAccount',
placeholder: '自动生成',
}"
:disable="true"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="date"
:val="state.formData.enterDate"
:config="{
label: '入职时间:',
field: 'enterDate',
format: 'YYYY-MM-dd',
placeholder: '请输入入职时间',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="select"
:val="state.formData.status"
:config="{
placeholder: '请输入状态',
label: '状态:',
field: 'status',
options: [
{
label: '有效',
value: 'notactive',
},
{
label: '无效',
value: 'left',
},
],
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row items-center col-3">
<fln-form-item
type="text"
:val="state.formData.jobNum"
:config="{
label: '员工工号:',
field: 'jobNum',
placeholder: '请输入员工工号',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row col-3">
<fln-form-item
type="upload"
title="图片上传"
:config="{
limit4096: true,
label: '员工近照:',
field: 'recentImg',
placeholder: '请输入员工近照',
style: 'width:80px',
}"
:val="state.formData.recentImg"
:otherParams="{
source: 'artwork',
mask: '',
type: 'image',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mt-md row col-3">
<div class="col-12 row items-center">
<fln-form-item
type="upload"
title="图片上传"
:config="{
limit4096: true,
label: '员工头像:',
field: 'avatar',
placeholder: '请输入员工近照',
style: 'width:80px',
}"
:val="state.formData.avatar"
:otherParams="{
source: 'artwork',
mask: '',
type: 'image',
}"
:disable="calcPageDisable()"
@triggerValChange="handleValChange"
>
<template #uploadRight v-if="!calcPageDisable()">
<div class="row" style="width: calc(100% - 84px)">
<img
v-for="(item, index) in state.defaultImgList"
:src="item + '?x-oss-process=image/resize,w_158,h_158'"
:key="index"
class="img-avatar"
:class="
state.defaultImgSelect == index ? 'border-active' : ''
"
@click="handleSetImg(item, index)"
/>
</div> </template
></fln-form-item>
</div>
</div>
</div>
</div>
<div
class="col-12 row fl-mt-md"
style="
background: #ffffff;
border-radius: 3px;
padding: 17px 20px;
box-shadow: 0 3px 6px 1px rgba(188, 188, 188, 0.18);
"
>
<div
v-if="
Array.isArray(state.depPositions) &&
state.depPositions.filter((item) => item.positions?.length > 0)
.length > 0
"
class="col-12 row"
>
<div
class="col-12 fl-mt-md row items-center fl-pb-xs"
style="min-height: 40px"
>
<div
class="col-12 row justify-between items-center fl-px-md"
style="
min-height: 40px;
background: #46299dff;
border-radius: 3px 3px 0 0;
"
>
<div class="color-white">设置所属部门及岗位</div>
</div>
<div
style="border: 1px solid #c3c3c3; border-radius: 0 0 3px 3px"
class="col-12 row fl-pb-xs"
>
<div
class="col-12 row"
style="padding: 11px 0; background: #dfd7f2"
>
<div class="col-3 text-center">所属部门</div>
<div class="col-6 text-left">所属岗位</div>
</div>
<template v-for="(row, rowIdx) in state.depPositions">
<div
class="col-12 row fl-py-sm"
v-if="row.positions?.length > 0"
:style="
rowIdx !== state.depPositions.length - 1
? 'border-bottom:1px solid #c3c3c3'
: ''
"
:key="rowIdx"
>
<div class="col-3 row items-center justify-center">
{{ row.name }}
</div>
<div class="col-6 row items-center">
<div
v-for="(tag, idx) in row.positions"
:key="idx"
class="fl-mr-sm fl-px-sm fl-py-xs row items-center fl-mb-xs"
style="border: solid 1px #ded7f1; border-radius: 3px"
:style="{
borderColor: tag.color,
color: tag.color || '#fff',
}"
>
<n-popover :show-arrow="true">
<template #trigger>
{{ tag.name }}
</template>
{{ tag.name }}
</n-popover>
<close-circle-outlined
v-if="!calcPageDisable()"
class="fl-ml-md"
@click="handleClearPage(rowIdx, tag)"
/>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
<div class="col-12 row">
<div
class="col-3 row fl-px-md"
style="overflow: auto; box-shadow: 0 3px 6px 1px #bcbcbc2e"
>
<fln-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fln-tree>
</div>
<div class="col-9 row fl-pl-md" style="align-content: flex-start">
<fln-table
class="artwork-table"
:config="state.tableConfig"
:defaultSelectedRows="state.tableDefaultPositions"
:clearSelectRowKey="state.tableClearKey"
:fatherData="state.treeSelectData"
:fatherParams="state.tableSearchData"
:refreshCount="state.tableConfig.refreshCount"
@triggerRowSelect="handleTableRowSelect"
@triggerRowSelectAll="handleTableCheckAll"
>
<template #table-header>
<span style="color: #fd0000" class="fl-mx-sm">*</span>
请至少勾选一个岗位</template
>
</fln-table>
</div>
</div>
</div>
</div>
<div
class="width-100 row fl-pt-md fl-pb-lg"
style="position: fixed; bottom: 0px; background: #f0f0f5"
>
<div class="col-10 row justify-center">
<n-button
tertiary
class="fl-mr-md"
style="width: 161px; background: #c7c7c9ff; color: #ffffffff"
@click="handleBack"
>返回</n-button
>
<n-button
tertiary
v-if="state.pageType === 'edit'"
style="width: 161px; height: 34px; background: #46299dff; color: #fff"
@click="handleSave"
>保存</n-button
>
<n-button
tertiary
v-if="state.pageType === 'create'"
style="width: 161px; height: 34px; background: #46299dff; color: #fff"
@click="handleCreate"
>保存</n-button
>
</div>
</div>
</template>
<script setup>
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flnFormItem from "@/components/flnlayout/form/flnformItem.vue";
import flnTree from "@/components/flnlayout/tree/flnindex.vue";
import { CloseCircleOutlined } from "@ant-design/icons-vue";
import {
ref,
reactive,
onBeforeMount,
onActivated,
onMounted,
getCurrentInstance,
computed,
nextTick,
watch,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const state = reactive({
tableClearKey: null,
treeSelectData: {},
tableDefaultPositions: [],
depPositions: [],
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
formData: {
nickName: null,
telNum: null,
status: "notactive",
positionName: null,
jobNum: null,
mailAccount: null,
enterDate: null,
recentImg: null,
avatar: null,
},
tableSearchData: {},
selectedRows: [],
tableConfig: {
hasSelection: true,
requestbysf: true,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "/position/v2/list",
params: [
{
label: "departmentID",
field: "key",
},
],
},
columns: [
{
title: "岗位ID",
field: "ID",
},
{
width: 100,
align: "left",
title: "岗位名",
field: "name",
type: "sfBgColor",
bgConfig: {
bgColorField: "color",
color: "#fff",
},
},
{
type: "tooltip",
align: "left",
width: 120,
title: "岗位描述",
field: "remark",
},
{
width: 360,
align: "left",
title: "页面权限",
field: "menuAuths",
type: "tags",
tagConfig: {
showLength: 3,
bgColor: "#fff",
borderRadius: "14px",
style: "border-radius: 14px",
},
},
],
},
defaultImgList: [
"https://cdns.fontree.cn/fonchain-main/prod/runtime/image/avart/0/d5f416d5-0b95-4b2c-82c4-6218d4017d17.jpg",
"https://cdns.fontree.cn/fonchain-main/prod/runtime/image/avart/0/012a339a-53d2-43f7-ae47-84d444d5ca6f.jpg",
"https://cdns.fontree.cn/fonchain-main/prod/runtime/file/avart/11/48ae18e2-37a4-4eab-bd03-5fe1099892c3.jpg",
"https://cdns.fontree.cn/fonchain-main/prod/runtime/file/avart/11/0a53fd8a-951e-4a8e-8d71-0198dad5e7ae.jpg",
],
defaultImgSelect: null,
btnLoading: false,
pageType: "create",
});
watch(
() => state.clickKey,
(newVal, oldVal) => {
state.tableDefaultPositions =
state.depPositions.filter((item) => item.ID === state.clickKey).length > 0
? state.depPositions.filter((item) => item.ID === state.clickKey)[0]
.positions
: [];
},
{ deep: true }
);
onBeforeMount(() => {
getTreeData();
if (route.query.type) {
state.pageType = route.query.type;
}
if (route.query.type === "view") {
state.tableConfig.hasSelection = false;
}
});
onActivated(() => {
getTreeData();
if (route.query.type) {
state.pageType = route.query.type;
}
if (route.query.type === "view") {
state.tableConfig.hasSelection = false;
}
});
onMounted(() => {});
const getPageData = () => {
if (route.query.ID) {
let url = "/user/v2/boss/staff/detail";
let params = {
ID: Number(route.query.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
state.formData = {
nickName: res.data.nickName,
telNum: res.data.telNum,
status: res.data.status,
positionName: res.data.positionName,
jobNum: res.data.jobNum,
mailAccount: res.data.mailAccount,
enterDate: res.data.enterDate,
recentImg: res.data.recentImg,
avatar: res.data.avatar,
};
state.depPositions = res.data.depPositions || [];
state.tableDefaultPositions =
state.depPositions.filter((item) => item.ID === state.clickKey)
.length > 0
? state.depPositions.filter(
(item) => item.ID === state.clickKey
)[0].positions
: [];
state.depPositions.forEach((item) => {
item.isLeader = item.isLeader || false;
});
} else {
processError(res.msg || "获取数据失败!");
}
},
() => {
processError("获取数据失败!");
},
() => {
processError("获取数据失败!");
}
);
}
};
const getTreeData = () => {
let params = {};
$request.HTTP.components.viewMyTree(params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
if (!state.expandedKeys.includes(data[0].key)) {
state.expandedKeys.push(data[0].key);
}
state.treeRefreshCount++;
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
state.tableConfig.refreshCount++;
if (route.query.ID) {
getPageData();
}
} else {
processError("获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const handleValChange = ({ val, config }) => {
state.formData[config.field] = val;
};
const handleValBlur = ({ val, config }) => {
if (config.field === "nickName" && state.formData.nickName) {
let url = "/user/mail/account";
let params = {
nickName: val,
ID: route.query.ID ? Number(route.query.ID) : null,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.formData.mailAccount = res.data.mailAccount;
} else {
processError("生成邮箱错误:" + res.msg);
state.formData.mailAccount = "";
}
},
() => {},
() => {}
);
}
};
const handleSearch = () => {
state.tableSearchData = JSON.parse(JSON.stringify(state.formData));
};
const handleTableRowSelect = ({ record, selected, selectedRows }) => {
if (
state.depPositions.filter((item) => item.ID === state.clickKey).length === 0
) {
state.depPositions.push({
isLeader: false,
ID: state.clickKey,
name: state.treeSelectData.title,
positions: selectedRows.map((item) => {
return {
ID: item.ID,
name: item.name,
color: item.color,
};
}),
});
} else {
state.depPositions.forEach((item) => {
if (item.ID === state.clickKey) {
if (selected) {
//
item.positions = item.positions.filter(
(depItem) => depItem.ID !== record.ID
);
item.positions = item.positions.concat([
{
ID: record.ID,
name: record.name,
color: record.color,
},
]);
} else {
item.positions = item.positions.filter(
(depItem) => depItem.ID !== record.ID
);
}
}
});
}
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
state.selectedRows = selectedRows;
};
const handleTableCheckAll = ({ selectedRowKeys, selectedRows }) => {
if (
state.depPositions.filter((item) => item.ID === state.clickKey).length === 0
) {
state.depPositions.push({
isLeader: false,
ID: state.clickKey,
name: state.treeSelectData.title,
positions: selectedRows.map((item) => {
return {
ID: item.ID,
name: item.name,
color: item.color,
};
}),
});
} else {
state.depPositions.forEach((item) => {
if (item.ID === state.clickKey) {
item.positions = selectedRows.map((item) => {
return {
ID: item.ID,
name: item.name,
color: item.color,
};
});
}
});
}
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
state.selectedRows = selectedRows;
};
const handleDialogSave = () => {
state.dialogDel = false;
};
const handleDialogBack = () => {
state.dialogDel = false;
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
state.tableConfig.refreshCount++;
};
const calcPageDisable = () => {
return state.pageType === "view";
};
const handleSetImg = (item, index) => {
state.formData.avatar = "";
nextTick(() => {
state.formData.avatar = item;
state.defaultImgSelect = index;
});
};
const handleCreate = () => {
if (
state.formData.jobNum === null ||
state.formData.jobNum === "" ||
state.formData.jobNum === undefined
) {
processError("请输入员工工号");
return;
}
state.btnLoading = true;
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
let url = "/user/v2/create";
let params = {
depPositions: state.depPositions,
};
Object.assign(params, state.formData);
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
processSuccess("操作成功!");
router.go(-1);
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
const handleSave = () => {
if (
state.formData.jobNum === null ||
state.formData.jobNum === "" ||
state.formData.jobNum === undefined
) {
processError("请输入员工工号");
return;
}
state.btnLoading = true;
state.depPositions = state.depPositions.filter((item) => {
return item.positions && item.positions.length > 0;
});
let url = "/user/v2/boss/staff/update";
let params = {
ID: Number(route.query.ID),
depPositions: state.depPositions,
};
Object.assign(params, state.formData);
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
processSuccess("操作成功!");
getPageData();
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
const handleLink = (path) => {
router.push(path);
};
const handleBack = () => {
router.go(-1);
};
const calcDepPositions = () => {
for (let key in state.depPositions) {
let data = state.depPositions[key];
if (!data || JSON.stringify(data) === "[]") {
delete state.depPositions[key];
}
}
};
const handleClearPage = (idx, clearItem) => {
state.depPositions[idx].positions.forEach((item, index) => {
if (item.name === clearItem.name) {
state.depPositions[idx].positions.splice(index, 1);
}
if (state.depPositions[idx].positions.length === 0) {
state.depPositions.splice(idx, 1);
}
});
state.tableClearKey = clearItem.ID;
};
const calcSfBgColorWidth = (val) => {
if (val) {
return val.length * 15 + "px !important";
}
return null;
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.avatar-uploader > .ant-upload) {
background: #fafafcff;
}
:deep(.ant-image) {
height: 100%;
width: 100%;
border-radius: 50%;
}
:deep(.ant-image-img) {
object-fit: cover !important;
border-radius: 50%;
}
:deep(.ant-image-mask) {
border-radius: 50%;
}
:deep(.ant-upload) {
color: #c1b2e5;
}
:deep(.ant-select) {
color: #000;
}
:deep(.ant-select-selector .ant-select-selection-placeholder) {
color: #c3c3c3 !important;
&::placeholder {
color: #c3c3c3 !important;
}
}
:deep(.ant-select:not(.ant-select-customize-input) .ant-select-selector) {
background: #fff;
}
}
.img-avatar {
width: 40px;
height: 40px;
margin: 3px;
cursor: pointer;
}
.border-active {
border: 2px solid #764cf6;
}
</style>

View File

@ -0,0 +1,578 @@
<template>
<div class="row" style="padding: 35px">
<div
class="col-3 fl-pa-md"
style="
background: #fff;
box-shadow: rgba(188, 188, 188, 0.18) 0px 3px 6px 1px;
"
>
<div
class="row font-16"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5"
>
<div class="fl-py-sm" style="border-bottom: 4px solid #764cf6">
组织架构
</div>
</div>
<div class="row" style="height: 76vh; overflow: auto">
<fl-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fl-tree>
</div>
</div>
<div class="col-9 row fl-px-md" style="align-items: flex-start">
<fln-table
:config="state.tableConfig"
:fatherData="state.treeSelectData"
:refreshCount="state.tableConfig.refreshCount"
@triggerRowActions="handleRowActions"
@triggerTableBatchActions="handleTableBatchActions"
>
<template #search-header>
<div
class="col-12 row font-18"
style="
color: #764cf6;
border-bottom: 1px solid #c1b2e5;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div class="fl-py-sm" style="border-bottom: 4px solid #764cf6">
{{ state.treeSelectData.title || "平台开发管理项目组" }}所有人员
</div>
<div>
<n-button @click="goToPeoManage" v-permission="'per-system-btn'">
系统所有人员
</n-button>
</div>
</div>
</template>
</fln-table>
</div>
</div>
</template>
<script setup>
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flTree from "@/components/flnlayout/tree/flnindex.vue";
import { NButton, NPopover } from "naive-ui";
import { Local } from "@/utils/storage.js";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter } from "vue-router";
const router = useRouter();
const currentInstance = getCurrentInstance();
const { $request, $hadRule } =
currentInstance.appContext.config.globalProperties;
const state = reactive({
permission: false,
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
btnLoading: false,
treeSelectData: {},
selectedRows: [],
tableConfig: {
tableScrollWitdh: 600,
hasSelection: true,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "user/v2/list",
params: [
{
label: "fatherDepartmentId",
field: "key",
},
],
},
searchConfig: [
{
type: "text",
label: "姓名",
field: "nickName",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "手机号",
field: "telNum",
class: "col-4",
placeholder: "",
},
{
type: "select",
label: "状态",
field: "status",
class: "col-4",
placeholder: "",
config: {
options: [
{
label: "有效",
value: "notactive",
},
{
label: "无效",
value: "left",
},
],
},
},
{
type: "text",
label: "员工工号",
field: "jobNum",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "部门",
field: "departmentName",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "岗位",
field: "positionName",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "邮箱",
field: "mailAccount",
class: "col-4",
placeholder: "",
},
{
type: "rangdate",
label: "入职时间",
class: "col-4",
field: ["startEnterDate", "endEnterDate"],
placeholder: ["开始时间", "结束时间"],
},
],
columns: [
{
width: 100,
title: "员工工号",
field: "jobNum",
},
{
width: 120,
title: "姓名",
field: "nickName",
},
{
type: "tooltip",
title: "手机",
field: "telNum",
},
{
width: 360,
title: "归属部门",
align: "left",
field: "depPositions",
type: "tags",
tagConfig: {
tagField: "name",
showLength: 3,
bgColor: "#E0E0E6FF",
},
},
{
width: 360,
title: $hadRule("posi-edit-btn")
? "岗位(可点击岗位修改权限)"
: "岗位",
align: "left",
field: "positions",
render(row, index) {
const positions = row.positions;
if (!positions || positions.length === 0) {
return h("span", "No positions available");
}
// 3
const visibleTags = positions.slice(0, 3).map((position) =>
h(
NButton,
{
style: {
marginRight: "2px",
borderWidth: "1px",
borderStyle: "solid",
color: position.color,
},
onClick: () => {
if ($hadRule("posi-edit-btn")) {
router.push(
`/posimanage/create?ID=${position.ID}&&type=edit`
);
}
},
},
{ default: () => position.name }
)
);
// 3
if (positions.length > 3) {
const hiddenTags = positions.slice(3);
visibleTags.push(
h(
NPopover,
{
trigger: "hover",
placement: "bottom",
},
{
trigger: () =>
h(
"span",
{
style: {
marginLeft: "10px",
borderWidth: "1px",
borderStyle: "solid",
color: "#000",
cursor: "pointer",
backgroundColor: "transparent",
padding: "10px 15px",
borderRadius: "5px",
},
},
"..."
),
default: () =>
h(
"div",
{
style: {
padding: "10px",
backgroundColor: "#fff",
borderRadius: "5px",
boxShadow: "0 2px 12px rgba(0, 0, 0, 0.1)",
},
},
hiddenTags.map((position) =>
h(
NButton,
{
style: {
marginRight: "2px",
borderWidth: "1px",
borderStyle: "solid",
color: position.color,
},
onClick: () => {
if ($hadRule("posi-edit-btn")) {
router.push(
`/posimanage/create?ID=${position.ID}&&type=edit`
);
}
},
},
{ default: () => position.name }
)
)
),
}
)
);
}
return visibleTags;
},
},
{
width: 260,
title: "邮箱",
field: "mailAccount",
},
{
width: 100,
type: "avatar",
title: "员工头像",
field: "avatar",
},
{
width: 100,
type: "avatar",
title: "员工近照",
field: "recentImg",
},
{
type: "select",
title: "状态",
field: "status",
config: {
options: [
{
label: "有效",
value: "notactive",
class: "color-primary",
},
{
label: "无效",
value: "left",
class: "color-draft",
},
],
},
},
{
width: 120,
title: "入职时间",
field: "enterDate",
},
{
width: 200,
title: "最近一次更新时间",
field: "updatedAt",
},
{
width: 140,
fixed: "right",
title: "操作人",
field: "operatorName",
},
{
width: 200,
fixed: "right",
title: "操作",
actions: [
{
actionType: "act_view",
type: "label",
label: "查看",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
{
actionType: "act_edit",
type: "label",
label: "修改",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
],
},
],
tableBatchActions: [
{
notLimitSelect: true,
actionType: "peo-create",
type: "primary",
label: "新增人员",
},
{
actionType: "peo-pass-set",
style: "background:#F7EFFFFF;color:#8352B4FF",
type: "primary",
label: "重置密码",
},
],
},
});
onBeforeMount(() => {
getTreeData();
});
onMounted(() => {});
const handleRowActions = ({ btnConfig, rowData }) => {
state.dialogData = rowData;
Local.set("orgmanage_treeSelectData", state.treeSelectData);
if (btnConfig.actionType === "act_edit") {
router.push(`/peomanage/editPeo?ID=${rowData.ID}&&type=edit`);
}
if (btnConfig.actionType === "act_view") {
router.push(`/peomanage/editPeo?ID=${rowData.ID}&&type=view`);
}
};
const handleTableBatchActions = ({ selectedRows, btnConfig, fatherData }) => {
state.selectedRows = selectedRows;
if (btnConfig.actionType === "peo-create") {
handleCreatePeo();
}
if (btnConfig.actionType === "peo-pass-set") {
handleResetPass();
}
};
const handleCreatePeo = () => {
router.push("/peomanage/editPeo");
};
const goToPeoManage = () => {
router.push("/peomanage/personnelManage");
};
const handleResetPass = () => {
state.btnLoading = true;
let url = "/user/v2/reset/pwd";
let params = {
IDs: state.selectedRows.map((item) => item.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.selectedRows = [];
state.tableConfig.refreshCount++;
processSuccess("操作成功!");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
//
const getTreeData = () => {
let params = {};
$request.HTTP.components.viewMyTree(params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
//
let localSelect = Local.get("orgmanage_treeSelectData");
if (localSelect && JSON.stringify(localSelect) !== "{}") {
state.treeSelectData = localSelect;
state.expandedKeys = localSelect.pathIds;
state.clickKey = localSelect.key;
} else {
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
if (
state.clickKey === data[0].key &&
!state.expandedKeys.includes(data[0].key)
) {
state.expandedKeys.push(data[0].key);
}
if (!state.expandedKeys.includes(state.clickKey)) {
state.expandedKeys.push(state.clickKey);
}
}
setTimeout(() => {
state.treeRefreshCount++;
state.tableConfig.refreshCount++;
}, 100);
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.label = item.name;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
Local.set("orgmanage_treeSelectData", state.treeSelectData);
state.tableConfig.refreshCount++;
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-select) {
color: #000;
}
:deep(.ant-select-selector .ant-select-selection-placeholder) {
color: #c3c3c3 !important;
&::placeholder {
color: #c3c3c3 !important;
}
}
:deep(.ant-select:not(.ant-select-customize-input) .ant-select-selector) {
background: #fff;
}
}
</style>

View File

@ -0,0 +1,483 @@
<template>
<div class="row"
style="padding: 35px">
<div class="col-12 row">
<fln-table :config="state.tableConfig"
:fatherData="state.treeSelectData"
:refreshCount="state.tableConfig.refreshCount"
@triggerRowActions="handleRowActions"
@triggerTableBatchActions="handleTableBatchActions">
<template #search-header>
<div class="col-12 row font-18"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5; display: flex; justify-content: space-between; align-items: center;">
<div class="fl-py-sm"
style="border-bottom: 4px solid #764cf6">
系统所有人员
</div>
<div>
<n-button @click="goToIndex">
返回部门人员
</n-button>
</div>
</div>
</template>
</fln-table>
</div>
</div>
<n-modal v-model:show="state.dialogDel"
style="width: 600px"
:mask-closable="false"
preset="card">
<template #header>
<div class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2">
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
删除
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div v-if="state.dialogData.type === 'one'"
class="font-20"
style="color: #1f2225ff; margin: 80px 0">
确定删除该人员吗 删除后将不可恢复
</div>
<div v-if="state.dialogData.type === 'batch'"
class="font-20"
style="color: #1f2225ff; margin: 80px 0">
确定删除选中的人员吗 删除后将不可恢复
</div>
<div class="col-12 row justify-center">
<n-button tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="handleDialogBack">返回</n-button>
<n-button tertiary
style="width: 161px; background: #46299dff; color: #fff"
@click="handleDialogSave">确定</n-button>
</div>
</div>
</n-modal>
</template>
<script setup>
import flnTable from "@/components/flnlayout/table/flntable.vue";
import { NButton } from "naive-ui";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter } from "vue-router";
const router = useRouter();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const state = reactive({
dialogDel: false,
dialogData: {},
btnLoading: false,
selectedRows: [],
tableConfig: {
tableScrollWitdh: 600,
hasSelection: true,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "user/v2/list",
params: [],
},
searchConfig: [
{
type: "text",
label: "姓名",
field: "nickName",
placeholder: "",
},
{
type: "text",
label: "手机号",
field: "telNum",
placeholder: "",
},
{
type: "select",
label: "状态",
field: "status",
placeholder: "",
config: {
options: [
{
label: "有效",
value: "notactive",
},
{
label: "无效",
value: "left",
},
],
},
},
{
type: "text",
label: "员工工号",
field: "jobNum",
placeholder: "",
},
{
type: "text",
label: "部门",
field: "departmentName",
placeholder: "",
},
{
type: "text",
label: "岗位",
field: "positionName",
placeholder: "",
},
{
type: "text",
label: "邮箱",
field: "mailAccount",
placeholder: "",
},
{
type: "rangdate",
label: "入职时间",
field: ["startEnterDate", "endEnterDate"],
placeholder: ["开始时间", "结束时间"],
},
],
columns: [
{
width: 100,
title: "员工工号",
field: "jobNum",
},
{
width: 120,
title: "姓名",
field: "nickName",
},
{
type: "tooltip",
title: "手机",
field: "telNum",
},
{
width: 360,
title: "归属部门",
align: 'left',
field: "depPositions",
type: "tags",
tagConfig: {
tagField: "name",
showLength: 3,
bgColor: "#E0E0E6FF",
},
},
{
width: 360,
title: "岗位",
align: 'left',
field: "positions",
type: "tags",
tagConfig: {
tagField: "name",
tagBgColorField: "color",
showLength: 3,
},
},
{
width: 260,
title: "邮箱",
field: "mailAccount",
},
{
width: 100,
type: "avatar",
title: "员工头像",
field: "avatar",
},
{
width: 100,
type: "avatar",
title: "员工近照",
field: "recentImg",
},
{
type: "select",
title: "状态",
field: "status",
config: {
options: [
{
label: "有效",
value: "notactive",
class: "color-primary",
},
{
label: "无效",
value: "left",
class: "color-draft",
},
],
},
},
{
width: 120,
title: "入职时间",
field: "enterDate",
},
{
width: 160,
title: "最近一次更新时间",
field: "updatedAt",
},
{
width: 140,
fixed: "right",
title: "操作人",
field: "operatorName",
},
{
width: 200,
fixed: "right",
title: "操作",
actions: [
{
actionType: "act_view",
type: "label",
label: "查看",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
{
actionType: "act_edit",
type: "label",
label: "修改",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
{
actionType: "act_del",
type: "label",
label: "删除",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
],
},
],
tableBatchActions: [
{
notLimitSelect: true,
actionType: "peo-create",
type: "primary",
label: "新建人员",
},
{
actionType: "peo-pass-set",
style: "background:#F7EFFFFF;color:#8352B4FF",
type: "primary",
label: "重置密码",
},
],
},
});
onBeforeMount(() => { });
onMounted(() => { });
const handleRowActions = ({ btnConfig, rowData }) => {
state.dialogData = rowData;
if (btnConfig.actionType === "act_del") {
if (rowData.status !== "left") {
processError("该人员在职,不可删除");
return
}
state.dialogData.type = "one";
state.dialogDel = true;
}
if (btnConfig.actionType === "act_edit") {
router.push(`/peomanage/create?ID=${rowData.ID}&&type=edit`);
}
if (btnConfig.actionType === "act_view") {
router.push(`/peomanage/create?ID=${rowData.ID}&&type=view`);
}
};
const handleTableBatchActions = ({ selectedRows, btnConfig, fatherData }) => {
state.selectedRows = selectedRows
if (btnConfig.actionType === "peo-create") {
handleCreatePeo();
}
if (btnConfig.actionType === "peo-pass-set") {
handleResetPass();
}
};
const goToIndex = () => {
router.push("/peomanage");
};
const handleCreatePeo = () => {
router.push("/peomanage/create");
};
const handleResetPass = () => {
state.btnLoading = true;
let url = "/user/v2/reset/pwd";
let params = {
IDs: state.selectedRows.map((item) => item.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.selectedRows = [];
state.tableConfig.refreshCount++;
processSuccess("操作成功!");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
const handleDeleteBatch = () => {
state.dialogData.type = "batch";
state.dialogDel = true;
};
const handleDeleteBatchSave = () => {
state.btnLoading = true;
let url = "/user/v2/batch/remove";
let params = {
IDs: state.selectedRows.map((item) => item.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.selectedRows = [];
state.tableConfig.refreshCount++;
processSuccess("操作成功!");
state.dialogDel = false;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
state.dialogDel = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
state.dialogDel = false;
}
);
};
const handleDialogSave = () => {
if (state.dialogData.type === "batch") {
handleDeleteBatchSave();
return;
}
state.btnLoading = true;
let url = "/user/v2/remove";
let params = {
ID: state.dialogData.ID,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.tableConfig.refreshCount++;
state.dialogDel = false;
processSuccess("操作成功!");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
state.dialogDel = false;
},
() => {
state.dialogDel = false;
state.btnLoading = false;
processError("操作失败!");
}
);
};
const handleDialogBack = () => {
state.dialogDel = false;
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-select) {
color: #000;
}
:deep(.ant-select-selector .ant-select-selection-placeholder) {
color: #c3c3c3 !important;
&::placeholder {
color: #c3c3c3 !important;
}
}
:deep(.ant-select:not(.ant-select-customize-input) .ant-select-selector) {
background: #fff;
}
}
</style>

View File

@ -0,0 +1,311 @@
<template>
<n-modal v-model:show="state.dialogModal" :mask-closable="false">
<n-card
style="width: 80vw"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
批量加入审批发起人
</div>
</div>
</template>
<template #header-extra>
<n-button @click="triggerCalcApproval" text style="font-size: 24px">
<n-icon>
<close-icon />
</n-icon>
</n-button>
</template>
<div class="row justify-center fl-pa-md" style="color: #333639">
<div class="row col-12 fl-mt-lg">
<div class="col-12">请选择审批类型</div>
<div class="col-12 fl-mt-md overflow-auto" style="height: 60vh">
<fln-table
:config="state.tableConfig"
:refreshCount="state.tableConfig.refreshCount"
@triggerSelectCheck="handleTableSelectCheck"
>
</fln-table>
</div>
</div>
<div class="col-12 row justify-center fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="triggerCalcApproval"
>上一步</n-button
>
<n-button
tertiary
style="width: 161px; background: #46299dff; color: #fff"
:disabled="state.selectedRows.length === 0"
@click="triggerCalcApproval('submit')"
>确定</n-button
>
</div>
</div>
</n-card>
</n-modal>
</template>
<script setup>
//
import {
UpOutlined,
DownOutlined,
CloseCircleOutlined,
PlusOutlined,
DragOutlined,
} from "@ant-design/icons-vue";
import flnFormItem from "@/components/flnlayout/form/flnformItem.vue";
import flAboutApproval from "./aboutApproval.vue";
import draggable from "vuedraggable";
import { Close as CloseIcon } from "@vicons/ionicons5";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
nextTick,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
import { ChevronDownOutline, ChevronUpOutline } from "@vicons/ionicons5";
import flnTable from "@/components/flnlayout/table/flntable.vue";
const emit = defineEmits(["triggerCalcApproval"]);
const state = reactive({
btnLoading: false,
dialogModal: true,
tableConfig: {
tableScrollWitdh: 600,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "GroupList",
url: "/approval/v2/only-group",
},
columns: [
{
title: "审批类型分组",
field: "Title",
},
{
title: "",
field: "",
},
{
title: "",
field: "",
},
{
title: "",
field: "",
},
{
title: "",
field: "",
},
{
title: "",
field: "",
},
],
expandConfig: {
defaultSelectedRows: [],
tableScrollWitdh: 600,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "TypeList",
url: "/approval/v2/type/all",
params: [
{
label: "domain",
field: "Domain",
},
{
label: "groupId",
field: "ID",
},
],
},
columns: [
{
type: "selection",
width: 50,
disabled(row) {
return row.Auths[0].UserID !== 0 || row.Auths[0].IsAll !== 0;
},
},
{
title: "流程名称",
field: "Title",
},
{
title: "发起人",
dataIndex: "Auths",
key: "Auths",
render(row) {
return h(
"div",
{
style:
"word-break:break-all;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;",
title: `${getAuths(row?.Auths)}`,
},
`${getAuths(row?.Auths)}`
);
},
},
{
title: "审批节点",
key: "ApprovalUsers",
dataIndex: "ApprovalUsers",
render(row) {
return h("div", {}, `${getApprovalUsers(row?.ApprovalUsers)}`);
},
},
{
title: "抄送",
dataIndex: "CopyUsers",
key: "CopyUsers",
render(row) {
return h(
"div",
{},
`${
Array.isArray(row.CopyUsers)
? row.CopyUsers.map((item) =>
item.ID !== 0
? item.Name
: `${item.DepartmentName}-${item.PositionName}`
).join(",")
: "-"
}`
);
},
},
{
title: "备注",
dataIndex: "Remark",
key: "Remark",
},
],
},
},
selectedRows: [],
});
onBeforeMount(() => {
state.dialogModal = true;
});
onMounted(() => {});
const getAuths = (auths) => {
if (auths) {
if (auths && auths.length === 1 && auths[0].IsAll === 1) {
return "全公司人员";
} else {
return auths
.filter((item) => item.IsAll === 0)
.map((item) =>
item.Name ? item.Name : item.DepartmentName + item.PositionName
)
.filter(Boolean)
.join("—");
}
}
};
const getApprovalUsers = (userList) => {
let user = "";
if (userList) {
user = userList
.filter((item) => item.DepartmentUID !== "undefined")
.map((item) => {
return {
flow:
item.IsDirect === 1 && item.IsDesignate === 1
? `直属第${item.Level}`
: item.IsDirect === 1
? `直属${item.Level}`
: item.Name
? item.Name
: item.DepartmentName + item.PositionName,
};
})
.map((item) => item.flow)
.join("—");
} else {
user = "";
}
return user;
};
const triggerCalcApproval = (type) => {
if (type === "submit") {
emit("triggerCalcApproval", state.selectedRows);
} else {
emit("triggerCalcApproval", []);
}
};
const handleTableSelectCheck = (data) => {
if (data && Array.isArray(data.selectedRow)) {
data.selectedRow = data.selectedRow.filter((item) => {
if (item) {
return item;
}
});
}
if (data.action === "check" || data.action === "checkAll") {
state.selectedRows = state.selectedRows.concat(data.selectedRow);
state.selectedRows = state.selectedRows.filter((item, index, arr) => {
return (
arr.findIndex((obj) => JSON.stringify(obj) === JSON.stringify(item)) ===
index
);
});
}
if (data.action === "uncheck") {
state.selectedRows = state.selectedRows.filter((item) => {
return item.ID !== data.selectedRow.ID;
});
}
if (data.action === "uncheckAll") {
state.selectedRows = state.selectedRows.filter((item) => {
return (
data.selectedRow.findIndex((obj) => {
return obj.ID === item.ID;
}) === -1
);
});
}
state.tableConfig.expandConfig.defaultSelectedRows = state.selectedRows;
};
</script>
<style lang="scss" scoped>
:deep(
.fln-table
.n-data-table-table
.n-checkbox.n-checkbox--disabled
.n-checkbox-box
) {
background-color: #d5d5d5 !important;
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<div class="fl-tree width-100 fl-mt-md">
<div class="row justify-end">
<div style="margin-right: 220px">是否为销售部门</div>
</div>
<n-tree
v-if="state.treeLoading"
block-line
:default-expanded-keys="state.expandedKeys"
:default-selected-keys="state.clickKey"
label-field="name"
key-field="key"
:expand-on-click="true"
:render-label="renderLabel"
:data="state.treeData"
@update:selected-keys="handleSelectTree"
/>
</div>
</template>
<script setup>
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
defineEmits,
watch,
nextTick,
} from "vue";
import treeLabel from "./treelabel.vue";
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
let props = defineProps({
data: Object,
refreshCount: Number,
config: Object,
expandedKeys: Array,
clickKey: [String, Number],
});
const state = reactive({
expandedKeys: [],
editTitle: "",
treeData: [],
clickKey: [],
treeLoading: true,
selectOptions: [],
});
watch(
() => props.refreshCount,
() => {
state.clickKey = [props.clickKey];
state.treeLoading = false;
nextTick(() => {
state.treeData = props.data;
calcDefaultConfig(state.treeData, 1);
state.treeLoading = true;
});
}
);
watch(
() => props.expandedKeys,
() => {
state.clickKey = [props.clickKey];
state.expandedKeys = props.expandedKeys;
},
{ deep: true }
);
onBeforeMount(() => {
state.clickKey = [props.clickKey];
state.treeData = props.data;
calcDefaultConfig(state.treeData, 1);
state.expandedKeys = state.treeData.map((item) => item.key);
});
onMounted(() => {
getSelectOptions();
});
const emit = defineEmits([
"triggerTreeAction",
"triggerTreeClick",
"triggerTreeDefaultClick",
]);
const handleSelectTree = (keys, option, meta) => {
if (keys.length === 1) {
emit("triggerTreeClick", { selectedKey: keys[0], tree: option[0] });
} else {
emit("triggerTreeDefaultClick");
}
};
const renderLabel = (option, checked) => {
return h(
treeLabel,
{
dataRef: option,
checked: checked,
config: props.config,
clickKey: props.clickKey,
options: state.selectOptions,
onTriggerTreeAction: handleTreeAction,
},
{}
);
};
const calcDefaultConfig = (data, level) => {
for (let item of data) {
if (!item.key) {
item.key = item.title + "_" + level;
}
item.edit = false;
if (item.children) {
calcDefaultConfig(item.children, level + 1);
}
}
};
const override = ({ option }) => {
if (option.children) {
return "toggleExpand";
}
return "default";
};
const handleTreeAction = ({ type, val }) => {
emit("triggerTreeAction", { type, val });
};
const getSelectOptions = () => {
let url = "department/head/list";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
let resData = res.data.list || [];
state.selectOptions = resData.filter((item) => item.alias);
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,240 @@
<template>
<div class="row items-center">
<div v-if="state.treeData.edit">
<n-input v-model:value="state.editTitle" style="width: 120px" />
</div>
<n-popover trigger="hover" v-else>
<template #trigger>
<div style="width: 120px" class="fl-px-sm sf-text-ellipsis">
{{ state.treeData.title }}
</div>
</template>
<div>{{ state.treeData.title }}</div>
</n-popover>
<div class="row" style="width: calc(100% - 120px)">
<div class="col-4 row items-center">
<n-icon
:component="CreateOutline"
class="fl-ml-sm"
size="20"
v-if="config?.actions.includes('edit') && !state.treeData.edit"
@click.stop="handleTreeEdit(state.treeData)"
/>
<n-icon
:component="Remove"
size="20"
v-if="
config?.actions.includes('subtraction') &&
!state.treeData.edit &&
visibleFormItem(config.subtractionShow, state.treeData)
"
class="fl-ml-sm"
@click.stop="handleTreeSubtraction(state.treeData)"
/>
<n-icon
:component="Add"
size="20"
v-if="
config?.actions.includes('add') &&
!state.treeData.edit &&
visibleFormItem(config.addShow, state.treeData)
"
class="fl-ml-sm"
@click.stop="handleTreeAdd(state.treeData)"
/>
<drag-outlined
v-if="
config?.actions.includes('move') &&
!state.treeData.edit &&
visibleFormItem(config.moveShow, state.treeData)
"
class="fl-ml-sm"
@click.stop="handleTreeMove(state.treeData)"
/>
<!-- <n-icon :component="MoveOutline"
size="20"
v-if="config?.actions.includes('move')&&!state.treeData.edit&&visibleFormItem(config.moveShow, state.treeData)"
class="fl-ml-sm"
@click.stop="handleTreeMove(state.treeData)" /> -->
<n-icon
:component="Checkmark"
size="20"
v-if="state.treeData.edit"
class="fl-ml-sm"
@click.stop="handleTreeSave(state.treeData)"
/>
<n-icon
:component="Close"
size="20"
v-if="state.treeData.edit"
class="fl-ml-md"
@click.stop="handleTreeNotSave(state.treeData)"
/>
</div>
<div class="col-8 row items-center justify-end" @click.stop>
<div>
<n-radio-group
v-model:value="state.radioVal"
@update:value="handleRadioChange"
>
<n-space>
<n-radio :value="true"> </n-radio>
<n-radio :value="false"> </n-radio>
</n-space>
</n-radio-group>
</div>
<div class="fl-ml-md" style="width: 200px" @click="handleSelectClick">
<n-select
v-model:value="state.selectVal"
size="small"
filterable
clearable
placeholder="请选择对应部门"
label-field="alias"
value-field="storeId"
:options="options"
@update:value="handleSelectChange"
/>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onBeforeMount, onMounted, watch, getCurrentInstance } from "vue";
import { visibleFormItem } from "@/utils/helper/form";
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
import {
UpOutlined,
DownOutlined,
CloseCircleOutlined,
PlusOutlined,
DragOutlined,
} from "@ant-design/icons-vue";
import {
Add,
Checkmark,
Close,
CreateOutline,
Remove,
MoveOutline,
} from "@vicons/ionicons5";
let props = defineProps({
dataRef: Object,
checked: Boolean,
config: Object,
clickKey: [String, Number],
options: Array,
});
const state = reactive({
expandedKeys: [],
editTitle: "",
treeData: [],
radioVal: null,
selectVal: null,
});
onBeforeMount(() => {
state.treeData = props.dataRef.option;
state.radioVal = state.treeData.sync;
if (state.treeData.syncId) {
state.selectVal = state.treeData.syncId * 1;
}
});
watch(
() => props.dataRef.option,
(val) => {
state.treeData = props.dataRef.option;
},
{ deep: true }
);
onMounted(() => {});
const emit = defineEmits(["triggerTreeAction", "triggerTreeClick"]);
// const myComponentRef = ref(null);
const handleTreeEdit = () => {
state.editTitle = state.treeData.title;
state.treeData.edit = true;
// myComponentRef.value.$forceUpdate();
};
const handleTreeAdd = () => {
emit("triggerTreeAction", { type: "add", val: state.treeData });
};
const handleTreeMove = () => {
emit("triggerTreeAction", { type: "move", val: state.treeData });
};
const handleTreeSubtraction = () => {
emit("triggerTreeAction", { type: "subtraction", val: state.treeData });
};
const handleTreeSave = () => {
state.treeData.title = state.editTitle;
emit("triggerTreeAction", { type: "save", val: state.treeData });
};
const handleTreeNotSave = () => {
state.editTitle = "";
emit("triggerTreeAction", { type: "cancel", val: state.treeData });
};
const handleRadioChange = (val) => {
let url = "department/v2/update";
let params = {
ID: state.treeData.key,
name: state.treeData.title,
sync: val,
syncID: state.treeData.syncId,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
state.treeData.sync = val;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
},
() => {
processError("操作失败!");
}
);
};
const handleSelectChange = (val) => {
let syncID = "";
if (val === null || val === undefined || val === "") {
syncID = "";
} else {
syncID = val + "";
}
let url = "department/v2/update";
let params = {
ID: state.treeData.key,
name: state.treeData.title,
sync: state.treeData.sync,
syncID: syncID,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
state.treeData.syncId = syncID;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
},
() => {
processError("操作失败!");
}
);
};
const handleSelectClick = () => {
console.log(props.options);
};
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,942 @@
<template>
<n-modal v-model:show="state.dialogModal" :mask-closable="false">
<n-card
style="width: 1050px"
:bordered="false"
role="dialog"
aria-modal="true"
>
<template #header>
<div class="col-12 row justify-center relative">
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
新增岗位
</div>
</div>
</template>
<template #header-extra>
<n-button @click="handleDialogBack" text style="font-size: 24px">
<n-icon>
<close-icon />
</n-icon>
</n-button>
</template>
<div class="row justify-center fl-pb-md" style="color: #333639">
<div class="col-12 row">
<div class="search-item fl-mb-sm row items-center">
<div>*岗位名</div>
<fln-form-item
style="width: 180px; margin-left: 45px"
type="text"
:config="{
placeholder: '请输入岗位名',
field: 'name',
}"
:val="state.formData.name"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mb-sm row items-center">
<div style="margin-left: 16px">岗位描述</div>
<fln-form-item
style="width: 180px; margin-left: 38px"
type="text"
:config="{
placeholder: '请输入岗位描述',
field: 'remark',
}"
:val="state.formData.remark"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mb-sm row items-center">
<div style="margin-left: 16px">标签颜色</div>
<fln-form-item
style="width: 122px; margin-left: 38px"
type="colorpicker"
:val="state.formData.color"
:config="{
field: 'color',
}"
@triggerValChange="handleValChange"
/>
</div>
<div class="search-item fl-mb-sm row items-center">
<fln-form-item
style="margin-left: 38px"
type="checkbox"
:val="state.formData.isOwner"
:config="{
field: 'isOwner',
options: [
{
label: '设为部门负责人',
value: 'true',
},
],
}"
@triggerValChange="handleValChange"
/>
</div>
</div>
<div class="row col-12 fl-mt-sm">
<div class="col-12 row justify-between fl-mt-md fl-mb-sm">
<div>请选择以下模板进行新增岗位</div>
<div>
可将该岗位模板加入到审批中<span
class="cursor"
style="color: #6c00ff"
@click="state.dialogApproval = true"
>去试试</span
>
</div>
<fl-about-approval
:show="state.dialogApproval"
@triggerCalcApproval="handleApproval"
/>
</div>
<div>
<div class="col-12 row">
<div
v-for="(temp, tempidx) in state.tempList"
:key="tempidx"
class="fl-py-xs text-center cursor fl-mr-sm fl-mb-sm row justify-center items-center"
:style="[
state.selectTemplate === temp.name
? { border: '1px solid #46299d' }
: {},
]"
style="
border-radius: 3px;
min-width: 176px;
background: #f7efff;
color: #8352b4;
"
@click="handleTempClick(temp)"
>
{{ temp.name }}
</div>
</div>
</div>
</div>
<div class="fl-my-lg row col-12">
<div class="col-2 font-14 fl-mb-md">已选模版</div>
<div class="col-10 row items-center fl-mb-md">
<div
class="fl-py-xs text-center row justify-center items-center"
style="
border-radius: 3px;
min-width: 193px;
background: #f7f7f7ff;
border: solid 1px #e0e0e5ff;
"
>
{{ state.selectTemplate }}
</div>
</div>
<div class="col-2 font-14">
*所选页面
<span style="color: #1f2225ff">{{
state.selectedRows.length
}}</span>
</div>
<n-spin
class="fl-ml-md"
v-if="state.dataLoading"
v-show="state.dataLoading"
description="获取中..."
style="color: #1f2225ff"
/>
<div v-else class="col-10 row">
<div
v-for="(select, idx) in state.selectedRows"
:key="idx"
class="fl-py-xs text-center fl-mr-sm fl-mb-sm row justify-center items-center"
style="
border-radius: 3px;
min-width: 193px;
background: #f7f7f7ff;
border: solid 1px #e0e0e5ff;
"
>
<n-tooltip trigger="hover">
<template #trigger>
{{ select.name }}
</template>
{{ select.name }}
</n-tooltip>
<close-circle-outlined
class="fl-ml-md"
@click="handleClearPage(select)"
/>
</div>
<div class="row items-center fl-mr-sm fl-mb-sm">
<div
style="
border-radius: 50%;
background: #e0e0e5;
width: 27px;
height: 27px;
"
class="row items-center justify-center fl-mr-sm cursor"
@click="handleEditSelectTemp"
>
<n-icon>
<ChevronUpOutline v-if="state.showTempRuleListData" />
<ChevronDownOutline v-else />
</n-icon>
</div>
<span style="color: rgba(0, 0, 0, 0.3)">点击展开可进行编辑</span>
</div>
</div>
</div>
<div
v-if="state.showTempRuleListData"
style="max-height: 40vh"
class="overflow-auto col-12 row"
>
<div
v-for="(row, rowIdx) in state.listData"
:key="rowIdx"
class="col-12 fl-mb-md row items-center fl-pb-xs"
style="min-height: 40px"
>
<div
class="col-12 row justify-between items-center fl-px-md"
style="min-height: 40px; background: #46299dff"
:style="{ borderRadius: row.showChild ? '3px 3px 0 0' : '3px' }"
>
<div class="color-white">
<n-checkbox
v-model:checked="row.selected"
@update:checked="(e) => handlePageSelectChange(e)"
>
</n-checkbox>
{{ row.name }}
</div>
<n-button
tertiary
v-if="!row.showChild"
style="background: #ffffffff; color: #46299dff"
icon-placement="right"
round
secondary
strong
size="small"
@click="row.showChild = true"
>
<template #icon>
<n-icon>
<ChevronDownOutline />
</n-icon>
</template>
展开
</n-button>
<n-button
tertiary
v-if="row.showChild"
style="background: #ffffffff; color: #46299dff"
icon-placement="right"
round
secondary
strong
size="small"
@click="row.showChild = false"
>
<template #icon>
<n-icon>
<ChevronUpOutline />
</n-icon>
</template>
收起
</n-button>
</div>
<div
v-if="row.showChild"
style="border: 1px solid #1f2225ff; border-radius: 0 0 3px 3px"
class="col-12 row"
>
<div
class="col-12 row"
style="padding: 11px 0; background: #dfd7f2"
>
<div class="col-3 text-center">按钮权限</div>
<div class="col-3 text-center">列表权限</div>
<div class="col-3 text-center">数据范围权限</div>
<div class="col-3 text-center">列表字段权限</div>
</div>
<div class="col-12 row">
<div class="col-3 fl-py-md">
<div class="" style="margin-left: 30%">
<div
v-if="
Array.isArray(row.buttonList) &&
row.buttonList.length > 1
"
class="row"
>
<n-checkbox
v-model:checked="row.btnSelectedAll"
:indeterminate="row.btnIndeterminate"
@update:checked="
(e) => handleBtnSelectAllChange(e, rowIdx)
"
>
全选
</n-checkbox>
</div>
<n-checkbox-group
v-model:value="row.btnSelected"
@update:value="(e) => handleBtnSelectChange(e, rowIdx)"
>
<div
class="col-12"
v-for="(btn, btnIdx) in row.buttonList"
:key="btnIdx"
>
<n-checkbox :value="btn.ID">
{{ btn.name }}
</n-checkbox>
</div>
</n-checkbox-group>
</div>
</div>
<div class="col-9 row">
<div
v-for="(list, listIdx) in row.interfaceList"
:key="listIdx"
class="col-12 row"
:style="{
'border-left':
row.interfaceList.length > 1 ? '1px solid #C1B2E5' : '',
'border-bottom':
listIdx !== row.interfaceList.length - 1
? '1px solid #C1B2E5'
: '',
}"
>
<div class="col-4 row fl-pa-md justify-center">
<n-checkbox
v-model:checked="list.selected"
@update:checked="
(e) => handleListSelectChange(e, rowIdx, listIdx)
"
>
{{ list.name }}
</n-checkbox>
</div>
<div class="col-4 row fl-pa-md justify-center">
<n-radio-group v-model:value="list.limitSelected">
<n-radio
:style="radioStyle"
v-for="(limit, limitIdx) in list.limit"
:key="limitIdx"
:value="limit.value"
>
<div class="relative">
{{ limit.label }}
<n-button
tertiary
v-if="
list.limitSelected === 5 && limit.value === 5
"
size="small"
style="
position: absolute;
right: -120px;
width: 70px;
background: #ffffff;
color: #6d5c9c;
border: 1px solid #8a67ef;
"
@click.stop="
handleChangeSelfLimitData(rowIdx, listIdx, list)
"
>设置</n-button
>
</div>
</n-radio>
</n-radio-group>
</div>
<div class="col-4 row fl-pa-md justify-center">
<n-checkbox-group
v-if="Array.isArray(list.positionRuleFields)"
v-model:value="list.fieldsSelected"
>
<div class="col-12">
<n-button
tertiary
class="fl-ml-md"
size="small"
style="
width: 70px;
background: #ffffff;
color: #6d5c9c;
border: 1px solid #8a67ef;
"
@click="handleEditField(rowIdx, listIdx)"
>设置</n-button
>
<n-button
tertiary
class="fl-ml-md"
size="small"
style="
width: 70px;
background: #ffffff;
color: #6d5c9c;
border: 1px solid #8a67ef;
"
@click="handleEditFieldSort(rowIdx, listIdx)"
>排序</n-button
>
</div>
<div
class="col-12"
v-for="(field, fieldIdx) in list.positionRuleFields"
:key="fieldIdx"
>
<n-checkbox :value="field.ID">
{{ field.fieldCnName }}
</n-checkbox>
</div>
</n-checkbox-group>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 row justify-center fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="handleDialogBack"
>返回</n-button
>
<n-button
tertiary
style="width: 161px; background: #46299dff; color: #fff"
:loading="state.btnLoading"
@click="handleDialogSubmit"
>保存</n-button
>
</div>
</div>
</n-card>
</n-modal>
<fl-limit-data
v-if="state.dialogSelfLimtiData"
:fatherData="state.dataDepartmentIds"
@triggerLimitData="handleLimitData"
/>
</template>
<script setup>
//
import {
UpOutlined,
DownOutlined,
CloseCircleOutlined,
PlusOutlined,
DragOutlined,
} from "@ant-design/icons-vue";
import { Close as CloseIcon } from "@vicons/ionicons5";
import flnFormItem from "@/components/flnlayout/form/flnformItem.vue";
import flAboutApproval from "./aboutApproval.vue";
import draggable from "vuedraggable";
import flLimitData from "./limitData.vue";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
nextTick,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
import { ChevronDownOutline, ChevronUpOutline } from "@vicons/ionicons5";
import { Local } from "@/utils/storage.js";
const emits = defineEmits(["triggerClose"]);
const radioStyle = reactive({
display: "flex",
height: "30px",
lineHeight: "30px",
});
let props = defineProps({
fatherData: {
type: Object,
},
});
const state = reactive({
selectedRows: [],
dialogField: false,
dialogData: {
fieldList: [],
},
listData: [],
formData: {
name: "",
remark: "",
color: "#1F2225",
isOwner: [],
},
btnLoading: false,
pageType: "create",
enabled: true,
dialogFieldSort: false,
dataLoading: false,
dialogModal: true,
tempList: [],
selectTemplate: "",
showTempRuleListData: false,
dialogApproval: false,
dialogSelfLimtiData: false,
dialogLimitData: {},
dataDepartmentIds: [],
approvalData: [],
});
onBeforeMount(() => {
console.log("onBeforeMount");
state.dialogModal = true;
getTempData();
if (route.query.type) {
state.pageType = route.query.type;
}
});
onMounted(() => {});
const getTempData = () => {
let url = "/position/v2/model/list";
$request.HTTP.components.postDataByParams(url, {}).then(
(res) => {
if (res.status === 0) {
state.tempList = res.data || [];
state.selectTemplate = state.tempList[0].name || "无数据";
if (state.tempList.length > 0) {
getRulesData(state.tempList[0]);
}
} else {
processError(res.msg || "获取数据失败!");
}
},
() => {
processError("获取数据失败!");
},
() => {
processError("获取数据失败!");
}
);
};
const getRulesData = (temp) => {
let url = "/position/v2/model/detail";
$request.HTTP.components.postDataByParams(url, { uuid: temp.uuid }).then(
(res) => {
if (res.status === 0) {
let data = res.data.positionTreeRule;
data.map((item) => {
item.selected = false;
item.showChild = false;
item.btnSelected = [];
item.btnSelectedAll = false;
item.btnIndeterminate = false;
if (Array.isArray(item.interfaceList)) {
item.interfaceList.map((listItem) => {
listItem.selected = false;
listItem.limit = [
{
label: "仅自己",
value: 1,
},
{
label: "自己及下属",
value: 2,
},
{
label: "本部门",
value: 3,
},
{
label: "所有",
value: 4,
},
];
listItem.limitSelected = 4;
listItem.positionRuleFields = listItem.positionRuleFields || [];
listItem.fieldsSelected = listItem.positionRuleFields.map(
(item) => item.ID
);
});
}
});
state.listData = data;
setRulesDataDefaultVal(data, state.listData);
state.selectedRows = state.listData.filter((item) => item.selected);
} else {
processError(res.msg || "获取数据失败!");
}
},
() => {
processError("获取数据失败!");
},
() => {
processError("获取数据失败!");
}
);
};
const handleValChange = ({ val, config }) => {
state.formData[config.field] = val;
};
const handleDialogSubmit = () => {
if (state.formData.name === "") {
processError("请填写岗位名称");
return;
}
let url = "/position/v2/create";
let params = {
name: state.formData.name,
remark: state.formData.remark,
color: state.formData.color,
positionTreeRule: [],
departmentID: Number(props.fatherData.key),
isLeader: state.formData.isOwner.length > 0,
};
let data = state.listData;
data
.filter((item) => item.selected)
.map((item) => {
let obj = {
ID: item.ID,
buttonList: Array.isArray(item.buttonList)
? item.buttonList.filter((btnItem) =>
item.btnSelected.includes(btnItem.ID)
)
: [],
interfaceList: Array.isArray(item.interfaceList)
? item.interfaceList
.filter((listItem) => listItem.selected)
.map((listItem) => {
return {
ID: listItem.ID,
dataRange: listItem.limitSelected,
positionRuleFields: Array.isArray(listItem.positionRuleFields)
? listItem.positionRuleFields.filter((fieldItem) =>
listItem.fieldsSelected.includes(fieldItem.ID)
)
: [],
};
})
: [],
};
params.positionTreeRule.push(obj);
});
state.btnLoading = true;
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
//
if (state.approvalData.length > 0) {
let url = "/approval/v2/create-auth";
let params = {
typeId: state.approvalData.map((item) => item.ID),
departmentUID: res.data.DepartmentID.toString(),
departmentName: res.data.DepartmentName,
positionUID: res.data.ID.toString(),
positionName: res.data.Name,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
processSuccess("操作成功!");
emits("triggerClose");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
state.btnLoading = false;
processError("操作失败!");
},
() => {
processError("操作失败!");
}
);
} else {
state.btnLoading = false;
processSuccess("操作成功!");
emits("triggerClose");
}
} else {
state.btnLoading = false;
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
const handleBack = () => {
router.go(-1);
};
const handleEditField = (rowIdx, listIdx) => {
state.dialogData.clickData = state.listData[rowIdx];
state.dialogData.ID = state.listData[rowIdx].interfaceList[listIdx].ID;
state.dialogData.rowIdx = rowIdx;
state.dialogData.listIdx = listIdx;
state.dialogData.fieldList = JSON.parse(
JSON.stringify(
state.listData[rowIdx].interfaceList[listIdx].positionRuleFields.map(
(item) => {
return {
ID: item.ID,
field: item.fieldKey,
fieldCnName: item.fieldCnName,
};
}
)
)
);
state.dialogField = true;
};
const handleBtnSelectAllChange = (checked, idx) => {
if (checked) {
state.listData[idx].btnSelected = state.listData[idx].buttonList.map(
(item) => item.ID
);
state.listData[idx].btnIndeterminate = false;
} else {
state.listData[idx].btnSelected = [];
state.listData[idx].btnIndeterminate = false;
}
};
const handleBtnSelectChange = (e, idx) => {
if (e.length === 0) {
state.listData[idx].btnSelectedAll = false;
state.listData[idx].btnIndeterminate = false;
} else if (e.length === state.listData[idx].buttonList.length) {
state.listData[idx].btnSelectedAll = true;
state.listData[idx].btnIndeterminate = false;
} else {
state.listData[idx].btnSelectedAll = false;
state.listData[idx].btnIndeterminate = true;
}
};
const handleListSelectChange = (e, rowIdx, listIdx) => {
state.listData[rowIdx].listSelected = state.listData[
rowIdx
].interfaceList.filter((item) => item.selected);
};
const handlePageSelectChange = (e) => {
state.selectedRows = state.listData.filter((item) => item.selected);
};
const handleClearPage = (select) => {
state.listData.forEach((item) => {
if (item.name === select.name) {
item.selected = false;
}
});
state.selectedRows = state.listData.filter((item) => item.selected);
};
const handleDelDialogFieldList = (delIdx) => {
state.dialogData.fieldList.splice(delIdx, 1);
};
const handleCreDialogFieldList = () => {
state.dialogData.fieldList.push({
field: "",
fieldCnName: "",
});
};
const handleDialogBack = () => {
state.dialogField = false;
emits("triggerClose");
};
const handleLink = (path) => {
router.push(path);
};
const setRulesDataDefaultVal = (pageData, listData) => {
pageData.map((pageDataItem) => {
listData.map((listItem) => {
if (pageDataItem.ID === listItem.ID) {
listItem.selected = true;
listItem.showChild = false;
listItem.btnSelected = Array.isArray(pageDataItem.buttonList)
? pageDataItem.buttonList.map((btnItem) => btnItem.ID)
: [];
if (
Array.isArray(listItem.interfaceList) &&
Array.isArray(pageDataItem.interfaceList)
) {
listItem.interfaceList.map((interfaceItem) => {
pageDataItem.interfaceList.map((pageDataInterfaceItem) => {
if (interfaceItem.ID === pageDataInterfaceItem.ID) {
interfaceItem.selected = true;
interfaceItem.limitSelected = pageDataInterfaceItem.dataRange;
pageDataInterfaceItem.positionRuleFields =
pageDataInterfaceItem.positionRuleFields || [];
interfaceItem.fieldsSelected =
pageDataInterfaceItem.positionRuleFields.map(
(fieldItem) => fieldItem.ID
);
interfaceItem.positionRuleFields.map((fieldItem) => {
pageDataInterfaceItem.positionRuleFields.map(
(pageDataFieldItem) => {
if (fieldItem.ID === pageDataFieldItem.ID) {
fieldItem.index = pageDataFieldItem.index || 0;
}
}
);
});
}
});
});
}
}
});
});
};
const handleEditFieldSort = (rowIdx, listIdx) => {
state.dialogData.clickData = state.listData[rowIdx];
state.dialogData.ID = state.listData[rowIdx].interfaceList[listIdx].ID;
state.dialogData.rowIdx = rowIdx;
state.dialogData.listIdx = listIdx;
let fieldsSelected =
state.listData[rowIdx].interfaceList[listIdx].fieldsSelected;
let fieldList = [];
state.listData[rowIdx].interfaceList[listIdx].positionRuleFields.map(
(item) => {
if (fieldsSelected.includes(item.ID)) {
fieldList.push({
ID: item.ID,
field: item.fieldKey,
fieldCnName: item.fieldCnName,
index: item.index || 0,
});
}
}
);
fieldList.sort((a, b) => {
return a.index - b.index;
});
state.dialogData.fieldList = fieldList;
state.dialogFieldSort = true;
};
const handleDialogSortSave = () => {
let rowIdx = state.dialogData.rowIdx;
let listIdx = state.dialogData.listIdx;
state.dialogData.fieldList.forEach((item, idx) => {
item.index = idx + 1;
});
state.listData[rowIdx].interfaceList[listIdx].positionRuleFields.map(
(item) => {
state.dialogData.fieldList.map((fieldItem) => {
if (item.ID === fieldItem.ID) {
item.index = fieldItem.index;
}
});
}
);
state.dialogFieldSort = false;
};
const handleDialogSortBack = () => {
state.dialogFieldSort = false;
};
const handleTempClick = (temp) => {
state.selectTemplate = temp.name;
getRulesData(temp);
};
const handleEditSelectTemp = () => {
state.showTempRuleListData = !state.showTempRuleListData;
};
const handleApproval = (approvalData) => {
state.approvalData = approvalData;
state.dialogApproval = false;
};
const handleChangeSelfLimitData = (rowIdx, listIdx, list) => {
state.dialogLimitData = {
rowIdx,
listIdx,
};
state.dataDepartmentIds = list.dataDepartmentIds;
state.dialogSelfLimtiData = true;
};
const handleLimitData = (data) => {
let rowIdx = state.dialogLimitData.rowIdx;
let listIdx = state.dialogLimitData.listIdx;
state.listData[rowIdx].interfaceList[listIdx].dataDepartmentIds = data;
state.dialogSelfLimtiData = false;
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
}
.n-card-header {
}
</style>

View File

@ -0,0 +1,628 @@
<template>
<div ref="posimanageeditAuth"></div>
<n-modal v-model:show="state.showModal" :mask-closable="false">
<n-card
style="width: 80vw"
:bordered="false"
class="create-contianer"
size="huge"
width="1200px"
role="dialog"
aria-modal="true"
>
<template #header>
<div class="title-visible">{{ props.title }}</div>
</template>
<template #header-extra>
<img
src="@/assets/image/icon/close-tabs.png"
class="close-icon"
@click="handleClose"
/>
</template>
<div class="center-content">
<div class="left-box">
<div class="title">基础设置</div>
<div class="content" ref="modalSelect">
<a-form
ref="formRef"
name="custom-validation"
:model="formState"
:rules="rules"
v-bind="layout"
:hideRequiredMark="true"
>
<a-form-item label="权限类型" name="type">
<a-radio-group
v-model:value="formState.type"
@change="changeType"
name="radioGroup"
>
<a-radio :value="'menu'">菜单权限</a-radio>
<a-radio :value="'button'">按钮权限</a-radio>
<a-radio :value="'interface'">列表字段权限</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="菜单管理"
name="menuType"
v-if="formState.type == 'menu'"
>
<a-radio-group
v-model:value="formState.menuType"
name="radioGroup"
>
<a-radio :value="'1'">父级菜单</a-radio>
<a-radio :value="'2'">子级菜单</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
label="上级菜单"
name="pid"
v-if="formState.menuType == '2'"
>
<n-select
v-model:value="formState.pid"
:options="state.menuList"
label-field="Title"
value-field="ID"
filterable
/>
<!-- <a-select ref="select"
v-model:value="formState.pid"
style="width: 200px">
<a-select-option v-for="(item,index) in state.menuList"
:key="index"
:value="item.ID">{{item.Title}}</a-select-option>
</a-select> -->
</a-form-item>
<a-form-item label="权限名称" name="title">
<n-input
v-model:value="formState.title"
autocomplete="off"
maxLength="12"
placeholder="最长输入12个字符"
/>
</a-form-item>
<a-form-item
label="上级权限"
name="pid"
v-if="formState.type !== 'menu'"
>
<n-tree-select
filterable
v-model:value="formState.pid"
:options="state.menuTreeList"
@update:value="handleUpdatemenuTreeValue"
/>
</a-form-item>
<a-form-item label="路由/按钮名/接口名" name="url">
<n-input
v-model:value="formState.url"
autocomplete="off"
:placeholder="
formState.type == 'menu' ? '不做路由跳转可为空' : ''
"
/>
</a-form-item>
<a-form-item
label="图标"
name="icon"
v-if="formState.type == 'menu' && formState.menuType == '1'"
>
<Upload
:maxCount="1"
v-model:uploadVal="formState.icon"
:zIndex="9000"
class="upload"
/>
</a-form-item>
<a-form-item
label="选中图标"
name="grayIcon"
v-if="formState.type == 'menu' && formState.menuType == '1'"
>
<Upload
:maxCount="1"
v-model:uploadVal="formState.grayIcon"
:zIndex="9000"
class="upload"
/>
</a-form-item>
<a-form-item
label="排序"
name="weigh"
v-if="formState.type == 'menu'"
>
<n-input-number
:show-button="false"
v-model:value="formState.weigh"
autocomplete="off"
placeholder="1-100,越大排序越靠前"
/>
</a-form-item>
</a-form>
<div v-show="formState.type == 'interface'" class="row">
<div class="col-4 text-right">
列表字段<span style="margin: 0 8px 0 2px">:</span>
</div>
<div class="col-8">
<div
v-for="(item, index) in formState.ruleFields"
:key="index"
style="margin-bottom: 10px"
>
<div class="flex">
<n-input
v-model:value="item.fieldCnName"
autocomplete="off"
placeholder="列表字段中文名称"
style="margin-bottom: 10px; width: 200px"
/>
<span
class="cancel-btn"
@click="cancelRule(index)"
v-if="formState.ruleFields.length > 1"
>移除</span
>
</div>
<n-input
type="textarea"
v-model:value="item.fieldKey"
placeholder="列表字段对应value值"
:autosize="{ minRows: 2, maxRows: 5 }"
/>
</div>
</div>
<div class="col-4 row"></div>
<div class="col-8 row">
<a-button @click="addInput" style="width: 120px">
<PlusOutlined />
</a-button>
</div>
</div>
</div>
</div>
</div>
<template #footer>
<div class="row justify-end">
<div class="footer-content" v-if="state.isEdit">
<!-- <a-popconfirm title="是否确认删除该权限,删除后不可恢复?"
cancel-text="取消"
ok-text="确定"
@confirm="handleDel"
@cancel="departmentCel">
<a-button class="basic-btn">删除该权限</a-button>
</a-popconfirm> -->
<a-button
class="primary-btn"
:loading="state.btnLoading"
@click="handleUpdate"
>完成修改</a-button
>
</div>
<div class="footer-content" v-else>
<a-button class="basic-btn" @click="resetForm">清空</a-button>
<a-button
class="primary-btn"
:loading="state.btnLoading"
@click="handleCreate"
>完成设置</a-button
>
</div>
</div>
</template>
</n-card>
</n-modal>
</template>
<script setup>
//
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons-vue";
import {
defineProps,
defineEmits,
watch,
reactive,
ref,
unref,
onMounted,
getCurrentInstance,
} from "vue";
import Upload from "@/components/upload.vue";
import { Form, message } from "ant-design-vue";
import { processSuccess, processError } from "@/utils/helper/message";
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const emits = defineEmits(["submit", "triggerClose"]);
let props = defineProps({
title: {
type: String,
require: true,
},
data: {
type: Object,
default: () => {},
},
});
const state = reactive({
btnLoading: false,
menuList: [],
menuTreeList: [],
isEdit: false,
showModal: true,
});
onMounted(() => {
state.showModal = true;
getMenu();
getMenuTree();
if (props.data?.id) {
state.isEdit = true;
getDetail();
} else {
state.isEdit = false;
formState.dataOpen = true;
formState.method = "*";
formState.type = "";
formState.icon = "";
formState.pid = null;
formState.title = "";
formState.menuType = "";
formState.url = "";
formState.weigh = "";
formState.ruleFields = [{ fieldKey: "", fieldCnName: "" }];
}
});
const handleClose = () => {
emits("triggerClose");
resetForm();
state.isEdit = false;
};
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const formRef = ref();
const formState = reactive({
grayIcon: "",
dataOpen: true,
method: "*",
type: "", // 'menu','button','interface'
icon: "",
pid: null,
title: "",
menuType: "",
url: "",
weigh: "",
ruleFields: [{ fieldKey: "", fieldCnName: "" }],
});
const filedJsonV = async (_rule, value) => {
//
if (
formState.type === "interface" &&
formState.ruleFields.some((val) => val.fieldKey == "" || val.fieldKey == "")
) {
return Promise.reject("请补充完整");
} else {
return Promise.resolve();
}
};
const urlV = async (_rule, value) => {
//
if (formState.type === "menu" && formState.menuType === "1") {
return Promise.resolve();
} else {
if (value == "") {
return Promise.reject("请输入路由/唯一标识");
} else {
return Promise.resolve();
}
}
};
const rules = {
type: [{ required: true, message: "请选择权限类型", trigger: "change" }],
icon: [{ required: true, message: "请上传Icon", trigger: "change" }],
grayIcon: [{ required: true, message: "请上传选中Icon", trigger: "change" }],
menuType: [{ required: true, message: "请选择菜单等级", trigger: "change" }],
pid: [
{ required: true, message: "请选择上级权限/上级菜单", trigger: "change" },
],
title: [{ required: true, message: "请输入权限名称", trigger: "change" }],
url: [{ required: true, validator: urlV, trigger: "change" }], // message: '/',
weigh: [{ required: true, message: "请输入排序", trigger: "change" }],
ruleFields: [
{
required: true,
validator: filedJsonV,
trigger: ["blur, change"],
type: "array",
},
],
};
const resetForm = () => {
formRef.value.resetFields();
};
const addInput = () => {
formState.ruleFields.push({ fieldKey: "", fieldCnName: "" });
};
const cancelRule = (index) => {
formState.ruleFields.splice(index, 1);
};
const changeType = () => {
formState.dataOpen = true;
formState.method = "*";
// formState.type = ''
formState.icon = "";
formState.pid = null;
formState.title = "";
formState.url = "";
formState.weigh = "";
formState.menuType = "";
formState.ruleFields = [{ fieldKey: "", fieldCnName: "" }];
};
const handleCreate = () => {
formRef.value
.validate()
.then(() => {
formState.pid = formState.pid ? Number(formState.pid) : 0;
formState.weigh = formState.weigh ? Number(formState.weigh) : 1;
state.btnLoading = true;
let url = "/rule/create";
let params = formState;
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
processSuccess("添加成功");
handleClose();
emits("triggerClose");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
state.btnLoading = false;
processError("操作失败!");
}
);
})
.catch((error) => {});
};
const handleDel = () => {
$request.HTTP.permission
.ruleRemove({ ID: formState.ID })
.then((res) => {
if (res.status == 0) {
message.success({
content: "删除成功",
duration: 2,
onClose() {
handleClose();
emits("triggerClose");
},
});
} else {
message.error(res.msg);
}
})
.catch((e) => {
message.error(e.response?.data?.msg || "操作失败,请稍后再试");
});
};
const departmentCel = () => {};
const handleUpdate = () => {
formRef.value
.validate()
.then(() => {
formState.pid = formState.pid ? Number(formState.pid) : 0;
formState.weigh = formState.weigh ? Number(formState.weigh) : 1;
state.btnLoading = true;
let url = "/rule/v2/update";
let params = formState;
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
message.success({
content: "修改成功",
duration: 2,
onClose() {
handleClose();
emits("triggerClose");
},
});
} else {
processError(res.msg || "操作失败!");
}
},
() => {
state.btnLoading = false;
processError("操作失败!");
}
);
})
.catch(() => {});
};
const getMenu = () => {
$request.HTTP.permission
.menuFList()
.then((res) => {
if (res.status == 0) {
state.menuList = res.data.data;
} else {
message.error(res.msg);
}
})
.catch((e) => {
message.error(e.response?.data?.msg || "操作失败,请稍后再试");
});
};
const getMenuTree = () => {
$request.HTTP.permission
.menuSList()
.then((res) => {
if (res.status == 0) {
state.menuTreeList = res.data.data.map((item) => {
let obj = {
label: item.Title,
key: item.ID,
children: [],
};
if (item.Son) {
obj.children = item.Son.map((i) => {
return {
key: i.ID,
label: i.Title,
};
});
}
return obj;
});
} else {
message.error(res.msg);
}
})
.catch((e) => {
message.error(e.response?.data?.msg || "操作失败,请稍后再试");
});
};
const handleUpdatemenuTreeValue = (val) => {
formState.pid = val;
};
const getDetail = () => {
let url = "/rule/v2/detail";
let params = {
ID: props.data.id,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
formState.ID = res.data.ID;
formState.type = res.data.type;
formState.title = res.data.title;
formState.url = res.data.url;
if (res.data.type == "button") {
formState.pid = res.data.pid;
}
if (res.data.type == "menu") {
formState.icon = res.data.icon;
formState.grayIcon = res.data.grayIcon;
formState.weigh = res.data.weigh;
formState.pid = res.data.pid;
formState.menuType = res.data.menuType;
}
if (res.data.type == "interface") {
formState.pid = res.data.pid;
formState.ruleFields = res.data.ruleFields;
}
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
</script>
<style lang="scss" scoped>
.title-visible {
text-align: center;
color: $theme-primary;
font-size: 22px;
}
:deep(.ant-modal-body) {
padding: 0 24px;
}
.create-contianer {
.ant-input {
color: #6d5c9c;
}
.center-content {
height: 65vh;
display: flex;
.title {
width: fit-content;
padding-left: 10px;
border-left: 4px solid $theme-primary;
color: $text-theme;
margin: 19px 0;
}
.left-box {
width: 55%;
padding-right: 30px;
.content {
height: 54vh;
overflow-y: auto;
}
div {
color: $text-theme;
}
:deep(.ant-form-item-label > label) {
color: $text-theme;
}
.ant-form-item input[type="text"],
input,
textarea {
background: #e3dfea;
}
.ant-select :deep(.ant-select-selector) {
background: #e3dfea;
}
.control-item {
:deep(.ant-form-item-control) {
margin-left: 0px !important;
}
}
.flex {
display: flex;
justify-content: space-between;
}
.cancel-btn {
color: #999999;
cursor: pointer;
&:hover {
color: $text-theme;
}
}
}
}
.upload {
:deep(.ant-image) {
width: 100px;
height: 100px;
}
:deep(.ant-image-img) {
width: 100px;
height: 100px;
}
}
.footer-content {
margin: 10px 0;
button {
width: 130px;
height: 30px;
text-align: center;
line-height: 30px;
padding: 0;
margin: 0 10px;
border-radius: 20px;
}
}
}
</style>

View File

@ -0,0 +1,674 @@
<template>
<div class="row" style="padding: 35px">
<div
class="col-3 fl-pa-md"
style="
background: #fff;
box-shadow: rgba(188, 188, 188, 0.18) 0px 3px 6px 1px;
"
>
<div
class="row font-16"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5"
>
<div class="fl-py-sm" style="border-bottom: 4px solid #764cf6">
组织架构
</div>
</div>
<div class="row" style="height: 76vh; overflow: auto">
<fl-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fl-tree>
</div>
</div>
<div class="col-9 fl-px-md">
<div style="background: #fff" class="fl-pa-md">
<div
class="col-12 row font-18"
style="
color: #764cf6;
border-bottom: 1px solid #c1b2e5;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<div class="fl-py-sm" style="border-bottom: 4px solid #764cf6">
岗位管理
</div>
<div>
<n-button @click="goToOrgManage" v-permission="'org-manage-btn'">
组织管理
</n-button>
</div>
</div>
<div class="col-12 row fl-mt-sm">
<fln-table
:config="state.tableConfig"
:fatherData="state.treeSelectData"
:refreshCount="state.tableConfig.refreshCount"
@triggerRowActions="handleRowActions"
@triggerTableBatchActions="handleTableBatchActions"
>
</fln-table>
</div>
</div>
</div>
<div class="col-12 row fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #39109c; color: #fff"
@click="handlePermissMgm"
>权限列表</n-button
>
</div>
</div>
<n-modal
v-model:show="state.dialogDel"
style="width: 600px"
:mask-closable="false"
preset="card"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
删除
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div
v-if="state.dialogData.type === 'one'"
class="font-20"
style="color: #6d5c9c; margin: 80px 0"
>
确定删除该岗位吗
</div>
<div
v-if="state.dialogData.type === 'batch'"
class="font-20"
style="color: #6d5c9c; margin: 80px 0"
>
确定删除选中的岗位吗
</div>
<div class="col-12 row justify-center">
<n-button
tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="handleDialogBack"
>返回</n-button
>
<n-button
tertiary
style="width: 161px; background: #46299dff; color: #fff"
@click="handleDialogSave"
>确定</n-button
>
</div>
</div>
</n-modal>
<fl-posi-permiss-mgm
v-if="state.dialogPermissMgm"
@triggerClose="state.dialogPermissMgm = false"
/>
<orgManage
:orgVisible="state.org.orgVisible"
:title="'组织管理'"
@orgHandleOk="orgHandleOk"
></orgManage>
<fl-posi-dialog-create
v-if="state.dialogPosiCreate"
:fatherData="state.treeSelectData"
@triggerClose="handleDialogCreateClose"
></fl-posi-dialog-create>
<n-modal
v-model:show="state.dialogLinkerNum"
style="width: 800px"
:mask-closable="true"
preset="card"
>
<div class="row justify-center fl-pa-md">
<fln-table
:config="state.linkerNumConfig"
:fatherData="state.dialogData"
:refreshCount="state.linkerNumConfig.refreshCount"
>
</fln-table>
<div class="col-12 row justify-center fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="state.dialogLinkerNum = false"
>关闭</n-button
>
</div>
</div>
</n-modal>
</template>
<script setup>
//
import flPosiPermissMgm from "./permissionDialog.vue";
import { NButton } from "naive-ui";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
nextTick,
h,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter } from "vue-router";
import login from "@/api/login";
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flTree from "@/components/flnlayout/tree/flnindex.vue";
import flPosiDialogCreate from "./dialogCreate.vue";
import { Local } from "@/utils/storage.js";
import orgManage from "./orgManage.vue";
const router = useRouter();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const title = ref("");
const state = reactive({
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
treeSelectData: {},
dialogPermissMgm: false,
dialogDel: false,
dialogData: {},
btnLoading: false,
selectedRows: [],
title: "",
org: {
orgVisible: false,
},
dialogLinkerNum: false,
tableConfig: {
tableScrollWitdh: 600,
rowKey: "ID",
hasSelection: true,
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "/position/v2/list",
params: [
{
label: "departmentID",
field: "key",
},
],
},
searchConfig: [
{
type: "text",
label: "岗位ID",
field: "ID",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "岗位名",
field: "name",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "页面权限",
field: "menuAuth",
class: "col-4",
placeholder: "",
},
{
type: "text",
label: "操作人",
field: "operatorName",
class: "col-4",
placeholder: "",
},
{
type: "rangdate",
label: "最近一次更新时间",
field: ["updateStartAt", "updateEndAt"],
class: "col-4",
placeholder: ["开始", "结束"],
},
],
columns: [
{
title: "岗位ID",
field: "ID",
},
{
width: 140,
title: "岗位名",
field: "name",
type: "sfBgColor",
bgConfig: {
bgColorField: "color",
color: "#fff",
},
},
// {
// width: 120,
// type: "select",
// title: "",
// field: "",
// config: {
// options: [
// {
// label: "",
// value: 1,
// },
// ],
// },
// },
{
width: 120,
type: "tooltip",
title: "岗位描述",
field: "remark",
},
{
width: 360,
title: "页面权限",
field: "menuAuths",
type: "tags",
tagConfig: {
showLength: 3,
bgColor: "#fff",
borderRadius: "14px",
style: "border-radius: 14px",
},
},
{
width: 140,
sorter: true,
title: "被关联人数",
field: "linkerNum",
render(row) {
return h(
"div",
{
style: "color: #409eff; cursor: pointer",
onClick: () => {
handleLinkerNum(row);
},
},
row.linkerNum
);
},
},
{
width: 140,
title: "操作人",
field: "operatorName",
},
{
width: 160,
title: "最近一次更新时间",
field: "updatedAt",
},
{
width: 200,
title: "操作",
fixed: "right",
actions: [
{
actionType: "act_view",
type: "label",
label: "查看",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
{
permission: "r_manage_posimanage_btn_edit",
actionType: "act_edit",
type: "label",
label: "修改",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
{
permission: "r_manage_posimanage_btn_delete",
actionType: "act_del",
type: "label",
label: "删除",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
],
},
],
tableBatchActions: [
{
permission: "r_manage_posimanage_btn_create",
notLimitSelect: true,
style: "background:#F7EFFFFF;color:#8352B4FF",
actionType: "posi-direct-create",
type: "primary",
label: "新建岗位",
},
{
permission: "r_manage_posimanage_btn_create",
notLimitSelect: true,
style: "background:#F7EFFFFF;color:#8352B4FF",
actionType: "posi-create",
type: "primary",
label: "模版新建岗位",
},
],
},
linkerNumConfig: {
tableScrollWitdh: 300,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "/user/v2/list",
params: [
{
label: "positionId",
field: "ID",
},
],
},
columns: [
{
title: "员工工号",
field: "jobNum",
},
{
title: "姓名",
field: "nickName",
},
{
title: "手机",
field: "telNum",
},
{
type: "select",
title: "状态",
field: "status",
config: {
options: [
{
label: "有效",
value: "notactive",
},
{
label: "无效",
value: "left",
},
],
},
},
],
},
dialogPosiCreate: false,
});
onBeforeMount(() => {
getTreeData();
});
onMounted(() => {});
const getTreeData = () => {
let url = "/department/v2/tree/filter";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
//
let localSelect = Local.get("posimanage_treeSelectData");
if (localSelect && JSON.stringify(localSelect) !== "{}") {
state.treeSelectData = localSelect;
state.expandedKeys = localSelect.pathIds;
state.clickKey = localSelect.key;
} else {
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
if (
state.clickKey === data[0].key &&
!state.expandedKeys.includes(data[0].key)
) {
state.expandedKeys.push(data[0].key);
}
if (!state.expandedKeys.includes(state.clickKey)) {
state.expandedKeys.push(state.clickKey);
}
}
state.treeRefreshCount++;
state.tableConfig.refreshCount++;
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.label = item.name;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
state.tableConfig.refreshCount++;
Local.set("posimanage_treeSelectData", state.treeSelectData);
};
const handleRowActions = ({ btnConfig, rowData }) => {
state.dialogData = rowData;
Local.set("posimanage_treeSelectData", state.treeSelectData);
if (btnConfig.actionType === "act_del") {
state.dialogData.type = "one";
state.dialogDel = true;
}
if (btnConfig.actionType === "act_edit") {
router.push(
`/posimanage/create?departmentID=${state.clickKey}&&ID=${rowData.ID}&&type=edit`
);
}
if (btnConfig.actionType === "act_view") {
router.push(
`/posimanage/create?departmentID=${state.clickKey}&&ID=${rowData.ID}&&type=view`
);
}
};
const handleTableBatchActions = ({ selectedRows, btnConfig, fatherData }) => {
state.selectedRows = selectedRows;
if (btnConfig.actionType === "posi-create") {
Local.set("posimanage_treeSelectData", state.treeSelectData);
state.dialogPosiCreate = true;
// router.push(
// `/posimanage/create?departmentID=${state.clickKey}&&type=create`
// );
}
if (btnConfig.actionType === "posi-delete-bat") {
handleDeleteBatch();
}
if (btnConfig.actionType === "posi-direct-create") {
router.push(
`/posimanage/create?departmentID=${
Local.get("posimanage_treeSelectData").key
}&&type=create`
);
}
};
const handleDeleteBatch = () => {
state.dialogData.type = "batch";
state.dialogDel = true;
};
const handleDeleteBatchSave = () => {
state.btnLoading = true;
let url = "/position/v2/batch/remove";
let params = {
IDs: state.selectedRows.map((item) => item.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.selectedRows = [];
state.tableConfig.refreshCount++;
processSuccess("操作成功!");
state.dialogDel = false;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
},
() => {
processError("操作失败!");
state.btnLoading = false;
}
);
};
//
const handleDialogSave = () => {
if (state.dialogData.type === "batch") {
handleDeleteBatchSave();
return;
}
state.btnLoading = true;
let url = "/position/v2/batch/remove";
let params = {
IDs: [state.dialogData.ID],
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.tableConfig.refreshCount++;
state.dialogDel = false;
processSuccess("操作成功!");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.btnLoading = false;
state.dialogDel = false;
},
() => {
state.dialogDel = false;
state.btnLoading = false;
processError("操作失败!");
}
);
};
const handleDialogBack = () => {
state.dialogDel = false;
};
const handlePermissMgm = () => {
state.dialogPermissMgm = true;
};
//
const goToOrgManage = () => {
state.org = { orgVisible: true };
};
const orgHandleOk = () => {
state.org.orgVisible = false;
getTreeData();
};
const handleDialogCreateClose = () => {
state.dialogPosiCreate = false;
state.tableConfig.refreshCount++;
};
const handleLinkerNum = (row) => {
console.log(row);
state.dialogData = row;
state.dialogLinkerNum = true;
};
</script>
<style lang="scss" scoped>
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<n-modal v-model:show="state.dialogModal"
:mask-closable="false">
<n-card style="width: 600px"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true">
<template #header>
<div class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2">
<div style="font-size: 20px; font-weight: bold; color: #1f2225">
自定义数据范围权限
</div>
</div>
</template>
<template #header-extra>
<n-button @click="handleLimitData"
text
style="font-size: 24px">
<n-icon>
<close-icon />
</n-icon>
</n-button>
</template>
<div class="row justify-center fl-pa-md"
style="color: #333639">
<div class="col-12 text-center font-14"
style="color: #333639">
被勾选的部门相关权限可被查看
</div>
<n-tree block-line
checkable
:selectable="false"
:data="state.treeData"
:default-checked-keys="props.fatherData"
@update:checked-keys="updateCheckedKeys" />
<div class="col-12 row justify-center fl-mt-md">
<n-button tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="handleLimitData">返回</n-button>
<n-button tertiary
style="width: 161px; background: #46299dff; color: #fff"
@click="handleLimitData('submit')">保存</n-button>
</div>
</div>
</n-card>
</n-modal>
</template>
<script setup>
//
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
nextTick,
} from "vue";
import { Close as CloseIcon } from "@vicons/ionicons5";
import { processError, processSuccess } from "@/utils/helper/message";
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
let props = defineProps({
fatherData: {
type: Array,
},
});
const state = reactive({
treeData: [],
dialogModal: true,
selectedKeys: []
});
onBeforeMount(() => {
state.dialogModal = true;
getTreeData()
if (props.fatherData) {
state.selectedKeys = props.fatherData;
}
});
const getTreeData = () => {
let url = "/department/v2/tree/my";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.label = item.name;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const emits = defineEmits(["triggerLimitData"]);
const handleLimitData = (type) => {
if (type === 'submit') {
emits("triggerLimitData", state.selectedKeys);
} else {
emits("triggerLimitData", []);
}
};
const updateCheckedKeys = (keys) => {
state.selectedKeys = keys;
};
</script>

View File

@ -0,0 +1,576 @@
<template>
<div class="org-dialog">
<n-modal
class="org-dialog"
v-model:show="props.orgVisible"
:z-index="1999"
:mask-closable="true"
style="min-width: 1000px; max-width: 1200px"
:to="app"
>
<n-card
:bordered="false"
:title="props.title"
size="huge"
role="dialog"
aria-modal="true"
>
<template #header-extra>
<div class="close-box">
<n-button @click="closeFn" text style="font-size: 24px">
<n-icon>
<close-icon />
</n-icon>
</n-button>
</div>
</template>
<div class="item-box">
<fl-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
:config="{
actions: ['edit', 'add', 'subtraction', 'move'],
moveShow: '[%=level%]>0',
subtractionShow: '[%=level%]>0',
}"
@triggerTreeAction="handleTreeAction"
@triggerTreeClick="handleTreeClick"
></fl-tree>
</div>
<template #footer>
<div
style="text-align: center; padding-left: 20px; padding-right: 10px"
>
<n-button class="button-box" @click="closeFn"> 返回 </n-button>
</div>
</template>
</n-card>
</n-modal>
<n-modal
v-model:show="state.dialogAddTree"
style="width: 600px"
:mask-closable="false"
preset="card"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
添加组织
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div class="fl-my-lg font-20">
<n-input
v-model:value="state.dialogAddTreeData"
placeholder="请输入组织名称"
/>
</div>
<div class="col-12 row justify-center">
<n-button
tertiary
style="width: 161px; background: #764cf6; color: #fff"
class="fl-mr-md"
@click="handleDialogAddTreeSave"
>保存</n-button
>
<n-button
tertiary
style="width: 161px; background: #eeeaf7; color: #774ff6"
@click="state.dialogAddTree = false"
>返回</n-button
>
</div>
</div>
</n-modal>
<n-modal
v-model:show="state.dialogSubtractionTree"
style="width: 600px"
:mask-closable="false"
preset="card"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
删除组织
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div class="font-20" style="margin: 115px 0">
是否确认删除该组织及其子组织如有
</div>
<div class="col-12 row justify-center">
<n-button
tertiary
style="width: 161px; background: #764cf6; color: #fff"
class="fl-mr-md"
@click="handleDialogSubtractionTreeSave"
>确定</n-button
>
<n-button
tertiary
style="width: 161px; background: #eeeaf7; color: #774ff6"
@click="state.dialogSubtractionTree = false"
>返回</n-button
>
</div>
</div>
</n-modal>
<n-modal
v-model:show="state.dialogMoveTree"
style="width: 600px"
:mask-closable="false"
preset="card"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
移动
</div>
</div>
</template>
<div class="row justify-center fl-pa-md">
<div class="fl-my-lg font-20">
<div class="font-16 fl-mb-md">请选择部门节点</div>
<fln-form-item
type="select"
:val="null"
:fatherData="state.dialogData"
:config="{
placeholder: '选择归属组织',
config: {
optionConfig: {
resDataField: 'list',
url: '/department/v2/base/all/list',
params: [
{
label: 'notDepartmentIds',
field: 'key',
type: 'Array',
},
],
labelField: 'name',
valueField: 'ID',
},
},
}"
:clearable="true"
@triggerValChange="handleValChange"
/>
<div class="font-14 fl-mt-sm" style="color: #ef0000">
确认后所选部门及其所有子部门将统一归属于新选定的上级部门之下且保持原有子部门层级关系不变
</div>
</div>
<div class="col-12 row justify-center fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #eeeaf7; color: #774ff6"
class="fl-mr-md"
@click="state.dialogMoveTree = false"
>取消</n-button
>
<n-button
tertiary
style="width: 161px; background: #764cf6; color: #fff"
@click="handleDialogMoveTreeSave"
>确定</n-button
>
</div>
</div>
</n-modal>
</div>
</template>
<script setup>
//
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flTree from "./components/flnindex.vue";
import {
ref,
reactive,
onBeforeMount,
defineProps,
defineEmits,
onMounted,
getCurrentInstance,
computed,
} from "vue";
import { Close as CloseIcon } from "@vicons/ionicons5";
import { Local } from "@/utils/storage.js";
import {
processError,
processSuccess,
processWarning,
} from "@/utils/helper/message";
import { useRouter } from "vue-router";
import flnFormItem from "@/components/flnlayout/form/flnformItem.vue";
const router = useRouter();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const app = document.getElementById("app");
const state = reactive({
dialogData: {},
dialogMoveTree: false,
dialogMoveTreeData: null,
departmentOptions: [],
dialogAddTree: false,
dialogAddTreeData: "",
dialogSubtractionTree: false,
treeSelectData: {},
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
btnLoading: false,
selectedRows: [],
tableConfig: {
requestbysf: true,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "user/v2/list",
params: [
{
label: "departmentID",
field: "key",
},
],
},
},
});
const props = defineProps({
orgVisible: {
type: Boolean,
default: false,
},
title: {
type: String,
require: true,
},
});
onBeforeMount(() => {
let treeSelectData = Local.get("orgmanage_treeSelectData");
if (treeSelectData) {
state.treeSelectData = treeSelectData;
state.clickKey = treeSelectData.key;
}
getTreeData();
});
onMounted(() => {});
const getTreeData = () => {
let url = "/department/v2/tree/my";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
if (
state.clickKey === data[0].key &&
!state.expandedKeys.includes(data[0].key)
) {
state.expandedKeys.push(data[0].key);
}
if (!state.expandedKeys.includes(state.clickKey)) {
state.expandedKeys.push(state.clickKey);
}
if (data.length === 1 && !state.expandedKeys.includes(data[0].key)) {
state.expandedKeys.push(data[0].key);
}
state.treeRefreshCount++;
state.tableConfig.refreshCount++;
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const handleCreatePosi = () => {};
const handleValChange = ({ val, config, selectOpt }) => {
state.dialogMoveTreeData = selectOpt;
};
const handleTreeAction = ({ type, val }) => {
state.dialogData = val;
if (type === "add") {
state.clickKey = val.key;
state.treeSelectData = val;
state.tableConfig.refreshCount++;
state.dialogAddTreeData = "";
state.dialogAddTree = true;
}
if (type === "move") {
state.clickKey = val.key;
state.treeSelectData = val;
state.tableConfig.refreshCount++;
state.dialogMoveTreeData = {};
state.dialogMoveTree = true;
}
if (type === "subtraction") {
state.dialogSubtractionTree = true;
}
if (type === "save") {
let url = "/department/v2/update";
let params = {
name: val.title,
ID: val.key,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
getTreeData();
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
},
() => {
processError("操作失败!");
}
);
}
if (type === "cancel") {
getTreeData();
}
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
Local.set("orgmanage_treeSelectData", tree);
state.tableConfig.refreshCount++;
};
// const handleTreeDefaultClick = () => {
// state.treeSelectData = state.treeData[0];
// state.clickKey = state.treeData[0].key;
// Local.remove("orgmanage_treeSelectData");
// state.tableConfig.refreshCount++;
// };
const handleCloseModal = () => {
// emits("triggerCloseModal");
};
const handleDialogMoveTreeSave = () => {
let url = "/department/v2/update";
let params = {
ID: state.dialogData.key,
name: state.dialogData.label,
pid: state.dialogMoveTreeData.value,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
if (Array.isArray(res.data.levelPathList)) {
state.expandedKeys = [];
res.data.levelPathList.forEach((treeid) => {
if (!state.expandedKeys.includes(treeid)) {
state.expandedKeys.push(treeid);
}
});
}
getTreeData();
state.dialogMoveTree = false;
if (
(state.dialogMoveTreeData.level || 0) +
1(state.dialogData.sonMaxDepth || 0) +
1 >
8
) {
processWarning("组织数已超过8级建议不要再增加高度");
} else {
processSuccess("操作成功!");
}
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.dialogAddTree = false;
},
() => {
state.dialogAddTree = false;
processError("操作失败!");
}
);
};
//
const handleDialogAddTreeSave = () => {
let url = "/department/v2/create";
let params = {
name: state.dialogAddTreeData,
pid: state.dialogData.key,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
if (Array.isArray(res.data.levelPathList)) {
state.expandedKeys = [];
res.data.levelPathList.forEach((treeid) => {
if (!state.expandedKeys.includes(treeid)) {
state.expandedKeys.push(treeid);
}
});
}
getTreeData();
state.dialogAddTree = false;
if (state.dialogData.level > 7) {
processWarning("组织数已超过8级建议不要再增加高度");
}
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.dialogAddTree = false;
},
() => {
state.dialogAddTree = false;
processError("操作失败!");
}
);
};
//
const handleDialogSubtractionTreeSave = () => {
let url = "/department/v2/remove";
let params = {
ID: state.dialogData.key,
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0) {
getTreeData();
state.dialogSubtractionTree = false;
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
state.dialogSubtractionTree = false;
},
() => {
state.dialogSubtractionTree = false;
processError("操作失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.label = item.name;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const emit = defineEmits(["update:orgVisible", "orgHandleOk"]);
const closeFn = () => {
emit("orgHandleOk");
};
watch(
() => props.orgVisible,
(val) => {
let treeSelectData = Local.get("orgmanage_treeSelectData");
if (treeSelectData) {
state.treeSelectData = treeSelectData;
state.clickKey = treeSelectData.key;
}
getTreeData();
}
);
</script>
<style lang="scss" scoped>
.org-dialog {
text-align: center;
.naive-modal-body {
height: 750px !important;
}
}
.item-box {
height: 50vh;
overflow: auto;
text-align: center;
padding-left: 100px;
padding-right: 100px;
}
.button-box {
width: 150px;
background-color: rgba(199, 199, 201, 1);
color: #fff;
}
.search-item {
:deep(.ant-input-affix-wrapper) {
background: #fff;
color: #c3c3c3;
}
:deep(.ant-input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
:deep(.ant-picker) {
width: 100%;
border-radius: 20px;
background: #fff !important;
color: #c3c3c3;
border: none;
}
:deep(.ant-picker-input > input) {
color: #000;
&::placeholder {
color: #c3c3c3;
}
}
}
</style>

View File

@ -0,0 +1,311 @@
<template>
<n-modal v-model:show="state.dialogModal" :mask-closable="false">
<n-card
style="width: 80vw"
:bordered="false"
role="dialog"
aria-modal="true"
>
<template #header>
<div class="col-12 row justify-center relative">
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
权限列表
</div>
</div>
</template>
<template #header-extra>
<n-button @click="handleDialogBack" text style="font-size: 24px">
<n-icon>
<close-icon />
</n-icon>
</n-button>
</template>
<div class="row justify-center fl-px-md fl-pb-md">
<div class="col-12 overflow-auto" style="height: 60vh">
<fln-table
:config="state.tableConfig"
:refreshCount="state.tableConfig.refreshCount"
:defaultSelectedRows="state.selectedRows"
@triggerRowActions="handleRowActions"
@triggerSelectCheck="handleTableSelectCheck"
>
<template #table-header-box>
<div class="col-12 row fl-mb-sm justify-between">
<div class="row items-center">
<n-button
tertiary
style="width: 161px; background: #eff3ff; color: #6b69a3"
class="fl-mr-md"
:disabled="state.selectedRows.length === 0"
@click="handleQuickSelectPosi"
>快速添加到岗位</n-button
>
<div v-if="state.selectedRows.length > 0">
已选 {{ state.selectedRows.length }}
</div>
</div>
<div>
<n-button
tertiary
style="width: 161px; background: #9c9ad3; color: #ffffff"
@click="handleCreatePermiss"
>新增权限</n-button
>
</div>
</div>
</template>
</fln-table>
</div>
<div class="col-12 row justify-center fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="handleDialogBack"
>返回</n-button
>
</div>
</div>
</n-card>
</n-modal>
<Auth
v-if="state.showModal"
:title="state.modalAuth.title"
:data="state.modalAuth.data"
@triggerClose="handleCloseEditAuth"
/>
<fl-permission-bind-posi
v-if="state.diallogPosi"
:fatherData="state.selectedRows"
@triggerClose="handleQuickSelectPosiClose"
/>
</template>
<script setup>
//
import {
UpOutlined,
DownOutlined,
CloseCircleOutlined,
PlusOutlined,
DragOutlined,
} from "@ant-design/icons-vue";
import { Close as CloseIcon } from "@vicons/ionicons5";
import flnFormItem from "@/components/flnlayout/form/flnformItem.vue";
import flAboutApproval from "./aboutApproval.vue";
import flPermissionBindPosi from "./permissionbindposi.vue";
import draggable from "vuedraggable";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
nextTick,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
import { ChevronDownOutline, ChevronUpOutline } from "@vicons/ionicons5";
import flnTable from "@/components/flnlayout/table/flntable.vue";
import Auth from "./editAuth.vue";
const emits = defineEmits(["triggerClose"]);
const state = reactive({
selectedRows: [],
btnLoading: false,
dialogModal: true,
tableConfig: {
tableScrollWitdh: 600,
rowKey: "ID",
hasSelection: true,
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "/rule/list",
},
searchStyle: "padding-top:0px !important;",
searchConfig: [
{
type: "select",
label: "权限类型",
field: "Type",
class: "col-4",
placeholder: "",
config: {
options: [
{
value: "menu",
label: "菜单权限",
},
{
value: "button",
label: "按钮权限",
},
{
value: "interface",
label: "接口权限",
},
],
},
},
{
type: "text",
label: "权限名称",
field: "Title",
class: "col-4",
placeholder: "",
},
],
columns: [
{
title: "权限编号",
field: "ID",
},
{
type: "tooltip",
title: "权限名",
field: "Title",
maxWidth: "100%",
},
{
type: "select",
title: "权限类型",
field: "Type",
config: {
options: [
{
value: "menu",
label: "菜单权限",
},
{
value: "button",
label: "按钮权限",
},
{
value: "interface",
label: "接口权限",
},
],
},
},
{
width: 200,
title: "操作",
fixed: "right",
actions: [
{
actionType: "act_edit",
type: "label",
label: "修改",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
{
actionType: "act_del",
type: "label",
label: "删除",
shape: "round",
class: "fl-mx-xs fl-mt-xs",
},
],
},
],
},
showModal: false,
modalAuth: { title: "新增权限", data: null },
diallogPosi: false,
});
onBeforeMount(() => {
state.dialogModal = true;
});
onMounted(() => {});
const handleDialogBack = () => {
state.dialogField = false;
emits("triggerClose");
};
const handleRowActions = ({ btnConfig, rowData }) => {
if (btnConfig.actionType === "act_edit") {
state.modalAuth = { title: "修改与删除-权限", data: { id: rowData.ID } };
state.showModal = true;
}
if (btnConfig.actionType === "act_del") {
$request.HTTP.permission
.ruleRemove({ ID: rowData.ID })
.then((res) => {
if (res.status == 0) {
processSuccess("删除成功");
state.tableConfig.refreshCount++;
} else {
processError(res.msg);
}
})
.catch((e) => {
processError(e.response?.data?.msg || "操作失败");
});
}
};
const handleTableSelectCheck = (data) => {
if (data && Array.isArray(data.selectedRow)) {
data.selectedRow = data.selectedRow.filter((item) => {
if (item) {
return item;
}
});
}
if (data.action === "check" || data.action === "checkAll") {
state.selectedRows = state.selectedRows.concat(data.selectedRow);
state.selectedRows = state.selectedRows.filter((item, index, arr) => {
return (
arr.findIndex((obj) => JSON.stringify(obj) === JSON.stringify(item)) ===
index
);
});
}
if (data.action === "uncheck") {
state.selectedRows = state.selectedRows.filter((item) => {
return item.ID !== data.selectedRow.ID;
});
}
if (data.action === "uncheckAll") {
state.selectedRows = state.selectedRows.filter((item) => {
return (
data.selectedRow.findIndex((obj) => {
return obj.ID === item.ID;
}) === -1
);
});
}
};
const handleCreatePermiss = () => {
state.modalAuth = { title: "新增权限", data: null };
state.showModal = true;
};
const handleQuickSelectPosi = () => {
state.diallogPosi = true;
};
const handleQuickSelectPosiClose = () => {
state.selectedRows = [];
state.diallogPosi = false;
};
const handleCloseEditAuth = (val) => {
state.showModal = false;
state.tableConfig.refreshCount++;
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,308 @@
<template>
<n-modal v-model:show="state.dialogModal" :mask-closable="false">
<n-card
style="width: 80vw"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<template #header>
<div
class="col-12 row justify-center relative"
style="border-bottom: 1px solid #dfd7f2"
>
<div style="font-size: 20px; font-weight: bold; color: #1f2225ff">
选择需要所选权限的岗位
</div>
</div>
</template>
<template #header-extra>
<n-button @click="handleDialogBack" text style="font-size: 24px">
<n-icon>
<close-icon />
</n-icon>
</n-button>
</template>
<div
class="row justify-center fl-pa-md overflow-auto"
style="max-height: 60vh"
>
<div
class="col-3 fl-pa-md row"
style="
background: #fff;
box-shadow: rgba(188, 188, 188, 0.18) 0px 3px 6px 1px;
"
>
<fl-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fl-tree>
</div>
<div class="col-9 fl-px-md">
<fln-table
:config="state.tableConfig"
:fatherData="state.treeSelectData"
:refreshCount="state.tableConfig.refreshCount"
:defaultSelectedRows="state.selectedRows"
@triggerRowActions="handleRowActions"
@triggerSelectCheck="handleTableSelectCheck"
>
</fln-table>
</div>
</div>
<div class="col-12 row justify-center fl-mt-md">
<n-button
tertiary
style="width: 161px; background: #c7c7c9ff; color: #fff"
class="fl-mr-md"
@click="handleDialogBack"
>上一步</n-button
>
<n-button
tertiary
style="width: 161px; background: #46299d; color: #fff"
class="fl-mr-md"
@click="handleDialogSave"
>保存</n-button
>
</div>
</n-card>
</n-modal>
</template>
<script setup>
//
import { NButton } from "naive-ui";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
nextTick,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter } from "vue-router";
import login from "@/api/login";
import flnTable from "@/components/flnlayout/table/flntable.vue";
import flTree from "@/components/flnlayout/tree/flnindex.vue";
import { Close as CloseIcon } from "@vicons/ionicons5";
import { Local } from "@/utils/storage.js";
const router = useRouter();
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const emits = defineEmits(["triggerClose"]);
let props = defineProps({
fatherData: {
type: Array,
},
});
const state = reactive({
clickKey: "",
treeRefreshCount: 0,
expandedKeys: [],
treeData: [],
treeSelectData: {},
btnLoading: false,
selectedRows: [],
tableConfig: {
tableScrollWitdh: 600,
rowKey: "ID",
hasSelection: true,
refreshCount: 0,
listUrl: {
resDataField: "data",
pageField: "page",
pageSizeField: "pageSize",
url: "/position/v2/list",
params: [
{
label: "departmentID",
field: "key",
},
],
},
columns: [
{
title: "岗位ID",
field: "ID",
},
{
width: 140,
title: "岗位名",
field: "name",
type: "sfBgColor",
bgConfig: {
bgColorField: "color",
color: "#fff",
},
},
{
width: 120,
type: "tooltip",
title: "岗位描述",
field: "remark",
},
{
width: 360,
title: "页面权限",
field: "menuAuths",
type: "tags",
tagConfig: {
showLength: 3,
bgColor: "#fff",
borderRadius: "14px",
style: "border-radius: 14px",
},
},
{
width: 140,
sorter: true,
title: "被关联人数",
field: "linkerNum",
},
],
},
dialogModal: true,
});
onBeforeMount(() => {
state.dialogModal = true;
getTreeData();
});
onMounted(() => {});
const getTreeData = () => {
let url = "/department/v2/tree/my";
let params = {};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes;
calcTreeData(data);
state.treeData = data;
if (JSON.stringify(state.treeSelectData) === "{}") {
state.treeSelectData = data[0];
state.clickKey = data[0].key;
}
if (
state.clickKey === data[0].key &&
!state.expandedKeys.includes(data[0].key)
) {
state.expandedKeys.push(data[0].key);
}
if (!state.expandedKeys.includes(state.clickKey)) {
state.expandedKeys.push(state.clickKey);
}
state.treeRefreshCount++;
state.tableConfig.refreshCount++;
} else {
processError(res.msg || "获取失败!");
}
},
() => {
processError("获取失败!");
},
() => {
processError("获取失败!");
}
);
};
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID;
item.label = item.name;
item.title = item.name;
if (item.sons) {
item.children = item.sons;
calcTreeData(item.children);
}
delete item.ID;
delete item.name;
delete item.sons;
}
};
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key;
state.treeSelectData = tree;
state.tableConfig.refreshCount++;
};
const handleRowActions = ({ btnConfig, rowData }) => {
Local.set("posimanage_treeSelectData", state.treeSelectData);
};
const handleDialogSave = () => {
if (state.selectedRows.length > 50) {
return processError("最多选择50个岗位");
}
state.btnLoading = true;
let url = "/position/v2/batch/bind/auth";
let params = {
ids: state.selectedRows.map((item) => item.ID),
ruleIds: props.fatherData.map((item) => item.ID),
};
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
state.btnLoading = false;
if (res.status === 0) {
state.tableConfig.refreshCount++;
processSuccess("操作成功!");
emits("triggerClose");
} else {
processError(res.msg || "操作失败!");
}
},
() => {
processError("操作失败!");
},
() => {
state.btnLoading = false;
processError("操作失败!");
}
);
};
const handleDialogBack = () => {
emits("triggerClose");
};
const handleTableSelectCheck = (data) => {
if (data && Array.isArray(data.selectedRow)) {
data.selectedRow = data.selectedRow.filter((item) => {
if (item) {
return item;
}
});
}
if (data.action === "check" || data.action === "checkAll") {
state.selectedRows = state.selectedRows.concat(data.selectedRow);
state.selectedRows = state.selectedRows.filter((item, index, arr) => {
return (
arr.findIndex((obj) => JSON.stringify(obj) === JSON.stringify(item)) ===
index
);
});
}
if (data.action === "uncheck") {
state.selectedRows = state.selectedRows.filter((item) => {
return item.ID !== data.selectedRow.ID;
});
}
if (data.action === "uncheckAll") {
state.selectedRows = state.selectedRows.filter((item) => {
return (
data.selectedRow.findIndex((obj) => {
return obj.ID === item.ID;
}) === -1
);
});
}
};
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,149 @@
<template>
<div class="row"
style="padding: 35px">
<div class="col-12 row">
<fln-table :config="state.tableConfig"
:refreshCount="state.tableConfig.refreshCount">
<template #search-header>
<div class="col-12 row font-18"
style="color: #764cf6; border-bottom: 1px solid #c1b2e5;">
<div class="fl-py-sm"
style="border-bottom: 4px solid #764cf6;">
岗位操作记录
</div>
</div>
</template>
</fln-table>
</div>
</div>
</template>
<script setup>
//
import { NButton } from "naive-ui";
import {
ref,
reactive,
onBeforeMount,
onMounted,
getCurrentInstance,
computed,
} from "vue";
import { processError, processSuccess } from "@/utils/helper/message";
import { useRouter } from "vue-router";
import flnTable from "@/components/flnlayout/table/flntable.vue";
const currentInstance = getCurrentInstance();
const { $request } = currentInstance.appContext.config.globalProperties;
const title = ref("");
const state = reactive({
btnLoading: false,
selectedRows: [],
tableConfig: {
tableScrollWitdh: 600,
rowKey: "ID",
refreshCount: 0,
listUrl: {
resDataField: "list",
pageField: "page",
pageSizeField: "pageSize",
url: "/position/v2/log/list",
},
searchConfig: [
{
type: "select",
label: "操作类型",
field: "operateType",
options: [
{
label: "新增",
value: "add",
},
{
label: "修改",
value: "edit",
},
{
label: "删除",
value: "del",
},
{
label: "查看",
value: "detail",
},
],
},
{
type: "text",
label: "操作人",
field: "operatorName",
placeholder: "",
},
{
type: "text",
label: "操作人账号",
field: "OperatorTel",
placeholder: "",
},
{
type: "rangdate",
label: "操作时间",
field: ["StartCreatedAt", "EndCreatedAt"],
placeholder: ["开始", "结束"],
},
],
columns: [
{
type: 'select',
title: "操作类型",
field: "operateType",
config: {
options: [
{
label: "新增",
value: "add",
},
{
label: "修改",
value: "edit",
},
{
label: "删除",
value: "del",
},
{
label: "查看",
value: "detail",
},
],
}
},
{
type: "tooltip",
title: "操作详情",
field: "info",
},
{
type: "tooltip",
title: "操作人",
field: "operatorName",
},
{
title: "操作人账号",
field: "OperatorTel",
},
{
title: "操作时间",
field: "createdAt",
},
],
},
});
onBeforeMount(() => {
});
onMounted(() => { });
</script>
<style lang="scss" scoped>
</style>