WebApp【公共组件库】@前端(For Git Submodule)
Tevin
2020-12-01 b29ed998189c3de5e408f37d61802dfa30e4f9a8
优化表单模块数据传递机制,不再污染数据源
10 files modified
223 ■■■■■ changed files
forms/chinaArea/CChinaArea.vue 16 ●●●● patch | view | raw | blame | history
forms/form/CForm.vue 54 ●●●● patch | view | raw | blame | history
forms/form/CFormAgreement.vue 6 ●●●● patch | view | raw | blame | history
forms/form/CFormItem.vue 32 ●●●● patch | view | raw | blame | history
forms/imagePicker/CImagePicker.vue 14 ●●●● patch | view | raw | blame | history
forms/input/CInput.vue 15 ●●●● patch | view | raw | blame | history
forms/input/CInputPhoneCode.vue 44 ●●●●● patch | view | raw | blame | history
forms/select/CSelect.vue 16 ●●●● patch | view | raw | blame | history
forms/switch/CSwitch.vue 12 ●●●● patch | view | raw | blame | history
forms/textarea/CTextArea.vue 14 ●●●● patch | view | raw | blame | history
forms/chinaArea/CChinaArea.vue
@@ -16,10 +16,10 @@
            <view @tap="evt=>handleOpen(evt)">
                <AtInput
                    ref="input"
                    :name="itemData.name"
                    :title="itemData.label"
                    :required="itemData.required"
                    :error="itemData.error"
                    :name="itemRes.name"
                    :title="itemRes.label"
                    :required="itemRes.required"
                    :error="itemRes.error"
                    :placeholder="placeholder"
                    :value="selected"
                >
