npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

el-form-dialog-chen-plus

v1.0.4

Published

element-plus 表单可配置化组件 表单弹窗可配置化组件 告别复制粘贴噩梦

Downloads

6

Readme

ElFormChen、ElFormDialogChen

表单配置化组件、表单弹窗配置化组件可轻松生成表单,告别复制粘贴的噩梦,支持 el-input/el-input-number/el-checkbox/el-radio/el-select/el-date-picker/el-time-picker/el-switch/el-slider/el-rate/el-color-picker/el-upload/el-cascader/el-autocomplete

安装

    1. 由于组件依赖 element-plus,所以项目需要引入element-plus
    1. 安装 ElFormDialogChen
      npm i el-form-dialog-chen-plus -S

注册

import ElFormDialogChen from "el-form-dialog-chen-plus";
import "el-form-dialog-chen-plus/lib/style.css";
Vue.use(ElFormDialogChen);

效果图

使用示例

<template>
  <div id="app">
    <el-button @click="open('add', '新增表单弹窗')">新增表单弹窗</el-button>
    <el-button @click="open('edit', '编辑表单弹窗')">编辑表单弹窗</el-button>
    <el-button @click="open('view', '编辑表单弹窗')">查看表单弹窗</el-button>
    <!-- 表单弹框 -->
    <ElFormDialogChen
      ref="formDialog"
      v-bind="dialog"
      @closedialog="dialog.visible = false"
      @onsubmit="dialog.onsubmit"
    >
      <template #slotTest="{ $data, $props, config }">
        <el-form-item label="插槽输入框" prop="slotTest">
          <el-input
            v-model="$data.form.slotTest"
            :disabled="$props.type === 'view' || config.disabled"
          />
        </el-form-item>
      </template>
    </ElFormDialogChen>
    <!-- 可配置表单 -->
    <ElFormChen
      v-bind="form"
      @onsubmit="form.onsubmit"
      style="padding-bottom: 40px"
    >
      <template #slotTest="{ $data, $props, config }">
        <el-form-item label="插槽输入框" prop="slotTest">
          <el-input
            v-model="$data.form.slotTest"
            :disabled="$props.type === 'view' || config.disabled"
          />
        </el-form-item>
      </template>
    </ElFormChen>
  </div>
</template>

