WebApp【公共组件库】@前端(For Git Submodule)
实现表单第二部分:验证优化、普通文本框、图片选择框、文本域、下拉选择框
13 files added
3 files modified
541 ■■■■■ changed files
forms/form/CForm.vue 6 ●●●● patch | view | raw | blame | history
forms/form/CFormItem.vue 57 ●●●● patch | view | raw | blame | history
forms/form/CFormSubmit.vue 17 ●●●●● patch | view | raw | blame | history
forms/form/cForm.scss 34 ●●●●● patch | view | raw | blame | history
forms/form/index.js 2 ●●●●● patch | view | raw | blame | history
forms/imagePicker/CImagePicker.vue 108 ●●●●● patch | view | raw | blame | history
forms/imagePicker/cImagePicker.scss 68 ●●●●● patch | view | raw | blame | history
forms/imagePicker/index.js 10 ●●●●● patch | view | raw | blame | history
forms/input/CInput.vue 35 ●●●●● patch | view | raw | blame | history
forms/input/index.js 10 ●●●●● patch | view | raw | blame | history
forms/select/CSelect.vue 79 ●●●●● patch | view | raw | blame | history
forms/select/cSelect.scss 23 ●●●●● patch | view | raw | blame | history
forms/select/index.js 10 ●●●●● patch | view | raw | blame | history
forms/textarea/CTextArea.vue 50 ●●●●● patch | view | raw | blame | history
forms/textarea/cTextArea.scss 22 ●●●●● patch | view | raw | blame | history
forms/textarea/index.js 10 ●●●●● patch | view | raw | blame | history
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) => {
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,
forms/form/CFormSubmit.vue
New file
@@ -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>
forms/form/cForm.scss
New file
@@ -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;
            }
        }
    }
}
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,
}
forms/imagePicker/CImagePicker.vue
New file
@@ -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>
forms/imagePicker/cImagePicker.scss
New file
@@ -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);
        }
    }
}
forms/imagePicker/index.js
New file
@@ -0,0 +1,10 @@
/**
 * CImagePicker
 * @author Tevin
 */
import CImagePicker from '@components/forms/imagePicker/CImagePicker.vue';
export {
    CImagePicker,
}
forms/input/CInput.vue
New file
@@ -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>
forms/input/index.js
New file
@@ -0,0 +1,10 @@
/**
 * CInput
 * @author Tevin
 */
import CInput from '@components/forms/input/CInput.vue';
export {
    CInput,
}
forms/select/CSelect.vue
New file
@@ -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>
forms/select/cSelect.scss
New file
@@ -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;
        }
    }
}
forms/select/index.js
New file
@@ -0,0 +1,10 @@
/**
 * CSelect
 * @author Tevin
 */
import CSelect from '@components/forms/select/CSelect.vue';
export {
    CSelect,
}
forms/textarea/CTextArea.vue
New file
@@ -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>
forms/textarea/cTextArea.scss
New file
@@ -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);
            }
        }
    }
}
forms/textarea/index.js
New file
@@ -0,0 +1,10 @@
/**
 * CTextArea
 * @author Tevin
 */
import CTextArea from '@components/forms/textarea/CTextArea.vue';
export {
    CTextArea,
}