Some checks failed
Build and Deploy (admin.xpcool.com) / build-and-deploy (push) Failing after 1h0m4s
245 lines
6.4 KiB
Vue
245 lines
6.4 KiB
Vue
<script lang="ts" setup>
|
||
import type { MenuNode } from '#/api/system';
|
||
|
||
import { nextTick, onMounted, reactive, ref } from 'vue';
|
||
|
||
import { useAccess } from '@vben/access';
|
||
import { Page } from '@vben/common-ui';
|
||
|
||
import { DialogPlugin, MessagePlugin } from 'tdesign-vue-next';
|
||
|
||
import { createMenu, deleteMenu, getMenuTree, updateMenu } from '#/api/system';
|
||
|
||
const { hasAccessByCodes } = useAccess();
|
||
|
||
const loading = ref(false);
|
||
// 菜单树(嵌套 children 结构),由 t-enhanced-table 树形模式渲染
|
||
const tree = ref<MenuNode[]>([]);
|
||
// t-enhanced-table 实例:异步数据加载完成后手动展开全部节点
|
||
const tableRef = ref<any>(null);
|
||
const dialogOpen = ref(false);
|
||
const editing = ref<MenuNode | null>(null);
|
||
|
||
const form = reactive({
|
||
parentId: 0,
|
||
name: '',
|
||
icon: '',
|
||
type: 1,
|
||
path: '',
|
||
component: '',
|
||
permission: '',
|
||
sort: 0,
|
||
status: 1,
|
||
hidden: false,
|
||
});
|
||
|
||
async function load() {
|
||
loading.value = true;
|
||
try {
|
||
const data = await getMenuTree();
|
||
tree.value = data.tree ?? [];
|
||
// 异步数据就绪后手动展开全部节点(defaultExpandAll 只在首次渲染时生效)
|
||
await nextTick();
|
||
tableRef.value?.expandAll?.();
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
function openCreate(parent?: MenuNode) {
|
||
editing.value = null;
|
||
Object.assign(form, {
|
||
parentId: parent?.id ?? 0,
|
||
name: '',
|
||
icon: '',
|
||
type: 1,
|
||
path: '',
|
||
component: '',
|
||
permission: '',
|
||
sort: 0,
|
||
status: 1,
|
||
hidden: false,
|
||
});
|
||
dialogOpen.value = true;
|
||
}
|
||
|
||
function openEdit(row: MenuNode) {
|
||
editing.value = row;
|
||
Object.assign(form, {
|
||
parentId: row.parentId,
|
||
name: row.name,
|
||
icon: row.icon,
|
||
type: row.type,
|
||
path: row.path,
|
||
component: row.component,
|
||
permission: row.permission,
|
||
sort: row.sort,
|
||
status: row.status,
|
||
hidden: row.hidden,
|
||
});
|
||
dialogOpen.value = true;
|
||
}
|
||
|
||
async function submit() {
|
||
if (editing.value) {
|
||
await updateMenu(editing.value.id, { ...form });
|
||
} else {
|
||
await createMenu({ ...form });
|
||
}
|
||
MessagePlugin.success('保存成功');
|
||
dialogOpen.value = false;
|
||
load();
|
||
}
|
||
|
||
function onDelete(row: MenuNode) {
|
||
const d = DialogPlugin.confirm({
|
||
header: `确认删除「${row.name}」?`,
|
||
onConfirm: async () => {
|
||
await deleteMenu(row.id);
|
||
MessagePlugin.success('删除成功');
|
||
load();
|
||
d.destroy();
|
||
},
|
||
});
|
||
}
|
||
|
||
const columns = [
|
||
{ colKey: 'name', title: '名称' },
|
||
{ colKey: 'typeText', title: '类型', width: 90 },
|
||
{ colKey: 'path', title: '路径' },
|
||
{ colKey: 'permission', title: '权限标识' },
|
||
{ colKey: 'component', title: '组件' },
|
||
{ colKey: 'sort', title: '排序', width: 70 },
|
||
{ colKey: 'op', title: '操作', width: 200 },
|
||
];
|
||
|
||
onMounted(load);
|
||
</script>
|
||
|
||
<template>
|
||
<Page auto-content-height>
|
||
<div class="wb-page-head">
|
||
<h2 class="wb-page-title">菜单管理</h2>
|
||
<p class="wb-page-sub">维护系统菜单树与按钮权限</p>
|
||
</div>
|
||
|
||
<t-card :bordered="false" class="wb-filter-card">
|
||
<div class="mb-3 flex justify-end">
|
||
<t-button
|
||
v-if="hasAccessByCodes(['system:menu:create'])"
|
||
theme="primary"
|
||
@click="openCreate()"
|
||
>
|
||
新增菜单
|
||
</t-button>
|
||
</div>
|
||
</t-card>
|
||
|
||
<t-card :bordered="false" class="wb-table-card">
|
||
<t-enhanced-table
|
||
ref="tableRef"
|
||
row-key="id"
|
||
:data="tree"
|
||
:columns="columns"
|
||
:loading="loading"
|
||
:pagination="false"
|
||
:tree="{
|
||
childrenKey: 'children',
|
||
treeNodeColumnIndex: 0,
|
||
defaultExpandAll: true,
|
||
indent: 20,
|
||
}"
|
||
>
|
||
<template #name="{ row }">
|
||
{{ row.name }}
|
||
</template>
|
||
<template #typeText="{ row }">
|
||
{{
|
||
row.type === 1 ? '菜单' : '按钮'
|
||
}}
|
||
</template>
|
||
<template #op="{ row }">
|
||
<t-button
|
||
v-if="hasAccessByCodes(['system:menu:create'])"
|
||
size="small"
|
||
theme="primary"
|
||
variant="text"
|
||
@click="openCreate(row)"
|
||
>
|
||
新增子项
|
||
</t-button>
|
||
<t-button
|
||
v-if="hasAccessByCodes(['system:menu:update'])"
|
||
size="small"
|
||
theme="primary"
|
||
variant="text"
|
||
@click="openEdit(row)"
|
||
>
|
||
编辑
|
||
</t-button>
|
||
<t-button
|
||
v-if="hasAccessByCodes(['system:menu:delete'])"
|
||
size="small"
|
||
theme="danger"
|
||
variant="text"
|
||
@click="onDelete(row)"
|
||
>
|
||
删除
|
||
</t-button>
|
||
</template>
|
||
</t-enhanced-table>
|
||
</t-card>
|
||
|
||
<t-dialog
|
||
v-model:visible="dialogOpen"
|
||
:header="editing ? '编辑菜单' : '新增菜单'"
|
||
:on-confirm="submit"
|
||
>
|
||
<t-form label-align="right" label-width="80px">
|
||
<t-form-item label="上级 ID">
|
||
<t-input-number v-model="form.parentId" :min="0" />
|
||
</t-form-item>
|
||
<t-form-item label="名称" required>
|
||
<t-input v-model="form.name" />
|
||
</t-form-item>
|
||
<t-form-item label="类型">
|
||
<t-radio-group v-model="form.type">
|
||
<t-radio-button :value="1">菜单</t-radio-button>
|
||
<t-radio-button :value="2">按钮</t-radio-button>
|
||
</t-radio-group>
|
||
</t-form-item>
|
||
<t-form-item v-if="form.type === 1" label="图标">
|
||
<t-input
|
||
v-model="form.icon"
|
||
placeholder="iconify 名称,如 mdi:home"
|
||
/>
|
||
</t-form-item>
|
||
<t-form-item label="路径">
|
||
<t-input v-model="form.path" placeholder="菜单路由或接口映射" />
|
||
</t-form-item>
|
||
<t-form-item v-if="form.type === 1" label="组件">
|
||
<t-input
|
||
v-model="form.component"
|
||
placeholder="如 system/admin/index"
|
||
/>
|
||
</t-form-item>
|
||
<t-form-item label="权限标识" required>
|
||
<t-input
|
||
v-model="form.permission"
|
||
placeholder="如 system:admin:list"
|
||
/>
|
||
</t-form-item>
|
||
<t-form-item label="排序">
|
||
<t-input-number v-model="form.sort" :min="0" />
|
||
</t-form-item>
|
||
<t-form-item label="状态">
|
||
<t-switch v-model="form.status" :custom-value="[1, 0]" />
|
||
</t-form-item>
|
||
<t-form-item label="隐藏">
|
||
<t-switch v-model="form.hidden" />
|
||
</t-form-item>
|
||
</t-form>
|
||
</t-dialog>
|
||
</Page>
|
||
</template>
|