@qin-ui/antd-vue-pro 2.2.1 → 2.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AI-CONTEXT.md CHANGED
@@ -1,150 +1,194 @@
1
- # @qin-ui/antd-vue-pro — AI Context
1
+ # @qin-ui/antd-vue-pro
2
2
 
3
- > Schema-driven component library wrapping **ant-design-vue v4** (`a-` prefix). Describe UIs as JS objects instead of template tag soup.
3
+ 基于 ant-design-vue v4(`a-` 前缀)封装的 Schema 驱动组件库。
4
+ 用 JS 配置对象描述 UI,不写 `<a-*>` 模板堆叠。ProForm/ProTable 是渲染引擎,所有真实 UI 来自 ant-design-vue。
4
5
 
5
- ---
6
-
7
- ## 1. Architecture
8
-
9
- ### 1.1 Three-layer rendering
6
+ ## 安装与注册
10
7
 
8
+ ```bash
9
+ pnpm add @qin-ui/antd-vue-pro # peerDeps: ant-design-vue ^4, vue ^3.5
11
10
  ```
12
- Field[] config layer { path:'name', component:'input', span:8, label:'Name', placeholder:'...' }
13
- │
14
- @qin-ui/antd-vue-pro Schema parser — strips props layer by layer
15
- │
16
- ant-design-vue v4 a-input / a-select / a-table / a-form-item / a-col ...
17
- ```
18
-
19
- ProForm/ProTable are **rendering engines**, not a component library. All actual UI comes from ant-design-vue. Field props are peeled off and dispatched to different DOM layers.
20
11
 
21
- ### 1.2 Component resolution
22
-
23
- `Field.component` string → `componentMap` → ant-design-vue component.
24
-
25
- If not found in the built-in map, the string is resolved against `ProComponentProvider` injected components; a Vue component object is rendered directly (used by `'custom'` internally).
26
-
27
- ### 1.3 Built-in component map
12
+ ```ts
13
+ import AntdVuePro from '@qin-ui/antd-vue-pro';
14
+ import '@qin-ui/antd-vue-pro/antd-vue-pro.css';
15
+ app.use(AntdVuePro); // 全局注册 ProForm / ProTable / ProComponentProvider
16
+ ```
28
17
 
29
- | Field `component` | ant-design-vue component | Key pass-through props (verify against https://antdv.com) |
30
- | :----------------- | :----------------------- | :------------------------------------------------------------------------------------- |
31
- | `'input'` | Input | placeholder, maxlength, allowClear, addonBefore, addonAfter, prefix, suffix, showCount |
32
- | `'textarea'` | TextArea | rows, maxlength, showCount, autoSize |
33
- | `'input-password'` | InputPassword | placeholder, maxlength, visibilityToggle |
34
- | `'input-search'` | InputSearch | placeholder, loading, onSearch |
35
- | `'input-number'` | InputNumber | min, max, step, precision, formatter, parser |
36
- | `'select'` | Select | **options**, mode, showSearch, filterOption, allowClear, loading |
37
- | `'cascader'` | Cascader | **options**, fieldNames, showSearch, allowClear |
38
- | `'date-picker'` | DatePicker | picker, format, showTime, disabledDate, valueFormat |
39
- | `'range-picker'` | RangePicker | format, showTime, disabledDate, allowClear |
40
- | `'time-picker'` | TimePicker | format, showSecond, hourStep, minuteStep |
41
- | `'checkbox-group'` | CheckboxGroup | **options**, direction |
42
- | `'radio-group'` | RadioGroup | **options**, direction, buttonStyle |
43
- | `'switch'` | Switch | checkedChildren, unCheckedChildren, loading |
44
- | `'slider'` | Slider | min, max, step, marks, range, tooltip |
45
- | `'tree-select'` | TreeSelect | **treeData**, showSearch, treeCheckable, fieldNames |
46
- | `'transfer'` | Transfer | **dataSource**, **titles**, **targetKeys**, showSearch, render |
47
- | `'custom'` | (user-supplied) | component receives a Vue component or `h()` render function |
18
+ ## 两条核心心智模型
48
19
 
49
- > The prop column is illustrative. **Always verify prop names/types against [antdv.com](https://antdv.com).**
20
+ **1. 配置驱动**:`useForm(数据, Field[])` / `useTable({ columns, searchFields })` 生成实例,组件接收实例。
50
21
 
51
- ---
22
+ **2. 属性分层透传**(最关键):Field 上的属性被"剥离到不同 DOM 层",不是全塞给输入控件。写错层就失效:
52
23
 
53
- ## 2. Property layering (core rule)
24
+ | 属性 | 落到哪一层 | 说明 |
25
+ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------- | :------------- |
26
+ | `span`/`offset`/`push`/`pull`/`flex`/`xs..xxl` | `<a-col>` Grid 层 | 仅 grid 开启时 |
27
+ | `label`/`rules`/`tooltip`/`colon`/`labelAlign`/`labelCol`/`wrapperCol`/`extra`/`help`/`validateFirst`/`validateTrigger`/`valuePropName`/`normalize`/`formItemClass`/`formItemStyle`/`formItemDataAttrs` | `<a-form-item>` 层 | 表单项 |
28
+ | `disabled`/`placeholder`/`allowClear`/`options`/`mode`/`maxlength`/`componentClass`/`componentStyle`/`componentDataAttrs` + 该控件其余 ant 原生属性 | 输入控件本身(a-input 等) | 其余全部 |
29
+ | `component`/`hidden`/`modelProp`/`valueFormatter`/`fields`/`slots`/`formItemContainer`/`componentContainer`/`extraProps` | ProForm 逻辑消费,**不绑到 DOM** | 框架级 |
54
30
 
55
- Field props are **stripped in layers** and dispatched to different DOM targets.
31
+ > 规则:`span` 给 Grid,`label`/`rules` 给 FormItem,其余给输入控件。
56
32
 
57
- ```
58
- <a-col :span="8"> ← Grid layer
59
- <a-form-item label="City"> ← FormItem layer
60
- <a-select placeholder="..." /> ← Input-control layer
61
- </a-form-item>
62
- </a-col>
63
- ```
33
+ ## component 映射(Field.component 字符串 -> ant-design-vue 组件)
64
34
 
65
- ### 2.1 Layer dispatch
35
+ `input`->Input · `textarea`->TextArea · `input-password`->InputPassword · `input-search`->InputSearch ·
36
+ `input-number`->InputNumber · `select`->Select · `cascader`->Cascader · `date-picker`->DatePicker ·
37
+ `range-picker`->RangePicker · `time-picker`->TimePicker · `checkbox-group`->CheckboxGroup ·
38
+ `radio-group`->RadioGroup · `switch`->Switch · `slider`->Slider · `tree-select`->TreeSelect ·
39
+ `transfer`->Transfer · `custom`->用户自定义组件
66
40
 
67
- **Grid layer → `<a-col>`** (only when grid is enabled):
68
- `span`, `order`, `offset`, `push`, `pull`, `flex`, `xs`, `sm`, `md`, `lg`, `xl`, `xxl`
41
+ > 写 Field 中输入控件的具体属性前,**属性名/类型以 https://antdv.com 官方文档为准**。
69
42
 
70
- **FormItem layer → `<a-form-item>`**:
71
- `label`, `rules`, `colon`, `labelAlign`, `labelCol`, `wrapperCol`, `tooltip`, `extra`, `help`, `validateFirst`, `validateTrigger`, `valuePropName`, `normalize`, `formItemClass`→`class`, `formItemStyle`→`style`, `formItemContainer`, `formItemDataAttrs`→`data-*`
43
+ ## 隐式默认行为(INJECT_CONFIG,易踩坑)
72
44
 
73
- **Input-control layer → the input component** (everything else):
74
- `disabled`, `placeholder`, `allowClear`, `options`, `showSearch`, `maxlength`, `mode`, `componentClass`→`class`, `componentStyle`→`style`, `componentDataAttrs`→`data-*`, and any ant-design-vue props for that component.
45
+ 由 `ProComponentProvider` 注入,优先级低于 Field 配置。**不写也有这些默认值,需知晓以免行为与预期不符:**
75
46
 
76
- **Consumed by ProForm logic, not bound to DOM**: `component`, `fields`, `hidden`, `slots`, `modelProp`, `valueFormatter`, `componentContainer`, `extraProps`.
47
+ | component | 默认预设 |
48
+ | :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
49
+ | `switch` | `{ modelProp: 'checked' }` -> v-model 绑 `checked` 而非 `value` |
50
+ | `input` / `input-password` | `{ maxlength: 100, allowClear: true, placeholder: '请输入' }` |
51
+ | `textarea` | `{ maxlength: 200, autoSize: {minRows:3,maxRows:6}, showCount: true, allowClear: true, placeholder: '请输入' }` |
52
+ | `input-number` | `{ max: 1e15-1, min: -(1e15+1), controls: false, placeholder: '请输入', style:{width:'100%'} }` |
53
+ | `select` / `cascader` | `{ allowClear: true, placeholder: '请选择', getPopupContainer }` |
54
+ | `date-picker` / `range-picker` / `time-picker` | `{ allowClear: true, getPopupContainer, style:{width:'100%'} }` |
55
+ | `pro-form` | `{ grid: { gutter: {xs:8, sm:16, md:16, lg:24} } }` |
56
+ | `pro-form-item` | `{ validateFirst: true, span: 8 }` |
57
+ | `pro-table` | `{ pagination:{showTotal, showSizeChanger, pageSizeOptions:['10'..'100'], showQuickJumper:true}, searchFormConfig:{layout:'grid', expand:{minExpandRows:2}}, control:true, addIndexColumn:true }` |
77
58
 
78
- ### 2.2 modelProp
59
+ > `modelProp` 控制 v-model 绑定名,默认 `'value'`->`v-model:value`。
79
60
 
80
- Controls the `v-model:xxx` binding name.
61
+ ## useForm -- 表单实例
81
62
 
82
- - Default: `modelProp: 'value'` → `v-model:value`
83
- - Switch uses `checked` — **ProComponentProvider presets `modelProp: 'checked'` for `'switch'`** so you rarely set it manually.
84
- - Override per-field when using a custom component that expects a different v-model prop.
63
+ ```ts
64
+ // 重载一(常用):初始数据 + 字段配置
65
+ const form = useForm<User>(
66
+ { name: '张三', age: 25 }, // initFormData
67
+ [
68
+ {
69
+ path: 'name',
70
+ label: '姓名',
71
+ component: 'input',
72
+ rules: [{ required: true }],
73
+ },
74
+ ] // Field[]
75
+ );
76
+ // 重载二:仅拿实例(root=true/false)
77
+ const form = useForm<User>(true);
78
+ ```
85
79
 
86
- ---
80
+ 返回 `Form` 实例(**2.0+ `formData` 是 reactive 对象,不用 `.value`**):
87
81
 
88
- ## 3. Progressive usage
82
+ | 成员 | 说明 |
83
+ | :------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- |
84
+ | `form.formData` | 响应式数据,可直接读写,支持深层路径 `formData.address.city` |
85
+ | `form.getFormData(path)` / `form.setFormData(path, value)` | 路径读写;setFormData 还支持 `setFormData(path, prev=>new)` 和 `setFormData({...})` 批量覆盖 |
86
+ | `form.fields` | 字段配置数组(Ref) |
87
+ | `form.getField(path)` / `form.setField(path, patch)` | 字段增改查;setField 默认合并,`{updateType:'rewrite'}` 覆盖 |
88
+ | `form.deleteField` / `form.appendField` / `form.prependField` / `form.getParentField` | 字段增删 |
89
+ | `form.formRef` | 底层 ant Form 实例引用(Ref),`formRef.value?.validate()` / `.resetFields()` |
89
90
 
90
- ### Level 1: Config-driven (no hand-written a-\* tags)
91
+ ## ProForm 最小示例
91
92
 
92
- ```ts
93
+ ```vue
94
+ <script setup lang="ts">
93
95
  import { ProForm, useForm } from '@qin-ui/antd-vue-pro';
94
-
95
- const form = useForm({ username: '', password: '' }, [
96
+ interface User {
97
+ username: string;
98
+ age: number;
99
+ gender: string;
100
+ }
101
+ const form = useForm<User>({ username: '', age: 18, gender: 'male' }, [
96
102
  {
97
103
  path: 'username',
98
104
  component: 'input',
99
105
  label: '用户名',
100
- placeholder: '请输入',
101
106
  rules: [{ required: true }],
102
107
  },
108
+ { path: 'age', component: 'input-number', label: '年龄', min: 1, max: 150 },
103
109
  {
104
- path: 'password',
105
- component: 'input-password',
106
- label: '密码',
107
- placeholder: '请输入',
108
- rules: [{ required: true }],
110
+ path: 'gender',
111
+ component: 'select',
112
+ label: '性别',
113
+ options: [
114
+ { label: '男', value: 'male' },
115
+ { label: '女', value: 'female' },
116
+ ],
109
117
  },
110
118
  ]);
119
+ const submit = async () => {
120
+ await form.formRef.value?.validate();
121
+ console.log(form.formData);
122
+ };
123
+ </script>
124
+ <template>
125
+ <ProForm :form="form" />
126
+ <a-button type="primary" @click="submit">提交</a-button>
127
+ </template>
111
128
  ```
