Skip to content

封装对话框组件

更新: 2025/3/23 10:54:53 字数: 0 字

需求分析

需求

  1. 教务系统大多都是小弹窗来提交数据,需要提交的内容并不多。
  2. 使用比较频繁,几乎每个页面都需要使用。

系统对话框

  系统一般都是通过这种弹出框来提交数据,象这样的对话框,我们可以封装成一个组件,这样,以后使用起来就方便了。

组件封装

定义 props

js
const props = defineProps({
  addTitle: {
    type: String,
    default: '新增'
  },
  editTitle: {
    type: String,
    default: '编辑'
  },
  columns: {
    type: Array,
    default: () => [],
  },
  submitRequest: {
    type: Function,
    default: () => {
    }
  },
  rules: {
    type: Object,
    default: () => []
  },
  gutter: {
    type: Number,
    default: 24
  },
  width: {
    type: String,
    default: '60%'
  },
  disabled: {
    type: Boolean,
    default: false
  }
})

定义 template

vue

<template>
  <el-dialog v-model="visibleDialog" width="680px" append-to-body @closed="_handleClose">
    <!-- 头部信息   -->
    <template #header>
      <strong>{{ isEdit ? props.editTitle : props.addTitle }}</strong>
    </template>
    <!-- 中间的表单信息 -->
    <template #default>
      <el-form v-loading="formLoading" ref="formRef" :model="formData" :rules="_rules" :disabled="disabled">
        <el-row :gutter="props.gutter">
          <template v-for="item in columns">
            <el-col :span="item.span || props.gutter">
              <el-form-item :prop="item.prop" :label="item.label">
                <template v-if="item.type === 'input'">
                  <el-input v-model="formData[item.prop]" :placeholder="item.placeholder || '请输入' + item.label "
                            :clearable="item.clearable || true"></el-input>
                </template>
                <template v-else-if="item.type === 'select'">
                  <el-select v-model="formData[item.prop]" :placeholder="item.placeholder || '请选择' + item.label"
                             :clearable="item.clearable || true">
                    <el-option v-for="(option, index) in item.options" :key="index" :label="option.label"
                               :value="option.value"></el-option>
                  </el-select>
                </template>
                <template v-else-if="item.type === 'radio'">
                  <el-radio-group v-model="formData[item.prop]">
                    <el-radio v-for="(option, index) in item.options" :key="index" :label="option.value">{{
                      option.label
                      }}
                    </el-radio>
                  </el-radio-group>
                </template>
                <template v-else-if="item.type === 'date'">
                  <el-date-picker v-model="formData[item.prop]" type="date"
                                  :placeholder="item.placeholder || '选择日期'"
                                  value-format="yyyy-MM-dd"></el-date-picker>
                </template>
              </el-form-item>
            </el-col>
          </template>
        </el-row>
      </el-form>
    </template>
    <!--  尾部按钮  -->
    <template #footer>
      <el-button @click="() => visibleDialog = false">取消</el-button>
      <el-button type="primary" @click="_handleSubmit">确定</el-button>
    </template>
  </el-dialog>
</template>

定义 script

js
import {computed, ref} from "vue";
import {ElMessage} from "element-plus";

const visibleDialog = ref(false)
const isEdit = ref(false) // 是否是编辑状态
const formLoading = ref(false) // 加载状态

// 表单数据
const formData = ref({})
const formRef = ref(null)

const props = defineProps({
  //...
})

const _rules = computed(() => {
  return props.rules || []
})
const cacheAdd = ref({})


const _handleClose = () => {
  formLoading.value = true
  if (!isEdit.value) {
    cacheAdd.value = {
      ...formData.value
    }
  }
  formRef.value.clearValidate()
  formRef.value.resetFields()
  formLoading.value = false
}

const openAdd = () => {
  visibleDialog.value = true
  isEdit.value = false
  // 处理一些默认选中的
  props.columns.forEach((item) => {
    if (item.defaultValue) {
      formData.value[item.prop] = item.defaultValue
    }
  })
  // 看是否有缓存数据
  Object.keys(cacheAdd.value).forEach((key) => {
    formData.value[key] = cacheAdd.value[key]
  })
}

