新增目录和权限
1. 创建页面文件
在 Vort-Admin/src/views 下新建 test 文件夹,在 test 文件夹下新增 Index.vue(主页面)和 Form.vue(新增/编辑/详情弹窗)两个页面文件。
Index.vue(主页面)

Form.vue(新增/编辑/详情弹窗)

2. 配置路由
2.1 新增路由文件
在 Vort-Admin/src/router/routes/admin 目录下新增 test.ts 路由文件。
示例结构(按需调整 title/icon/name/path):
import Layout from "@/layouts/base/Index.vue";
export default {
path: "/test",
name: "test",
component: Layout,
meta: { title: "测试" },
children: [
{
path: "",
name: "testDir",
meta: { title: "测试一级目录", icon: "menu/xxx" },
redirect: "/test/list",
children: [
{
path: "list",
name: "testManage",
meta: { title: "测试二级目录" },
component: () => import("@/views/test/Index.vue")
}
]
}
]
};
2.2 注册路由模块
在 Vort-Admin/src/router/routes/admin/index.ts 中引入并加入导出数组。

3. 配置菜单权限
进入:设置 → 账号权限 → 菜单管理(管理后台 Tab)。

菜单会根据接口返回的菜单树渲染,唯一编号(code) 需要与前端路由的 name 一致(且只能输入英文字母)。
3.1 新增顶级目录(测试总目录)
点击 添加菜单,填写:
- 菜单名称:测试
- 唯一编号:
test - ICO编码:
menu/xxx - 是否显示:是
- 排序:按需

