@xclqmc/base-form 1.0.0
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/package.json +50 -0
- package/src/components/BaseForm/BaseForm.vue +440 -0
- package/src/components/BaseForm/FormContent.vue +179 -0
- package/src/components/BaseForm/rules.ts +234 -0
- package/src/components/BaseForm/types.ts +292 -0
- package/src/components/base/BaseDrawer.vue +197 -0
- package/src/components/base/BaseEditor.vue +215 -0
- package/src/components/base/BaseInput.vue +47 -0
- package/src/components/base/BasePopup.vue +366 -0
- package/src/components/base/BaseRadio.vue +113 -0
- package/src/components/base/BaseSelect.vue +136 -0
- package/src/components/base/registry.ts +39 -0
- package/src/hooks/component.ts +128 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/message.ts +97 -0
- package/src/hooks/props.ts +50 -0
- package/src/index.ts +36 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseForm 字段校验:把 fields 上的声明式校验配置翻译成 element-plus 的 el-form 规则
|
|
3
|
+
* 支持:必填 / 简化 validator / 原生规则(含内置格式 type:phone/idCard/english、长度、数值范围、正则)
|
|
4
|
+
*/
|
|
5
|
+
import type { BaseFormField } from "./types";
|
|
6
|
+
|
|
7
|
+
/** 内置校验正则 */
|
|
8
|
+
export const PATTERNS: Record<string, RegExp> = {
|
|
9
|
+
/** 手机号:1 开头 + 3-9 + 9 位数字 */
|
|
10
|
+
phone: /^1[3-9]\d{9}$/,
|
|
11
|
+
/** 身份证号:15 位 或 18 位(末位可为 X/x) */
|
|
12
|
+
idCard: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
|
|
13
|
+
/** 邮箱 */
|
|
14
|
+
email: /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/,
|
|
15
|
+
/** 数值:整数或小数,可带负号 */
|
|
16
|
+
number: /^-?\d+(\.\d+)?$/,
|
|
17
|
+
/** 纯英文:只允许英文字母 A-Z / a-z(不含数字、空格与中文) */
|
|
18
|
+
english: /^[A-Za-z]+$/,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** 内置格式校验项的默认提示:以字段 label 为前缀,风格统一为「{label}手机不正确」「{label}的邮箱格式不正确」等 */
|
|
22
|
+
const builtInMessage = (key: string, label: string): string => {
|
|
23
|
+
switch (key) {
|
|
24
|
+
case "phone":
|
|
25
|
+
return `${label}手机格式不正确`;
|
|
26
|
+
case "idCard":
|
|
27
|
+
return `${label}身份证格式不正确`;
|
|
28
|
+
case "email":
|
|
29
|
+
return `${label}的邮箱格式不正确`;
|
|
30
|
+
case "number":
|
|
31
|
+
return `${label}数值不正确`;
|
|
32
|
+
case "english":
|
|
33
|
+
case "en":
|
|
34
|
+
return `${label}只能输入英文`;
|
|
35
|
+
default:
|
|
36
|
+
return `${label}格式不正确`;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** el-form 原生 `type` 校验的中文名(用于生成默认 message) */
|
|
41
|
+
const TYPE_MSG: Record<string, string> = {
|
|
42
|
+
email: "邮箱",
|
|
43
|
+
url: "链接",
|
|
44
|
+
date: "日期",
|
|
45
|
+
integer: "整数",
|
|
46
|
+
float: "浮点数",
|
|
47
|
+
number: "数字",
|
|
48
|
+
hex: "十六进制",
|
|
49
|
+
enum: "枚举",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/** 非 el-form 原生的内置格式 type(phone/idCard/number/english):el-form 不认识这些 type,
|
|
53
|
+
* rules 里写 `type: 'phone'` 时转成内置正则 pattern 校验,默认提示见 builtInMessage(如「{label}手机不正确」)。
|
|
54
|
+
* 这样 `rules: [{ type: 'phone' }]` 与字段级 `phone` 属性等价,方便网盘等模块统一用 rules 写法。
|
|
55
|
+
* `english` 为纯英文校验(只允许 A-Z/a-z),等价别名 `en`。 */
|
|
56
|
+
const NON_NATIVE_TYPE_PATTERNS: Record<string, RegExp> = {
|
|
57
|
+
phone: PATTERNS.phone,
|
|
58
|
+
idCard: PATTERNS.idCard,
|
|
59
|
+
number: PATTERNS.number,
|
|
60
|
+
english: PATTERNS.english,
|
|
61
|
+
en: PATTERNS.english,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** 校验触发时机:选项类字段(radio / select)用 change,其余用 blur */
|
|
65
|
+
const getTrigger = (field: BaseFormField) =>
|
|
66
|
+
field.type === "radio" || field.type === "select" ? "change" : "blur";
|
|
67
|
+
|
|
68
|
+
/** 值为空:非必填字段留空时跳过格式校验,交给 required 规则处理 */
|
|
69
|
+
const isBlank = (value: any) =>
|
|
70
|
+
value === null ||
|
|
71
|
+
value === undefined ||
|
|
72
|
+
value === "" ||
|
|
73
|
+
(Array.isArray(value) && value.length === 0);
|
|
74
|
+
|
|
75
|
+
/** 是否必填:支持布尔与 (formData) => boolean 函数 */
|
|
76
|
+
const getRequired = (
|
|
77
|
+
field: BaseFormField,
|
|
78
|
+
getForm: () => Record<string, any>,
|
|
79
|
+
) =>
|
|
80
|
+
typeof field.required === "function"
|
|
81
|
+
? !!field.required(getForm())
|
|
82
|
+
: !!field.required;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 构建单个字段的校验规则数组
|
|
86
|
+
* @param field 字段配置
|
|
87
|
+
* @param getForm 取当前表单值的函数,作为自定义校验 validator 的第二个入参
|
|
88
|
+
*/
|
|
89
|
+
export function buildFieldRules(
|
|
90
|
+
field: BaseFormField,
|
|
91
|
+
getForm: () => Record<string, any>,
|
|
92
|
+
): any[] {
|
|
93
|
+
const rules: any[] = [];
|
|
94
|
+
const trigger = getTrigger(field);
|
|
95
|
+
|
|
96
|
+
// 必填:用原生 required 以保留 el-form-item 的红色星号;默认提示「{label}必填项」
|
|
97
|
+
if (getRequired(field, getForm)) {
|
|
98
|
+
rules.push({ required: true, message: `${field.label}必填项`, trigger });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 原生规则:直接追加在最后,用于复杂场景。
|
|
102
|
+
// 其中 validator 采用简化契约:(formData) => any,
|
|
103
|
+
// 返回空(undefined/null/'')或布尔 = 通过;返回非空字符串 = 错误提示。
|
|
104
|
+
// 其余属性(required/min/max/pattern/type/message/trigger 等)保持原生 el-form 写法。
|
|
105
|
+
if (field.rules?.length) {
|
|
106
|
+
field.rules.forEach((rule: any) => {
|
|
107
|
+
if (typeof rule.validator === "function") {
|
|
108
|
+
const simpleValidator = rule.validator;
|
|
109
|
+
rules.push({
|
|
110
|
+
...rule,
|
|
111
|
+
// trigger 缺省统一为 blur
|
|
112
|
+
trigger: rule.trigger ?? "blur",
|
|
113
|
+
validator: (_r: any, _v: any, callback: any) => {
|
|
114
|
+
const res = simpleValidator(getForm());
|
|
115
|
+
if (res == null || res === "" || typeof res === "boolean")
|
|
116
|
+
return callback();
|
|
117
|
+
callback(new Error(String(res)));
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
// 原生规则:缺省 trigger = blur;message 按常见类型自动补全,避免每条都手写
|
|
122
|
+
const filled = { ...rule };
|
|
123
|
+
if (filled.trigger == null) filled.trigger = "blur";
|
|
124
|
+
|
|
125
|
+
// 非 el-form 原生的内置格式(phone/idCard/number):el-form 不认识 type,转成内置正则 pattern 校验
|
|
126
|
+
if (filled.type && NON_NATIVE_TYPE_PATTERNS[filled.type]) {
|
|
127
|
+
const regex = NON_NATIVE_TYPE_PATTERNS[filled.type];
|
|
128
|
+
rules.push({
|
|
129
|
+
...filled,
|
|
130
|
+
pattern: regex,
|
|
131
|
+
type: undefined,
|
|
132
|
+
message: filled.message ?? builtInMessage(filled.type, field.label),
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 长度/数值范围走自定义 validator,自动提示在各自分支里生成,此处不抢先补 message
|
|
138
|
+
const hasLength = filled.minLength != null || filled.maxLength != null;
|
|
139
|
+
const hasRange = filled.min != null || filled.max != null;
|
|
140
|
+
|
|
141
|
+
if (filled.message == null && !hasLength && !hasRange) {
|
|
142
|
+
if (filled.required) {
|
|
143
|
+
filled.message = `${field.label}必填项`;
|
|
144
|
+
} else if (filled.type) {
|
|
145
|
+
const name = TYPE_MSG[filled.type] ?? filled.type;
|
|
146
|
+
filled.message = `${field.label}的${name}格式不正确`;
|
|
147
|
+
} else if (filled.pattern) {
|
|
148
|
+
// 自定义正则:缺省提示为「{label}格式不正确」
|
|
149
|
+
filled.message = `${field.label}格式不正确`;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 长度校验(minLength/maxLength):el-form 原生无此键(原生只有 len/min/max),
|
|
154
|
+
// 直接透传会被 async-validator 静默忽略,故转成自定义 validator 按字符数校验。
|
|
155
|
+
// 空值跳过(交给 required 规则处理),与其余内置校验行为一致。
|
|
156
|
+
if (hasLength) {
|
|
157
|
+
const min: number | null = filled.minLength ?? null;
|
|
158
|
+
const max: number | null = filled.maxLength ?? null;
|
|
159
|
+
delete filled.minLength;
|
|
160
|
+
delete filled.maxLength;
|
|
161
|
+
const message =
|
|
162
|
+
rule.message ??
|
|
163
|
+
(min != null && max != null
|
|
164
|
+
? `${field.label}长度需在 ${min}~${max} 之间`
|
|
165
|
+
: min != null
|
|
166
|
+
? `${field.label}长度不能少于 ${min} 个字符`
|
|
167
|
+
: `${field.label}长度不能超过 ${max} 个字符`);
|
|
168
|
+
rules.push({
|
|
169
|
+
...filled,
|
|
170
|
+
message,
|
|
171
|
+
validator: (_r: any, value: any, callback: any) => {
|
|
172
|
+
if (isBlank(value)) return callback();
|
|
173
|
+
const len = String(value).length;
|
|
174
|
+
if (min != null && len < min) return callback(new Error(message));
|
|
175
|
+
if (max != null && len > max) return callback(new Error(message));
|
|
176
|
+
callback();
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 数值范围(min/max):el-form 原生对字符串按「长度」处理,与本项目约定(min/max=数值范围)
|
|
183
|
+
// 不一致,故同样转成自定义 validator,按 Number(value) 比较
|
|
184
|
+
if (hasRange) {
|
|
185
|
+
const min: number | null = filled.min ?? null;
|
|
186
|
+
const max: number | null = filled.max ?? null;
|
|
187
|
+
delete filled.min;
|
|
188
|
+
delete filled.max;
|
|
189
|
+
const message =
|
|
190
|
+
rule.message ??
|
|
191
|
+
(min != null && max != null
|
|
192
|
+
? `${field.label}数值需在 ${min}~${max} 之间`
|
|
193
|
+
: min != null
|
|
194
|
+
? `${field.label}数值不能小于 ${min}`
|
|
195
|
+
: `${field.label}数值不能大于 ${max}`);
|
|
196
|
+
rules.push({
|
|
197
|
+
...filled,
|
|
198
|
+
message,
|
|
199
|
+
validator: (_r: any, value: any, callback: any) => {
|
|
200
|
+
if (isBlank(value)) return callback();
|
|
201
|
+
const num = Number(value);
|
|
202
|
+
if (Number.isNaN(num))
|
|
203
|
+
return callback(new Error(`${field.label}数值不正确`));
|
|
204
|
+
if (min != null && num < min) return callback(new Error(message));
|
|
205
|
+
if (max != null && num > max) return callback(new Error(message));
|
|
206
|
+
callback();
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
rules.push(filled);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return rules;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 构建整个表单的 rules(key 为字段 value,值为规则数组)
|
|
222
|
+
* @param fields 字段列表
|
|
223
|
+
* @param getForm 取当前表单值的函数
|
|
224
|
+
*/
|
|
225
|
+
export function buildRules(
|
|
226
|
+
fields: BaseFormField[] = [],
|
|
227
|
+
getForm: () => Record<string, any>,
|
|
228
|
+
): Record<string, any[]> {
|
|
229
|
+
return fields.reduce((prev: Record<string, any[]>, field: BaseFormField) => {
|
|
230
|
+
const rules = buildFieldRules(field, getForm);
|
|
231
|
+
if (rules.length) prev[field.value] = rules;
|
|
232
|
+
return prev;
|
|
233
|
+
}, {});
|
|
234
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseForm 相关类型定义(与组件 index.vue 分离,仅放类型)
|
|
3
|
+
*/
|
|
4
|
+
import type { Component } from "vue";
|
|
5
|
+
|
|
6
|
+
/** 表单字段配置(属性按使用频率/重要程度排列:核心 value/label/type → 显隐/占位/默认值 → 校验 → 渲染/布局 → 回调) */
|
|
7
|
+
export interface BaseFormField {
|
|
8
|
+
/** 字段名(v-model 绑定的 key,必填) */
|
|
9
|
+
value: string;
|
|
10
|
+
/** 字段标签(表单左侧显示,必填) */
|
|
11
|
+
label: string;
|
|
12
|
+
/**
|
|
13
|
+
* 字段级标签宽度:存在时优先级高于 config.labelWidth(仅作用于本字段的 el-form-item)。
|
|
14
|
+
* 常用于上传/图片类控件想顶头左对齐(如 '0px'),避免被统一标签宽留白。
|
|
15
|
+
*/
|
|
16
|
+
labelWidth?: string | null;
|
|
17
|
+
/**
|
|
18
|
+
* 字段类型(渲染组件):
|
|
19
|
+
* - 内置:input=输入框;radio=单选组;select=下拉框;editor=富文本(wangEditor)
|
|
20
|
+
* - 自定义:先经 registerField(type, component) 注入组件,
|
|
21
|
+
* 再在字段里引用该 type;注入的 type 与内置重复时以注入为准(覆盖内置渲染)
|
|
22
|
+
*/
|
|
23
|
+
type: string;
|
|
24
|
+
/**
|
|
25
|
+
* 是否显示该字段,缺省 true。用于按条件动态显隐。
|
|
26
|
+
* - 布尔值:true 显示 / false 移除 DOM(布局重排、回收空间)
|
|
27
|
+
* - 函数:(formData) => boolean,入参为当前表单值,返回 true 显示、false 移除
|
|
28
|
+
* 注意:show=false 是「移除 DOM」,会回收布局空间;若想保留布局空间仅隐藏,请用 hidden
|
|
29
|
+
*/
|
|
30
|
+
show?: boolean | ((formData: any) => boolean);
|
|
31
|
+
/**
|
|
32
|
+
* 是否隐藏但保留布局空间,缺省 false。
|
|
33
|
+
* - 布尔值:true 隐藏 / false 显示(默认)
|
|
34
|
+
* - 函数:(formData) => boolean,入参为当前表单值,返回 true 隐藏
|
|
35
|
+
* 与 show 的区别:show=false 会「移除 DOM」(空间回收、布局重排);hidden=true 仅「不可见」,
|
|
36
|
+
* DOM 仍在、仍占据布局空间(visibility:hidden),常用于占位对齐或值由程序填充的隐藏字段
|
|
37
|
+
*/
|
|
38
|
+
hidden?: boolean | ((formData: any) => boolean);
|
|
39
|
+
/** 占位提示文案 */
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
/** 默认值(新增时回填;编辑时被详情覆盖) */
|
|
42
|
+
defaultValue?: any;
|
|
43
|
+
/**
|
|
44
|
+
* 是否必填,缺省 false。
|
|
45
|
+
* - 布尔值:true 必填 / false 非必填
|
|
46
|
+
* - 函数:(formData) => boolean,入参为当前表单值,返回 true 必填
|
|
47
|
+
* 校验失败提示取 placeholder,缺省"不能为空"
|
|
48
|
+
*/
|
|
49
|
+
required?: boolean | ((formData: any) => boolean);
|
|
50
|
+
/**
|
|
51
|
+
* 校验规则数组(原生 el-form 规则写法 + 本库扩展),追加在内置必填规则之后;复杂校验统一写在这里。
|
|
52
|
+
* 支持:原生 required/pattern/type/message/trigger、简化 validator((formData)=>提示)、
|
|
53
|
+
* 长度 minLength/maxLength、数值范围 min/max、内置格式 type(phone/idCard/english)
|
|
54
|
+
*/
|
|
55
|
+
rules?: BaseFormRule[] | null;
|
|
56
|
+
/**
|
|
57
|
+
* 是否禁用该字段。
|
|
58
|
+
* - 布尔值:true 禁用 / false 不禁用(默认 false)
|
|
59
|
+
* - 函数:(formData) => boolean,入参为当前表单值,返回 true 禁用、false 不禁用
|
|
60
|
+
*/
|
|
61
|
+
disabled?: boolean | ((formData: any) => boolean);
|
|
62
|
+
/**
|
|
63
|
+
* 字段渲染格式:
|
|
64
|
+
* - `input`:字符串,输入框类型(如 `'textarea'` 多行 / `'number'` 数字框)
|
|
65
|
+
* - `radio` / `select`:选项来源,三选一:
|
|
66
|
+
* ① 字符串 = 字典 code,由字段控件经 BaseForm 注入的 `api.dict(code)` 拉取选项
|
|
67
|
+
* ② 数组 = 直接注入的选项数组
|
|
68
|
+
* ③ 函数 `(formData) => 数组`(支持 Promise),入参为当前表单值,便于选项依赖其它字段(联动)
|
|
69
|
+
*/
|
|
70
|
+
format?:
|
|
71
|
+
| string
|
|
72
|
+
| any[]
|
|
73
|
+
| ((formData: any) => Promise<any> | any[] | string)
|
|
74
|
+
| null;
|
|
75
|
+
/**
|
|
76
|
+
* 字段级组件入参:整体透传给该字段渲染的组件(`input`→`el-input`、`radio`→`BaseRadio`、`select`→`BaseSelect`、`editor`→`BaseEditor`)。
|
|
77
|
+
* 支持两种写法:**对象**(静态)或 **函数 `(formData) => 配置对象`**(按当前表单值动态算,如数量/参数依赖其它字段)。
|
|
78
|
+
* `type='select'` 常用项:label(选项文案的键名,默认 'label')/ value(选项值的键名,默认 'value')
|
|
79
|
+
* / multiple(是否多选)/ disabledName(选项禁用标记的键名,默认 'disabled')/ maxCollapseTags(多选折叠标签数)
|
|
80
|
+
*/
|
|
81
|
+
params?:
|
|
82
|
+
| Record<string, any>
|
|
83
|
+
| ((formData: any) => Record<string, any>)
|
|
84
|
+
| null;
|
|
85
|
+
/** 表单项栅格 span(24=整行,缺省见 config.span) */
|
|
86
|
+
span?: number | null;
|
|
87
|
+
/** 表单项下边距(缺省见 config.marginBottom) */
|
|
88
|
+
marginBottom?: string | null;
|
|
89
|
+
/**
|
|
90
|
+
* 字段点击回调(所有类型均生效)。入参为原生 click 事件对象。
|
|
91
|
+
* 常用于只读字段点击触发选择器弹窗等场景。
|
|
92
|
+
*/
|
|
93
|
+
click?: ((event: any) => void) | null;
|
|
94
|
+
/**
|
|
95
|
+
* 字段值变化回调(所有类型均生效)。入参为变化后的新值。
|
|
96
|
+
* - input:输入框 change 事件(失焦/回车时触发),值为输入值
|
|
97
|
+
* - radio:单选组 change 事件,值为选中项的值
|
|
98
|
+
* - select:下拉框 change 事件,值为选中项的值(多选时为数组)
|
|
99
|
+
* - editor:内容变化即触发,值为最新 HTML
|
|
100
|
+
* 需要在回调里改动其它字段时,直接读写父组件的 `formData`(即 v-model:form 绑定的对象)
|
|
101
|
+
*/
|
|
102
|
+
change?: ((value: any) => void) | null;
|
|
103
|
+
/**
|
|
104
|
+
* 联动依赖字段:当这些字段的值变化时,会重新获取本字段的选项列表(仅 radio / select 生效)并触发 onWatch。
|
|
105
|
+
* 字符串表示单个字段,数组表示多个字段。常用于「下拉框选项依赖另一字段」场景
|
|
106
|
+
*/
|
|
107
|
+
watch?: string | string[] | null;
|
|
108
|
+
/**
|
|
109
|
+
* 联动回调:当 watch 中任一依赖字段变化时触发。
|
|
110
|
+
* 入参为 (formData, changedKey):formData 为当前表单值,changedKey 为发生变化的字段名。
|
|
111
|
+
* 典型用法:依赖变化后清空/重置本字段值,如 formData[this.value] = ''
|
|
112
|
+
*/
|
|
113
|
+
onWatch?: ((formData: any, changedKey?: string) => void) | null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** 字段级校验规则:原生 el-form 规则写法 + 本库扩展(简化 validator / 长度 / 数值范围 / 内置格式 type) */
|
|
117
|
+
export interface BaseFormRule {
|
|
118
|
+
/** 是否必填 */
|
|
119
|
+
required?: boolean;
|
|
120
|
+
/** 校验失败提示 */
|
|
121
|
+
message?: string;
|
|
122
|
+
/** 触发时机,缺省 blur */
|
|
123
|
+
trigger?: "blur" | "change" | ("blur" | "change")[];
|
|
124
|
+
/** 自定义正则 */
|
|
125
|
+
pattern?: RegExp;
|
|
126
|
+
/**
|
|
127
|
+
* 格式类型:
|
|
128
|
+
* - el-form 原生:email / url / date / integer / float / number / hex / enum
|
|
129
|
+
* - 本库扩展(el-form 不认识,自动转内置正则校验):phone=手机号 / idCard=身份证号 / english=纯英文(别名 en)
|
|
130
|
+
*/
|
|
131
|
+
type?: string;
|
|
132
|
+
/** 数值范围下限(本库按数值大小比较,非字符串长度) */
|
|
133
|
+
min?: number;
|
|
134
|
+
/** 数值范围上限 */
|
|
135
|
+
max?: number;
|
|
136
|
+
/** 长度下限(本库扩展,按字符数校验;el-form 原生无此键) */
|
|
137
|
+
minLength?: number;
|
|
138
|
+
/** 长度上限(本库扩展) */
|
|
139
|
+
maxLength?: number;
|
|
140
|
+
/** 简化校验器:(formData) => any。返回空(undefined/null/'')或布尔 = 通过;返回非空字符串 = 错误提示 */
|
|
141
|
+
validator?: (formData: any) => any;
|
|
142
|
+
/** 其余原生 el-form 规则键原样透传 */
|
|
143
|
+
[key: string]: any;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 表单接口配置(get=详情 / add=新增 / put=修改,按 CRUD 自然顺序;dict=字典) */
|
|
147
|
+
export interface BaseFormApi {
|
|
148
|
+
/** 详情接口:可返回 axios 响应({ data })或直接返回数据本体 */
|
|
149
|
+
get?: ((id: any) => Promise<any>) | null;
|
|
150
|
+
/** 新增接口 */
|
|
151
|
+
add?: ((data: any) => Promise<any>) | null;
|
|
152
|
+
/** 修改接口 */
|
|
153
|
+
put?: ((data: any) => Promise<any>) | null;
|
|
154
|
+
/** 字典接口:radio / select 的 format 为字符串(字典 code)时,字段控件经 BaseForm 注入按 code 拉取选项列表 */
|
|
155
|
+
dict?: ((code: string) => Promise<any[]>) | null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 弹窗/表单全局配置(属性按使用频率/重要程度排列:主键/标签宽 → 形态/尺寸 → 标题/高度 → 布局 → 抽屉方向) */
|
|
159
|
+
export interface BaseFormConfig {
|
|
160
|
+
/** 实体主键字段名(默认 id)。openDialog 据此包裹主键、submit 据此判定新增/编辑 */
|
|
161
|
+
id?: string;
|
|
162
|
+
/** 标签宽度 */
|
|
163
|
+
labelWidth?: string;
|
|
164
|
+
/**
|
|
165
|
+
* 显示形态(统一入口,三选一):
|
|
166
|
+
* - 'dialog' = 弹窗(居中弹窗,默认)
|
|
167
|
+
* - 'drawer' = 抽屉(右侧滑出,用 BaseDrawer 渲染)
|
|
168
|
+
* - 'form' = 原始表单(表单直接内嵌在父组件中,不包裹弹窗/抽屉)
|
|
169
|
+
*/
|
|
170
|
+
mode?: "dialog" | "drawer" | "form";
|
|
171
|
+
/** 弹窗宽度(mode 为 dialog/drawer 时生效),缺省 600px */
|
|
172
|
+
width?: string;
|
|
173
|
+
/**
|
|
174
|
+
* 弹窗标题:
|
|
175
|
+
* - 省略 → 按主键(id)判断:存在值=「编辑」,无值=「新增」
|
|
176
|
+
* - 字符串 → 直接使用
|
|
177
|
+
* - 函数 (formData) => string → 以表单值为入参动态生成
|
|
178
|
+
*/
|
|
179
|
+
title?: string | ((formData: any) => string) | null;
|
|
180
|
+
/** 弹窗高度(mode 为 dialog/drawer 时生效),缺省 null(不限制,由内容自适应)。传入如 '70vh' / '500px' 则固定弹窗高度、内容区滚动 */
|
|
181
|
+
height?: string | null;
|
|
182
|
+
/** 表单项栅格 span(24=整行) */
|
|
183
|
+
span?: number;
|
|
184
|
+
/** 表单项下边距(最后一行自动去除) */
|
|
185
|
+
marginBottom?: string;
|
|
186
|
+
/**
|
|
187
|
+
* 抽屉方向(保留备用)。当前 drawer 使用 BaseDrawer(仅右侧滑出),direction 暂不生效
|
|
188
|
+
*/
|
|
189
|
+
direction?: "rtl" | "ltr" | "ttb" | "btt";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 打开弹窗钩子:返回数据则作为表单值回填(覆盖默认空表单),不返回则保持默认 */
|
|
193
|
+
export type BaseFormOpen = (row: any) => any | Promise<any>;
|
|
194
|
+
|
|
195
|
+
/** 子组件注册项:component 为组件本身,ref 为别名(通过 getComponentRef(ref) 获取实例) */
|
|
196
|
+
export interface BaseFormComponent {
|
|
197
|
+
component: Component;
|
|
198
|
+
ref: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** 提交钩子:提供后覆盖内置提交逻辑(自行调 add/put、自定义提示),BaseForm 仍负责关闭弹窗与刷新列表 */
|
|
202
|
+
export type BaseFormSubmit = (formData: any) => any | Promise<any>;
|
|
203
|
+
|
|
204
|
+
/** 事件钩子集合:open / submit / beforeClose / close 归类于此,避免散落在 options 顶层 */
|
|
205
|
+
export interface BaseFormEvent {
|
|
206
|
+
/** 打开弹窗执行代码:返回表单值则直接回填 */
|
|
207
|
+
open?: BaseFormOpen | null;
|
|
208
|
+
/** 提交执行代码:提供后覆盖内置 submit */
|
|
209
|
+
submit?: BaseFormSubmit | null;
|
|
210
|
+
/** 关闭之前回调:关闭弹窗/抽屉前触发(X / 取消 / ESC / 提交成功后关闭均生效) */
|
|
211
|
+
beforeClose?: (() => void) | null;
|
|
212
|
+
/** 关闭回调:关闭完成后触发(与组件 @close 事件同时机) */
|
|
213
|
+
close?: (() => void) | null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** BaseForm options 配置(属性按轻重排列:轻量配置 config/api 在上,内容最多的 fields 在下,可选扩展 components/event 在底部) */
|
|
217
|
+
export interface BaseFormOptions {
|
|
218
|
+
/** 弹窗/表单全局配置(含显示形态 mode、主键 id、标题 title、宽高 width/height 等) */
|
|
219
|
+
config?: BaseFormConfig;
|
|
220
|
+
/** 接口(详情/新增/编辑) */
|
|
221
|
+
api?: BaseFormApi;
|
|
222
|
+
/** 字段列表(内容最多,置于底部) */
|
|
223
|
+
fields?: BaseFormField[];
|
|
224
|
+
/**
|
|
225
|
+
* 子组件列表:注册到组件内,可通过 `getComponentRef(ref)` 获取实例并调用其方法。
|
|
226
|
+
* 形如 [{ component: Permission, ref: 'permissionRef' }],常用于表单内嵌关联的弹窗(如授权)。
|
|
227
|
+
*/
|
|
228
|
+
components?: BaseFormComponent[];
|
|
229
|
+
/** 事件钩子集合:open(打开弹窗,返回表单值则直接回填)/ submit(覆盖内置提交逻辑) */
|
|
230
|
+
event?: BaseFormEvent;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** 默认 options 配置:穷举全部属性(无值的一律写 null),宿主传值后同键覆盖 */
|
|
234
|
+
export const defaultFormOptions: BaseFormOptions = {
|
|
235
|
+
config: {
|
|
236
|
+
id: "id",
|
|
237
|
+
labelWidth: "75px",
|
|
238
|
+
mode: "dialog",
|
|
239
|
+
width: "600px",
|
|
240
|
+
title: null,
|
|
241
|
+
height: null,
|
|
242
|
+
span: 24,
|
|
243
|
+
marginBottom: "17px",
|
|
244
|
+
direction: "rtl",
|
|
245
|
+
},
|
|
246
|
+
api: {
|
|
247
|
+
get: null,
|
|
248
|
+
add: null,
|
|
249
|
+
put: null,
|
|
250
|
+
dict: null,
|
|
251
|
+
},
|
|
252
|
+
fields: [
|
|
253
|
+
{
|
|
254
|
+
value: "",
|
|
255
|
+
label: "",
|
|
256
|
+
labelWidth: null,
|
|
257
|
+
type: "input",
|
|
258
|
+
show: true,
|
|
259
|
+
hidden: false,
|
|
260
|
+
placeholder: "请输入值",
|
|
261
|
+
defaultValue: null,
|
|
262
|
+
required: false,
|
|
263
|
+
rules: null,
|
|
264
|
+
disabled: false,
|
|
265
|
+
format: null,
|
|
266
|
+
params: null,
|
|
267
|
+
span: null,
|
|
268
|
+
marginBottom: null,
|
|
269
|
+
click: null,
|
|
270
|
+
change: null,
|
|
271
|
+
watch: null,
|
|
272
|
+
onWatch: null,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
components: [],
|
|
276
|
+
event: {
|
|
277
|
+
open: null,
|
|
278
|
+
submit: null,
|
|
279
|
+
beforeClose: null,
|
|
280
|
+
close: null,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 设置全局默认 options:入参为局部配置,config / api / event 逐键合并进默认值,
|
|
286
|
+
* 在应用入口调用一次即可对所有 BaseForm 生效;表单级 options 传值仍优先于全局默认
|
|
287
|
+
*/
|
|
288
|
+
export function setDefaultFormOptions(partial: BaseFormOptions = {}) {
|
|
289
|
+
if (partial.config) Object.assign(defaultFormOptions.config!, partial.config);
|
|
290
|
+
if (partial.api) Object.assign(defaultFormOptions.api!, partial.api);
|
|
291
|
+
if (partial.event) Object.assign(defaultFormOptions.event!, partial.event);
|
|
292
|
+
}
|