name: scaffold-dev-guide description: Vue3 中后台脚手架项目二次开发指南。指导开发者按照项目架构规范创建新业务模块,包括 API 两层架构(generated/client + composables)、路由配置、VxeTable + VbenForm 页面开发、i18n 国际化、Pinia Store 等完整工作流。当用户要求新建模块、新增页面、添加 API、创建 CRUD 功能、或询问项目开发规范时使用此 Skill。
脚手架项目二次开发指南
项目概览
基于 Vue 3 + Vite + TypeScript 的中后台管理系统脚手架,面向二开场景。
核心技术栈: Vue 3.5, Ant Design Vue 4.2, Tailwind CSS, Shadcn-ui, Pinia, Vue Router, Vue Query (TanStack Query), VxeTable, i18n, Axios
应用入口: apps/admin/src/
Vben 框架核心机制
本项目基于 Vben Admin 配置型框架,通过配置而非编码来完成大部分开发工作。理解以下机制至关重要:
组件注册机制(不要绕过)
框架有 两套组件注册体系,分别用于不同场景:
1. VbenForm 组件(schema 中使用)
定义在 adapter/component/index.ts,通过 globalShareState.setComponents() 注册。在 schema 的 component 字段中 只能使用这些注册过的名称:
Input, InputNumber, InputPassword, Select, ApiSelect, TreeSelect,
ApiTreeSelect, RadioGroup, Checkbox, CheckboxGroup, Switch, DatePicker,
RangePicker, TimePicker, Textarea, Upload, Editor, IconPicker, AutoComplete,
Mentions, Rate, Divider, Space, DefaultButton, PrimaryButton, ApiTree
2. Template 全局组件(template 中使用)
定义在 registerGlobComp.ts,通过 app.use() 全局注册。在 template 中 直接用 a-* 前缀:
<a-button>, <a-tag>, <a-popconfirm>, <a-input>, <a-select>,
<a-tree>, <a-table>, <a-dropdown>, <a-menu>, <a-card>, <a-space>,
<a-switch>, <a-tabs>, <a-divider>, <a-layout>
禁止:
import { Tag, Button } from 'ant-design-vue'然后在 template 中用<Tag>或<Button>。
配置驱动模式(不要自己写逻辑)
| 场景 | 配置方式 | 不要做 |
|---|---|---|
| 表格列日期格式化 | formatter: 'formatDateTime' |
Slot 中用 dayjs 格式化 |
| 表单必填校验 | rules: 'required' / 'selectRequired' |
用 Zod 或自定义校验函数 |
| 列表数据加载 | proxyConfig.ajax.query |
手动 watch + ref + async function |
| 分页 | pagerConfig: {} |
手动管理分页状态 |
| 表格刷新 | gridApi.reload() |
手动重新请求数据 |
| 表单赋值/取值 | baseFormApi.setValues() / baseFormApi.getValues() |
直接操作 DOM 或 ref |
| 表单校验 | baseFormApi.validate() |
手动检查每个字段 |
目录结构速查
apps/admin/src/
├── api/ # API 层(两层架构)
│ ├── generated/ # ← protobuf 自动生成,禁止手动编辑
│ ├── client.ts # ← ApiClient 单例(ClientTransport 适配器)
│ └── composables/ # ← Vue Query hooks 层:use*/fetch*/枚举工具
├── adapter/ # VbenForm + VxeTable 适配器配置
├── router/routes/modules/# ← 路由模块(按功能拆分)
├── stores/ # Pinia 状态管理
├── views/app/ # 业务页面(按功能模块组织)
├── locales/langs/ # i18n 国际化文件
│ ├── zh-CN/ # enum.json, menu.json, page.json, ui.json
│ └── en-US/
└── transport/rest/ # HTTP 传输层(PaginationQuery, requestApi)
关键约定(必须遵守)
数据层约定
- 禁止直接引用
#/api/generated/路径 — 业务层通过#/api统一入口导入 - composables 直接使用
apiClient— 导入apiClientfrom#/api/client,调用apiClient.xxxService.Method() - 组件内用
use*hooks,组件外(Store/路由守卫)用fetch*函数 - 更新操作只传变化字段 —
useUpdate*内部自动生成updateMask - Pinia Store 中不可依赖
useRouter()— Store 初始化时路由可能未就绪 - 所有列表查询统一使用
PaginationQuery
Vben 框架强规约
- 表单组件必须使用注册名 —
schema中component字段只能使用adapter/component/index.ts中注册的名称(如Input、Select、ApiSelect),不要使用 Ant Design Vue 原始组件名(如AInput、ASelect)或原生 HTML 标签 - Template 中使用
a-*前缀 — Ant Design Vue 组件已全局注册,在 template 中直接用<a-button>、<a-tag>、<a-popconfirm>等,不需要import { Tag } from 'ant-design-vue' - 图标在
:iconprop 中必须用h()渲染 —:icon="h(LucideFilePenLine)",图标组件从@vben/icons导入 - 日期列用
formatter: 'formatDateTime'— 不要在 Slot 中用 dayjs 手动格式化 - 表单校验用内置规则名 —
rules: 'required'或rules: 'selectRequired',不要用 Zod 表达式 - 删除操作必须二次确认 — 使用
<a-popconfirm>,不要用window.confirm或直接删除 - 国际化文本不硬编码 — 使用
$t()引用 locales 文件中的 key - 使用严格相等运算符
===— 禁止使用==
新建业务模块 Checklist
二开时创建新模块的标准流程:
- [ ] Step 1: 确认 generated 层已有类型(protobuf 已生成)
- [ ] Step 2: 创建 composables 层(src/api/composables/xxx.ts)
- [ ] Step 3: 注册导出(composables/index.ts)
- [ ] Step 4: 添加 i18n 翻译(zh-CN + en-US)
- [ ] Step 5: 创建路由模块(router/routes/modules/app/xxx.ts)
- [ ] Step 6: 创建视图页面(views/app/xxx/)
Step 1: 确认 generated 类型
确保 src/api/generated/admin/service/v1 中包含目标模块的:
- 类型定义:
xxxservicev1_EntityName - 请求/响应类型
- ApiClient 中已有
xxxServicegetter
Step 2-3: API 层开发
详见 api-patterns.md,包含完整的 composable 模板代码。
Step 4: i18n 国际化
在四个 JSON 文件中添加对应 key:
| 文件 | 用途 | key 前缀示例 |
|---|---|---|
zh-CN/enum.json |
枚举翻译 | "enum.xxx.status.NORMAL": "正常" |
zh-CN/menu.json |
菜单名称 | "menu.xxx.moduleName": "模块名" |
zh-CN/page.json |
页面文本 | "page.xxx.name": "名称" |
zh-CN/ui.json |
通用 UI 文本 | 已有通用 key,无需修改 |
en-US/ 目录下同步添加英文翻译。
Step 5: 路由配置
新建 router/routes/modules/app/xxx.ts:
import type { RouteRecordRaw } from 'vue-router';
import { BasicLayout } from '#/layouts';
import { $t } from '#/locales';
const routes: RouteRecordRaw[] = [
{
path: '/xxx',
name: 'XxxManagement',
component: BasicLayout,
redirect: '/xxx/list',
meta: {
order: 3001, // 菜单排序
icon: 'lucide:xxx', // lucide 图标
title: $t('menu.xxx.moduleName'),
authority: ['sys:platform_admin', 'sys:tenant_manager'],
},
children: [
{
path: 'list',
name: 'XxxList',
meta: {
title: $t('menu.xxx.list'),
authority: ['sys:platform_admin'],
},
component: () => import('#/views/app/xxx/index.vue'),
},
],
},
];
export default routes;
Step 6: 视图页面开发
详见 view-patterns.md,包含列表页、Drawer 表单页的完整模板。
导入路径约定
// API 导入 — 统一通过 #/api 入口
import { useListUsers, PaginationQuery, userStatusToName } from '#/api';
// 类型导入 — 也通过 #/api(re-export 了 generated 类型)
import { type identityservicev1_User as User } from '#/api';
// 适配器导入
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
// 布局与通用组件
import { Page, useVbenDrawer } from '@vben/common-ui';
// 国际化
import { $t } from '@vben/locales';
// 图标(lucide)
import { LucideFilePenLine, LucideTrash2 } from '@vben/icons';
参考文件
- API 两层架构详细模板和模式: api-patterns.md
- 视图/页面/表单/表格详细模板: view-patterns.md