Files
UserhomeFront/src/views/group-user/AdjustGroupContainer.vue
T
2026-08-18 23:31:27 +08:00

258 lines
7.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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) {
console.warn(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) {
console.warn(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>