3.2 新增一级目录
在“测试”这一行点击 添加子菜单,填写:
- 菜单名称:测试一级目录
- 唯一编号:
testDir - ICO编码:
menu/xxx - 是否显示:是
- 排序:按需
3.3 新增二级菜单(落地页面)
在“测试一级目录”这一行点击 添加子菜单,填写:
- 菜单名称:测试二级目录
- 唯一编号:
testManage - 是否显示:是
- 排序:按需
保存后刷新页面,菜单栏会根据当前用户权限自动生成对应目录与页面入口。
4. 如何打开测试页面的详情(新增/编辑/详情弹窗)
在 Vort-Admin/src/views/test/Index.vue 中通过 DialogForm 打开 Form.vue:
- 新增:
params={ act: 'add' } - 编辑/详情:
params={ act: 'detail', id: row.id }
4.1 Index.vue 示例代码
<template>
<div class="container">
<div class="content-wrapper flex flex-wrap flex-col gap-4">
<vort-card class="vort-surface" :title="pageTitle">
<SearchToolbar :on-search="onSearchSubmit" :on-reset="resetParams">
<SearchForm>
<!-- 搜索项 -->
<SearchFormItem label="关键词">
<vort-input v-model="filterParams.keyword" placeholder="请输入关键词" />
</SearchFormItem>
<SearchFormActions />
</SearchForm>
<!-- 操作按钮区域 -->
<template #actions>
<div class="flex gap-2 flex-wrap">
<!-- 新增按钮 -->
<DialogForm
:component="TestForm"
:params="{ act: 'add' }"
is-drawer
title="新增测试"
width="580px"
@ok="loadData"
>
<vort-button variant="primary">新增</vort-button>
</DialogForm>
</div>
</template>
</SearchToolbar>
</vort-card>
<!-- 数据表格 -->
<vort-card class="vort-surface">
<div class="vort-table-wrapper">
<vort-table :data-source="listData" :loading="loading" row-key="id" size="large">
<vort-table-column label="ID" prop="id" :width="80" />
<vort-table-column label="名称" prop="name" />
<vort-table-column label="创建时间" prop="createTime" />
<!-- 操作列 -->
<vort-table-column :width="180" fixed="right" label="操作">
<template #default="{ row }">
<TableActions type="text" :divider="true">
<!-- 编辑 -->
<TableActionsItem>
<DialogForm
:component="TestForm"
:params="{ act: 'detail', id: row.id }"
is-drawer
title="编辑测试"
width="580px"
@ok="loadData"
>
<a class="btn-link">编辑</a>
</DialogForm>
</TableActionsItem>
<!-- 删除 -->
<TableActionsItem>
<DeleteRecord
:params="{ id: row.id }"
:request-api="testApi.del"
:on-success="loadData"
>
<a class="btn-link">删除</a>
</DeleteRecord>
</TableActionsItem>
</TableActions>
</template>
</vort-table-column>
</vort-table>
<!-- 分页 -->
<div v-if="showPagination" class="vort-pagination-wrapper">
<vort-pagination
v-model:current="filterParams.page"
v-model:page-size="filterParams.size"
:total="total"
show-total-info
show-size-changer
show-quick-jumper
@change="loadData"
/>
</div>
</div>
</vort-card>
</div>
</div>
</template>
<script lang="ts" setup>
// ===================== 组件导入 =====================
import {
SearchToolbar,
SearchForm,
SearchFormItem,
SearchFormActions,
DialogForm,
TableActions,
TableActionsItem,
DeleteRecord
} from "@/components/vort-biz";
import TestForm from "./Form.vue";
// ===================== API & 类型导入 =====================
import { testApi } from "@/api/modules/test";
import type { TestListParams, TestItem } from "@/types/modules/test";
// ===================== Hooks =====================
import { useCrudPage, usePageTitle } from "@/hooks";
const { pageTitle } = usePageTitle();
// ===================== CRUD 页面 Hook =====================
const { listData, loading, total, filterParams, showPagination, loadData, onSearchSubmit, resetParams } = useCrudPage<
TestItem,
TestListParams
>({
api: testApi.getList,
idKey: "id",
defaultParams: {
page: 1,
size: 20,
keyword: ""
}
});
// ===================== 初始化 =====================
loadData();
</script>
在 Vort-Admin/src/views/test/Form.vue 中按需实现:
act=detail:根据id获取详情并回填- 提交:根据
act调用create/update接口,成功后关闭弹窗并触发父页面@ok刷新列表
4.2 Form.vue 示例代码
<template>
<vort-spin :spinning="loading">
<vort-form
ref="formRef"
:model="formState as Record<string, unknown>"
:rules="formRules"
label-width="100px"
:disabled="isReadonly"
>
<vort-form-item label="名称" name="name" required>
<vort-input v-model="formState.name" placeholder="请输入名称" />
</vort-form-item>
<vort-form-item label="描述" name="description">
<vort-textarea v-model="formState.description" :rows="3" placeholder="请输入描述" />
</vort-form-item>
<vort-form-item label="排序" name="sortOrder">
<vort-input-number v-model="formState.sortOrder" />
</vort-form-item>
</vort-form>
</vort-spin>
</template>
<script lang="ts" setup>
import { shallowRef } from "vue";
import { z } from "zod";
// ===================== Hooks =====================
import { useDialogForm, type DialogFormProps, type CommonFormParams } from "@/hooks";
// ===================== API & 类型 =====================
import { testApi } from "@/api/modules/test";
import type { TestFormData } from "@/types/modules/test";
// ===================== Props =====================
const props = defineProps<DialogFormProps<CommonFormParams>>();
// ===================== 表单配置 =====================
const formRef = shallowRef();
// 表单验证规则(使用 zod)
const formRules = z.object({
name: z.string().trim().min(1, "名称不能为空").max(50, "名称最多50个字符"),
description: z.string().optional(),
sortOrder: z.number().optional()
});
// ===================== 使用 Hook =====================
const { loading, formState, isReadonly, onSubmit } = useDialogForm<TestFormData>({
props,
formRef,
// 表单默认值
defaultFormState: {
name: "",
description: "",
sortOrder: 50
},
// 获取详情接口
detailApi: (params) => testApi.getDetail("detail", params),
// 提交接口(新增/编辑)
submitApi: (operation, data) =>
testApi.update(operation, {
...data,
id: props.params?.id
})
});
// ===================== 暴露方法 =====================
// 必须暴露 onFormSubmit 方法,DialogForm 会调用此方法提交表单
defineExpose({
onFormSubmit: onSubmit
});
</script>
4.3 核心要点说明
| 要点 | 说明 |
|---|---|
DialogForm 组件 | 用于包裹触发按钮,点击后自动打开弹窗/抽屉并加载 Form 组件 |
params 属性 | 传递给 Form 组件的参数,act 为操作类型(add/detail),id 为数据 ID |
@ok 事件 | Form 提交成功后触发,通常用于刷新列表 |
useDialogForm Hook | 封装了加载详情、表单状态管理、提交等通用逻辑 |
defineExpose | 必须暴露 onFormSubmit 方法,DialogForm 点击确定时会调用此方法 |