<script setup>
import { reactive, ref } from "vue";
let defaultFormConfig = reactive([
  // type支持cascader、autocomplete、input、slot、text、number、select、checkbox、radio、year、month、date、dates、week、datetime、datetimerange、daterange、monthrange、time、switch、slider、rate、color、textarea,属性同element-plus相关表单控件的属性,事件同element-plus相关表单控件的事件
  {
    type: "autocomplete", // 控件类型 input/select
    key: "autocomplete", // v-model绑定的键名
    label: "自动补全输入框", // 控件名称
    required: false, // 是否必须
    span: 24, // 控件整体占用宽度(同col)
    placeholder: "请输入",
    defaultValue: "", // 默认值
    style: "width: 100%", // 控件样式
    elColStyle: "",
    "fetch-suggestions": querySearch,
    events: {
      // 事件同el-autocomplete事件 https://element-plus.gitee.io/zh-CN/component/autocomplete.html#%E4%BA%8B%E4%BB%B6
      blur: handle,
      focus: handle,
      change: handle,
      input: handle,
      clear: handle,
    },
  },
  {
    type: "input", // 控件类型
    key: "name", // v-model绑定的键名
    label: "输入框", // 控件名称
    required: false, // 是否必须
    span: 12, // 控件整体占用宽度(同col)
    placeholder: "请输入",
    defaultValue: "默认值", // 默认值
    style: "width: 100%", // 控件样式
    elColStyle: "",
    addAttrs: ["slotTest"],
    validator: validator,
    events: {
      // 事件同el-input事件 https://element-plus.gitee.io/zh-CN/component/input.html#%E4%BA%8B%E4%BB%B6
      blur: handle,
      focus: handle,
      change: handle,
      input: handle,
      clear: handle,
    },
  },
  {
    type: "slot", // 插槽类型
    slotName: "slotTest", //插槽名字
    span: 12, // 控件整体占用宽度(同col)
  },
  {
    type: "text", // 文本类型
    label: "文本类型", //文字
    span: 24,
    style: "font-weight: bold",
  },
  {
    type: "number", // 控件类型 number
    key: "code", // 键名
    label: "数字输入框", // 控件名称
    required: true, // 是否必须
    span: 12, // 控件整体占用宽度(同col)
    controls: false,
    placeholder: "请输入",
    defaultValue: 0, // 默认值
    disabled: true,
    style: "width: 100%", // 控件样式
    events: {
      // 事件同el-input-number事件 https://element-plus.gitee.io/zh-CN/component/input-number.html#%E4%BA%8B%E4%BB%B6
      blur: handle,
      focus: handle,
      change: handle,
      input: handle,
      clear: handle,
    },
  },
  {
    type: "select",
    key: "projectId",
    label: "下拉",
    required: true,
    span: 12,
    placeholder: "请选择",
    style: "width: 100%",
    optionLabelKey: "label",
    optionValueKey: "value",
    options: [
      {
        label: "青岛POC",
        value: 1,
      },
      {
        label: "青烟厂智能安全生产监管平台",
        value: 2,
      },
      {
        label: "D-AIOT平台全局测试",
        value: 3,
      },
    ],
    events: {
      // 事件同el-select事件https://element-plus.gitee.io/zh-CN/component/time-select.html#%E4%BA%8B%E4%BB%B6
      change: handle,
      "visible-change": handle,
      "remove-tag": handle,
      blur: handle,
      focus: handle,
      clear: handle,
    },
  },
  {
    type: "select",
    key: "productId",
    label: "下拉新增控件",
    required: true,
    span: 24,
    placeholder: "请选择产品归属",
    style: "width: 100%",
    optionLabelKey: "name",
    optionValueKey: "id",
    options: [
      {
        id: 1,
        name: "电机-非直连",
      },
      {
        id: 2,
        name: "ONVIF摄像头",
      },
      {
        id: 3,
        name: "大数据关联产品",
      },
    ],
    addFormConfig: [
      {
        type: "select",
        key: "parentId",
        label: "新增的控件",
        required: true,
        span: 24,
        placeholder: "请选择",
        style: "width: 100%",
        optionLabelKey: "label",
        optionValueKey: "value",
        options: [
          {
            label: "测试",
            value: 1,
          },
        ],
      },
    ],
    events: {
      change: handleProduct,
    },
  },
  {
    type: "checkbox",
    key: "checkbox",
    label: "多选框",
    required: true,
    span: 24,
    placeholder: "请选择",
    style: "width: 100%",
    optionLabelKey: "label",
    optionValueKey: "value",
    defaultValue: [1],
    options: [
      {
        label: "青岛POC",
        value: 1,
      },
      {
        label: "青烟厂智能安全生产监管平台",
        value: 2,
      },
      {
        label: "D-AIOT平台全局测试",
        value: 3,
      },
    ],
  },
  {
    type: "radio",
    key: "radio",
    label: "单选",
    required: true,
    span: 24,
    placeholder: "请选择项目归属",
    style: "width: 100%",
    optionLabelKey: "label",
    optionValueKey: "value",
    defaultValue: 1,
    options: [
      {
        label: "青岛POC",
        value: 1,
      },
      {
        label: "青烟厂智能安全生产监管平台",
        value: 2,
      },
      {
        label: "D-AIOT平台全局测试",
        value: 3,
      },
    ],
    events: {
      // 事件同el-radio事件https://element-plus.gitee.io/zh-CN/component/radio.html#radio-%E4%BA%8B%E4%BB%B6
      change: handle,
      "visible-change": handle,
      "remove-tag": handle,
      blur: handle,
      focus: handle,
      clear: handle,
    },
  },
  {
    type: "datetime",
    key: "date",
    label: "日期",
    span: 24,
    rows: 4,
    maxlength: 200,
    disabled: false,
    placeholder: "请选择日期",
    style: "width: 100%",
  },
  {
    type: "time",
    key: "time",
    label: "时刻",
    span: 24,
    rows: 4,
    maxlength: 200,
    disabled: false,
    placeholder: "请选择时刻",
    style: "width: 100%",
  },
  {
    type: "switch",
    key: "switch",
    label: "开关",
    span: 24,
    rows: 4,
    maxlength: 200,
    required: false,
    disabled: false,
    placeholder: "请选择",
    style: "width: 100%",
  },
  {
    type: "slider",
    key: "slider",
    label: "滑块",
    span: 24,
    rows: 4,
    maxlength: 200,
    disabled: false,
    placeholder: "请选择",
  },
  {
    type: "rate",
    key: "rate",
    label: "评分",
    span: 24,
    rows: 4,
    maxlength: 200,
    disabled: false,
    placeholder: "请选择",
  },
  {
    type: "color",
    key: "color",
    label: "颜色",
    span: 24,
    rows: 4,
    maxlength: 200,
    disabled: false,
    placeholder: "请选择",
    defaultValue: "rgba(255, 69, 0, 0.68)",
  },
  {
    type: "file",
    key: "file",
    label: "文件",
    span: 24,
    rows: 4,
    fileList: [
      {
        name: "food.jpeg",
        url: "https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100",
      },
      {
        name: "food2.jpeg",
        url: "https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100",
      },
    ],
    showFileList: false,
    listType: "text",
    disabled: false,
    placeholder: "只能上传jpg/png文件,且不超过500kb",
  },
  {
    type: "textarea",
    key: "describe",
    label: "描述",
    span: 24,
    rows: 4,
    maxlength: 200,
    disabled: false,
    placeholder: "请输入描述",
    style: "width: 100%",
  },
  {
    type: "cascader",
    key: "cascader",
    label: "级联选择器",
    span: 24,
    required: false,
    disabled: false,
    placeholder: "请选择",
    style: "width: 100%",
    options: [
      {
        value: "guide",
        label: "Guide",
        children: [
          {
            value: "disciplines",
            label: "Disciplines",
            children: [
              {
                value: "consistency",
                label: "Consistency",
              },
              {
                value: "feedback",
                label: "Feedback",
              },
              {
                value: "efficiency",
                label: "Efficiency",
              },
              {
                value: "controllability",
                label: "Controllability",
              },
            ],
          },
          {
            value: "navigation",
            label: "Navigation",
            children: [
              {
                value: "side nav",
                label: "Side Navigation",
              },
              {
                value: "top nav",
                label: "Top Navigation",
              },
            ],
          },
        ],
      },
    ],
  },
]);
let dialog = reactive({
  visible: false,
  isLoading: false,
  type: "edit",
  title: "表单模态框",
  labelPosition: "right",
  formConfig: [...defaultFormConfig],
  formData: {},
  addAttrs: [],
  onsubmit: onsubmit,
  size: "default",
  submitLoading: true,
  dialog: true,
  language: "zh-cn", // 语言zh-cn、en
});
let form = reactive({ ...dialog, dialog: false });

