From 0e5a00bf8804b600415f53e71b80d12b250958be Mon Sep 17 00:00:00 2001
From: 祖安之光 <11848914+light-of-zuan@user.noreply.gitee.com>
Date: Fri, 10 Apr 2026 11:13:49 +0800
Subject: [PATCH] 添加暂存和图片

---
 src/views/components/formDialog.vue |  535 ++++++++++++++++++++++++++++++++++------------------------
 1 files changed, 314 insertions(+), 221 deletions(-)

diff --git a/src/views/components/formDialog.vue b/src/views/components/formDialog.vue
index fdb0322..08bfb5c 100644
--- a/src/views/components/formDialog.vue
+++ b/src/views/components/formDialog.vue
@@ -1,62 +1,74 @@
 <template>
   <div class="dynamic-form">
     <el-dialog
-        v-model="dialogVisible"
-        :title="state.title"
-        width="50%"
-        :before-close="closeDialog"
-        :close-on-press-escape="false"
-        :close-on-click-modal="false"
+      v-model="dialogVisible"
+      :title="state.title"
+      width="50%"
+      :before-close="closeDialog"
+      :close-on-press-escape="false"
+      :close-on-click-modal="false"
     >
       <el-dialog v-model="state.dialogVisible">
-        <el-image style="width: 100%" :src="state.dialogImageUrl"/>
+        <el-image style="width: 100%" :src="state.dialogImageUrl" />
       </el-dialog>
       <el-form
-          :model="state.formData"
-          size="default"
-          ref="formRef"
-          :rules="state.formRules"
-          label-width="160px"
+        :model="state.formData"
+        size="default"
+        ref="formRef"
+        :rules="state.formRules"
+        label-width="160px"
       >
         <template v-for="(item, index) in config.formItems" :key="index">
-        <el-divider v-if="item.type === 'divider'" content-position="left">{{item.content}}</el-divider>
-        <el-form-item
+          <el-divider v-if="item.type === 'divider'" content-position="left">{{
+            item.content
+          }}</el-divider>
+          <el-form-item
             v-else
             :label="item.label + (item.label.endsWith(':') ? '' : ':')"
             :prop="item.prop"
             :rules="item.rules"
-        >
-          <!-- 文件上传组件 -->
-          <el-upload
+          >
+            <!-- 文件上传组件 -->
+            <el-upload
               v-if="item.type === 'upload'"
               :action="state.uploadUrl"
               :headers="state.header"
               method="post"
-              :on-success="(res, uploadFile) => handleUploadSuccess(res, uploadFile, item.prop)"
+              :on-success="
+                (res, uploadFile) =>
+                  handleUploadSuccess(res, uploadFile, item.prop)
+              "
               :on-exceed="showTip"
               :limit="item.limit || 1"
               v-model:file-list="state.fileLists[item.prop]"
               :before-upload="(rawFile) => beforeUpload(rawFile, item)"
-              :on-remove="(file, uploadFiles) => handleRemove(file, uploadFiles, item.prop)"
+              :on-remove="
+                (file, uploadFiles) =>
+                  handleRemove(file, uploadFiles, item.prop)
+              "
               :accept="item.accept || '.doc,.docx,.pdf,.jpg,.jpeg,.png'"
               :disabled="state.title === '查看' || item.disabled"
-              :list-type="item.uploadType == 'img'?'picture-card':''"
+              :list-type="item.uploadType == 'img' ? 'picture-card' : ''"
               :on-preview="handlePictureCardPreview"