112
129
 
113
- ```html
114
- <ProForm :form="form" />
115
- ```
116
-
117
- ### Level 2: Field linkage
130
+ ## 自定义组件(4 种方式)
118
131
 
119
- Control props (`disabled`, `hidden`, `rules`, etc.) support three reactivity patterns:
132
+ `Field.component` 的解析优先级(高 -> 低):
133
+ `teleport 插槽注入` > `ProComponentProvider.componentMap` > `内置 componentMap` > `原始 component 值`
120
134
 
121
- ```ts
122
- // A) computed() — declarative, for linkages known at init (preferred)
123
- disabled: computed(() => !form.formData.enabled),
124
- rules: computed(() => [{ required: form.formData.hasLimit, message: '必填' }]),
135
+ ### 方式 1:传入 SFC 单文件组件对象(推荐,单字段最常用)
125
136
 
126
- // B) ref() — external shared state
127
- const isDisabled = ref(false);
137
+ `component` 直接传组件对象,**必须用 `markRaw` 包裹**。
128
138
 
129
- // C) setField() — imperative, for event-driven changes
130
- form.setField('limitCount', { disabled: true });
139
+ ```ts
140
+ import { markRaw } from 'vue';
141
+ import MyInput from './MyInput.vue';
142
+ { path: 'code', label: '验证码', component: markRaw(MyInput) }
131
143
  ```
132
144
 
133
- **Rule: pick the simplest. computed() for init-time, setField() for runtime events, ref() for external state.**
145
+ > ⚠️ **必须 markRaw**:不包会被 Vue 当成响应式对象深度代理,触发性能警告甚至渲染异常。
146
+ > 类型上 `component` 接受 `RenderComponentType | Raw<RenderComponentType>`,`Raw<T>` 即 markRaw 的类型标记。
147
+ > 此字段不支持响应式(不能包 ref/computed)。
134
148
 
135
- ### Level 3: Custom component
149
+ ### 方式 2:传入 render 函数(单字段,需动态拼装 props 时)
150
+
151
+ `component` 传 `(props, ctx) => VNode`。`props` 含 v-model 绑定值与 path,`ctx.attrs` 含透传属性。
136
152
 
137
153
  ```ts
138
154
  import { h } from 'vue';
139
- import CustomInput from './CustomInput.vue';
155
+ import MyInput from './MyInput.vue';
156
+ { path: 'code', label: '验证码', component: (p, ctx) => h(MyInput, { ...p, ...ctx.attrs }) }
157
+ ```
158
+
159
+ ### 方式 3:ProComponentProvider 注入 componentMap(全局复用,推荐多字段场景)
160
+
161
+ 在根用 `componentMap` 注册,Field 里用字符串引用。**可覆盖内置组件**(如 `input: MyInput` 替换全局 input)。
140
162
 
141
- // In field config:
142
- { path: 'key', component: (p, ctx) => h(CustomInput, { ...p, ...ctx.attrs }) }
163
+ ```vue
164
+ <script setup lang="ts">
165
+ import { ProComponentProvider } from '@qin-ui/antd-vue-pro';
166
+ import MyRichTextEditor from './components/MyRichTextEditor.vue';
167
+ const componentMap = { 'rich-editor': MyRichTextEditor };
168
+ </script>
169
+ <template>
170
+ <ProComponentProvider :component-map="componentMap">
171
+ <ProForm :form="form" />
172
+ </ProComponentProvider>
173
+ </template>
143
174
  ```
144
175
 
145
- Or via scoped slot in template (all binding params forwarded via `v-bind="scoped"`):
176
+ **追加全局声明获取强类型补全**(项目任意 `.d.ts`):
146
177
 
147
- ```html
178
+ ```ts
179
+ declare module '@qin-ui/antd-vue-pro' {
180
+ interface ComponentMap {
181
+ 'rich-editor': typeof MyRichTextEditor;
182
+ }
183
+ }
184
+ // 之后 component: 'rich-editor' 即获精准属性类型联想
185
+ ```
186
+
187
+ ### 方式 4:模板 scoped slot(声明式,简单替换)
188
+
189
+ 插槽名 = 字段 `path`,绑定参数通过 `v-bind="scoped"` 转发(teleport 机制,优先级最高)。
190
+
191
+ ```vue
148
192
  <ProForm :form="form">
149
193
  <template #agreement="scoped">
150
194
  <a-checkbox v-bind="scoped">同意协议</a-checkbox>
@@ -152,85 +196,142 @@ Or via scoped slot in template (all binding params forwarded via `v-bind="scoped
152
196
  </ProForm>