function open(handelType, title) {
  dialog.type = handelType;
  dialog.title = title;
  dialog.formConfig = [...defaultFormConfig];
  dialog.formData = {};
  dialog.visible = true;
}

const restaurants = ref(loadAll());
function querySearch(queryString, cb) {
  const results = queryString
    ? restaurants.value.filter(createFilter(queryString))
    : restaurants.value;
  // call callback function to return suggestions
  cb(results);
}

function createFilter(queryString) {
  return (restaurant) => {
    return (
      restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
    );
  };
}

function loadAll() {
  return [
    { value: "vue", link: "https://github.com/vuejs/vue" },
    { value: "element", link: "https://github.com/ElemeFE/element" },
    { value: "cooking", link: "https://github.com/ElemeFE/cooking" },
    { value: "mint-ui", link: "https://github.com/ElemeFE/mint-ui" },
    { value: "vuex", link: "https://github.com/vuejs/vuex" },
    { value: "vue-router", link: "https://github.com/vuejs/vue-router" },
    { value: "babel", link: "https://github.com/babel/babel" },
  ];
}

// val: 值, config:配置, index:序号, treatedFormConfig:生成好的表单配置列表, formData:表单数据, srcFormData: 表单原始数据
function handle(val, config, index, treatedFormConfig, formData, srcFormData) {}
// 动态新增/减少控件
function handleProduct(
  val,
  config,
  index,
  treatedFormConfig,
  formData,
  srcFormData
) {
  console.log(val, config, index, treatedFormConfig, formData, srcFormData);
  let formConfig = [...defaultFormConfig];
  formConfig.splice(index + 1, 0, ...(config.addFormConfig || []));

  dialog.formConfig = formConfig;
  dialog.formData = formData;
}
// 自定义表单校验规则
function validator(rule, value, callback) {
  if (!value) {
    callback(new Error("不能为空"));
  } else {
    callback();
  }
}
// handelType 弹窗操作类型, formData 表单数据, srcFormData 原始表单数据, closeCb 关闭弹窗函数
function onsubmit(handelType, formData, srcFormData, closeCb) {
  new Promise((resolve, reject) => {
    setTimeout(closeCb, 1000);
  });
}
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  position: absolute;
  top: 20px;
  right: 20px;
  bottom: 20px;
  left: 20px;
}
</style>

ElFormChen 源码