-          >
-            <el-icon v-if="item.uploadType == 'img'"><Plus /></el-icon>
-            <el-button v-else type="primary">点击上传</el-button>
-            <template #tip>
-              <div class="el-upload__tip" v-if="item.tip">
-                {{ item.tip }}
-              </div>
-              <div class="el-upload__tip" v-else>
-                支持上传{{ item.accept || '.doc,.docx,.pdf,.jpg,.jpeg,.png' }}格式, 尺寸小于{{ (item.maxSize || 5) }}M,最多可上传{{ item.limit || 1 }}份
-              </div>
-            </template>
-          </el-upload>
+            >
+              <el-icon v-if="item.uploadType == 'img'"><Plus /></el-icon>
+              <el-button v-else type="primary">点击上传</el-button>
+              <template #tip>
+                <div class="el-upload__tip" v-if="item.tip">
+                  {{ item.tip }}
+                </div>
+                <div class="el-upload__tip" v-else>
+                  支持上传{{
+                    item.accept || ".doc,.docx,.pdf,.jpg,.jpeg,.png"
+                  }}格式, 尺寸小于{{ item.maxSize || 5 }}M,最多可上传{{
+                    item.limit || 1
+                  }}份
+                </div>
+              </template>
+            </el-upload>
 
-          <!-- 多选选择框组件 -->
-          <el-select
+            <!-- 多选选择框组件 -->
+            <el-select
               v-else-if="item.type === 'select' && item.multiple"
               v-model="state.formData[item.prop]"
               :placeholder="item.placeholder || '请选择'"
@@ -68,18 +80,22 @@
               :collapse-tags="item.collapseTags"
               :collapse-tags-tooltip="item.collapseTagsTooltip"
               :style="item.style || 'width: 100%'"
-              @change="item.changeEvent ? handleEvent(item.changeEvent, state.formData[item.prop]) : null"
-          >
-            <el-option
+              @change="
+                item.changeEvent
+                  ? handleEvent(item.changeEvent, state.formData[item.prop])
+                  : null
+              "
+            >
+              <el-option
                 v-for="option in getOptions(item.options)"
                 :key="option.value"
                 :label="option.label"
                 :value="option.value"
-            />
-          </el-select>
+              />
+            </el-select>
 
-          <!-- 单选选择框组件 -->
-          <el-select
+            <!-- 单选选择框组件 -->
+            <el-select
               v-else-if="item.type === 'select'"
               v-model="state.formData[item.prop]"
               :placeholder="item.placeholder || '请选择'"
@@ -87,18 +103,22 @@
               :clearable="item.clearable !== false"
               :filterable="item.filterable"
               :style="item.style || 'width: 100%'"
-              @change="item.changeEvent ? handleEvent(item.changeEvent, state.formData[item.prop]) : null"
-          >
-            <el-option
+              @change="
+                item.changeEvent
+                  ? handleEvent(item.changeEvent, state.formData[item.prop])
+                  : null
+              "
+            >
+              <el-option
                 v-for="option in getOptions(item.options)"
                 :key="option.value"
                 :label="option.label"
                 :value="option.value"
-            />
-          </el-select>
+              />
+            </el-select>
 
-          <!-- 级联选择组件 -->
-          <el-cascader
+            <!-- 级联选择组件 -->
+            <el-cascader
               v-else-if="item.type === 'cascader'"
               v-model="state.formData[item.prop]"
               :options="getCascaderOptions(item)"
@@ -109,12 +129,15 @@
               :filterable="item.filterable"
               :show-all-levels="item.showAllLevels !== false"
               :style="item.style || 'width: 100%'"
-              @change="item.changeEvent ? handleEvent(item.changeEvent, state.formData[item.prop]) : null"
-          />
+              @change="
+                item.changeEvent
+                  ? handleEvent(item.changeEvent, state.formData[item.prop])
+                  : null
+              "
+            />
 
-
-          <!-- 日期选择器组件 -->
-          <el-date-picker
+            <!-- 日期选择器组件 -->
+            <el-date-picker
               v-else-if="item.type === 'date'"
               v-model="state.formData[item.prop]"
               :type="item.dateType || 'date'"
@@ -122,10 +145,10 @@
               :disabled="state.title === '查看' || item.disabled"
               :value-format="item.valueFormat || 'YYYY-MM-DD'"
               :style="item.style || 'width: 100%'"
-          />
+            />
 
-          <!-- 文本域组件 -->
-          <el-input
+            <!-- 文本域组件 -->
+            <el-input
               v-else-if="item.type === 'textarea'"
               v-model="state.formData[item.prop]"
               type="textarea"
@@ -134,10 +157,10 @@
               :readonly="state.title === '查看'"
               :autosize="item.autosize || { minRows: 2 }"
               :style="item.style || 'width: 100%'"
