From efc0c3a697969503634138d7881543f4099b81ca Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期三, 20 五月 2026 13:32:09 +0800
Subject: [PATCH] 审批模板导入只能从已有模板导入

---
 src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue |  238 ++++++++++++++++++++++++++++++++++++++++++++++++++---------
 1 files changed, 200 insertions(+), 38 deletions(-)

diff --git a/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue b/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
index 1881f60..a5d1da7 100644
--- a/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
+++ b/src/views/officeProcessAutomation/ApproveManage/approve-template/components/FormConfigEditor.vue
@@ -21,12 +21,20 @@
           </el-tag>
         </div>
         <div class="fce-toolbar-actions">
-          <el-dropdown trigger="click" @command="applyPreset">
-            <el-button size="small">浠庨璁惧鍏�</el-button>
+          <el-dropdown trigger="click" @visible-change="onImportDropdownVisible" @command="importFromTemplate">
+            <el-button size="small" :loading="templateImportLoading">浠庡凡鏈夋ā鏉垮鍏�</el-button>
             <template #dropdown>
               <el-dropdown-menu>
-                <el-dropdown-item v-for="p in FORM_CONFIG_PRESETS" :key="p.key" :command="p.key">
-                  {{ p.label }}
+                <el-dropdown-item v-if="!templateImportOptions.length" disabled>
+                  鏆傛棤鍏朵粬瀹℃壒妯℃澘
+                </el-dropdown-item>
+                <el-dropdown-item
+                  v-for="t in templateImportOptions"
+                  :key="t.id"
+                  :command="t.id"
+                >
+                  <span>{{ t.label }}</span>
+                  <el-tag v-if="!t.enabled" size="small" type="info" class="import-tag">宸插仠鐢�</el-tag>
                 </el-dropdown-item>
               </el-dropdown-menu>
             </template>
@@ -38,7 +46,7 @@
       <el-empty
         v-if="!inner.fields.length"
         class="fce-empty"
-        description="鏆傛棤濉姤椤癸紝鍙坊鍔犳垨浠庨璁惧揩閫熷鍏�"
+        description="鏆傛棤濉姤椤癸紝鍙坊鍔犳垨浠庡凡鏈夊鎵规ā鏉垮鍏�"
         :image-size="72"
       />
 
@@ -219,10 +227,12 @@
               placeholder="閫夊~"
               style="width: 100%"
               clearable
+              filterable
+              :loading="optionSourceLoading"
               @change="emitOut"
             >
               <el-option
-                v-for="o in field.options.filter((x) => x.value !== '' && x.value != null)"
+                v-for="o in resolvedSelectOptions(field)"
                 :key="String(o.value)"
                 :label="o.label || o.value"
                 :value="o.value"
@@ -231,28 +241,52 @@
           </div>
 
           <div v-if="field.type === 'select'" class="fce-section fce-section--options">
-            <div class="fce-options-head">
-              <span class="fce-section-title">涓嬫媺閫夐」</span>
-              <el-button type="primary" link size="small" :icon="Plus" @click="addOption(field)">
-                娣诲姞閫夐」
-              </el-button>
-            </div>
-            <div
-              v-for="(opt, oi) in field.options"
-              :key="oi"
-              class="fce-option-row"
-            >
-              <span class="fce-option-index">{{ oi + 1 }}</span>
-              <el-input v-model="opt.label" placeholder="鏄剧ず鏂囨湰" @input="emitOut" />
-              <el-input v-model="opt.value" placeholder="閫夐」鍊�" class="fce-option-value" @input="emitOut" />
-              <el-button
-                type="danger"
-                link
-                :icon="Delete"
-                :disabled="field.options.length <= 1"
-                @click="removeOption(field, oi)"
-              />
-            </div>
+            <span class="fce-section-title">涓嬫媺閫夐」</span>
+            <el-row :gutter="16" class="fce-source-row">
+              <el-col :span="12">
+                <el-form-item label="閫夐」鏉ユ簮" class="fce-field-item">
+                  <el-select
+                    v-model="field.optionSource"
+                    style="width: 100%"
+                    @change="onOptionSourceChange(field)"
+                  >
+                    <el-option
+                      v-for="s in SELECT_OPTION_SOURCE_OPTIONS"
+                      :key="s.value"
+                      :label="s.label"
+                      :value="s.value"
+                    />
+                  </el-select>
+                </el-form-item>
+              </el-col>
+            </el-row>
+            <p v-if="isDynamicOptionSource(field.optionSource)" class="fce-source-tip">
+              {{ optionSourceDesc(field.optionSource) }}銆傛彁浜ゅ鎵规椂灏嗚嚜鍔ㄥ姞杞芥渶鏂版暟鎹紝鏃犻渶鎵嬪姩缁存姢閫夐」銆�
+            </p>
+            <template v-if="!isDynamicOptionSource(field.optionSource)">
+              <div class="fce-options-head">
+                <span class="fce-section-subtitle">鎵嬪姩閫夐」</span>
+                <el-button type="primary" link size="small" :icon="Plus" @click="addOption(field)">
+                  娣诲姞閫夐」
+                </el-button>
+              </div>
+              <div
+                v-for="(opt, oi) in field.options"
+                :key="oi"
+                class="fce-option-row"
+              >
+                <span class="fce-option-index">{{ oi + 1 }}</span>
+                <el-input v-model="opt.label" placeholder="鏄剧ず鏂囨湰" @input="emitOut" />
+                <el-input v-model="opt.value" placeholder="閫夐」鍊�" class="fce-option-value" @input="emitOut" />
+                <el-button
+                  type="danger"
+                  link
+                  :icon="Delete"
+                  :disabled="field.options.length <= 1"
+                  @click="removeOption(field, oi)"
+                />
+              </div>
+            </template>
           </div>
         </div>
       </div>