<template>
  <el-config-provider :locale="locale">
    <div class="formdialog" v-loading="isLoading">
      <el-form
        :model="allData.form"
        :rules="allData.rules"
        :labelWidth="labelWidth"
        :label-position="labelPosition"
        :size="size"
        :style="{
          maxHeight: dialog
            ? 'calc(100vh - 310px)'
            : '10000000000px !important',
        }"
        ref="formRef"
      >
        <el-row :gutter="20">
          <el-col
            v-for="(config, index) in allData.treatedFormConfig.filter(
              (config) => !config.hidden
            )"
            :key="config.key"
            :span="config.span"
            :style="config.elColStyle || ''"
          >
            <slot
              v-if="config.type === 'slot'"
              :name="config.slotName"
              :$props="props"
              :$data="allData"
              :config="config"
            />
            <div
              v-else-if="config.type === 'text'"
              class="dflex mgb10"
              style="text-align: right"
              :style="config.style"
            >
              <div
                :style="{ width: labelWidth }"
                style="padding-right: 12px; box-sizing: border-box"
              >
                <span class="mgr10">{{ config.label }}</span>
              </div>
              <div class="flex"></div>
            </div>

            <el-form-item v-else :label="`${config.label}`" :prop="config.key">
              <!-- 自动补全输入框 -->
              <el-autocomplete
                v-if="config.type === 'autocomplete'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              />
              <!-- 输入框 -->
              <el-input
                v-if="config.type === 'input'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              />
              <!-- 数字 -->
              <el-input-number
                v-if="config.type === 'number'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              />
              <!-- 文本域 -->
              <el-input
                v-if="config.type === 'textarea'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
              </el-input>
              <!-- 多选 -->
              <el-checkbox-group
                v-if="config.type === 'checkbox'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
                <el-checkbox
                  v-for="item in config.options"
                  :key="
                    config.optionValueKey
                      ? item[config.optionValueKey]
                      : item.value
                  "
                  :label="
                    config.optionValueKey
                      ? item[config.optionValueKey]
                      : item.value
                  "
                >
                  {{
                    config.optionLabelKey
                      ? item[config.optionLabelKey]
                      : item.label
                  }}
                </el-checkbox>
              </el-checkbox-group>
              <!-- 单选 -->
              <el-radio-group
                v-if="config.type === 'radio'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
                <el-radio
                  v-for="item in config.options"
                  :key="
                    config.optionValueKey
                      ? item[config.optionValueKey]
                      : item.value
                  "
                  :label="
                    config.optionValueKey
                      ? item[config.optionValueKey]
                      : item.value
                  "
                >
                  {{
                    config.optionLabelKey
                      ? item[config.optionLabelKey]
                      : item.label
                  }}
                </el-radio>
              </el-radio-group>
              <!-- 下拉 -->
              <el-select
                v-if="config.type === 'select'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
                <el-option
                  v-for="item in config.options"
                  :key="
                    config.optionValueKey
                      ? item[config.optionValueKey]
                      : item.value
                  "
                  :label="
                    config.optionLabelKey
                      ? item[config.optionLabelKey]
                      : item.label
                  "
                  :value="
                    config.optionValueKey
                      ? item[config.optionValueKey]
                      : item.value
                  "
                />
              </el-select>
              <!-- 日期 -->
              <el-date-picker
                v-if="
                  [
                    'year',
                    'month',
                    'date',
                    'dates',
                    'week',
                    'datetime',
                    'datetimerange',
                    'daterange',
                    'monthrange',
                  ].includes(config.type)
                "
                :type="config.type"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
              </el-date-picker>
              <!-- 时间 -->
              <el-time-picker
                v-if="config.type == 'time'"
                :type="config.type"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
              </el-time-picker>
              <!-- 开关 -->
              <el-switch
                v-if="config.type === 'switch'"
                type="date"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
              </el-switch>
              <!-- 滑块 -->
              <el-slider
                v-if="config.type === 'slider'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
              </el-slider>
              <!-- 评分 -->
              <el-rate
                v-if="config.type === 'rate'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              ></el-rate>
              <!-- 颜色 -->
              <el-color-picker
                v-if="config.type === 'color'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              ></el-color-picker>
              <!-- 文件上传 -->
              <el-upload
                v-if="config.type === 'file'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :action="config.action || ''"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              >
                <el-button
                  v-if="config['listType'] != 'picture-card'"
                  type="primary"
                  :size="size"
                  :disabled="type === 'view' || config.disabled"
                  >点击上传</el-button
                >
                <template #tip v-if="config.placeholder">
                  <div class="el-upload__tip">
                    <div v-html="config.placeholder" class="disabled"></div>
                  </div>
                </template>
              </el-upload>
              <!-- 评分 -->
              <el-cascader
                v-if="config.type === 'cascader'"
                v-model.trim="allData.form[config.key]"
                v-bind="config"
                :disabled="type === 'view' || config.disabled"
                v-on="eventsAddParams(config.events, config, index)"
              ></el-cascader>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div class="text-center mgt20">
        <el-button @click="closedialog">关 闭</el-button>
        <el-button v-if="type !== 'view'" type="primary" @click="onsubmit"
          >保 存</el-button
        >
        <slot name="addBtns" :$data="allData" />
      </div>
      <slot name="tip" />
    </div>
  </el-config-provider>
</template>

<script setup>
import { reactive, ref, watch, nextTick, computed } from "vue";
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import en from "element-plus/dist/locale/en.mjs";

const emits = defineEmits(["closedialog", "onsubmit", "updata:props.visible"]);

