vue2+element-UI表单封装

针对单进行封装,在编辑页面直接传入字段数组就可以展示表单:

弹窗:

javascript 复制代码
<!-- 公共对话框组件 -->
<template>
  <el-dialog
    :title="dialogTitle"
    :visible.sync="dialogVisible"
    width="800px"
    :close-on-click-modal="false"
    custom-class="fixed-dialog"
  >
    <common-form
      ref="formRef"
      :formData="formData"
      :formFields="formFields"
      :rules="rules"
      :labelWidth="labelWidth"
      @submit="(localFormData) => submitSuccess(localFormData)"
    ></common-form>
    <template slot="footer">
      <div class="dialog-footer">
        <div class="dialog-footer-button">
          <el-button @click="handleCancel" size="mini">取消</el-button>
          <el-button type="primary" @click="submitForm" size="mini">确定</el-button>
        </div>
      </div>
    </template>
  </el-dialog>
</template>
<script>
import ImageUpload from './imageUpload.vue';
import commonForm from './commonForm.vue';
export default {
  name: 'commonDialog',
  components: {
    ImageUpload,
    commonForm
  },
  props: {
    dialogVisible: {
      type: Boolean,
      default: false
    },
    dialogTitle: {
      type: String,
      default: "新增"
    },
    formData: {
      type: Object,
      default: () => ({})
    },
    formFields: {
      type: Array,
      default: () => []
    },
    rules: {
      type: Object,
      default: () => ({})
    },
    labelWidth: {
      type: String,
      default: "120px"
    },
  },
  methods: {
    submitForm() {
      this.$refs.formRef.submitForm();
    },
    submitSuccess(localFormData) {
      this.$emit("submit", localFormData);
    },
    handleCancel() {
      this.$refs.formRef.handleCancel();
      this.$emit('cancel');
    },
  }
};
</script>

<style scoped>
/* 引入公共对话框样式 */
@import '@/assets/styles/commonDialog.css';
/* 固定弹窗样式 */
:deep(.fixed-dialog) {
  position: fixed !important;
  top: 50% !important;
  left: 50% !important;
  transform: translate(-50%, -50%) !important;
  margin: 0 !important;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  z-index: 2002 !important;
}

:deep(.fixed-dialog .el-dialog__body) {
  flex: 1;
  overflow-y: auto;
  max-height: calc(90vh - 100px);
}

/* 标题居左 */
:deep(.fixed-dialog .el-dialog__header) {
  text-align: left !important;
}

</style>

form表单:

javascript 复制代码
<!-- 公共表单组件 -->
<!-- 公共表单组件 -->
<template>
  <el-form 
    :model="localFormData"
    ref="formRef"
    :rules="rules"
    :label-width="labelWidth"
  >
    <el-form-item 
      v-for="item in formFields" 
      :key="item.prop"
      :label="item.label" 
      :prop="item.prop"
    >
      <!-- 优先使用自定义插槽 -->
      <slot 
        :name="item.prop" 
        :formData="localFormData" 
        :field="item"
      >
        <!-- 输入框 -->
        <el-input 
          v-if="item.type === 'input'"
          v-model="localFormData[item.prop]" 
          :placeholder="item.placeholder" 
          :maxlength="item.maxlength" 
          style="width: 100%" 
          size="mini"
          show-word-limit 
        ></el-input>
        
        <!-- 文本域 -->
        <el-input 
          v-else-if="item.type === 'textarea'"
          v-model="localFormData[item.prop]" 
          type="textarea" 
          :rows="item.rows || 3"
          :placeholder="item.placeholder" 
          :maxlength="item.maxlength" 
          show-word-limit 
          style="width: 100%" 
          size="mini"
        ></el-input>
        
        <!-- 选择器 -->
        <el-select 
          v-else-if="item.type === 'select'"
          v-model="localFormData[item.prop]" 
          :placeholder="item.placeholder" 
          style="width: 100%" 
          size="mini"
          :disabled="typeof item.disabled === 'string' ? evaluateCondition(item.disabled) : item.disabled"
          :clearable="item.clearable"
          @change="item.change ? item.change(localFormData[item.prop]) : null"
        >
          <el-option 
            v-for="option in item.options" 
            :key="option.value" 
            :label="option.label" 
            :value="option.value"
            :disabled="option.disabled || false"
          ></el-option>
        </el-select>
        
        <!-- 开关 -->
        <el-switch 
          v-else-if="item.type === 'switch'"
          v-model="localFormData[item.prop]" 
          :active-value="1" 
          :inactive-value="0" 
          active-color="#13ce66" 
          inactive-color="#b6aeaeff"
        ></el-switch>
        
        <!-- 图片上传 -->
        <image-upload 
          v-else-if="item.type === 'upload'"
          v-model="localFormData[item.prop]"
          :image-url="localFormData[item.prop]" 
          :action="item.action"
          :accept="item.accept"
          :width="item.width"
          :height="item.height"
          :max-width="item.maxWidth"
          :max-height="item.maxHeight"
          :show-delete="item.showDelete || false"
          @upload-success="(imageUrl) => handleUploadSuccess(imageUrl, item.prop)"
        ></image-upload>
        <p v-if="item.type === 'upload'" class="hint">仅支持上传png/jpg/jpeg文件,上传尺寸为{{item.maxWidth}}*{{item.maxHeight}}px,最多允许上传1张图片,且图片大小不超过10M</p>
      </slot>
    </el-form-item>
  </el-form>