const openEdit = (data) => {
  formLoading.value = true
  visibleDialog.value = true
  isEdit.value = true
  Object.keys(data).forEach((key) => {
    formData.value[key] = data[key]
  })
  formLoading.value = false
}

const _handleSubmit = () => {
  if (!formRef.value) return
  formRef.value.validate().then(() => {
    formLoading.value = true
    props.submitRequest(formData.value).then((res) => {
      visibleDialog.value = false
      ElMessage.success(res.msg)
    }).catch(() => {
      ElMessage.error('保存失败')
    }).finally(() => {
      formLoading.value = false
    })
  }).catch(() => {
    console.log('校验不通过')
  })
}

defineExpose({
    openAdd,
    openEdit
})

使用说明

  下面的代码是使用时的例子。

vue

<script setup>
  const propsDialog = ref(null);

  const rule = {
    name: [
      {required: true, message: '请输入基地名称', trigger: 'blur'},
      {min: 3, max: 30, message: '长度在 3 到 30 个字符', trigger: 'blur'}
    ]
  };
  const columns = [
    {type: 'input', label: '基地名称', prop: 'name', placeholder: '请输入基地名称'},
    {type: 'input', label: '基地地址', prop: 'address', placeholder: '请输入基地地址'},
    {
      type: 'select',
      label: '基地类型',
      prop: 'baseType',
      placeholder: '请选择基地类型',
      options: [{label: '师范实习基地', value: 1}, {label: '工程实习基地', value: 2}]
    },
    {type: 'input', label: '企业代码', prop: 'code', placeholder: '请输入企业代码'},
    {type: 'date', label: '开始时间', prop: 'startTime', placeholder: '请选择开始时间'},
    {type: 'date', label: '结束时间', prop: 'endTime', placeholder: '请选择结束时间'},
    {
      type: 'select',
      label: '创建院系',
      prop: 'college',
      placeholder: '请选择创建院系',
      options: [{label: '智能学院', value: 1}, {label: '音舞学院', value: 2}]
    },
    {
      type: 'radio',
      span: 12,
      label: '是否在校内',
      prop: 'isSchool',
      defaultValue: 1,
      options: [{label: '是', value: 1}, {label: '否', value: 2}]
    },
    {
      type: 'radio',
      span: 12,
      label: '是否为创业实习基地',
      prop: 'isInternshipBase',
      defaultValue: 1,
      options: [{label: '是', value: 1}, {label: '否', value: 2}]
    },
    {
      type: 'radio',
      label: '是否为示范性教育实践基地',
      prop: 'isDemonstrationBase',
      defaultValue: 1,
      options: [{label: '是', value: 1}, {label: '否', value: 2}]
    },
  ];

  const tableData = {
    name: '基地名称',
    address: '基地地址',
    baseType: 1,
    code: '001',
    college: 1,
    isSchool: 1,
    isInternshipBase: 1,
    isDemonstrationBase: 1
  }
</script>

<template>
  <el-button type="primary" @click="propsDialog.openAdd()" plain>新增</el-button>
  <el-button type="primary" @click="propsDialog.openEdit(tableData)" plain>编辑</el-button>

  <!-- 使用 PropDialog 组件 -->
  <PropDialog width="800px" ref="propsDialog" :columns="columns" :submitRequest="submitRequest" :rules="rule"/>
</template>

  点击 "新增" 或者 "编辑" 按钮,可以弹出对应的对话框,这里是上面代码的运行结果。

信息展示
基地名称基地名称
基地地址基地地址
基地类型1
企业代码001
创建院系1
创业实习基地1
实践基地1
实践基地1

表单动态化

  有的时候我们可能根据表单的配置动态化接下来的表单,比如:

  这种动态化表单

结果展示

vue
<script setup>

import {ref} from "vue";