let props = defineProps({
  visible: {
    // 显示/隐藏
    type: Boolean,
    default: false,
  },
  title: {
    // 标题
    type: String,
    default: "",
  },
  width: {
    // 宽度
    type: String,
    default: "",
  },
  destroyOnClose: {
    type: Boolean,
    default: true,
  },
  closeOnClickModal: {
    // 点击遮罩是否关闭弹窗
    type: Boolean,
    default: false,
  },
  dialog: {
    // 是否是弹窗的表单
    type: Boolean,
    default: false,
  },
  type: {
    // 类型
    type: String,
    default: "add", // add or edit or view
  },
  labelPosition: {
    // 标签的位置 right/left/top
    type: String,
    default: "right",
  },
  labelWidth: {
    // 标签宽度
    type: String,
    default: "140px",
  },
  formConfig: {
    // 表单配置
    type: Array,
    default: () => [],
  },
  formData: {
    // 表单数据
    type: Object,
    default: () => {},
  },
  size: {
    // 表达尺寸 default/small/large
    type: String,
    default: "",
  },
  submitLoading: {
    // 提交是否显示loading状态
    type: Boolean,
    default: true,
  },
  language: {
    // 语言
    type: String,
    default: "zh-cn",
    validator(value) {
      return ["zh-cn", "en"].includes(value);
    },
  },
});

let isLoading = ref(false);
let allData = reactive({
  form: {},
  rules: {},
  treatedFormConfig: [],
  saveFormConfig: [],
  saveRules: {},
  saveForm: {},
});
const formRef = ref(null);
const locale = computed(() => (props.language === "zh-cn" ? zhCn : en));
let dialogVisible = computed({
  get() {
    return props.visible;
  },
  set(newValue) {
    emits("updata:props.visible", newValue);
  },
});

watch(
  () => props.formConfig,
  () => {
    init();
  },
  { immediate: true, deep: true }
);

function init() {
  let form = {};
  let rules = {};
  allData.form = {};
  allData.rules = {};
  allData.treatedFormConfig = [];

  allData.saveFormConfig = [];
  allData.saveRules = {};
  allData.saveForm = {};

  allData.treatedFormConfig = [...props.formConfig];
  allData.treatedFormConfig.forEach((config, index) => {
    if (config.type === "text" || config.type === "slot") return;
    form[config.key] =
      props.formData[config.key] ||
      props.formData[config.key] === 0 ||
      props.formData[config.key] === false
        ? props.formData[config.key]
        : config.defaultValue ||
          config.defaultValue == undefined ||
          config.defaultValue === false
        ? config.defaultValue
        : config.type === "number"
        ? 0
        : "";
    // 如果有追加参数
    if (config.addKeys && config.addKeys.length) {
      config.addKeys.forEach((key) => {
        form[key] =
          props.formData[key] ||
          props.formData[key] === 0 ||
          props.formData[key] === false
            ? props.formData[key]
            : "";
      });
    }
    // 如果是必填,则生成规则
    let rule = {
      required: !!config.required,
    };
    if (config.validator) {
      rule.validator = config.validator;
    } else {
      rule.message = config.errorMsg || config.placeholder;
    }
    rules[config.key] = rule;

    // 如果是日期,并且有配置日期选择限制
    if (
      config.type === "date" &&
      config.disabledDateFormKey &&
      config.operator
    ) {
      config.pickerOptions = {
        disabledDate(time) {
          if (form[config.disabledDateFormKey]) {
            let date = new Date(form[config.disabledDateFormKey]);
            let val =
              config.operator === ">"
                ? time.getTime() > date.getTime()
                : time.getTime() < date.getTime();
            return date ? val : false;
          }
          return false;
        },
      };
    }
  });
  allData.form = form;

  allData.rules = rules;
  reset();
  // 保存formConfig,rules,form
  allData.saveFormConfig = [...props.formConfig];
  allData.saveRules = {
    ...rules,
  };
  allData.saveForm = {
    ...form,
  };
}
// 给事件回调函数添加参数
function eventsAddParams(events, config, index) {
  let rEvents = {};

  for (const key in events) {
    rEvents[key] = () => {
      events[key](
        allData.form[config.key],
        config,
        index,
        allData.treatedFormConfig,
        allData.form,
        props.formData
      );
    };
  }
  return rEvents;
}
function onsubmit() {
  formRef?.value?.validate((valid) => {
    if (valid) {
      let data = {
        ...allData.form,
      };

      if (props.submitLoading) {
        isLoading.value = true;
        emits(
          "onsubmit",
          props.type,
          data,
          props.formData,
          closedialog,
          hideLoading
        );
      } else {
        closedialog();
      }
    } else {
      console.log("error submit!!");
      return false;
    }
  });
}
function showLoading() {
  isLoading.value = true;
}
function hideLoading() {
  isLoading.value = false;
}
function closedialog() {
  hideLoading();
  emits("closedialog");
  return false;
}
function reset() {
  nextTick(() => {
    formRef?.value?.clearValidate();
  });
}
</script>
<style lang="less" scoped>
.dflex {
  display: flex;
}
.flex {
  flex: 1;
}
.text-center {
  text-align: center;
}
.text-left {
  text-align: left;
}
.mgb10 {
  margin-bottom: 10px;
}
.mgt20 {
  margin-top: 20px;
}
.disabled {
  color: #c0c4cc !important;
  cursor: not-allowed;
}
:deep(.el-dialog__body) {
  padding: 0px 0px 20px 0px;
}
:deep(.el-form-item__label) {
  height: 34px;
}