-          />
+            />
 
-          <!-- 默认文本输入框组件 -->
-          <el-input
+            <!-- 默认文本输入框组件 -->
+            <el-input
               v-else
               v-model="state.formData[item.prop]"
               :placeholder="item.placeholder || '请输入'"
@@ -145,15 +168,29 @@
               :readonly="state.title === '查看'"
               :style="item.style || 'width: 100%'"
               :type="item.inputType || 'text'"
-          />
-        </el-form-item>
+            />
+          </el-form-item>
         </template>
       </el-form>
 
       <template #footer v-if="state.title !== '查看'">
         <span class="dialog-footer">
+          <el-button
+            type="primary"
+            @click="onSave"
+            v-if="state.hasSave"
+            size="default"
+            v-preReClick
+            >暂存</el-button
+          >
           <el-button @click="closeDialog" size="default">取 消</el-button>
-          <el-button type="primary" @click="onSubmit" size="default" v-preReClick>确认</el-button>
+          <el-button
+            type="primary"
+            @click="onSubmit"
+            size="default"
+            v-preReClick
+            >确认</el-button
+          >
         </span>
       </template>
     </el-dialog>
@@ -161,305 +198,362 @@
 </template>
 
 <script setup>
-import { reactive, ref, nextTick } from 'vue'
-import { ElMessage } from "element-plus"
-import { getToken } from "@/utils/auth"
+import { reactive, ref, nextTick } from "vue";
+import { ElMessage } from "element-plus";
+import { getToken } from "@/utils/auth";
 
 // 定义props
 const props = defineProps({
   config: {
     type: Object,
     required: true,
-    default: () => ({})
+    default: () => ({}),
   },
   // API相关函数
   api: {
     type: Object,
-    default: () => ({})
+    default: () => ({}),
   },
   // 数据加载函数
   dataLoader: {
     type: Object,
-    default: () => ({})
+    default: () => ({}),
   },
   // 文件上传相关配置
   uploadConfig: {
     type: Object,
     default: () => ({
-      uploadUrl: import.meta.env.VITE_APP_BASE_API + '/common/upload',
-      deleteUrl: '/common/deleteFile'
-    })
-  }
-})
+      uploadUrl: import.meta.env.VITE_APP_BASE_API + "/common/upload",
+      deleteUrl: "/common/deleteFile",
+    }),
+  },
+});
 
-const emit = defineEmits(["submit", "close", "update:modelValue"])
+const emit = defineEmits(["submit", "close", "update:modelValue"]);
 
