admin.xpcool.com/apps/web-tdesign/src/views/system/menu/index.vue

245 lines
6.4 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.

<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>