diff --git a/example/docs/zh-CN/components/form.md b/example/docs/zh-CN/components/form.md index f87c90eb..3414d9d6 100644 --- a/example/docs/zh-CN/components/form.md +++ b/example/docs/zh-CN/components/form.md @@ -4,37 +4,62 @@ ::: demo - - + + - - + + + + + + + + + + + + 写作 + 画画 + 编码 + + + - 提交 + 提交 + +::: + +::: title 校验规则 - 通过表单配置 +::: + +::: demo + + + + + + + + + + + + + + + + + + + + 提交 + + + + + + +::: + +::: title 校验规则 - 通过表单子项配置 +::: + +::: demo + + + + + + + + + + + + + + + + + + + + + + + 提交 + + + + + + +::: + +::: title 原生submit方式校验 -- 校验通过将提交表单 -- 不推荐使用 +::: + +::: demo + + + + + + + 账户 + + + + + + + + 提交 + + + + + + +::: + +::: title 表单(form)属性 ::: ::: table -| Name | Description | Accepted Values | -| ----- | ----------- | --------------- | -| model | 表单绑定值 | -- | +| 属性 | 描述 | 类型 | 可选值 | 默认值 | +| ------------- | -------------------------------------------------------------- | --------- | -------------- | ------- | +| model | 表单绑定值 | `object` | - | {} | +| required | 是否必填 | `boolean` | `true` `false` | `false` | +| rules | 表单校验规则; 可查看[async-validator](https://github.com/yiminghe/async-validator) | `object` | - | - | +| initValidate | 是否一开始就校验表单 | `boolean` | `true` `false` | `false` | +| useCN | 是否使用中文错误提示 | `boolean` | `true` `false` | `false` | +| requiredIcons | 必填前缀图标`class` | `string` | - | `*` | +| required-erroer-message | 必填错误提示信息 | `string` | - | `%s不能为空`| +| validate-message | 自定义校验错误提示信息; 由于内置了中文错误提示,可按需求增量增加可参考 [layui-vue 内置中文错误提示](https://gitee.com/layui-vue/layui-vue/tree/master/src/module/formItem/cnValidateMessage.ts) | `string` | - | `%s不能为空`| ::: -::: title 表单事件 +::: title 表单(form)事件 ::: ::: table -| Name | Description | Accepted Values | -| ------ | ----------- | --------------- | -| submit | 提交事件 | -- | +| 属性 | 描述 | 回调参数 | +| ------------- | ------------------------------- | -------------- | +| submit | 提交事件`(不推荐使用)` | (`isValidate`, `model`, `errors`) `isValidate`: (`boolean`)是否校验通过 `model`: (`object`)表单绑定的值 `errors`: (`Array`)校验结果的错误信息 | + ::: -::: title 表单项属性 +::: title 表单(form)方法 ::: ::: table -| Name | Description | Accepted Values | -| ----- | ----------- | --------------- | -| label | 标题名称 | -- | +| 属性 | 描述 | 入参 | +| ------------- | -------------- | -------------- | +| validate | 表单校验; 如果没有`callback`回调,会返回`Promise`, `Promise`参数为{`isValidate`, `model`, `errors`} 参数具体描述请看上面表单`submit`提交事件 | (`fields` `[可选]`, `callback` `[可选]`) `fields`: (`string` / `string[]` / `function`)单独校验的字段,该字段如果为`function`, 则认为`callback`入参,校验全部字段; `callback`: (`function`)校验之后的回调,回调参数为(`isValidate`, `model`, `errors`);参数具体描述请看上面表单`submit`提交事件| +| clearValidate | 清除表单校验 | (`fields`[可选]) `fields`: (`string` / `string[]`)需要进行清除校验的表单字段, 如果该字段为空则清除全部校验| +| reset | 重置表单所有值 | - | + ::: +::: title 表单项(form-item)属性 +::: + +::: table + +| 属性 | 描述 | 类型 | 可选值 | 默认值 | +| ------------- | -------------------------------------------------------------- | --------- | -------------- | ------- | +| prop | 在表单绑定值(`model`)中字段`key` | `string` | - | - | +| label | 子项前边描述值,**尽量填写**,中文校验错误需要用到 | `string` | - | - | +| required | 是否必填 | `boolean` | `true` `false` | `false` | +| rules | 表单校验规则; 可查看[async-validator](https://github.com/yiminghe/async-validator) | `object` | - | - | +| error-message | 表单校验失败固定提示语 | `string` |`block` `inline`| `block` | +| mode | 表单项显示的模式,`块元素` / `行元素` | `string` |`block` `inline`| `block` | + +::: + +::: title 表单项(form-item)方法 +::: + +::: table + +| 属性 | 描述 | 入参 | +| ------------- | -------------- | -------------- | +| validate | 表单校验; | (`callback` `[可选]`) `callback`: (`function`)校验之后的回调,回调参数为(`errors`, `fields`); `errors`: (`Array`)校验结果的错误信息; `fields`: (`Array`)当前校验的字段信息| +| clearValidate | 清除表单校验 | - | + + +::: + +::: title 表单项(form-item)插槽 +::: + +::: table + +| 属性 | 描述 | 可使用参数 | +| ------------- | ---------------------------------------------------------------- | --------------------------------------- | +| - | 默认插槽 | 传递进来的`props`和表单绑定的值(`model`) | +| label | 子项前边描述插槽如果使用此插槽,`props`**尽量**也传递`label`参数 | 传递进来的`props`和表单绑定的值(`model`) | +| required | 必填前缀插槽 | `*` / `表单props` 中的 `requiredIcons` | + +::: + +::: title 关于 async-validator 的使用 +::: + +查看: https://github.com/yiminghe/async-validator +中文翻译: https://www.cnblogs.com/wozho/p/10955525.html + +::: title async-validator 基本校验类型 +::: + +::: table +| 属性 | 描述 | 使用方式 | +| -------------| ----------------- | ------------------- | +| number | 数字 | `{type : 'number'}` | +| boolean | 布尔类型 | `{type : 'boolean'}` | +| method | 方法 | `{type : 'method'}` | +| regexp | 正则表达式 | `{type : 'regexp'}` | +| integer | 整型数字 | `{type : 'integer'}` | +| float | 浮点小数 | `{type : 'float'}` | +| array | 数组 | `{type : 'array'}` | +| object | 对象 | `{type : 'object'}` | +| enum | 枚举 | `{type : 'enum'}` | +| date | 日期 | `{type : 'date'}` | +| url | url | `{type : 'url'}` | +| hex | 十六进制 | `{type : 'hex'}` | +| email | 邮箱 | `{type : 'email'}` | +::: + +::: title async-validator 中 validator参数使用 +::: + +```javascript +{ + validator: (rule, value) => value === 'root' +} +``` + +::: title async-validator 中 asyncValidator参数使用 +::: + +```javascript +{ + asyncValidator: (rule, value) => { + return new Promise((resolve, reject) => { + if (value < 18) { + reject('too young'); // reject with error message + } else { + resolve(); + } + }); + } +} +``` + + +::: title form 配置 async-validator 的使用 +::: + +```javascript +{ + key : rule // key为表单子项需要校验的对应key名称, rule为校验规则,格式为{} +} +// 例如 表单绑定值为 {email : 'xxx', username: 'xxxxx'} +// 需要校验邮箱,而邮箱输入框在表单绑定的值中key为email, +// 需要校验用户名长度为8到16之间,而用户名输入框在表单绑定的值中key为username, +{ + email : { + type : 'email' + }, + username : { + type : 'string', + min : 8, + max : 16 + } +} +``` + +::: title form-item 配置 async-validator 的使用 +::: + +```javascript +// 例如 表单绑定值为 {email : 'xxx', phone: 'xxxxx'} +// 需要校验邮箱,而邮箱输入框在表单绑定的值中key为email +{ + type : 'email' +} +``` + + ::: comment ::: \ No newline at end of file diff --git a/package.json b/package.json index 8ff46ba1..51c41179 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@layui/hooks-vue": "^0.1.6", "@layui/icons-vue": "^1.0.1", "@layui/layer-vue": "^1.2.0", + "async-validator": "^4.0.7", "evtd": "^0.2.3", "vue": "^3.2.26", "vue-router": "^4.0.12" diff --git a/src/module/form/index.vue b/src/module/form/index.vue index e4bb4ffa..cb49941e 100644 --- a/src/module/form/index.vue +++ b/src/module/form/index.vue @@ -1,25 +1,140 @@ - + - + + diff --git a/src/module/formItem/cnValidateMessage.ts b/src/module/formItem/cnValidateMessage.ts new file mode 100644 index 00000000..5e6dab9a --- /dev/null +++ b/src/module/formItem/cnValidateMessage.ts @@ -0,0 +1,49 @@ +import { ValidateMessages } from "async-validator"; +// 中文翻译 --> 根据 async-validator 中 ValidateMessages 进行翻译 +export default { + default: "%s验证失败", + required: "%s不能为空", + enum: "%s不在枚举%s里面", + whitespace: "%s不能为空", + date: { + format: "%s日期%s不是一个有效格式的日期%s", + parse: "%s无法解析为日期,%s是无效的", + invalid: "%s日期%s是无效的" + }, + types: { + number: '%s不是一个有效的数字', + boolean: '%s不是一个有效的布尔类型', + method: '%s不是一个有效的方法', + regexp: '%s不是一个有效的正则表达式', + integer: '%s不是一个有效的整型数字', + float: '%s不是一个有效的浮点小数', + array: '%s不是一个有效的数组', + object: '%s不是一个有效的对象', + enum: '%s不是一个有效的枚举', + date: '%s不是一个有效的日期', + url: '%s不是一个有效的url', + hex: '%s不是一个有效的十六进制', + email: '%s不是一个有效的邮箱' + }, + string: { + len: "%s必须是长度为%s个字符", + min: "%s最小长度为%s个字符", + max: "%s最长%s个字符", + range: "%s字符长度需要在%s和%s直接" + }, + number: { + len: "%s长度必须为%s", + min: "%s必须小于%s", + max: "%s必须大于%s", + range: "%s需要在%s和%s之间" + }, + array: { + len: "%s长度必须为%s", + min: "%s长度必须小于%s", + max: "%s长度必须大于%s", + range: "%s长度需要在%s和%s之间" + }, + pattern: { + "mismatch": "%s值%s不能匹配%s" + } +} as ValidateMessages; \ No newline at end of file diff --git a/src/module/formItem/index.less b/src/module/formItem/index.less new file mode 100644 index 00000000..83650ef7 --- /dev/null +++ b/src/module/formItem/index.less @@ -0,0 +1,47 @@ +@error_color : red; + +.layui-required{ + color: @error_color; + font-size: 12px; + line-height: 1; +} + +.layui-form .layui-form-item{ + .layui-input-block + ,.layui-input-inline{ + .layui-form-danger { + border-color: #ff5722 !important; + } + } +} + +.layui-error-message { + color: @error_color; + font-size: 12px; + line-height: 1; + padding-top: 2px; + position: absolute; + top: 100%; + left: 0; +} + +.layui-error-message-anim { + -ms-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-animation: layui-top-show-anim 0.3s ease 1; + animation: layui-top-show-anim 0.3s ease 1; +} + + +@keyframes layui-top-show-anim { + 0% { + opacity: 0.3; + transform: rotateX(45deg); + } + + 100% { + opacity: 1; + transform: rotateX(0); + } +} \ No newline at end of file diff --git a/src/module/formItem/index.vue b/src/module/formItem/index.vue index f7db647e..fa4071bb 100644 --- a/src/module/formItem/index.vue +++ b/src/module/formItem/index.vue @@ -1,21 +1,140 @@ - - {{ label }} + + + + {{layForm.requiredIcons? '' : '*'}} + + + {{ label }} + + - + + + + {{errorMsg}} diff --git a/src/module/select/index.vue b/src/module/select/index.vue index 307a8ec0..bfaa4f03 100644 --- a/src/module/select/index.vue +++ b/src/module/select/index.vue @@ -41,10 +41,10 @@ const props = defineProps<{ const openState = ref(false) const open = function () { - openState.value = true + openState.value = !openState.value } -const selectItem = reactive({ label: '', value: props.modelValue }) +const selectItem = reactive({ label: null, value: props.modelValue }) provide('selectItem', selectItem) provide('openState', openState) @@ -56,4 +56,12 @@ watch(selectItem, function (item) { emit('change', item.value) emit('update:modelValue', item.value) }) + +watch(()=>props.modelValue, function (value) { + if (!value) { + selectItem.label = null; + selectItem.value = ''; + emit('update:modelValue', null); + } +}) diff --git a/src/module/type/form.ts b/src/module/type/form.ts new file mode 100644 index 00000000..0a14155a --- /dev/null +++ b/src/module/type/form.ts @@ -0,0 +1,37 @@ +import type { ValidateCallback, ValidateError, ValidateMessages } from 'async-validator' + +export const layFormKey = 'LayForm' + +export interface LayFormContext { + model: modelType + required?: boolean + requiredErrorMessage?: string + validateMessage: ValidateMessages + rules?: Record + useCN : boolean + requiredIcons?: string + addField: (field: LayFormItemContext) => void +} + +export interface LayFormItemContext { + prop?: string + $el: HTMLDivElement + required?: boolean + rules?: Record + validate(callback?: ValidateCallback): void + clearValidate(): void +} + +export declare type modelType = { [key: string]: any } + +export declare interface FormCallback { + ( + isValid?: boolean, + model?: modelType, + errors?: ValidateError[] | null + ): void +} + +export declare interface FieldValidateError extends ValidateError { + label ?: string +} \ No newline at end of file diff --git a/src/module/type/index.ts b/src/module/type/index.ts index 2481e0c1..3c1ad287 100644 --- a/src/module/type/index.ts +++ b/src/module/type/index.ts @@ -1,2 +1,3 @@ export * from './public' export * from './select' +export * from './form'