未验证 提交 6867016d 编写于 作者: richard_1015's avatar richard_1015 提交者: GitHub

feta: form 表单组件,支持校验 (#846)

上级 143f98f2
......@@ -64,7 +64,7 @@
}
},
"dependencies": {
"sass": "^1.34.0",
"sass": "~1.32.0",
"vue-router": "^4.0.11"
},
"devDependencies": {
......
......@@ -556,7 +556,7 @@
"sort": 2,
"cName": "菜单组件",
"type": "component",
"taro":true,
"taro": true,
"show": true,
"desc": "下拉菜单组件",
"author": "yangjinjun3"
......@@ -871,6 +871,29 @@
"show": true,
"taro": true,
"author": "liqiong"
},
{
"version": "3.0.0",
"name": "Form",
"type": "component",
"cName": "表单",
"desc": "1",
"sort": 21,
"show": true,
"taro": true,
"author": "richard1015"
},
{
"version": "3.0.0",
"name": "FormItem",
"type": "component",
"cName": "表单Item",
"desc": "表单Item",
"sort": 22,
"show": false,
"taro": true,
"exportEmpty": true,
"author": "richard1015"
}
]
},
......
......@@ -10,7 +10,7 @@
}
&__label {
margin-left: 15px;
font-size: 16px;
font-size: 14px;
color: $checkbox-label-color;
&--disabled {
color: $checkbox-label-disable-color;
......
import { isPromise } from '../../utils/util';
import { computed, provide, reactive, VNode, watch } from 'vue';
import { FormItemRule } from '../formitem/types';
import { ErrorMessage, FormRule } from './types';
export const component = {
props: {
modelValue: {
type: Object,
default: {}
}
},
components: {},
emits: ['validate'],
setup(props: any, { emit, slots }: any) {
const formErrorTip = computed(() => reactive<any>({}));
provide('formErrorTip', formErrorTip);
const init = (value = props.modelValue) => {
Object.keys(value).forEach((item) => {
formErrorTip.value[item] = '';
});
};
const reset = () => {
init();
};
watch(
() => props.modelValue,
(value: any) => {
init(value);
},
{ immediate: true }
);
const findFormItem = (vnodes: VNode[]) => {
let task: FormRule[] = [];
vnodes.forEach((vnode: VNode, index: number) => {
let type = vnode.type;
type = (type as any).name || type;
if (type == 'nut-form-item') {
task.push({
prop: vnode.props?.['prop'],
rules: vnode.props?.['rules'] || []
});
}
});
return task;
};
const tipMessage = (errorMsg: ErrorMessage) => {
if (errorMsg.message) {
emit('validate', errorMsg);
}
formErrorTip.value[errorMsg.prop] = errorMsg.message;
};
const checkRule = (item: FormRule): Promise<ErrorMessage | boolean> => {
const { rules, prop } = item;
const _Promise = (errorMsg: ErrorMessage): Promise<ErrorMessage> => {
return new Promise((resolve, reject) => {
tipMessage(errorMsg);
resolve(errorMsg);
});
};
const value = props.modelValue[prop];
// clear tips
tipMessage({ prop, message: '' });
while (rules.length) {
const { required, validator, regex, message } = rules.shift() as FormItemRule;
const errorMsg = { prop, message };
if (required) {
if (!value) {
return _Promise(errorMsg);
}
}
if (regex && !regex.test(String(value))) {
return _Promise(errorMsg);
}
if (validator) {
const result = validator(value);
if (isPromise(result)) {
return new Promise((r, j) => {
result.then((res) => {
if (!res) {
tipMessage(errorMsg);
r(errorMsg);
} else {
r(true);
}
});
});
} else {
if (!result) {
return _Promise(errorMsg);
}
}
}
}
return Promise.resolve(true);
};
const validate = () => {
return new Promise((resolve, reject) => {
let task = findFormItem(slots.default());
let errors = task.map((item) => {
return checkRule(item);
});
Promise.all(errors).then((errorRes) => {
errorRes = errorRes.filter((item) => item != true);
const res = { valid: true, errors: [] };
if (errorRes.length) {
res.valid = false;
res.errors = errorRes as any;
}
resolve(res);
});
});
};
const onSubmit = () => {
validate();
return false;
};
return { validate, reset, onSubmit, formErrorTip };
}
};
<template>
<div class="demo full">
<h2>基本用法</h2>
<nut-form>
<nut-form-item label="姓名">
<input class="nut-input-text" placeholder="请输入姓名" type="text" />
</nut-form-item>
<nut-form-item label="年龄">
<input class="nut-input-text" placeholder="请输入年龄" type="text" />
</nut-form-item>
<nut-form-item label="联系电话">
<input class="nut-input-text" placeholder="请输入联系电话" type="text" />
</nut-form-item>
<nut-form-item label="地址">
<input class="nut-input-text" placeholder="请输入地址" type="text" />
</nut-form-item>
</nut-form>
<h2>表单校验</h2>
<nut-form :model-value="formData" ref="ruleForm">
<nut-form-item label="姓名" prop="name" required :rules="[{ required: true, message: '请填写姓名' }]">
<input class="nut-input-text" v-model="formData.name" placeholder="请输入姓名" type="text" />
</nut-form-item>
<nut-form-item
label="年龄"
prop="age"
required
:rules="[
{ required: true, message: '请填写年龄' },
{ validator: customValidator, message: '必须输入数字' },
{ regex: /^(\d{1,2}|1\d{2}|200)$/, message: '必须输入0-200区间' }
]"
>
<input
class="nut-input-text"
v-model="formData.age"
placeholder="请输入年龄,必须数字且0-200区间"
type="text"
/>
</nut-form-item>
<nut-form-item
label="联系电话"
prop="tel"
required
:rules="[
{ required: true, message: '请填写联系电话' },
{ validator: asyncValidator, message: '电话格式不正确' }
]"
>
<input
class="nut-input-text"
v-model="formData.tel"
placeholder="请输入联系电话,异步校验电话格式"
type="text"
/>
</nut-form-item>
<nut-form-item label="地址" prop="address" required :rules="[{ required: true, message: '请填写地址' }]">
<input class="nut-input-text" v-model="formData.address" placeholder="请输入地址" type="text" />
</nut-form-item>
<nut-cell>
<nut-button type="primary" size="small" style="margin-right: 10px" @click="submit">提交</nut-button>
<nut-button size="small" @click="reset">重置提示状态</nut-button>
</nut-cell>
</nut-form>
<h2>表单类型</h2>
<nut-form>
<nut-form-item label="开关">
<nut-switch v-model="formData2.switch"></nut-switch>
</nut-form-item>
<nut-form-item label="复选框">
<nut-checkbox v-model="formData2.checkbox">复选框</nut-checkbox>
</nut-form-item>
<nut-form-item label="单选按钮">
<nut-radiogroup direction="horizontal" v-model="formData2.radio">
<nut-radio label="1">选项1</nut-radio>
<nut-radio disabled label="2">选项2</nut-radio>
<nut-radio label="3">选项3</nut-radio>
</nut-radiogroup>
</nut-form-item>
<nut-form-item label="评分">
<nut-rate v-model="formData2.rate" />
</nut-form-item>
<nut-form-item label="步进器">
<nut-inputnumber v-model="formData2.number" />
</nut-form-item>
<nut-form-item label="滑块">
<nut-range hidden-tag v-model="formData2.range"></nut-range>
</nut-form-item>
<nut-form-item label="文件上传">
<nut-uploader url="http://服务地址" v-model:file-list="formData2.defaultFileList" maximum="3" multiple>
</nut-uploader>
</nut-form-item>
<nut-form-item label="地址">
<input
class="nut-input-text"
v-model="formData2.address"
@click="addressModule.methods.show"
readonly
placeholder="请选择地址"
type="text"
/>
<!-- nut-address -->
<nut-address
v-model:visible="addressModule.state.show"
:province="addressModule.state.province"
:city="addressModule.state.city"
:country="addressModule.state.country"
:town="addressModule.state.town"
@change="addressModule.methods.onChange"
custom-address-title="请选择所在地区"
></nut-address>
</nut-form-item>
</nut-form>
</div>
</template>
<script lang="ts">
import { Toast } from '@/packages/nutui.vue';
import { reactive, ref } from 'vue';
import { createComponent } from '../../utils/create';
const { createDemo } = createComponent('form');
export default createDemo({
props: {},
setup() {
const formData = reactive({
name: '',
age: '',
tel: '',
address: ''
});
const validate = (item: any) => {
console.log(item);
};
const formData2 = reactive({
switch: false,
checkbox: false,
radio: 0,
number: 0,
rate: 3,
range: 30,
address: '',
defaultFileList: [
{
name: '文件1.png',
url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
status: 'success',
message: '上传成功',
type: 'image'
},
{
name: '文件2.png',
url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
status: 'uploading',
message: '上传中...',
type: 'image'
}
]
});
const addressModule = reactive({
state: {
show: false,
province: [
{ id: 1, name: '北京' },
{ id: 2, name: '广西' },
{ id: 3, name: '江西' },
{ id: 4, name: '四川' }
],
city: [
{ id: 7, name: '朝阳区' },
{ id: 8, name: '崇文区' },
{ id: 9, name: '昌平区' },
{ id: 6, name: '石景山区' }
],
country: [
{ id: 3, name: '八里庄街道' },
{ id: 9, name: '北苑' },
{ id: 4, name: '常营乡' }
],
town: []
},
methods: {
show() {
addressModule.state.show = !addressModule.state.show;
if (addressModule.state.show) {
formData2.address = '';
}
},
onChange({ custom, next, value }: any) {
formData2.address += value.name;
const name = addressModule.state[next];
if (name.length < 1) {
addressModule.state.show = false;
}
}
}
});
const ruleForm = ref<any>(null);
const submit = () => {
ruleForm.value.validate().then(({ valid, errors }: any) => {
if (valid) {
console.log('success', formData);
} else {
console.log('error submit!!', errors);
}
});
};
const reset = () => {
ruleForm.value.reset();
};
// 函数校验
const customValidator = (val: string) => /^\d+$/.test(val);
// Promise 异步校验
const asyncValidator = (val: string) => {
return new Promise((resolve) => {
Toast.loading('模拟异步验证中...');
setTimeout(() => {
Toast.hide();
resolve(/^400(-?)[0-9]{7}$|^1\d{10}$|^0[0-9]{2,3}-[0-9]{7,8}$/.test(val));
}, 1000);
});
};
return { ruleForm, formData, validate, customValidator, asyncValidator, submit, reset, formData2, addressModule };
}
});
</script>
<style lang="scss" scoped></style>
# Form 表单组件
### 介绍
用于数据录入、校验,支持输入框、单选框、复选框、文件上传等类型,需要与 Cell 组件搭配使用。
### 安装
``` javascript
import { createApp } from 'vue';
// vue
import { Form,FormItem,Cell,CellGroup } from '@nutui/nutui';
// taro
import { Form,FormItem,Cell,CellGroup } from '@nutui/nutui-taro';
const app = createApp();
app.use(Form);
app.use(FormItem);
app.use(Cell);
app.use(CellGroup);
```
### 基础用法
``` html
<nut-form>
<nut-form-item label="姓名">
<input class="nut-input-text" placeholder="请输入姓名" type="text" />
</nut-form-item>
<nut-form-item label="年龄">
<input class="nut-input-text" placeholder="请输入年龄" type="text" />
</nut-form-item>
<nut-form-item label="联系电话">
<input class="nut-input-text" placeholder="请输入联系电话" type="text" />
</nut-form-item>
<nut-form-item label="地址">
<input class="nut-input-text" placeholder="请输入地址" type="text" />
</nut-form-item>
</nut-form>
```
### 表单校验
``` html
<nut-form :model-value="formData" ref="ruleForm">
<nut-form-item label="姓名" prop="name" required :rules="[{ required: true, message: '请填写姓名' }]">
<input class="nut-input-text" v-model="formData.name" placeholder="请输入姓名" type="text" />
</nut-form-item>
<nut-form-item label="年龄" prop="age" required :rules="[
{ required: true, message: '请填写年龄' },
{ validator: customValidator, message: '必须输入数字' },
{ regex: /^(\d{1,2}|1\d{2}|200)$/, message: '必须输入0-200区间' }
]">
<input class="nut-input-text" v-model="formData.age" placeholder="请输入年龄,必须数字且0-200区间" type="text" />
</nut-form-item>
<nut-form-item label="联系电话" prop="tel" required :rules="[
{ required: true, message: '请填写联系电话' },
{ validator: asyncValidator, message: '电话格式不正确' }
]">
<input class="nut-input-text" v-model="formData.tel" placeholder="请输入联系电话,异步校验电话格式" type="text" />
</nut-form-item>
<nut-form-item label="地址" prop="address" required :rules="[{ required: true, message: '请填写地址' }]">
<input class="nut-input-text" v-model="formData.address" placeholder="请输入地址" type="text" />
</nut-form-item>
<nut-cell>
<nut-button type="primary" size="small" style="margin-right: 10px" @click="submit">提交</nut-button>
<nut-button size="small" @click="reset">重置提示状态</nut-button>
</nut-cell>
</nut-form>
```
``` javascript
setup(){
const formData = reactive({
name: '',
age: '',
tel: '',
address: ''
});
const validate = (item: any) => {
console.log(item);
};
const ruleForm = ref<any>(null);
const submit = () => {
ruleForm.value.validate().then(({ valid, errors }: any) => {
if (valid) {
console.log('success', formData);
} else {
console.log('error submit!!', errors);
}
});
};
const reset = () => {
ruleForm.value.reset();
};
// 函数校验
const customValidator = (val: string) => /^\d+$/.test(val);
// Promise 异步校验
const asyncValidator = (val: string) => {
return new Promise((resolve) => {
Toast.loading('模拟异步验证中...');
setTimeout(() => {
Toast.hide();
resolve(/^400(-?)[0-9]{7}$|^1\d{10}$|^0[0-9]{2,3}-[0-9]{7,8}$/.test(val));
}, 1000);
});
};
return { ruleForm, formData, validate, customValidator, asyncValidator, submit, reset, formData2, addressModule };
}
```
### 表单类型
``` html
<nut-form>
<nut-form-item label="开关">
<nut-switch v-model="formData2.switch"></nut-switch>
</nut-form-item>
<nut-form-item label="复选框">
<nut-checkbox v-model="formData2.checkbox">复选框</nut-checkbox>
</nut-form-item>
<nut-form-item label="单选按钮">
<nut-radiogroup direction="horizontal" v-model="formData2.radio">
<nut-radio label="1">选项1</nut-radio>
<nut-radio disabled label="2">选项2</nut-radio>
<nut-radio label="3">选项3</nut-radio>
</nut-radiogroup>
</nut-form-item>
<nut-form-item label="评分">
<nut-rate v-model="formData2.rate" />
</nut-form-item>
<nut-form-item label="步进器">
<nut-inputnumber v-model="formData2.number" />
</nut-form-item>
<nut-form-item label="滑块">
<nut-range hidden-tag v-model="formData2.range"></nut-range>
</nut-form-item>
<nut-form-item label="文件上传">
<nut-uploader url="http://服务地址" v-model:file-list="formData2.defaultFileList" maximum="3" multiple>
</nut-uploader>
</nut-form-item>
<nut-form-item label="地址">
<input class="nut-input-text" v-model="formData2.address" @click="addressModule.methods.show" readonly
placeholder="请选择地址" type="text" />
<!-- nut-address -->
<nut-address v-model:visible="addressModule.state.show" :province="addressModule.state.province"
:city="addressModule.state.city" :country="addressModule.state.country" :town="addressModule.state.town"
@change="addressModule.methods.onChange" custom-address-title="请选择所在地区"></nut-address>
</nut-form-item>
</nut-form>
```
``` javascript
setup(){
const formData2 = reactive({
switch: false,
checkbox: false,
radio: 0,
number: 0,
rate: 3,
range: 30,
address: '',
defaultFileList: [
{
name: '文件1.png',
url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
status: 'success',
message: '上传成功',
type: 'image'
},
{
name: '文件2.png',
url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
status: 'uploading',
message: '上传中...',
type: 'image'
}
]
});
const addressModule = reactive({
state: {
show: false,
province: [
{ id: 1, name: '北京' },
{ id: 2, name: '广西' },
{ id: 3, name: '江西' },
{ id: 4, name: '四川' }
],
city: [
{ id: 7, name: '朝阳区' },
{ id: 8, name: '崇文区' },
{ id: 9, name: '昌平区' },
{ id: 6, name: '石景山区' }
],
country: [
{ id: 3, name: '八里庄街道' },
{ id: 9, name: '北苑' },
{ id: 4, name: '常营乡' }
],
town: []
},
methods: {
show() {
addressModule.state.show = !addressModule.state.show;
if (addressModule.state.show) {
formData2.address = '';
}
},
onChange({ custom, next, value }: any) {
formData2.address += value.name;
const name = addressModule.state[next];
if (name.length < 1) {
addressModule.state.show = false;
}
}
}
});
return { formData2, addressModule };
}
```
### Form Props
| 参数 | 说明 | 类型 | 默认值 |
|-------------|--------------------------------------|--------|--------|
| model-value | 表单数据对象(使用表单校验时,_必填_) | object | |
### Form Events
| 事件名 | 说明 | 回调参数 |
|----------|----------------------------|------------------------------------------------------------|
| validate | 任一表单项被校验失败后触发 | 被校验的表单项 prop 值,校验是否通过,错误消息(如果存在) |
### FormItem Props
| 参数 | 说明 | 类型 | 默认值 |
|---------------------|--------------------------------------------------|------------------|---------|
| required | 是否显示必填字段的标签旁边的红色星号 | boolean | `false` |
| label-width | 表单项 label 宽度,默认单位为`px` | number \| string | `90px` |
| label-align | 表单项 label 对齐方式,可选值为 `center` `right` | string | `left` |
| body-align | 输入框对齐方式,可选值为 `center` `right` | string | `left` |
| error-message-align | 错误提示文案对齐方式,可选值为 `center` `right` | string | `left` |
| show-error-line | 是否在校验不通过时标红输入框 | boolean | `true` |
| show-error-message | 是否在校验不通过时在输入框下方展示错误提示 | boolean | `true` |
### FormItem Rule 数据结构
使用 FormItem 的`rules`属性可以定义校验规则,可选属性如下:
| 键名 | 说明 | 类型 |
|-----------|------------------------|-----------------------------------------------|
| required | 是否为必选字段 | boolean |
| message | 错误提示文案 | string |
| validator | 通过函数进行校验 | (value, rule) => boolean \| string \| Promise |
| regex | 通过正则表达式进行校验 | RegExp |
### Methods
通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#ref) 可以获取到 Form 实例并调用实例方法
| 方法名 | 说明 | 参数 | 返回值 |
|--------|------------------------|------|---------|
| submit | 提交表单进行校验的方法 | - | Promise |
| reset | 清空校验结果 | - | - |
\ No newline at end of file
<template>
<form class="nut-form" @submit="onSubmit">
<nut-cell-group>
<slot></slot>
</nut-cell-group>
</form>
</template>
<script lang="ts">
import { createComponent } from '../../utils/create';
const { create } = createComponent('form');
import { component } from './common';
export default create(component);
</script>
<template>
<form class="nut-form" @submit="onSubmit">
<nut-cell-group>
<slot></slot>
</nut-cell-group>
</form>
</template>
<script lang="ts">
import { createComponent } from '../../utils/create';
const { create } = createComponent('form');
import { component } from './common';
export default create(component);
</script>
import { FormItemRule } from '../formitem/types';
export type FormRule = {
prop: string;
rules: FormItemRule[];
};
export type ErrorMessage = {
prop: string;
message: string;
};
.nut-form-item {
display: flex;
&::before {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 16px;
bottom: 0;
left: 16px;
transform: scaleX(0);
}
&.error {
&.line {
&::before {
border-bottom: 1px solid $form-item-error-line-color;
transform: scaleX(1);
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1) 0ms;
}
}
}
&__label {
font-size: 14px;
font-weight: normal;
width: 90px;
margin-right: 10px;
flex: none;
display: inline-block;
word-wrap: break-word;
&.required {
&::before {
content: '*';
color: $form-item-required-color;
margin-right: 4px;
}
}
}
&__body {
flex: 1;
display: flex;
flex-direction: column;
&__slots {
.nut-input-text {
font-size: 14px;
width: 100%;
outline: 0 none;
border: 0;
text-decoration: none;
}
.nut-range-container {
min-height: 24px;
}
}
&__tips {
font-size: 10px;
color: $form-item-error-message-color;
}
}
}
<template>
<nut-cell class="nut-form-item" :class="{ error: parent[prop], line: showErrorLine }">
<view class="nut-cell__title nut-form-item__label" :style="labelStyle" v-if="label" :class="{ required: required }">
{{ label }}</view
>
<view class="nut-cell__value nut-form-item__body">
<view class="nut-form-item__body__slots" :style="bodyStyle">
<slot></slot>
</view>
<view class="nut-form-item__body__tips" :style="errorMessageStyle" v-if="parent[prop] && showErrorMessage">
{{ parent[prop] }}</view
>
</view>
</nut-cell>
</template>
<script lang="ts">
import { pxCheck } from '../../utils/pxCheck';
import { computed, inject, PropType, ref } from 'vue';
import { createComponent } from '../../utils/create';
const { componentName, create } = createComponent('form-item');
import { FormItemRule } from './types';
export default create({
inheritAttrs: false,
props: {
prop: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
rules: {
type: Array as PropType<FormItemRule[]>,
default: () => {
return [];
}
},
required: {
type: Boolean,
default: false
},
showErrorMessage: {
type: Boolean,
default: true
},
showErrorLine: {
type: Boolean,
default: true
},
labelWidth: {
type: [String, Number],
default: ''
},
labelAlign: {
type: String,
default: 'left'
},
errorMessageAlign: {
type: String,
default: 'left'
},
bodyAlign: {
type: String,
default: 'left'
}
},
components: {},
emits: [''],
setup(props, { emit }) {
const parent = inject('formErrorTip') as any;
const labelStyle = computed(() => {
return {
width: pxCheck(props.labelWidth),
textAlign: props.labelAlign
};
});
const bodyStyle = computed(() => {
return {
textAlign: props.bodyAlign
};
});
const errorMessageStyle = computed(() => {
return {
textAlign: props.errorMessageAlign
};
});
return { parent, labelStyle, bodyStyle, errorMessageStyle };
}
});
</script>
export class FormItemRule {
regex?: RegExp;
required?: boolean;
message!: string;
validator?: (value: any) => boolean | string | Promise<boolean | string>;
}
.nut-switch {
cursor: pointer;
display: flex;
display: inline-flex;
align-items: center;
background-color: $primary-color;
border-radius: 21px;
......
......@@ -240,7 +240,7 @@ setup() {
### Methods
通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#key) 可以获取到 Uploader 实例并调用实例方法
通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#ref) 可以获取到 Uploader 实例并调用实例方法
| 方法名 | 说明 | 参数 | 返回值 |
|------------------|------------------------------------------------------------|------|--------|
......
......@@ -219,7 +219,7 @@ setup() {
### Methods
通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#key) 可以获取到 Uploader 实例并调用实例方法
通过 [ref](https://v3.cn.vuejs.org/api/special-attributes.html#ref) 可以获取到 Uploader 实例并调用实例方法
| 方法名 | 说明 | 参数 | 返回值 |
|------------------|------------------------------------------------------------|------|--------|
......
......@@ -378,6 +378,11 @@ $searchbar-background: $white !default;
$searchbar-input-background: #f7f7f7 !default;
$searchbar-right-out-color: $black !default;
// form
$form-item-error-line-color: $primary-color !default;
$form-item-required-color: $primary-color !default;
$form-item-error-message-color: $primary-color !default;
// sku
$sku-item-border: 1px solid $primary-color;
$sku-item-disable-line: line-through;
......
......@@ -37,3 +37,24 @@ export const TypeOfFun = (value: any) => {
return 'unknow';
}
};
//
export const objectToString = Object.prototype.toString;
export const toTypeString = (value: unknown): string => objectToString.call(value);
export const toRawType = (value: unknown): string => {
// extract "RawType" from strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
export const isArray = Array.isArray;
export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === '[object Map]';
export const isSet = (val: unknown): val is Set<any> => toTypeString(val) === '[object Set]';
export const isDate = (val: unknown): val is Date => val instanceof Date;
export const isFunction = (val: unknown): val is Function => typeof val === 'function';
export const isString = (val: unknown): val is string => typeof val === 'string';
export const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol';
export const isObject = (val: unknown): val is Record<any, any> => val !== null && typeof val === 'object';
export const isPromise = <T = any>(val: unknown): val is Promise<T> => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};
......@@ -62,6 +62,7 @@ export default {
'pages/rate/index',
'pages/radio/index',
'pages/calendar/index',
'pages/form/index',
'pages/shortpassword/index',
'pages/picker/index',
'pages/datepicker/index',
......
export default {
navigationBarTitleText: 'Form'
};
<template>
<div class="demo full">
<h2>基本用法</h2>
<nut-form>
<nut-form-item label="姓名">
<input class="nut-input-text" placeholder="请输入姓名" type="text" />
</nut-form-item>
<nut-form-item label="年龄">
<input class="nut-input-text" placeholder="请输入年龄" type="text" />
</nut-form-item>
<nut-form-item label="联系电话">
<input class="nut-input-text" placeholder="请输入联系电话" type="text" />
</nut-form-item>
<nut-form-item label="地址">
<input class="nut-input-text" placeholder="请输入地址" type="text" />
</nut-form-item>
</nut-form>
<h2>表单校验</h2>
<nut-form :model-value="formData" ref="ruleForm">
<nut-form-item label="姓名" prop="name" required :rules="[{ required: true, message: '请填写姓名' }]">
<input class="nut-input-text" v-model="formData.name" placeholder="请输入姓名" type="text" />
</nut-form-item>
<nut-form-item
label="年龄"
prop="age"
required
:rules="[
{ required: true, message: '请填写年龄' },
{ validator: customValidator, message: '必须输入数字' },
{ regex: /^(\d{1,2}|1\d{2}|200)$/, message: '必须输入0-200区间' }
]"
>
<input
class="nut-input-text"
v-model="formData.age"
placeholder="请输入年龄,必须数字且0-200区间"
type="text"
/>
</nut-form-item>
<nut-form-item
label="联系电话"
prop="tel"
required
:rules="[
{ required: true, message: '请填写联系电话' },
{ validator: asyncValidator, message: '电话格式不正确' }
]"
>
<input
class="nut-input-text"
v-model="formData.tel"
placeholder="请输入联系电话,异步校验电话格式"
type="text"
/>
</nut-form-item>
<nut-form-item label="地址" prop="address" required :rules="[{ required: true, message: '请填写地址' }]">
<input class="nut-input-text" v-model="formData.address" placeholder="请输入地址" type="text" />
</nut-form-item>
<nut-cell>
<nut-button type="primary" size="small" style="margin-right: 10px" @click="submit">提交</nut-button>
<nut-button size="small" @click="reset">重置提示状态</nut-button>
</nut-cell>
</nut-form>
<h2>表单类型</h2>
<nut-form>
<nut-form-item label="开关">
<nut-switch v-model="formData2.switch"></nut-switch>
</nut-form-item>
<nut-form-item label="复选框">
<nut-checkbox v-model="formData2.checkbox">复选框</nut-checkbox>
</nut-form-item>
<nut-form-item label="单选按钮">
<nut-radiogroup direction="horizontal" v-model="formData2.radio">
<nut-radio label="1">选项1</nut-radio>
<nut-radio disabled label="2">选项2</nut-radio>
<nut-radio label="3">选项3</nut-radio>
</nut-radiogroup>
</nut-form-item>
<nut-form-item label="评分">
<nut-rate v-model="formData2.rate" />
</nut-form-item>
<nut-form-item label="步进器">
<nut-inputnumber v-model="formData2.number" />
</nut-form-item>
<nut-form-item label="滑块">
<nut-range hidden-tag v-model="formData2.range"></nut-range>
</nut-form-item>
<nut-form-item label="文件上传">
<nut-uploader url="http://服务地址" v-model:file-list="formData2.defaultFileList" maximum="3" multiple>
</nut-uploader>
</nut-form-item>
<nut-form-item label="地址">
<input
class="nut-input-text"
v-model="formData2.address"
@click="addressModule.methods.show"
readonly
placeholder="请选择地址"
type="text"
/>
<!-- nut-address -->
<nut-address
v-model:visible="addressModule.state.show"
:province="addressModule.state.province"
:city="addressModule.state.city"
:country="addressModule.state.country"
:town="addressModule.state.town"
@change="addressModule.methods.onChange"
custom-address-title="请选择所在地区"
></nut-address>
</nut-form-item>
</nut-form>
</div>
</template>
<script lang="ts">
import { reactive, ref } from 'vue';
export default {
props: {},
setup() {
const formData = reactive({
name: '',
age: '',
tel: '',
address: ''
});
const validate = (item: any) => {
console.log(item);
};
const formData2 = reactive({
switch: false,
checkbox: false,
radio: 0,
number: 0,
rate: 3,
range: 30,
address: '',
defaultFileList: [
{
name: '文件1.png',
url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
status: 'success',
message: '上传成功',
type: 'image'
},
{
name: '文件2.png',
url: 'https://m.360buyimg.com/babel/jfs/t1/164410/22/25162/93384/616eac6cE6c711350/0cac53c1b82e1b05.gif',
status: 'uploading',
message: '上传中...',
type: 'image'
}
]
});
const addressModule = reactive({
state: {
show: false,
province: [
{ id: 1, name: '北京' },
{ id: 2, name: '广西' },
{ id: 3, name: '江西' },
{ id: 4, name: '四川' }
],
city: [
{ id: 7, name: '朝阳区' },
{ id: 8, name: '崇文区' },
{ id: 9, name: '昌平区' },
{ id: 6, name: '石景山区' }
],
country: [
{ id: 3, name: '八里庄街道' },
{ id: 9, name: '北苑' },
{ id: 4, name: '常营乡' }
],
town: []
},
methods: {
show() {
addressModule.state.show = !addressModule.state.show;
if (addressModule.state.show) {
formData2.address = '';
}
},
onChange({ custom, next, value }: any) {
formData2.address += value.name;
const name = addressModule.state[next];
if (name.length < 1) {
addressModule.state.show = false;
}
}
}
});
const ruleForm = ref<any>(null);
const submit = () => {
ruleForm.value.validate().then(({ valid, errors }: any) => {
if (valid) {
console.log('success', formData);
} else {
console.log('error submit!!', errors);
}
});
};
const reset = () => {
ruleForm.value.reset();
};
// 函数校验
const customValidator = (val: string) => /^\d+$/.test(val);
// Promise 异步校验
const asyncValidator = (val: string) => {
return new Promise((resolve) => {
console.log('模拟异步验证中...');
setTimeout(() => {
console.log('验证完成');
resolve(/^400(-?)[0-9]{7}$|^1\d{10}$|^0[0-9]{2,3}-[0-9]{7,8}$/.test(val));
}, 1000);
});
};
return { ruleForm, formData, validate, customValidator, asyncValidator, submit, reset, formData2, addressModule };
}
};
</script>
<style lang="scss" scoped></style>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册