:deep(.el-input-number input) {
  text-align: left;
}

:deep(.el-slider) {
  padding: 0 10px;
}

:deep(.el-rate) {
  position: relative;
  top: 10px;
}

:deep(.el-form-item--mini) {
  .el-slider {
    position: relative;
    top: -7px;
  }
  .el-rate {
    position: relative;
    top: 5px;
  }
}

:deep(.el-form-item--small) {
  .el-slider {
    position: relative;
    top: -5px;
  }
  .el-rate {
    position: relative;
    top: 5px;
  }
}

.formdialog {
  margin-top: 30px;
}

.el-form {
  padding: 0px 20px;
  margin: 10px 0;
  min-height: 100px;
  overflow-y: auto;
}

.dsc {
  height: 45px;
  background: #f7f9fc;
  padding-left: 10px;
  line-height: 45px;
  // font-size: 0.72917vw;
  color: rgb(51, 119, 255);
  i {
    color: rgb(51, 119, 255);
  }
}
</style>

ElFormDialogChen 源码

<template>
  <el-config-provider :locale="locale">
    <el-dialog
      top="50px"
      modal-append-to-body
      :title="title || '模态框'"
      v-model="dialogVisible"
      :width="width || '800px'"
      :destroyOnClose="destroyOnClose"
      :closeOnClickModal="closeOnClickModal"
      @close="closedialog"
    >
      <div v-if="dialogVisible">
        <div class="formdialog" v-loading="isLoading">
          <el-form
            :model="allData.form"
            :rules="allData.rules"
            :labelWidth="labelWidth"
            :label-position="labelPosition"
            :size="size"
            :style="{
              maxHeight: dialog
                ? 'calc(100vh - 310px)'
                : '10000000000px !important',
            }"
            ref="formRef"
          >
            <el-row :gutter="20">
              <el-col
                v-for="(config, index) in allData.treatedFormConfig.filter(
                  (config) => !config.hidden
                )"
                :key="config.key"
                :span="config.span"
                :style="config.elColStyle || ''"
              >
                <slot
                  v-if="config.type === 'slot'"
                  :name="config.slotName"
                  :$props="props"
                  :$data="allData"
                  :config="config"
                />
                <div
                  v-else-if="config.type === 'text'"
                  class="dflex mgb10"
                  style="text-align: right"
                  :style="config.style"
                >
                  <div
                    :style="{ width: labelWidth }"
                    style="padding-right: 12px; box-sizing: border-box"
                  >
                    <span class="mgr10">{{ config.label }}</span>
                  </div>
                  <div class="flex"></div>
                </div>

                <el-form-item
                  v-else
                  :label="`${config.label}`"
                  :prop="config.key"
                >
                  <!-- 自动补全输入框 -->
                  <el-autocomplete
                    v-if="config.type === 'autocomplete'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  />
                  <!-- 输入框 -->
                  <el-input
                    v-if="config.type === 'input'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  />
                  <!-- 数字 -->
                  <el-input-number
                    v-if="config.type === 'number'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  />
                  <!-- 文本域 -->
                  <el-input
                    v-if="config.type === 'textarea'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                  </el-input>
                  <!-- 多选 -->
                  <el-checkbox-group
                    v-if="config.type === 'checkbox'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                    <el-checkbox
                      v-for="item in config.options"
                      :key="
                        config.optionValueKey
                          ? item[config.optionValueKey]
                          : item.value
                      "
                      :label="
                        config.optionValueKey
                          ? item[config.optionValueKey]
                          : item.value
                      "
                    >
                      {{
                        config.optionLabelKey
                          ? item[config.optionLabelKey]
                          : item.label
                      }}
                    </el-checkbox>
                  </el-checkbox-group>
                  <!-- 单选 -->
                  <el-radio-group
                    v-if="config.type === 'radio'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                    <el-radio
                      v-for="item in config.options"
                      :key="
                        config.optionValueKey
                          ? item[config.optionValueKey]
                          : item.value
                      "
                      :label="
                        config.optionValueKey
                          ? item[config.optionValueKey]
                          : item.value
                      "
                    >
                      {{
                        config.optionLabelKey
                          ? item[config.optionLabelKey]
                          : item.label
                      }}
                    </el-radio>
                  </el-radio-group>
                  <!-- 下拉 -->
                  <el-select
                    v-if="config.type === 'select'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                    <el-option
                      v-for="item in config.options"
                      :key="
                        config.optionValueKey
                          ? item[config.optionValueKey]
                          : item.value
                      "
                      :label="
                        config.optionLabelKey
                          ? item[config.optionLabelKey]
                          : item.label
                      "
                      :value="
                        config.optionValueKey
                          ? item[config.optionValueKey]
                          : item.value
                      "
                    />
                  </el-select>
                  <!-- 日期 -->
                  <el-date-picker
                    v-if="
                      [
                        'year',
                        'month',
                        'date',
                        'dates',
                        'week',
                        'datetime',
                        'datetimerange',
                        'daterange',
                        'monthrange',
                      ].includes(config.type)
                    "
                    :type="config.type"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                  </el-date-picker>
                  <!-- 时间 -->
                  <el-time-picker
                    v-if="config.type == 'time'"
                    :type="config.type"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                  </el-time-picker>
                  <!-- 开关 -->
                  <el-switch
                    v-if="config.type === 'switch'"
                    type="date"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                  </el-switch>
                  <!-- 滑块 -->
                  <el-slider
                    v-if="config.type === 'slider'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                  </el-slider>
                  <!-- 评分 -->
                  <el-rate
                    v-if="config.type === 'rate'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  ></el-rate>
                  <!-- 颜色 -->
                  <el-color-picker
                    v-if="config.type === 'color'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  ></el-color-picker>
                  <!-- 文件上传 -->
                  <el-upload
                    v-if="config.type === 'file'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :action="config.action || ''"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  >
                    <el-button
                      v-if="config['listType'] != 'picture-card'"
                      type="primary"
                      :size="size"
                      :disabled="type === 'view' || config.disabled"
                      >点击上传</el-button
                    >
                    <template #tip v-if="config.placeholder">
                      <div class="el-upload__tip">
                        <div v-html="config.placeholder" class="disabled"></div>
                      </div>
                    </template>
                  </el-upload>
                  <!-- 评分 -->
                  <el-cascader
                    v-if="config.type === 'cascader'"
                    v-model.trim="allData.form[config.key]"
                    v-bind="config"
                    :disabled="type === 'view' || config.disabled"
                    v-on="eventsAddParams(config.events, config, index)"
                  ></el-cascader>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
          <div class="text-center mgt20">
            <el-button @click="closedialog">关 闭</el-button>
            <el-button v-if="type !== 'view'" type="primary" @click="onsubmit"
              >保 存</el-button
            >
            <slot name="addBtns" :$data="allData" />
          </div>
          <slot name="tip" />
        </div>
      </div>
    </el-dialog>
  </el-config-provider>
