From 29193e47e2181bacc76bcd4516d1c049ea1a717e Mon Sep 17 00:00:00 2001
From: Hooray <304327508@qq.com>
Date: Sat, 27 Jun 2026 17:31:07 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20FaForm=20=E8=A1=A8?=
=?UTF-8?q?=E5=8D=95=E7=BB=84=E4=BB=B6=E5=8F=8A=E7=A4=BA=E4=BE=8B=EF=BC=8C?=
=?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E7=99=BB=E5=BD=95=E6=B3=A8=E5=86=8C?=
=?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../skills/fa-crud-page-generator/SKILL.md | 147 +++--
.../references/templates.md | 618 +++++++++++++-----
.agents/skills/fa-form-builder/SKILL.md | 17 +-
.../fa-form-builder/references/templates.md | 228 +++----
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../AppAccountForm/edit-password.vue | 111 ++--
.../src/components/AppAccountForm/login.vue | 118 ++--
.../components/AppAccountForm/register.vue | 117 ++--
.../AppAccountForm/reset-password.vue | 110 ++--
.../src/router/modules/component.example.ts | 8 +
apps/example/src/types/components.d.ts | 4 +
.../src/views/component_example/form.vue | 17 +
packages/components/package.json | 1 +
packages/components/resolver.ts | 2 +
.../src/basic/checkbox/CheckboxGroup.vue | 3 +-
.../components/src/basic/form/FormItem.vue | 312 +++++++++
packages/components/src/basic/form/README.md | 68 ++
.../src/basic/form/_examples/_async.vue | 74 +++
.../src/basic/form/_examples/_basic.vue | 68 ++
.../src/basic/form/_examples/_composite.vue | 90 +++
.../src/basic/form/_examples/_disabled.vue | 64 ++
.../src/basic/form/_examples/_dynamic.vue | 113 ++++
.../src/basic/form/_examples/_layout.vue | 60 ++
.../src/basic/form/_examples/_methods.vue | 84 +++
.../src/basic/form/_examples/_slot.vue | 66 ++
.../src/basic/form/_examples/index.ts | 59 ++
packages/components/src/basic/form/context.ts | 13 +
.../src/basic/form/form/FormControl.vue | 17 +
.../src/basic/form/form/FormDescription.vue | 21 +
.../src/basic/form/form/FormItem.vue | 23 +
.../src/basic/form/form/FormLabel.vue | 25 +
.../src/basic/form/form/FormMessage.vue | 33 +
.../components/src/basic/form/form/index.ts | 7 +
.../src/basic/form/form/injectionKeys.ts | 3 +
.../src/basic/form/form/useFormField.ts | 31 +
packages/components/src/basic/form/index.ts | 5 +
packages/components/src/basic/form/index.vue | 319 +++++++++
.../src/basic/radio-group/index.vue | 3 +-
packages/components/src/examples.ts | 1 +
packages/components/src/index.ts | 2 +
pnpm-lock.yaml | 3 +
skills/fa-crud-page-generator/SKILL.md | 13 +-
.../references/templates.md | 151 +++--
skills/fa-form-builder/SKILL.md | 17 +-
.../fa-form-builder/references/templates.md | 228 +++----
72 files changed, 4021 insertions(+), 2645 deletions(-)
create mode 100644 apps/example/src/views/component_example/form.vue
create mode 100644 packages/components/src/basic/form/FormItem.vue
create mode 100644 packages/components/src/basic/form/README.md
create mode 100644 packages/components/src/basic/form/_examples/_async.vue
create mode 100644 packages/components/src/basic/form/_examples/_basic.vue
create mode 100644 packages/components/src/basic/form/_examples/_composite.vue
create mode 100644 packages/components/src/basic/form/_examples/_disabled.vue
create mode 100644 packages/components/src/basic/form/_examples/_dynamic.vue
create mode 100644 packages/components/src/basic/form/_examples/_layout.vue
create mode 100644 packages/components/src/basic/form/_examples/_methods.vue
create mode 100644 packages/components/src/basic/form/_examples/_slot.vue
create mode 100644 packages/components/src/basic/form/_examples/index.ts
create mode 100644 packages/components/src/basic/form/context.ts
create mode 100644 packages/components/src/basic/form/form/FormControl.vue
create mode 100644 packages/components/src/basic/form/form/FormDescription.vue
create mode 100644 packages/components/src/basic/form/form/FormItem.vue
create mode 100644 packages/components/src/basic/form/form/FormLabel.vue
create mode 100644 packages/components/src/basic/form/form/FormMessage.vue
create mode 100644 packages/components/src/basic/form/form/index.ts
create mode 100644 packages/components/src/basic/form/form/injectionKeys.ts
create mode 100644 packages/components/src/basic/form/form/useFormField.ts
create mode 100644 packages/components/src/basic/form/index.ts
create mode 100644 packages/components/src/basic/form/index.vue
diff --git a/.agents/skills/fa-crud-page-generator/SKILL.md b/.agents/skills/fa-crud-page-generator/SKILL.md
index 201b6645..75563660 100644
--- a/.agents/skills/fa-crud-page-generator/SKILL.md
+++ b/.agents/skills/fa-crud-page-generator/SKILL.md
@@ -1,72 +1,57 @@
---
name: fa-crud-page-generator
-description: "为 Fantastic-admin 框架生成完整的 CRUD 业务模块,包含列表页、详情/编辑页、表单组件、API 模块和 Mock 数据。当用户说:'帮我做一个商品管理页面'、'搭一个用户列表,能搜索、能分页、能新增编辑删除'、'生成一个订单模块'、'我需要一个带增删改查的页面'、'快速搭一个管理页面骨架',即使用户只是说'新建一个 XX 管理模块'也应触发此技能。"
+description: "为 Fantastic-admin 框架生成完整的 CRUD 业务模块,遵循框架标准模块结构和内建 Fa* 组件体系,使用 FaForm/FaFormItem 生成新增/编辑表单,产出列表页、详情/编辑页、DetailForm 表单组件、API 模块、fake mock 数据、可选路由。用户说:'帮我做一个商品管理页面'、'搭一个用户列表,能搜索分页新增编辑删除'、'生成订单模块'、'新建 XX 管理模块'、'快速搭一个标准模块/CRUD 页面' 时,即使没有明确说 CRUD,也应触发此技能。"
---
# CRUD 页面生成器
-在 Fantastic-admin 框架中生成完整的 CRUD 业务模块,一次性生成所有相关文件,让开发者专注于业务逻辑而非重复代码。
+在 Fantastic-admin 框架中生成标准 CRUD 业务模块。生成时遵循框架当前标准模块结构:
+列表页、独立详情页、`DetailForm` 表单组件、API 模块、fake mock、可选路由和 route i18n
+要一次性配套生成。
-**生成的文件清单:**
-- `apps//src/views/{path}/{name}/list.vue` — 列表页(搜索栏 + 表格 + 分页)
-- `apps//src/views/{path}/{name}/detail.vue` — 详情/编辑页(仅 router 模式需要)
-- `apps//src/views/{path}/{name}/components/DetailForm/index.vue` — 表单组件
+优先复刻框架现有写法,而不是重新发明页面结构:列表页用 `FaSearchBar` + `FaTable` +
+`FaPagination`,详情表单抽成 `components/DetailForm/index.vue`,独立详情页用
+`FaFixedBar` 固定顶部标题和底部提交区。
+
+**生成文件清单:**
+- `apps//src/views/{path}/{module}/list.vue` — 列表页
+- `apps//src/views/{path}/{module}/detail.vue` — 独立详情页,仅 `router` 模式需要
+- `apps//src/views/{path}/{module}/components/DetailForm/index.vue` — 新增/编辑表单
- `apps//src/api/modules/{fileName}.ts` — API 模块
-- `apps//src/api/modules/{fileName}.fake.ts` — Mock 数据(可选)
+- `apps//src/api/fake_modules/{fileName}.fake.ts` — fake mock 数据,可选
+- `apps//src/router/modules/{routeFileName}.ts` — 前端路由,可选
+- `apps//src/locales/lang/route/{locale}.json` — 路由标题 i18n,可选
---
-## 第一步:确认工作区(必须阻塞等待用户回复)
+## 先确认应用
-本项目是 monorepo 架构,`apps/` 目录下存放各应用。**在执行任何文件读写操作之前**,必须先确认目标应用:
+本项目是 pnpm monorepo。开始写文件前先确认目标应用:
-1. 执行 `ls apps/` 列出所有可用应用
-2. **立即向用户提问**,明确询问要在哪个应用中生成 CRUD 模块,并**停止等待回复**
-3. 收到用户明确回复后,才能继续后续步骤
+1. 如果用户已经明确写了 `apps/`、`admin 应用`、`pro 应用` 等目标应用,直接使用该应用。
+2. 如果没有明确目标应用,先执行 `ls apps/` 查看可用应用,然后询问用户要生成到哪个应用,并等待回复。
+3. 后续所有路径都以 `apps//src/` 为根。
-> **严格规则**:如果用户没有在请求中明确说明目标应用(例如"在 example 应用中"、"apps/core"),则必须提问,不得自行猜测或默认选择任何应用。
-
-确认后,后续所有文件路径均以该应用目录为根。
+不要默认猜测应用;生成到错误应用的返工成本很高。
---
-## 工作流程
+## 信息收集
-### Step 1:收集基本信息
+尽量一次性收集会影响文件结构的信息:
-向用户询问以下内容(可合并为一次提问):
+- **模块名**:英文,用于目录、API 文件和路由,如 `product`、`order`、`systemUser`
+- **模块中文名**:用于标题和提示,如 `商品`、`订单`、`用户`
+- **存放路径**:位于 `src/views/` 下的子路径,如 `mall`、`system/user`,可为空
+- **字段清单**:字段名、中文标签、类型、是否列表显示、是否搜索、是否表单显示、是否必填
+- **详情展示模式**:`router`、`modal`、`drawer`,默认 `router`
+- **是否启用批量删除**:默认启用
+- **是否支持列表高度自适应**:默认生成 `tableAutoHeight` 代码
+- **是否生成 fake mock**:开发阶段默认推荐生成
+- **是否生成路由和 route i18n**:仅当前端路由由本地配置维护时生成
-1. **模块名**(英文,用于文件名和路由,如 `user`、`product`、`order`)
-2. **模块中文名**(用于页面标题和提示文字,如 `用户`、`商品`、`订单`)
-3. **存放路径**(在 `apps//src/views/` 下的子目录,如 `system`、`mall/product`,留空则直接放在 `src/views/` 下)
-
-### Step 2:收集字段信息
-
-询问用户该模块有哪些字段,每个字段需要:
-- **字段名**(英文,如 `name`、`status`、`createdAt`)
-- **中文标签**(如 `姓名`、`状态`、`创建时间`)
-- **字段类型**(`string` | `number` | `boolean` | `date`)
-- **是否在列表中显示**
-- **是否在搜索栏中显示**
-- **是否在表单中显示**(新增/编辑)
-- **是否必填**(表单验证)
-
-如果用户没有提供字段信息,使用默认字段 `title`(标题,string,必填)作为示例占位,并在生成后提示用户替换。
-
-### Step 3:收集功能选项
-
-以下三个选项会直接影响生成的文件结构,需要在同一轮提问中一起确认,避免后续重复生成:
-
-4. **详情展示模式**:
- - `router` — 跳转到独立的详情页(默认,适合复杂表单)
- - `modal` — 弹窗(适合简单表单)
- - `drawer` — 抽屉(适合中等复杂度表单)
-5. **是否生成 Mock 数据**(是/否,开发阶段推荐开启)
-6. **是否同时生成路由配置**(是/否,仅 `app.routeBaseOn` 为 `frontend` 时有效;选是时需额外询问归属的主导航分组名称)
-
-### Step 4:确认并生成
-
-汇总信息,展示将要生成的文件列表,确认后写入文件。
+如果用户只给出模块名和很少字段,使用一个最小可运行字段:
+`title`(标题,`string`,列表/搜索/表单显示,必填),并在完成后提醒用户继续替换业务字段。
---
@@ -74,38 +59,60 @@ description: "为 Fantastic-admin 框架生成完整的 CRUD 业务模块,包
给定模块名 `name` 和路径 `path`(相对于 `apps//src/views/`):
-| 用途 | 规则 | 示例(path=system, name=user) |
-|------|------|-------------------------------|
-| 视图目录 | `apps//src/views/{path}/{snakeCase(name)}/` | `apps//src/views/system/user/` |
-| API 文件名 | path 非空时:`{camelCase(path)}_{camelCase(name)}`;path 为空时:`{camelCase(name)}` | `systemUser` |
-| 组件名 | `PascalCase({path}-{name}-list/detail)` | `SystemUserList` |
-| API URL 前缀 | `/{path}/{snakeCase(name)}/` | `/system/user/` |
+| 用途 | 规则 | 示例(path 为空,name=product) |
+|------|------|----------------------------------------|
+| 视图目录 | `src/views/{path}/{snake_case(name)}/` | `src/views/product/` |
+| API 文件名 | path 参与业务命名时使用 camelCase;普通模块用 `camelCase(name)` | `product` |
+| fake 文件名 | 与 API 文件名一致,放到 `src/api/fake_modules/` | `product.fake.ts` |
+| 列表组件名 | `PascalCase({path}-{name}-list)` | `ProductList` |
+| 详情组件名 | `PascalCase({path}-{name}-detail)` | `ProductDetail` |
+| 路由 name | `camelCase(name)List` / `camelCase(name)Detail` | `productList` |
+| API URL 前缀 | `snake_case(path/name)`,无前导斜杠 | `product` |
-**snakeCase 规则**:多个单词用下划线连接,如 `productCategory` → `product_category`
+`snake_case` 用下划线连接多个单词,如 `productCategory` -> `product_category`。
---
-## 代码模板
+## 代码生成规则
-详细的代码模板见 [references/templates.md](references/templates.md),其中也包含 faker.js 字段类型映射表。
+详细模板见 [references/templates.md](references/templates.md)。生成时按这些规则填充模板:
-生成代码时,根据用户提供的字段信息替换模板中的占位符:
-- 列表页的 `ElTableColumn` 根据"列表显示"字段生成
-- 搜索栏的 `ElFormItem` 根据"搜索显示"字段生成
-- 表单组件的 `ElFormItem` 根据"表单显示"字段生成
-- API 模块的 TypeScript 类型根据字段定义生成
-- Mock 数据根据字段类型使用合适的 faker.js 方法生成(见 templates.md 中的映射表)
+- `list.vue` 使用 `
-
- {fields.form}
-
+
+ {formFields}
+
```
---
-## api.ts 模板
+## api/modules/{fileName}.ts 模板
```typescript
import api from '../index'
@@ -420,7 +537,9 @@ export default {
}),
detail: (id: number | string) => api.get('{apiPrefix}/detail', {
- params: { id },
+ params: {
+ id,
+ },
fake: true,
}),
@@ -432,27 +551,30 @@ export default {
fake: true,
}),
- delete: (id: number | string) => api.post('{apiPrefix}/delete', { id }, {
+ delete: (id: number | string) => api.post('{apiPrefix}/delete', {
+ id,
+ }, {
fake: true,
}),
}
```
-> 不需要 Mock 数据时,去掉所有 `fake: true` 选项即可。
+不生成 fake mock 时,去掉所有 `fake: true` 配置,并确保接口地址与真实后端约定一致。
---
-## fake.ts 模板
+## api/fake_modules/{fileName}.fake.ts 模板
```typescript
-import { faker } from '@faker-js/faker/locale/zh_CN'
+import { faker } from '@faker-js/faker'
import { defineFakeRoute } from 'vite-plugin-fake-server/client'
+// {cname}
const {moduleName}List: any[] = []
for (let i = 0; i < 50; i++) {
{moduleName}List.push({
id: i + 1,
- {fields.mock}
+ {mockFields}
})
}
@@ -462,8 +584,9 @@ export default defineFakeRoute([
method: 'get',
response: ({ query }) => {
const { {searchQueryFields}, from, limit } = query
- let list = {moduleName}List
- {searchFilters}
+ const list = {moduleName}List.filter((item) => {
+ return {searchFilterExpression}
+ })
const pageList = list.filter((_item, index) => {
return index >= ~~from && index < (~~from + ~~limit)
})
@@ -493,122 +616,257 @@ export default defineFakeRoute([
url: '/fake/{apiPrefix}/create',
method: 'post',
response: () => {
- return { error: '', status: 1, data: { isSuccess: true } }
+ return {
+ error: '',
+ status: 1,
+ data: {
+ isSuccess: true,
+ },
+ }
},
},
{
url: '/fake/{apiPrefix}/edit',
method: 'post',
response: () => {
- return { error: '', status: 1, data: { isSuccess: true } }
+ return {
+ error: '',
+ status: 1,
+ data: {
+ isSuccess: true,
+ },
+ }
},
},
{
url: '/fake/{apiPrefix}/delete',
method: 'post',
response: () => {
- return { error: '', status: 1, data: { isSuccess: true } }
+ return {
+ error: '',
+ status: 1,
+ data: {
+ isSuccess: true,
+ },
+ }
},
},
])
```
+如果有多个搜索字段,可以用 `&&` 组合过滤条件:
+
+```typescript
+return (title ? item.title.includes(title) : true)
+ && (status !== undefined && status !== '' ? item.status === ~~status : true)
+```
+
+---
+
+## router/modules/{routeFileName}.ts 模板
+
+```typescript
+import type { RouteRecordRaw } from 'vue-router'
+import { $t } from '@/locales'
+
+function Layout() {
+ return import('@/layouts/index.vue')
+}
+
+const routes: RouteRecordRaw = {
+ path: '{routePath}',
+ component: Layout,
+ name: '{routeRootName}',
+ meta: {
+ title: '{routeTitle}',
+ icon: '{icon}',
+ },
+ children: [
+ {
+ path: '',
+ name: '{routeListName}',
+ component: () => import('@/views/{viewPath}/list.vue'),
+ meta: {
+ title: '{cname}列表',
+ menu: false,
+ breadcrumb: false,
+ keepAlive: '{routeDetailName}',
+ },
+ },
+ {
+ path: 'detail/:id?',
+ name: '{routeDetailName}',
+ component: () => import('@/views/{viewPath}/detail.vue'),
+ meta: {
+ title: '...',
+ menu: false,
+ activeMenu: '{routePath}',
+ keepAlive: true,
+ noKeepAlive: '{routeListName}',
+ },
+ },
+ ],
+}
+
+export default routes
+```
+
+`modal` / `drawer` 模式不需要详情路由,列表路由也不需要 `keepAlive: '{routeDetailName}'`。
+
---
## 字段生成规则
+### TypeScript item 字段
+
+```typescript
+title: string
+status: number
+createdAt: string
+```
+
+### 搜索默认值和查询参数
+
+```typescript
+const searchDefault = {
+ title: '',
+ status: '',
+}
+
+const params = {
+ ...getParams(),
+ ...(search.value.title && { title: search.value.title }),
+ ...(search.value.status !== '' && { status: search.value.status }),
+}
+```
+
### 搜索栏字段
-每个搜索字段用 `FaLabel` 包裹,内部控件根据字段类型自行选择合适的 Fa* 或 El* 组件。次要搜索条件加 `v-show="!fold"` 实现折叠。
+首个主要搜索字段直接显示,次要搜索条件使用 `v-show="!fold"`:
```vue
-
+
```
-### 列表列
+### FaTable 列
-普通列直接用 `prop` 渲染,需要自定义展示时使用 `#default` slot:
+普通字段:
+
+```typescript
+{
+ accessorKey: '{field}',
+ header: '{label}',
+},
+```
+
+需要自定义展示的字段使用 `id`,并在模板里添加 `#cell-{id}` 插槽:
+
+```typescript
+{
+ id: 'status',
+ header: '状态',
+ width: 100,
+ align: 'center',
+},
+```
```vue
-
-
-
-
-
-
-
-
-
+
+
+ {{ row.original.status ? '启用' : '停用' }}
+
+
```
### 表单字段
-每个表单字段用 `ElFormItem` 包裹,内部控件根据字段类型自行选择合适的 Fa* 或 El* 组件,注意加 `class="w-full"`。
-
```vue
-
-
-
+
+
+
```
-### 表单验证规则
+`FaFormItem` 会自动向第一个子控件注入 `modelValue` 和 `onUpdate:modelValue`,不要再手写
+`v-model="model.{field}"` 或 `v-model="model.value.{field}"`。
+
+### 表单 model 类型字段
```typescript
-// 必填文本
-{field}: [
- { required: true, message: '请输入{label}', trigger: 'blur' },
-],
-// 必填选择
-{field}: [
- { required: true, message: '请选择{label}', trigger: 'change' },
-],
+{field}: string
+status: number
+enabled: boolean
```
+### 表单初始值
+
+```typescript
+{field}: '',
+status: 0,
+enabled: true,
+```
+
+### 表单详情赋值
+
+```typescript
+model.value.{field} = res.data.{field}
+```
+
+### 表单验证 schema
+
+文本输入:
+
+```typescript
+{field}(value: string) {
+ return value?.trim() ? true : '请输入{label}'
+},
+```
+
+选择、开关、日期:
+
+```typescript
+{field}(value: string | number | boolean | undefined) {
+ return value !== undefined && value !== '' ? true : '请选择{label}'
+},
+```
+
+非必填字段不要生成 validation schema 项;生成必填字段时,在对应 `FaFormItem` 上同时加
+`required`。
+
### Mock 数据字段映射
-| 字段类型 | faker 方法 |
-|---------|-----------|
-| 姓名 | `faker.person.fullName()` |
-| 账号/用户名 | `faker.person.firstName()` |
-| 手机号 | `faker.phone.number({ style: 'international' })` |
+| 字段类型/含义 | faker 方法 |
+|---------------|------------|
+| 标题 | `faker.book.title()` 或 `faker.lorem.words(3)` |
+| 名称 | `faker.person.fullName()` 或 `faker.commerce.productName()` |
+| 用户名 | `faker.internet.username()` |
+| 手机号 | `faker.phone.number()` |
| 邮箱 | `faker.internet.email()` |
-| 标题/名称 | `faker.lorem.words(3)` |
| 描述 | `faker.lorem.sentence()` |
-| 数字(枚举) | `faker.number.int(2)` |
-| 布尔(状态) | `faker.datatype.boolean()` |
+| 数量 | `faker.number.int({ min: 1, max: 100 })` |
| 金额 | `faker.number.float({ min: 10, max: 9999, fractionDigits: 2 })` |
+| 布尔状态 | `faker.datatype.boolean()` |
+| 枚举状态 | `faker.helpers.arrayElement([0, 1, 2])` |
| 日期 | `faker.date.recent().toISOString()` |
-### Mock 搜索过滤模式
+### Mock 搜索过滤
+
+字符串模糊匹配:
-**string 模糊匹配:**
```typescript
-list = list.filter((item) => {
- return {field} ? item.{field}.includes({field}) : true
-})
+title ? item.title.includes(title) : true
```
-**枚举/数字精确匹配(注意:query 参数均为字符串,`0` 是 falsy,不能直接用 `{field} ?` 判断):**
+数字/枚举精确匹配。注意 query 参数是字符串,`0` 不能用普通 truthy 判断:
+
```typescript
-list = list.filter((item) => {
- return {field} !== undefined && {field} !== '' ? item.{field} === ~~{field} : true
-})
+status !== undefined && status !== '' ? item.status === ~~status : true
```
-
----
-
-## faker.js 字段类型映射
-
-| 字段类型 | 推荐 faker 方法 |
-|---------|----------------|
-| string(名称类) | `faker.person.fullName()` 或 `faker.commerce.productName()` |
-| string(标题类) | `faker.lorem.words(3)` |
-| string(描述类) | `faker.lorem.sentence()` |
-| number(ID) | 自增 `i + 1` |
-| number(金额) | `faker.number.float({ min: 10, max: 9999, fractionDigits: 2 })` |
-| number(数量) | `faker.number.int({ min: 1, max: 100 })` |
-| boolean | `faker.datatype.boolean()` |
-| date | `faker.date.recent().toISOString()` |
-| status(枚举) | `faker.helpers.arrayElement([0, 1, 2])` |
diff --git a/.agents/skills/fa-form-builder/SKILL.md b/.agents/skills/fa-form-builder/SKILL.md
index fbd97ca4..b48222fe 100755
--- a/.agents/skills/fa-form-builder/SKILL.md
+++ b/.agents/skills/fa-form-builder/SKILL.md
@@ -1,11 +1,11 @@
---
name: fa-form-builder
-description: "为 Fantastic-admin 框架生成独立的表单页面,使用 vee-validate + zod 验证,全部使用框架内建 Fa* 组件。当用户说:'帮我做一个用户信息填写页'、'我只需要一个提交表单,不需要列表'、'做个设置页面,有几个输入框和保存按钮'、'生成一个注册/编辑/配置表单页'、'只要表单页,不需要增删改查',即使用户只是说'做个表单页面'也应触发此技能。"
+description: "为 Fantastic-admin 框架生成独立的表单页面,使用 FaForm/FaFormItem + vee-validate + zod 验证,表单 model 使用 ref,全部使用框架内建 Fa* 组件。当用户说:'帮我做一个用户信息填写页'、'我只需要一个提交表单,不需要列表'、'做个设置页面,有几个输入框和保存按钮'、'生成一个注册/编辑/配置表单页'、'只要表单页,不需要增删改查',即使用户只是说'做个表单页面'也应触发此技能。"
---
# 表单页面生成器
-在 Fantastic-admin 框架中生成独立的 Router 表单页面,使用 vee-validate + zod 完成表单验证,全部使用框架内建 Fa* 组件,不引入任何 Element Plus 组件。
+在 Fantastic-admin 框架中生成独立的 Router 表单页面,使用 `FaForm/FaFormItem` 承接 vee-validate + zod 表单验证。
**生成的文件:**
- `apps//src/views/{path}/{name}/index.vue` — 表单页面(含验证、提交骨架、固定操作栏)
@@ -50,14 +50,14 @@ description: "为 Fantastic-admin 框架生成独立的表单页面,使用 vee
字段较多时单列会导致页面过长,双列更紧凑。根据字段数量和复杂度判断是否询问用户布局偏好:
-- **单列**:`max-w-600px`,`space-y-6`(字段少、字段较长时适合)
+- **单列**:`max-w-600px`,`grid gap-6`(字段少、字段较长时适合)
- **双列**:`max-w-1200px`,`grid grid-cols-1 gap-x-8 gap-y-6 items-start md:grid-cols-2`(字段多、字段较短时适合)
### Step 4:确认并生成
汇总信息,展示将要生成的文件,确认后写入。
-生成完成后提示:如需配置路由,请使用 `fa-route-generator` 技能;如需 API 模块,请手动创建。
+生成完成后提示:如需配置路由,请使用 `fa-route-generator` 技能;如需 API 模块,请使用 `fa-crud-page-generator` 或手动创建。
---
@@ -80,9 +80,10 @@ description: "为 Fantastic-admin 框架生成独立的表单页面,使用 vee
- `{cname}` → 模块中文名
- `{componentName}` → PascalCase 组件名
- `{zodSchema}` → zod 字段定义(每个必填字段对应一行 zod 规则)
-- `{initialValues}` → 字段初始值(string 默认 `''`,boolean 默认 `false`,number 默认 `0`,array 默认 `[]`)
-- `{formItems}` → 各字段对应的 FormField 代码片段
+- `{initialValues}` → `model` 字段初始值(string 默认 `''`,boolean 默认 `false`,number 默认 `0`,array 默认 `[]`)
+- `{formItems}` → 各字段对应的 FaFormItem 代码片段
+- `{imports}` → 必要的额外 import;默认留空,Fa* 组件通常由自动导入处理
- `{maxWidth}` → 单列 `max-w-600px` / 双列 `max-w-1200px`
-- `{gridClass}` → 双列时 `grid grid-cols-1 gap-x-8 gap-y-6 items-start md:grid-cols-2` / 单列时 `space-y-6`
+- `{gridClass}` → 双列时 `grid grid-cols-1 gap-x-8 gap-y-6 items-start md:grid-cols-2` / 单列时 `grid gap-6`
-生成的代码是骨架:API 调用处用 `// TODO:` 注释标记,动态数据源(select options、upload action 等)用占位注释标记,用户根据实际接口替换。操作栏按钮使用 `FaButton`:取消用 `variant="outline"`,提交用默认 variant 并传 `:loading="isSubmitting"`。
+生成的代码是骨架:API 调用处用 `// TODO:` 注释标记,动态数据源(select options、upload action 等)用占位注释标记,用户根据实际接口替换。表单对象必须使用 `const model = ref({...})`,模板中直接 `:model="model"`;脚本里读写字段使用 `model.value.xxx`。操作栏按钮使用 `FaButton`:取消用 `variant="outline"`,提交用默认 variant 并传 `:loading="loading"`。
diff --git a/.agents/skills/fa-form-builder/references/templates.md b/.agents/skills/fa-form-builder/references/templates.md
index 2bc3d242..7d1ef15c 100755
--- a/.agents/skills/fa-form-builder/references/templates.md
+++ b/.agents/skills/fa-form-builder/references/templates.md
@@ -1,16 +1,16 @@
# 表单页面代码模板
-使用 vee-validate + zod 验证,全部使用 Fa* 内建组件,不引入任何 Element Plus 组件。
+使用 FaForm/FaFormItem + vee-validate + zod 验证。
占位符说明:
- `{cname}` — 模块中文名
- `{componentName}` — 组件名(PascalCase)
- `{zodSchema}` — zod 字段定义
-- `{initialValues}` — 表单初始值
-- `{formItems}` — FormField 列表
+- `{initialValues}` — `model` 表单初始值
+- `{formItems}` — FaFormItem 列表
- `{imports}` — 需要手动 import 的组件
- `{maxWidth}` — 单列 `max-w-600px` / 双列 `max-w-1200px`
-- `{gridClass}` — 双列时 `grid grid-cols-1 gap-x-8 gap-y-6 items-start md:grid-cols-2` / 单列时 `space-y-6`
+- `{gridClass}` — 双列时 `grid grid-cols-1 gap-x-8 gap-y-6 items-start md:grid-cols-2` / 单列时 `grid gap-6`
---
@@ -18,10 +18,10 @@
```vue
@@ -50,52 +54,31 @@ const onSubmit = form.handleSubmit((values) => {
请输入原密码、新密码和确认密码
-
+
diff --git a/apps/core-ant-design-vue/src/components/AppAccountForm/login.vue b/apps/core-ant-design-vue/src/components/AppAccountForm/login.vue
index 191b61d9..4a307afd 100755
--- a/apps/core-ant-design-vue/src/components/AppAccountForm/login.vue
+++ b/apps/core-ant-design-vue/src/components/AppAccountForm/login.vue
@@ -1,8 +1,7 @@
@@ -79,48 +86,33 @@ function testAccount(account: string) {
/>