</template>

<script>
import ImageUpload from './imageUpload.vue';
export default {
  name: 'commonForm',
  components: {
    ImageUpload,
  },
  props: {
    formData: {
      type: Object,
      default: () => ({})
    },
    formFields: {
      type: Array,
      default: () => []
    },
    rules: {
      type: Object,
      default: () => ({})
    },
    labelWidth: {
      type: String,
      default: "120px"
    },
    // 父组件传过来的 dialogVisible 用于清空校验
    dialogVisible: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      localFormData: {}
    }
  },
  watch: {
    dialogVisible(val) {
      if (val) {
        this.$nextTick(() => {
          if (this.$refs.formRef) {
            this.$refs.formRef.clearValidate();
          }
        });
      }
    },
    formData: {
      handler(newVal) {
        this.localFormData = JSON.parse(JSON.stringify(newVal));
      },
      deep: true,
      immediate: true
    }
  },
  methods: {
    submitForm() {
      this.$refs.formRef.validate((valid) => {
        if (valid) {
          this.$emit("submit", this.localFormData);
        } else {
          return false;
        }
      });
    },
    handleCancel() {
      if (this.$refs.formRef) {
        this.$refs.formRef.clearValidate();
        this.$refs.formRef.resetFields();
      }
    },
    handleClearValidate() {
      this.$refs.formRef.clearValidate();
    },
    handleResetFields() {
      this.$refs.formRef.resetFields();
    },
    handleUploadSuccess(imageUrl, prop) {
      this.localFormData[prop] = imageUrl;
      this.$emit('upload-success', this.localFormData);
    },
    evaluateCondition(condition) {
      try {
        return new Function('localFormData', `return ${condition}`)(this.localFormData);
      } catch (error) {
        console.error('条件解析错误:', error);
        return true;
      }
    },
  }
};
</script>

<style scoped>
@import '@/assets/styles/commonDialog.css';
.hint {
  font-size: 12px;
  color: #909399;
  margin-top: 5px;
}
</style>
</script>

<style scoped>
@import '@/assets/styles/commonDialog.css';
</style>

引用方式:

javascript 复制代码
<!-- 新增/编辑分类 -->
<template>
  <common-dialog
    :dialog-title="dialogTitle"
    :dialog-visible="dialogVisible"
    :form-data="formData"
    :form-fields="formFields"
    :rules="rules"
    :label-width="'120px'"
    @submit="(localFormData) => handleSubmit(localFormData)"
    @cancel="handleCancel"
  ></common-dialog>
</template>

<script>
import CommonDialog from '@/components/commonDialog.vue';
import { addCategory ,updateCategory } from '@/api/category.js';