153
197
  ```
154
198
 
155
- ---
199
+ ### 选择建议
156
200
 
157
- ## 4. Anti-patterns
201
+ - 单字段复用一个 SFC -> 方式 1(markRaw(SFC))
202
+ - 单字段、需动态拼装 props -> 方式 2(render 函数)
203
+ - 多处复用 / 替换内置组件 -> 方式 3(componentMap + 声明扩充)
204
+ - 简单声明式替换、不想写 h() -> 方式 4(scoped slot)
158
205
 
159
- - **Never hand-write `<a-form-item>` inside `<ProForm>`.** ProForm renders FormItems from Field config automatically.
160
- - **Never guess pass-through prop names.** Verify against [ant-design-vue docs](https://antdv.com).
161
- - **Don't confuse Grid props with input props.** `span` goes to `<a-col>`, not the input.
162
- - **Use `computed()` for reactive Field configs.** Plain values won't track dependencies.
163
- - **Prefer the simplest linkage pattern.** Don't use `setField()` when `computed()` suffices.
206
+ ### 自定义组件需遵守的约定
164
207
 
165
- ---
208
+ - **v-model**:默认绑 `value`(`v-model:value`)。若组件用别的 prop(如 Switch 用 `checked`),通过字段 `modelProp` 指定,或在 componentVars 预设 `modelProp`
209
+ - **接收 path**:组件会收到 `path` prop(字段路径)
210
+ - **属性透传**:Field 上除框架级属性外(component/hidden/modelProp/valueFormatter/fields/slots/formItemContainer/componentContainer/extraProps 等),其余作为 attrs 透传给自定义组件
166
211
 
167
- ## 5. ProComponentProvider — global defaults
212
+ ## valueFormatter -- 字段值转换
168
213
 
169
- Wrap your app to set defaults for all ProForm/ProTable instances. Merged with priority (highest first):
214
+ 控制表单值与组件值之间的转换(如日期 dayjs 与字符串互转)。**在 onUpdate:前执行**,支持两种形态:
170
215
 
171
- 1. **Field-level config** (always wins)
172
- 2. **`componentVars`** passed to ProComponentProvider
173
- 3. **`INJECT_CONFIG`** built-in presets
216
+ ```ts
217
+ // 函数形态:(新值, 旧值) => 转换后的值,写回 formData
218
+ { path: 'name', valueFormatter: (val, oldVal) => val?.trim() }
219
+
220
+ // 对象形态:get 读出时转换,set 写入时转换
221
+ { path: 'birthday', component: 'date-picker',
222
+ valueFormatter: {
223
+ get: (val) => val ? dayjs(val) : null, // formData -> 组件显示
224
+ set: (val) => val ? dayjs(val).format('YYYY-MM-DD') : null, // 组件 -> formData
225
+ } }
226
+ ```
174
227
 
175
- ### Built-in presets (INJECT_CONFIG)
228
+ ## useTable -- 表格实例
176
229
 
177
- | Component | Preset |
178
- | :--------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- |
179
- | `switch` | `{ modelProp: 'checked' }` |
180
- | `input` | `{ maxlength: 100, allowClear: true, placeholder: '请输入' }` |
181
- | `textarea` | `{ maxlength: 200, autoSize: {minRows:3,maxRows:6}, showCount: true, allowClear: true, placeholder: '请输入' }` |
182
- | `select` / `cascader` | `{ allowClear: true, placeholder: '请选择', getPopupContainer }` |
183
- | `input-number` | `{ max: 10^15, min: -(10^15), controls: false, placeholder: '请输入', style: {width:'100%'} }` |
184
- | `date-picker` / `range-picker` / `time-picker` | `{ allowClear: true, getPopupContainer, style: {width:'100%'} }` |
185
- | `pro-table` | `{ pagination: {showTotal, showSizeChanger, showQuickJumper}, searchFormConfig: {layout:'grid'}, control: true, addIndexColumn: true }` |
186
- | `pro-form` | `{ grid: { gutter: {xs:8, sm:16, md:16, lg:24} } }` |
187
- | `pro-form-item` | `{ validateFirst: true, span: 8 }` |
230
+ ```ts
231
+ const table = useTable<SearchParams, User>({
232
+ columns: [{ dataIndex: 'name', title: '姓名', width: 120 }],
233
+ dataSource: [],
234
+ pageParam: { current: 1, pageSize: 10, total: 0 }, // 默认值
235
+ searchParam: { keyword: '', status: '' },
236
+ searchFields: [ // 与 ProForm 同一套 Field[] 格式
237
+ { path: 'keyword', component: 'input', label: '关键词' },
238
+ { path: 'status', component: 'select', label: '状态', options: [...] },
239
+ ],
240
+ });
241
+ ```
188
242
 
189
- ### Override example
243
+ 返回 `Table` 实例:
190
244
 
191
- ```vue
192
- <ProComponentProvider
193
- :component-vars="{
194
- 'pro-form-item': { span: 6 },
195
- input: { maxlength: 200 },
196
- select: { showSearch: true },
197
- 'pro-table': { addIndexColumn: false },
198
- }"
199
- >
200
- <ProForm :form="form" />
201
- <ProTable :table="table" />
202
- </ProComponentProvider>
203
- ```
245
+ | 成员 | 说明 |
246
+ | :-------------------------------------------------------------------- | :----------------------------------------------------- |
247
+ | `table.columns` | 列配置(Ref) |
248
+ | `table.dataSource` | 表格数据源(Ref) |
249
+ | `table.pageParam` | 分页参数(reactive):`current` / `pageSize` / `total` |
250
+ | `table.searchForm` | 搜索表单实例(Form 类型,所有 useForm 方法可用) |
251
+ | `table.setColumn` / `deleteColumn` / `appendColumn` / `prependColumn` | 列增删改查(用法同 setField) |
252
+ | `table.setPageParam(patch)` | 设置分页参数 |
253
+ | `table.resetQueryParams()` | 重置分页到第一页 + 恢复搜索条件到初始值 |
204
254
 
205
- ---
255
+ ## ProTable -- 配置驱动表格
206
256
 
207
- ## 6. ProTable quick reference
257
+ **关键:ProTable 透传所有 ant-design-vue TableProps**(`& TableProps`),可直接写 `row-key` / `bordered` / `scroll` / `row-selection` 等原生属性;`size`、`loading` 是 v-model。
208
258
 
209
- ```ts
210
- import { ProTable, useTable } from '@qin-ui/antd-vue-pro';
259
+ | prop | 说明 |
260
+ | :----------------- | :-------------------------------------------------------------- |
261
+ | `table` | useTable 实例 |
262
+ | `search` | 数据查询方法 `() => Promise`,ProTable 内部调用它(非自动触发) |
263
+ | `addIndexColumn` | 首列插入 index 列 |
264
+ | `immediateSearch` | onMounted 时立即触发一次 search |
265
+ | `control` | 是否显示 size/列控制按钮,可 `{sizeControl, columnControl}` |
266
+ | `searchFormConfig` | 搜索栏配置,含 `hidden`(隐藏搜索区)、`container` |
267
+ | `tableContainer` | 表格区容器 |
268
+
269
+ **数据流**:搜索->重置分页到第 1 页->调 `search()`;分页/排序变化->更新 `pageParam`->调 `search()`;重置->`resetQueryParams()`->调 `search()`。
270
+
271
+ ## Column -- 列配置
272
+
273
+ 继承 ant-design-vue `ColumnType`,**所有原生列属性可用**(`title` / `width` / `fixed` / `align` / `customRender` / `ellipsis`…)。新增:
211
274
 
