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