@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 +258 -157
- package/README.md +218 -6
- package/api.json +101 -1
- package/bin/init-ai.mjs +118 -97
- package/package.json +4 -4
package/AI-CONTEXT.md
CHANGED
|
@@ -1,150 +1,194 @@
|
|
|
1
|
-
# @qin-ui/antd-vue-pro
|
|
1
|
+
# @qin-ui/antd-vue-pro
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
+
**1. 配置驱动**:`useForm(数据, Field[])` / `useTable({ columns, searchFields })` 生成实例,组件接收实例。
|
|
50
21
|
|
|
51
|
-
|
|
22
|
+
**2. 属性分层透传**(最关键):Field 上的属性被"剥离到不同 DOM 层",不是全塞给输入控件。写错层就失效:
|
|
52
23
|
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
`span`, `order`, `offset`, `push`, `pull`, `flex`, `xs`, `sm`, `md`, `lg`, `xl`, `xxl`
|
|
41
|
+
> 写 Field 中输入控件的具体属性前,**属性名/类型以 https://antdv.com 官方文档为准**。
|
|
69
42
|
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
+
> `modelProp` 控制 v-model 绑定名,默认 `'value'`->`v-model:value`。
|
|
79
60
|
|
|
80
|
-
|
|
61
|
+
## useForm -- 表单实例
|
|
81
62
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
## ProForm 最小示例
|
|
91
92
|
|
|
92
|
-
```
|
|
93
|
+
```vue
|
|
94
|
+
<script setup lang="ts">
|
|
93
95
|
import { ProForm, useForm } from '@qin-ui/antd-vue-pro';
|
|
94
|
-
|
|
95
|
-
|
|
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: '
|
|
105
|
-
component: '
|
|
106
|
-
label: '
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
<ProForm :form="form" />
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Level 2: Field linkage
|
|
130
|
+
## 自定义组件(4 种方式)
|
|
118
131
|
|
|
119
|
-
|
|
132
|
+
`Field.component` 的解析优先级(高 -> 低):
|
|
133
|
+
`teleport 插槽注入` > `ProComponentProvider.componentMap` > `内置 componentMap` > `原始 component 值`
|
|
120
134
|
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
const isDisabled = ref(false);
|
|
137
|
+
`component` 直接传组件对象,**必须用 `markRaw` 包裹**。
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
145
|
+
> ⚠️ **必须 markRaw**:不包会被 Vue 当成响应式对象深度代理,触发性能警告甚至渲染异常。
|
|
146
|
+
> 类型上 `component` 接受 `RenderComponentType | Raw<RenderComponentType>`,`Raw<T>` 即 markRaw 的类型标记。
|
|
147
|
+
> 此字段不支持响应式(不能包 ref/computed)。
|
|
134
148
|
|
|
135
|
-
###
|
|
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
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
176
|
+
**追加全局声明获取强类型补全**(项目任意 `.d.ts`):
|
|
146
177
|
|
|
147
|
-
```
|
|
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
|
-
|
|
201
|
+
- 单字段复用一个 SFC -> 方式 1(markRaw(SFC))
|
|
202
|
+
- 单字段、需动态拼装 props -> 方式 2(render 函数)
|
|
203
|
+
- 多处复用 / 替换内置组件 -> 方式 3(componentMap + 声明扩充)
|
|
204
|
+
- 简单声明式替换、不想写 h() -> 方式 4(scoped slot)
|
|
158
205
|
|
|
159
|
-
|
|
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
|
-
##
|
|
212
|
+
## valueFormatter -- 字段值转换
|
|
168
213
|
|
|
169
|
-
|
|
214
|
+
控制表单值与组件值之间的转换(如日期 dayjs 与字符串互转)。**在 onUpdate:前执行**,支持两种形态:
|
|
170
215
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
228
|
+
## useTable -- 表格实例
|
|
176
229
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
243
|
+
返回 `Table` 实例:
|
|
190
244
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
257
|
+
**关键:ProTable 透传所有 ant-design-vue TableProps**(`& TableProps`),可直接写 `row-key` / `bordered` / `scroll` / `row-selection` 等原生属性;`size`、`loading` 是 v-model。
|
|
208
258
|
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
215
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
227
|
-
|
|
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
|
-
##
|
|
334
|
+
## 按需深查
|
|
233
335
|
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
*
|
|
7
|
-
* 1.
|
|
8
|
-
* 2.
|
|
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
|
-
* 生成
|
|
71
|
-
*
|
|
94
|
+
* 生成 AGENTS.md 区块内容
|
|
95
|
+
* = AI-CONTEXT.md 全文(核心使用规则,AI 必然读到)
|
|
72
96
|
*/
|
|
73
|
-
function
|
|
74
|
-
|
|
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
|
-
*
|
|
105
|
-
*
|
|
102
|
+
* 智能合并 AGENTS.md:
|
|
103
|
+
* - 文件不存在 -> 创建,内容 = 标记区块
|
|
104
|
+
* - 已有本库区块 -> 幂等替换区块内容(支持升级后更新)
|
|
105
|
+
* - 已有其他内容但无本库区块 -> 末尾追加区块(多库共存不冲突)
|
|
106
106
|
*/
|
|
107
|
-
function
|
|
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
|
-
|
|
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
|
|
250
|
+
${bold(`${PKG_NAME} CLI - AI 上下文初始化`)}
|
|
220
251
|
|
|
221
252
|
${bold('用法:')}
|
|
222
253
|
npx ${PKG_NAME} init-ai
|
|
223
254
|
|
|
224
255
|
${bold('说明:')}
|
|
225
|
-
|
|
256
|
+
在项目中生成双载体 AI 上下文,使 AI 工具能够深度理解和规范使用 ${PKG_NAME}。
|
|
226
257
|
|
|
227
258
|
生成的文件结构:
|
|
259
|
+
AGENTS.md 核心使用规则(跨工具自动注入)
|
|
228
260
|
.agents/
|
|
229
|
-
|
|
230
|
-
${PKG_SHORT}.md
|
|
231
|
-
|
|
232
|
-
|
|
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}
|
|
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. 生成
|
|
261
|
-
console.log(bold('📋
|
|
262
|
-
|
|
263
|
-
|
|
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. 生成
|
|
272
|
-
console.log(bold('📖
|
|
273
|
-
const apiDocContent = getApiDocContent();
|
|
299
|
+
// 2. 生成 Skills 参考(按需调取)
|
|
300
|
+
console.log(bold('📖 参考载体:.agents/skills/(按需调取)'));
|
|
274
301
|
writeFileWithLog(
|
|
275
|
-
path.join(process.cwd(), '.agents/
|
|
276
|
-
|
|
277
|
-
`.agents/
|
|
302
|
+
path.join(process.cwd(), '.agents/skills', `${PKG_SHORT}.md`),
|
|
303
|
+
getSkillContent(),
|
|
304
|
+
`.agents/skills/${PKG_SHORT}.md`
|
|
278
305
|
);
|
|
279
|
-
console.log(dim('
|
|
306
|
+
console.log(dim(' -> 完整 README + 结构化 API 元数据,skills 工具按需调取'));
|
|
280
307
|
console.log('');
|
|
281
308
|
|
|
282
309
|
// 3. 完成提示
|
|
283
|
-
console.log(green('✅
|
|
310
|
+
console.log(green('✅ 完成!已生成双载体 AI 上下文文件。'));
|
|
284
311
|
console.log('');
|
|
285
312
|
console.log(`${bold('📂 生成的文件:')}`);
|
|
286
|
-
console.log(` ${cyan('.
|
|
287
|
-
console.log(`
|
|
288
|
-
console.log(`
|
|
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(
|
|
294
|
-
console.log(`
|
|
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.
|
|
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
|
-
"
|
|
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",
|