uni-app 封装通用表单组件

4,537 阅读1分钟

表单封装

由于表单的通用性,封装了一个基于uview的表单,因为后端需要的是每编辑一条就保存一条便在其中暴露出来了userLeave事件,目前只有几个通用组件,后续可以添加通用扩展

ComForm/index.vue

<template>
  <view class="com-form">
    <u-form
      v-if="formRest"
      :model="formData"
      ref="uForm"
      label-width="140"
    >
      <u-form-item
        v-for="(item, key) in formDesc"
        :key="key"
        :label="['rate', 'PropertyType'].includes(item.type) ? '' : item.label"
        :label-style="item.labelStyle"
      >
        <!-- input -->
        <u-input @blur="onUserLeave({item, value: formData[key]})" :clearable="false" v-if="item.type === 'input'" v-model="formData[key]" :input-align="item.attr.inputAlign" :placeholder="item.attr.placeholder" />
        
        <!-- select -->
        <template v-if="item.type === 'select'">
          <u-input 
						type="select"
						:value="formData[key]"
						@click="item.show = true"
            :placeholder="item.attr.placeholder"
						:input-align="item.attr.inputAlign"
					/>
          <u-select
            v-model="item.show"
            :mode="item.mode"
            :list="item.options"
            :label-name="item.attr.labelName"
            :value-name="item.attr.valueName"
            @confirm="e => select(e, key, item)"
          />
        </template>

        <!-- houseType -->
        <u-input
          v-if="item.type === 'houseType'"
          :value="formData[key]"
          type="select"
          @click="houseSelectOpen(key, item)"
          :placeholder="item.attr.placeholder"
          :input-align="item.attr.inputAlign"
        />
        
        <!-- region -->
        <template v-if="item.type === 'region'">
          <u-input
            :value="formData[key] && (formData[key].province.label + formData[key].city.label + formData[key].area.label)"
            type="select"
            @click="regionShow = true"
            :placeholder="item.attr.placeholder"
            :input-align="item.attr.inputAlign"
          />
          <u-picker
            mode="region"
            v-model="regionShow"
            :default-region="formData[key] && [formData[key].province.label, formData[key].city.label, formData[key].area.label]"
            @confirm="e => regionChange(e, key, item)"
          />
        </template>

        <!-- rate -->
        <rate
          v-if="item.type === 'rate'"
          :label="item.label"
          :init-data="formData[key]"
          @change="e => rateChange(e, key, item)"
        />

        <!-- Tags -->
        <tags
          v-if="item.type === 'PropertyType'"
          v-model="formData[key]"
          @tagChange="onUserLeave({item, value: formData[key]})"
          :label="item.label"
          :custom="item.allowCustom"
        />

      </u-form-item>
    </u-form>
    <house-select ref="houseSelect" :seleData="houseListData" label="户型选择" @change="numSeleChange" />
  </view>
</template>

<script>
import HouseSelect from '@/components/houseType/compoundNumSele'
import Rate from './components/Rate'
import Tags from './components/Tags'

export default {
  name: 'ComForm',
  components: {
    HouseSelect,
    Rate,
    Tags
  },
  model: {
    prop: 'formData',
    event: 'input'
  },
  props: {
    // 表单描述
    formDesc: {
      type: Object,
      required: true
    },
    // 表单数据
    formData: {
      type: Object,
      required: true
    }
  },
  data () {
    return {
      defaultRegion: ['江西省', '南昌市', '西湖区'],
      regionShow: false,
      formRest: true,
      houseListData: [
        {
          label"卧室数量",
          key"字段名",
          value"选中的值",
          unit"室",
          customfalse//当前值是否是自定义的
          customValue:''//自定义的值
          data: [
            { label"1"value"1"chechedfalse },
            { label"2"value"2"chechedfalse },
            { label"3"value"3"chechedfalse }
          ]
        },
        {
          label"客厅餐厅数量",
          key"字段名",
          value"选中的值",
          unit"厅",
          customfalse//当前值是否是自定义的
          customValue:''//自定义的值
          data: [
            { label"0"value"0"chechedfalse },
            { label"1"value"1"chechedfalse },
            { label"2"value"2"chechedfalse }
          ]
        },
        {
          label"卫生间数量",
          key"字段名",
          value"选中的值",
          unit"卫",
          customfalse//当前值是否是自定义的
          customValue:''//自定义的值
          data: [
            { label"0"value"0"chechedfalse },
            { label"1"value"1"chechedfalse },
            { label"2"value"2"chechedfalse }
          ]
        }
      ]
    }
  },
  methods: {
    getValue(field) {
      return this.formData[field]
    },
    houseSelectOpen (key, item) {
			this.$refs.houseSelect.open(key, item)
    },
    onUserLeave(e) { // 用户离开事件
      this.$emit('userLeave', e)
    },
    numSeleChange ({list, key, item}) {
	  this.houseListData = list
	  let value = ''
      this.houseListData.map((item, index) => {
          if (item.custom) {
              value === '' ? value = item.customValue + item.unit : value = value + item.customValue + item.unit
          } else {
              value === '' ? value = item.value + item.unit : value = value + item.value + item.unit
          }
      })
      this.formData[key] = value
      this.onUserLeave({item, value: this.formData[key]})
    },
    regionChange (e, key, item) {
      this.formData[key] = e
      this.onUserLeave({item, value: this.formData[key]})
    },
    select (e, key, item) {
      let str = ''
      e.forEach(item => {
        str += item.value
      })
      this.formData[key] = str

      this.onUserLeave({item, value: this.formData[key]})
    },
    rateChange (e, key, item) {
      this.formData[key] = e
      this.onUserLeave({item, value: this.formData[key]})
    }
  }
}
</script>

