vue2 + iview 自定义表单

154 阅读2分钟
// c-form.vue

<template>
  <div class="filter-wrapper" :class="layout">
    <div class="filter-form-item-wrapper" v-for="(config, key) in configs" :key="key" v-show="!config.hide">
      <div class="filter-form-item-control">
        <div class="filter-form-item-label" :style="labelStyle">
          <span v-if="config.validate" style="color: #f60;font-size: 16px">*</span>
          <!-- <NBeautyLabel :text="config.label" /> -->
          {{ config.label }}
        </div>
        <div class="filter-form-item-colon">:</div>
        <div class="filter-form-item-box" :class="hasSwitch ? 'has-switch' : ''">
          <!-- text -->
          <template v-if="config.type === 'text'">
            <div class="text" :style="{ width: config.diffVal ? '50%' : '100%' }">{{ config.value }}</div>&nbsp;
            <div class="text" :style="{ width: '50%' }" v-if="config.diffVal">{{ config.diffVal }}</div>
          </template>
          <!-- input -->
          <template v-if="config.type.includes('input')">
            <!-- input -->
            <template v-if="config.type === 'input'">
              <Input @on-focus="onFocus($event, config)" v-model="config.value" :placeholder="config.placeholder" :style="{ ...config.style, ...style }" :disabled="config.disabled" />
            </template>
            <!-- input-textarea -->
            <template v-if="config.type === 'input-textarea'">
              <Input type="textarea" :autosize="true" v-model="config.value" :placeholder="config.placeholder" :style="{ ...config.style, ...style }" :disabled="config.disabled" />
            </template>
            <!-- input-range -->
            <template v-if="config.type === 'input-range'">
              <Input v-model="config.value" placeholder="min" :style="{ ...config.style, ...style }" :disabled="config.disabled" />
              &nbsp;~&nbsp;
              <Input v-model="config.value2" placeholder="max" :style="{ ...config.style, ...style }" :disabled="config.disabled" />
            </template>
            <!-- input-number -->
            <template v-if="config.type === 'input-number'">
              <InputNumber @on-focus="onFocus($event, config)" :max="config.maxNum" :min="config.minNum" v-model="config.value" :formatter="config.formatter" :placeholder="config.placeholder" :style="{ width: '100%', ...config.style, ...style }" :disabled="config.disabled" />
            </template>
            <!-- input + number + slider -->
            <template v-if="config.type === 'input-slider-number'">
              <Slider :style="{ ...config.style, ...style, marginLeft: '10px' }" :min="1" :max="10000" v-model="config.value"/>
              <Input type="number" number style="width: 100px;margin-left: 10px;" v-model="config.value"/>
            </template>
            <!-- input + select -->
            <template v-if="config.type === 'input-select'">
              <Input v-model="config.value" :style="{ ...config.style, ...style }" :disabled="config.disabled" >
                <Select style="width: 80px" v-model="config.value2" slot="prepend">
                  <Option
                    v-for="(option, index) in config.options"
                    v-bind:key="index"
                    :value="option.value"
                  >{{ option.label }}</Option>
                </Select>
              </Input>
            </template>
            <!-- input range + select -->
            <template v-if="config.type === 'input-range-select'">
              <Select style="width: 33%" v-model="config.value" clearable @on-clear="onClear(key)">
                <Option
                  v-for="(option, index) in config.options"
                  v-bind:key="index"
                  :value="option.value"
                >{{ option.label }}</Option>
              </Select>
              &nbsp;:&nbsp;
              <InputNumber v-model="config.value2" placeholder="min(不填为0)" :max="+config.value3 - 1" :style="{ ...config.style, ...style, width: '30%' }" :disabled="config.disabled" />
              &nbsp;~&nbsp;
              <InputNumber v-model="config.value3" placeholder="max(不填为0)" :min="+config.value2 + 1" :style="{ ...config.style, ...style, width: '30%' }" :disabled="config.disabled" />
            </template>
          </template>
          <!-- select -->
          <template v-if="config.type.includes('select')">
            <!-- select -->
            <template v-if="config.type === 'select'">
              <Select
                v-model="config.value"
                filterable
                @on-query-change="onSelectQueryChange($event, config)"
                :placeholder="config.placeholder"
                :disabled="config.disabled"
                :style="{ ...config.style, ...style }">
                <Option
                  v-for="(option, index) in config.options"
                  v-bind:key="index"
                  :value="option.value"
                >{{ option.label }}</Option>
                <!-- -{{ option.value }} -->
              </Select>
            </template>
            <!-- select-multi -->
            <template v-if="config.type === 'select-multi'">
              <Select
                v-model="config.value"
                multiple
                filterable
                :max-tag-count="1"
                :disabled="config.disabled"
                :placeholder="config.placeholder"
                :style="{ ...config.style, ...style }"
                @on-change="handleOnSelectMulti($event, key, config)">
                <Option :value="config.value.length === config.options.length ? 'empty' : 'all'">
                  {{ config.value.length === config.options.length ? '全不选' : '全选' }}
                </Option>
                <Option
                  v-for="(option, index) in config.options"
                  v-bind:key="index"
                  :value="option.value"
                >{{ option.label }}</Option>
              </Select>
            </template>
            <!-- select-img -->
            <template v-if="config.type === 'select-img'">
              <Select
                v-model="config.value"
                :disabled="config.disabled"
                :placeholder="config.placeholder"
                :style="{ ...config.style, ...style }">
                <Option
                  v-for="(option, index) in config.options"
                  v-bind:key="index"
                  :value="option.value"
                >
                <span class="option-img">
                  <img :src="getCodeImgUrl(option.value)">
                </span>
                {{ option.label }}</Option>
              </Select>
              <div v-if="config.value" class="config-img">
                <img :src="getCodeImgUrl(config.value)">
              </div>
            </template>
          </template>
          <!-- radio -->
          <template v-if="config.type.includes('radio')">
            <template v-if="config.type === 'radio-group'">
              <RadioGroup v-model="config.value" :style="{ ...config.style, ...style }">
                <Radio v-for="(item, index) in config.options" :key="index" :label='item.value' :disabled="config.disabled">{{ item.label }}</Radio>
              </RadioGroup>
            </template>
          </template>
          <!-- checkbox -->
          <template v-if="config.type.includes('checkbox')">
            <template v-if="config.type === 'checkbox-group'">
              <CheckboxGroup v-model="config.value" :style="{ ...config.style, ...style }">
                <Checkbox v-for="(item, index) in config.options" :key="index" :label='item.value' :disabled="config.disabled">{{ item.label }}</Checkbox>
              </CheckboxGroup>
            </template>
          </template>
          <!-- date -->
          <template v-if="config.type.includes('date')">
            <DatePicker
              transfer
              v-model="config.value"
              :type="config.type"
              :clearable="false"
              :placeholder="config.placeholder"
              :style="{ ...config.style, ...style }"
              :disabled="config.disabled"
            />
          </template>
          <!-- upload -->
          <template v-if="config.type.includes('upload')">
            <NUpload
              :value="config.value"
              :multiline="config.multiline"
              :paste="config.paste"
              :maxNum="config.maxNum" />
          </template>
          <!-- button -->
          <template v-if="config.type === 'button'">
            <Button type="primary" :disabled="config.disabled" @click="config.click" :style="{ ...config.style, ...style }">{{ config.label }}</Button>
          </template>
          <div class="switch" v-if="config.disableCtl && hasSwitch">
            <i-switch size="small" :value="config.disabled" @on-change="onSwitchDisabled($event, key, config)"/>
          </div>
        </div>
      </div>
      <div class="filter-form-item-tips"
        :style="layout === 'vertically' ? { paddingLeft: `calc(${labelStyle.width || '25%'} + 15px)` } : { paddingLeft: `calc(100% - ${config.style ? config.style.width : '150px'})`}">
        {{  state ? (!config.disabled && config.validate ? config.validate() : '') : '' }}
      </div>
      <div class="filter-form-item-slot" v-show="config.showSlot">
        <slot :name="config.slot"></slot>
      </div>
    </div>
    <div class="filter-form-item-wrapper" :style="layout === 'vertically' ? { paddingLeft: `calc(${labelStyle.width || '25%'} + 15px)` } : {}" v-if="hasSubmit || hasCancel">
      <Button v-if="hasSubmit" style="width: 100px" type="primary" :loading="loading" @click="handleOnSubmit">{{ submitText }}</Button>&nbsp;&nbsp;
      <Button v-if="hasCancel" style="width: 100px" type="default" :loading="loading" @click="handleOnCancel">{{ cancelText }}</Button>
    </div>
  </div>
