init: UserBaseFront (frontend)
This commit is contained in:
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<div class="group-user-tree">
|
||||
<ul class="opera-ul">
|
||||
<li class="add-node-li" @click="showAddNode">
|
||||
<Tooltip content="新增子组" placement="top" transfer>
|
||||
<SvgIcon name="tree-icon-1" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
<Tooltip content="编辑组" placement="top" transfer>
|
||||
<li @click="showEditNode">
|
||||
<SvgIcon name="tree-icon-2" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip content="子组排序" placement="top" transfer>
|
||||
<li @click="showSortNode">
|
||||
<SvgIcon name="tree-icon-3" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip content="移动组" placement="top" transfer>
|
||||
<li @click="showMoveNode">
|
||||
<SvgIcon name="tree-icon-4" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip content="删除组" placement="top" transfer>
|
||||
<li @click="showDeleteNode">
|
||||
<SvgIcon name="tree-icon-5" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
<Tooltip content="搜索组" placement="top" transfer>
|
||||
<li class="search-node-li" @click="refTreePanel?.openSearch()">
|
||||
<Icon type="md-search" />
|
||||
</li>
|
||||
</Tooltip>
|
||||
</ul>
|
||||
|
||||
<GroupTreeSearchPanel ref="refTreePanel" v-model:curTreeNode="curTreeNode" :treeApi="treeApi"
|
||||
:defaultSearchVisible="false" :showCloseButton="true" @on-change="treeLink" @on-tree-data="onTreeData" />
|
||||
|
||||
<Modal v-model="editNodeModal.modal" :title="editNodeModal.title" :width="450" :mask-closable="false">
|
||||
<Form ref="refFormData" :label-width="100" :model="editNodeModal" class="myform mb-20 mr-20">
|
||||
<Form-Item label="当前位置:">
|
||||
{{ curNodePath.join(' > ') }}
|
||||
</Form-Item>
|
||||
<Form-Item label="组类型:" prop="type"
|
||||
:rules="{ required: true, type: 'number', message: '请选择组类型', trigger: 'change' }">
|
||||
<Select v-model="editNodeModal.type" placeholder="请选择组类型" transfer>
|
||||
<Option v-for="t in groupTypeOptions" :key="t.value" :value="t.value">{{ t.label }}</Option>
|
||||
</Select>
|
||||
</Form-Item>
|
||||
<Form-Item label="节点名称:" prop="name" :rules="{ required: true, message: '请输入节点名称', trigger: 'blur' }">
|
||||
<Input ref="refNodeNameInput" maxlength="30" v-model="editNodeModal.name" />
|
||||
</Form-Item>
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button type="link" v-if="!editNodeModal.id" @click="switchToBatch">切换至批量添加</Button>
|
||||
<Button type="primary" :loading="editNodeModal.loading" @click="saveEditNode">确认</Button>
|
||||
<Button @click="editNodeModal.modal = false">取消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<BatchAddGroup v-model:visible="batchModal" :tree-data="treeData" :default-pid="curTreeNode?.id"
|
||||
:default-type="batchDefaultType" @success="onBatchSuccess" />
|
||||
|
||||
<GroupSort v-model:visible="sortModal" :parent-id="sortParentId" :parent-name="sortParentName"
|
||||
:children="sortChildrenSource" @saved="onSortSaved" />
|
||||
|
||||
<GroupMove v-model:visible="moveModal" :tree-data="treeData" :move-id="moveNodeId" :move-name="moveNodeName"
|
||||
:move-path="moveNodePath" @saved="onMoveSaved" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, onMounted, reactive, ref, useTemplateRef } from "vue";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Message } from "view-ui-plus";
|
||||
import SvgIcon from "@/components/SvgIcon.vue";
|
||||
import GroupTreeSearchPanel from '@/components/GroupTreeSearchPanel.vue'
|
||||
import { useDefaultStore } from "@/store";
|
||||
import MyConfirm from "@/components/MyConfirm";
|
||||
import Apis from "@/api";
|
||||
import BatchAddGroup from "./BatchAddGroup.vue";
|
||||
import GroupSort from "./GroupSort.vue";
|
||||
import GroupMove from "./GroupMove.vue";
|
||||
import Utils from "@/utils/index.ts";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
treeApi?: () => Promise<any[]>;
|
||||
}>(), {
|
||||
treeApi: () => Apis.SysGroup.getTree(),
|
||||
})
|
||||
const treeApi = props.treeApi
|
||||
|
||||
const emit = defineEmits<{
|
||||
'on-change': [treeNode: any]
|
||||
'on-tree-data': [tree: any[]]
|
||||
}>()
|
||||
|
||||
const curTreeNode = defineModel<any>('curTreeNode', { default: {} })
|
||||
|
||||
const store = useDefaultStore();
|
||||
|
||||
const refTreePanel = useTemplateRef('refTreePanel');
|
||||
const refFormData = useTemplateRef('refFormData')
|
||||
const refNodeNameInput = useTemplateRef('refNodeNameInput')
|
||||
|
||||
const treeData = ref<any[]>([])
|
||||
|
||||
const batchModal = ref(false)
|
||||
const batchDefaultType = ref<number | null>(null)
|
||||
|
||||
const sortModal = ref(false)
|
||||
const sortParentId = ref<number | null>(null)
|
||||
const sortParentName = ref('')
|
||||
const sortChildrenSource = ref<any[]>([])
|
||||
|
||||
const moveModal = ref(false)
|
||||
const moveNodeId = ref<number | null>(null)
|
||||
const moveNodeName = ref('')
|
||||
const moveNodePath = ref('')
|
||||
|
||||
const editNodeModal = reactive({
|
||||
modal: false,
|
||||
title: '',
|
||||
loading: false,
|
||||
name: null as any,
|
||||
id: null as any,
|
||||
parentId: null as any,
|
||||
type: null as any,
|
||||
})
|
||||
|
||||
const groupTypeOptions = computed(() => {
|
||||
const dict: any = (store.getSysDict as any)?.grouptype || [];
|
||||
const map = (x: any) => ({ value: Number(x.key ?? x.k), label: String(x.value ?? x.v ?? x.name) });
|
||||
const list = Array.isArray(dict) ? dict.map(map) : Object.entries(dict).map(([k, v]) => ({ value: Number(k), label: String(v) }));
|
||||
if (list.length === 0) list.push({ value: 4, label: '班级组' });
|
||||
return list;
|
||||
})
|
||||
|
||||
const curNodePath = computed(() => {
|
||||
if (!curTreeNode.value || !curTreeNode.value.getPath) return [];
|
||||
return curTreeNode.value.getPath().map((x: any) => x.name)
|
||||
})
|
||||
|
||||
function onTreeData(tree: any[]) {
|
||||
treeData.value = tree;
|
||||
emit('on-tree-data', cloneDeep(tree));
|
||||
}
|
||||
|
||||
function treeLink(treeNode: any) {
|
||||
const copyNode = cloneDeep(treeNode)
|
||||
copyNode.children = [];
|
||||
curTreeNode.value = copyNode;
|
||||
emit('on-change', copyNode)
|
||||
}
|
||||
|
||||
function currentNode() {
|
||||
return curTreeNode.value?.id ? curTreeNode.value : (treeData.value[0] || {});
|
||||
}
|
||||
|
||||
async function showAddNode() {
|
||||
const node = currentNode();
|
||||
editNodeModal.title = '新增子组';
|
||||
refFormData.value?.resetFields();
|
||||
await Utils.sleep(0);
|
||||
editNodeModal.modal = true;
|
||||
editNodeModal.id = null;
|
||||
editNodeModal.name = null;
|
||||
editNodeModal.parentId = node?.id ?? null;
|
||||
editNodeModal.type = node?.type ?? groupTypeOptions.value[0]?.value ?? null;
|
||||
setTimeout(() => {
|
||||
|
||||
refNodeNameInput.value?.focus();
|
||||
}, 300);
|
||||
|
||||
}
|
||||
|
||||
async function showEditNode() {
|
||||
const node = currentNode();
|
||||
editNodeModal.title = '编辑组';
|
||||
refFormData.value?.resetFields();
|
||||
await Utils.sleep(0);
|
||||
editNodeModal.modal = true;
|
||||
editNodeModal.id = node?.id ?? null;
|
||||
editNodeModal.name = node?.name ?? '';
|
||||
editNodeModal.parentId = node?.parentId ?? null;
|
||||
editNodeModal.type = node?.type ?? null;
|
||||
|
||||
nextTick().then(() => refNodeNameInput.value?.focus())
|
||||
}
|
||||
|
||||
function switchToBatch() {
|
||||
batchDefaultType.value = editNodeModal.type ?? null;
|
||||
editNodeModal.modal = false;
|
||||
batchModal.value = true;
|
||||
}
|
||||
|
||||
function findNode(list: any[], id: number): any {
|
||||
for (const n of list || []) {
|
||||
if (n.id === id) return n;
|
||||
if (n.children?.length) {
|
||||
const r = findNode(n.children, id);
|
||||
if (r) return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function showSortNode() {
|
||||
const node = currentNode();
|
||||
if (!node?.id) {
|
||||
Message.warning('请先选择一个组');
|
||||
return;
|
||||
}
|
||||
const found = findNode(treeData.value, node.id);
|
||||
sortChildrenSource.value = found?.children ?? [];
|
||||
sortParentId.value = node.id;
|
||||
sortParentName.value = node.name ?? '';
|
||||
sortModal.value = true;
|
||||
}
|
||||
|
||||
function showMoveNode() {
|
||||
const node = currentNode();
|
||||
if (!node?.id) {
|
||||
Message.warning('请先选择一个组');
|
||||
return;
|
||||
}
|
||||
moveNodeId.value = node.id;
|
||||
moveNodeName.value = node.name ?? '';
|
||||
moveNodePath.value = node.getPath ? node.getPath().map((x: any) => x.name).join('/') : (node.name ?? '');
|
||||
moveModal.value = true;
|
||||
}
|
||||
|
||||
function detachNode(list: any[], id: number): any {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].id === id) {
|
||||
const [n] = list.splice(i, 1);
|
||||
return n;
|
||||
}
|
||||
if (list[i].children?.length) {
|
||||
const r = detachNode(list[i].children, id);
|
||||
if (r) {
|
||||
if (list[i].children.length === 0) {
|
||||
delete list[i].children;
|
||||
list[i].isParent = false;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function attachNode(list: any[], pid: number, node: any): boolean {
|
||||
for (const n of list) {
|
||||
if (n.id === pid) {
|
||||
n.children = n.children || [];
|
||||
n.children.push(node);
|
||||
n.isParent = true;
|
||||
return true;
|
||||
}
|
||||
if (n.children?.length && attachNode(n.children, pid, node)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function onMoveSaved({ movedId, newPid }: { movedId: number; newPid: number }) {
|
||||
const full = findNode(treeData.value, movedId);
|
||||
const isParent = !!(full?.children && full.children.length);
|
||||
const name = full?.name ?? moveNodeName.value;
|
||||
const children = isParent ? cloneDeep(full?.children) : undefined;
|
||||
|
||||
const detached = detachNode(treeData.value, movedId);
|
||||
if (detached) {
|
||||
attachNode(treeData.value, newPid, { ...detached, isParent, children: isParent ? detached.children : undefined });
|
||||
}
|
||||
|
||||
refTreePanel.value?.removeNode(movedId);
|
||||
refTreePanel.value?.addNote(newPid, movedId, name, isParent, children);
|
||||
refTreePanel.value?.selectNode(newPid, true);
|
||||
}
|
||||
|
||||
function showDeleteNode() {
|
||||
const node = currentNode();
|
||||
if (!node?.id) {
|
||||
Message.warning('请先选择一个组');
|
||||
return;
|
||||
}
|
||||
MyConfirm({
|
||||
title: '删除组',
|
||||
msg: `确定删除名为【${node?.name}】的组?`,
|
||||
info: '删除后不可恢复!',
|
||||
async onOk() {
|
||||
try {
|
||||
await Apis.SysGroup.delete({ pathParams: { id: node?.id } });
|
||||
Message.success('删除成功!');
|
||||
refTreePanel.value?.removeNode(node?.id);
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function saveEditNode() {
|
||||
const validate = await refFormData.value?.validate();
|
||||
if (!validate) {
|
||||
Message.error('请填写红色星号内容!');
|
||||
return;
|
||||
}
|
||||
const { id, name } = editNodeModal;
|
||||
editNodeModal.loading = true;
|
||||
try {
|
||||
if (!id) {
|
||||
const parentId = editNodeModal.parentId;
|
||||
const data: any = { name, parentId, type: Number(editNodeModal.type) };
|
||||
await Apis.SysGroup.add({ data });
|
||||
Message.success('新增成功');
|
||||
await refTreePanel.value?.refreshTree(parentId);
|
||||
} else {
|
||||
await Apis.SysGroup.edit({ pathParams: { id }, data: { name, type: Number(editNodeModal.type) } });
|
||||
Message.success('编辑成功');
|
||||
await refTreePanel.value?.refreshTree(id);
|
||||
}
|
||||
editNodeModal.modal = false;
|
||||
} catch (e: any) {
|
||||
Message.error(e?.message || '保存失败');
|
||||
} finally {
|
||||
editNodeModal.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onBatchSuccess() {
|
||||
refTreePanel.value?.refreshTree(curTreeNode.value?.id);
|
||||
}
|
||||
|
||||
function onSortSaved() {
|
||||
refTreePanel.value?.refreshTree(curTreeNode.value?.id);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick();
|
||||
refTreePanel.value?.refreshTree(curTreeNode.value?.id);
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.group-user-tree {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.opera-ul {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
padding-bottom: 10px;
|
||||
|
||||
li {
|
||||
padding: 10px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.ivu-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
li.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.4;
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.add-node-li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-node-li {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user