!2 czh-20260414-实现分发功能的前端
Merge pull request !2 from 陈志浩/feature/distribution
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
|
||||
enum Api {
|
||||
list = '/officialdocDistribute/djOfficialdocDistribute/list',
|
||||
save = '/officialdocDistribute/djOfficialdocDistribute/add',
|
||||
edit = '/officialdocDistribute/djOfficialdocDistribute/edit',
|
||||
deleteOne = '/officialdocDistribute/djOfficialdocDistribute/delete',
|
||||
}
|
||||
|
||||
export const list = (params?) => defHttp.get({ url: Api.list, params });
|
||||
export const save = (params) => defHttp.post({ url: Api.save, params });
|
||||
export const edit = (params) => defHttp.post({ url: Api.edit, params });
|
||||
export const deleteOne = (params) => defHttp.delete({ url: Api.deleteOne, params }, { joinParamsToUrl: true });
|
||||
export const saveOrUpdate = (params, isUpdate) => {
|
||||
let url = isUpdate ? Api.edit : Api.save;
|
||||
return defHttp.post({ url, params });
|
||||
};
|
||||
|
||||
export interface DepartTreeNode {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
title: string;
|
||||
isLeaf?: boolean;
|
||||
departType?: string | number;
|
||||
distributable?: number;
|
||||
disableCheckbox?: boolean;
|
||||
assignType?: number;
|
||||
children?: DepartTreeNode[];
|
||||
description?: string;
|
||||
parentId?: string;
|
||||
orgCode?: string;
|
||||
}
|
||||
|
||||
export interface UserItem {
|
||||
id: string;
|
||||
username: string;
|
||||
realname: string;
|
||||
orgCodeTxt?: string;
|
||||
userSecurityLevel?: number;
|
||||
userSecurityLevel_dictText?: string;
|
||||
sex_dictText?: string;
|
||||
sortno?: number;
|
||||
departId?: string;
|
||||
departName?: string;
|
||||
assignType?: number;
|
||||
}
|
||||
|
||||
export interface DistributeRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
userId: string;
|
||||
username: string;
|
||||
selected: string;
|
||||
}
|
||||
|
||||
export type AssignType = 1 | 2 | 3;
|
||||
|
||||
export function deleteIfExist<T extends Record<string, any>>(array: T[], value: T, key: string): void {
|
||||
const idx = array.findIndex((item) => item[key] === value[key]);
|
||||
if (idx >= 0) {
|
||||
array.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function recurTree<T extends Record<string, any>, R>(tree: T[], callback: (node: T) => R[]): R[] {
|
||||
let res: R[] = [];
|
||||
tree.forEach((item) => {
|
||||
res = res.concat(callback(item));
|
||||
if (item.children) {
|
||||
res = res.concat(recurTree(item.children, callback));
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
export function filterDepartTree<T extends Record<string, any>>(departTree: T[], searchValue: string): T[] {
|
||||
let filtered: T[] = [];
|
||||
departTree.forEach((item) => {
|
||||
if (item.title && item.title.includes(searchValue)) {
|
||||
filtered.push(Object.assign({}, item, { children: null, isLeaf: true }));
|
||||
}
|
||||
if (item.children) {
|
||||
const result = filterDepartTree(item.children, searchValue);
|
||||
if (result.length > 0) {
|
||||
filtered = filtered.concat(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
@@ -0,0 +1,696 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" @click="showModal" v-if="showButton">
|
||||
分发对象
|
||||
</a-button>
|
||||
<a-row v-if="showButton" style="margin-top: 8px">
|
||||
<a-col :span="24" style="width: 850px">
|
||||
<a-button-group v-for="d in distributes" :key="d.id" style="margin: 0 8px 0 0">
|
||||
<a-button type="primary" @click="setDistributeAndSubmit(d)" :title="d.name" style="min-width: 80px">
|
||||
<span class="distribute-name">{{ d.name }}</span>
|
||||
</a-button>
|
||||
<a-button type="primary" @click="deleteDistribute(d.id)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-card style="margin: 8px 0 8px 0; width: 850px; height: 170px; overflow-y: auto" :body-style="{ padding: '8px 8px 0 8px' }">
|
||||
<SzSelectedDepartsAndUsers
|
||||
key="out"
|
||||
:disabled="true"
|
||||
:selectedReadUsers="selectedReadUsers"
|
||||
:selectedUsers="selectedUsers"
|
||||
:selectedReadDeparts="selectedReadDeparts"
|
||||
:selectedAssistDeparts="selectedAssistDeparts"
|
||||
:selectedMainDeparts="selectedMainDeparts"
|
||||
@update:selectedMainDeparts="handleMainDepartsChange"
|
||||
@update:selectedAssistDeparts="handleAssistDepartsChange"
|
||||
@update:selectedReadDeparts="handleReadDepartsChange"
|
||||
@update:selectedUsers="handleUsersChange"
|
||||
@update:selectedReadUsers="handleReadUsersChange"
|
||||
/>
|
||||
</a-card>
|
||||
<a-modal
|
||||
:width="width"
|
||||
:title="titleText"
|
||||
v-model:open="visible"
|
||||
centered
|
||||
:confirm-loading="confirmLoading"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<div>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="3">
|
||||
<h3 style="padding-left: 8px">已存方案:</h3>
|
||||
<div style="height: 556px">
|
||||
<a-empty v-if="distributes.length === 0" description="未保存方案" />
|
||||
<a-button-group v-for="d in distributes" :key="d.id" class="small-space distribute-btn-group">
|
||||
<a-button size="small" type="primary" @click="setDistribute(d)" :title="d.name" style="width: 100px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
|
||||
<span class="distribute-name">{{ d.name }}</span>
|
||||
</a-button>
|
||||
<a-button size="small" type="primary" @click="deleteDistribute(d.id)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-card
|
||||
:tab-list="tabList"
|
||||
:active-tab-key="activeTab"
|
||||
@tabChange="(key) => onTabChange(key)"
|
||||
style="overflow: hidden"
|
||||
:body-style="{ height: '556px', overflow: 'hidden' }"
|
||||
>
|
||||
<div v-if="activeTab === '1'">
|
||||
<SzSelectDepart
|
||||
:selectedReadDeparts="selectedReadDeparts"
|
||||
:selectedAssistDeparts="selectedAssistDeparts"
|
||||
:selectedMainDeparts="selectedMainDeparts"
|
||||
@update:selectedMainDeparts="handleMainDepartsChange"
|
||||
@update:selectedAssistDeparts="handleAssistDepartsChange"
|
||||
@update:selectedReadDeparts="handleReadDepartsChange"
|
||||
:excludeDeparts="excludeDeparts"
|
||||
:departId="departId"
|
||||
:role="role"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab === '2'">
|
||||
<SzSelectUser
|
||||
key="selectUser"
|
||||
v-model:selectedDepId="selectedUserDeptId"
|
||||
@update:checkedUser="handleUsersChange"
|
||||
:checkedUser="selectedUsers"
|
||||
:secLevel="secLevel"
|
||||
:departId="departId"
|
||||
:role="role"
|
||||
:isSpecial="isSpecial"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="activeTab === '3'">
|
||||
<SzSelectUser
|
||||
key="selectReadUser"
|
||||
v-model:selectedDepId="selectedReadUserDeptId"
|
||||
@update:checkedUser="handleReadUsersChange"
|
||||
:checkedUser="selectedReadUsers"
|
||||
:secLevel="secLevel"
|
||||
:departId="departId"
|
||||
:role="role"
|
||||
:isSpecial="isSpecial"
|
||||
/>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="9">
|
||||
<a-card :body-style="{ height: '556px', display: 'flex', flexDirection: 'column' }">
|
||||
<template #title>
|
||||
已选
|
||||
<a-button type="danger" @click="clearSelected">清空</a-button>
|
||||
</template>
|
||||
<div style="flex: 1; overflow: auto; min-height: 0">
|
||||
<SzSelectedDepartsAndUsers
|
||||
key="in"
|
||||
:selectedReadUsers="selectedReadUsers"
|
||||
:selectedUsers="selectedUsers"
|
||||
:selectedReadDeparts="selectedReadDeparts"
|
||||
:selectedAssistDeparts="selectedAssistDeparts"
|
||||
:selectedMainDeparts="selectedMainDeparts"
|
||||
@update:selectedMainDeparts="handleMainDepartsChange"
|
||||
@update:selectedAssistDeparts="handleAssistDepartsChange"
|
||||
@update:selectedReadDeparts="handleReadDepartsChange"
|
||||
@update:selectedUsers="handleUsersChange"
|
||||
@update:selectedReadUsers="handleReadUsersChange"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin: 0 8px 8px 8px">
|
||||
<a-form-item label="分发意见">
|
||||
<a-textarea placeholder="请在此处输入意见。" style="height: 100px" v-model:value="reason" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div style="margin: 0 8px 0 8px">
|
||||
<a-input-search v-model:value="activeDistribute" @search="saveDistributes" enter-button="保存为方案" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch, onMounted, h } from 'vue';
|
||||
import { CloseOutlined } from '@ant-design/icons-vue';
|
||||
import { Modal, message, Empty } from 'ant-design-vue';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { filterDictTextByCache } from '/@/utils/dict/JDictSelectUtil';
|
||||
import { deleteIfExist, list, save, edit, deleteOne } from './SzDistribute.api';
|
||||
import type { DepartTreeNode, UserItem } from './SzDistribute.api';
|
||||
import SzSelectDepart from './SzSelectDepart.vue';
|
||||
import SzSelectUser from './SzSelectUser.vue';
|
||||
import SzSelectedDepartsAndUsers from './SzSelectedDepartsAndUsers.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
width?: string | number;
|
||||
title: string;
|
||||
docId?: string;
|
||||
departId?: string;
|
||||
role: string | number;
|
||||
secLevel: any;
|
||||
checkedMainDepartIds?: string;
|
||||
checkedAssistDepartIds?: string;
|
||||
checkedReadDepartIds?: string;
|
||||
checkedUsernames?: string;
|
||||
checkedReadUsernames?: string;
|
||||
reasons?: string;
|
||||
isSpecial?: number;
|
||||
modelValue?: string;
|
||||
showButton?: boolean;
|
||||
}>(),
|
||||
{
|
||||
width: 1500,
|
||||
title: '分发方案',
|
||||
docId: '',
|
||||
departId: '',
|
||||
role: 1,
|
||||
checkedMainDepartIds: '',
|
||||
checkedAssistDepartIds: '',
|
||||
checkedReadDepartIds: '',
|
||||
checkedUsernames: '',
|
||||
checkedReadUsernames: '',
|
||||
reasons: '',
|
||||
isSpecial: 0,
|
||||
modelValue: '',
|
||||
showButton: true,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:checkedDepartIds',
|
||||
'update:checkedMainDepartIds',
|
||||
'update:checkedAssistDepartIds',
|
||||
'update:checkedReadDepartIds',
|
||||
'update:checkedUsernames',
|
||||
'update:checkedReadUsernames',
|
||||
'update:reasons',
|
||||
'update:modelValue',
|
||||
'update:checkedDeparts',
|
||||
'change',
|
||||
]);
|
||||
|
||||
const excludeDeparts = ref<string[]>([]);
|
||||
const visible = ref(false);
|
||||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
const confirmLoading = ref(false);
|
||||
const activeTab = ref('1');
|
||||
const activeDistribute = ref('');
|
||||
const selectedMainDeparts = ref<DepartTreeNode[]>([]);
|
||||
const selectedAssistDeparts = ref<DepartTreeNode[]>([]);
|
||||
const selectedReadDeparts = ref<DepartTreeNode[]>([]);
|
||||
const selectedUsers = ref<UserItem[]>([]);
|
||||
const selectedReadUsers = ref<UserItem[]>([]);
|
||||
const selectedUserDeptId = ref('');
|
||||
const selectedReadUserDeptId = ref('');
|
||||
const reason = ref('');
|
||||
const distributes = ref<any[]>([]);
|
||||
|
||||
const tabList = [
|
||||
{ key: '1', tab: '选择部门' },
|
||||
{ key: '2', tab: '选择办文人员' },
|
||||
{ key: '3', tab: '选择阅文人员' },
|
||||
];
|
||||
|
||||
const selectedMainDepartIds = computed(() => selectedMainDeparts.value.map((item) => item.id));
|
||||
const selectedAssistDepartIds = computed(() => selectedAssistDeparts.value.map((item) => item.id));
|
||||
const selectedReadDepartIds = computed(() => selectedReadDeparts.value.map((item) => item.id));
|
||||
const selectedDepartIds = computed(() => selectedMainDepartIds.value.concat(selectedAssistDepartIds.value).concat(selectedReadDepartIds.value));
|
||||
const selectedUsernames = computed(() => selectedUsers.value.map((item) => item.username));
|
||||
const selectedReadUsernames = computed(() => selectedReadUsers.value.map((item) => item.username));
|
||||
|
||||
const distributeStr = computed(() => {
|
||||
const mainIds = selectedMainDeparts.value.map((item) => item.id);
|
||||
const assistIds = selectedAssistDeparts.value.map((item) => item.id);
|
||||
const readIds = selectedReadDeparts.value.map((item) => item.id);
|
||||
const usernames = selectedUsers.value.map((item) => item.username);
|
||||
const readUsernames = selectedReadUsers.value.map((item) => item.username);
|
||||
let res = '';
|
||||
if (mainIds.length > 0) res += 'main: ' + mainIds.join(',');
|
||||
if (assistIds.length > 0) res += 'assist: ' + assistIds.join(',');
|
||||
if (readIds.length > 0) res += 'read:' + readIds.join(',');
|
||||
if (usernames.length > 0) res += 'user:' + usernames.join(',');
|
||||
if (readUsernames.length > 0) res += 'readUser:' + readUsernames.join(',');
|
||||
return res;
|
||||
});
|
||||
|
||||
const titleText = computed(() => {
|
||||
const secLevelText = filterDictTextByCache('officialdoc_security_level', props.secLevel);
|
||||
if (secLevelText && secLevelText !== 'undefined' && secLevelText !== props.secLevel) {
|
||||
return '(' + secLevelText + ')' + props.title;
|
||||
}
|
||||
return props.title;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.docId,
|
||||
() => {
|
||||
getExcludeDepartIds();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.checkedMainDepartIds,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) refreshDepartsAndUsersFromIds({ mainDepartIds: newVal });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.checkedAssistDepartIds,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) refreshDepartsAndUsersFromIds({ assistDepartIds: newVal });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.checkedReadDepartIds,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) refreshDepartsAndUsersFromIds({ readDepartIds: newVal });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.checkedUsernames,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) refreshDepartsAndUsersFromIds({ usernames: newVal });
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.checkedReadUsernames,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal !== oldVal) refreshDepartsAndUsersFromIds({ readUsernames: newVal });
|
||||
}
|
||||
);
|
||||
|
||||
function refreshDepartsAndUsersFromIds(params: {
|
||||
mainDepartIds?: string;
|
||||
assistDepartIds?: string;
|
||||
readDepartIds?: string;
|
||||
usernames?: string;
|
||||
readUsernames?: string;
|
||||
}) {
|
||||
const { mainDepartIds, assistDepartIds, readDepartIds, usernames, readUsernames } = params;
|
||||
if (mainDepartIds) {
|
||||
defHttp.get({ url: '/sys/sysDepart/queryTreeList', params: { ids: mainDepartIds } }).then((res: any) => {
|
||||
res.forEach((item: any) => (item.assignType = 1));
|
||||
selectedMainDeparts.value = [...res];
|
||||
}).catch(() => { selectedMainDeparts.value = []; });
|
||||
} else {
|
||||
selectedMainDeparts.value = [];
|
||||
}
|
||||
if (assistDepartIds) {
|
||||
defHttp.get({ url: '/sys/sysDepart/queryTreeList', params: { ids: assistDepartIds } }).then((res: any) => {
|
||||
res.forEach((item: any) => (item.assignType = 2));
|
||||
selectedAssistDeparts.value = [...res];
|
||||
}).catch(() => { selectedAssistDeparts.value = []; });
|
||||
} else {
|
||||
selectedAssistDeparts.value = [];
|
||||
}
|
||||
if (readDepartIds) {
|
||||
defHttp.get({ url: '/sys/sysDepart/queryTreeList', params: { ids: readDepartIds } }).then((res: any) => {
|
||||
selectedReadDeparts.value = [...res];
|
||||
}).catch(() => { selectedReadDeparts.value = []; });
|
||||
} else {
|
||||
selectedReadDeparts.value = [];
|
||||
}
|
||||
if (usernames) {
|
||||
defHttp.get({ url: '/sys/user/queryUserComponentData', params: { username: usernames, isMultiTranslate: 'true', pageNo: 1, pageSize: 9999 } }).then((res: any) => {
|
||||
selectedUsers.value = [...(res.records || res)];
|
||||
}).catch(() => { selectedUsers.value = []; });
|
||||
} else {
|
||||
selectedUsers.value = [];
|
||||
}
|
||||
if (readUsernames) {
|
||||
defHttp.get({ url: '/sys/user/queryUserComponentData', params: { username: readUsernames, isMultiTranslate: 'true', pageNo: 1, pageSize: 9999 } }).then((res: any) => {
|
||||
selectedReadUsers.value = [...(res.records || res)];
|
||||
}).catch(() => { selectedReadUsers.value = []; });
|
||||
} else {
|
||||
selectedReadUsers.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
selectedMainDeparts.value = [];
|
||||
selectedAssistDeparts.value = [];
|
||||
selectedReadDeparts.value = [];
|
||||
selectedUsers.value = [];
|
||||
selectedReadUsers.value = [];
|
||||
refreshDepartsAndUsersFromIds({
|
||||
mainDepartIds: props.checkedMainDepartIds,
|
||||
assistDepartIds: props.checkedAssistDepartIds,
|
||||
readDepartIds: props.checkedReadDepartIds,
|
||||
usernames: props.checkedUsernames,
|
||||
readUsernames: props.checkedReadUsernames,
|
||||
});
|
||||
getExcludeDepartIds();
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
function submitDistribute() {
|
||||
emitChanges();
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
submitDistribute();
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
selectedMainDeparts.value = [];
|
||||
selectedAssistDeparts.value = [];
|
||||
selectedReadDeparts.value = [];
|
||||
selectedUsers.value = [];
|
||||
selectedReadUsers.value = [];
|
||||
refreshDepartsAndUsersFromIds({
|
||||
mainDepartIds: props.checkedMainDepartIds,
|
||||
assistDepartIds: props.checkedAssistDepartIds,
|
||||
readDepartIds: props.checkedReadDepartIds,
|
||||
usernames: props.checkedUsernames,
|
||||
readUsernames: props.checkedReadUsernames,
|
||||
});
|
||||
}
|
||||
|
||||
function onTabChange(key: string) {
|
||||
activeTab.value = key;
|
||||
}
|
||||
|
||||
function validateUser(savedUsers: UserItem[]) {
|
||||
const uniqueUsers = Array.from(new Set(savedUsers));
|
||||
const userIds = uniqueUsers.map((user) => user.id).join(',');
|
||||
defHttp
|
||||
.get({ url: '/sys/user/queryUserComponentData', params: { id: userIds, isMultiTranslate: 'true', pageNo: 1, pageSize: 9999 } })
|
||||
.then((res: any) => {
|
||||
const userList = res.records || res;
|
||||
const refreshedUsersMap = new Map(userList.map((user: any) => [user.id, user]));
|
||||
let secProhibitUser: any[] = [];
|
||||
let departChangeUser: any[] = [];
|
||||
let abnormalUser: any[] = [];
|
||||
uniqueUsers.forEach((user) => {
|
||||
const refreshedUser = refreshedUsersMap.get(user.id);
|
||||
if (refreshedUser) {
|
||||
if (refreshedUser.userSecurityLevel <= props.secLevel) {
|
||||
secProhibitUser.push(refreshedUser);
|
||||
}
|
||||
if (user.orgCodeTxt !== refreshedUser.orgCodeTxt) {
|
||||
departChangeUser.push(refreshedUser);
|
||||
}
|
||||
} else {
|
||||
abnormalUser.push(user);
|
||||
}
|
||||
});
|
||||
let confirmStr = '';
|
||||
if (secProhibitUser.length > 0) {
|
||||
confirmStr += '密级不满足要求的员工: ' + secProhibitUser.map((user) => user.realname).join(',') + '。';
|
||||
}
|
||||
if (departChangeUser.length > 0) {
|
||||
confirmStr += '部门发生变动的员工:' + departChangeUser.map((user) => user.realname).join(',') + '。';
|
||||
}
|
||||
if (abnormalUser.length > 0) {
|
||||
confirmStr += '已离职或状态为冻结的员工:' + abnormalUser.map((user) => user.realname).join(',');
|
||||
}
|
||||
if (confirmStr !== '') {
|
||||
Modal.warning({
|
||||
title: '方案保存的部分员工发生变动!将自动取消以下人员:',
|
||||
content: h('div', confirmStr),
|
||||
onOk: () => {
|
||||
const deleteUsers = secProhibitUser.concat(departChangeUser).concat(abnormalUser);
|
||||
deleteUsers.forEach((deleteUser) => {
|
||||
const delIdx = selectedUsers.value.findIndex((user) => user.id === deleteUser.id);
|
||||
if (delIdx >= 0) {
|
||||
selectedUsers.value.splice(delIdx, 1);
|
||||
}
|
||||
const delIdx1 = selectedReadUsers.value.findIndex((user) => user.id === deleteUser.id);
|
||||
if (delIdx1 >= 0) {
|
||||
selectedReadUsers.value.splice(delIdx1, 1);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
return secProhibitUser.length === 0 && departChangeUser.length === 0 && abnormalUser.length === 0;
|
||||
});
|
||||
}
|
||||
|
||||
function setDistribute(distribute: any) {
|
||||
validateUser(distribute.selected.selectedUsers.concat(distribute.selected.selectedReadUsers));
|
||||
activeTab.value = '1';
|
||||
activeDistribute.value = distribute.name;
|
||||
selectedMainDeparts.value = [...distribute.selected.selectedMainDeparts];
|
||||
selectedAssistDeparts.value = [...distribute.selected.selectedAssistDeparts];
|
||||
selectedReadDeparts.value = [...distribute.selected.selectedReadDeparts];
|
||||
selectedUsers.value = [...distribute.selected.selectedUsers];
|
||||
selectedReadUsers.value = [...distribute.selected.selectedReadUsers];
|
||||
reason.value = distribute.selected.reason;
|
||||
message.success('加载方案成功!');
|
||||
}
|
||||
|
||||
function setDistributeAndSubmit(distribute: any) {
|
||||
setDistribute(distribute);
|
||||
submitDistribute();
|
||||
}
|
||||
|
||||
function refreshDistributes() {
|
||||
list().then((res: any) => {
|
||||
distributes.value = [...res];
|
||||
});
|
||||
}
|
||||
|
||||
function saveDistributes(name: string) {
|
||||
if (name === '') {
|
||||
message.error('请输入方案名!');
|
||||
return;
|
||||
}
|
||||
const dIdx = distributes.value.findIndex((item) => item.name === name);
|
||||
const userStore = useUserStore();
|
||||
const userId = userStore.getUserInfo?.id;
|
||||
const username = userStore.getUserInfo?.username;
|
||||
const data = {
|
||||
name,
|
||||
userId,
|
||||
username,
|
||||
selected: {
|
||||
selectedMainDeparts: selectedMainDeparts.value,
|
||||
selectedAssistDeparts: selectedAssistDeparts.value,
|
||||
selectedReadDeparts: selectedReadDeparts.value,
|
||||
selectedUsers: selectedUsers.value,
|
||||
selectedReadUsers: selectedReadUsers.value,
|
||||
reason: reason.value,
|
||||
},
|
||||
};
|
||||
if (dIdx < 0) {
|
||||
save(data).then(() => {
|
||||
message.success('新增方案成功!');
|
||||
refreshDistributes();
|
||||
});
|
||||
} else {
|
||||
data.id = distributes.value[dIdx].id;
|
||||
edit(data).then(() => {
|
||||
message.success('更新方案成功!');
|
||||
refreshDistributes();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteDistribute(id: string) {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '是否删除该方案?',
|
||||
onOk: () => {
|
||||
deleteOne({ id }).then(() => {
|
||||
refreshDistributes();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function generateReasonText(): string {
|
||||
let result = '';
|
||||
if (selectedMainDeparts.value.length > 0) {
|
||||
const departNames = selectedMainDeparts.value.map((item) => item.title).join(',');
|
||||
result += '请' + departNames + '主办。';
|
||||
}
|
||||
if (selectedAssistDeparts.value.length > 0) {
|
||||
const departNames = selectedAssistDeparts.value.map((item) => item.title).join(',');
|
||||
result += '请' + departNames + '协办。';
|
||||
}
|
||||
if (selectedReadDeparts.value.length > 0) {
|
||||
const departNames = selectedReadDeparts.value.map((item) => item.title).join(',');
|
||||
result += '请' + departNames + '阅。';
|
||||
}
|
||||
if (selectedUsers.value.length > 0) {
|
||||
const userNames = selectedUsers.value.map((item) => item.realname).join(',');
|
||||
result += '请' + userNames + '办理。';
|
||||
}
|
||||
if (selectedReadUsers.value.length > 0) {
|
||||
const readUserNames = selectedReadUsers.value.map((item) => item.realname).join(',');
|
||||
result += '请' + readUserNames + '阅。';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function clearSelected() {
|
||||
selectedMainDeparts.value = [];
|
||||
selectedAssistDeparts.value = [];
|
||||
selectedReadDeparts.value = [];
|
||||
selectedUsers.value = [];
|
||||
selectedReadUsers.value = [];
|
||||
}
|
||||
|
||||
function getExcludeDepartIds() {
|
||||
if (props.docId !== '') {
|
||||
defHttp
|
||||
.get({ url: '/distributionProcessinfo/officialdocDistributionProcessinfo/queryDeptIds', params: { docId: props.docId } })
|
||||
.then((res: any) => {
|
||||
excludeDeparts.value = res;
|
||||
})
|
||||
.catch(() => {
|
||||
excludeDeparts.value = [];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleMainDepartsChange(newVal: DepartTreeNode[]) {
|
||||
selectedMainDeparts.value = [...newVal];
|
||||
}
|
||||
|
||||
function handleAssistDepartsChange(newVal: DepartTreeNode[]) {
|
||||
selectedAssistDeparts.value = [...newVal];
|
||||
}
|
||||
|
||||
function handleReadDepartsChange(newVal: DepartTreeNode[]) {
|
||||
selectedReadDeparts.value = [...newVal];
|
||||
}
|
||||
|
||||
function handleUsersChange(newVal: UserItem[]) {
|
||||
selectedUsers.value = [...newVal];
|
||||
}
|
||||
|
||||
function handleReadUsersChange(newVal: UserItem[]) {
|
||||
selectedReadUsers.value = [...newVal];
|
||||
}
|
||||
|
||||
function emitChanges() {
|
||||
emit('update:checkedDepartIds', selectedDepartIds.value.join(','));
|
||||
emit('update:checkedMainDepartIds', selectedMainDepartIds.value.join(','));
|
||||
emit('update:checkedAssistDepartIds', selectedAssistDepartIds.value.join(','));
|
||||
emit('update:checkedReadDepartIds', selectedReadDepartIds.value.join(','));
|
||||
emit('update:checkedUsernames', selectedUsernames.value.join(','));
|
||||
emit('update:checkedReadUsernames', selectedReadUsernames.value.join(','));
|
||||
emit('update:modelValue', distributeStr.value);
|
||||
emit('change', distributeStr.value);
|
||||
if (reason.value) {
|
||||
emit('update:reasons', reason.value);
|
||||
} else {
|
||||
emit('update:reasons', generateReasonText());
|
||||
}
|
||||
emit('update:checkedDeparts', selectedMainDeparts.value.concat(selectedAssistDeparts.value).concat(selectedReadDeparts.value));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.role === 2 || props.role === '2') {
|
||||
activeTab.value = '3';
|
||||
}
|
||||
refreshDepartsAndUsersFromIds({
|
||||
mainDepartIds: props.checkedMainDepartIds,
|
||||
assistDepartIds: props.checkedAssistDepartIds,
|
||||
readDepartIds: props.checkedReadDepartIds,
|
||||
usernames: props.checkedUsernames,
|
||||
readUsernames: props.checkedReadUsernames,
|
||||
});
|
||||
refreshDistributes();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.small-space {
|
||||
margin: 0 8px 8px 8px;
|
||||
}
|
||||
|
||||
.distribute-btn-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.distribute-name-btn {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.distribute-name {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
max-width: 60px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
&.btn-red {
|
||||
&,
|
||||
&:active,
|
||||
&.active {
|
||||
color: #f5222d;
|
||||
background: #fff1f0;
|
||||
border-color: #ffa39e;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #f5222d;
|
||||
background: #fff1f0;
|
||||
border-color: #ffa39e;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-blue {
|
||||
&,
|
||||
&:active,
|
||||
&.active {
|
||||
color: #1890ff;
|
||||
background: #e6f7ff;
|
||||
border-color: #91d5ff;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #1890ff;
|
||||
background: #e6f7ff;
|
||||
border-color: #91d5ff;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-green {
|
||||
&,
|
||||
&:active,
|
||||
&.active {
|
||||
color: #52c41a;
|
||||
background: #f6ffed;
|
||||
border-color: #b7eb8f;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #52c41a;
|
||||
background: #f6ffed;
|
||||
border-color: #b7eb8f;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,659 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-spin tip="Loading..." :spinning="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-input-search v-model:value="searchDepartValue" style="margin-bottom: 1px" placeholder="请输入部门名称" />
|
||||
</a-col>
|
||||
<a-col :span="18">
|
||||
<a-space>
|
||||
<a-input-group v-if="showCheckAll" compact style="display: inline-block; width: auto">
|
||||
<a-button @click="checkAllDepart">全选部门</a-button>
|
||||
<a-select v-model:value="checkAllAssignType" :options="assignTypeList" />
|
||||
</a-input-group>
|
||||
<a-button type="danger" v-if="showCancelCheckAll" @click="uncheckAllDepart">取消全选</a-button>
|
||||
|
||||
<a-input-group v-if="showCheckAllMange" compact style="display: inline-block; width: auto">
|
||||
<a-button @click="checkAllManageDepart">全选职能部门</a-button>
|
||||
<a-select v-model:value="checkAllManageAssignType" :options="assignTypeList" />
|
||||
</a-input-group>
|
||||
<a-button type="danger" v-if="showCancelCheckAllMange" @click="uncheckAllManageDepart">取消全选职能</a-button>
|
||||
|
||||
<a-input-group v-if="showCheckAllBusiness" compact style="display: inline-block; width: auto">
|
||||
<a-button @click="checkAllBusinessesDepart">全选业务部门</a-button>
|
||||
<a-select v-model:value="checkAllBusinessAssignType" :options="assignTypeList" />
|
||||
</a-input-group>
|
||||
<a-button type="danger" v-if="showCancelCheckAllBusiness" @click="uncheckAllBusinessesDepart">取消全选业务</a-button>
|
||||
|
||||
<a-input-group v-if="showCheckAllCurrentSubDepart" compact style="display: inline-block; width: auto">
|
||||
<a-button @click="checkAllCurrentSubDepart">全选本部门科室</a-button>
|
||||
<a-select v-model:value="checkAllCurrentSubDepartAssignType" :options="assignTypeList" />
|
||||
</a-input-group>
|
||||
<a-button type="danger" v-if="showCancelCheckAllCurrentSubDepart" @click="uncheckAllCurrentSubDepart">取消本部门科室</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div v-if="filterDepartTreeData.length === 0" style="height: 470px; display: flex; align-items: center; justify-content: center">
|
||||
<a-empty description="暂无数据" />
|
||||
</div>
|
||||
<a-tree
|
||||
v-else
|
||||
ref="treeRef"
|
||||
checkable
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
class="my-dept-select-tree"
|
||||
:tree-data="filterDepartTreeData"
|
||||
:check-strictly="true"
|
||||
@check="onDepartCheck"
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
style="overflow-y: auto"
|
||||
>
|
||||
<template #title="{ key: treeKey, title, disableCheckbox, assignType }">
|
||||
<a-dropdown :trigger="['contextmenu']">
|
||||
<span v-if="disableCheckbox" style="color: rgba(0,0,0,0.25); cursor: not-allowed">{{ title }} </span>
|
||||
<span v-else @click="toggleCheck(treeKey, assignType)">{{ title }} </span>
|
||||
</a-dropdown>
|
||||
<a-button-group v-if="!disableCheckbox && checkedKeys.checked?.includes(treeKey)">
|
||||
<a-button size="small" :class="assignType === 1 ? 'btn-red' : ''" @click="checkMainDepart(treeKey, assignType)">办</a-button>
|
||||
<a-button size="small" :class="assignType === 2 ? 'btn-blue' : ''" @click="checkAssistDepart(treeKey, assignType)">协</a-button>
|
||||
<a-button size="small" :class="assignType === 3 || !assignType ? 'btn-green' : ''" @click="checkReadDepart(treeKey, assignType)">阅</a-button>
|
||||
</a-button-group>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { queryTreeList } from '/@/api/common/api';
|
||||
import { pushIfNotExist } from '/@/utils/common/compUtils';
|
||||
import { deleteIfExist, recurTree, filterDepartTree } from './SzDistribute.api';
|
||||
import type { DepartTreeNode } from './SzDistribute.api';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
selectedMainDeparts: DepartTreeNode[];
|
||||
selectedAssistDeparts: DepartTreeNode[];
|
||||
selectedReadDeparts: DepartTreeNode[];
|
||||
excludeDeparts?: string[];
|
||||
departId: string;
|
||||
role: string | number;
|
||||
}>(),
|
||||
{
|
||||
excludeDeparts: () => [],
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:selectedMainDeparts',
|
||||
'update:selectedAssistDeparts',
|
||||
'update:selectedReadDeparts',
|
||||
]);
|
||||
|
||||
const treeRef = ref();
|
||||
const treeData = ref<DepartTreeNode[]>([]);
|
||||
const checkedKeys = ref<{ checked: string[]; halfChecked: string[] }>({ checked: [], halfChecked: [] });
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
const searchDepartValue = ref('');
|
||||
const departType = ref<string | number>('');
|
||||
const defaultExpandedKeys = ref<string[]>([]);
|
||||
const checkAllAssignType = ref<number>(3);
|
||||
const checkAllManageAssignType = ref<number>(3);
|
||||
const checkAllBusinessAssignType = ref<number>(3);
|
||||
const checkAllCurrentSubDepartAssignType = ref<number>(3);
|
||||
|
||||
const assignTypeList = [
|
||||
{ value: 1, label: '办' },
|
||||
{ value: 2, label: '协' },
|
||||
{ value: 3, label: '阅' },
|
||||
];
|
||||
|
||||
const selectedDeparts = computed(() => [...props.selectedMainDeparts, ...props.selectedAssistDeparts, ...props.selectedReadDeparts]);
|
||||
|
||||
const filterDepartTreeData = computed(() => {
|
||||
if (!searchDepartValue.value) return treeData.value;
|
||||
return filterDepartTree(treeData.value, searchDepartValue.value);
|
||||
});
|
||||
|
||||
const manageDeparts = computed(() => {
|
||||
return recurTree(treeData.value, (node) => {
|
||||
if (String(node.departType) === '1' && !node.disableCheckbox) return [node];
|
||||
return [];
|
||||
});
|
||||
});
|
||||
|
||||
const businessesDeparts = computed(() => {
|
||||
return recurTree(treeData.value, (node) => {
|
||||
if (String(node.departType) === '2' && !node.disableCheckbox) return [node];
|
||||
return [];
|
||||
});
|
||||
});
|
||||
|
||||
const subDeparts = computed(() => {
|
||||
return recurTree(treeData.value, (node) => {
|
||||
if (String(node.departType) === '3' && !node.disableCheckbox) return [node];
|
||||
return [];
|
||||
});
|
||||
});
|
||||
|
||||
const showCheckAll = computed(() => {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
return (manageDeparts.value.length + businessesDeparts.value.length > 0) && selectedDeparts.value.length < manageDeparts.value.length + businessesDeparts.value.length;
|
||||
} else {
|
||||
return (departType.value === 0 || departType.value === '0' || departType.value === 1 || departType.value === '1')
|
||||
&& (manageDeparts.value.length + businessesDeparts.value.length > 0)
|
||||
&& selectedDeparts.value.length < manageDeparts.value.length + businessesDeparts.value.length;
|
||||
}
|
||||
});
|
||||
|
||||
const showCancelCheckAll = computed(() => {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
return selectedDeparts.value.length > 0 && !showCheckAll.value;
|
||||
} else {
|
||||
return (departType.value === 0 || departType.value === '0' || departType.value === 1 || departType.value === '1')
|
||||
&& selectedDeparts.value.length > 0 && !showCheckAll.value;
|
||||
}
|
||||
});
|
||||
|
||||
const showCheckAllMange = computed(() => {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
return manageDeparts.value.length > 0 && !manageDeparts.value.every((item) => selectedDeparts.value.some((i) => i.id === item.id));
|
||||
} else {
|
||||
return (departType.value === 0 || departType.value === '0' || departType.value === 1 || departType.value === '1')
|
||||
&& manageDeparts.value.length > 0
|
||||
&& !manageDeparts.value.every((item) => selectedDeparts.value.some((i) => i.id === item.id));
|
||||
}
|
||||
});
|
||||
|
||||
const showCancelCheckAllMange = computed(() => {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
return selectedDeparts.value.length > 0 && !showCheckAllMange.value;
|
||||
} else {
|
||||
return (departType.value === 0 || departType.value === '0' || departType.value === 1 || departType.value === '1')
|
||||
&& selectedDeparts.value.length > 0 && !showCheckAllMange.value;
|
||||
}
|
||||
});
|
||||
|
||||
const showCheckAllBusiness = computed(() => {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
return businessesDeparts.value.length > 0 && !businessesDeparts.value.every((item) => selectedDeparts.value.some((i) => i.id === item.id));
|
||||
} else {
|
||||
return (departType.value === 0 || departType.value === '0' || departType.value === 1 || departType.value === '1')
|
||||
&& businessesDeparts.value.length > 0
|
||||
&& !businessesDeparts.value.every((item) => selectedDeparts.value.some((i) => i.id === item.id));
|
||||
}
|
||||
});
|
||||
|
||||
const showCancelCheckAllBusiness = computed(() => {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
return selectedDeparts.value.length > 0 && !showCheckAllBusiness.value;
|
||||
} else {
|
||||
return (departType.value === 0 || departType.value === '0' || departType.value === 1 || departType.value === '1')
|
||||
&& selectedDeparts.value.length > 0 && !showCheckAllBusiness.value;
|
||||
}
|
||||
});
|
||||
|
||||
const showCheckAllCurrentSubDepart = computed(() => {
|
||||
return (props.role === 2 || props.role === '2')
|
||||
&& (departType.value === 2 || departType.value === '2' || departType.value === 3 || departType.value === '3')
|
||||
&& subDeparts.value.length > 0
|
||||
&& !subDeparts.value.every((item) => selectedDeparts.value.some((i) => i.id === item.id));
|
||||
});
|
||||
|
||||
const showCancelCheckAllCurrentSubDepart = computed(() => {
|
||||
return (props.role === 2 || props.role === '2')
|
||||
&& (departType.value === 2 || departType.value === '2' || departType.value === 3 || departType.value === '3')
|
||||
&& selectedDeparts.value.length > 0
|
||||
&& !showCheckAllCurrentSubDepart.value;
|
||||
});
|
||||
|
||||
watch(
|
||||
selectedDeparts,
|
||||
() => {
|
||||
setDefaultCheckedKeys();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.excludeDeparts,
|
||||
() => {
|
||||
loadDepart();
|
||||
}
|
||||
);
|
||||
|
||||
function emitChanges(payload: { mainDeparts?: DepartTreeNode[]; assistDeparts?: DepartTreeNode[]; readDeparts?: DepartTreeNode[] }) {
|
||||
if (payload.mainDeparts) emit('update:selectedMainDeparts', payload.mainDeparts);
|
||||
if (payload.assistDeparts) emit('update:selectedAssistDeparts', payload.assistDeparts);
|
||||
if (payload.readDeparts) emit('update:selectedReadDeparts', payload.readDeparts);
|
||||
}
|
||||
|
||||
function getFromInfo(treeKey: string, assignType: number) {
|
||||
let fromArray: DepartTreeNode[] = [];
|
||||
let fromKey: string;
|
||||
if (assignType === 1) {
|
||||
fromArray = [...props.selectedMainDeparts];
|
||||
fromKey = 'mainDeparts';
|
||||
} else if (assignType === 2) {
|
||||
fromArray = [...props.selectedAssistDeparts];
|
||||
fromKey = 'assistDeparts';
|
||||
} else {
|
||||
fromArray = [...props.selectedReadDeparts];
|
||||
fromKey = 'readDeparts';
|
||||
}
|
||||
const fromIdx = fromArray.findIndex((item) => item.key === treeKey);
|
||||
return { fromArray, fromIdx, fromKey };
|
||||
}
|
||||
|
||||
function leaderTreeRender(tree: DepartTreeNode[]) {
|
||||
let childDistributable = false;
|
||||
let res: DepartTreeNode[] = [];
|
||||
tree.forEach((item) => {
|
||||
if (item.distributable === 1) {
|
||||
childDistributable = true;
|
||||
if (item.children) {
|
||||
const r = leaderTreeRender(item.children);
|
||||
if (r.res) {
|
||||
item.children = r.res;
|
||||
} else {
|
||||
item.children = undefined;
|
||||
}
|
||||
}
|
||||
res.push(item);
|
||||
} else {
|
||||
if (item.children) {
|
||||
item.disableCheckbox = true;
|
||||
const r = leaderTreeRender(item.children);
|
||||
if (r.res && r.childDistributable) {
|
||||
item.children = r.res;
|
||||
res.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return { childDistributable, res };
|
||||
}
|
||||
|
||||
function manageTreeRender(tree: DepartTreeNode[]) {
|
||||
let childDistributable = false;
|
||||
let res: DepartTreeNode[] = [];
|
||||
tree.forEach((item) => {
|
||||
if (item.distributable === 1 && item.id === props.departId) {
|
||||
childDistributable = true;
|
||||
item.disableCheckbox = true;
|
||||
res.push(item);
|
||||
} else if (item.distributable === 1 && (String(item.departType) === '1' || String(item.departType) === '2')) {
|
||||
childDistributable = true;
|
||||
if (item.children) {
|
||||
const r = manageTreeRender(item.children);
|
||||
if (r.res) {
|
||||
item.children = r.res;
|
||||
} else {
|
||||
item.children = undefined;
|
||||
}
|
||||
}
|
||||
res.push(item);
|
||||
} else {
|
||||
if (item.children) {
|
||||
item.disableCheckbox = true;
|
||||
const r = manageTreeRender(item.children);
|
||||
if (r.res && r.childDistributable) {
|
||||
item.children = r.res;
|
||||
res.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return { childDistributable, res };
|
||||
}
|
||||
|
||||
function businessesRender(tree: DepartTreeNode[]) {
|
||||
let childDistributable = false;
|
||||
let res: DepartTreeNode[] = [];
|
||||
tree.forEach((item) => {
|
||||
if (item.distributable === 1 && item.id === props.departId) {
|
||||
childDistributable = true;
|
||||
defaultExpandedKeys.value.push(item.key);
|
||||
item.disableCheckbox = true;
|
||||
res.push(item);
|
||||
} else {
|
||||
if (item.children) {
|
||||
item.disableCheckbox = true;
|
||||
const r = businessesRender(item.children);
|
||||
if (r.res && r.childDistributable) {
|
||||
item.children = r.res;
|
||||
res.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return { childDistributable, res };
|
||||
}
|
||||
|
||||
function subDepartsRender(tree: DepartTreeNode[]) {
|
||||
let childDistributable = false;
|
||||
let res: DepartTreeNode[] = [];
|
||||
tree.forEach((item) => {
|
||||
if (item.children && item.children.some((child) => child.id === props.departId)) {
|
||||
childDistributable = true;
|
||||
defaultExpandedKeys.value.push(item.key);
|
||||
item.disableCheckbox = true;
|
||||
res.push(item);
|
||||
} else {
|
||||
if (item.children) {
|
||||
item.disableCheckbox = true;
|
||||
const r = subDepartsRender(item.children);
|
||||
if (r.res && r.childDistributable) {
|
||||
item.children = r.res;
|
||||
res.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return { childDistributable, res };
|
||||
}
|
||||
|
||||
function excludeDepartsTree(tree: DepartTreeNode[]) {
|
||||
const excludes = props.excludeDeparts;
|
||||
tree.forEach((item) => {
|
||||
if (excludes.includes(item.id)) {
|
||||
item.disableCheckbox = true;
|
||||
}
|
||||
if (item.children) {
|
||||
excludeDepartsTree(item.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderMainAssistRead() {
|
||||
recurTree(treeData.value, (node) => {
|
||||
selectedDeparts.value.some((item) => {
|
||||
if (item.key === node.key) {
|
||||
node.assignType = item.assignType;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadDepart() {
|
||||
try {
|
||||
const res = await queryTreeList();
|
||||
let arr = [...(res as any)];
|
||||
recurTree(arr, (node) => {
|
||||
if (node.distributable === undefined || node.distributable === null) {
|
||||
node.distributable = 1;
|
||||
}
|
||||
if (node.id === props.departId) {
|
||||
departType.value = node.departType;
|
||||
}
|
||||
});
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
treeData.value = leaderTreeRender(arr).res;
|
||||
} else {
|
||||
if (departType.value === 0 || departType.value === '0') {
|
||||
treeData.value = leaderTreeRender(arr).res;
|
||||
} else if (departType.value === 1 || departType.value === '1') {
|
||||
treeData.value = manageTreeRender(arr).res;
|
||||
} else if (departType.value === 2 || departType.value === '2') {
|
||||
treeData.value = businessesRender(arr).res;
|
||||
} else if (departType.value === 3 || departType.value === '3') {
|
||||
treeData.value = subDepartsRender(arr).res;
|
||||
}
|
||||
}
|
||||
excludeDepartsTree(treeData.value);
|
||||
setDefaultCheckedKeys();
|
||||
treeData.value.forEach((item) => {
|
||||
defaultExpandedKeys.value.push(item.key);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('loadDepart error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function onDepartCheck(checkedKeysValue: any, e: { checked: boolean; checkedNodes: any[]; node: any; event: Event }) {
|
||||
const node = e.node.dataRef || e.node;
|
||||
node.assignType = 3;
|
||||
let mainDeparts = [...props.selectedMainDeparts];
|
||||
let assistDeparts = [...props.selectedAssistDeparts];
|
||||
let readDeparts = [...props.selectedReadDeparts];
|
||||
if (e.checked) {
|
||||
if (node.assignType === 1) {
|
||||
mainDeparts.push(node);
|
||||
} else if (node.assignType === 2) {
|
||||
assistDeparts.push(node);
|
||||
} else {
|
||||
readDeparts.push(node);
|
||||
}
|
||||
} else {
|
||||
deleteIfExist(mainDeparts, node, 'id');
|
||||
deleteIfExist(assistDeparts, node, 'id');
|
||||
deleteIfExist(readDeparts, node, 'id');
|
||||
}
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function setDefaultCheckedKeys() {
|
||||
checkedKeys.value = { checked: selectedDeparts.value.map((item) => item.key), halfChecked: [] };
|
||||
renderMainAssistRead();
|
||||
}
|
||||
|
||||
function toggleCheck(treeKey: string, assignType: number) {
|
||||
const selectedNode = recurTree(treeData.value, (node) => {
|
||||
if (node.id === treeKey) return [node];
|
||||
return [];
|
||||
});
|
||||
let readDeparts = [...props.selectedReadDeparts];
|
||||
if (checkedKeys.value.checked.includes(treeKey)) {
|
||||
const { fromArray, fromIdx, fromKey } = getFromInfo(treeKey, assignType);
|
||||
fromArray.splice(fromIdx, 1);
|
||||
emitChanges({ [fromKey]: fromArray });
|
||||
} else {
|
||||
selectedNode[0].assignType = 3;
|
||||
readDeparts.push(selectedNode[0]);
|
||||
emitChanges({ readDeparts });
|
||||
}
|
||||
}
|
||||
|
||||
function checkAllDepart() {
|
||||
const allDepart = recurTree(treeData.value, (node) => {
|
||||
if (!node.disableCheckbox && (String(node.departType) === '1' || String(node.departType) === '2')) {
|
||||
node.assignType = checkAllAssignType.value;
|
||||
return [node];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
let mainDeparts: DepartTreeNode[] = [];
|
||||
let assistDeparts: DepartTreeNode[] = [];
|
||||
let readDeparts: DepartTreeNode[] = [];
|
||||
if (checkAllAssignType.value === 1) {
|
||||
mainDeparts = allDepart;
|
||||
} else if (checkAllAssignType.value === 2) {
|
||||
assistDeparts = allDepart;
|
||||
} else if (checkAllAssignType.value === 3) {
|
||||
readDeparts = allDepart;
|
||||
}
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function uncheckAllDepart() {
|
||||
emitChanges({ mainDeparts: [], assistDeparts: [], readDeparts: [] });
|
||||
}
|
||||
|
||||
function checkAllManageDepart() {
|
||||
const allManageDepart = recurTree(treeData.value, (node) => {
|
||||
if (!node.disableCheckbox && String(node.departType) === '1') {
|
||||
node.assignType = checkAllManageAssignType.value;
|
||||
return [node];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
let mainDeparts = [...props.selectedMainDeparts];
|
||||
let assistDeparts = [...props.selectedAssistDeparts];
|
||||
let readDeparts = [...props.selectedReadDeparts];
|
||||
if (checkAllManageAssignType.value === 1) {
|
||||
mainDeparts = allManageDepart.concat(props.selectedMainDeparts);
|
||||
} else if (checkAllManageAssignType.value === 2) {
|
||||
assistDeparts = allManageDepart.concat(props.selectedAssistDeparts);
|
||||
} else if (checkAllManageAssignType.value === 3) {
|
||||
readDeparts = allManageDepart.concat(props.selectedReadDeparts);
|
||||
}
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function uncheckAllManageDepart() {
|
||||
const allManageDepart = recurTree(treeData.value, (node) => {
|
||||
if (String(node.departType) === '1') return [node];
|
||||
return [];
|
||||
});
|
||||
const mainDeparts = props.selectedMainDeparts.filter((item) => !allManageDepart.some((manage) => item.id === manage.id));
|
||||
const assistDeparts = props.selectedAssistDeparts.filter((item) => !allManageDepart.some((manage) => item.id === manage.id));
|
||||
const readDeparts = props.selectedReadDeparts.filter((item) => !allManageDepart.some((manage) => item.id === manage.id));
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function checkAllBusinessesDepart() {
|
||||
const allBusinessesDepart = recurTree(treeData.value, (node) => {
|
||||
if (!node.disableCheckbox && String(node.departType) === '2') {
|
||||
node.assignType = checkAllBusinessAssignType.value;
|
||||
return [node];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
let mainDeparts = [...props.selectedMainDeparts];
|
||||
let assistDeparts = [...props.selectedAssistDeparts];
|
||||
let readDeparts = [...props.selectedReadDeparts];
|
||||
if (checkAllBusinessAssignType.value === 1) {
|
||||
mainDeparts = allBusinessesDepart.concat(props.selectedMainDeparts);
|
||||
} else if (checkAllBusinessAssignType.value === 2) {
|
||||
assistDeparts = allBusinessesDepart.concat(props.selectedAssistDeparts);
|
||||
} else if (checkAllBusinessAssignType.value === 3) {
|
||||
readDeparts = allBusinessesDepart.concat(props.selectedReadDeparts);
|
||||
}
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function uncheckAllBusinessesDepart() {
|
||||
const allBusinessesDepart = recurTree(treeData.value, (node) => {
|
||||
if (String(node.departType) === '2') return [node];
|
||||
return [];
|
||||
});
|
||||
const mainDeparts = props.selectedMainDeparts.filter((item) => !allBusinessesDepart.some((manage) => item.id === manage.id));
|
||||
const assistDeparts = props.selectedAssistDeparts.filter((item) => !allBusinessesDepart.some((manage) => item.id === manage.id));
|
||||
const readDeparts = props.selectedReadDeparts.filter((item) => !allBusinessesDepart.some((manage) => item.id === manage.id));
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function checkAllCurrentSubDepart() {
|
||||
const allSubDepart = recurTree(treeData.value, (node) => {
|
||||
if (!node.disableCheckbox && String(node.departType) === '3') {
|
||||
node.assignType = checkAllCurrentSubDepartAssignType.value;
|
||||
return [node];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
let mainDeparts = [...props.selectedMainDeparts];
|
||||
let assistDeparts = [...props.selectedAssistDeparts];
|
||||
let readDeparts = [...props.selectedReadDeparts];
|
||||
if (checkAllCurrentSubDepartAssignType.value === 1) {
|
||||
mainDeparts = allSubDepart.concat(props.selectedMainDeparts);
|
||||
} else if (checkAllCurrentSubDepartAssignType.value === 2) {
|
||||
assistDeparts = allSubDepart.concat(props.selectedAssistDeparts);
|
||||
} else if (checkAllCurrentSubDepartAssignType.value === 3) {
|
||||
readDeparts = allSubDepart.concat(props.selectedReadDeparts);
|
||||
}
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function uncheckAllCurrentSubDepart() {
|
||||
const allCurrentSubDepart = recurTree(treeData.value, (node) => {
|
||||
if (String(node.departType) === '3') return [node];
|
||||
return [];
|
||||
});
|
||||
const mainDeparts = props.selectedMainDeparts.filter((item) => !allCurrentSubDepart.some((manage) => item.id === manage.id));
|
||||
const assistDeparts = props.selectedAssistDeparts.filter((item) => !allCurrentSubDepart.some((manage) => item.id === manage.id));
|
||||
const readDeparts = props.selectedReadDeparts.filter((item) => !allCurrentSubDepart.some((manage) => item.id === manage.id));
|
||||
emitChanges({ mainDeparts, assistDeparts, readDeparts });
|
||||
}
|
||||
|
||||
function checkMainDepart(treeKey: string, assignType: number) {
|
||||
const selected = props.selectedMainDeparts.some((item) => item.key === treeKey);
|
||||
if (!selected) {
|
||||
const { fromArray, fromIdx, fromKey } = getFromInfo(treeKey, assignType);
|
||||
const toArray = [...props.selectedMainDeparts];
|
||||
recurTree(treeData.value, (node) => {
|
||||
if (node.key === treeKey) {
|
||||
node.assignType = 1;
|
||||
toArray.push(node);
|
||||
fromArray.splice(fromIdx, 1);
|
||||
}
|
||||
});
|
||||
emitChanges({ [fromKey]: fromArray, mainDeparts: toArray });
|
||||
}
|
||||
}
|
||||
|
||||
function checkAssistDepart(treeKey: string, assignType: number) {
|
||||
const selected = props.selectedAssistDeparts.some((item) => item.key === treeKey);
|
||||
if (!selected) {
|
||||
const { fromArray, fromIdx, fromKey } = getFromInfo(treeKey, assignType);
|
||||
const toArray = [...props.selectedAssistDeparts];
|
||||
recurTree(treeData.value, (node) => {
|
||||
if (node.key === treeKey) {
|
||||
node.assignType = 2;
|
||||
toArray.push(node);
|
||||
fromArray.splice(fromIdx, 1);
|
||||
}
|
||||
});
|
||||
emitChanges({ [fromKey]: fromArray, assistDeparts: toArray });
|
||||
}
|
||||
}
|
||||
|
||||
function checkReadDepart(treeKey: string, assignType: number) {
|
||||
const selected = props.selectedReadDeparts.some((item) => item.key === treeKey);
|
||||
if (!selected) {
|
||||
const { fromArray, fromIdx, fromKey } = getFromInfo(treeKey, assignType);
|
||||
const toArray = [...props.selectedReadDeparts];
|
||||
recurTree(treeData.value, (node) => {
|
||||
if (node.key === treeKey) {
|
||||
node.assignType = 3;
|
||||
toArray.push(node);
|
||||
fromArray.splice(fromIdx, 1);
|
||||
}
|
||||
});
|
||||
emitChanges({ [fromKey]: fromArray, readDeparts: toArray });
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDepart();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.my-dept-select-tree {
|
||||
height: 470px;
|
||||
&.fullscreen {
|
||||
height: calc(100vh - 250px);
|
||||
}
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.btn-red {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
.btn-blue {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
.btn-green {
|
||||
background-color: #52c41a;
|
||||
color: #fff;
|
||||
border-color: #52c41a;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,411 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-row>
|
||||
<a-col :md="12" :sm="24">
|
||||
<div style="height: 100%; border-right: 1px solid #e8e8e8; padding-right: 16px; margin-right: 16px">
|
||||
<a-input-search v-model:value="searchDepartValue" style="margin-bottom: 16px" placeholder="请输入部门名称" />
|
||||
<a-directory-tree
|
||||
selectable
|
||||
v-model:selectedKeys="selectedDepIds"
|
||||
:check-strictly="true"
|
||||
:dropdown-style="{ maxHeight: '200px', overflow: 'auto' }"
|
||||
:tree-data="filterDepartTreeData"
|
||||
:expand-action="false"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
@select="onDepSelect"
|
||||
style="height: 470px; overflow: auto"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24">
|
||||
<a-input-search
|
||||
:style="{ width: '100px', marginBottom: '15px' }"
|
||||
placeholder="输入姓名"
|
||||
v-model:value="queryParam.realname"
|
||||
@search="onSearch"
|
||||
/>
|
||||
<a-button @click="searchReset(1)" style="margin-left: 8px">重置</a-button>
|
||||
<a-button type="primary" @click="selectAll" style="margin-left: 8px">全选</a-button>
|
||||
<a-button type="danger" @click="unselectAll" style="margin-left: 8px">清空</a-button>
|
||||
<a-empty v-if="selectedDepIds.length === 0" description="请先选择部门!" />
|
||||
<a-table
|
||||
v-else
|
||||
:scroll="{ y: 460, ...scrollTrigger }"
|
||||
size="middle"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:data-source="dataSource"
|
||||
:pagination="ipagination"
|
||||
:row-selection="{ selectedRowKeys: selectedRowKeys, onSelectAll: onSelectAll, onSelect: onSelect, type: getType }"
|
||||
:loading="loading"
|
||||
@change="handleTableChange"
|
||||
:custom-row="customRow"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import { queryTreeList } from '/@/api/common/api';
|
||||
import { defHttp } from '/@/utils/http/axios';
|
||||
import { pushIfNotExist, filterObj } from '/@/utils/common/compUtils';
|
||||
import { deleteIfExist, recurTree, filterDepartTree } from './SzDistribute.api';
|
||||
import type { UserItem, DepartTreeNode } from './SzDistribute.api';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
secLevel: any;
|
||||
checkedUser?: UserItem[];
|
||||
selectedDepId?: string;
|
||||
departId: string;
|
||||
role: string | number;
|
||||
isSpecial: number;
|
||||
multi?: boolean;
|
||||
}>(),
|
||||
{
|
||||
checkedUser: () => [],
|
||||
selectedDepId: '',
|
||||
multi: true,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:checkedUser', 'update:selectedDepId']);
|
||||
|
||||
const searchDepartValue = ref('');
|
||||
const queryParam = reactive<Record<string, any>>({ realname: '' });
|
||||
const dataSource = ref<UserItem[]>([]);
|
||||
const allUserList = ref<UserItem[]>([]);
|
||||
const selectedDepIds = ref<string[]>([]);
|
||||
const selectedRows = ref<UserItem[]>([]);
|
||||
const departTree = ref<DepartTreeNode[]>([]);
|
||||
const fullDepartTree = ref<DepartTreeNode[]>([]);
|
||||
const loading = ref(false);
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
const scrollTrigger = ref<Record<string, any>>({});
|
||||
const isTreeLoaded = ref(false);
|
||||
|
||||
function findDepartIdByOrgCode(tree: DepartTreeNode[], orgCode: string): string | null {
|
||||
for (const node of tree) {
|
||||
if (node.orgCode === orgCode) {
|
||||
return node.id;
|
||||
}
|
||||
if (node.children && node.children.length > 0) {
|
||||
const found = findDepartIdByOrgCode(node.children, orgCode);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveDepartId(departIdOrOrgCode: string): string {
|
||||
if (!departIdOrOrgCode) return departIdOrOrgCode;
|
||||
const uuidPattern = /^[0-9a-f]{32}$/i;
|
||||
if (uuidPattern.test(departIdOrOrgCode.replace(/-/g, ''))) return departIdOrOrgCode;
|
||||
const realId = findDepartIdByOrgCode(fullDepartTree.value, departIdOrOrgCode);
|
||||
if (realId) {
|
||||
console.log(`resolveDepartId: "${departIdOrOrgCode}" (orgCode) -> "${realId}" (UUID)`);
|
||||
return realId;
|
||||
}
|
||||
console.warn(`resolveDepartId: cannot find UUID for orgCode "${departIdOrOrgCode}", using as-is`);
|
||||
return departIdOrOrgCode;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{ title: '用户姓名', align: 'center' as const, dataIndex: 'realname' },
|
||||
{ title: '人员密级', align: 'center' as const, dataIndex: 'userSecurityLevel_dictText' },
|
||||
];
|
||||
|
||||
const ipagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 7,
|
||||
showQuickJumper: true,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const isorter = reactive({
|
||||
column: 'createTime',
|
||||
order: 'desc',
|
||||
});
|
||||
|
||||
const url = {
|
||||
pageUserDepart: '/sys/user/queryUserComponentData',
|
||||
listUserDepart: '/sys/user/queryUserComponentData',
|
||||
};
|
||||
|
||||
const getType = computed(() => (props.multi ? 'checkbox' : 'radio'));
|
||||
|
||||
const selectedRowKeys = computed(() => selectedRows.value.map((item) => item.id));
|
||||
|
||||
const filterDepartTreeData = computed(() => {
|
||||
if (!searchDepartValue.value) return departTree.value;
|
||||
return filterDepartTree(departTree.value, searchDepartValue.value);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.checkedUser,
|
||||
() => {
|
||||
selectedRows.value = [...(props.checkedUser || [])];
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.departId,
|
||||
() => {
|
||||
queryDepartTree();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.role,
|
||||
() => {
|
||||
queryDepartTree();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.secLevel,
|
||||
() => {
|
||||
if (selectedDepIds.value.length > 0) {
|
||||
getAllUsersInDepart();
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.selectedDepId,
|
||||
(newVal) => {
|
||||
if (newVal && (props.role === 1 || props.role === '1')) {
|
||||
selectedDepIds.value = [resolveDepartId(newVal)];
|
||||
getAllUsersInDepart();
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function initData() {
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
const depId = props.selectedDepId || props.departId;
|
||||
if (depId) {
|
||||
selectedDepIds.value = [resolveDepartId(depId)];
|
||||
getAllUsersInDepart();
|
||||
loadData();
|
||||
}
|
||||
} else {
|
||||
const resolvedId = resolveDepartId(props.departId);
|
||||
selectedDepIds.value = [resolvedId];
|
||||
getAllUsersInDepart();
|
||||
loadData();
|
||||
}
|
||||
if (props.checkedUser) {
|
||||
selectedRows.value = [...props.checkedUser];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(arg?: number) {
|
||||
if (selectedDepIds.value.length > 0) {
|
||||
if (arg === 1) {
|
||||
ipagination.current = 1;
|
||||
}
|
||||
const params = getQueryParams();
|
||||
loading.value = true;
|
||||
try {
|
||||
const res: any = await defHttp.get({ url: url.pageUserDepart, params });
|
||||
dataSource.value = (res.records || []).filter((user: UserItem) => {
|
||||
const userLevel = user.userSecurityLevel;
|
||||
const docLevel = props.secLevel;
|
||||
return userLevel !== null && userLevel > docLevel;
|
||||
});
|
||||
ipagination.total = res.total;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryParams() {
|
||||
const param = Object.assign({}, queryParam, isorter);
|
||||
param.searchSecurityLevel = props.secLevel;
|
||||
param.field = getQueryField();
|
||||
param.pageNo = ipagination.current;
|
||||
param.pageSize = ipagination.pageSize;
|
||||
param.departId = selectedDepIds.value.join(',');
|
||||
param.isSpecial = props.isSpecial;
|
||||
return filterObj(param);
|
||||
}
|
||||
|
||||
function getQueryField() {
|
||||
let str = 'id,';
|
||||
for (let a = 0; a < columns.length; a++) {
|
||||
str += ',' + columns[a].dataIndex;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function searchReset(num?: number) {
|
||||
if (num !== 0) {
|
||||
queryParam.realname = '';
|
||||
loadData(1);
|
||||
}
|
||||
}
|
||||
|
||||
function handleTableChange(pagination: any, _filters: any, sorter: any) {
|
||||
if (Object.keys(sorter).length > 0) {
|
||||
isorter.column = sorter.field;
|
||||
isorter.order = sorter.order === 'ascend' ? 'asc' : 'desc';
|
||||
}
|
||||
Object.assign(ipagination, pagination);
|
||||
loadData();
|
||||
}
|
||||
|
||||
async function getAllUsersInDepart() {
|
||||
if (selectedDepIds.value.length === 0 || (selectedDepIds.value.length === 1 && !selectedDepIds.value[0])) {
|
||||
console.warn('getAllUsersInDepart: selectedDepIds is empty, skip loading');
|
||||
allUserList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const param = Object.assign({}, queryParam);
|
||||
param.searchSecurityLevel = props.secLevel;
|
||||
param.field = getQueryField();
|
||||
param.departId = selectedDepIds.value.join(',');
|
||||
|
||||
param.isSpecial = props.isSpecial;
|
||||
param.pageNo = 1;
|
||||
param.pageSize = 9999;
|
||||
const filteredParam = filterObj(param);
|
||||
try {
|
||||
const res: any = await defHttp.get({ url: url.listUserDepart, params: filteredParam });
|
||||
allUserList.value = (res.records || res).filter((user: UserItem) => {
|
||||
const userLevel = user.userSecurityLevel;
|
||||
const docLevel = props.secLevel;
|
||||
return userLevel !== null && userLevel > docLevel;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('getAllUsersInDepart error:', e);
|
||||
allUserList.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
function onDepSelect(keys: string[]) {
|
||||
if (keys.length > 0 && keys[0] != null) {
|
||||
selectedDepIds.value = [keys[0]];
|
||||
emit('update:selectedDepId', keys[0]);
|
||||
loadData(1);
|
||||
getAllUsersInDepart();
|
||||
}
|
||||
}
|
||||
|
||||
function onSelect(record: UserItem, selected: boolean) {
|
||||
if (selected) {
|
||||
pushIfNotExist(selectedRows.value, record, 'id');
|
||||
} else {
|
||||
deleteIfExist(selectedRows.value, record, 'id');
|
||||
}
|
||||
selectedRows.value.sort((a, b) => (a.sortno || 0) - (b.sortno || 0));
|
||||
emit('update:checkedUser', selectedRows.value);
|
||||
}
|
||||
|
||||
function onSelectAll(selected: boolean, selectedRecords: UserItem[], changeRecords: UserItem[]) {
|
||||
if (selected) {
|
||||
selectedRecords.forEach((item) => {
|
||||
pushIfNotExist(selectedRows.value, item, 'id');
|
||||
});
|
||||
} else {
|
||||
const rowsToRemove = changeRecords && changeRecords.length > 0 ? changeRecords : dataSource.value;
|
||||
rowsToRemove.forEach((item) => {
|
||||
deleteIfExist(selectedRows.value, item, 'id');
|
||||
});
|
||||
}
|
||||
selectedRows.value.sort((a, b) => (a.sortno || 0) - (b.sortno || 0));
|
||||
emit('update:checkedUser', selectedRows.value);
|
||||
}
|
||||
|
||||
function onSearch() {
|
||||
loadData(1);
|
||||
}
|
||||
|
||||
async function queryDepartTree() {
|
||||
expandedKeys.value = [];
|
||||
departTree.value = [];
|
||||
try {
|
||||
const res: any = await queryTreeList();
|
||||
const arr = [...res];
|
||||
const departId = props.departId;
|
||||
|
||||
fullDepartTree.value = [...arr];
|
||||
|
||||
if (props.role === 1 || props.role === '1') {
|
||||
departTree.value = [...arr];
|
||||
} else {
|
||||
const resolvedDepartId = resolveDepartId(departId);
|
||||
const tree = recurTree(arr, (node) => {
|
||||
if (node.id === resolvedDepartId) return [node];
|
||||
return [];
|
||||
});
|
||||
departTree.value = [...tree];
|
||||
}
|
||||
departTree.value.forEach((item) => {
|
||||
expandedKeys.value.push(item.key);
|
||||
});
|
||||
isTreeLoaded.value = true;
|
||||
await initData();
|
||||
} catch (e) {
|
||||
console.error('queryDepartTree error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
allUserList.value.forEach((item) => {
|
||||
pushIfNotExist(selectedRows.value, item, 'id');
|
||||
});
|
||||
selectedRows.value.sort((a, b) => (a.sortno || 0) - (b.sortno || 0));
|
||||
emit('update:checkedUser', selectedRows.value);
|
||||
}
|
||||
|
||||
function unselectAll() {
|
||||
allUserList.value.forEach((item) => {
|
||||
deleteIfExist(selectedRows.value, item, 'id');
|
||||
});
|
||||
emit('update:checkedUser', selectedRows.value);
|
||||
}
|
||||
|
||||
function customRow(record: UserItem) {
|
||||
return {
|
||||
onClick: (_event: Event) => {
|
||||
const clickRowKey = record.id;
|
||||
if (selectedRowKeys.value.includes(clickRowKey)) {
|
||||
deleteIfExist(selectedRows.value, record, 'id');
|
||||
} else {
|
||||
pushIfNotExist(selectedRows.value, record, 'id');
|
||||
}
|
||||
selectedRows.value.sort((a, b) => (a.sortno || 0) - (b.sortno || 0));
|
||||
emit('update:checkedUser', selectedRows.value);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
resetScreenSize();
|
||||
await queryDepartTree();
|
||||
});
|
||||
|
||||
function resetScreenSize() {
|
||||
const screenWidth = document.body.clientWidth;
|
||||
if (screenWidth < 500) {
|
||||
scrollTrigger.value = { x: 800 };
|
||||
} else {
|
||||
scrollTrigger.value = {};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ant-table-tbody .ant-table-row td {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<div style="margin-left: 1px">
|
||||
<a-empty v-if="!haveSelected" />
|
||||
<div v-else>
|
||||
<a-button-group v-for="(depart, index) in selectedMainDeparts" :key="'main-' + depart.id + '-' + index" class="small-space selected-depart">
|
||||
<a-dropdown>
|
||||
<template v-if="!disabled" #overlay>
|
||||
<a-menu @click="handMainDepartAssignType($event, index)">
|
||||
<a-menu-item key="1">办</a-menu-item>
|
||||
<a-menu-item key="2">协</a-menu-item>
|
||||
<a-menu-item key="3">阅</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button class="btn-red" size="small">
|
||||
<ApartmentOutlined />
|
||||
办
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popover trigger="click">
|
||||
<template #content>
|
||||
<a-descriptions title="部门信息" :column="1" style="width: 300px">
|
||||
<a-descriptions-item label="部门名">{{ depart.title }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否有子部门">{{ depart.children ? '是' : '否' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="部门描述">{{ depart.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<a-button class="btn-red" size="small">{{ depart.title }}</a-button>
|
||||
</a-popover>
|
||||
<a-button v-if="!disabled" class="btn-red" size="small" @click="deleteSelectedMainDepart(index)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
|
||||
<a-button-group v-for="(depart, index) in selectedAssistDeparts" :key="'assist-' + depart.id + '-' + index" class="small-space selected-depart">
|
||||
<a-dropdown>
|
||||
<template v-if="!disabled" #overlay>
|
||||
<a-menu @click="handAssistDepartAssignType($event, index)">
|
||||
<a-menu-item key="1">办</a-menu-item>
|
||||
<a-menu-item key="2">协</a-menu-item>
|
||||
<a-menu-item key="3">阅</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button class="btn-blue" size="small">
|
||||
<ApartmentOutlined />
|
||||
协
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popover trigger="click">
|
||||
<template #content>
|
||||
<a-descriptions title="部门信息" :column="1" style="width: 300px">
|
||||
<a-descriptions-item label="部门名">{{ depart.title }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否有子部门">{{ depart.children ? '是' : '否' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="部门描述">{{ depart.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<a-button class="btn-blue" size="small">{{ depart.title }}</a-button>
|
||||
</a-popover>
|
||||
<a-button v-if="!disabled" class="btn-blue" size="small" @click="deleteSelectedAssistDepart(index)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
|
||||
<a-button-group v-for="(depart, index) in selectedReadDeparts" :key="'read-' + depart.id + '-' + index" class="small-space selected-depart">
|
||||
<a-dropdown>
|
||||
<template v-if="!disabled" #overlay>
|
||||
<a-menu @click="handReadDepartAssignType($event, index)">
|
||||
<a-menu-item key="1">办</a-menu-item>
|
||||
<a-menu-item key="2">协</a-menu-item>
|
||||
<a-menu-item key="3">阅</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button class="btn-green" size="small">
|
||||
<ApartmentOutlined />
|
||||
阅
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popover trigger="click">
|
||||
<template #content>
|
||||
<a-descriptions title="部门信息" :column="1" style="width: 300px">
|
||||
<a-descriptions-item label="部门名">{{ depart.title }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否有子部门">{{ depart.children ? '是' : '否' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="部门描述">{{ depart.description }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<a-button class="btn-green" size="small">{{ depart.title }}</a-button>
|
||||
</a-popover>
|
||||
<a-button v-if="!disabled" class="btn-green" size="small" @click="deleteSelectedReadDepart(index)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
|
||||
<a-button-group v-for="(user, index) in selectedUsers" :key="'user-' + user.id + '-' + index" class="small-space selected-user-do">
|
||||
<a-dropdown>
|
||||
<template v-if="!disabled" #overlay>
|
||||
<a-menu @click="handUserAssignType($event, index)">
|
||||
<a-menu-item key="1">办</a-menu-item>
|
||||
<a-menu-item key="3">阅</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button class="btn-red" size="small">
|
||||
<UserOutlined />
|
||||
办
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popover trigger="click">
|
||||
<template #content>
|
||||
<a-descriptions title="人员信息" :column="1" style="width: 300px">
|
||||
<a-descriptions-item label="姓名">{{ user.realname }}</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">{{ user.sex_dictText }}</a-descriptions-item>
|
||||
<a-descriptions-item label="密级">{{ user.userSecurityLevel_dictText }}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属部门">{{ user.orgCodeTxt }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<a-button class="btn-red" size="small">{{ user.realname }}</a-button>
|
||||
</a-popover>
|
||||
<a-button v-if="!disabled" class="btn-red" size="small" @click="deleteSelectedUsers(index)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
|
||||
<a-button-group v-for="(user, index) in selectedReadUsers" :key="'readuser-' + user.id + '-' + index" class="small-space selected-user-do">
|
||||
<a-dropdown>
|
||||
<template v-if="!disabled" #overlay>
|
||||
<a-menu @click="handReadUserAssignType($event, index)">
|
||||
<a-menu-item key="1">办</a-menu-item>
|
||||
<a-menu-item key="3">阅</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<a-button class="btn-green" size="small">
|
||||
<UserOutlined />
|
||||
阅
|
||||
</a-button>
|
||||
</a-dropdown>
|
||||
<a-popover trigger="click">
|
||||
<template #content>
|
||||
<a-descriptions title="人员信息" :column="1" style="width: 300px">
|
||||
<a-descriptions-item label="姓名">{{ user.realname }}</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">{{ user.sex_dictText }}</a-descriptions-item>
|
||||
<a-descriptions-item label="密级">{{ user.userSecurityLevel_dictText }}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属部门">{{ user.orgCodeTxt }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
<a-button class="btn-green" size="small">{{ user.realname }}</a-button>
|
||||
</a-popover>
|
||||
<a-button v-if="!disabled" class="btn-green" size="small" @click="deleteSelectedReadUser(index)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { ApartmentOutlined, UserOutlined, CloseOutlined } from '@ant-design/icons-vue';
|
||||
import type { DepartTreeNode, UserItem } from './SzDistribute.api';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
disabled?: boolean;
|
||||
selectedMainDeparts: DepartTreeNode[];
|
||||
selectedAssistDeparts: DepartTreeNode[];
|
||||
selectedReadDeparts: DepartTreeNode[];
|
||||
selectedUsers: UserItem[];
|
||||
selectedReadUsers: UserItem[];
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
selectedMainDeparts: () => [],
|
||||
selectedAssistDeparts: () => [],
|
||||
selectedReadDeparts: () => [],
|
||||
selectedUsers: () => [],
|
||||
selectedReadUsers: () => [],
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:selectedMainDeparts',
|
||||
'update:selectedAssistDeparts',
|
||||
'update:selectedReadDeparts',
|
||||
'update:selectedUsers',
|
||||
'update:selectedReadUsers',
|
||||
]);
|
||||
|
||||
const haveSelected = computed(() => {
|
||||
return !(
|
||||
props.selectedMainDeparts.length === 0 &&
|
||||
props.selectedAssistDeparts.length === 0 &&
|
||||
props.selectedReadDeparts.length === 0 &&
|
||||
props.selectedUsers.length === 0 &&
|
||||
props.selectedReadUsers.length === 0
|
||||
);
|
||||
});
|
||||
|
||||
function emitChanges(payload: {
|
||||
mainDeparts?: DepartTreeNode[];
|
||||
assistDeparts?: DepartTreeNode[];
|
||||
readDeparts?: DepartTreeNode[];
|
||||
users?: UserItem[];
|
||||
readUsers?: UserItem[];
|
||||
}) {
|
||||
if (payload.mainDeparts) emit('update:selectedMainDeparts', payload.mainDeparts);
|
||||
if (payload.assistDeparts) emit('update:selectedAssistDeparts', payload.assistDeparts);
|
||||
if (payload.readDeparts) emit('update:selectedReadDeparts', payload.readDeparts);
|
||||
if (payload.users) emit('update:selectedUsers', payload.users);
|
||||
if (payload.readUsers) emit('update:selectedReadUsers', payload.readUsers);
|
||||
}
|
||||
|
||||
function handMainDepartAssignType(e: { key: string }, index: number) {
|
||||
if (e.key === '2') {
|
||||
const fromArray = [...props.selectedMainDeparts];
|
||||
const targetArray = [...props.selectedAssistDeparts];
|
||||
fromArray[index].assignType = 2;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ mainDeparts: fromArray, assistDeparts: targetArray });
|
||||
} else if (e.key === '3') {
|
||||
const fromArray = [...props.selectedMainDeparts];
|
||||
const targetArray = [...props.selectedReadDeparts];
|
||||
fromArray[index].assignType = 3;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ mainDeparts: fromArray, readDeparts: targetArray });
|
||||
}
|
||||
}
|
||||
|
||||
function handAssistDepartAssignType(e: { key: string }, index: number) {
|
||||
if (e.key === '1') {
|
||||
const fromArray = [...props.selectedAssistDeparts];
|
||||
const targetArray = [...props.selectedMainDeparts];
|
||||
fromArray[index].assignType = 1;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ assistDeparts: fromArray, mainDeparts: targetArray });
|
||||
} else if (e.key === '3') {
|
||||
const fromArray = [...props.selectedAssistDeparts];
|
||||
const targetArray = [...props.selectedReadDeparts];
|
||||
fromArray[index].assignType = 3;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ assistDeparts: fromArray, readDeparts: targetArray });
|
||||
}
|
||||
}
|
||||
|
||||
function handReadDepartAssignType(e: { key: string }, index: number) {
|
||||
if (e.key === '1') {
|
||||
const fromArray = [...props.selectedReadDeparts];
|
||||
const targetArray = [...props.selectedMainDeparts];
|
||||
fromArray[index].assignType = 1;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ readDeparts: fromArray, mainDeparts: targetArray });
|
||||
} else if (e.key === '2') {
|
||||
const fromArray = [...props.selectedReadDeparts];
|
||||
const targetArray = [...props.selectedAssistDeparts];
|
||||
fromArray[index].assignType = 2;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ readDeparts: fromArray, assistDeparts: targetArray });
|
||||
}
|
||||
}
|
||||
|
||||
function handUserAssignType(e: { key: string }, index: number) {
|
||||
if (e.key === '3') {
|
||||
const fromArray = [...props.selectedUsers];
|
||||
const targetArray = [...props.selectedReadUsers];
|
||||
fromArray[index].assignType = 3;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ users: fromArray, readUsers: targetArray });
|
||||
}
|
||||
}
|
||||
|
||||
function handReadUserAssignType(e: { key: string }, index: number) {
|
||||
if (e.key === '1') {
|
||||
const fromArray = [...props.selectedReadUsers];
|
||||
const targetArray = [...props.selectedUsers];
|
||||
fromArray[index].assignType = 1;
|
||||
targetArray.push(fromArray[index]);
|
||||
fromArray.splice(index, 1);
|
||||
emitChanges({ readUsers: fromArray, users: targetArray });
|
||||
}
|
||||
}
|
||||
|
||||
function deleteSelectedMainDepart(index: number) {
|
||||
const changeArray = [...props.selectedMainDeparts];
|
||||
changeArray.splice(index, 1);
|
||||
emitChanges({ mainDeparts: changeArray });
|
||||
}
|
||||
|
||||
function deleteSelectedAssistDepart(index: number) {
|
||||
const changeArray = [...props.selectedAssistDeparts];
|
||||
changeArray.splice(index, 1);
|
||||
emitChanges({ assistDeparts: changeArray });
|
||||
}
|
||||
|
||||
function deleteSelectedReadDepart(index: number) {
|
||||
const changeArray = [...props.selectedReadDeparts];
|
||||
changeArray.splice(index, 1);
|
||||
emitChanges({ readDeparts: changeArray });
|
||||
}
|
||||
|
||||
function deleteSelectedUsers(index: number) {
|
||||
const changeArray = [...props.selectedUsers];
|
||||
changeArray.splice(index, 1);
|
||||
emitChanges({ users: changeArray });
|
||||
}
|
||||
|
||||
function deleteSelectedReadUser(index: number) {
|
||||
const changeArray = [...props.selectedReadUsers];
|
||||
changeArray.splice(index, 1);
|
||||
emitChanges({ readUsers: changeArray });
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.small-space {
|
||||
margin: 2px;
|
||||
}
|
||||
.selected-depart {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.selected-user-do {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.btn-red {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
border-color: #ff4d4f;
|
||||
}
|
||||
.btn-blue {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
border-color: #1890ff;
|
||||
}
|
||||
.btn-green {
|
||||
background-color: #52c41a;
|
||||
color: #fff;
|
||||
border-color: #52c41a;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,290 @@
|
||||
# SzDistribute 分发方案组件 使用说明
|
||||
|
||||
## 组件概述
|
||||
|
||||
SzDistribute 是一个公文分发方案组件,支持按部门(主办/协办/阅文)和人员(办文/阅文)进行分发配置,并提供方案保存/加载功能。
|
||||
|
||||
基于 JeecgBoot 3.8.0 + Vue 3 + TypeScript 开发。
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
distributeComponent/
|
||||
├── SzDistribute.vue # 主入口组件(分发方案弹窗)
|
||||
├── SzSelectDepart.vue # 部门选择组件(树形多选 + 办/协/阅标记)
|
||||
├── SzSelectUser.vue # 人员选择组件(部门树 + 人员表格)
|
||||
├── SzSelectedDepartsAndUsers.vue # 已选部门和人员展示组件
|
||||
├── SzDistribute.api.ts # 方案增删改查 API
|
||||
├── types.ts # TypeScript 类型定义
|
||||
├── utils.ts # 工具函数
|
||||
└── USAGE.md # 本文件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<SzDistribute
|
||||
title="分发方案"
|
||||
:secLevel="secLevel"
|
||||
:role="role"
|
||||
:departId="departId"
|
||||
:docId="docId"
|
||||
:isSpecial="isSpecial"
|
||||
v-model:checkedMainDepartIds="checkedMainDepartIds"
|
||||
v-model:checkedAssistDepartIds="checkedAssistDepartIds"
|
||||
v-model:checkedReadDepartIds="checkedReadDepartIds"
|
||||
v-model:checkedUsernames="checkedUsernames"
|
||||
v-model:checkedReadUsernames="checkedReadUsernames"
|
||||
v-model:reasons="reasons"
|
||||
@change="handleDistributeChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import SzDistribute from '/@/components/semri/distributeComponent/SzDistribute.vue';
|
||||
|
||||
const secLevel = 3; // 密级:1=非密, 2=内部, 3=秘密, 4=机密
|
||||
const role = 1; // 角色:1=领导, 2=部门负责人
|
||||
const departId = 'A01'; // 当前用户部门(支持 orgCode 或 UUID)
|
||||
const docId = ''; // 公文ID(可选,用于排除已分发部门)
|
||||
const isSpecial = 0; // 是否特殊分发
|
||||
|
||||
const checkedMainDepartIds = ref('');
|
||||
const checkedAssistDepartIds = ref('');
|
||||
const checkedReadDepartIds = ref('');
|
||||
const checkedUsernames = ref('');
|
||||
const checkedReadUsernames = ref('');
|
||||
const reasons = ref('');
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SzDistribute 主组件 API
|
||||
|
||||
### Props(输入)
|
||||
|
||||
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| `secLevel` | `Number` | 是 | - | 文件密级。1=非密, 2=内部, 3=秘密, 4=机密 |
|
||||
| `role` | `Number \| String` | 是 | `1` | 当前用户角色。1=领导, 2=部门负责人 |
|
||||
| `departId` | `String` | 是 | `''` | 当前用户部门ID。支持 orgCode(如 `"A01"`)或 UUID 格式,组件内部自动转换 |
|
||||
| `docId` | `String` | 否 | `''` | 公文ID。传入后自动排除该公文已分发的部门 |
|
||||
| `isSpecial` | `Number` | 否 | `0` | 是否特殊分发。0=否, 1=是 |
|
||||
| `title` | `String` | 否 | `'分发方案'` | 弹窗标题(会自动追加密级文字) |
|
||||
| `width` | `Number \| String` | 否 | `1500` | 弹窗宽度(px) |
|
||||
| `showButton` | `Boolean` | 否 | `true` | 是否显示"分发对象"触发按钮。设为 `false` 时需通过 ref 调用 `showModal()` |
|
||||
|
||||
### v-model(双向绑定)
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `checkedMainDepartIds` | `String` | 主办部门ID列表,逗号分隔,如 `"id1,id2,id3"` |
|
||||
| `checkedAssistDepartIds` | `String` | 协办部门ID列表,逗号分隔 |
|
||||
| `checkedReadDepartIds` | `String` | 阅文部门ID列表,逗号分隔 |
|
||||
| `checkedUsernames` | `String` | 办文人员用户名列表,逗号分隔,如 `"zhangsan,lisi"` |
|
||||
| `checkedReadUsernames` | `String` | 阅文人员用户名列表,逗号分隔 |
|
||||
| `reasons` | `String` | 分发意见/备注文本 |
|
||||
| `modelValue` | `String` | 完整分发数据字符串(内部格式) |
|
||||
|
||||
### Events(事件)
|
||||
|
||||
| 事件 | 参数 | 说明 |
|
||||
|------|------|------|
|
||||
| `change` | `distributeStr: string` | 分发方案确认时触发 |
|
||||
| `update:checkedDepartIds` | `ids: string` | 所有已选部门ID(逗号分隔) |
|
||||
| `update:checkedDeparts` | `DepartTreeNode[]` | 所有已选部门节点对象数组 |
|
||||
|
||||
### Methods(通过 ref 调用)
|
||||
|
||||
```typescript
|
||||
const distributeRef = ref();
|
||||
|
||||
// 手动打开分发弹窗
|
||||
distributeRef.value.showModal();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 角色权限说明
|
||||
|
||||
### 领导(role=1)
|
||||
|
||||
- 部门选择 Tab:显示全部部门树,可自由选择主办/协办/阅文
|
||||
- 办文人员 Tab:显示全部部门树,选择部门后加载该部门人员
|
||||
- 阅文人员 Tab:同办文人员
|
||||
- 默认激活 Tab:选择部门
|
||||
|
||||
### 部门负责人(role=2)
|
||||
|
||||
- 部门选择 Tab:根据当前部门的类型(职能/业务/科室)显示可见部门
|
||||
- 办文人员 Tab:只显示本部门及下级部门的人员
|
||||
- 阅文人员 Tab:同办文人员
|
||||
- 默认激活 Tab:选择阅文人员(因部门负责人通常负责阅文分发)
|
||||
|
||||
---
|
||||
|
||||
## 子组件说明
|
||||
|
||||
### SzSelectDepart — 部门选择
|
||||
|
||||
树形结构展示部门,每个选中的部门可标记为「办」「协」「阅」三种类型。
|
||||
|
||||
**Props:**
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `selectedMainDeparts` | `DepartTreeNode[]` | 当前主办部门列表 |
|
||||
| `selectedAssistDeparts` | `DepartTreeNode[]` | 当前协办部门列表 |
|
||||
| `selectedReadDeparts` | `DepartTreeNode[]` | 当前阅文部门列表 |
|
||||
| `excludeDeparts` | `string[]` | 需排除的部门ID列表 |
|
||||
| `departId` | `string` | 当前用户部门ID |
|
||||
| `role` | `string \| number` | 用户角色 |
|
||||
|
||||
**Events:** `update:selectedMainDeparts`, `update:selectedAssistDeparts`, `update:selectedReadDeparts`
|
||||
|
||||
**快捷操作:**
|
||||
- 全选部门 / 全选职能部门 / 全选业务部门 / 全选本部门科室
|
||||
- 每种全选可指定办/协/阅类型
|
||||
- 右键节点文字可快速切换选中状态
|
||||
|
||||
### SzSelectUser — 人员选择
|
||||
|
||||
左侧部门树 + 右侧人员表格,支持搜索、分页、全选。
|
||||
|
||||
**Props:**
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `secLevel` | `any` | 文件密级,用于过滤人员(只显示密级 > 文件密级的人员) |
|
||||
| `checkedUser` | `UserItem[]` | 已选中的人员列表 |
|
||||
| `selectedDepId` | `string` | 当前选中的部门ID |
|
||||
| `departId` | `string` | 当前用户部门ID(支持 orgCode 或 UUID) |
|
||||
| `role` | `string \| number` | 用户角色 |
|
||||
| `isSpecial` | `number` | 是否特殊分发 |
|
||||
| `multi` | `boolean` | 是否多选(默认 `true`) |
|
||||
|
||||
**v-model:** `selectedDepId` — 当前选中部门
|
||||
|
||||
**Events:** `update:checkedUser`, `update:selectedDepId`
|
||||
|
||||
**密级过滤规则:** 只显示 `人员密级 > 文件密级` 的人员。例如文件密级为 3(秘密),则只显示密级为 4(机密)的人员。
|
||||
|
||||
### SzSelectedDepartsAndUsers — 已选展示
|
||||
|
||||
以标签形式展示已选择的部门和人员,支持删除和办/协/阅类型切换。
|
||||
|
||||
**Props:**
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `disabled` | `boolean` | 是否禁用操作(默认 `false`) |
|
||||
| `selectedMainDeparts` | `DepartTreeNode[]` | 主办部门 |
|
||||
| `selectedAssistDeparts` | `DepartTreeNode[]` | 协办部门 |
|
||||
| `selectedReadDeparts` | `DepartTreeNode[]` | 阅文部门 |
|
||||
| `selectedUsers` | `UserItem[]` | 办文人员 |
|
||||
| `selectedReadUsers` | `UserItem[]` | 阅文人员 |
|
||||
|
||||
**Events:** `update:selectedMainDeparts`, `update:selectedAssistDeparts`, `update:selectedReadDeparts`, `update:selectedUsers`, `update:selectedReadUsers`
|
||||
|
||||
---
|
||||
|
||||
## 类型定义
|
||||
|
||||
### DepartTreeNode
|
||||
|
||||
```typescript
|
||||
interface DepartTreeNode {
|
||||
id: string; // 部门 UUID
|
||||
key: string; // 树节点 key(等于 id)
|
||||
value: string; // 树节点 value(等于 id)
|
||||
title: string; // 部门名称
|
||||
isLeaf?: boolean; // 是否叶子节点
|
||||
departType?: string | number; // 部门类型:0=根, 1=职能, 2=业务, 3=科室
|
||||
distributable?: number; // 是否可分发:1=是
|
||||
disableCheckbox?: boolean; // 是否禁用选择
|
||||
assignType?: number; // 分配类型:1=办, 2=协, 3=阅
|
||||
children?: DepartTreeNode[]; // 子部门
|
||||
description?: string; // 部门描述
|
||||
parentId?: string; // 父部门ID
|
||||
orgCode?: string; // 部门编码(如 "A01", "A03A02")
|
||||
}
|
||||
```
|
||||
|
||||
### UserItem
|
||||
|
||||
```typescript
|
||||
interface UserItem {
|
||||
id: string;
|
||||
username: string;
|
||||
realname: string;
|
||||
orgCodeTxt?: string; // 所属部门名称
|
||||
userSecurityLevel?: number; // 人员密级数值
|
||||
userSecurityLevel_dictText?: string; // 人员密级文字
|
||||
sex_dictText?: string; // 性别
|
||||
sortno?: number; // 排序号
|
||||
departId?: string;
|
||||
departName?: string;
|
||||
assignType?: number; // 分配类型:1=办, 3=阅
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## departId 说明
|
||||
|
||||
`departId` 属性支持两种格式,组件内部会自动识别并转换:
|
||||
|
||||
| 格式 | 示例 | 说明 |
|
||||
|------|------|------|
|
||||
| orgCode | `"A01"`, `"A03A02"` | 部门编码,通常来自 `userInfo.orgCode` |
|
||||
| UUID | `"f4d5979edc1d4785ba2322f0b8200a21"` | 部门唯一标识 |
|
||||
|
||||
推荐从 `useUserStore().getUserInfo.orgCode` 获取:
|
||||
|
||||
```typescript
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const departId = userStore.getUserInfo?.orgCode || '';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方案管理
|
||||
|
||||
组件内置方案保存/加载功能,数据存储在后端 `djOfficialdocDistribute` 表中。
|
||||
|
||||
- **保存方案**:输入方案名 → 点击"保存为方案"
|
||||
- **加载方案**:在弹窗左侧已存方案列表中点击方案名
|
||||
- **快速应用**:在弹窗外部的方案按钮直接点击即可应用并提交
|
||||
- **删除方案**:点击方案旁的关闭按钮
|
||||
|
||||
加载方案时会自动校验人员状态(密级变更、部门变动、离职冻结),异常人员会弹出提示并自动移除。
|
||||
|
||||
---
|
||||
|
||||
## 依赖的后端接口
|
||||
|
||||
| 接口 | 用途 |
|
||||
|------|------|
|
||||
| `GET /sys/sysDepart/queryTreeList` | 获取部门树 |
|
||||
| `GET /sys/user/queryUserComponentData` | 查询部门下的人员 |
|
||||
| `GET /officialdocDistribute/djOfficialdocDistribute/list` | 方案列表 |
|
||||
| `POST /officialdocDistribute/djOfficialdocDistribute/add` | 新增方案 |
|
||||
| `POST /officialdocDistribute/djOfficialdocDistribute/edit` | 编辑方案 |
|
||||
| `DELETE /officialdocDistribute/djOfficialdocDistribute/delete` | 删除方案 |
|
||||
| `GET /distributionProcessinfo/officialdocDistributionProcessinfo/queryDeptIds` | 查询公文已分发部门ID |
|
||||
|
||||
---
|
||||
|
||||
## 测试页面
|
||||
|
||||
访问 `/semri/test/SzDistributeTest` 可查看组件演示和所有输入输出字段。
|
||||
@@ -0,0 +1,351 @@
|
||||
<template>
|
||||
<div class="sz-distribute-demo-container">
|
||||
<a-card title="SzDistribute 分发方案组件示例" style="margin-bottom: 16px">
|
||||
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }" style="max-width: 600px">
|
||||
<a-form-item label="密级(secLevel)">
|
||||
<a-radio-group v-model:value="formData.secLevel">
|
||||
<a-radio :value="1">非密</a-radio>
|
||||
<a-radio :value="2">内部</a-radio>
|
||||
<a-radio :value="3">秘密</a-radio>
|
||||
<a-radio :value="4">机密</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色(role)">
|
||||
<a-radio-group v-model:value="formData.role">
|
||||
<a-radio :value="1">领导(role=1)</a-radio>
|
||||
<a-radio :value="2">部门负责人(role=2)</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="当前部门ID">
|
||||
<a-input v-model:value="formData.departId" placeholder="输入部门ID" />
|
||||
</a-form-item>
|
||||
<a-form-item label="公文ID(docId)">
|
||||
<a-input v-model:value="formData.docId" placeholder="输入公文ID(可选,用于排除已分发部门)" />
|
||||
</a-form-item>
|
||||
<a-form-item label="是否特殊">
|
||||
<a-switch v-model:checked="isSpecialChecked" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<a-divider />
|
||||
|
||||
<SzDistribute
|
||||
ref="distributeRef"
|
||||
title="分发方案"
|
||||
:secLevel="formData.secLevel"
|
||||
:role="formData.role"
|
||||
:departId="formData.departId"
|
||||
:docId="formData.docId"
|
||||
:isSpecial="isSpecialChecked ? 1 : 0"
|
||||
v-model:checkedMainDepartIds="formData.checkedMainDepartIds"
|
||||
v-model:checkedAssistDepartIds="formData.checkedAssistDepartIds"
|
||||
v-model:checkedReadDepartIds="formData.checkedReadDepartIds"
|
||||
v-model:checkedUsernames="formData.checkedUsernames"
|
||||
v-model:checkedReadUsernames="formData.checkedReadUsernames"
|
||||
v-model:reasons="formData.reasons"
|
||||
@change="handleDistributeChange"
|
||||
@update:checkedDepartIds="handleCheckedDepartIdsChange"
|
||||
/>
|
||||
|
||||
<div style="margin-top: 16px">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="handleSave" :loading="saving">模拟保存</a-button>
|
||||
<a-button @click="handleReset">重置</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card title="输入字段(Input)" style="margin-bottom: 16px">
|
||||
<a-descriptions :column="1" size="small" bordered>
|
||||
<a-descriptions-item label="密级(secLevel)">
|
||||
{{ formData.secLevel }} - {{ formData.secLevel === 1 ? '非密' : formData.secLevel === 2 ? '内部' : formData.secLevel === 3 ? '秘密' : '机密' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="角色(role)">
|
||||
{{ formData.role }} - {{ formData.role === 1 ? '领导' : '部门负责人' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="当前部门ID(departId)">
|
||||
{{ formData.departId || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="公文ID(docId)">
|
||||
{{ formData.docId || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="是否特殊(isSpecial)">
|
||||
{{ isSpecialChecked ? '是(1)' : '否(0)' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card title="输出字段(Output)" style="margin-bottom: 16px">
|
||||
<a-descriptions :column="1" size="small" bordered>
|
||||
<a-descriptions-item label="主办部门IDs(checkedMainDepartIds)">
|
||||
{{ formData.checkedMainDepartIds || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="协办部门IDs(checkedAssistDepartIds)">
|
||||
{{ formData.checkedAssistDepartIds || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="阅文部门IDs(checkedReadDepartIds)">
|
||||
{{ formData.checkedReadDepartIds || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="办文人员用户名(checkedUsernames)">
|
||||
{{ formData.checkedUsernames || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="阅文人员用户名(checkedReadUsernames)">
|
||||
{{ formData.checkedReadUsernames || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="分发意见(reasons)">
|
||||
{{ formData.reasons || '(空)' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card title="字段含义说明" style="margin-bottom: 16px">
|
||||
<a-collapse>
|
||||
<a-collapse-panel key="input" header="输入字段说明">
|
||||
<a-table :columns="inputColumns" :data-source="inputFields" :pagination="false" size="small" />
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel key="output" header="输出字段说明">
|
||||
<a-table :columns="outputColumns" :data-source="outputFields" :pagination="false" size="small" />
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-card>
|
||||
|
||||
<a-card title="事件日志">
|
||||
<div class="event-log-wrapper">
|
||||
<a-button size="small" @click="eventLogs = []" style="margin-bottom: 8px">清空日志</a-button>
|
||||
<div class="event-log-list">
|
||||
<div v-for="(log, idx) in eventLogs" :key="idx" class="event-log-item">
|
||||
<a-tag :color="log.type === 'success' ? 'green' : log.type === 'error' ? 'red' : 'blue'" size="small">
|
||||
{{ log.type }}
|
||||
</a-tag>
|
||||
<span class="log-time">{{ log.time }}</span>
|
||||
<span class="log-msg">{{ log.message }}</span>
|
||||
</div>
|
||||
<div v-if="eventLogs.length === 0" class="empty-log">暂无日志</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import SzDistribute from '/@/components/semri/distributeComponent/SzDistribute.vue';
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const userStore = useUserStore();
|
||||
const userInfo = computed(() => userStore.getUserInfo);
|
||||
|
||||
const distributeRef = ref<InstanceType<typeof SzDistribute> | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
addLog('info', '页面初始化');
|
||||
addLog('info', `当前用户信息: ${JSON.stringify(userInfo.value)}`);
|
||||
|
||||
if (!formData.departId && userInfo.value?.orgCode) {
|
||||
formData.departId = userInfo.value.orgCode;
|
||||
addLog('info', `自动获取当前用户部门ID: ${formData.departId}`);
|
||||
} else if (!formData.departId) {
|
||||
addLog('warn', '当前用户没有部门信息,请手动输入部门ID或选择一个测试部门ID');
|
||||
// 提供一些测试用的部门ID
|
||||
addLog('info', '测试提示:可以使用部门ID如 "a7d898d8964111efb0b2a4e2a1f0467d" 进行测试');
|
||||
} else {
|
||||
addLog('info', `使用手动输入的部门ID: ${formData.departId}`);
|
||||
}
|
||||
});
|
||||
const eventLogs = ref<any[]>([]);
|
||||
const saving = ref(false);
|
||||
const isSpecialChecked = ref(false);
|
||||
|
||||
const inputColumns = [
|
||||
{ title: '字段名', dataIndex: 'name', key: 'name', width: 200 },
|
||||
{ title: '类型', dataIndex: 'type', key: 'type', width: 100 },
|
||||
{ title: '必填', dataIndex: 'required', key: 'required', width: 80 },
|
||||
{ title: '说明', dataIndex: 'description', key: 'description' },
|
||||
];
|
||||
|
||||
const inputFields = [
|
||||
{
|
||||
name: 'secLevel',
|
||||
type: 'Number',
|
||||
required: '是',
|
||||
description: '文件密级,1=非密、2=内部、3=秘密、4=机密。用于控制显示的人员密级范围,只显示人员密级 > 文件密级的用户',
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
type: 'Number',
|
||||
required: '是',
|
||||
description: '当前用户角色,1=领导、2=部门负责人。不同的角色有不同的分发权限和界面展示',
|
||||
},
|
||||
{
|
||||
name: 'departId',
|
||||
type: 'String',
|
||||
required: '是',
|
||||
description: '当前登录用户的部门ID,用于确定当前部门,以便在分发时排除当前部门',
|
||||
},
|
||||
{
|
||||
name: 'docId',
|
||||
type: 'String',
|
||||
required: '否',
|
||||
description: '公文ID(可选),用于查询已分发的部门,在重新分发时排除已分发的部门',
|
||||
},
|
||||
{
|
||||
name: 'isSpecial',
|
||||
type: 'Number',
|
||||
required: '否',
|
||||
description: '是否特殊分发,0=否、1=是。特殊分发可能有不同的权限控制或业务逻辑',
|
||||
},
|
||||
];
|
||||
|
||||
const outputColumns = [
|
||||
{ title: '字段名', dataIndex: 'name', key: 'name', width: 200 },
|
||||
{ title: '类型', dataIndex: 'type', key: 'type', width: 100 },
|
||||
{ title: '格式', dataIndex: 'format', key: 'format', width: 200 },
|
||||
{ title: '说明', dataIndex: 'description', key: 'description' },
|
||||
];
|
||||
|
||||
const outputFields = [
|
||||
{
|
||||
name: 'checkedMainDepartIds',
|
||||
type: 'String',
|
||||
format: '逗号分隔的ID字符串',
|
||||
description: '主办部门ID列表,多个部门用逗号分隔,如:"dept1,dept2,dept3"',
|
||||
},
|
||||
{
|
||||
name: 'checkedAssistDepartIds',
|
||||
type: 'String',
|
||||
format: '逗号分隔的ID字符串',
|
||||
description: '协办部门ID列表,多个部门用逗号分隔,用于标识协助办理的部门',
|
||||
},
|
||||
{
|
||||
name: 'checkedReadDepartIds',
|
||||
type: 'String',
|
||||
format: '逗号分隔的ID字符串',
|
||||
description: '阅文部门ID列表,多个部门用逗号分隔,用于标识仅需要阅读公文的部门',
|
||||
},
|
||||
{
|
||||
name: 'checkedUsernames',
|
||||
type: 'String',
|
||||
format: '逗号分隔的用户名字符串',
|
||||
description: '办文人员用户名列表,多个用户用逗号分隔,用于标识需要办理公文的具体人员',
|
||||
},
|
||||
{
|
||||
name: 'checkedReadUsernames',
|
||||
type: 'String',
|
||||
format: '逗号分隔的用户名字符串',
|
||||
description: '阅文人员用户名列表,多个用户用逗号分隔,用于标识仅需要阅读公文的具体人员',
|
||||
},
|
||||
{
|
||||
name: 'reasons',
|
||||
type: 'String',
|
||||
format: '文本字符串',
|
||||
description: '分发意见或备注,用于记录分发的说明或意见',
|
||||
},
|
||||
];
|
||||
|
||||
const formData = reactive({
|
||||
secLevel: 3,
|
||||
role: 1 as number,
|
||||
departId: '',
|
||||
docId: '',
|
||||
isSpecial: 0,
|
||||
checkedMainDepartIds: '',
|
||||
checkedAssistDepartIds: '',
|
||||
checkedReadDepartIds: '',
|
||||
checkedUsernames: '',
|
||||
checkedReadUsernames: '',
|
||||
reasons: '',
|
||||
});
|
||||
|
||||
function addLog(type: string, message: string) {
|
||||
const now = new Date();
|
||||
const time = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
|
||||
eventLogs.value.unshift({ type, message, time });
|
||||
if (eventLogs.value.length > 50) eventLogs.value.pop();
|
||||
}
|
||||
|
||||
function handleDistributeChange(data: any) {
|
||||
addLog('info', `分发方案变更: ${data}`);
|
||||
}
|
||||
|
||||
function handleCheckedDepartIdsChange(data: string) {
|
||||
addLog('info', `已选部门IDs: ${data}`);
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true;
|
||||
addLog('info', '开始模拟保存...');
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
addLog('success', `保存成功!主办部门: ${formData.checkedMainDepartIds || '无'},协办部门: ${formData.checkedAssistDepartIds || '无'},阅文部门: ${formData.checkedReadDepartIds || '无'},办文人员: ${formData.checkedUsernames || '无'},阅文人员: ${formData.checkedReadUsernames || '无'}`);
|
||||
} catch (error: any) {
|
||||
addLog('error', `保存失败: ${error.message || '未知错误'}`);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
formData.secLevel = 3;
|
||||
formData.role = 1;
|
||||
formData.departId = '';
|
||||
formData.docId = '';
|
||||
formData.isSpecial = 0;
|
||||
isSpecialChecked.value = false;
|
||||
formData.checkedMainDepartIds = '';
|
||||
formData.checkedAssistDepartIds = '';
|
||||
formData.checkedReadDepartIds = '';
|
||||
formData.checkedUsernames = '';
|
||||
formData.checkedReadUsernames = '';
|
||||
formData.reasons = '';
|
||||
eventLogs.value = [];
|
||||
addLog('info', '表单已重置');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sz-distribute-demo-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.event-log-wrapper {
|
||||
.event-log-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.event-log-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-msg {
|
||||
font-size: 13px;
|
||||
color: #262626;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-log {
|
||||
text-align: center;
|
||||
color: #8c8c8c;
|
||||
font-size: 13px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user