</template>

<script lang='ts'>
import { Component, Prop, Vue, Emit, Watch } from 'vue-property-decorator'
import { FilterConfigI } from './types'
import NBeautyLabel from '@/components/dumb/N-beauty-label.vue'
import NUpload from '@/components/dumb/N-upload.vue'
import { getCodeImgUrl } from '@/utils'

@Component({ components: { NBeautyLabel, NUpload } })
export default class NFilter extends Vue {
  @Prop({ type: Object, default: () => ({}) }) readonly configs!: { [propName: string]: FilterConfigI }
  @Prop({ type: String, default: 'horizontally' }) readonly layout!: string
  @Prop({ type: Boolean, default: false }) readonly loading!: boolean
  @Prop({ type: Boolean, default: true }) readonly hasSubmit!: boolean
  @Prop({ type: Boolean, default: true }) readonly hasCancel!: boolean
  @Prop({ type: String, default: '查询' }) readonly submitText!: string
  @Prop({ type: String, default: '重置' }) readonly cancelText!: string
  @Prop({ type: Boolean, default: false }) readonly hasSwitch!: boolean
  @Prop({ type: Object, default: () => ({}) }) readonly labelStyle!: {}
  state: boolean = false
  getCodeImgUrl = getCodeImgUrl

  get style() {
    if (this.layout === 'vertically') {
      return { maxWidth: `calc(100% - 15px - ${ this.hasSwitch ? '60px' : '0' })` }
    } else {
      return {}
    }
  }

