新增目录和权限

1. 创建页面文件

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

Index.vue(主页面)

image.png

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

image.png


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 中引入并加入导出数组。

image.png


3. 配置菜单权限

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

image.png

菜单会根据接口返回的菜单树渲染,唯一编号(code) 需要与前端路由的 name 一致(且只能输入英文字母)。

3.1 新增顶级目录(测试总目录)

点击 添加菜单,填写:

  • 菜单名称:测试
  • 唯一编号:test
  • ICO编码:menu/xxx
  • 是否显示:是
  • 排序:按需

image.png

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 点击确定时会调用此方法
新增目录和权限
Enter search text
Outline
1. 创建页面文件
2. 配置路由
2.1 新增路由文件
2.2 注册路由模块
3. 配置菜单权限
3.1 新增顶级目录(测试总目录)
3.2 新增一级目录
3.3 新增二级菜单(落地页面)
4. 如何打开测试页面的详情(新增/编辑/详情弹窗)
4.1 Index.vue 示例代码
4.2 Form.vue 示例代码
4.3 核心要点说明