-const dialogVisible = ref(false)
-const formRef = ref()
+const dialogVisible = ref(false);
+const formRef = ref();
 
 const state = reactive({
-  title: '',
+  title: "",
   formData: {},
   formRules: {},
   uploadUrl: props.uploadConfig.uploadUrl,
   header: {
-    Authorization: getToken()
+    Authorization: getToken(),
   },
   fileLists: {},
   companyList: [],
   deptList: [],
   userList: [],
   dialogVisible: false,
-  dialogImageUrl: ''
-})
+  dialogImageUrl: "",
+  hasSave: false,
+});
 
 const initFormData = () => {
-  const formData = {}
-  state.fileLists = {}
+  const formData = {};
+  state.fileLists = {};
 
-  props.config.formItems?.forEach(item => {
-
-    if (item.type === 'select' && item.multiple) {
-      formData[item.prop] = Array.isArray(item.defaultValue) ? item.defaultValue : []
+  props.config.formItems?.forEach((item) => {
+    if (item.type === "select" && item.multiple) {
+      formData[item.prop] = Array.isArray(item.defaultValue)
+        ? item.defaultValue
+        : [];
     } else {
-      formData[item.prop] = item.defaultValue !== undefined ? item.defaultValue : null
+      formData[item.prop] =
+        item.defaultValue !== undefined ? item.defaultValue : null;
     }
 
-    if (item.type === 'upload') {
-      state.fileLists[item.prop] = []
+    if (item.type === "upload") {
+      state.fileLists[item.prop] = [];
     }
-  })
+  });
 
-  const formRules = {}
-  props.config.formItems?.forEach(item => {
+  const formRules = {};
+  props.config.formItems?.forEach((item) => {
     if (item.rules) {
-      formRules[item.prop] = item.rules
+      formRules[item.prop] = item.rules;
     }
 
-    if (item.type === 'upload' && item.required) {
-      formRules[item.prop] = formRules[item.prop] || []
+    if (item.type === "upload" && item.required) {
+      formRules[item.prop] = formRules[item.prop] || [];
       formRules[item.prop].push({
         required: true,
         validator: (rule, value, callback) => {
           if (state.fileLists[item.prop]?.length === 0) {
-            callback(new Error(`请上传${item.label}`))
+            callback(new Error(`请上传${item.label}`));
           } else {
-            callback()
+            callback();
           }
         },
-        trigger: 'blur'
-      })
+        trigger: "blur",
+      });
     }
 
-    if (item.type === 'select' && item.multiple && item.required) {
-      formRules[item.prop] = formRules[item.prop] || []
+    if (item.type === "select" && item.multiple && item.required) {
+      formRules[item.prop] = formRules[item.prop] || [];
       formRules[item.prop].push({
         validator: (rule, value, callback) => {
           if (!value || value.length === 0) {
-            callback(new Error(`请选择${item.label}`))
+            callback(new Error(`请选择${item.label}`));
           } else {
-            callback()
+            callback();
           }
         },
-        trigger: 'change'
-      })
+        trigger: "change",
+      });
     }
-  })
+  });
 
-  state.formData = formData
-  state.formRules = formRules
-}
+  state.formData = formData;
+  state.formRules = formRules;
+};
 
 const getOptions = (options) => {
-  if (!options) return []
+  if (!options) return [];
 
-  if (typeof options === 'function') {
-    return options()
+  if (typeof options === "function") {
+    return options();
   }
 
   if (Array.isArray(options)) {
-    return options
+    return options;
   }
 
-  return []
-}
+  return [];
+};
 
 const getCascaderOptions = (item) => {
-  if (!item.options) return []
+  if (!item.options) return [];
 
-  if (typeof item.options === 'function') {
-    return item.options()
+  if (typeof item.options === "function") {
+    return item.options();
   }
 
   if (Array.isArray(item.options)) {
-    return item.options
+    return item.options;
   }
 
-  if (item.options && typeof item.options === 'object' && 'value' in item.options) {
-    return item.options.value
+  if (
+    item.options &&
+    typeof item.options === "object" &&
+    "value" in item.options
+  ) {
+    return item.options.value;
   }
 
-  return []
-}
+  return [];
+};
 
 const beforeUpload = (rawFile, item) => {
-  const maxSize = item.maxSize || 5
+  const maxSize = item.maxSize || 5;
   if (rawFile.size / 1024 / 1024 > maxSize) {
-    ElMessage.warning(`文件大小不能超过${maxSize}M`)
-    return false
+    ElMessage.warning(`文件大小不能超过${maxSize}M`);
+    return false;
   }
-  return true
-}
+  return true;
+};
 
 const handleUploadSuccess = (res, uploadFile, prop) => {
   if (res.code === 200) {
-    ElMessage.success('上传成功')
+    ElMessage.success("上传成功");
   } else {
-    state.fileLists[prop] = state.fileLists[prop].filter(file => file.uid !== uploadFile.uid)
-    ElMessage.warning(res.message || '文件上传失败')
+    state.fileLists[prop] = state.fileLists[prop].filter(
+      (file) => file.uid !== uploadFile.uid,
+    );
+    ElMessage.warning(res.message || "文件上传失败");
   }
-  state.formData[prop] = state.fileLists[prop].filter(i=>i.status == 'success').map(item=>{
-    return item.response.fileName
-  }).join(',')
-}
+  state.formData[prop] = state.fileLists[prop]
+    .filter((i) => i.status == "success")
+    .map((item) => {
+      return item.response.fileName;
+    })
+    .join(",");
+};
 
 const handleRemove = async (file, uploadFiles, prop) => {
   try {
-    if(file.uid){
-      state.fileLists[prop] = state.fileLists[prop].filter(f => f.uid !== file.uid)
-    }else{
-      state.fileLists[prop] = state.fileLists[prop].filter(f => f.url !== file.url)
+    if (file.uid) {
+      state.fileLists[prop] = state.fileLists[prop].filter(
+        (f) => f.uid !== file.uid,
+      );
+    } else {
+      state.fileLists[prop] = state.fileLists[prop].filter(
+        (f) => f.url !== file.url,
+      );
     }
-    state.formData[prop] = state.fileLists[prop].filter(i=>i.status == 'success').map(item=>{
-      return item.response.fileName
-    }).join(',')
-    ElMessage.success('文件已删除')
+    state.formData[prop] = state.fileLists[prop]
+      .filter((i) => i.status == "success")
+      .map((item) => {
+        return item.response.fileName;
+      })
+      .join(",");
+    ElMessage.success("文件已删除");
   } catch (error) {
-    console.error('删除文件时出错:', error)
-    ElMessage.warning('文件删除失败')
+    console.error("删除文件时出错:", error);
+    ElMessage.warning("文件删除失败");
   }
-}
+};
 
 const handlePictureCardPreview = (uploadFile) => {
-  state.dialogImageUrl = uploadFile.url
-  state.dialogVisible = true
-}
+  state.dialogImageUrl = uploadFile.url;
+  state.dialogVisible = true;
+};
 
 const showTip = () => {
-  ElMessage.warning('超出文件上传数量')
-}
+  ElMessage.warning("超出文件上传数量");
+};
 
 const handleEvent = async (eventName, value) => {
   try {
-    if (eventName === 'getUserListByRole' && props.dataLoader.getUserListByRole) {
-      const userList = await props.dataLoader.getUserListByRole(value)
-      state.userList = userList
-      const userItem = props.config.formItems.find(item => item.prop === 'reformUserId')
+    if (
+      eventName === "getUserListByRole" &&
+      props.dataLoader.getUserListByRole
+    ) {
+      const userList = await props.dataLoader.getUserListByRole(value);
+      state.userList = userList;
+      const userItem = props.config.formItems.find(
+        (item) => item.prop === "reformUserId",
+      );
       if (userItem) {
-        userItem.options = () => userList.map(user => ({
-          value: user.userId,
-          label: user.nickName
-        }))
-        state.formData.reformUserId = null
-        await nextTick()
+        userItem.options = () =>
+          userList.map((user) => ({
+            value: user.userId,
+            label: user.nickName,
+          }));
+        state.formData.reformUserId = null;
+        await nextTick();
       }
     }
   } catch (error) {
-    console.error('事件处理失败:', error)
-    ElMessage.error('数据加载失败')
+    console.error("事件处理失败:", error);
+    ElMessage.error("数据加载失败");
   }
-}
+};
 