  @Emit('on-submit')
  onSubmit() {
    return true
  }
  @Emit('on-get-submit')
  onGetSubmit() {
    return this.handleOnSubmit
  }
  @Emit('on-cancel')
  handleOnCancel() {
    return true
  }
  onFocus(event: any, config: FilterConfigI) {
    config.onFocus && config.onFocus()
  }

  handleOnSubmit() {
    this.state = true
    for (const key in this.configs) {
      if (this.configs.hasOwnProperty(key)) {
        const item = this.configs[key]
        const { hide, validate, disabled, value } = item
        // 非隐藏 且 校验回调失败
        if (!hide && validate && validate()) {
          this.$Message.warning(validate())
          return false
        }
      }
    }
    this.onSubmit()
    this.state = false
    return true
  }
  onSelectQueryChange(query: string, config: FilterConfigI) {
    query && config.onSelectQueryChange && config.onSelectQueryChange(query)
  }
  onSwitchDisabled(disabled: boolean, key: string, config: FilterConfigI) {
    this.configs[key].disabled = disabled
    this.$forceUpdate()
  }
  handleOnSelectMulti(selected: string[], key: string, config: FilterConfigI) {
    if (selected.includes('all')) {
      this.configs[key].value = config.options!.map((c) => c.value)
    }
    if (selected.includes('empty')) {
      this.configs[key].value = []
    }
  }
  onClear(key: string) {
    this.$set(this.configs[key], 'value', '')
  }
  mounted() {
    this.onGetSubmit()
    if (this.hasSwitch) {
      for (const key in this.configs) {
        if (this.configs.hasOwnProperty(key)) {
          if (this.configs[key].disableCtl) {
            this.configs[key].disabled = true
          }
        }
      }
      this.$forceUpdate()
    }
  }
}
</script>

