<script lang="ts" setup>
|
import type { ScrollActionType, TreeActionType } from '@jnpf/ui';
|
|
import { computed, nextTick, reactive, ref, toRefs, unref, watch } from 'vue';
|
|
import { useGlobSetting, useMessage } from '@jnpf/hooks';
|
import { BasicTree, ScrollContainer } from '@jnpf/ui';
|
import { BasicModal, useModalInner } from '@jnpf/ui/modal';
|
|
import { useDebounceFn } from '@vueuse/core';
|
import { Avatar, Checkbox, Input, TabPane, Tabs, Tag } from 'ant-design-vue';
|
|
import { getOrganizeSelector } from '#/api/permission/organize';
|
import { getPositionSelectorTree } from '#/api/permission/position';
|
import { getSelectedList, getUserList } from '#/api/permission/user';
|
import { createShare, createSingleShare, getShareUserList } from '#/api/teamwork/document';
|
import { $t } from '#/locales';
|
import { useOrganizeStore } from '#/store';
|
|
interface State {
|
activeKey: string;
|
finish: boolean;
|
innerValue: any[] | string | undefined;
|
lastList: any[];
|
lastLoading: boolean;
|
listActiveKey: string;
|
loading: boolean;
|
options: any[];
|
orgLoading: boolean;
|
orgTreeData: any[];
|
parentId: string;
|
query: any;
|
selectedData: any[];
|
selectedIds: any[];
|
treeData: any[];
|
userQuery: any;
|
documentIds: string;
|
isBatch: boolean;
|
}
|
|
const emit = defineEmits(['register', 'reload']);
|
const { createMessage } = useMessage();
|
|
const [registerModal, { changeLoading, changeOkLoading, closeModal }] = useModalInner(init);
|
const organizeStore = useOrganizeStore();
|
const globSetting = useGlobSetting();
|
const apiUrl = ref(globSetting.apiURL);
|
const orgTreeRef = ref<Nullable<TreeActionType>>(null);
|
const posTreeRef = ref<Nullable<TreeActionType>>(null);
|
const infiniteBody = ref<Nullable<ScrollActionType>>(null);
|
const state = reactive<State>({
|
activeKey: '',
|
finish: false,
|
innerValue: '',
|
lastList: [],
|
lastLoading: false,
|
loading: false,
|
options: [],
|
orgLoading: false,
|
orgTreeData: [],
|
parentId: '0',
|
query: {
|
currentPage: 1,
|
hasPage: 0,
|
keyword: '',
|
pageSize: 20,
|
},
|
selectedData: [],
|
selectedIds: [],
|
treeData: [],
|
userQuery: {},
|
listActiveKey: '',
|
documentIds: '',
|
isBatch: false,
|
});
|
const { activeKey, lastList, lastLoading, query, selectedData, selectedIds, treeData, listActiveKey } = toRefs(state);
|
const organizeSubList: any = [
|
{ fullName: '当前组织', id: '--org', orgNameTree: '' },
|
{ fullName: '当前组织及子组织', id: '--subOrg', orgNameTree: '及子组织' },
|
{ fullName: '当前组织及子孙组织', id: '--progenyOrg', orgNameTree: '及子孙组织' },
|
];
|
const positionSubList: any = [
|
{ fullName: '当前岗位', id: '--pos', orgNameTree: '' },
|
{ fullName: '当前岗位及子岗位', id: '--subPos', orgNameTree: '及子岗位' },
|
{ fullName: '当前岗位及子孙岗位', id: '--progenyPos', orgNameTree: '及子孙岗位' },
|
];
|
const defaultUserQuery = {
|
enabledMark: 1,
|
groupId: '',
|
organizeId: '',
|
positionId: '',
|
roleId: '',
|
};
|
const debounceHandleSearch = useDebounceFn(handleSearch, 300);
|
|
const getOrgTreeBindValue = computed(() => {
|
const key = Date.now();
|
const data: any = {
|
key,
|
loadData: onLoadData,
|
loading: state.orgLoading,
|
onSelect: handleOrgSelect,
|
treeData: state.orgTreeData,
|
};
|
return data;
|
});
|
const getPosTreeBindValue = computed(() => {
|
const key = Date.now() + 1000;
|
const data: any = {
|
defaultExpandAll: true,
|
key,
|
loading: state.loading,
|
onSelect: handleSelect,
|
treeData: state.treeData,
|
};
|
return data;
|
});
|
|
watch(
|
() => state.selectedData,
|
(val) => {
|
state.selectedIds = val.map((o) => o.id);
|
},
|
{ deep: true },
|
);
|
|
function init(data) {
|
changeLoading(true);
|
state.documentIds = data.ids;
|
state.isBatch = data.isBatch;
|
state.activeKey = 'user';
|
state.selectedData = [];
|
onTabChange();
|
if (data.isBatch) return changeLoading(false);
|
getShareUserList(state.documentIds[0]).then((res) => {
|
if (!res.data.list.length) return changeLoading(false);
|
const ids = res.data.list.map((o) => o.shareUserId);
|
getSelectedList(ids).then((res) => {
|
state.selectedData = res.data.list || [];
|
changeLoading(false);
|
});
|
});
|
}
|
function onTabChange() {
|
state.query.hasPage = 0;
|
state.query.keyword = '';
|
initData();
|
nextTick(() => {
|
bindScroll();
|
});
|
}
|
function handleSearch(e) {
|
const value = e.target.value;
|
if (state.lastLoading) return;
|
if (state.activeKey !== 'user') state.activeKey = 'user';
|
state.query.keyword = value || '';
|
state.query.hasPage = state.query.keyword ? 1 : 0;
|
if (state.query.hasPage) {
|
state.userQuery = { ...defaultUserQuery };
|
nextTick(() => {
|
bindScroll();
|
});
|
}
|
initData();
|
}
|
function handleOrgSelect(keys) {
|
if (keys.length === 0) return;
|
const data = getOrgTree().getSelectedNode(keys[0]);
|
state.activeKey == 'organize' ? getSubData(data) : getPosTreeData(keys[0]);
|
}
|
function getSubData(item) {
|
const subList = state.activeKey === 'organize' ? organizeSubList : positionSubList;
|
const data: any = [];
|
for (const element of subList) {
|
data.push({ ...item, fullName: element.fullName, icon: '', id: `${item.id}${element.id}`, orgNameTree: item.orgNameTree + element.orgNameTree });
|
}
|
state.lastList = data;
|
}
|
// 获取岗位树
|
function getPosTreeData(id) {
|
state.loading = true;
|
getPositionSelectorTree({ organizeId: id })
|
.then((res) => {
|
state.treeData = res.data.list || [];
|
if (state.treeData.length > 0) {
|
nextTick(() => {
|
const keys = [state.treeData[0].id];
|
getPosTree().setSelectedKeys(keys);
|
handleSelect(keys);
|
});
|
} else {
|
state.lastList = [];
|
}
|
state.loading = false;
|
})
|
.catch(() => {
|
state.loading = false;
|
});
|
}
|
function handleSelect(keys) {
|
if (keys.length === 0) return;
|
const data = getPosTree().getSelectedNode(keys[0]);
|
getSubData(data);
|
}
|
function handleListNodeClick(data) {
|
state.listActiveKey = data.id;
|
const key = `${state.activeKey === 'user' ? 'group' : state.activeKey}Id`;
|
state.userQuery = { ...defaultUserQuery, [key]: state.listActiveKey };
|
state.lastList = [];
|
state.finish = false;
|
state.query.currentPage = 1;
|
getLastList();
|
}
|
function handleNodeClick(data) {
|
const index = state.selectedData.findIndex((o) => o.id === data.id);
|
if (index !== -1) return state.selectedData.splice(index, 1);
|
state.selectedData.push(data);
|
}
|
function removeAll() {
|
state.selectedData = [];
|
}
|
function removeData(index: number) {
|
state.selectedData.splice(index, 1);
|
}
|
function getOrgTree() {
|
const tree = unref(orgTreeRef);
|
if (!tree) {
|
throw new Error('tree is null!');
|
}
|
return tree;
|
}
|
function getPosTree() {
|
const tree = unref(posTreeRef);
|
if (!tree) {
|
throw new Error('tree is null!');
|
}
|
return tree;
|
}
|
function bindScroll() {
|
const bodyRef = infiniteBody.value;
|
const vBody = bodyRef?.getScrollWrap();
|
vBody?.addEventListener('scroll', () => {
|
if (state.activeKey !== 'user') return;
|
if (vBody.scrollTop > 0 && vBody.scrollHeight - vBody.clientHeight - vBody.scrollTop <= 200 && !state.lastLoading && !state.finish) {
|
state.query.currentPage += 1;
|
getLastList();
|
}
|
});
|
}
|
function onLoadData(node) {
|
state.parentId = node.id;
|
return new Promise((resolve: (value?: unknown) => void) => {
|
getOrganizeSelector({ parentId: state.parentId }).then((res) => {
|
const list = res.data.list;
|
getOrgTree().updateNodeByKey(node.eventKey, { isLeaf: list.length === 0, children: list });
|
resolve();
|
});
|
});
|
}
|
function getOrgList() {
|
state.orgLoading = true;
|
getOrganizeSelector({ parentId: state.parentId })
|
.then((res) => {
|
state.orgTreeData = res.data.list;
|
if (state.orgTreeData.length > 0 && state.parentId == '0') {
|
nextTick(() => {
|
const keys = [state.orgTreeData[0].id];
|
getOrgTree().setSelectedKeys(keys);
|
handleOrgSelect(keys);
|
});
|
}
|
state.orgLoading = false;
|
})
|
.catch(() => {
|
state.orgLoading = false;
|
});
|
}
|
function getLastList() {
|
state.lastLoading = true;
|
const query = { ...state.query, ...state.userQuery };
|
getUserList(query)
|
.then((res) => {
|
const list = res.data.list.map((o) => ({ ...o, id: `${o.id}--user`, orgNameTree: o.orgNameTree || o.fullName }));
|
state.finish = list.length < state.query.pageSize;
|
state.lastList = [...state.lastList, ...list];
|
state.lastLoading = false;
|
})
|
.catch(() => {
|
state.lastLoading = false;
|
});
|
}
|
async function initData() {
|
state.parentId = '0';
|
state.query.currentPage = 1;
|
state.finish = false;
|
state.orgTreeData = [];
|
state.treeData = [];
|
state.lastList = [];
|
state.listActiveKey = '';
|
switch (state.activeKey) {
|
case 'group': {
|
const res = await organizeStore.getGroupList();
|
state.lastList = res.map((o) => ({ ...o, id: `${o.id}--group`, orgNameTree: o.orgNameTree || o.fullName }));
|
break;
|
}
|
case 'organize':
|
case 'position': {
|
getOrgList();
|
break;
|
}
|
case 'role': {
|
const res = await organizeStore.getRoleList();
|
state.lastList = res.map((o) => ({ ...o, id: `${o.id}--role`, orgNameTree: o.orgNameTree || o.fullName }));
|
break;
|
}
|
case 'user': {
|
if (state.query.keyword) return getLastList();
|
const list = await organizeStore.getGroupList();
|
state.treeData = [{ fullName: '全部用户', icon: 'icon-ym icon-ym-generator-group1', id: '' }, ...list];
|
handleListNodeClick(state.treeData[0]);
|
break;
|
}
|
default: {
|
state.treeData = [];
|
}
|
}
|
}
|
function handleSubmit() {
|
if (!state.selectedData.length) return createMessage.error('请选择共享人员');
|
changeOkLoading(true);
|
const userIds = state.selectedData.map((o) => o.id);
|
const methods = state.isBatch ? createShare : createSingleShare;
|
const id = state.isBatch ? state.documentIds : state.documentIds[0];
|
methods(id, userIds)
|
.then((res) => {
|
createMessage.success(res.msg);
|
changeOkLoading(false);
|
closeModal();
|
emit('reload');
|
})
|
.catch(() => {
|
changeOkLoading(false);
|
});
|
}
|
</script>
|
<template>
|
<BasicModal v-bind="$attrs" @register="registerModal" title="共享文件" :width="800" @ok="handleSubmit" destroy-on-close class="common-select-modal">
|
<template #insertFooter>
|
<div class="float-left">
|
<span class="mr-[10px]">{{ $t('component.jnpf.common.selected') }}({{ selectedData.length }})</span>
|
<span class="remove-all-btn" @click="removeAll">{{ $t('component.jnpf.common.clearAll') }}</span>
|
</div>
|
</template>
|
<div class="common-select-modal-main">
|
<div class="selected-pane">
|
<Tag v-for="(item, i) in selectedData" :key="item.id" :closable="true" class="selected-pane-tag" @close="removeData(i)">
|
{{ item.orgNameTree }}
|
</Tag>
|
</div>
|
<div class="select-pane">
|
<div class="select-pane-tool">
|
<Tabs v-model:active-key="activeKey" :tab-bar-gutter="20" class="select-pane-tool-tabs" @change="onTabChange">
|
<TabPane key="user" tab="用户" />
|
<TabPane key="organize" tab="组织架构" />
|
<TabPane key="position" tab="岗位" />
|
<TabPane key="role" tab="角色" />
|
<TabPane key="group" tab="用户组" />
|
<template #rightExtra>
|
<Input v-model:value="query.keyword" :placeholder="$t('common.enterKeyword')" allow-clear @change="debounceHandleSearch" />
|
</template>
|
</Tabs>
|
<div class="select-pane-main">
|
<ScrollContainer class="select-pane-list select-pane-item" v-if="activeKey === 'user' && !query.hasPage">
|
<div
|
v-for="(item, i) in treeData"
|
:key="i"
|
class="select-pane-list__item"
|
:class="{ 'select-pane-list__item-active': listActiveKey === item.id }"
|
@click="handleListNodeClick(item)">
|
<div class="select-pane-list__item-title">
|
<i :class="item.icon" v-if="item.icon" class="mr-[6px]"></i>
|
<span :title="item.fullName">{{ item.fullName }}</span>
|
</div>
|
</div>
|
<jnpf-empty v-if="treeData.length === 0" />
|
</ScrollContainer>
|
<BasicTree
|
ref="orgTreeRef"
|
class="tree-main select-pane-item"
|
v-bind="getOrgTreeBindValue"
|
v-if="activeKey === 'organize' || activeKey === 'position'" />
|
<BasicTree ref="posTreeRef" class="tree-main select-pane-item" v-bind="getPosTreeBindValue" v-if="activeKey === 'position'" />
|
<ScrollContainer class="select-pane-list select-pane-item" ref="infiniteBody" v-loading="lastLoading && query.currentPage === 1">
|
<div v-for="(item, i) in lastList" :key="i" class="select-pane-list__item" @click="handleNodeClick(item)">
|
<div class="select-pane-list__item-title">
|
<Avatar :size="26" :src="apiUrl + item.headIcon" class="select-pane-list__item--headIcon" v-if="item.headIcon" />
|
<i :class="item.icon" class="mr-[6px]" v-if="item.icon && !item.headIcon"></i>
|
<span :title="item.fullName" v-if="['organize', 'position'].includes(activeKey)">{{ item.fullName }}</span>
|
<span :title="item.orgNameTree" v-else>{{ item.orgNameTree }}</span>
|
</div>
|
<div class="select-pane-list__item-action">
|
<Checkbox :checked="selectedIds.includes(item.id)" />
|
</div>
|
</div>
|
<jnpf-empty v-if="lastList.length === 0" />
|
</ScrollContainer>
|
</div>
|
</div>
|
</div>
|
</div>
|
</BasicModal>
|
</template>
|