main.vue 6.62 KB
<template>
  <div class="dialog-container">
    <ElDialog
      v-el-drag-dialog
      :title="title"
      :visible.sync="visible"
      :close-on-click-modal="false"
      :width="width"
      @close="onCancel"
      @closed="$emit('closed')"
    >
      <ElForm
        ref="form"
        :model="formData"
        :rules="rules"
        :label-width="labelWidth"
        @submit.native.prevent
      >
        <ElFormItem
          v-for="field in formSchema"
          :key="field.key"
          :label="field.label"
          :prop="field.key"
          :error="errors[field.key]"
        >
          <div v-if="field.disabled">
            <span>
              {{ formData[field.key] }}
            </span>
          </div>
          <div
            v-else-if="field.type === 'tip'"
            class="tip"
          >
            提示: {{ field.message }}
          </div>
          <!-- 动态组件 -->
          <Component
            :is="field.component"
            v-else-if="field.component"
            v-model="formData[field.key]"
            v-bind="field.props"
            :placeholder="field.placeholder"
            clearable
          />

          <!-- 文本输入框 -->
          <ElInput
            v-else-if="!field.type || field.type === 'text'"
            v-model.trim="formData[field.key]"
            :placeholder="field.placeholder"
            :disabled="field.disabled"
            clearable
          />

          <!-- 文本域输入框 -->
          <ElInput
            v-else-if="field.type === 'textarea'"
            v-model.trim="formData[field.key]"
            :placeholder="field.placeholder"
            :autosize="{ minRows: 2, maxRows: 4 }"
            :disabled="field.disabled"
            type="textarea"
            clearable
          />

          <!-- 密码输入 -->
          <ElInput
            v-else-if="field.type === 'password'"
            v-model.trim="formData[field.key]"
            :placeholder="field.placeholder"
            :disabled="field.disabled"
            type="password"
            clearable
          />

          <!-- 下拉选择 -->
          <ElSelect
            v-else-if="field.type === 'select'"
            v-model="formData[field.key]"
            v-loading="optionsLoading[field.key]"
            :disabled="field.disabled"
            :placeholder="field.placeholder"
            clearable
          >
            <ElOption
              v-for="option in field.options"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            />
          </ElSelect>

          <!-- 级联选择 -->
          <ElCascader
            v-else-if="field.type === 'cascader'"
            v-model="formData[field.key]"
            v-loading="optionsLoading[field.key]"
            :placeholder="field.placeholder"
            :props="{ emitPath: false, ...field.props }"
            :options="field.options"
            :show-all-levels="false"
            filterable
            clearable
            class="select-field"
          />
        </ElFormItem>
      </ElForm>
      <div
        slot="footer"
        class="footer-container"
      >
        <ElButton @click="onCancel">
          取 消
        </ElButton>
        <ElButton
          type="primary"
          :loading="submitLoading"
          @click="onSubmit"
        >
          {{ confirmButtonText }}
        </ElButton>
      </div>
    </ElDialog>
  </div>
</template>

<script>
import ElDragDialog from '@/directives/el-drag-dialog'

export default {
  directives: {
    ElDragDialog
  },
  props: {
    title: {
      type: String,
      default: null
    },
    width: {
      type: String,
      default: null
    },
    labelWidth: {
      type: String,
      default: '120px'
    },
    schema: {
      type: Array,
      required: true
    },
    rules: {
      type: Object,
      default: null
    },
    data: {
      type: Object,
      default: () => {
      }
    },
    submitMethod: {
      type: Function,
      default: null
    },
    submitSuccessMessage: {
      type: String,
      default: null
    },
    confirmButtonText: {
      type: String,
      default: '保 存'
    }
  },
  data() {
    return {
      visible: false,
      formSchema: [],
      formData: {},
      errors: {},
      optionsLoading: {},
      submitLoading: false
    }
  },
  created() {
    this.formData = this.data
    this.init()
  },
  methods: {
    async init() {
      const asyncMethods = {}
      this.formSchema = this.schema.map(field => {
        if (!(field.options instanceof Function)) {
          return field
        }
        asyncMethods[field.key] = field.options
        return {
          ...field,
          options: []
        }
      })
      await Promise.all(Object.entries(asyncMethods).map(async ([key, fun]) => {
        this.$set(this.optionsLoading, key, true)
        this.formSchema.find(field => field.key === key).options = await fun()
        this.$set(this.optionsLoading, key, false)
      }))
    },
    onSubmit() {
      this.$refs.form.validate(async valid => {
        if (valid) {
          const submitMethod = this.submitMethod
          const formData = this.formData
          this.$emit('submit', formData)
          if (!submitMethod) {
            this.close()
            return
          }

          this.submitLoading = true
          let submitResult = {}
          try {
            submitResult = await submitMethod(formData)
          } finally {
            this.submitLoading = false
          }

          const {
            code,
            data
          } = submitResult

          if (code === 0) {
            if (this.submitSuccessMessage) {
              this.$message.success(this.submitSuccessMessage)
            }
            this.close()
            return
          }

          if (code === 'BAD_REQUEST') {
            const errors = {}
            Object.entries(data).forEach(([key, tips]) => {
              errors[key] = tips.join('、')
            })
            this.setErrors(errors)
          }
        }
      })
    },
    onCancel() {
      this.$emit('cancel')
      this.close()
    },
    setErrors(errors) {
      this.errors = errors
    },
    show() {
      this.visible = true
    },
    close() {
      this.visible = false
      this.$emit('close')
    }
  }
}
</script>
<style
  scoped
  lang="scss">
.select-field {
  ::v-deep {
    .el-cascader__search-input {
      font-size: 10px;
    }
  }
}

::v-deep {
  .select-container {
    width: 100%;

    .el-select {
      width: 100%;
    }
  }

  .el-textarea__inner {
    font-family: auto;
  }
}

.footer-container {
  text-align: center;
}

.tip {
  color: red;
  margin-top: -16px;
}
</style>