Skip to content

Commit 89965ec

Browse files
kagolgitee-org
authored andcommitted
!183 Form表单组件基础版
Merge pull request !183 from AlanLee/dev
2 parents 72b78ce + da2cf1b commit 89965ec

File tree

19 files changed

+2675
-6
lines changed

19 files changed

+2675
-6
lines changed

devui/form/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { App } from 'vue'
2+
import Form from './src/form'
3+
import FormLabel from './src/form-label/form-label';
4+
import FormItem from './src/form-item/form-item';
5+
import FormControl from './src/form-control/form-control';
6+
import FormOperation from './src/form-operation/form-operation';
7+
import dValidateRules from './src/directive/d-validate-rules';
8+
9+
Form.install = function(app: App) {
10+
app.component(Form.name, Form);
11+
app.directive('d-validate-rules', dValidateRules);
12+
}
13+
14+
FormLabel.install = function(app: App) {
15+
app.component(FormLabel.name, FormLabel)
16+
}
17+
18+
FormItem.install = function(app: App) {
19+
app.component(FormItem.name, FormItem)
20+
}
21+
22+
FormControl.install = function(app: App) {
23+
app.component(FormControl.name, FormControl)
24+
}
25+
26+
FormOperation.install = function(app: App) {
27+
app.component(FormOperation.name, FormOperation)
28+
}
29+
30+
export { Form, FormLabel, FormItem, FormControl, FormOperation }
31+
32+
export default {
33+
title: 'Form 表单',
34+
category: '数据录入',
35+
install(app: App): void {
36+
app.use(Form as any);
37+
app.use(FormLabel as any);
38+
app.use(FormItem as any);
39+
app.use(FormControl as any);
40+
app.use(FormOperation as any);
41+
}
42+
}
Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
import AsyncValidator from 'async-validator';
2+
import { VNode } from 'vue';
3+
import './style.scss';
4+
import { debounce } from 'lodash';
5+
import EventBus from '../util/event-bus';
6+
7+
// 获取async-validator可用的规则名
8+
function getAvaliableRuleObj(ruleName: string, value) {
9+
if(!ruleName) {
10+
console.error("[v-d-validate] validator's key is invalid");
11+
return null;
12+
}
13+
switch(ruleName) {
14+
case 'maxlength':
15+
return {
16+
type: 'string',
17+
max: value,
18+
asyncValidator: (rule, val) => {
19+
return new Promise((resolve, reject) => {
20+
if(val.length > value) {
21+
reject('最大长度为' + value);
22+
}else {
23+
resolve('校验通过');
24+
}
25+
})
26+
}
27+
};
28+
case 'minlength':
29+
return {
30+
type: 'string',
31+
min: value,
32+
asyncValidator: (rule, val) => {
33+
return new Promise((resolve, reject) => {
34+
if(val.length < value) {
35+
reject('最小长度为' + value);
36+
}else {
37+
resolve('校验通过');
38+
}
39+
})
40+
}
41+
};
42+
case 'min':
43+
return {
44+
type: 'number',
45+
asyncValidator: (rule, val) => {
46+
return new Promise((resolve, reject) => {
47+
if(val < value) {
48+
reject('最小值为' + value);
49+
}else {
50+
resolve('校验通过');
51+
}
52+
})
53+
}
54+
};
55+
case 'max':
56+
return {
57+
type: 'number',
58+
asyncValidator: (rule, val) => {
59+
return new Promise((resolve, reject) => {
60+
if(val > value) {
61+
reject('最大值为' + value);
62+
}else {
63+
resolve('校验通过');
64+
}
65+
})
66+
}
67+
};
68+
case 'required':
69+
return {
70+
reqiured: true,
71+
asyncValidator: (rule, val) => {
72+
return new Promise((resolve, reject) => {
73+
if(!val) {
74+
reject('必填项');
75+
}else {
76+
resolve('校验通过');
77+
}
78+
})
79+
}
80+
};
81+
case 'requiredTrue':
82+
return {
83+
asyncValidator: (rule, val) => {
84+
return new Promise((resolve, reject) => {
85+
if(!val) {
86+
reject('必须为true值');
87+
}else {
88+
resolve('校验通过');
89+
}
90+
})
91+
}
92+
};
93+
case 'email':
94+
return {
95+
type: 'email',
96+
message: '邮箱格式不正确'
97+
};
98+
case 'pattern':
99+
return {
100+
type: 'regexp',
101+
pattern: value,
102+
message: '只能包含数字与大小写字符',
103+
validator: (rule, val) => value.test(val),
104+
};
105+
case 'whitespace':
106+
return {
107+
message: '输入不能全部为空格或空字符',
108+
validator: (rule, val) => !!val.trim()
109+
};
110+
default:
111+
return {
112+
[ruleName]: value,
113+
};
114+
}
115+
}
116+
117+
function getKeyValueOfObjectList(obj): {key: string; value: any;}[] {
118+
const kvArr = [];
119+
for (const key in obj) {
120+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
121+
kvArr.push({
122+
key,
123+
value: obj[key]
124+
})
125+
}
126+
}
127+
return kvArr;
128+
}
129+
130+
131+
function isObject(obj): boolean {
132+
return Object.prototype.toString.call(obj).slice(8, -1) === 'Object';
133+
}
134+
135+
136+
function hasKey(obj, key): boolean {
137+
if (!isObject(obj)) return false;
138+
return Object.prototype.hasOwnProperty.call(obj, key);
139+
}
140+
141+
function handleErrorStrategy(el: HTMLElement): void {
142+
const classList: Array<string> = [...el.classList];
143+
classList.push('d-validate-rules-error-pristine');
144+
el.setAttribute('class', classList.join(' '));
145+
}
146+
147+
function handleErrorStrategyPass(el: HTMLElement): void {
148+
const classList: Array<string> = [...el.classList];
149+
const index = classList.indexOf('d-validate-rules-error-pristine');
150+
index !== -1 && classList.splice(index, 1);
151+
el.setAttribute('class', classList.join(' '));
152+
}
153+
154+
function handleValidateError(el: HTMLElement, tipEl: HTMLElement, message: string, isFormTag: boolean, messageShowType: string): void {
155+
// 如果该指令用在form标签上,这里做特殊处理
156+
if(isFormTag && messageShowType === 'toast') {
157+
// todo:待替换为toast
158+
alert(message);
159+
return;
160+
}
161+
162+
tipEl.innerText = '' + message;
163+
tipEl.style.display = 'inline-flex';
164+
tipEl.setAttribute('class', 'd-validate-tip');
165+
handleErrorStrategy(el);
166+
}
167+
168+
function handleValidatePass(el: HTMLElement, tipEl: HTMLElement): void {
169+
tipEl.style.display = 'none';
170+
handleErrorStrategyPass(el);
171+
}
172+
173+
// 获取表单name
174+
function getFormName(binding): string {
175+
const _refs = binding.instance.$refs;
176+
const key = Object.keys(_refs)[0];
177+
return _refs[key]['name'];
178+
}
179+
180+
// 校验处理函数
181+
function validateFn({validator, modelValue, el, tipEl, isFormTag, messageShowType}) {
182+
validator.validate({modelName: modelValue}).then(() => {
183+
handleValidatePass(el, tipEl);
184+
}).catch((err) => {
185+
const { errors } = err;
186+
if(!errors || errors.length === 0) return;
187+
let msg = '';
188+
189+
// todo: 待支持国际化
190+
if(typeof errors[0].message === 'object') {
191+
msg = errors[0].message.default;
192+
}else {
193+
msg = errors[0].message;
194+
}
195+
196+
handleValidateError(el, tipEl, msg, isFormTag, messageShowType);
197+
})
198+
}
199+
200+
export default {
201+
mounted(el: HTMLElement, binding: any, vnode: VNode): void {
202+
const isFormTag = el.tagName === 'FORM';
203+
204+
const hasOptions = isObject(binding.value) && hasKey(binding.value, 'options');
205+
const {rules: bindingRules, options = {}, messageShowType = 'popover'} = binding.value;
206+
isFormTag && console.log('messageShowType', messageShowType);
207+
208+
let {errorStrategy} = binding.value;
209+
// errorStrategy可配置在options对象中
210+
const { updateOn = 'change', errorStrategy: optionsErrorStrategy = 'dirty', asyncDebounceTime = 300} = options;
211+
212+
if(!errorStrategy) {
213+
errorStrategy = optionsErrorStrategy;
214+
}
215+
216+
// 判断是否有options,有就取binding.value对象中的rules对象,再判断有没有rules对象,没有就取binding.value
217+
const bindRules = hasOptions ? bindingRules : (bindingRules ? bindingRules : binding.value);
218+
219+
const isCustomValidator = bindRules && isObject(bindRules) && (hasKey(bindRules, 'validators') || hasKey(bindRules, 'asyncValidators'));
220+
221+
const rules = Array.isArray(bindRules) ? bindRules : [bindRules];
222+
const tipEl = document.createElement('span');
223+
224+
// messageShowType控制是否显示文字提示
225+
if(messageShowType !== 'none') {
226+
el.parentNode.append(tipEl);
227+
}
228+
229+
const descriptor = {
230+
modelName: []
231+
};
232+
233+
rules.forEach((rule) => {
234+
const kvObjList = !Array.isArray(rule) && getKeyValueOfObjectList(rule);
235+
let ruleObj = {};
236+
let avaliableRuleObj = {};
237+
kvObjList.forEach(item => {
238+
avaliableRuleObj = getAvaliableRuleObj(item.key, item.value);
239+
ruleObj = {...ruleObj, ...avaliableRuleObj};
240+
});
241+
descriptor.modelName.push(ruleObj);
242+
});
243+
244+
// 使用自定义的验证器
245+
if(isCustomValidator) {
246+
// descriptor.modelName = [];
247+
const {validators, asyncValidators} = bindRules;
248+
249+
// 校验器
250+
validators && validators.forEach(item => {
251+
const ruleObj = {
252+
message: item?.message || '',
253+
validator: (rule, value) => item.validator(rule, value),
254+
}
255+
descriptor.modelName.push(ruleObj);
256+
});
257+
258+
// 异步校验器
259+
asyncValidators && asyncValidators.forEach(item => {
260+
const ruleObj = {
261+
message: item?.message || '',
262+
asyncValidator: (rule, value, callback) => {
263+
return new Promise(debounce((resolve, reject) => {
264+
const res = item.asyncValidator(rule, value);
265+
if(res) {
266+
resolve('');
267+
}else {
268+
reject(rule.message);
269+
}
270+
}, asyncDebounceTime))
271+
},
272+
}
273+
descriptor.modelName.push(ruleObj);
274+
});
275+
}
276+
277+
// 校验器对象
278+
const validator = new AsyncValidator(descriptor);
279+
280+
const htmlEventValidateHandler = (e) => {
281+
const modelValue = e.target.value;
282+
validateFn({validator, modelValue, el, tipEl, isFormTag: false, messageShowType});
283+
}
284+
285+
// 监听事件验证
286+
vnode.children[0].el.addEventListener(updateOn, htmlEventValidateHandler);
287+
288+
// 设置errorStrategy
289+
if(errorStrategy === 'pristine') {
290+
handleErrorStrategy(el);
291+
// pristine为初始化验证,初始化时需改变下原始值才能出发验证
292+
vnode.children[0].props.value = '' + vnode.children[0].props.value;
293+
}
294+
295+
const formName = getFormName(binding);
296+
// 处理表单提交验证
297+
formName && EventBus.on(`formSubmit:${formName}`, () => {
298+
const modelValue = isFormTag ? '' : vnode.children[0].el.value;
299+
300+
// 进行提交验证
301+
validateFn({validator, modelValue, el, tipEl, isFormTag, messageShowType});
302+
});
303+
304+
}
305+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
.d-validate-rules-error-pristine {
3+
// background-color: #ffeeed;
4+
input {
5+
background-color: #ffeeed;
6+
border: 1px solid #f66f6a;
7+
8+
&:focus {
9+
border: 1px solid #f66f6a !important;
10+
}
11+
12+
&:hover {
13+
border: 1px solid #f66f6a !important;
14+
}
15+
}
16+
}
17+
18+
.d-validate-tip {
19+
display: flex;
20+
justify-content: center;
21+
align-items: center;
22+
font-size: 12px;
23+
color: #f66f6a;
24+
}

0 commit comments

Comments
 (0)