const propsDialog = ref(null)
const columns = [
  {type: 'select', label: '上级菜单', prop: 'parentId', options: [{label: '一级菜单', value: 0}], isShow: ({isMenu}) => {
    return isMenu === 0 || isMenu === 1}},
  {type: 'radio', label: '菜单类型', prop: 'isMenu', defaultValue: 2, options: [{label: '目录', value: 2}, {label: '菜单', value: 0}, {label: '功能权限', value: 1}]},
  {type: 'slot', label: '菜单图标', prop: 'icon'},
  {type: 'input', span: 12, label: '菜单名称', prop: 'name'},
  {type: 'input-number', span: 12, label: '显示排序', prop: 'orderNum'},
  {type: 'input-number', span: 12, label: '类别编号', prop: 'category'},
  {type: 'radio',  span: 12, label: '是否外链', prop: 'isFrame', defaultValue: 1, options: [{label: '是', value: 0}, {label: '否', value: 1}]},
  {type: 'input', span: 12, label: '路由地址', prop: 'path', isShow: ({isMenu}) => {
      return isMenu  !== 1 }},
  {type: 'input', span: 12, label: '组件路径', prop: 'component', isShow: ({isMenu}) => {
      return isMenu === 0}},
    {type: 'input', span: 12, label: '权限字符', prop: 'permissionValue'},
    {type: 'input', span: 12, label: '路由参数', prop: 'query', isShow: ({isMenu}) => {
      return isMenu === 0}},
  {type: 'radio', span: 12, label: '是否缓存', prop: 'isCache', defaultValue: 0, options: [{label: '缓存', value: 0}, {label: '不缓存', value: 1}], isShow: ({isMenu}) => {
      return isMenu === 0}},
    {type: 'radio',span: 12,  label: '显示状态', prop: 'visible', defaultValue: 1, options: [{label: '显示', value: 1}, {label: '隐藏', value: 0}], isShow: ({isMenu}) => {
        return isMenu  !== 1 }},
    {type: 'radio', span: 12, label: '菜单状态', prop: 'status', defaultValue: 2, options: [{label: '停用', value: 1}, {label: '使用', value: 2}], isShow: ({isMenu}) => {
      return isMenu !== 1}},
]

const submitRequest = (value) => {
  console.log('submitRequest', value)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({code: 200, msg: '保存成功'})
    }, 1000)
  })
}

const rules = {
  permissionDescription: [
    {required: true, message: '菜单名称不能为空', trigger: 'blur'},
  ],
  orderNum: [
    {required: true, message: '菜单顺序不能为空', trigger: 'blur'},
  ],
  category: [
    {required: true, message: '类别编号不能为空', trigger: 'blur'},
  ],
  path: [{required: true, message: '路由地址不能为空', trigger: 'blur'}],
}
</script>

<template>
  <div>
    <el-button type="primary" @click="propsDialog.openAdd()" plain>新增</el-button>

    <PropDialog width="800px" ref="propsDialog" :columns="columns" :rules="rules" :submitRequest="submitRequest">
      <template #icon="{formData}">
        <el-select v-model="formData.icon" placeholder="请选择菜单图标">
          <el-option value="1" label="无图标"></el-option>
          <el-option value="2" label="系统管理图标"></el-option>
        </el-select>
      </template>
    </PropDialog>
  </div>
</template>

<style scoped>

</style>
vue
<script setup>
import {computed, ref} from "vue";
import {ElMessage} from "element-plus";

const visibleDialog = ref(false)
const isEdit = ref(false) // 是否是编辑状态
const formLoading = ref(false) // 加载状态

// 表单数据
const formData = ref({})
const formRef = ref(null)

const props = defineProps({
  addTitle: {
    type: String,
    default: '新增'
  },
  editTitle: {
    type: String,
    default: '编辑'
  },
  columns: {
    type: Array,
    default: () => [],
  },
  submitRequest: {
    type: Function,
    default: () => {
    }
  },
  rules: {
    type: Object,
    default: () => []
  },
  gutter: {
    type: Number,
    default: 24
  },
  width: {
    type: String,
    default: '60%'
  },
  disabled: {
    type: Boolean,
    default: false
  },
  isCacheAdd: {
    type: Boolean,
    default: false
  }
})