<style scoped lang='less'>
.filter-wrapper {
  display: flex;
  align-items: top;
  flex-wrap: wrap;
  font-size: 14px;
  &.horizontally, & {
    .filter-form-item-wrapper {
      margin-right: 10px;
      &:last-child {
        margin-right: unset;
      }
      .filter-form-item-control {
        display: flex;
        align-items: center;
        justify-content: space-between;
        .filter-form-item-label {
          font-size: 12px;
        }
        .filter-form-item-colon {
          width: 15px;
          text-align: center;
        }
        .filter-form-item-box {
          position: relative;
          display: flex;
          align-items: center;
          justify-content: space-between;
          flex: 1;
          .text {
            width: 100%;
            min-height: 30px;
            line-height: 30px;
            display: inline-block;
            vertical-align: middle;
            border-radius: 3px;
            background-color: #f3f3f3;
            text-align: center;
            border: 1px solid #dcdee2;
            border-radius: 4px;
            word-break: break-all;
          }
          .option-img {
            width: 20px;
            height: 20px;
            display: inline-block;
            vertical-align: sub;
            background: #439eb4;
            border-radius: 3px;
            padding: 3px;
            img {
              width: 100%;
              height: 100%;
            }
          }
          .config-img {
            margin-left: 5px;
            width: 30px;
            height: 30px;
            border-radius: 3px;
            background: #439eb4;
            padding: 3px;
            img {
              width: 100%;
              height: 100%;
            }
          }
        }
      }
      .filter-form-item-tips {
        // height: 18px;
        margin: 5px 0;
        color: red;
        font-size: 12px;
      }
    }
  }
  &.vertically {
    padding: 10px 20px;
    flex-direction: row;
    .filter-form-item-wrapper {
      width: 100%;
      display: block;
      margin-right: unset;
      margin-bottom: 10px;
      .filter-form-item-control {
        .filter-form-item-label {
          width: 25%;
          text-align: right;
        }
        .filter-form-item-box {
          width: calc(100% - 25% - 15px);
          .switch {
            display: inline-block;
            width: 30px;
          }
          &.has-switch {
            width: calc(100% - 25% - 15px - 30px);
          }
        }
      }
      .filter-form-item-tips {
        text-align: left;
      }
    }
  }
}
</style>
// 使用
<CForm 
  :configs="formObj" // 表单选项
  layout="vertically"
  :has-submit="true" // 是否显示提交按钮
  :has-cancel="true" // 是否显示取消按钮
  @on-get-submit="onGetSubmit"
/>

import CForm from '@/components/c-form.vue'

component: { CForm }