212
- const table = useTable<Row>({
275
+ - `dataIndex`:列数据路径(主用),类型安全,支持 `'name'` / `'address.city'` / `['address','city']`
276
+ - `key`:辅助标识(dataIndex 不满足时用,优先级更低)
277
+ - `hidden`:隐藏该列(配合列控制)
278
+
279
+ ## ProTable 最小示例
280
+
281
+ ```vue
282
+ <script setup lang="ts">
283
+ import { ProTable, useTable } from '@qin-ui/antd-vue-pro';
284
+ interface User {
285
+ id: number;
286
+ name: string;
287
+ status: string;
288
+ }
289
+ interface Search {
290
+ keyword: string;
291
+ }
292
+
293
+ const table = useTable<Search, User>({
213
294
  columns: [
214
- { title: 'ID', dataIndex: 'id', width: 80 },
215
- { title: 'Name', dataIndex: 'name', width: 120 },
216
- ],
217
- // searchFields uses the same Field[] format as ProForm
218
- searchFields: [
219
- { path: 'name', component: 'input', placeholder: 'Search' },
220
- { path: 'status', component: 'select', options: [...] },
295
+ { dataIndex: 'name', title: '姓名', width: 120 },
296
+ { dataIndex: 'status', title: '状态' },
221
297
  ],
298
+ searchFields: [{ path: 'keyword', component: 'input', label: '关键词' }],
222
299
  });
223
- // table.searchForm is a ProForm instance — all form methods available
300
+
301
+ const fetchData = async () => {
302
+ // 组装查询参数:搜索条件 + 分页
303
+ const res = await api.list({
304
+ ...table.searchForm.formData,
305
+ ...table.pageParam,
306
+ });
307
+ table.dataSource.value = res.data;
308
+ table.setPageParam({ total: res.total });
309
+ };
310
+ </script>
311
+ <template>
312
+ <ProTable :table="table" :search="fetchData" row-key="id" immediate-search />
313
+ </template>
224
314
  ```
225
315
 
226
- ```html
227
- <ProTable :table="table" :search="fetchData" addIndexColumn immediateSearch />
316
+ ## 响应式联动(三种模式,选最简的)
317
+
318
+ ```ts
319
+ disabled: computed(() => !form.formData.enabled), // A 声明式,初始化时已知(首选)
320
+ const isDisabled = ref(false); // B 外部共享状态
321
+ form.setField('limitCount', { disabled: true }); // C 命令式,事件驱动
228
322
  ```
229
323
 
230
- ---
324
+ > **不支持响应式**(不能包 ref/computed)的属性:`component` / `formItemContainer` / `componentContainer` / `valueFormatter` / `fields` / `slots` / `modelProp`。
325
+
326
+ ## 反模式
327
+
328
+ - ❌ 在 `<ProForm>` 内手写 `<a-form-item>` -- ProForm 从 Field 配置自动渲染。
329
+ - ❌ 猜透传属性名 -- 先查 antdv.com。
330
+ - ❌ 把 `span` 当输入控件属性。
331
+ - ❌ 传 SFC 给 `component` 不用 `markRaw`。
332
+ - ❌ 在 ProTable 上忘了传 `:search`,或期望它自动请求 -- 查询由你提供并驱动。
231
333
 
232
- ## 7. References
334
+ ## 按需深查
233
335
 
234
- 1. **`node_modules/@qin-ui/antd-vue-pro/api.json`** — Structured API metadata (signatures, types, JSDoc examples)
235
- 2. **`node_modules/@qin-ui/antd-vue-pro/README.md`** — Full usage docs and code examples
236
- 3. **[antdv.com](https://antdv.com)** — ant-design-vue component API reference (always check before writing pass-through props)
336
+ - `node_modules/@qin-ui/antd-vue-pro/api.json` -- 结构化 API 元数据(函数签名、参数、类型)。
337
+ - `node_modules/@qin-ui/antd-vue-pro/README.md` -- 完整用法与进阶示例。
package/README.md CHANGED
@@ -38,6 +38,23 @@ pnpm add @qin-ui/antd-vue-pro
38
38
 
39
39
  ---
40
40
 
41
+ ## 🤖 AI 辅助开发
42
+
43
+ 本项目内置 AI 上下文初始化工具。安装后在项目根目录执行:
44
+
45
+ ```bash
46
+ npx @qin-ui/antd-vue-pro init-ai
47
+ ```
48
+
49
+ 将在项目中生成双载体 AI 上下文,使 Cursor / Claude Code / Codex 等 AI 工具深度理解本库的 Schema 驱动用法与属性透传规则:
50
+
51
+ - `AGENTS.md` — 核心使用规则(跨工具开放约定,会话时自动注入,AI 默认加载)
52
+ - `.agents/skills/antd-vue-pro.md` — 完整 API 参考(支持 skills 发现的工具按需调取)
53
+
54
+ > 生成的 `AGENTS.md` 使用标记区块写入,重复执行 `init-ai` 可安全更新,不影响项目内其他库的指令。建议将生成的文件提交到 Git 仓库,团队共享 AI 上下文。
55
+
56
+ ---
57
+
41
58
  ## 🚀 快速开始
42
59
 
43
60
  ### 1. 全局注册组件(可选)
@@ -119,6 +136,29 @@ const handleSubmit = async () => {
119
136
 
120
137
  ---
121
138
 
139
+ ## 🧱 属性分层透传(核心规则)
140
+
141
+ ProForm/ProTable 是渲染引擎,所有真实 UI 来自 ant-design-vue。**Field 上的属性会被"剥离到不同 DOM 层",不是全塞给输入控件**。写错层就会失效,这是最容易踩的坑:
142
+
143
+ ```
144
+ <a-col :span="8"> ← Grid 层(仅 grid 开启时)
145
+ <a-form-item label="..."> ← FormItem 层
146
+ <a-input placeholder> ← 输入控件层
147
+ </a-form-item>
148
+ </a-col>
149
+ ```
150
+
151
+ | 属性 | 落到哪一层 | 说明 |
152
+ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------- | :------------------- |
153
+ | `span` / `offset` / `push` / `pull` / `flex` / `xs`..`xxl` | `<a-col>` Grid 层 | 仅 `grid` 开启时生效 |
154
+ | `label` / `rules` / `tooltip` / `colon` / `labelCol` / `wrapperCol` / `extra` / `help` / `validateTrigger` / `validateFirst` / `valuePropName` / `normalize` / `formItemStyle` / `formItemClass` / `formItemDataAttrs` | `<a-form-item>` 层 | 表单项相关属性 |
155
+ | `disabled` / `placeholder` / `allowClear` / `options` / `mode` / `maxlength` / `componentStyle` / `componentClass` / `componentDataAttrs` + 该控件其余 ant-design-vue 原生属性 | 输入控件本身(`a-input` 等) | 其余全部 |
156
+ | `component` / `hidden` / `modelProp` / `valueFormatter` / `fields` / `slots` / `formItemContainer` / `componentContainer` / `extraProps` | ProForm 逻辑消费,**不绑到 DOM** | 框架级属性 |
157
+
158
+ > **规则**:`span` 给 Grid,`label`/`rules` 给 FormItem,其余给输入控件。输入控件的具体属性名/类型请以 [ant-design-vue 官方文档](https://antdv.com) 为准。
159
+
160
+ ---
161
+
122
162
  ## 📚 组件 API 说明
123
163
 
124
164
  ### 1. ProComponentProvider
@@ -131,6 +171,24 @@ const handleSubmit = async () => {
131
171
  | :--------------- | :-------------------------------------- | :-------------- | :----- |
132
172
  | `component-vars` | 需要全局 provide 给子组件的默认属性配置 | `ComponentVars` | - |
133
173
 
174
+ #### 内置默认预设(INJECT_CONFIG)
175
+
176
+ ProComponentProvider 内置了一套默认属性预设,**即使不配置 `component-vars` 也会生效**。Field 配置优先级最高,会覆盖这些预设。以下为各组件的内置默认值(写代码时需知晓,避免行为与预期不符):
177
+
178
+ | component | 默认预设 |
179
+ | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
180
+ | `switch` | `{ modelProp: 'checked' }` —— v-model 绑定 `checked` 而非 `value` |
181
+ | `input` / `input-password` | `{ maxlength: 100, allowClear: true, placeholder: '请输入' }` |
182
+ | `textarea` | `{ maxlength: 200, autoSize: { minRows: 3, maxRows: 6 }, showCount: true, allowClear: true, placeholder: '请输入' }` |
183
+ | `input-number` | `{ max: 1e15-1, min: -(1e15+1), controls: false, placeholder: '请输入', style: { width: '100%' } }` |
184
+ | `select` / `cascader` | `{ allowClear: true, placeholder: '请选择', getPopupContainer }` |
185
+ | `date-picker` / `range-picker` / `time-picker` | `{ allowClear: true, getPopupContainer, style: { width: '100%' } }` |
186
+ | `pro-form` | `{ grid: { gutter: { xs: 8, sm: 16, md: 16, lg: 24 } } }` |
187
+ | `pro-form-item` | `{ validateFirst: true, span: 8 }` |
188
+ | `pro-table` | `{ pagination: { showTotal, showSizeChanger, pageSizeOptions: ['10'..'100'], showQuickJumper: true }, searchFormConfig: { layout: 'grid', expand: { minExpandRows: 2 } }, control: true, addIndexColumn: true }` |
189
+
190
+ > `modelProp` 控制 v-model 绑定的属性名,默认 `'value'`(即 `v-model:value`)。`switch` 经预设绑定为 `checked`,通常无需手动设置。
191
+
134
192
  ---
135
193
 
136
194
  ### 2. ProForm
@@ -177,6 +235,32 @@ const handleSubmit = async () => {
177
235
  | `toolbar` | 自定义工具栏区域(位于表格右上方控制按钮旁) |
178
236
  | `table` | 完全自定义渲染表格区域 |
179
237
 
238
+ #### 数据流
239
+
240
+ ProTable 不会自动请求数据,**查询由你提供的 `search` 回调驱动**。内部串联逻辑:
241
+
242
+ ```
243
+ 点击搜索 -> 重置分页到第 1 页 -> 调用 search()
244
+ 分页/排序变化 -> 更新 pageParam -> 调用 search()
245
+ 点击重置 -> resetQueryParams() -> 调用 search()
246
+ 挂载(immediateSearch) -> 立即触发一次 -> 调用 search()
247
+ ```
248
+
249
+ `search` 回调内通常组装查询参数并赋值数据源:
250
+
251
+ ```ts
252
+ const fetchData = async () => {
253
+ const res = await api.list({
254
+ ...table.searchForm.formData, // 搜索条件
255
+ ...table.pageParam, // 分页
256
+ });
257
+ table.dataSource.value = res.data;
258
+ table.setPageParam({ total: res.total });
259
+ };
260
+
261
+ // 重置按钮(ProTable 内部已串联 resetQueryParams -> search,一般无需手动处理)
262
+ ```
263
+
180
264
  ---
181
265
 
182
266
  ## ⚙️ 核心 Hooks 说明
@@ -244,6 +328,16 @@ const table = useTable<D, T>({
244
328
  | `setPageParam(param)` | 增量设置当前分页参数值,支持传入对象或更新函数 | `(param) => void` |
245
329
  | `resetQueryParams()` | 重置搜索参数,并恢复分页至第 1 页 | `() => void` |
246
330
 
331
+ #### 列配置(Column)
332
+
333
+ `columns` 数组中每一项为 `Column<T>` 类型,**继承 ant-design-vue `ColumnType`**,因此所有原生列属性(`title` / `width` / `fixed` / `align` / `customRender` / `ellipsis` 等)均可用。在此基础上新增:
334
+
335
+ | 属性 | 说明 | 类型 | 默认值 |
336
+ | :---------- | :---------------------------------------------------------------------------------------- | :------------------- | :------ |
337
+ | `dataIndex` | 列数据路径(主要字段),类型安全,支持 `'name'` / `'address.city'` / `['address','city']` | `string \| string[]` | - |
338
+ | `key` | 列字段标识(辅助),仅当 `dataIndex` 无法满足时使用,匹配优先级低于 `dataIndex` | `string` | - |
339
+ | `hidden` | 是否隐藏该列(配合列动态控制) | `boolean` | `false` |
340
+
247
341
  ---
248
342
 
249
343
  ## 📄 字段配置(`Field`)详解
@@ -275,6 +369,63 @@ const table = useTable<D, T>({
275
369
  | `fields` | 嵌套子表单项的 Schema 数组配置(用于分组或复杂嵌套表单) | `Field[]` | - |
276
370
  | `grid` | 针对当前嵌套子表单项所使用的网格布局参数(仅当有 `fields` 时有效) | `boolean \| GridProps` | - |
277
371
 
372
+ #### `valueFormatter` 字段值转换
373
+
374
+ 控制表单值与组件值之间的转换(如日期 `dayjs` 与字符串互转)。在 `onUpdate:value` 之前执行,支持两种形态:
375
+
376
+ ```ts
377
+ // 函数形态:(新值, 旧值) => 转换后的值,写回 formData
378
+ { path: 'name', component: 'input', valueFormatter: (val, oldVal) => val?.trim() }
379
+
380
+ // 对象形态:get 读出时转换,set 写入时转换
381
+ {
382
+ path: 'birthday',
383
+ component: 'date-picker',
384
+ valueFormatter: {
385
+ get: (val) => (val ? dayjs(val) : null), // formData -> 组件显示
386
+ set: (val) => (val ? dayjs(val).format('YYYY-MM-DD') : null), // 组件 -> formData
387
+ },
388
+ }
389
+ ```
390
+
391
+ > ⚠️ `valueFormatter` 不支持响应式(不能包 `ref`/`computed`),与 `component` / `fields` / `slots` / `modelProp` 等同属不支持响应式的属性。
392
+
393
+ ---
394
+
395
+ ### 🔄 响应式联动
396
+
397
+ 控制类属性(`disabled` / `hidden` / `rules` 等)支持三种响应式模式,按场景选最简的:
398
+
399
+ ```ts
400
+ // A. computed() 声明式 -- 初始化时已知的联动(首选)
401
+ disabled: computed(() => !form.formData.enabled),
402
+ rules: computed(() => form.formData.hasLimit ? [{ required: true, message: '必填' }] : []),
403
+
404
+ // B. ref() -- 外部共享状态
405
+ const isDisabled = ref(false);
406
+ disabled: isDisabled,
407
+
408
+ // C. setField() 命令式 -- 事件驱动的运行时变更
409
+ form.setField('limitCount', { disabled: true });
410
+ ```
411
+
412
+ > **规则**:初始化时已知的联动用 `computed()`,运行时事件触发的用 `setField()`,外部共享状态用 `ref()`。能用 `computed()` 解决就不要用 `setField()`。
413
+
414
+ #### 不支持响应式的属性
415
+
416
+ 以下属性不能包 `ref`/`computed`(会失效或引发渲染异常),只能传静态值:
417
+
418
+ | 属性 | 原因 |
419
+ | :----------------------------------------- | :-------------------------------------------------- |
420
+ | `component` | 组件对象/函数,响应式代理会破坏渲染(需 `markRaw`) |
421
+ | `valueFormatter` | 纯转换函数,无需响应式 |
422
+ | `fields` | 嵌套字段数组结构,响应式代理会干扰内部解析 |
423
+ | `slots` | 插槽内容,响应式代理会破坏渲染 |
424
+ | `modelProp` | 字符串标识,无需响应式 |
425
+ | `formItemContainer` / `componentContainer` | 容器组件,需 `markRaw` |
426
+
427
+ > 动态切换 `component` 或 `slots` 时,用 `setField()` 命令式更新,而非包 `computed`。
428
+
278
429
  ---
279
430
 
280
431
  ### 内置组件映射(`component` 可选值)
@@ -303,9 +454,45 @@ const table = useTable<D, T>({
303
454
 
304
455
  ---
305
456
 
306
- ### 🧩 扩充注册您的自定义组件
457
+ ### 🧩 自定义组件(4 种方式)
307
458
 
308
- #### 步骤 1:利用 ComponentProvider 全局注册
459
+ `Field.component` 的解析优先级(高 -> 低):
460
+
461
+ `teleport 插槽注入` > `ProComponentProvider.componentMap` > `内置 componentMap` > `原始 component 值`
462
+
463
+ #### 方式 1:传入 SFC 单文件组件对象(推荐,单字段最常用)
464
+
465
+ `component` 直接传组件对象,**必须用 `markRaw` 包裹**:
466
+
467
+ ```ts
468
+ import { markRaw } from 'vue';
469
+ import MyInput from './MyInput.vue';
470
+
471
+ const fields = [{ path: 'code', label: '验证码', component: markRaw(MyInput) }];
472
+ ```
473
+
474
+ > ⚠️ **必须 `markRaw`**:不包会被 Vue 当成响应式对象深度代理,触发性能警告甚至渲染异常。类型上 `component` 接受 `RenderComponentType | Raw<RenderComponentType>`,`Raw<T>` 即 markRaw 的类型标记。此字段不支持响应式(不能包 `ref`/`computed`)。
475
+
476
+ #### 方式 2:传入 render 函数(单字段,需动态拼装 props 时)
477
+
478
+ `component` 传 `(props, ctx) => VNode`。`props` 含 v-model 绑定值与 path,`ctx.attrs` 含透传属性:
479
+
480
+ ```ts
481
+ import { h } from 'vue';
482
+ import MyInput from './MyInput.vue';
483
+
484
+ const fields = [
485
+ {
486
+ path: 'code',
487
+ label: '验证码',
488
+ component: (p, ctx) => h(MyInput, { ...p, ...ctx.attrs }),
489
+ },
490
+ ];
491
+ ```
492
+
493
+ #### 方式 3:ProComponentProvider 注入 componentMap(全局复用,推荐多字段场景)
494
+
495
+ 在根用 `componentMap` 注册,Field 里用字符串引用。**可覆盖内置组件**(如 `input: MyInput` 替换全局 `input`)。
309
496
 
310
497
  ```vue
311
498
  <script setup lang="ts">
@@ -324,9 +511,7 @@ const componentMap = {
324
511
  </template>
325
512
  ```
326
513
 
327
- #### 步骤 2:追加 TypeScript 全局声明获取强类型补全
328
-
329
- 在项目任意 `.d.ts` 类型文件中写入以下代码:
514
+ 追加 TypeScript 全局声明获取强类型补全(项目任意 `.d.ts`):
330
515
 
331
516
  ```ts
332
517
  declare module '@qin-ui/antd-vue-pro' {
@@ -336,7 +521,34 @@ declare module '@qin-ui/antd-vue-pro' {
336
521
  }
337
522
  ```
338
523
 
339
- 现在,您在表单配置中写入 `component: 'rich-editor'` 时,将自动获得精准的属性类型联想与校验!
524
+ 之后 `component: 'rich-editor'` 即获精准属性类型联想与校验。
525
+
526
+ #### 方式 4:模板 scoped slot(声明式,简单替换)
527
+
528
+ 插槽名 = 字段 `path`,绑定参数通过 `v-bind="scoped"` 转发(teleport 机制,优先级最高):
529
+
530
+ ```vue
531
+ <ProForm :form="form">
532
+ <template #agreement="scoped">
533
+ <a-checkbox v-bind="scoped">同意协议</a-checkbox>
534
+ </template>
535
+ </ProForm>
536
+ ```
537
+
538
+ #### 选择建议
539
+
540
+ | 场景 | 推荐方式 |
541
+ | :--------------------------- | :-------------------------------- |
542
+ | 单字段复用一个 SFC | 方式 1(`markRaw(SFC)`) |
543
+ | 单字段、需动态拼装 props | 方式 2(render 函数) |
544
+ | 多处复用 / 替换内置组件 | 方式 3(componentMap + 声明扩充) |
545
+ | 简单声明式替换、不想写 `h()` | 方式 4(scoped slot) |
546
+
547
+ #### 自定义组件需遵守的约定
548
+
549
+ - **v-model**:默认绑 `value`(`v-model:value`)。若组件用别的 prop(如 Switch 用 `checked`),通过字段 `modelProp` 指定,或在 `componentVars` 预设 `modelProp`。
550
+ - **接收 path**:组件会收到 `path` prop(字段路径),可用于透传或标识。
551
+ - **属性透传**:Field 上除框架级属性外(`component` / `hidden` / `modelProp` / `valueFormatter` / `fields` / `slots` / `formItemContainer` / `componentContainer` / `extraProps` 等),其余作为 attrs 透传给自定义组件。
340
552
 
341
553
  ---
342
554
 
package/api.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-07-10T10:21:21.983Z",
2
+ "generatedAt": "2026-07-15T06:38:34.266Z",
3
3
  "name": "@qin-ui/antd-vue-pro",
4
4
  "api": [
5
5
  {
@@ -8,6 +8,20 @@
8
8
  "package": "@qin-ui/antd-vue-pro",
9
9
  "signature": "<ProComponentProvider>",
10
10
  "description": "@qin-ui/antd-vue-pro 全局配置提供者组件\n用于在组件树的顶层配置所有子组件的全局默认属性。\n支持配置 ProForm、ProTable、ProFormItem 以及所有内置组件的默认属性。",
11
+ "params": [
12
+ {
13
+ "name": "componentVars",
14
+ "type": "ComponentVars",
15
+ "optional": true,
16
+ "description": "组件全局配置变量"
17
+ },
18
+ {
19
+ "name": "componentMap",
20
+ "type": "Record<ComponentName, any>",
21
+ "optional": true,
22
+ "description": "自定义组件映射,用于替换或扩展内置组件"
23
+ }
24
+ ],
11
25
  "examples": [
12
26
  "```vue\n<template>\n<ProComponentProvider\n:componentVars=\"{\n'pro-form': { labelCol: { span: 4 } },\n'input': { placeholder: '请输入', maxlength: 200 },\n'select': { placeholder: '请选择' },\n}\"\n>\n<ProForm :form=\"form\" />\n</ProComponentProvider>\n</template>\n```"
13
27
  ]
@@ -32,6 +46,26 @@
32
46
  "package": "@qin-ui/antd-vue-pro",
33
47
  "signature": "<ProForm>",
34
48
  "description": "@qin-ui/antd-vue-pro 配置驱动表单组件\n架构设计说明 (Architecture Overview)\n[ 数据层 (Core) ] => [ 绑定层 (ProForm) ] => [ 渲染层 (BaseFormItem / BaseField) ]\n1. 数据隔离:ProForm 本身不负责存储和管理表单状态,所有核心状态(formData, fields, 校验规则)\n均交由底层的 `useForm`(@qin-ui/pro-components-core)驱动。\n2. 上下文透传:通过 `provide` 将核心状态下发给子组件,避免了深层嵌套的 Props 传递。\n3. 递归渲染:网格布局和具体字段的解析交由 `<BaseFormItem>` 递归完成,实现了 UI 布局与表单逻辑的解耦。\n通过配置驱动的方式快速构建表单,支持:\n- 字段联动(通过 setField、watch 等实现)\n- 嵌套字段(通过 fields 递归配置)\n- 自定义组件(通过 component 属性和 custom 类型)\n- 网格布局(通过 grid 属性)\n- 动态插槽(以字段 path 命名的插槽)\n- 全局配置注入(通过 ProComponentProvider)",
49
+ "params": [
50
+ {
51
+ "name": "form",
52
+ "type": "F",
53
+ "optional": true,
54
+ "description": "useForm 返回的表单实例"
55
+ },
56
+ {
57
+ "name": "grid",
58
+ "type": "boolean | GridProps",
59
+ "optional": true,
60
+ "description": "是否启用网格布局 (默认 false)"
61
+ },
62
+ {
63
+ "name": "...attrs",
64
+ "type": "...FormProps",
65
+ "optional": false,
66
+ "description": "Ant Design Vue Form 组件的其他属性"
67
+ }
68
+ ],
35
69
  "typeParams": [
36
70
  {
37
71
  "name": "F"
@@ -74,6 +108,14 @@
74
108
  "package": "@qin-ui/antd-vue-pro",
75
109
  "signature": "export const useFormData = <D extends Data = Data>(\n initFormData?: ExtendWithAny<DeepPartial<D>>\n): UseFormDataReturn<D> =>",
76
110
  "description": "表单数据处理 Hook\n提供响应式表单数据的管理能力,支持:\n- 响应式数据存储(基于 Vue reactive)\n- 深层路径读写(支持点号分隔,如 'address.city')\n- 类型安全的路径提示(传入泛型 D 后,path 参数可获得类型推导)\n- 父子表单自动注入(非根表单会从注入中获取数据)",
111
+ "params": [
112
+ {
113
+ "name": "initFormData",
114
+ "type": "ExtendWithAny<DeepPartial<D>>",
115
+ "optional": true,
116
+ "description": "初始表单数据"
117
+ }
118
+ ],
77
119
  "returns": "{UseFormDataReturn<D>} 表单数据操作对象",
78
120
  "typeParams": [
79
121
  {
@@ -90,6 +132,14 @@
90
132
  "package": "@qin-ui/antd-vue-pro",
91
133
  "signature": "export function useFields<D extends Data = Data>(\n initFields?: Fields<D>\n): UseFieldsReturn<D, Field<ComponentName, D>, FormItemInstance>;",
92
134
  "description": "字段配置管理 Hook\n基于 `@qin-ui/pro-components-core` 的 `useFields`,将泛型参数绑定为 Ant Design Vue 的本地类型:\n- 字段类型 F -> `Field<ComponentName, D>`(支持 Ant Design Vue 组件类型推导)\n- FormItem 实例 -> Ant Design Vue 的 `FormItemInstance`\n提供对字段配置数组的增删改查操作,支持:\n- 通过路径字符串或查找函数定位字段\n- 深层嵌套字段的遍历和匹配\n- 字段配置的合并/覆盖更新\n- 字段的添加、插入、删除\n- 父级字段查找\n- `fields` - 字段配置数组(响应式 Ref\\<F[]\\>)\n- `getField(path)` - 获取字段配置,支持路径字符串或查找函数\n- `setField(path, field, options?)` - 更新字段配置,默认合并模式\n- `deleteField(path, options?)` - 删除字段配置\n- `appendField(path, field, options?)` - 在指定字段后追加\n- `prependField(path, field, options?)` - 在指定字段前插入\n- `getParentField(path, options?)` - 获取父级字段配置",
135
+ "params": [
136
+ {
137
+ "name": "initFields",
138
+ "type": "Fields<D>",
139
+ "optional": true,
140
+ "description": "初始字段配置数组"
141
+ }
142
+ ],
93
143
  "returns": "字段操作对象,包含以下属性:",
94
144
  "typeParams": [
95
145
  {
@@ -131,6 +181,26 @@
131
181
  "package": "@qin-ui/antd-vue-pro",
132
182
  "signature": "export function useForm<D extends Data = Data>(\n initFormData?: ExtendWithAny<DeepPartial<D>>,\n initFields?: Field<ComponentName, D>[],\n root?: boolean\n): Form<D, Field<ComponentName, D>>;",
133
183
  "description": "创建表单实例的 Hook\n基于 `@qin-ui/pro-components-core` 的 `useForm`,将泛型参数绑定为 Ant Design Vue 的本地类型:\n- 字段类型 F -> `Field<ComponentName, D>`(支持 Ant Design Vue 所有内置组件类型推导)\n- 表单实例 -> Ant Design Vue 的 `FormInstance`(使 formRef 获得完整的类型提示)\n表单实例组合了三个子模块的能力:\n- **useFormData**:表单数据管理(响应式数据、读写、批量更新)\n- **useFields**:字段配置管理(增删改查字段配置,支持嵌套)\n- **useFormRef**:底层 Form 组件实例引用",
184
+ "params": [
185
+ {
186
+ "name": "initFormData",
187
+ "type": "ExtendWithAny<DeepPartial<D>>",
188
+ "optional": true,
189
+ "description": "初始表单数据"
190
+ },
191
+ {
192
+ "name": "initFields",
193
+ "type": "Field<ComponentName, D>[]",
194
+ "optional": true,
195
+ "description": "初始字段配置数组"
196
+ },
197
+ {
198
+ "name": "root",
199
+ "type": "boolean",
200
+ "optional": true,
201
+ "description": "是否为根表单。设为 `false` 时尝试从父组件注入获取表单实例 (默认 true)"
202
+ }
203
+ ],
134
204
  "returns": "{Form<D>} 表单实例,包含数据操作、字段操作和 ref 操作",
135
205
  "typeParams": [
136
206
  {
@@ -206,6 +276,36 @@
206
276
  "type": "object",
207
277
  "optional": false,
208
278
  "description": "表格配置参数"
279
+ },
280
+ {
281
+ "name": "params.columns",
282
+ "type": "Columns<T>",
283
+ "optional": true,
284
+ "description": "初始列配置数组"
285
+ },
286
+ {
287
+ "name": "params.dataSource",
288
+ "type": "T[]",
289
+ "optional": true,
290
+ "description": "初始数据源"
291
+ },
292
+ {
293
+ "name": "params.pageParam",
294
+ "type": "PageParam",
295
+ "optional": true,
296
+ "description": "初始分页参数,默认 `{ current: 1, pageSize: 10, total: 0 }`"
297
+ },
298
+ {
299
+ "name": "params.searchParam",
300
+ "type": "ExtendWithAny<DeepPartial<D>>",
301
+ "optional": true,
302
+ "description": "初始搜索参数"
303
+ },
304
+ {
305
+ "name": "params.searchFields",
306
+ "type": "Fields<D>",
307
+ "optional": true,
308
+ "description": "搜索表单字段配置"
209
309
  }
210
310
  ],
211
311
  "returns": "{Table<D, T>} 表格实例",
package/bin/init-ai.mjs CHANGED
@@ -3,10 +3,9 @@
3
3
  /**
4
4
  * @qin-ui/antd-vue-pro AI 上下文初始化 CLI 工具
5
5
  *
6
- * 在消费方项目中生成分层 AI 上下文文件,使 AI 工具能够:
7
- * 1. 理解 @qin-ui/antd-vue-pro 的 Schema 驱动架构
8
- * 2. 掌握属性透传规则(何时查阅 ant-design-vue 文档)
9
- * 3. 快速获取完整 API 参考
6
+ * 在消费方项目中生成双载体 AI 上下文:
7
+ * 1. 根目录 AGENTS.md —— 核心使用规则,跨工具自动注入(主载体)
8
+ * 2. .agents/skills/{pkg}.md —— 完整 API 参考,支持 skills 发现的工具按需调取(参考载体)
10
9
  *
11
10
  * 用法:
12
11
  * npx @qin-ui/antd-vue-pro init-ai
@@ -28,6 +27,10 @@ const PKG_JSON = JSON.parse(
28
27
  const PKG_NAME = PKG_JSON.name;
29
28
  const PKG_SHORT = PKG_NAME.replace(/^@qin-ui\//, '');
30
29
 
30
+ // AGENTS.md 智能合并区块标记(支持多库共存,幂等更新)
31
+ const BEGIN_MARKER = `<!-- BEGIN ${PKG_NAME} -->`;
32
+ const END_MARKER = `<!-- END ${PKG_NAME} -->`;
33
+
31
34
  // ==================== 终端颜色 ====================
32
35
 
33
36
  const green = s => `\x1b[32m${s}\x1b[0m`;
@@ -64,56 +67,103 @@ function getApiJsonContent() {
64
67
  }
65
68
  }
66
69
 
67
- // ==================== 内容生成 ====================
70
+ // ==================== 工具函数 ====================
71
+
72
+ function escapeRegExp(s) {
73
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
74
+ }
75
+
76
+ function ensureDir(dirPath) {
77
+ if (!fs.existsSync(dirPath)) {
78
+ fs.mkdirSync(dirPath, { recursive: true });
79
+ }
80
+ }
81
+
82
+ function writeFileWithLog(filePath, content, label) {
83
+ const dir = path.dirname(filePath);
84
+ ensureDir(dir);
85
+ const existed = fs.existsSync(filePath);
86
+ fs.writeFileSync(filePath, content, 'utf-8');
87
+ const status = existed ? yellow('[更新]') : green('[创建]');
88
+ console.log(` ${status} ${cyan(label)}`);
89
+ }
90
+
91
+ // ==================== AGENTS.md(主载体,自动注入)====================
68
92
 
69
93
  /**
70
- * 生成 rules 文件内容(Instructions 层)
71
- * 包含:架构关系、透传规则、反模式、ant-design-vue 资源链接
94
+ * 生成 AGENTS.md 区块内容
95
+ * = AI-CONTEXT.md 全文(核心使用规则,AI 必然读到)
72
96
  */
73
- function getRulesContent() {
74
- const aiContext = getAiContextContent();
75
-
76
- return [
77
- '---',
78
- `description: "${PKG_NAME} 组件库使用规范 — Schema 驱动、属性透传规则"`,
79
- 'globs: ["**/*.vue", "**/*.ts", "**/*.tsx"]',
80
- 'alwaysApply: false',
81
- '---',
82
- '',
83
- aiContext,
84
- '',
85
- '---',
86
- '',
87
- '## 本地参考文件',
88
- '',
89
- '本规则文件提供了核心架构和用法指南。需要完整 API 参考或底层组件文档时,请阅读以下资源:',
90
- '',
91
- `- **\`.agents/docs/${PKG_SHORT}-api.md\`** — 完整 API 文档(组件、Hooks、类型、Field 配置参考)`,
92
- `- **\`node_modules/${PKG_NAME}/api.json\`** — 结构化 API 元数据(函数签名、类型定义)`,
93
- '',
94
- '## 底层组件文档',
95
- '',
96
- `${PKG_NAME} 基于 **ant-design-vue**(v4,\`a-\` 前缀)封装。编写 Field 中的输入控件属性前,请查阅 ant-design-vue 官方文档确认属性名和类型:`,
97
- '',
98
- '- **[ant-design-vue 官方文档](https://antdv.com)** — 组件 API 参考(中文)',
99
- '',
100
- ].join('\n');
97
+ function getAgentsBlock() {
98
+ return getAiContextContent();
101
99
  }
102
100
 
103
101
  /**
104
- * 生成 API 文档内容(Resources 层)
105
- * 从 README.md + api.json 合并提取完整 API 参考
102
+ * 智能合并 AGENTS.md:
103
+ * - 文件不存在 -> 创建,内容 = 标记区块
104
+ * - 已有本库区块 -> 幂等替换区块内容(支持升级后更新)
105
+ * - 已有其他内容但无本库区块 -> 末尾追加区块(多库共存不冲突)
106
106
  */
107
- function getApiDocContent() {
107
+ function writeAgentsMd(cwd) {
108
+ const agentsPath = path.join(cwd, 'AGENTS.md');
109
+ const blockContent = getAgentsBlock();
110
+ const newBlock = `${BEGIN_MARKER}\n${blockContent}\n${END_MARKER}`;
111
+
112
+ let existing = '';
113
+ if (fs.existsSync(agentsPath)) {
114
+ existing = fs.readFileSync(agentsPath, 'utf-8');
115
+ }
116
+
117
+ let result;
118
+ const hasBlock =
119
+ existing.includes(BEGIN_MARKER) && existing.includes(END_MARKER);
120
+
121
+ if (hasBlock) {
122
+ // 幂等替换已有区块
123
+ const blockRegex = new RegExp(
124
+ `${escapeRegExp(BEGIN_MARKER)}[\\s\\S]*?${escapeRegExp(END_MARKER)}`,
125
+ 'g'
126
+ );
127
+ result = existing.replace(blockRegex, newBlock);
128
+ } else if (existing.trim()) {
129
+ // 已有其他内容,追加区块
130
+ result = `${existing.trimEnd()}\n\n${newBlock}\n`;
131
+ } else {
132
+ // 文件不存在或空
133
+ result = `${newBlock}\n`;
134
+ }
135
+
136
+ const existed = fs.existsSync(agentsPath);
137
+ fs.writeFileSync(agentsPath, result, 'utf-8');
138
+ const status = existed ? yellow('[更新]') : green('[创建]');
139
+ console.log(` ${status} ${cyan('AGENTS.md')}`);
140
+ }
141
+
142
+ // ==================== Skills(参考载体,按需调取)====================
143
+
144
+ /**
145
+ * 生成 skill 文件内容(完整 API 参考 + skill frontmatter)
146
+ * 供支持 skills 发现的工具按需调取,避免全量注入占 token
147
+ */
148
+ function getSkillContent() {
108
149
  const readme = getReadmeContent();
109
150
  const apiJson = getApiJsonContent();
110
151
 
111
152
  const sections = [];
112
153
 
113
- sections.push(`# ${PKG_NAME} — 完整 API 参考`, '');
154
+ // skill frontmatter
155
+ sections.push('---');
156
+ sections.push(`name: ${PKG_SHORT}`);
157
+ sections.push(
158
+ `description: ${PKG_NAME} 完整 API 参考 - 组件、Hooks、类型、Field/Column 配置。当需要精确的函数签名、参数类型或完整用法示例时调用。`
159
+ );
160
+ sections.push('---');
161
+ sections.push('');
162
+
163
+ sections.push(`# ${PKG_NAME} - 完整 API 参考`, '');
114
164
  sections.push(`> 自动生成自 README.md 和 api.json。版本: ${PKG_JSON.version}`, '');
115
165
 
116
- // README 内容(已包含完整的使用文档)
166
+ // README 内容(完整使用文档)
117
167
  if (readme) {
118
168
  sections.push(readme);
119
169
  sections.push('');
@@ -121,10 +171,8 @@ function getApiDocContent() {
121
171
 
122
172
  // 结构化 API 元数据
123
173
  if (apiJson && apiJson.api && apiJson.api.length > 0) {
124
- sections.push('---');
125
- sections.push('');
126
- sections.push('## 结构化 API 元数据(自动提取自源码 JSDoc)');
127
- sections.push('');
174
+ sections.push('---', '');
175
+ sections.push('## 结构化 API 元数据(自动提取自源码 JSDoc)', '');
128
176
 
129
177
  for (const item of apiJson.api) {
130
178
  const typeLabel = {
@@ -195,41 +243,26 @@ function getApiDocContent() {
195
243
  return sections.join('\n');
196
244
  }
197
245
 
198
- // ==================== 文件写入 ====================
199
-
200
- function ensureDir(dirPath) {
201
- if (!fs.existsSync(dirPath)) {
202
- fs.mkdirSync(dirPath, { recursive: true });
203
- }
204
- }
205
-
206
- function writeFileWithLog(filePath, content, label) {
207
- const dir = path.dirname(filePath);
208
- ensureDir(dir);
209
- const existed = fs.existsSync(filePath);
210
- fs.writeFileSync(filePath, content, 'utf-8');
211
- const status = existed ? yellow('[更新]') : green('[创建]');
212
- console.log(` ${status} ${cyan(label)}`);
213
- }
214
-
215
246
  // ==================== CLI 入口 ====================
216
247
 
217
248
  function printHelp() {
218
249
  console.log(`
219
- ${bold(`${PKG_NAME} CLI — AI 上下文初始化`)}
250
+ ${bold(`${PKG_NAME} CLI - AI 上下文初始化`)}
220
251
 
221
252
  ${bold('用法:')}
222
253
  npx ${PKG_NAME} init-ai
223
254
 
224
255
  ${bold('说明:')}
225
- 在项目中生成分层 AI 上下文文件,使 AI 工具能够深度理解和规范使用 ${PKG_NAME}。
256
+ 在项目中生成双载体 AI 上下文,使 AI 工具能够深度理解和规范使用 ${PKG_NAME}。
226
257
 
227
258
  生成的文件结构:
259
+ AGENTS.md 核心使用规则(跨工具自动注入)
228
260
  .agents/
229
- rules/
230
- ${PKG_SHORT}.md 规则文件(架构、透传规则、反模式)
231
- docs/
232
- ${PKG_SHORT}-api.md API 参考(完整文档 + 结构化元数据)
261
+ skills/
262
+ ${PKG_SHORT}.md 完整 API 参考(按需调取)
263
+
264
+ ${dim('AGENTS.md 是跨工具开放约定,Codex / Claude Code / Cursor 等会话时自动注入。')}
265
+ ${dim('.agents/skills/ 供支持 skills 发现的工具按需调取完整 API。')}
233
266
 
234
267
  ${bold('选项:')}
235
268
  --help 显示帮助信息
@@ -253,49 +286,37 @@ function main() {
253
286
  }
254
287
 
255
288
  console.log('');
256
- console.log(bold(`📦 ${PKG_NAME} — AI 上下文初始化 v${PKG_JSON.version}`));
289
+ console.log(bold(`📦 ${PKG_NAME} - AI 上下文初始化 v${PKG_JSON.version}`));
257
290
  console.log(dim(` 基于 ant-design-vue 的 Schema 驱动组件库`));
258
291
  console.log('');
259
292
 
260
- // 1. 生成 Rules 文件
261
- console.log(bold('📋 规则层 (Rules)'));
262
- const rulesContent = getRulesContent();
263
- writeFileWithLog(
264
- path.join(process.cwd(), '.agents/rules', `${PKG_SHORT}.md`),
265
- rulesContent,
266
- `.agents/rules/${PKG_SHORT}.md`
267
- );
268
- console.log(dim(' → 架构概览、属性透传决策树、渐进用法、反模式'));
293
+ // 1. 生成 AGENTS.md(主载体)
294
+ console.log(bold('📋 主载体:AGENTS.md(自动注入)'));
295
+ writeAgentsMd(process.cwd());
296
+ console.log(dim(' -> 核心使用规则,Codex/Claude Code/Cursor 等会话自动加载'));
269
297
  console.log('');
270
298
 
271
- // 2. 生成 API 文档
272
- console.log(bold('📖 参考层 (Docs)'));
273
- const apiDocContent = getApiDocContent();
299
+ // 2. 生成 Skills 参考(按需调取)
300
+ console.log(bold('📖 参考载体:.agents/skills/(按需调取)'));
274
301
  writeFileWithLog(
275
- path.join(process.cwd(), '.agents/docs', `${PKG_SHORT}-api.md`),
276
- apiDocContent,
277
- `.agents/docs/${PKG_SHORT}-api.md`
302
+ path.join(process.cwd(), '.agents/skills', `${PKG_SHORT}.md`),
303
+ getSkillContent(),
304
+ `.agents/skills/${PKG_SHORT}.md`
278
305
  );
279
- console.log(dim(' → 完整 README 文档 + 结构化 API 元数据'));
306
+ console.log(dim(' -> 完整 README + 结构化 API 元数据,skills 工具按需调取'));
280
307
  console.log('');
281
308
 
282
309
  // 3. 完成提示
283
- console.log(green('✅ 完成!已生成分层 AI 上下文文件。'));
310
+ console.log(green('✅ 完成!已生成双载体 AI 上下文文件。'));
284
311
  console.log('');
285
312
  console.log(`${bold('📂 生成的文件:')}`);
286
- console.log(` ${cyan('.agents/')}`);
287
- console.log(` ├── ${cyan('rules/')}`);
288
- console.log(` │ └── ${cyan(PKG_SHORT + '.md')} ${dim('← 核心规则(AI 优先读取)')}`);
289
- console.log(` └── ${cyan('docs/')}`);
290
- console.log(` └── ${cyan(PKG_SHORT + '-api.md')} ${dim('← 完整 API 参考')}`);
313
+ console.log(` ${cyan('AGENTS.md')} ${dim('← 核心规则(AI 自动读取)')}`);
314
+ console.log(` ${cyan('.agents/skills/')}`);
315
+ console.log(` └── ${cyan(PKG_SHORT + '.md')} ${dim('← 完整 API 参考(按需调取)')}`);
291
316
  console.log('');
292
-
293
- console.log(`${bold('🔄 建议的下一步:')}`);
294
- console.log(` 1. 提交 ${cyan('.agents/')} 目录到 Git 仓库,团队共享`);
295
- console.log(` 2. 编写 Field 配置中的输入控件属性前,查阅 ant-design-vue 官方文档:`);
296
- console.log(` ${cyan('https://antdv.com')}`);
297
- console.log(` 3. ${dim('(可选)查看完整 API 参考:')}`);
298
- console.log(` ${dim(`cat .agents/docs/${PKG_SHORT}-api.md`)}`);
317
+ console.log(`${bold('🔄 说明:')}`);
318
+ console.log(` - AGENTS.md 已用标记区块写入,重复执行 init-ai 可安全更新(不破坏其他库的指令)`);
319
+ console.log(` - 编写 Field 中输入控件属性前,查阅 ant-design-vue 官方文档:${cyan('https://antdv.com')}`);
299
320
  console.log('');
300
321
  }
301
322
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qin-ui/antd-vue-pro",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "二次封装antd vue组件",
5
5
  "bin": {
6
6
  "antd-vue-pro": "./bin/init-ai.mjs"
@@ -42,11 +42,11 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "scroll-into-view-if-needed": "^3.1.0",
45
- "@qin-ui/pro-components-core": "^1.0.1"
45
+ "vue-component-type-helpers": "^3.2.5",
46
+ "@qin-ui/pro-components-core": "^1.0.3"
46
47
  },
47
48
  "devDependencies": {
48
- "ant-design-vue": "^4",
49
- "vue-component-type-helpers": "^3.2.5"
49
+ "ant-design-vue": "^4"
50
50
  },
51
51
  "sideEffects": [
52
52
  "**/*.css",