</template>

<script setup>
import { reactive, ref, watch, nextTick, computed } from "vue";
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
import en from "element-plus/dist/locale/en.mjs";

const emits = defineEmits(["closedialog", "onsubmit", "updata:props.visible"]);

let props = defineProps({
  visible: {
    // 显示/隐藏
    type: Boolean,
    default: false,
  },
  title: {
    // 标题
    type: String,
    default: "",
  },
  width: {
    // 宽度
    type: String,
    default: "",
  },
  destroyOnClose: {
    type: Boolean,
    default: true,
  },
  closeOnClickModal: {
    // 点击遮罩是否关闭弹窗
    type: Boolean,
    default: false,
  },
  dialog: {
    // 是否是弹窗的表单
    type: Boolean,
    default: false,
  },
  type: {
    // 类型
    type: String,
    default: "add", // add or edit or view
  },
  labelPosition: {
    // 标签的位置 right/left/top
    type: String,
    default: "right",
  },
  labelWidth: {
    // 标签宽度
    type: String,
    default: "140px",
  },
  formConfig: {
    // 表单配置
    type: Array,
    default: () => [],
  },
  formData: {
    // 表单数据
    type: Object,
    default: () => {},
  },
  size: {
    // 表达尺寸 default/small/large
    type: String,
    default: "",
  },
  submitLoading: {
    // 提交是否显示loading状态
    type: Boolean,
    default: true,
  },
  language: {
    // 语言
    type: String,
    default: "zh-cn",
    validator(value) {
      return ["zh-cn", "en"].includes(value);
    },
  },
});

let isLoading = ref(false);
let allData = reactive({
  form: {},
  rules: {},
  treatedFormConfig: [],
  saveFormConfig: [],
  saveRules: {},
  saveForm: {},
});
const formRef = ref(null);
const locale = computed(() => (props.language === "zh-cn" ? zhCn : en));
let dialogVisible = computed({
  get() {
    return props.visible;
  },
  set(newValue) {
    emits("updata:props.visible", newValue);
  },
});

watch(
  () => props.visible,
  () => {
    if (props.visible) {
      init();
    }
  }
);