@@ -262,23 +296,47 @@
 
 <script setup>
 import { Bottom, Delete, Plus, Top } from "@element-plus/icons-vue";
-import { reactive, watch } from "vue";
 import {
-  FORM_CONFIG_PRESETS,
+  getApprovalTemplateDetail,
+  listApprovalTemplate,
+  TEMPLATE_TYPE_BUILTIN,
+  TEMPLATE_TYPE_CUSTOM,
+} from "@/api/officeProcessAutomation/approvalTemplate.js";
+import { ElMessage, ElMessageBox } from "element-plus";
+import { reactive, ref, watch } from "vue";
+import {
+  mapEnabledFromApi,
+  unwrapTemplateDetail,
+  unwrapTemplateList,
+} from "../approveTemplateConstants.js";
+import {
   FORM_FIELD_TYPE_OPTIONS,
-  applyFormConfigPreset,
   createEmptyFormConfigData,
   createEmptyFormField,
   formFieldTypeLabel,
+  parseFormConfigToData,
 } from "../formConfigUtils.js";
+import {
+  SELECT_OPTION_SOURCE,
+  SELECT_OPTION_SOURCE_OPTIONS,
+  isDynamicOptionSource,
+} from "../selectOptionSource.js";
+import { useSelectOptionSources } from "../useSelectOptionSources.js";
 
 const props = defineProps({
   modelValue: { type: Object, default: () => createEmptyFormConfigData() },
+  /** 缂栬緫褰撳墠妯℃澘鏃舵帓闄よ嚜韬紝閬垮厤浠庤嚜宸卞鍏� */
+  excludeTemplateId: { type: [String, Number], default: null },
 });
 
 const emit = defineEmits(["update:modelValue"]);
 
 const inner = reactive(createEmptyFormConfigData());
+
+const { loading: optionSourceLoading, ensureForFields, getOptions } = useSelectOptionSources();
+
+const templateImportOptions = ref([]);
+const templateImportLoading = ref(false);
 
 function typeLabel(type) {
   return formFieldTypeLabel(type);
@@ -289,6 +347,15 @@
   return `閫夊~锛岄�夋嫨妯℃澘鏃跺皢棰勫~${name}`;
 }
 
+function optionSourceDesc(source) {
+  return SELECT_OPTION_SOURCE_OPTIONS.find((x) => x.value === source)?.desc || "";
+}
+
+function resolvedSelectOptions(field) {
+  if (field.type !== "select") return [];
+  return getOptions(field);
+}
+
 function syncFromProps(v) {
   const src = v || createEmptyFormConfigData();
   inner.summaryPlaceholder = src.summaryPlaceholder || "";
@@ -296,8 +363,10 @@
     ...createEmptyFormField(),
     ...f,
     _uid: f._uid || createEmptyFormField()._uid,
+    optionSource: f.optionSource || SELECT_OPTION_SOURCE.STATIC,
     options: (f.options || [{ label: "", value: "" }]).map((o) => ({ ...o })),
   }));
+  ensureForFields(inner.fields);
 }
 
 function emitOut() {
@@ -313,6 +382,7 @@
       min: f.min,
       precision: f.precision,
       defaultValue: cloneDefaultValue(f),
+      optionSource: f.optionSource || SELECT_OPTION_SOURCE.STATIC,
       options: (f.options || []).map((o) => ({ label: o.label, value: o.value })),
     })),
   });
@@ -333,6 +403,7 @@
 
 function addField() {
   inner.fields.push(createEmptyFormField());
+  ensureForFields(inner.fields);
   emitOut();
 }
 