export default {
  name: 'edit',
  components: {
    CommonDialog
  },
  props: {
    dialogVisible: {
      type: Boolean,
      default: false
    },
    navList: {
      type: Array,
      default: () => []
    },
    formData: {
      type: Object,
      default: () => ({
        id: null,
        navMenuId: null,
        menuId: null,
        menuName: "",
        menuNameInEnglish: "",
        routeName: "",
        routePath: "",
        componentPath: "",
        status: 1,
        sortNum: 0,
        bgImage: "",
        detailImage: "",
        intro: "",
        isStandard: 0,
        detailContent: "",
        configDetail: 0
      })
    },
    dialogTitle: {
      type: String,
      default: "新增"
    }
  },
  data() {
    return {
      rules: {
        navMenuId: [
          { required: true, message: "请输入导航菜单", trigger: "blur" }
        ],
        menuName: [
          { required: true, message: "请输入菜单名称", trigger: "blur" }
        ],
        menuNameInEnglish: [
          { required: true, message: "请输入菜单英文", trigger: "blur" }
        ],
        routeName: [
          { required: true, message: "请输入路由名称", trigger: "blur" }
        ],
        routePath: [
          { required: true, message: "请输入路由地址", trigger: "blur" }
        ],
        componentPath: [
          { required: true, message: "请输入组件路径", trigger: "blur" }
        ],
        status: [
          { required: true, message: "请选择状态", trigger: "change" }
        ],
        bgImage: [
          { required: true, message: "请上传分类图片", trigger: "change" }
        ],
        intro: [
          { required: true, message: "请输入分类简介", trigger: "change" }
        ],
        detailContent: [
          { required: true, message: "请输入详情介绍", trigger: "change" }
        ],
        configDetail: [
          { required: true, message:"请选择是否可配置详情", trigger: "change" }
        ]
      },
      baseUrl: this.$global.baseUrl
    };
  },
  computed: {
    formFields() {
      return [
        {
          type: 'select',
          label: '导航菜单',
          prop: 'navMenuId',
          placeholder: '请选择导航菜单',
          options: this.navList.map(item => ({ label: item.menuName, value: item.id, disabled: item.status === 0 })),
          showAble: false
        },
        {
          type: 'input',
          label: '菜单名称',
          prop: 'menuName',
          placeholder: '请输入菜单名称',
          maxlength: 10
        },
        {
          type: 'input',
          label: '菜单英文',
          prop: 'menuNameInEnglish',
          placeholder: '请输入菜单英文',
          maxlength: 100
        },
        {
          type: 'input',
          label: '路由名称',
          prop: 'routeName',
          placeholder: '请输入路由名称',
          maxlength: 100
        },
        {
          type: 'input',
          label: '路由地址',
          prop: 'routePath',
          placeholder: '请输入路由地址',
          maxlength: 100
        },
        {
          type: 'input',
          label: '组件路径',
          prop: 'componentPath',
          placeholder: '请输入组件路径',
          maxlength: 100
        },
        {
          type: 'upload',
          label: '分类图片',
          prop: 'bgImage',
          action: this.$global.baseUrl + '/file/file/upload/category' ,
          accept: 'image/png, image/jpeg, image/jpg',
          maxWidth: 384,
          maxHeight: 256,
          width: 197,
          height: 128,
        },
        {
          prop: 'detailImage',
          type: 'upload',
          label: '详情图片',
          action: this.$global.baseUrl + '/file/file/upload/category' ,
          accept: 'image/png, image/jpeg, image/jpg',
          maxWidth: 1920,
          maxHeight: 480,
          width: 480,
          height: 120,
          showDelete: true
        },
        {
          type: 'switch',
          label: '状态',
          prop: 'status'
        },
        {
          type: 'switch',
          label: '是否展示标准品',
          prop: 'isStandard'
        },
        {
          type: 'textarea',
          label: '分类简介',
          prop: 'intro',
          placeholder: '请输入分类简介',
          maxlength: 100,
        },
        {
          type: 'textarea',
          label: '详情介绍',
          prop: 'detailContent',
          placeholder: '请输入详情介绍',
          maxlength: 2000,
          rows: 3
        }
      ];
    }
  },
  methods: {
    handleSubmit(localFormData) {
      if (localFormData.id) {
        // 编辑
        updateCategory(localFormData).then((res) => {
          if (res.code === 200) {
            this.$message.success('编辑成功');
            this.$emit("submit");
          } else {
            this.$message.error('编辑失败,请联系管理员');
          }
        });
      } else {
        // 新增
        addCategory(localFormData).then((res) => {
          if (res.code === 200) {
            this.$message.success('新增成功');
            this.$emit("submit");
          } else {
            this.$message.error('新增失败,请联系管理员');
          }
        });
      }
    },
    handleCancel() {
      this.$emit('cancel');
    }
  }
};
</script>

<style scoped>
/* 引入公共对话框样式 */
@import '@/assets/styles/commonDialog.css';
</style>

样式:

相关推荐
pixcarp2 小时前
Nginx实战部署与踩坑总结 附带详细配置教程
服务器·前端·后端·nginx·golang
Live&&learn2 小时前
Vue项目打包后内联字符串不显示的原因
前端·javascript·vue.js
爱上好庆祝2 小时前
学习js的第三天
前端·css·人工智能·学习·计算机外设·js
aq55356002 小时前
Chrome如何重塑Web标准的未来格局
前端·chrome
宁雨桥2 小时前
深入剖析Vue2与Vue3响应式原理:从Object.defineProperty到Proxy的演进
前端·vue.js
wytraining2 小时前
SDD规范驱动开发
前端
深海鱼在掘金2 小时前
Next.js从入门到实战保姆级教程(第十四章):性能优化深度实践
前端·typescript·next.js
tiger从容淡定是人生2 小时前
Selenium与Playwright:两大Web自动化框架的深入对比
前端·selenium·测试工具·自动化·web测试·playwright·信息化战略
好运的阿财2 小时前
OpenClaw工具拆解之 web_fetch+image_generate
前端·python·机器学习·ai·ai编程·openclaw·openclaw工具