const _rules = computed(() => {
  return props.rules || []
})
const cacheAdd = ref({})


const _handleClose = () => {
  formLoading.value = true
  if (!isEdit.value && props.isCacheAdd) {
    cacheAdd.value = {
      ...formData.value
    }
  }
  formRef.value.clearValidate()
  formRef.value.resetFields()
  formLoading.value = false
}

const openAdd = () => {
  visibleDialog.value = true
  isEdit.value = false
  // 处理一些默认选中的
  props.columns.forEach((item) => {
    if (item.defaultValue) {
      formData.value[item.prop] = item.defaultValue
    }
  })
  // 看是否有缓存数据
  if(!props.isCacheAdd) return
  Object.keys(cacheAdd.value).forEach((key) => {
    formData.value[key] = cacheAdd.value[key]
  })
}

const openEdit = (data) => {
  formLoading.value = true
  visibleDialog.value = true
  isEdit.value = true
  Object.keys(data).forEach((key) => {
    formData.value[key] = data[key]
  })
  formLoading.value = false
}

defineExpose({
  openAdd,
  openEdit
})

const _handleSubmit = () => {
  if (!formRef.value) return
  formRef.value.validate().then(() => {
    formLoading.value = true
    props.submitRequest(formData.value).then((res) => {
      visibleDialog.value = false
      ElMessage.success(res.msg)
    }).catch(() => {
      ElMessage.error('保存失败')
    }).finally(() => {
      formLoading.value = false
    })
  }).catch(() => {
    console.log('校验不通过')
  })
}
</script>

<template>
  <el-dialog v-model="visibleDialog" width="680px" append-to-body @closed="_handleClose">
    <template #header>
      <strong>{{ isEdit ? props.editTitle : props.addTitle }}</strong>
    </template>
    <template #default>
      <el-form v-loading="formLoading" ref="formRef" :model="formData" :rules="_rules" :disabled="disabled">
        <el-row :gutter="props.gutter">
          <template v-for="item in columns">
            <el-col v-show="item.isShow ? item.isShow(formData) : true" :span="item.span || props.gutter">
              <el-form-item :prop="item?.prop" :label="item?.label">
                <template v-if="item.type === 'input'">
                  <el-input v-model="formData[item.prop]" :placeholder="item.placeholder || '请输入' + item.label "
                            :clearable="item.clearable || true"></el-input>
                </template>
                <template v-else-if="item.type === 'select'">
                  <el-select v-model="formData[item.prop]" :placeholder="item.placeholder || '请选择' + item.label"
                             :clearable="item.clearable || true">
                    <el-option v-for="(option, index) in item.options" :key="index" :label="option.label"
                               :value="option.value"></el-option>
                  </el-select>
                </template>
                <template v-else-if="item.type === 'radio'">
                  <el-radio-group v-model="formData[item.prop]">
                    <el-radio v-for="(option, index) in item.options" :key="index" :label="option.value">{{
                        option.label
                      }}
                    </el-radio>
                  </el-radio-group>
                </template>
                <template v-else-if="item.type === 'input-number'">
                  <el-input-number
                      v-model="formData[item.prop]"
                      controls-position="right"
                      :min="item.min || 0"
                      :placeholder="item.placeholder || '请键入'"
                  />
                </template>
                <template v-else-if="item.type === 'date'">
                  <el-date-picker v-model="formData[item.prop]" type="date"
                                  :placeholder="item.placeholder || '选择日期'"
                                  value-format="yyyy-MM-dd"></el-date-picker>
                </template>
                <!--插槽                -->
                <template v-else-if="item.type === 'slot'">
                  <slot :name="item.prop" :formData="formData"></slot>
                </template>
              </el-form-item>
            </el-col>
          </template>
        </el-row>
      </el-form>
    </template>
    <template #footer>
      <el-button @click="() => visibleDialog = false">取消</el-button>
      <el-button type="primary" @click="_handleSubmit">确定</el-button>
    </template>
  </el-dialog>
</template>

<style scoped>
:deep(.el-form-item__label) {
  font-weight: bold;
}


</style>

道友再会.