watch(
  () => props.formConfig,
  () => {
    init();
  },
  { immediate: true, deep: true }
);

function init() {
  let form = {};
  let rules = {};
  allData.form = {};
  allData.rules = {};
  allData.treatedFormConfig = [];

  allData.saveFormConfig = [];
  allData.saveRules = {};
  allData.saveForm = {};

  allData.treatedFormConfig = [...props.formConfig];
  allData.treatedFormConfig.forEach((config, index) => {
    if (config.type === "text" || config.type === "slot") return;
    form[config.key] =
      props.formData[config.key] ||
      props.formData[config.key] === 0 ||
      props.formData[config.key] === false
        ? props.formData[config.key]
        : config.defaultValue ||
          config.defaultValue == undefined ||
          config.defaultValue === false
        ? config.defaultValue
        : config.type === "number"
        ? 0
        : "";
    // 如果有追加参数
    if (config.addKeys && config.addKeys.length) {
      config.addKeys.forEach((key) => {
        form[key] =
          props.formData[key] ||
          props.formData[key] === 0 ||
          props.formData[key] === false
            ? props.formData[key]
            : "";
      });
    }
    // 如果是必填,则生成规则
    let rule = {
      required: !!config.required,
    };
    if (config.validator) {
      rule.validator = config.validator;
    } else {
      rule.message = config.errorMsg || config.placeholder;
    }
    rules[config.key] = rule;

    // 如果是日期,并且有配置日期选择限制
    if (
      config.type === "date" &&
      config.disabledDateFormKey &&
      config.operator
    ) {
      config.pickerOptions = {
        disabledDate(time) {
          if (form[config.disabledDateFormKey]) {
            let date = new Date(form[config.disabledDateFormKey]);
            let val =
              config.operator === ">"
                ? time.getTime() > date.getTime()
                : time.getTime() < date.getTime();
            return date ? val : false;
          }
          return false;
        },
      };
    }
  });
  allData.form = form;

  allData.rules = rules;
  reset();
  // 保存formConfig,rules,form
  allData.saveFormConfig = [...props.formConfig];
  allData.saveRules = {
    ...rules,
  };
  allData.saveForm = {
    ...form,
  };
}
// 给事件回调函数添加参数
function eventsAddParams(events, config, index) {
  let rEvents = {};

  for (const key in events) {
    rEvents[key] = () => {
      events[key](
        allData.form[config.key],
        config,
        index,
        allData.treatedFormConfig,
        allData.form,
        props.formData
      );
    };
  }
  return rEvents;
}
function onsubmit() {
  formRef?.value?.validate((valid) => {
    if (valid) {
      let data = {
        ...allData.form,
      };

      if (props.submitLoading) {
        isLoading.value = true;
        emits(
          "onsubmit",
          props.type,
          data,
          props.formData,
          closedialog,
          hideLoading
        );
      } else {
        closedialog();
      }
    } else {
      console.log("error submit!!");
      return false;
    }
  });
}
function showLoading() {
  isLoading.value = true;
}
function hideLoading() {
  isLoading.value = false;
}
function closedialog() {
  hideLoading();
  emits("closedialog");
  return false;
}
function reset() {
  nextTick(() => {
    formRef?.value?.clearValidate();
  });
}
</script>
<style lang="less" scoped>
.dflex {
  display: flex;
}
.flex {
  flex: 1;
}
.text-center {
  text-align: center;
}
.text-left {
  text-align: left;
}
.mgb10 {
  margin-bottom: 10px;
}
.mgt20 {
  margin-top: 20px;
}
.disabled {
  color: #c0c4cc !important;
  cursor: not-allowed;
}
:deep(.el-dialog__body) {
  padding: 0px 0px 20px 0px;
}
:deep(.el-form-item__label) {
  height: 34px;
}

:deep(.el-input-number input) {
  text-align: left;
}

:deep(.el-slider) {
  padding: 0 10px;
}

:deep(.el-rate) {
  position: relative;
  top: 10px;
}

:deep(.el-form-item--mini) {
  .el-slider {
    position: relative;
    top: -7px;
  }
  .el-rate {
    position: relative;
    top: 5px;
  }
}

:deep(.el-form-item--small) {
  .el-slider {
    position: relative;
    top: -5px;
  }
  .el-rate {
    position: relative;
    top: 5px;
  }
}

.formdialog {
  margin-top: 30px;
}

.el-form {
  padding: 0px 20px;
  margin: 10px 0;
  min-height: 100px;
  overflow-y: auto;
}

.dsc {
  height: 45px;
  background: #f7f9fc;
  padding-left: 10px;
  line-height: 45px;
  // font-size: 0.72917vw;
  color: rgb(51, 119, 255);
  i {
    color: rgb(51, 119, 255);
  }
}
</style>