From 81b8a7944eaf3d736247b155f7335d5fe1e64fb3 Mon Sep 17 00:00:00 2001 From: Tevin <tingquanren@163.com> Date: Fri, 27 Nov 2020 16:56:58 +0800 Subject: [PATCH] 实现表单第二部分:验证优化、普通文本框、图片选择框、文本域、下拉选择框 --- forms/imagePicker/cImagePicker.scss | 68 ++++++ forms/textarea/CTextArea.vue | 50 +++++ forms/select/cSelect.scss | 23 ++ forms/select/index.js | 10 + forms/form/CFormItem.vue | 57 ++++- forms/imagePicker/CImagePicker.vue | 108 ++++++++++ forms/form/cForm.scss | 34 +++ forms/input/index.js | 10 + forms/imagePicker/index.js | 10 + forms/input/CInput.vue | 35 +++ forms/form/CFormSubmit.vue | 17 + forms/textarea/cTextArea.scss | 22 ++ forms/textarea/index.js | 10 + forms/form/index.js | 2 forms/form/CForm.vue | 6 forms/select/CSelect.vue | 79 +++++++ 16 files changed, 527 insertions(+), 14 deletions(-) diff --git a/forms/form/CForm.vue b/forms/form/CForm.vue index db0608b..365fcac 100644 --- a/forms/form/CForm.vue +++ b/forms/form/CForm.vue @@ -14,6 +14,7 @@ <script> import Taro from '@tarojs/taro'; +import './cForm.scss'; export default { name: 'CForm', @@ -51,7 +52,10 @@ }, }, mounted() { - this.formData.$handleChange = (evt) => { + this.formData.$handleChange = (evt = []) => { + Object.keys(evt).forEach((key) => { + this.formData[key] = evt[key]; + }); this.onChange && this.onChange(evt); }; this.formData.$regItemValidator = (name, cb) => { diff --git a/forms/form/CFormItem.vue b/forms/form/CFormItem.vue index a8e57fc..ffbf570 100644 --- a/forms/form/CFormItem.vue +++ b/forms/form/CFormItem.vue @@ -6,17 +6,7 @@ <template> <view class="c-form-item"> - <view class="c-form-item-label"> - <text - class="required" - v-if="required" - >*</text> - <text>{{label || name}}</text> - </view> - <slot - :value="formData[name]" - :onChange="evt=>onChange(evt)" - /> + <slot :itemData="itemData" /> </view> </template> @@ -34,7 +24,37 @@ formData: Object, }, data() { - return {}; + return { + error: false, + }; + }, + computed: { + itemData() { + return { + formData: this.formData, + name: this.name, + label: this.label, + required: this.isRequired, + error: this.error, + onChange: (evt) => this.onChange(evt), + }; + }, + isRequired() { + if (this.required) { + return true; + } else { + if (!this.rules || this.rules.length === 0) { + return false; + } else if (this.rules.length > 0) { + for (let rule of this.rules) { + if (rule.required) { + return true; + } + } + return false; + } + } + }, }, methods: { onChange(evt) { @@ -64,12 +84,14 @@ // 未设置验证 if (!this.required && !this.rules) { this.formData.$regItemValidator(this.name, () => { + this.error = false; return Promise.resolve({ name: this.name, passed: true, }); }); } else { + // 验证规则 const descriptor = this.rules || []; if (this.required) { descriptor.unshift({ @@ -79,7 +101,10 @@ const validator = new Schema({ [this.name]: descriptor, }); - validator.messages(validateMsgs); // 自定义验证消息 + // 汉化通用验证消息 + validator.messages(validateMsgs); + // 注册验证 + let errTimer = null; this.formData.$regItemValidator(this.name, () => { return validator .validate({ @@ -87,12 +112,18 @@ }) .then( (res) => { + this.error = false; return { name: this.name, passed: true, }; }, ({ errors, fields }) => { + this.error = true; + clearTimeout(errTimer); + errTimer = setTimeout(() => { + this.error = false; + }, 5000); return { name: this.name, passed: false, diff --git a/forms/form/CFormSubmit.vue b/forms/form/CFormSubmit.vue new file mode 100644 index 0000000..87b7e2d --- /dev/null +++ b/forms/form/CFormSubmit.vue @@ -0,0 +1,17 @@ +<template> + <view class="c-form-submit"> + <button + form-type="submit" + type="primary" + > + <slot>提交</slot> + </button> + </view> +</template> + +<script> +export default { + name: 'CFormSubmit', + props: {}, +}; +</script> \ No newline at end of file diff --git a/forms/form/cForm.scss b/forms/form/cForm.scss new file mode 100644 index 0000000..07140a1 --- /dev/null +++ b/forms/form/cForm.scss @@ -0,0 +1,34 @@ +/** + * form + * @author Tevin + */ + +@import "../../common/sassMixin"; + +.c-form { + width: 100%; + .c-form-item { + .at-input__title--required { + position: relative; + &::before { + @include position(absolute, 50% -0.5rem n n); + transform: translateY(-50%); + } + } + } + .c-form-submit { + margin-top: 0.8rem; + padding: 0 0.42667rem; + [type=primary] { + font-size: 0.7rem; + line-height: 2.2; + border: 1PX solid #1e8ad2; + background: #1e8ad2; + &:not([disabled]):active { + opacity: .6; + border: 1PX solid #1e8ad2; + background: #1e8ad2; + } + } + } +} \ No newline at end of file diff --git a/forms/form/index.js b/forms/form/index.js index 7176d3e..73b1531 100644 --- a/forms/form/index.js +++ b/forms/form/index.js @@ -5,8 +5,10 @@ import CForm from '@components/forms/form/CForm.vue'; import CFormItem from '@components/forms/form/CFormItem.vue'; +import CFormSubmit from '@components/forms/form/CFormSubmit.vue'; export { CForm, CFormItem, + CFormSubmit, } \ No newline at end of file diff --git a/forms/imagePicker/CImagePicker.vue b/forms/imagePicker/CImagePicker.vue new file mode 100644 index 0000000..1ed467b --- /dev/null +++ b/forms/imagePicker/CImagePicker.vue @@ -0,0 +1,108 @@ +/** + * CImagePicker + * @author Tevin + */ + +<template> + <view class="c-image-picker"> + <AtInput + ref="input" + :name="itemData.name" + :title="itemData.label" + :required="itemData.required" + :error="itemData.error" + /> + <AtImagePicker + ref="picker" + multiple + mode="aspectFit" + :count="9" + :length="3" + :files="files" + :onChange="(files,operationType,index)=>handleChange(files,operationType,index)" + :onFail="evt=>handleFail(evt)" + :onImageClick="(index, file)=>handleImgView(index,file)" + /> + <AtCurtain + class="c-image-picker-view" + closeBtnPosition="top-right" + :isOpened="showImg" + :onClose="evt=>handleImgClose()" + > + <image + class="img" + mode="aspectFit" + :src="curtainImg" + /> + </AtCurtain> + </view> +</template> + +<script> +import { $ } from '@tarojs/extend'; +import { AtInput, AtImagePicker, AtCurtain } from 'taro-ui-vue'; +import './cImagePicker.scss'; + +export default { + name: 'CImagePicker', + components: { + AtInput, + AtImagePicker, + AtCurtain, + }, + props: { + itemData: Object, + }, + data() { + return { + showImg: false, + curtainImg: '', + }; + }, + computed: { + files() { + const value = this.itemData.formData[this.itemData.name]; + let files = []; + if (Object.prototype.toString.call(value) === '[object String]') { + files = value.split(',').map((url) => ({ url })); + } else if (Object.prototype.toString.call(value) === '[object Array]') { + files = value.map((url) => ({ url })); + } + files.push({ type: 'btn' }); + return files; + }, + }, + methods: { + handleChange(files, operationType, index) { + if (operationType === 'add') { + } + const value = []; + files.forEach((file) => { + if (file.type === 'btn') { + return; + } + value.push(file.url); + }); + this.itemData.onChange(value); + }, + handleImgView(index, file) { + this.curtainImg = file.url; + this.showImg = true; + }, + handleImgClose() { + this.showImg = false; + }, + handleFail(msg) { + Taro.showToast({ + title: msg, + icon: 'none', + mask: true, + duration: 2000, + }); + }, + }, + mounted() { + $(this.$refs.input.$el).find('.at-input__input').prepend(this.$refs.picker.$el); + }, +}; +</script> \ No newline at end of file diff --git a/forms/imagePicker/cImagePicker.scss b/forms/imagePicker/cImagePicker.scss new file mode 100644 index 0000000..ea70e6f --- /dev/null +++ b/forms/imagePicker/cImagePicker.scss @@ -0,0 +1,68 @@ +/** + * image picker + * @author Tevin + */ + +@import "../../common/sassMixin"; + +.c-image-picker { + .at-input { + padding: 0; + } + .at-input__input { + .weui-input { + display: none; + } + .at-image-picker { + padding-bottom: 0.25rem; + } + .at-image-picker__flex-box { + padding: 0.21333rem 0 0; + margin-left: -0.21333rem; + } + .at-image-picker__preview-img { + text-align: center; + background-color: #f8f8f8; + } + .at-image-picker__choose-btn { + .add-bar { + display: none; + &:last-child { + display: inline-block; + } + width: 1.6rem; + height: 1.6rem; + font-family: iconfont; + font-style: normal; + font-weight: 400; + font-variant: normal; + text-transform: none; + text-rendering: auto; + line-height: 1; + font-size: 1.6rem; + color: #cfe0ed; + -webkit-font-smoothing: antialiased; + vertical-align: middle; + background: none; + &::before { + content: "\e90f"; + } + } + } + } + .c-image-picker-view { + .at-curtain__container { + width: 95%; + height: 100%; + } + .at-curtain__body { + height: 75%; + } + .img { + width: 100%; + height: 100%; + text-align: center; + @include flexbox(flex, center center); + } + } +} \ No newline at end of file diff --git a/forms/imagePicker/index.js b/forms/imagePicker/index.js new file mode 100644 index 0000000..80b1495 --- /dev/null +++ b/forms/imagePicker/index.js @@ -0,0 +1,10 @@ +/** + * CImagePicker + * @author Tevin + */ + +import CImagePicker from '@components/forms/imagePicker/CImagePicker.vue'; + +export { + CImagePicker, +} \ No newline at end of file diff --git a/forms/input/CInput.vue b/forms/input/CInput.vue new file mode 100644 index 0000000..c09081c --- /dev/null +++ b/forms/input/CInput.vue @@ -0,0 +1,35 @@ +/** + * CInput + * @author Tevin + */ + +<template> + <AtInput + :name="itemData.name" + :title="itemData.label" + :type="type" + :placeholder="placeholder" + :required="itemData.required" + :error="itemData.error" + :value="itemData.formData[itemData.name]" + :onChange="evt=>itemData.onChange(evt)" + > + <slot /> + </AtInput> +</template> + +<script> +import { AtInput } from 'taro-ui-vue'; + +export default { + name: 'CInput', + components: { + AtInput, + }, + props: { + type: String, + placeholder: String, + itemData: Object, + }, +}; +</script> \ No newline at end of file diff --git a/forms/input/index.js b/forms/input/index.js new file mode 100644 index 0000000..76e3937 --- /dev/null +++ b/forms/input/index.js @@ -0,0 +1,10 @@ +/** + * CInput + * @author Tevin + */ + +import CInput from '@components/forms/input/CInput.vue'; + +export { + CInput, +} \ No newline at end of file diff --git a/forms/select/CSelect.vue b/forms/select/CSelect.vue new file mode 100644 index 0000000..9d3f847 --- /dev/null +++ b/forms/select/CSelect.vue @@ -0,0 +1,79 @@ +/** + * CSelect + * @author Tevin + */ + +<template> + <view class="c-select"> + <picker + mode="selector" + :range="range" + :value="current" + @change="evt=>handleChange(evt.detail)" + > + <AtInput + ref="input" + :name="itemData.name" + :title="itemData.label" + :required="itemData.required" + :error="itemData.error" + :placeholder="placeholder" + :value="selected" + > + <view class="at-icon at-icon-chevron-right" /> + </AtInput> + </picker> + </view> +</template> + +<script> +import { AtInput } from 'taro-ui-vue'; +import './cSelect.scss'; + +export default { + name: 'CSelect', + components: { + AtInput, + }, + props: { + options: Array, + placeholder: String, + itemData: Object, + }, + data() { + return { + optionKey: typeof this.options[0].value === 'undefined' ? 'id' : 'value', + }; + }, + computed: { + range() { + return (this.options || []).map((item) => item.name || item[this.optionKey]); + }, + current() { + const curVal = this.itemData.formData[this.itemData.name]; + for (let i = 0, item; (item = this.options[i]); i++) { + if (curVal === item[this.optionKey]) { + return i; + } + } + return -1; + }, + selected() { + const curVal = this.itemData.formData[this.itemData.name]; + for (let i = 0, item; (item = this.options[i]); i++) { + if (curVal === item[this.optionKey]) { + return item.name; + } + } + return ''; + }, + }, + methods: { + handleChange(evt) { + const item = this.options[evt.value]; + this.itemData.onChange(item[this.optionKey]); + }, + }, + mounted() {}, +}; +</script> \ No newline at end of file diff --git a/forms/select/cSelect.scss b/forms/select/cSelect.scss new file mode 100644 index 0000000..f817c46 --- /dev/null +++ b/forms/select/cSelect.scss @@ -0,0 +1,23 @@ +/** + * select + * @author Tevin + */ + +@import "../../common/sassMixin"; + +.c-select { + .at-input__input { + .weui-input { + pointer-events: none; + } + } + .at-input__children { + &::after { + display: none; + } + .at-icon-chevron-right { + font-size: 1.024rem; + color: #ccc; + } + } +} \ No newline at end of file diff --git a/forms/select/index.js b/forms/select/index.js new file mode 100644 index 0000000..0e6b3da --- /dev/null +++ b/forms/select/index.js @@ -0,0 +1,10 @@ +/** + * CSelect + * @author Tevin + */ + +import CSelect from '@components/forms/select/CSelect.vue'; + +export { + CSelect, +} \ No newline at end of file diff --git a/forms/textarea/CTextArea.vue b/forms/textarea/CTextArea.vue new file mode 100644 index 0000000..3a592ba --- /dev/null +++ b/forms/textarea/CTextArea.vue @@ -0,0 +1,50 @@ +/** + * CTextArea + * @author Tevin + */ + +<template> + <view class="c-textarea"> + <AtInput + ref="input" + :name="itemData.name" + :title="itemData.label" + :required="itemData.required" + :error="itemData.error" + /> + <textarea + ref="textarea" + class="textarea" + :style="{height: height || '2rem'}" + :placeholder="placeholder" + :value="itemData.formData[itemData.name]" + :autoFocus="true" + @input="evt=>itemData.onChange(evt.detail.value)" + /> + </view> +</template> + +<script> +import { $ } from '@tarojs/extend'; +import { AtInput } from 'taro-ui-vue'; +import './cTextArea.scss'; + +export default { + name: 'CTextArea', + components: { + AtInput, + }, + props: { + height: String, + placeholder: String, + itemData: Object, + }, + data() { + return {}; + }, + methods: {}, + mounted() { + $(this.$refs.input.$el).find('.at-input__input').prepend(this.$refs.textarea.$el); + }, +}; +</script> \ No newline at end of file diff --git a/forms/textarea/cTextArea.scss b/forms/textarea/cTextArea.scss new file mode 100644 index 0000000..4e41d6a --- /dev/null +++ b/forms/textarea/cTextArea.scss @@ -0,0 +1,22 @@ +/** + * image picker + * @author Tevin + */ + +@import "../../common/sassMixin"; + +.c-textarea { + .at-input__input { + .weui-input { + display: none; + } + .textarea { + width: 100%; + height: 2rem; + textarea { + resize: none; + @include flexbox(flex, center center); + } + } + } +} \ No newline at end of file diff --git a/forms/textarea/index.js b/forms/textarea/index.js new file mode 100644 index 0000000..74626a9 --- /dev/null +++ b/forms/textarea/index.js @@ -0,0 +1,10 @@ +/** + * CTextArea + * @author Tevin + */ + +import CTextArea from '@components/forms/textarea/CTextArea.vue'; + +export { + CTextArea, +} \ No newline at end of file -- Gitblit v1.9.1