@@ -357,10 +428,23 @@
 }
 
 function onTypeChange(field) {
-  if (field.type === "select" && (!field.options || !field.options.length)) {
-    field.options = [{ label: "", value: "" }];
+  if (field.type === "select") {
+    if (!field.optionSource) field.optionSource = SELECT_OPTION_SOURCE.STATIC;
+    if (!field.options || !field.options.length) {
+      field.options = [{ label: "", value: "" }];
+    }
+    ensureForFields(inner.fields);
   }
   resetDefaultValueForType(field);
+  emitOut();
+}
+
+function onOptionSourceChange(field) {
+  field.defaultValue = "";
+  if (!isDynamicOptionSource(field.optionSource) && (!field.options || !field.options.length)) {
+    field.options = [{ label: "", value: "" }];
+  }
+  ensureForFields(inner.fields);
   emitOut();
 }
 
@@ -375,10 +459,66 @@
   emitOut();
 }
 
-function applyPreset(key) {
-  const data = applyFormConfigPreset(key);
-  syncFromProps(data);
-  emitOut();
+async function loadTemplateImportOptions() {
+  templateImportLoading.value = true;
+  try {
+    const [customRes, builtinRes] = await Promise.all([
+      listApprovalTemplate(TEMPLATE_TYPE_CUSTOM),
+      listApprovalTemplate(TEMPLATE_TYPE_BUILTIN),
+    ]);
+    const excludeId =
+      props.excludeTemplateId != null && props.excludeTemplateId !== ""
+        ? String(props.excludeTemplateId)
+        : "";
+    templateImportOptions.value = [...unwrapTemplateList(customRes), ...unwrapTemplateList(builtinRes)]
+      .filter((row) => row?.id != null && String(row.id) !== excludeId)
+      .map((row) => ({
+        id: row.id,
+        label: row.templateName || `妯℃澘 #${row.id}`,
+        enabled: mapEnabledFromApi(row.enabled),
+      }));
+  } catch {
+    templateImportOptions.value = [];
+    ElMessage.error("鍔犺浇瀹℃壒妯℃澘鍒楄〃澶辫触");
+  } finally {
+    templateImportLoading.value = false;
+  }
+}
+
+function onImportDropdownVisible(visible) {
+  if (visible) loadTemplateImportOptions();
+}
+
+async function importFromTemplate(templateId) {
+  if (!templateId) return;
+  if (inner.fields.length) {
+    try {
+      await ElMessageBox.confirm("灏嗚鐩栧綋鍓嶅~鎶ラ」閰嶇疆锛屾槸鍚︾户缁紵", "浠庢ā鏉垮鍏�", {
+        type: "warning",
+        confirmButtonText: "缁х画瀵煎叆",
+        cancelButtonText: "鍙栨秷",
+      });
+    } catch {
+      return;
+    }
+  }
+  templateImportLoading.value = true;
+  try {
+    const res = await getApprovalTemplateDetail(templateId);
+    const row = unwrapTemplateDetail(res);
+    const data = parseFormConfigToData(row?.formConfig);
+    if (!data.fields?.length) {
+      ElMessage.warning("璇ユā鏉挎湭閰嶇疆濉姤椤�");
+      return;
+    }
+    syncFromProps(data);
+    emitOut();
+    ElMessage.success(`宸插鍏ャ��${row.templateName || "妯℃澘"}銆嶇殑濉姤椤筦);
+  } catch {
+    ElMessage.error("鍔犺浇妯℃澘璇︽儏澶辫触");
+  } finally {
+    templateImportLoading.value = false;
+  }
 }
 </script>
 
@@ -435,6 +575,10 @@
   display: flex;
   align-items: center;
   gap: 8px;
+}
+.import-tag {
+  margin-left: 8px;
+  vertical-align: middle;
 }
 
 .fce-empty {
@@ -585,10 +729,28 @@
   margin-bottom: 10px;
 }
 
-.fce-options-head .fce-section-title {
+.fce-options-head .fce-section-title,
+.fce-options-head .fce-section-subtitle {
   margin-bottom: 0;
 }
 
+.fce-section-subtitle {
+  font-size: 12px;
+  font-weight: 600;
+  color: var(--el-text-color-secondary);
+}
+
+.fce-source-row {
+  margin-bottom: 4px;
+}
+
+.fce-source-tip {
+  margin: 0 0 10px;
+  font-size: 12px;
+  color: var(--el-text-color-secondary);
+  line-height: 1.5;
+}
+
 .fce-option-row {
   display: flex;
   align-items: center;

--
Gitblit v1.9.3