@@ -49,7 +49,7 @@
            default: false,
        },
        placeholder: String,
        itemData: Object,
        itemRes: Object,
    },
    data() {
        return {
@@ -59,7 +59,7 @@
    },
    computed: {
        selected() {
            const curVal = this.itemData.formData[this.itemData.name];
            const curVal = this.itemRes.formData[this.itemRes.name];
            if (curVal && curVal.length === 3) {
                return getRegionNames(curVal).join(' / ');
            } else {
@@ -74,7 +74,7 @@
                evt.preventDefault();
                return;
            }
            const curVal = this.itemData.formData[this.itemData.name];
            const curVal = this.itemRes.formData[this.itemRes.name];
            const range = [
                locationTree,
                locationTree[0].children,
@@ -136,7 +136,7 @@
            codes[1] = city.value;
            const area = city.children[detail[2]];
            codes[2] = area.value;
            this.itemData.onChange(codes);
            this.itemRes.onChange(codes);
        },
    },
    mounted() {},
forms/form/CForm.vue
@@ -8,7 +8,7 @@
        class="c-form"
        @submit="evt=>handleSubmit()"
    >
        <slot :formData="formData" />
        <slot :formRes="formRes" />
    </form>
</template>
@@ -25,6 +25,9 @@
    },
    data() {
        return {
            formRes: {
                formData: this.formData,
            },
            validators: {},
        };
    },
@@ -32,10 +35,14 @@
        handleSubmit() {
            const checklist = [];
            Object.keys(this.validators).forEach((key) => {
                checklist.push(this.validators[key]());
                // 仍有效的验证器
                if (this.validators[key]) {
                    checklist.push(this.validators[key]());
                }
            });
            Promise.all(checklist).then((validations) => {
                for (let validation of validations) {
                    // 第一个不通过项提示
                    if (!validation.passed) {
                        Taro.showToast({
                            title: validation.msg,
@@ -50,19 +57,52 @@
                this.onFinish && this.onFinish();
            });
        },
        preCheckValidators(callback) {
            const checklist = [];
            Object.keys(this.validators).forEach((key) => {
                // 仍有效的验证器
                if (this.validators[key]) {
                    checklist.push(this.validators[key]('msgOnly'));
                }
            });
            Promise.all(checklist).then((validations) => {
                for (let validation of validations) {
                    // 跳过同意协议
                    if (validation.name === '$agreement') {
                        continue;
                    }
                    if (!validation.passed) {
                        Taro.showToast({
                            title: validation.msg,
                            icon: 'none',
                            mask: false,
                            duration: 2000,
                        });
                        // 检查失败
                        callback && callback(false);
                        return;
                    }
                }
                // 检查通过
                callback && callback(true);
            });
        },
    },
    mounted() {
        this.formData.$handleChange = (evt = []) => {
        // 当表单项变化时
        this.formRes.$handleChange = (evt = []) => {
            Object.keys(evt).forEach((key) => {
                if (typeof this.formData[key] === 'undefined') {
                    this.$set(this.formData, key, evt[key]);
                // 直接改值
                if (typeof this.formRes.formData[key] === 'undefined') {
                    this.$set(this.formRes.formData, key, evt[key]);
                } else {
                    this.formData[key] = evt[key];
                    this.formRes.formData[key] = evt[key];
                }
            });
            this.onChange && this.onChange(evt);
        };
        this.formData.$regItemValidator = (name, cb) => {
        // 注册表单验证器
        this.formRes.$regItemValidator = (name, cb) => {
            this.validators[name] = cb;
        };
    },
forms/form/CFormAgreement.vue
@@ -38,12 +38,12 @@
        AtFloatLayout,
    },
    props: {
        formData: Object,
        formRes: Object,
        protocol: String,
    },
    data() {
        return {
            name: 'agreement',
            name: '$agreement',
            protocolShow: false,
            checked: false,
        };
@@ -61,7 +61,7 @@
    },
    mounted() {
        this.$nextTick(() => {
            this.formData.$regItemValidator(this.name, () => {
            this.formRes.$regItemValidator(this.name, () => {
                if (this.checked) {
                    return Promise.resolve({
                        name: this.name,
forms/form/CFormItem.vue
@@ -6,7 +6,7 @@
<template>
    <view class="c-form-item">
        <slot :itemData="itemData" />
        <slot :itemRes="itemRes" />
    </view>
</template>
@@ -21,7 +21,7 @@
        label: String,
        required: Boolean,
        rules: Array,
        formData: Object,
        formRes: Object,
    },
    data() {
        return {
@@ -29,9 +29,9 @@
        };
    },
    computed: {
        itemData() {
        itemRes() {
            return {
                formData: this.formData,
                formData: this.formRes.formData,
                name: this.name,
                label: this.label,
                required: this.isRequired,
@@ -71,10 +71,10 @@
                }
            }
            // 未改变值不触发
            if (this.formData[this.name] === evt) {
            if (this.formRes.formData[this.name] === evt) {
                return;
            }
            this.formData.$handleChange({
            this.formRes.$handleChange({
                [this.name]: evt,
            });
        },
@@ -83,7 +83,7 @@
        this.$nextTick(() => {
            // 未设置验证
            if (!this.required && !this.rules) {
                this.formData.$regItemValidator(this.name, () => {
                this.formRes.$regItemValidator(this.name, (validateType) => {
                    this.error = false;
                    return Promise.resolve({
                        name: this.name,
@@ -105,10 +105,10 @@
                validator.messages(validateMsgs);
                // 注册验证
                let errTimer = null;
                this.formData.$regItemValidator(this.name, () => {
                this.formRes.$regItemValidator(this.name, (validateType) => {
                    return validator
                        .validate({
                            [this.name]: this.formData[this.name],
                            [this.name]: this.formRes.formData[this.name],
                        })
                        .then(
                            (res) => {
@@ -119,11 +119,13 @@
                                };
                            },
                            ({ errors, fields }) => {
                                this.error = true;
                                clearTimeout(errTimer);
                                errTimer = setTimeout(() => {
                                    this.error = false;
                                }, 5000);
                                if (validateType !== 'msgOnly') {
                                    this.error = true;
                                    clearTimeout(errTimer);
                                    errTimer = setTimeout(() => {
                                        this.error = false;
                                    }, 5000);
                                }
                                return {
                                    name: this.name,
                                    passed: false,
@@ -139,7 +141,7 @@
        });
    },
    beforeDestroy() {
        this.formData.$regItemValidator(this.name, null);
        this.formRes.$regItemValidator(this.name, null);
    },
};
</script>
forms/imagePicker/CImagePicker.vue
@@ -7,10 +7,10 @@
    <view class="c-image-picker">
        <AtInput
            ref="input"
            :name="itemData.name"
            :title="itemData.label"
            :required="itemData.required"
            :error="itemData.error"
            :name="itemRes.name"
            :title="itemRes.label"
            :required="itemRes.required"
            :error="itemRes.error"
        />
        <AtImagePicker
            ref="picker"
@@ -51,7 +51,7 @@
        AtCurtain,
    },
    props: {
        itemData: Object,
        itemRes: Object,
    },
    data() {
        return {
@@ -61,7 +61,7 @@
    },
    computed: {
        files() {
            const value = this.itemData.formData[this.itemData.name];
            const value = this.itemRes.formData[this.itemRes.name];
            let files = [];
            if (Object.prototype.toString.call(value) === '[object String]') {
                files = value.split(',').map((url) => ({ url }));
@@ -83,7 +83,7 @@
                }
                value.push(file.url);
            });
            this.itemData.onChange(value);
            this.itemRes.onChange(value);
        },
        handleImgView(index, file) {
            this.curtainImg = file.url;
forms/input/CInput.vue
@@ -5,14 +5,14 @@
<template>
    <AtInput
        :name="itemData.name"
        :title="itemData.label"
        :name="itemRes.name"
        :title="itemRes.label"
        :type="type"
        :placeholder="placeholder"
        :required="itemData.required"
        :error="itemData.error"
        :value="itemData.formData[itemData.name]"
        :onChange="evt=>itemData.onChange(evt)"
        :required="itemRes.required"
        :error="itemRes.error"
        :value="itemRes.formData[itemRes.name]"
        :onChange="evt=>itemRes.onChange(evt)"
    >
        <slot />
    </AtInput>
@@ -29,7 +29,8 @@
    props: {
        type: String,
        placeholder: String,
        itemData: Object,
        itemRes: Object,
    },
    mounted() {},
};
</script>
forms/input/CInputPhoneCode.vue
@@ -6,14 +6,14 @@
<template>
    <view class="c-input-phone-code">
        <AtInput
            :name="itemData.name"
            :title="itemData.label"
            :name="itemRes.name"
            :title="itemRes.label"
            type="text"
            :placeholder="placeholder"
            :required="itemData.required"
            :error="itemData.error"
            :value="itemData.formData[itemData.name]"
            :onChange="evt=>itemData.onChange(evt)"
            :required="itemRes.required"
            :error="itemRes.error"
            :value="itemRes.formData[itemRes.name]"
            :onChange="evt=>itemRes.onChange(evt)"
        >
            <AtButton
                class="c-input-phone-btn"
@@ -38,7 +38,8 @@
    },
    props: {
        placeholder: String,
        itemData: Object,
        itemRes: Object,
        autoStart: { type: Boolean, default: true },
        onCallPhoneCode: Function,
    },
    data() {
@@ -61,16 +62,25 @@
            if (this.holding) {
                return;
            }
            this.holding = true;
            this.cd = 60;
            this.onCallPhoneCode();
            const timer = setInterval(() => {
                this.cd -= 1;
                if (this.cd === 0) {
                    this.holding = false;
                    this.cd = 60;
                }
            }, 1000);
            const startCD = () => {
                this.holding = true;
                this.cd = 60;
                setInterval(() => {
                    this.cd -= 1;
                    if (this.cd === 0) {
                        this.holding = false;
                        this.cd = 60;
                    }
                }, 1000);
            };
            if (this.autoStart) {
                startCD();
                this.onCallPhoneCode(() => null);
            } else {
                this.onCallPhoneCode(() => {
                    startCD();
                });
            }
        },
    },
};
forms/select/CSelect.vue
@@ -14,10 +14,10 @@
        >
            <AtInput
                ref="input"
                :name="itemData.name"
                :title="itemData.label"
                :required="itemData.required"
                :error="itemData.error"
                :name="itemRes.name"
                :title="itemRes.label"
                :required="itemRes.required"
                :error="itemRes.error"
                :placeholder="placeholder"
                :value="selected"
            >
@@ -39,7 +39,7 @@
    props: {
        options: Array,
        placeholder: String,
        itemData: Object,
        itemRes: Object,
    },
    data() {
        return {};
@@ -49,7 +49,7 @@
            return typeof (this.options[0] || {}).value === 'undefined' ? 'id' : 'value';
        },
        current() {
            const curVal = this.itemData.formData[this.itemData.name];
            const curVal = this.itemRes.formData[this.itemRes.name];
            for (let i = 0, item; (item = this.options[i]); i++) {
                if (curVal === item[this.optionKey]) {
                    return i;
@@ -58,7 +58,7 @@
            return -1;
        },
        selected() {
            const curVal = this.itemData.formData[this.itemData.name];
            const curVal = this.itemRes.formData[this.itemRes.name];
            for (let i = 0, item; (item = this.options[i]); i++) {
                if (curVal === item[this.optionKey]) {
                    return item.name;
@@ -70,7 +70,7 @@
    methods: {
        handleChange(evt) {
            const item = this.options[evt.value];
            this.itemData.onChange(item[this.optionKey]);
            this.itemRes.onChange(item[this.optionKey]);
        },
    },
    mounted() {},
forms/switch/CSwitch.vue
@@ -6,9 +6,9 @@
<template>
    <view :class="['c-switch', className]">
        <AtSwitch
            :title="itemData.label"
            :checked="itemData.formData[itemData.name]"
            :onChange="evt=>itemData.onChange(evt)"
            :title="itemRes.label"
            :checked="itemRes.formData[itemRes.name]"
            :onChange="evt=>itemRes.onChange(evt)"
        />
    </view>
</template>
@@ -21,7 +21,7 @@
    name: 'CSwitch',
    components: { AtSwitch },
    props: {
        itemData: Object,
        itemRes: Object,
    },
    data() {
        return {};
@@ -29,10 +29,10 @@
    computed: {
        className() {
            let className = '';
            if (this.itemData.required) {
            if (this.itemRes.required) {
                className += 'c-switch-required ';
            }
            if (this.itemData.error) {
            if (this.itemRes.error) {
                className += 'c-switch-error ';
            }
            return className;
forms/textarea/CTextArea.vue
@@ -7,19 +7,19 @@
    <view class="c-textarea">
        <AtInput
            ref="input"
            :name="itemData.name"
            :title="itemData.label"
            :required="itemData.required"
            :error="itemData.error"
            :name="itemRes.name"
            :title="itemRes.label"
            :required="itemRes.required"
            :error="itemRes.error"
        />
        <textarea
            ref="textarea"
            class="textarea"
            :style="{height: height || '2rem'}"
            :placeholder="placeholder"
            :value="itemData.formData[itemData.name]"
            :value="itemRes.formData[itemRes.name]"
            :autoFocus="true"
            @input="evt=>itemData.onChange(evt.detail.value)"
            @input="evt=>itemRes.onChange(evt.detail.value)"
        />
    </view>
</template>
@@ -37,7 +37,7 @@
    props: {
        height: String,
        placeholder: String,
        itemData: Object,
        itemRes: Object,
    },
    data() {
        return {};