data: () {
  return {
      formObj: { [propName: string]: FilterConfigI } = {
    record_id: {
      type: 'text',
      label: 'Record ID',
      value: '',
      disabled: true
    },
    path_consis: {
      type: 'select',
      label: '路线一致性',
      value: 0,
      defaultVal: 0,
      options: this.baseConfig.path_consis,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.path_consis
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择路线一致性'
      }
    },
    tag: {
      type: 'select',
      label: '评价结论',
      value: 0,
      defaultVal: 0,
      options: this.baseConfig.manual_tag,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.tag
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择评价结论'
      }
    },
    expect_action: {
      type: 'select',
      label: '期望播报',
      value: '0',
      defaultVal: '',
      hide: true,
      options: this.baseConfig.action,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.expect_action
        return value && `${value}` && options!.find((op) => op.value == value) ? '' : '请选择期望播报'
      }
    },
    best_code: {
      type: 'select-img',
      label: '最佳Code',
      value: 0,
      defaultVal: 0,
      hide: true,
      options: [],
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.best_code
        return value && options!.find((op) => op.value == value) ? '' : '请选择最佳code'
      }
    },
    cross_shape: {
      type: 'select',
      label: '路口形态',
      value: 0,
      defaultVal: 0,
      hide: true,
      options: this.baseConfig.cross_action,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.cross_shape
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择路口形态'
      }
    },
    assist1: {
      type: 'select',
      label: '辅助动作1',
      value: 0,
      defaultVal: 0,
      hide: true,
      options: this.baseConfig.assist_action,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.assist1
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择辅助动作1'
      }
    },
    assist2: {
      type: 'select',
      label: '辅助动作2',
      value: 0,
      defaultVal: 0,
      hide: true,
      options: this.baseConfig.assist_action,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.assist2
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择辅助动作2'
      }
    },
    issue_type: {
      type: 'select',
      label: '问题类型',
      value: 2,
      defaultVal: 0,
      options: this.baseConfig.issue_type,
      hide: true,
      disableCtl: !this.isMarkJob,
      validate: this.is_public_evaluation && !this.isMarkJob ? undefined : () => {
        const { value, options } = this.formObj.issue_type
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择问题类型'
      }
    },
    err_level: {
      type: 'select',
      label: '问题等级',
      value: 1,
      defaultVal: 0,
      options: this.baseConfig.err_level,
      hide: true,
      disableCtl: !this.isMarkJob,
      validate: this.is_public_evaluation && !this.isMarkJob ? undefined : () => {
        const { value, options } = this.formObj.err_level
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择问题等级'
      }
    },
    issue_sub_type: {
      type: 'select',
      label: '问题原因',
      value: 1,
      defaultVal: 0,
      disableCtl: !this.isMarkJob,
      options: this.baseConfig.road_issue_net,
      hide: true,
      validate: () => {
        const { value, options } = this.formObj.issue_sub_type
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择问题原因'
      }
    },
    issue_solution: {
      type: 'select',
      label: '解决办法',
      value: 1,
      defaultVal: 0,
      disableCtl: !this.isMarkJob,
      options: this.baseConfig.issue_solution,
      hide: true,
      validate: () => {
        const { value, options } = this.formObj.issue_solution
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择解决办法'
      }
    },
    navigate: {
      type: 'button',
      label: '跳转强制诱导',
      value: '',
      click: () => {
        if (!this.curDataRow) {
          return
        }
        const { link_list, map_ver } = this.curDataRow
        const { best_code, assist1, assist2, custom_mark } = this.formObj
        const link_list_arr = link_list.split(',')
        const inlink = link_list_arr[0]
        const outlink = link_list_arr[link_list_arr.length - 1]
        const passlinks = link_list_arr.slice(1, link_list_arr.length - 1).join(',')
        const url = `http://10.89.110.12:8070/#/code/nav-new?` + queryString.stringify({
          inlink,
          outlink,
          passlinks,
          roadver: map_ver,
          from: 'navicompass',
          maincode: best_code.value,
          assist: `${assist1.value},${assist2.value}`,
          type: 1,
          remark: custom_mark.value
        })
        window.open(url, '_blank')
      },
      hide: true
    },
    auto_move: {
      type: 'radio-group',
      label: '是否流转',
      value: 0,
      defaultVal: 0,
      options: [{ label: '是', value: 1 }, { label: '否', value: 0 }],
      hide: true,
      disableCtl: !this.isMarkJob,
      validate: () => {
        const { value, options } = this.formObj.auto_move
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择是否流转'
      }
    },
    issue_point: {
      type: 'input',
      label: '问题点',
      value: '',
      defaultVal: '',
      placeholder: 'lng,lat',
      disableCtl: !this.isMarkJob,
      validate: () => this.formObj.issue_point.value ? '' : '请输入问题点',
      hide: true
    },
    issue_status: {
      type: 'select',
      label: '问题状态',
      value: 0,
      defaultVal: 0,
      options: this.baseConfig.issue_status,
      disableCtl: !this.isMarkJob,
      hide: true,
      validate: () => {
        const { value, options } = this.formObj.issue_status
        return `${value}` && options!.find((op) => op.value == value) ? '' : '请选择问题状态'
      }
    },
    custom_mark: {
      type: 'input-textarea',
      label: '备注',
      value: '',
      placeholder: '备注',
      disableCtl: !this.isMarkJob,
    },
    mark_result: {
      type: 'input-textarea',
      label: '评测结果记录',
      value: '',
      hide: this.is_public_evaluation, // 众验则隐藏
      placeholder: '评测结果记录',
      disabled: true
    },
    verify_result: {
      type: 'input-textarea',
      label: '质检结果记录',
      value: '',
      placeholder: '质检结果记录',
      disabled: true
    }
  }
  }
}