b5d70ee4 by lihua

组织架构管理

1 parent d7b78de0
1 import request from "@/utils/request";
2
3 /** 获取租户列表(分页) */
4 export const getTenantSinglePage = (params) => request({
5 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/tenant/single-page`,
6 method: 'post',
7 data: params
8 })
9
10 /** 删除租户 */
11 export const removeTenant = (data) => request({
12 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/tenant/remove`,
13 method: 'delete',
14 data
15 })
16
17 export const addTenant = (params) => request({
18 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/tenant/update`,
19 method: 'post',
20 data: params
21 })
22
23 export const updateTenant = (params) => request({
24 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/tenant/update`,
25 method: 'put',
26 data: params
27 })
28
29 /** 更新租户状态 */
30 export const updateTenantState = (guid, state: string = 'Y') => request({
31 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/tenant/update-state?tenantGuid=${guid}&state=${state}`,
32 method: 'get'
33 })
34
35 /** 获取会员进度列表 */
36 export const getMemberGressList = (params) => request({
37 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/pending-task/page-list`,
38 method: 'post',
39 data: params
40 })
41
42 /** 获取任务执行日志 */
43 export const getTaskExecutionLog = (guid) => request({
44 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/pending-task/task-info?guid=${guid}`,
45 method: 'get'
46 })
47
48 /** 任务重启 */
49 export const getTaskRestart = (guid) => request({
50 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/pending-task/restart?guid=${guid}`,
51 method: 'get'
52 })
53
54 /**
55 * 获取部门tree列表
56 * @param param
57 * @returns
58 */
59 export const getOrganisationTreeList = (params) => request({
60 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/organisation/get-tree-list`,
61 method: 'post',
62 data: params
63 })
64
65 export const removeOrganisation = (guids) => request({
66 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/organisation/removeListByGuids`,
67 method: 'delete',
68 data: guids
69 })
70
71
72 /**
73 * 修改部门关系
74 * @param param
75 * @returns
76 */
77 export const updateOrganisation= (params) => request({
78 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/organisation/update`,
79 method: 'put',
80 data: params
81 });
82
83 /**
84 * 新增部门
85 * @param param
86 * @returns
87 */
88 export const addOrganisation = (params) => request({
89 url: `${import.meta.env.VITE_APP_PERSONAL_URL}/organisation/save`,
90 method: 'post',
91 data: params
92 });
93
...@@ -330,6 +330,53 @@ export const useValidator = () => { ...@@ -330,6 +330,53 @@ export const useValidator = () => {
330 }; 330 };
331 }; 331 };
332 332
333 /**
334 * 中文名字限制校验(表单验证函数)
335 * @param {*} message 错误提示
336 * @param {*} minChineseCount 最小中文长度
337 */
338 const minChineseCount = (message?, minChineseCount = 2) => {
339 const chinesePattern = new RegExp(`^[\u4e00-\u9fa5]{${minChineseCount},}$`);
340 return {
341 validator: (_, val, callback) => {
342 let trimmedInput = val ? val.replace(/\s+/g, '') : '';
343 if (!chinesePattern.test(trimmedInput) && val) {
344 callback(new Error(message || `输入必须包含至少${minChineseCount}个中文字符!`))
345 } else {
346 callback()
347 }
348 },
349 trigger: 'blur'
350 }
351 }
352
353 const email = (message?: string): FormItemRule => {
354 return {
355 validator: (_, val, callback) => {
356 if (val && !/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(val)) {
357 callback(new Error(message || '邮箱格式不正确!'))
358 } else {
359 callback()
360 }
361 },
362 trigger: 'blur'
363 }
364 }
365
366 const mobileNumber = (message?: string): FormItemRule => {
367 return {
368 validator: (_, val, callback) => {
369 if (val && !/^1[3-9]\d{9}$/.test(val)) {
370 callback(new Error(message || '手机号码格式不正确!'))
371 } else {
372 callback()
373 }
374 },
375 trigger: 'blur'
376 }
377 }
378
379
333 return { 380 return {
334 required, 381 required,
335 regexpValidate, 382 regexpValidate,
...@@ -343,6 +390,9 @@ export const useValidator = () => { ...@@ -343,6 +390,9 @@ export const useValidator = () => {
343 isUSCCCode, 390 isUSCCCode,
344 idCode, 391 idCode,
345 validateIPList, 392 validateIPList,
346 validateDomainList 393 validateDomainList,
394 minChineseCount,
395 email,
396 mobileNumber
347 } 397 }
348 } 398 }
......
1 import type { RouteRecordRaw } from 'vue-router'
2 function Layout() {
3 return import('@/layouts/index.vue')
4 }
5 const routes: RouteRecordRaw[] = [
6 {
7 path: '/data-basic/department-manage',
8 component: Layout,
9 meta: {
10 title: '组织架构管理',
11 icon: 'sidebar-videos',
12 },
13 children: [{
14 path: '',
15 name: 'departmentInfoList',
16 component: () => import('@/views/data_basic/departmentInfoList.vue'),
17 meta: {
18 title: '组织架构管理',
19 sidebar: false,
20 breadcrumb: false,
21 cache: true
22 },
23 }]
24 },
25 ]
26
27
28 export default routes
...\ No newline at end of file ...\ No newline at end of file
...@@ -8,6 +8,7 @@ import DataFacilitator from './modules/dataFacilitator'; ...@@ -8,6 +8,7 @@ import DataFacilitator from './modules/dataFacilitator';
8 import HomeIndex from './modules/homeIndex'; 8 import HomeIndex from './modules/homeIndex';
9 import DataDelivery from './modules/dataDelivery'; 9 import DataDelivery from './modules/dataDelivery';
10 import DataAnonymization from './modules/dataAnonymization'; 10 import DataAnonymization from './modules/dataAnonymization';
11 import DataBasic from './modules/dataBasic';
11 12
12 import useSettingsStore from '@/store/modules/settings' 13 import useSettingsStore from '@/store/modules/settings'
13 14
...@@ -101,6 +102,7 @@ const asyncRoutes: RouteRecordRaw[] = [ ...@@ -101,6 +102,7 @@ const asyncRoutes: RouteRecordRaw[] = [
101 ...HomeIndex, 102 ...HomeIndex,
102 ...DataDelivery, 103 ...DataDelivery,
103 ...DataAnonymization, 104 ...DataAnonymization,
105 ...DataBasic,
104 // ...DataAssetRegistry, 106 // ...DataAssetRegistry,
105 ] 107 ]
106 108
......
1 <route lang="yaml">
2 name: departmentInfoList
3 </route>
4
5 <template>
6 <div class="container_wrap">
7 <div class="table_tool_wrap">
8 <!-- 头部搜索 -->
9 <TableTools :searchItems="searchItemList" :searchId="'organize-manage-search'" @search="toSearch" :init="false" />
10 <div class="tools_btns">
11 <el-button type="primary" @click="handleCreate">新增部门</el-button>
12 </div>
13 </div>
14 <div class="table_panel_wrap">
15 <!-- 部门信息表格 -->
16 <Table :tableInfo="tableInfo" @tablePageChange="tablePageChange" />
17 </div>
18
19 <!-- 部门信息抽屉 -->
20 <Drawer ref="drawerRef" :drawerInfo="drawerInfo" @drawerBtnClick="drawerBtnClick">
21 </Drawer>
22 </div>
23 </template>
24
25 <script lang="ts" setup name="departmentInfoList">
26 import TableTools from "@/components/Tools/table_tools.vue";
27 import { commonPageConfig } from '@/components/PageNav/index';
28 import { TableColumnWidth } from "@/utils/enum";
29 import {
30 getOrganisationTreeList,
31 removeOrganisation,
32 updateOrganisation,
33 addOrganisation,
34 } from "@/api/modules/dataBasic";
35 import { useValidator } from '@/hooks/useValidator';
36 import useUserStore from "@/store/modules/user";
37 import Moment from 'moment';
38 import { cloneDeep } from "lodash-es";
39
40 const userStore = useUserStore();
41 const userData = JSON.parse(userStore.userData)
42
43 const { proxy } = getCurrentInstance() as any;
44 const router = useRouter()
45 const formItem1 = ref()
46 const drawerRef = ref()
47
48 const { required } = useValidator()
49
50 const searchItemList = ref([{
51 type: "input",
52 label: "",
53 field: "organisationName",
54 default: "",
55 placeholder: "部门名称/部门编码/部门负责人",
56 clearable: true,
57 }, {
58 type: "select",
59 label: "",
60 field: "bizState",
61 default: "Y",
62 options: [{
63 value: 'Y',
64 label: '启用'
65 }, {
66 value: 'S',
67 label: '停用'
68 }],
69 placeholder: "状态",
70 clearable: true,
71 filterable: true,
72 }])
73
74 /** 分页及搜索传参信息配置。 */
75 const page = ref({
76 ...commonPageConfig,
77 organisationName: '',
78 bizState: 'Y'
79 });
80
81 const currTableData: any = ref({});
82
83 const tableInfo = ref({
84 id: 'department-table',
85 rowKey: 'guid',
86 fields: [
87 { label: "序号", type: "index", width: TableColumnWidth.INDEX, align: "center" },
88 { label: "部门名称", field: "organisationName", width: 170 },
89 { label: "部门编号", field: "organisationCode", width: 100 },
90 { label: "部门负责人", field: "chargeStaffName", width: 120 },
91 {
92 label: "状态",
93 field: "bizState",
94 width: TableColumnWidth.STATE,
95 align: 'center',
96 type: "tag",
97 getName: (scope) => {
98 return scope.row.bizState === 'Y' ? '启用' : '停用';
99 }
100 },
101 { label: "界面排序", field: "orderNum", width: 100, align: 'center' },
102 {
103 label: "创建时间", field: "createTime", width: TableColumnWidth.DATETIME, getName: (scope) => {
104 return scope.row.createTime ? Moment(scope.row.createTime).format('YYYY-MM-DD HH:mm:ss') : '--';
105 }
106 },
107 ],
108 data: [],
109 // page: {
110 // type: "normal",
111 // rows: 0,
112 // ...page.value,
113 // },
114 showPage: false,
115 loading: false,
116 actionInfo: {
117 label: "操作",
118 type: "btn",
119 width: 150,
120 fixed: 'right',
121 btns: (scope) => {
122 return [
123 { label: "详情", value: "detail", click: toDetail },
124 { label: "编辑", value: "edit", click: toEdit },
125 { label: "删除", value: "delete", click: toDelete }
126 ];
127 }
128 }
129 })
130
131 const toSearch = (val: any, clear: boolean = false) => {
132 if (clear) {
133 searchItemList.value.map((item) => (item.default = ""));
134 page.value.organisationName = '';
135 page.value.bizState = 'Y';
136 } else {
137 page.value.organisationName = val.organisationName;
138 page.value.bizState = val.bizState;
139 }
140 getTableData();
141 };
142
143 const getTableData = () => {
144 tableInfo.value.loading = true;
145 getOrganisationTreeList({
146 pageIndex: page.value.curr,
147 pageSize: page.value.limit,
148 organisationName: page.value.organisationName,
149 bizState: page.value.bizState,
150 tenantGuid: userData.tenantGuid,
151 }).then((res: any) => {
152 if (res?.code == proxy.$passCode) {
153 const data = res.data || [];
154 tableInfo.value.data = data;
155 // tableInfo.value.page.limit = data.pageSize
156 // tableInfo.value.page.curr = data.pageIndex
157 // tableInfo.value.page.rows = data.totalRows
158 } else {
159 proxy.$ElMessage({
160 type: 'error',
161 message: res.msg,
162 })
163 }
164 tableInfo.value.loading = false
165 })
166 }
167
168 const tablePageChange = (info) => {
169 page.value.curr = Number(info.curr);
170 page.value.limit = Number(info.limit);
171 // tableInfo.value.page.curr = page.value.curr;
172 // tableInfo.value.page.limit = page.value.limit;
173 getTableData();
174 };
175
176 const toEdit = (scope) => {
177 currTableData.value = scope.row;
178 drawerInfo.value.visible = true;
179 drawerInfo.value.header.title = '编辑部门'
180 drawerInfo.value.type = 'edit';
181 drawerInfo.value.footer.visible = true;
182 drawerInfo.value.container.contents[0].formInfo.readonly = false;
183 depEditFormItems.value.forEach(item => {
184 item.default = scope.row[item.field] || '';
185 });
186 // depEditFormItems.value[1].options = cloneDeep(tableInfo.value.data);
187 // TODO,禁用下级。
188 selectParentEdit(scope.row.guid);
189 }
190
191 // 这里有个需求就是编辑时,不能选择自己和自己的子类children作为上级分类,每条数据加上disabled属性true or false
192 const selectParentEdit = (guid: string) => {
193 const cloneData = cloneDeep(tableInfo.value.data); // 深拷贝数据,避免直接修改原始数据
194 const disableNodeAndChildren = (node: any, disabled: boolean) => {
195 node.disabled = disabled;
196 if (node.children && node.children.length > 0) {
197 node.children.forEach((child: any) => disableNodeAndChildren(child, disabled));
198 }
199 };
200 const updateDisabledStatus = (nodes: any[], guid: string) => {
201 nodes.forEach((node) => {
202 if (node.guid === guid) {
203 // 禁用当前节点及其所有子节点
204 disableNodeAndChildren(node, true);
205 } else {
206 // 其他节点保持启用状态
207 node.disabled = false;
208 if (node.children && node.children.length > 0) {
209 updateDisabledStatus(node.children, guid);
210 }
211 }
212 });
213 };
214 updateDisabledStatus(cloneData, guid);
215 depEditFormItems.value[1].options = cloneData; // 更新选项
216 };
217
218 const toDetail = (scope) => {
219 drawerInfo.value.visible = true;
220 drawerInfo.value.header.title = '详情'
221 drawerInfo.value.footer.visible = false;
222 drawerInfo.value.container.contents[0].formInfo.readonly = true;
223 depEditFormItems.value.forEach(item => {
224 item.default = scope.row[item.field] || '';
225 });
226 depEditFormItems.value[1].options = tableInfo.value.data;
227 }
228
229 const toDelete = (scope) => {
230 proxy.$openMessageBox("数据删除后不可恢复,确定是否删除?", "warning", () => {
231 removeOrganisation([scope.row.guid]).then((res: any) => {
232 if (res.data.code === proxy.$passCode) {
233 proxy.$ElMessage({
234 type: 'success',
235 message: '删除成功'
236 })
237 page.value.curr = 1;
238 getTableData();
239 } else {
240 proxy.$ElMessage({
241 type: 'error',
242 message: res.msg,
243 })
244 }
245 })
246 })
247 }
248
249 const handleCreate = () => {
250 drawerInfo.value.visible = true;
251 drawerInfo.value.header.title = '新增部门';
252 drawerInfo.value.type = 'add';
253 drawerInfo.value.footer.visible = true;
254 depEditFormItems.value.forEach(item => {
255 item.default = '';
256 item.field == 'bizState' && (item.default = 'Y');
257 });
258 depEditFormItems.value[1].options = tableInfo.value.data;
259 drawerInfo.value.container.contents[0].formInfo.readonly = false;
260 }
261
262 const depEditFormItems = ref([
263 {
264 field: 'organisationName',
265 label: '部门名称',
266 type: 'input',
267 placeholder: '请输入',
268 visible: true,
269 maxlength: 32,
270 required: true,
271 default: '',
272 clearable: true,
273 },
274 {
275 label: "上级部门",
276 type: "tree-select",
277 placeholder: "请选择",
278 field: "parentGuid",
279 options: [],
280 lazy: false,
281 expandKeys: [],
282 props: {
283 label: 'organisationName',
284 value: 'guid',
285 children: 'children',
286 isLeaf: 'isLeaf'
287 },
288 filterable: true,
289 clearable: true,
290 default: '',
291 required: false,
292 },
293 {
294 label: '界面排序',
295 type: 'input',
296 maxlength: 10,
297 placeholder: '请输入',
298 field: 'orderNum',
299 regexp: /[^\d]/g,
300 default: '',
301 clearable: true,
302 required: false,
303 },
304 {
305 label: '状态',
306 type: 'switch',
307 field: 'bizState',
308 default: 'Y',
309 placeholder: '请选择',
310 activeValue: 'Y',
311 inactiveValue: 'S',
312 switchWidth: 32,
313 },
314 ])
315
316 const depEditFormRules = ref({
317 organisationName: [required()]
318 })
319
320 /** 新增分类的form */
321 const depEditFormInfo = ref({
322 type: "form",
323 title: "",
324 col: "span",
325 formInfo: {
326 id: "add-dep-form",
327 readonly: false,
328 items: depEditFormItems.value,
329 rules: depEditFormRules.value,
330 },
331 });
332
333 const drawerInfo = ref({
334 visible: false,
335 direction: "rtl",
336 size: 520,
337 header: {
338 title: "新增部门",
339 },
340 type: "",
341 container: {
342 contents: [depEditFormInfo.value],
343 },
344 footer: {
345 visible: true,
346 btns: [
347 { type: "default", label: "取消", value: "cancel" },
348 { type: "primary", label: "确认", value: "save", loading: false },
349 ],
350 },
351 })
352
353 const drawerBtnClick = (btn, info) => {
354 if (btn.value == "cancel") {
355 drawerInfo.value.visible = false;
356 } else if (btn.value == "save") {
357 let isAdd = drawerInfo.value.type == 'add';
358 let message = !isAdd ? '修改成功' : '新增成功'
359 if (!isAdd) {
360 info.guid = currTableData.value.guid;
361 }
362 drawerInfo.value.footer.btns[1].loading = true;
363 let ps: any = null;
364 if (isAdd) {
365 ps = addOrganisation({
366 ...info,
367 tenantGuid: userData.tenantGuid
368 })
369 } else {
370 ps = updateOrganisation({
371 ...info,
372 tenantGuid: userData.tenantGuid
373 })
374 }
375 ps.then(res => {
376 drawerInfo.value.footer.btns[1].loading = false;
377 if (res?.code === proxy.$passCode) {
378 proxy.$ElMessage({
379 type: 'success',
380 message: message
381 })
382 drawerInfo.value.visible = false;
383 getTableData();
384 } else {
385 res?.msg && proxy.$ElMessage({
386 type: 'error',
387 message: res.msg,
388 })
389 }
390 })
391 }
392 }
393
394 onBeforeMount(() => {
395 toSearch({});
396 })
397
398 </script>
399
400 <style lang="scss" scoped>
401 .table_tool_wrap {
402 width: 100%;
403 height: 84px !important;
404 padding: 0 8px;
405
406 .tools_btns {
407 padding: 0px 0 0;
408 }
409 }
410
411 .table_panel_wrap {
412 width: 100%;
413 height: calc(100% - 84px);
414 padding: 0px 8px 8px;
415 }
416 </style>
...\ No newline at end of file ...\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!