组用户功能修订
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<Modal v-model="visible" :title="title" :width="720" :mask-closable="false" :loading="submitting"
|
||||
@on-visible-change="onVisibleChange">
|
||||
<div class="adjust-group-body">
|
||||
<div class="left-panel">
|
||||
<div class="panel-title">待选:</div>
|
||||
<div class="tree-wrap">
|
||||
<GroupTreeSearchPanel ref="treePanelRef" :treeApi="treeApi" :checkable="true"
|
||||
:defaultSearchVisible="true" :showCloseButton="false" searchPlaceholder="输入组名称搜索"
|
||||
@on-tree-data="onTreeData" @on-tree-check="onTreeCheck" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="panel-title">已选:</div>
|
||||
<div class="selected-wrap">
|
||||
<div v-if="selectedGroupIds.length === 0" class="empty-tip">暂无已选组</div>
|
||||
<div v-else class="selected-list">
|
||||
<div v-for="id in selectedGroupIds" :key="id" class="selected-item">
|
||||
<div class="item-main">
|
||||
<span class="item-name">{{ nodeMap[id]?.name || id }}</span>
|
||||
<span class="item-path">{{ pathMap[id] || '' }}</span>
|
||||
</div>
|
||||
<Icon type="md-trash" class="item-delete" @click="removeGroup(id)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<Button type="primary" :loading="submitting" @click="handleSubmit">提交</Button>
|
||||
<Button @click="visible = false">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from "vue";
|
||||
import { Message } from "view-ui-plus";
|
||||
import GroupTreeSearchPanel from '@/components/GroupTreeSearchPanel.vue'
|
||||
|
||||
defineOptions({ name: "AdjustGroupContainer" });
|
||||
|
||||
const visible = ref(false);
|
||||
const submitting = ref(false);
|
||||
const userIds = ref<number[]>([]);
|
||||
const userName = ref('');
|
||||
const treeData = ref<any[]>([]);
|
||||
const selectedGroupIds = ref<number[]>([]);
|
||||
const nodeMap = ref<Record<number, any>>({});
|
||||
const pathMap = ref<Record<number, string>>({});
|
||||
const pendingGroupIds = ref<number[] | null>(null);
|
||||
|
||||
let onSuccessCallback: (() => void) | null = null;
|
||||
|
||||
const treePanelRef = ref<InstanceType<typeof GroupTreeSearchPanel> | null>(null);
|
||||
|
||||
const title = computed(() => {
|
||||
if (userIds.value.length === 1) return `调整组:${userName.value || ''}`;
|
||||
return `调整组:已选 ${userIds.value.length} 人`;
|
||||
});
|
||||
|
||||
function treeApi() {
|
||||
return Promise.resolve(treeData.value || []);
|
||||
}
|
||||
|
||||
function buildNodeMap(list: any[], path = '') {
|
||||
list.forEach((n: any) => {
|
||||
const curPath = path ? `${path}/${n.name}` : n.name;
|
||||
nodeMap.value[n.id] = n;
|
||||
pathMap.value[n.id] = curPath;
|
||||
if (n.children?.length) buildNodeMap(n.children, curPath);
|
||||
});
|
||||
}
|
||||
|
||||
function onTreeData(tree: any[]) {
|
||||
buildNodeMap(tree || []);
|
||||
if (userIds.value.length === 1 && pendingGroupIds.value) {
|
||||
initSelected(pendingGroupIds.value);
|
||||
pendingGroupIds.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUserGroups(userId: number) {
|
||||
try {
|
||||
const groupIds: number[] = await Apis.SysUser.getGroups({ pathParams: { userId }, cacheFor: null });
|
||||
if (treeData.value?.length) {
|
||||
initSelected(groupIds || []);
|
||||
} else {
|
||||
pendingGroupIds.value = groupIds || [];
|
||||
}
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '加载用户所在组失败');
|
||||
}
|
||||
}
|
||||
|
||||
function initSelected(groupIds: number[]) {
|
||||
const existIds = groupIds.filter(id => nodeMap.value[id]);
|
||||
selectedGroupIds.value = existIds;
|
||||
nextTick(() => {
|
||||
existIds.forEach(id => treePanelRef.value?.checkNodeById(id, true));
|
||||
});
|
||||
}
|
||||
|
||||
// 勾选/取消勾选即同步右侧:勾选则加入,取消则移除
|
||||
function onTreeCheck() {
|
||||
const checked = treePanelRef.value?.getCheckedNodes() || [];
|
||||
selectedGroupIds.value = checked.map((n: any) => Number(n.id));
|
||||
}
|
||||
|
||||
function removeGroup(id: number) {
|
||||
selectedGroupIds.value = selectedGroupIds.value.filter(x => x !== id);
|
||||
treePanelRef.value?.checkNodeById(id, false);
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (selectedGroupIds.value.length === 0) {
|
||||
Message.error('一个人至少要有一个组');
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
try {
|
||||
await Apis.SysUser.changeGroups({
|
||||
data: { userIds: userIds.value, groupIds: selectedGroupIds.value }
|
||||
});
|
||||
Message.success('调整成功');
|
||||
visible.value = false;
|
||||
onSuccessCallback?.();
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '调整失败');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onVisibleChange(show: boolean) {
|
||||
if (!show) {
|
||||
selectedGroupIds.value = [];
|
||||
pendingGroupIds.value = null;
|
||||
treeData.value = [];
|
||||
nodeMap.value = {};
|
||||
pathMap.value = {};
|
||||
}
|
||||
}
|
||||
|
||||
function open(ids: number[], users: any[], tree: any[], onSuccess?: () => void) {
|
||||
if (!ids?.length) {
|
||||
Message.error('请选择待调整用户');
|
||||
return;
|
||||
}
|
||||
userIds.value = ids;
|
||||
userName.value = users?.[0]?.realName || users?.[0]?.account || '';
|
||||
treeData.value = tree || [];
|
||||
selectedGroupIds.value = [];
|
||||
pendingGroupIds.value = null;
|
||||
nodeMap.value = {};
|
||||
pathMap.value = {};
|
||||
onSuccessCallback = onSuccess || null;
|
||||
visible.value = true;
|
||||
|
||||
nextTick(() => {
|
||||
treePanelRef.value?.loadTree();
|
||||
if (ids.length === 1) {
|
||||
loadUserGroups(ids[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({ open, visible });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.adjust-group-body {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: 420px;
|
||||
gap: 12px;
|
||||
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.tree-wrap,
|
||||
.selected-wrap {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.selected-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
|
||||
&:hover {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
.item-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.item-path {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.item-delete {
|
||||
font-size: 18px;
|
||||
color: #ed4014;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user