星级组件

ComForm/components/Rate.vue

<template>
  <view class="rate">
    <view class="title">
      <view>{{label}}</view>
      <!-- <view>一般</view> -->
    </view>
    <view class="rate__content">
      <u-rate
        :count="count"
        :size="size"
        :active-color="activeColor"
        v-model="rate"
        @change="e => $emit('change', e)"
      />
    </view>
  </view>
</template>

<script>
export default {
  name: 'Rate',
  props: {
    label: {
      type: String,
      default: '客户意向度'
    },
    initData: {
      type: Number,
      default: 0
    }
  },
  data () {
    return {
      count: 10,
      size: '53',
      rate: 0,
      activeColor: '#FF7716'
    }
  },
  created () {
    this.rate = this.initData
  }
}
</script>

<style lang="scss" scoped>
.rate {
  width: 100vw;
  margin: -20rpx -32rpx 0;
  border-top: 10rpx solid #F7F8FA;
  padding: 27rpx 32rpx;
  background: white;
  .title {
    display: flex;
    justify-content: space-between;
  }
  &__content {
    margin-top: 30rpx;
    margin-left: -5rpx;
  }
}
</style>

标签组件

ComForm/components/Tags.vue

<template>
  <view class="tags">
    <view class="title">{{label}}</view>
    <view>
      <template v-for="(tag, index) in tags">
        <u-tag
          v-if="tag.selected"
          :key="tag.text + index"
          :text="tag.text"
          mode="dark"
          type="primary"
          :closeable="custom"
          @click="tag.selected = false"
          @close="removeTag(index)"
        />
        <u-tag
          v-else
          :key="tag.text + index"
          :text="tag.text"
          mode="dark"
          type="info"
          bg-color="#F7F8FA"
          color="#323233"
          :closeable="custom"
          @click="tag.selected = true"
          @close="removeTag(index)"
        />
      </template>
      <view v-if="custom" class="add" @click="modalShow = true">
        <text class="iconfont icon-jiahao" />添加标签
      </view>
    </view>
    <u-modal 
      v-model="modalShow"
      show-cancel-button
      :mask-close-able="true"
      title="添加标签"
      confirm-text="保存"
      @confirm="addTag"
    >
      <view class="slot-content">
        <u-field
          v-model="modalValue"
          label-width="0"
          placeholder="标签不超过8个字"
          :border-bottom="false"
          focus
          maxlength="8"
          confirm-type="保存"
          :field-style="fieldStyle"
        />
      </view>
   </u-modal>
  </view>
</template>

<script>
export default {
  name: 'Tags',
  model: {
    prop: 'tags',
    event: 'input'
  },
  props: {
    tags: {
      type: Array,
      default: () => []
    },
    label: {
      type: String,
      default: '物业类型'
    },
    custom: {
      type: Boolean,
      default: false
    }
  },
  watch: {
    tags: {
      deep: true,
      handler (newVal) {
        this.$emit('tagChange')
        // this.$emit('input', newVal)
      }
    }
  },
  data () {
    return {
      modalShow: false,
      modalValue: '',
      fieldStyle: {
        border: '1px solid #C0C4CC',
        padding: '13rpx 21rpx',
        borderRadius: '4px'
      }
    }
  },
  methods: {
    addTag () {
      this.tags.push({
        text: this.modalValue,
        selected: true
      })
      this.modalValue = ''
    },
    removeTag (index) {
      this.tags.splice(index, 1)
    }
  }
}
</script>