-const openDialog = async (type, initialData) => {
-  state.title = type === 'add' ? '新增' : type === 'edit' ? '编辑' : type === 'rectify' ? '整改' : '查看'
-
-  initFormData()
+const openDialog = async (type, initialData, hasSave) => {
+  state.title =
+    type === "add"
+      ? "新增"
+      : type === "edit"
+        ? "编辑"
+        : type === "rectify"
+          ? "整改"
+          : "查看";
+  state.hasSave = hasSave
+  initFormData();
 
   try {
     if (props.dataLoader.loadInitialData) {
-      const initialData = await props.dataLoader.loadInitialData(id)
-      Object.assign(state, initialData)
+      const initialData = await props.dataLoader.loadInitialData(id);
+      Object.assign(state, initialData);
     }
   } catch (error) {
-    console.error('无初始数据:', error)
+    console.error("无初始数据:", error);
   }
 
   if (initialData) {
-    Object.keys(state.formData).forEach(key => {
+    Object.keys(state.formData).forEach((key) => {
       if (key in initialData) {
-
-        const item = props.config.formItems.find(i => i.prop === key)
-        if (item && item.type === 'select' && item.multiple) {
-          state.formData[key] = Array.isArray(initialData[key]) ? initialData[key] : (initialData[key] ? [initialData[key]] : [])
+        const item = props.config.formItems.find((i) => i.prop === key);
+        if (item && item.type === "select" && item.multiple) {
+          state.formData[key] = Array.isArray(initialData[key])
+            ? initialData[key]
+            : initialData[key]
+              ? [initialData[key]]
+              : [];
         } else {
-          state.formData[key] = initialData[key]
+          state.formData[key] = initialData[key];
         }
       }
-    })
-    state.formData.id = initialData.id || null
+    });
+    state.formData.id = initialData.id || null;
 
-    props.config.formItems?.forEach(item => {
-      if (item.type === 'upload' && initialData[item.prop]) {
-        state.fileLists[item.prop] = [{
-          url: initialData[item.prop],
-          name: item.label || '文件'
-        }]
-        state.fileLists[item.prop] = initialData[item.prop].split(',').map(i=>{
-          return {
-            url: import.meta.env.VITE_APP_BASE_API + i,
-            status: 'success',
-            name: item.label || '文件',
-            response: {
-              fileName: i
-            }
-          }
-        })
+    props.config.formItems?.forEach((item) => {
+      if (item.type === "upload" && initialData[item.prop]) {
+        state.fileLists[item.prop] = [
+          {
+            url: initialData[item.prop],
+            name: item.label || "文件",
+          },
+        ];
+        state.fileLists[item.prop] = initialData[item.prop]
+          .split(",")
+          .map((i) => {
+            return {
+              url: import.meta.env.VITE_APP_BASE_API + i,
+              status: "success",
+              name: item.label || "文件",
+              response: {
+                fileName: i,
+              },
+            };
+          });
       }
-    })
+    });
   }
-  dialogVisible.value = true
+  dialogVisible.value = true;
   await nextTick()
-}
+};
 
 // 提交表单
 const onSubmit = async () => {
   try {
-    const valid = await formRef.value.validate()
+    const valid = await formRef.value.validate();
     if (valid) {
+      const submitData = { ...state.formData };
 
-      const submitData = { ...state.formData }
-
-      props.config.formItems?.forEach(item => {
-        if (item.type === 'select' && item.multiple &&
-            Array.isArray(submitData[item.prop]) &&
-            submitData[item.prop].length === 0) {
-          submitData[item.prop] = null
+      props.config.formItems?.forEach((item) => {
+        if (
+          item.type === "select" &&
+          item.multiple &&
+          Array.isArray(submitData[item.prop]) &&
+          submitData[item.prop].length === 0
+        ) {
+          submitData[item.prop] = null;
         }
-      })
+      });
 
-      emit('submit', submitData, state.title)
+      emit("submit", submitData, state.title);
+    }else{
+      ElMessage.error("请完善表单内容");
     }
   } catch (error) {
-    console.error('表单验证失败:', error)
-    ElMessage.error('请完善表单内容')
+    console.error("表单验证失败:", error);
+    ElMessage.error("请完善表单内容");
   }
-}
+};
 
+const onSave = async () => {
+  const submitData = { ...state.formData };
+  props.config.formItems?.forEach((item) => {
+    if (
+      item.type === "select" &&
+      item.multiple &&
+      Array.isArray(submitData[item.prop]) &&
+      submitData[item.prop].length === 0
+    ) {
+      submitData[item.prop] = null;
+    }
+  });
+  emit("onSave", submitData, state.title);
+};
 
 const closeDialog = () => {
-  formRef.value.clearValidate()
-  formRef.value.resetFields()
-  dialogVisible.value = false
-}
-
+  if (formRef.value) {
+    formRef.value.clearValidate();
+    formRef.value.resetFields();
+  }
+  dialogVisible.value = false;
+};
 
 defineExpose({
   openDialog,
-  closeDialog
-})
+  closeDialog,
+});
 
-
-initFormData()
+initFormData();
 </script>
 
 <style scoped lang="scss">
@@ -474,7 +568,6 @@
   color: #606266;
   margin-top: 7px;
 }
-
 
 :deep(.el-select__tags) {
   white-space: nowrap;

--
Gitblit v1.9.2