Browse Source

代码重构

dev-V1.0.1
庄锐波 6 months ago
parent
commit
286572de6a
  1. 3
      zdxt-web-client/zdxt-admin-plus-ui/package.json
  2. 8
      zdxt-web-client/zdxt-admin-plus-ui/src/store/modules/permission.ts
  3. 299
      zdxt-web-client/zdxt-admin-plus-ui/src/views/demo/wunderbaum/demo1.vue
  4. 44
      zdxt-web-client/zdxt-admin-plus-ui/src/views/login.vue
  5. 2
      zdxt-web-client/zdxt-admin-plus-ui/tsconfig.json

3
zdxt-web-client/zdxt-admin-plus-ui/package.json

@ -44,7 +44,8 @@
"vue-json-pretty": "2.6.0", "vue-json-pretty": "2.6.0",
"vue-router": "4.6.3", "vue-router": "4.6.3",
"vue-types": "6.0.0", "vue-types": "6.0.0",
"vxe-table": "4.17.7" "vxe-table": "4.17.7",
"wunderbaum": "^0.14.1"
}, },
"devDependencies": { "devDependencies": {
"@iconify/json": "^2.2.403", "@iconify/json": "^2.2.403",

8
zdxt-web-client/zdxt-admin-plus-ui/src/store/modules/permission.ts

@ -70,8 +70,10 @@ export const usePermissionStore = defineStore('permission', () => {
sidebarRouters.value = routes; sidebarRouters.value = routes;
}; };
const generateRoutes = async (systemCode?: string): Promise<RouteRecordRaw[]> => { const generateRoutes = async (systemCode?: string): Promise<RouteRecordRaw[]> => {
const res = await getRouters(systemCode || currentSystemCode.value); const code = systemCode || currentSystemCode.value;
const res = await getRouters(code);
const { data } = res; const { data } = res;
console.log('[路由调试] systemCode =', code, ', 后端返回路由数据 =', JSON.parse(JSON.stringify(data)));
const sdata = JSON.parse(JSON.stringify(data)); const sdata = JSON.parse(JSON.stringify(data));
const rdata = JSON.parse(JSON.stringify(data)); const rdata = JSON.parse(JSON.stringify(data));
const defaultData = JSON.parse(JSON.stringify(data)); const defaultData = JSON.parse(JSON.stringify(data));
@ -205,15 +207,19 @@ export const filterDynamicRoutes = (routes: RouteRecordRaw[]) => {
export const loadView = (view: any, name: string) => { export const loadView = (view: any, name: string) => {
let res; let res;
const allDirs: string[] = [];
for (const path in modules) { for (const path in modules) {
const viewsIndex = path.indexOf('/views/'); const viewsIndex = path.indexOf('/views/');
let dir = path.substring(viewsIndex + 7); let dir = path.substring(viewsIndex + 7);
dir = dir.substring(0, dir.lastIndexOf('.vue')); dir = dir.substring(0, dir.lastIndexOf('.vue'));
allDirs.push(dir);
if (dir === view) { if (dir === view) {
res = createCustomNameComponent(modules[path], { name }); res = createCustomNameComponent(modules[path], { name });
console.log('[路由调试] loadView 匹配成功: view =', view, ', name =', name);
return res; return res;
} }
} }
console.warn('[路由调试] loadView 未匹配到组件! view =', JSON.stringify(view), ', name =', name, '\n glob扫描到的路径:', allDirs);
return res; return res;
}; };

299
zdxt-web-client/zdxt-admin-plus-ui/src/views/demo/wunderbaum/demo1.vue

@ -0,0 +1,299 @@
<script setup lang="ts">
import { Wunderbaum } from 'wunderbaum';
import 'wunderbaum/dist/wunderbaum.css';
import { deptTreeSelect } from '@/api/system/user';
import type { DeptTreeVO } from '@/api/system/dept/types';
// ===== 类型定义 =====
interface WbNodeData {
title: string;
key: string;
children?: WbNodeData[];
[k: string]: unknown;
}
// ===== DOM Refs =====
const leftTreeEl = ref<HTMLDivElement>();
const rightTreeEl = ref<HTMLDivElement>();
const leftFilterText = ref('');
const rightFilterText = ref('');
// ===== Wunderbaum 实例 =====
let leftTree: Wunderbaum;
let rightTree: Wunderbaum;
// ===== 数据转换:DeptTreeVO → WbNodeData =====
const convertToWbData = (nodes: DeptTreeVO[]): WbNodeData[] => {
return nodes.map((node) => ({
title: node.label,
key: String(node.id),
expanded: true,
children: node.children?.length ? convertToWbData(node.children) : undefined
}));
};
// ===== 加载部门数据 =====
const loading = ref(true);
const initTrees = async () => {
try {
loading.value = true;
const { data } = await deptTreeSelect();
const wbData = convertToWbData(data);
// 左树 & 右树使用相同数据(深拷贝)
const leftData = JSON.parse(JSON.stringify(wbData));
const rightData = JSON.parse(JSON.stringify(wbData));
// 共享的 DnD 配置
const dndOptions = {
preventForeignNodes: false, // 允许跨树拖拽
preventRecursion: true, // 禁止拖入自身子节点
preventVoidMoves: true, // 禁止无意义移动
dropEffectDefault: 'move' as const,
effectAllowed: 'all' as const,
guessDropEffect: true,
dragStart: () => true,
dragEnter: () => {
// 允许放置在 before / after / over 三个位置
return new Set(['before', 'after', 'over'] as const);
},
drop: (e: any) => {
const sourceNode = e.sourceNode;
const targetNode = e.node;
const suggestedDropMode = e.suggestedDropMode;
if (sourceNode && targetNode) {
sourceNode.moveTo(targetNode, suggestedDropMode);
// 更新两棵树的渲染
leftTree?.update('structure' as any);
rightTree?.update('structure' as any);
}
}
};
// 创建左树
leftTree = new Wunderbaum({
id: 'leftTree',
element: leftTreeEl.value!,
source: leftData,
debugLevel: 0,
checkbox: false,
filter: {
autoExpand: true,
mode: 'hide'
},
dnd: dndOptions,
init: () => {
loading.value = false;
}
});
// 创建右树
rightTree = new Wunderbaum({
id: 'rightTree',
element: rightTreeEl.value!,
source: rightData,
debugLevel: 0,
checkbox: false,
filter: {
autoExpand: true,
mode: 'hide'
},
dnd: dndOptions
});
} catch (err) {
console.error('加载部门树失败:', err);
loading.value = false;
}
};
// ===== 过滤逻辑 =====
const onLeftFilter = () => {
if (!leftTree) return;
const keyword = leftFilterText.value.trim();
if (keyword) {
leftTree.filterNodes(keyword, { autoExpand: true, mode: 'hide' });
} else {
leftTree.clearFilter();
}
};
const onRightFilter = () => {
if (!rightTree) return;
const keyword = rightFilterText.value.trim();
if (keyword) {
rightTree.filterNodes(keyword, { autoExpand: true, mode: 'hide' });
} else {
rightTree.clearFilter();
}
};
// ===== 展开 / 收起全部 =====
const expandAll = (tree: Wunderbaum | undefined) => {
tree?.expandAll(true);
};
const collapseAll = (tree: Wunderbaum | undefined) => {
tree?.expandAll(false);
};
// ===== 生命周期 =====
onMounted(() => {
initTrees();
});
onBeforeUnmount(() => {
leftTree?.destroy();
rightTree?.destroy();
});
</script>
<template>
<div class="wunderbaum-demo-page">
<h3 class="page-title">Wunderbaum 双树跨树拖拽 Demo</h3>
<p class="page-desc">左右两棵部门树支持互相拖拽节点(拖入节点前方、后方、或内部),支持搜索过滤。拖拽结果不保存回服务端。</p>
<div v-loading="loading" class="tree-container">
<!-- 左树 -->
<div class="tree-panel">
<div class="tree-toolbar">
<span class="tree-label">部门树 A</span>
<el-input
v-model="leftFilterText"
placeholder="搜索..."
clearable
size="small"
style="width: 180px"
@input="onLeftFilter"
@clear="onLeftFilter"
/>
<el-button size="small" @click="expandAll(leftTree)">展开全部</el-button>
<el-button size="small" @click="collapseAll(leftTree)">收起全部</el-button>
</div>
<div ref="leftTreeEl" class="tree-wrapper"></div>
</div>
<!-- 中间拖拽提示 -->
<div class="drag-hint">
<el-icon :size="28"><Sort /></el-icon>
<span>拖拽</span>
</div>
<!-- 右树 -->
<div class="tree-panel">
<div class="tree-toolbar">
<span class="tree-label">部门树 B</span>
<el-input
v-model="rightFilterText"
placeholder="搜索..."
clearable
size="small"
style="width: 180px"
@input="onRightFilter"
@clear="onRightFilter"
/>
<el-button size="small" @click="expandAll(rightTree)">展开全部</el-button>
<el-button size="small" @click="collapseAll(rightTree)">收起全部</el-button>
</div>
<div ref="rightTreeEl" class="tree-wrapper"></div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.wunderbaum-demo-page {
padding: 20px;
}
.page-title {
margin: 0 0 8px 0;
font-size: 18px;
}
.page-desc {
margin: 0 0 16px 0;
color: #666;
font-size: 13px;
}
.tree-container {
display: flex;
align-items: stretch;
gap: 12px;
min-height: 500px;
}
.tree-panel {
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid #dcdfe6;
border-radius: 4px;
overflow: hidden;
}
.tree-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f5f7fa;
border-bottom: 1px solid #dcdfe6;
flex-shrink: 0;
}
.tree-label {
font-weight: 600;
font-size: 14px;
white-space: nowrap;
}
.tree-wrapper {
flex: 1;
min-height: 400px;
overflow: auto;
}
.drag-hint {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
color: #909399;
font-size: 12px;
flex-shrink: 0;
width: 50px;
}
// Wunderbaum 拖拽样式增强
:deep(.wunderbaum) {
height: 100%;
}
:deep(.wb-row.wb-drop-over) {
background-color: #ecf5ff;
}
:deep(.wb-row.wb-drop-before::before) {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: #409eff;
}
:deep(.wb-row.wb-drop-after::after) {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background: #409eff;
}
</style>

44
zdxt-web-client/zdxt-admin-plus-ui/src/views/login.vue

@ -70,7 +70,25 @@
<router-link class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link> <router-link class="link-type" :to="'/register'">{{ proxy.$t('login.switchRegisterPage') }}</router-link>
</div> </div>
</el-form-item> </el-form-item>
<!-- 测试用户快捷登录按钮(仅开发环境显示) -->
<el-form-item style="width: 100%; margin-bottom: 0">
<el-button size="large" type="danger" style="width: 100%" @click="showTestUserDialog = true">
选择测试用户
</el-button>
</el-form-item>
</el-form> </el-form>
<!-- 测试用户选择弹窗 -->
<el-dialog v-model="showTestUserDialog" title="选择测试用户" width="420px" append-to-body>
<el-table :data="testUsers" highlight-current-row style="width: 100%" @row-click="selectTestUser">
<el-table-column prop="label" label="角色" width="140" />
<el-table-column prop="username" label="用户名" width="140" />
<el-table-column label="操作" align="center">
<template #default="{ row }">
<el-button type="primary" link @click="selectTestUser(row)">使用</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
<!-- 底部 --> <!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span> <span>Copyright © 2018-2026 疯狂的狮子Li All Rights Reserved.</span>
@ -124,6 +142,32 @@ const loginRef = ref<ElFormInstance>();
// 租户列表 // 租户列表
const tenantList = ref<TenantVO[]>([]); const tenantList = ref<TenantVO[]>([]);
// ===== 测试用户快捷登录 =====
const showTestUserDialog = ref(false);
interface TestUser {
label: string;
tenantId: string;
username: string;
password: string;
}
const testUsers = ref<TestUser[]>([
{ label: '超级管理员', tenantId: '000000', username: 'admin', password: 'szsf@zfjd' }
]);
const selectTestUser = (user: TestUser) => {
loginForm.value.tenantId = user.tenantId;
loginForm.value.username = user.username;
loginForm.value.password = user.password;
showTestUserDialog.value = false;
// 选择后自动登录
nextTick(() => {
handleLogin();
});
};
// ===== 测试用户快捷登录 END =====
watch( watch(
() => router.currentRoute.value, () => router.currentRoute.value,
(newRoute: any) => { (newRoute: any) => {

2
zdxt-web-client/zdxt-admin-plus-ui/tsconfig.json

@ -5,7 +5,7 @@
// https://vite.dev/config/build-options.html#build-target // https://vite.dev/config/build-options.html#build-target
"target": "ES2020", "target": "ES2020",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Node",
"lib": ["ESNext", "DOM", "DOM.Iterable"], "lib": ["ESNext", "DOM", "DOM.Iterable"],
"skipLibCheck": true, "skipLibCheck": true,
// This setting lets you specify a file for storing incremental compilation information as a part of composite projects which enables faster building of larger TypeScript codebases. // This setting lets you specify a file for storing incremental compilation information as a part of composite projects which enables faster building of larger TypeScript codebases.

Loading…
Cancel
Save