<style lang="scss" scoped>
.tags {
  margin-bottom: 25rpx;
  .title {
    margin-bottom: 18rpx;
  }
  u-tag, .u-tag {
    display: inline-block;
    margin-right: 20rpx;
    margin-bottom: 20rpx;
    font-size: 26rpx;
    &:last-child {
      margin-right: 0;
    }
  }
  .add {
    position: relative;
    display: inline-block;
    line-height: initial;
    font-size: 24rpx;
    padding: 12rpx 22rpx;
    padding-left: 70rpx;
    background: #F7F8FA;
    border-radius: 3px;
    .iconfont {
      position: absolute;
      top: 50%;
      left: 22rpx;
      margin-right: 10rpx;
      transform: translateY(-50%);
    }
  }
}
</style>

使用

<template>
	<com-form
      v-model="form"
      :form-desc="formDesc"
      @userLeave="onUserLeave"
    />
</template>

<script>
const formType = [
  'input', // 单行文本
  'textarea', // 多行文本
  'region', // 地址
  'date', // 时间
  'select', // 下拉框
  'PropertyType', // 标签选择
  'houseType', // 户型选择
  'rate', // 星级意向度
]

export default {
    data () {
        return {
            formDesc: {
				name: {
					type: 'input',
					label: '姓名',
					attr: {
						placeholder: '请备注客户姓名',
						inputAlign: 'right'
					}
				},
				phone: {
					type: 'input',
					label: '电话',
					attr: {
						placeholder: '请备注客户电话',
						inputAlign: 'right'
					}
				},
				houseType: {
					type: 'houseType',
					label: '房型',
					attr: {
						placeholder: '请备注户型',
						inputAlign: 'right'
					}
				},
				region: {
					type: 'region',
					label: '地区范围',
					attr: {
						placeholder: '请备注地区范围',
						inputAlign: 'right'
					}
				},
				rate: {
					type: 'rate'
				}
			},
            form: {}
        }
    },
    methods: {
        setFormDesc (data) { // 渲染form
			const formDesc = {}
			const form = {}
			data.forEach((item, index) => {
				formDesc[item.code] = {
					type: formType[item.dataType],
					label: item.label,
					allowCustom: item.allowCustom,
					attr: {
						placeholder: item.placeholder,
						inputAlign: 'right',
						customIntentionId: item.customIntentionId,
						id: item.id,
						code: item.code
					}
				}

				if (formType[item.dataType] === 'region') { // 地区处理
					let splitArr = []
					let data = {
						province: {
							label: ''
						},
						city: {
							label: ''
						},
						area: {
							label: ''
						}
					}
					if (item.customerIntentionValue) {
						splitArr = item.customerIntentionValue.split('|')
					} else if (item.initData) {
						splitArr = item.initData.split('|')
					}
					if (splitArr.length) {
						data = {
							province: {
								label: splitArr[0]
							},
							city: {
								label: splitArr[1]
							},
							area: {
								label: splitArr[2]
							}
						}
					}
					form[item.code] = data
				} else if (formType[item.dataType] === 'PropertyType') { // 标签类型特殊处理
					const data = []
					let splitArr = item.initData ? item.initData.split('|') : []
					let splitSelectedArr = item.customerIntentionValue ? item.customerIntentionValue.split('|') : []
					// if (item.customerIntentionValue) {
					// 	splitArr = item.customerIntentionValue.split('|')
					// } else if (item.initData) {
					// 	splitArr = item.initData.split('|')
					// }
				
					if (item.allowCustom && splitSelectedArr !== '') {	// 后端要求,自定义标签只有选中的
						splitSelectedArr.forEach(el => {
							let tagItem = {
								text: el,
								selected: true
							}
							data.push(tagItem)
						})
						form[item.code] = data
						return
					}
					if (splitArr !== '') { // 普通标签
						splitArr.forEach(el => { // 赋值
							let tagItem = {
								text: el,
								selected: false
							}
							if (splitSelectedArr !== '') {
								let splitSelected = splitSelectedArr.find(selectedItem => selectedItem === el)
								if (splitSelected) {
									tagItem.selected = true
								}
							}
							data.push(tagItem)
						})
					}
					form[item.code] = data
				} else if (formType[item.dataType] === 'rate') { // 后端传的字符串转换一下
					form[item.code] = Number(item.customerIntentionValue) || Number(item.initData)
				} else {
					form[item.code] = item.customerIntentionValue || item.initData
				}
			})
			this.formDesc = formDesc
			this.form = form
		}
    }
}
</script>