实现表单第二部分:验证优化、普通文本框、图片选择框、文本域、下拉选择框
13 files added
3 files modified
| | |
| | | |
| | | <script> |
| | | import Taro from '@tarojs/taro'; |
| | | import './cForm.scss'; |
| | | |
| | | export default { |
| | | name: 'CForm', |
| | |
| | | }, |
| | | }, |
| | | 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) => { |
| | |
| | | |
| | | <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> |
| | | |
| | |
| | | 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) { |
| | |
| | | // 未设置验证 |
| | | 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({ |
| | |
| | | const validator = new Schema({ |
| | | [this.name]: descriptor, |
| | | }); |
| | | validator.messages(validateMsgs); // 自定义验证消息 |
| | | // 汉化通用验证消息 |
| | | validator.messages(validateMsgs); |
| | | // 注册验证 |
| | | let errTimer = null; |
| | | this.formData.$regItemValidator(this.name, () => { |
| | | return validator |
| | | .validate({ |
| | |
| | | }) |
| | | .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, |
New file |
| | |
| | | <template> |
| | | <view class="c-form-submit"> |
| | | <button |
| | | form-type="submit" |
| | | type="primary" |
| | | > |
| | | <slot>提交</slot> |
| | | </button> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'CFormSubmit', |
| | | props: {}, |
| | | }; |
| | | </script> |
New file |
| | |
| | | /** |
| | | * 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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | 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, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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> |
New file |
| | |
| | | /** |
| | | * 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); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | /** |
| | | * CImagePicker |
| | | * @author Tevin |
| | | */ |
| | | |
| | | import CImagePicker from '@components/forms/imagePicker/CImagePicker.vue'; |
| | | |
| | | export { |
| | | CImagePicker, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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> |
New file |
| | |
| | | /** |
| | | * CInput |
| | | * @author Tevin |
| | | */ |
| | | |
| | | import CInput from '@components/forms/input/CInput.vue'; |
| | | |
| | | export { |
| | | CInput, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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> |
New file |
| | |
| | | /** |
| | | * 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; |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | /** |
| | | * CSelect |
| | | * @author Tevin |
| | | */ |
| | | |
| | | import CSelect from '@components/forms/select/CSelect.vue'; |
| | | |
| | | export { |
| | | CSelect, |
| | | } |
New file |
| | |
| | | /** |
| | | * 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> |
New file |
| | |
| | | /** |
| | | * 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); |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | /** |
| | | * CTextArea |
| | | * @author Tevin |
| | | */ |
| | | |
| | | import CTextArea from '@components/forms/textarea/CTextArea.vue'; |
| | | |
| | | export { |
| | | CTextArea, |
| | | } |