From b014cdaf7fcf42cd2b310968f9d47d4420444a6a Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期四, 21 五月 2026 11:46:38 +0800
Subject: [PATCH] 审批模板增加配置模板导入,按钮权限控制,新建页面ui优化

---
 src/pages/oa/_utils/approvalTemplateType.js            |    7 
 src/pages/oa/_utils/approvalFormField.js               |  363 +++++++
 src/pages/oa/ApproveManage/approve-list/index.vue      |   44 
 src/pages/oa/ApproveManage/approve-template/edit.vue   | 1443 ++++++++++++++++++++++++++----
 src/config/oaWorkbench.js                              |   50 
 src/api/oa/approvalInstance.js                         |    5 
 src/pages/oa/ApproveManage/approve-list/apply.vue      |  802 +++++++++++++---
 src/pages/oa/ApproveManage/approve-template/detail.vue |   10 
 src/pages/oa/ApproveManage/approve-template/index.vue  |   10 
 9 files changed, 2,310 insertions(+), 424 deletions(-)

diff --git a/src/api/oa/approvalInstance.js b/src/api/oa/approvalInstance.js
index 08b1f0c..604f437 100644
--- a/src/api/oa/approvalInstance.js
+++ b/src/api/oa/approvalInstance.js
@@ -18,7 +18,10 @@
   });
 }
 
-/** 瀹℃牳涓慨鏀瑰鎵瑰疄渚� PUT /approvalInstance/update */
+/**
+ * 淇敼瀹℃壒瀹炰緥 PUT /approvalInstance/update
+ * @param {Object} approvalInstanceDto 瀹℃壒瀹炰緥锛堥渶鍚� id锛屽叾浣欏瓧娈垫寜涓氬姟淇濈暀/鏇存柊锛�
+ */
 export function updateApprovalInstance(approvalInstanceDto) {
   return request({
     url: "/approvalInstance/update",
diff --git a/src/config/oaWorkbench.js b/src/config/oaWorkbench.js
index 78d8689..c1263d2 100644
--- a/src/config/oaWorkbench.js
+++ b/src/config/oaWorkbench.js
@@ -8,13 +8,13 @@
     key: "HrManage",
     name: "浜轰簨绠$悊",
     children: [
-      { label: "鍛樺伐妗f", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.staffArchive },
-      { label: "鍛樺伐鍚堝悓", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.staffContract },
+      // { label: "鍛樺伐妗f", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.staffArchive },
+      // { label: "鍛樺伐鍚堝悓", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.staffContract },
       { label: "杞鐢宠", icon: "/static/images/icon/hetongguanli.svg", path: OA_NAV.regularApply },
       { label: "璋冨矖鐢宠", icon: "/static/images/icon/renyuanxinzi.svg", path: OA_NAV.transferApply },
       { label: "绂昏亴鐢宠", icon: "/static/images/icon/qingjiaguanli.svg", path: OA_NAV.resignApply },
       { label: "宸ヤ綔浜ゆ帴", icon: "/static/images/icon/gongchuguanli.svg", path: OA_NAV.workHandover },
-      { label: "宀椾綅绠$悊", icon: "/static/images/icon/gongxuguanli.svg", path: OA_NAV.postManage },
+      // { label: "宀椾綅绠$悊", icon: "/static/images/icon/gongxuguanli.svg", path: OA_NAV.postManage },
     ],
   },
   {
@@ -33,14 +33,14 @@
       { label: "璐圭敤鎶ラ攢", icon: "/static/images/icon/baoxiaoguanli.svg", path: OA_NAV.costReimburse },
     ],
   },
-  {
-    key: "ContractManage",
-    name: "鍚堝悓绠$悊",
-    children: [
-      { label: "閲囪喘鍚堝悓", icon: "/static/images/icon/caigoutaizhang.svg", path: OA_NAV.purchaseContract },
-      { label: "閿�鍞悎鍚�", icon: "/static/images/icon/xiaoshoutaizhang.svg", path: OA_NAV.saleContract },
-    ],
-  },
+  // {
+  //   key: "ContractManage",
+  //   name: "鍚堝悓绠$悊",
+  //   children: [
+  //     { label: "閲囪喘鍚堝悓", icon: "/static/images/icon/caigoutaizhang.svg", path: OA_NAV.purchaseContract },
+  //     { label: "閿�鍞悎鍚�", icon: "/static/images/icon/xiaoshoutaizhang.svg", path: OA_NAV.saleContract },
+  //   ],
+  // },
   {
     key: "ApproveManage",
     name: "瀹℃壒绠$悊",
@@ -49,20 +49,20 @@
       { label: "瀹℃壒妯℃澘", icon: "/static/images/icon/guizhangzhidu.svg", path: OA_NAV.approveTemplate },
     ],
   },
-  {
-    key: "EnterpriseNews",
-    name: "浼佷笟鏂伴椈",
-    children: [
-      { label: "浼佷笟鏂伴椈", icon: "/static/images/icon/zhishiku.svg", path: OA_NAV.enterpriseNews },
-    ],
-  },
-  {
-    key: "NoticeAnnouncement",
-    name: "鍏憡閫氱煡",
-    children: [
-      { label: "鍏憡閫氱煡", icon: "/static/images/icon/tongzhigonggao.svg", path: OA_NAV.noticeAnnouncement },
-    ],
-  },
+  // {
+  //   key: "EnterpriseNews",
+  //   name: "浼佷笟鏂伴椈",
+  //   children: [
+  //     { label: "浼佷笟鏂伴椈", icon: "/static/images/icon/zhishiku.svg", path: OA_NAV.enterpriseNews },
+  //   ],
+  // },
+  // {
+  //   key: "NoticeAnnouncement",
+  //   name: "鍏憡閫氱煡",
+  //   children: [
+  //     { label: "鍏憡閫氱煡", icon: "/static/images/icon/tongzhigonggao.svg", path: OA_NAV.noticeAnnouncement },
+  //   ],
+  // },
 ];
 
 /** 宸ヤ綔鍙版墎骞宠彍鍗曪紙绾墠绔厤缃級 */
diff --git a/src/pages/oa/ApproveManage/approve-list/apply.vue b/src/pages/oa/ApproveManage/approve-list/apply.vue
index 12f49c5..3e873a8 100644
--- a/src/pages/oa/ApproveManage/approve-list/apply.vue
+++ b/src/pages/oa/ApproveManage/approve-list/apply.vue
@@ -16,83 +16,150 @@
         <text class="loading-text">鍔犺浇涓�...</text>
       </view>
       <template v-else-if="detail">
-        <view class="section">
-          <view class="section-title">鍩烘湰淇℃伅</view>
-          <view class="form-body">
-            <view class="form-row">
-              <text class="form-label required">瀹℃壒鏍囬</text>
+        <up-form :model="form"
+                 label-width="88"
+                 input-align="right">
+          <u-cell-group title="鍩烘湰淇℃伅"
+                        class="form-section">
+            <up-form-item label="瀹℃壒鏍囬"
+                          required
+                          class="form-item-name">
               <up-input v-model="form.title"
+                        class="name-input-inline"
                         placeholder="璇疯緭鍏ュ鎵规爣棰�"
                         maxlength="100"
                         clearable />
-            </view>
-            <view class="form-row">
-              <text class="form-label">瀹℃壒妯℃澘</text>
-              <text class="form-readonly">{{ templateName }}</text>
-            </view>
-            <view class="form-row">
-              <text class="form-label">鐢宠浜�</text>
-              <text class="form-readonly">{{ displayApplicantName }}</text>
-            </view>
-          </view>
-        </view>
+            </up-form-item>
+            <up-form-item label="瀹℃壒妯℃澘"
+                          class="form-item-readonly">
+              <up-input :model-value="templateName"
+                        readonly />
+            </up-form-item>
+            <up-form-item label="鐢宠浜�"
+                          class="form-item-readonly">
+              <up-input :model-value="displayApplicantName"
+                        readonly />
+            </up-form-item>
+          </u-cell-group>
+        </up-form>
 
-        <view class="section">
-          <view class="section-title">濉姤鍐呭</view>
+        <view class="section-card">
+          <view class="section-head">
+            <text class="section-title">濉姤鍐呭</text>
+          </view>
           <view v-if="formConfigData.prompt"
                 class="form-prompt">
             {{ formConfigData.prompt }}
           </view>
-          <view v-if="formConfigData.fields.length"
-                class="form-body">
-            <view v-for="field in formConfigData.fields"
-                  :key="field.key"
-                  class="form-row form-row--field">
-              <text class="form-label"
-                    :class="{ required: field.required }">{{ field.label }}</text>
-              <up-textarea v-if="field.type === 'textarea'"
+          <up-form v-if="formConfigData.fields.length"
+                   :model="formValues"
+                   label-width="88"
+                   input-align="right"
+                   class="dynamic-form">
+            <up-form-item v-for="field in formConfigData.fields"
+                          :key="field.key"
+                          :label="field.label"
+                          :required="!!field.required"
+                          :class="formItemClass(field)">
+              <up-textarea v-if="isTextareaField(field)"
                            v-model="formValues[field.key]"
                            :placeholder="`璇疯緭鍏�${field.label}`"
                            maxlength="500"
                            border="surround"
                            height="80" />
-              <view v-else-if="field.type === 'date'"
-                    class="date-trigger"
-                    @click="openDatePicker(field.key)">
-                <up-input :model-value="formValues[field.key]"
+              <view v-else-if="isDatetimerangeField(field)"
+                    class="daterange-fill">
+                <view class="range-fill-row"
+                      @click="openRangePicker(field, 'start')">
+                  <text class="range-fill-label">寮�濮�</text>
+                  <up-input :model-value="getRangePartDisplay(field, 'start')"
+                            placeholder="寮�濮嬫椂闂�"
+                            readonly />
+                  <up-icon name="calendar"
+                           size="16"
+                           color="#909399" />
+                </view>
+                <text class="range-fill-sep">鑷�</text>
+                <view class="range-fill-row"
+                      @click="openRangePicker(field, 'end')">
+                  <text class="range-fill-label">缁撴潫</text>
+                  <up-input :model-value="getRangePartDisplay(field, 'end')"
+                            placeholder="缁撴潫鏃堕棿"
+                            readonly />
+                  <up-icon name="calendar"
+                           size="16"
+                           color="#909399" />
+                </view>
+              </view>
+              <view v-else-if="isDateLikeField(field)"
+                    class="field-trigger"
+                    @click="openDatePicker(field)">
+                <up-input :model-value="formatFieldDisplayValue(field, formValues[field.key])"
                           :placeholder="`璇烽�夋嫨${field.label}`"
                           readonly />
+                <up-icon :name="getDatePickerMode(field) === 'time' ? 'clock' : 'calendar'"
+                         size="18"
+                         color="#909399" />
+              </view>
+              <view v-else-if="isSelectField(field)"
+                    class="field-trigger"
+                    @click="openSelectPicker(field)">
+                <up-input :model-value="getSelectDisplayText(field)"
+                          :placeholder="`璇烽�夋嫨${field.label}`"
+                          readonly />
+                <up-icon name="arrow-right"
+                         size="16"
+                         color="#c0c4cc" />
               </view>
               <up-input v-else
                         v-model="formValues[field.key]"
-                        :type="field.type === 'number' ? 'digit' : 'text'"
+                        :type="isNumberField(field) ? 'digit' : 'text'"
                         :placeholder="`璇疯緭鍏�${field.label}`"
                         clearable />
-            </view>
-          </view>
+            </up-form-item>
+          </up-form>
           <view v-else
                 class="empty-hint">璇ユā鏉挎殏鏃犲~鎶ラ」</view>
         </view>
 
-        <view class="section">
-          <view class="section-title">瀹℃壒娴佺▼</view>
+        <view class="section-card">
+          <view class="section-head">
+            <text class="section-title">瀹℃壒娴佺▼</text>
+          </view>
           <view v-if="detail.nodes?.length"
-                class="flow-list">
-            <view v-for="(node, index) in detail.nodes"
-                  :key="node.id || index"
-                  class="flow-card">
-              <view class="flow-card-head">
-                <text class="flow-level">绗瑊{ levelLabel(node.levelNo || index + 1) }}绾�</text>
-                <text class="flow-type">{{ approveTypeText(node.approveType) }}</text>
+                class="flow-wrap">
+            <view v-for="(node, nodeIndex) in detail.nodes"
+                  :key="node.id || nodeIndex"
+                  class="flow-node-block">
+              <view class="flow-node-card">
+                <view class="node-header">
+                  <view class="node-level-badge">{{ node.levelNo || nodeIndex + 1 }}</view>
+                  <text class="node-level-text">绗瑊{ levelLabel(node.levelNo || nodeIndex + 1) }}绾�</text>
+                </view>
+                <view class="approve-type-row approve-type-row--readonly">
+                  <view class="type-btn"
+                        :class="{ active: node.approveType !== 'OR' }">
+                    浼氱
+                  </view>
+                  <view class="type-btn"
+                        :class="{ active: node.approveType === 'OR' }">
+                    鎴栫
+                  </view>
+                </view>
+                <view class="approver-list">
+                  <view v-for="(approver, aIdx) in node.approvers || []"
+                        :key="approver.id || aIdx"
+                        class="approver-chip">
+                    <view class="approver-avatar">{{ (approver.approverName || "?").charAt(0) }}</view>
+                    <text class="approver-name">{{ approver.approverName || "-" }}</text>
+                  </view>
+                  <text v-if="!(node.approvers || []).length"
+                        class="empty-hint inline">鏆傛棤瀹℃壒浜�</text>
+                </view>
               </view>
-              <view class="approver-tags">
-                <text v-for="(approver, aIdx) in node.approvers || []"
-                      :key="approver.id || aIdx"
-                      class="approver-tag">
-                  {{ approver.approverName || "-" }}
-                </text>
-                <text v-if="!(node.approvers || []).length"
-                      class="empty-hint inline">鏆傛棤瀹℃壒浜�</text>
+              <view v-if="nodeIndex < detail.nodes.length - 1"
+                    class="flow-connector">
+                <view class="flow-connector-line" />
               </view>
             </view>
           </view>
@@ -119,10 +186,16 @@
               @close="showDatePicker = false">
       <up-datetime-picker :show="true"
                           v-model="datePickerTs"
-                          mode="date"
+                          :mode="datePickerMode"
                           @confirm="onDateConfirm"
-                          @cancel="showDatePicker = false" />
+                          @cancel="onDatePickerCancel" />
     </up-popup>
+
+    <up-action-sheet :show="showSelectSheet"
+                     :title="selectSheetTitle"
+                     :actions="selectSheetActions"
+                     @select="onSelectOption"
+                     @close="showSelectSheet = false" />
   </view>
 </template>
 
@@ -137,7 +210,28 @@
     updateApprovalInstance,
   } from "@/api/oa/approvalInstance.js";
   import useUserStore from "@/store/modules/user";
-  import { formatDateToYMD, parseTime } from "@/utils/ruoyi";
+  import { parseTime } from "@/utils/ruoyi";
+  import { getDept } from "@/api/collaborativeApproval/approvalProcess.js";
+  import { userListNoPageByTenantId } from "@/api/system/user";
+  import {
+    formatDatetimerangeDisplay,
+    formatFieldDateValue,
+    formatFieldDisplayValue,
+    getDatePickerMode,
+    getFieldInitialValue,
+    getFieldOptionLabel,
+    isDatetimerangeField,
+    isDateLikeField,
+    isNumberField,
+    isSelectField,
+    isTextareaField,
+    joinDatetimerangeValue,
+    mergeFormConfigForEdit,
+    parseDatetimerangeValue,
+    resolveFieldOptions,
+    parseApprovalFormConfig,
+    parseFieldDateToTs,
+  } from "../../_utils/approvalFormField.js";
 
   const EDIT_STORAGE_KEY = "oa_approve_instance_edit_row";
   const LEVEL_TEXT = ["", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�", "涓�", "鍏�", "涔�", "鍗�"];
@@ -154,12 +248,25 @@
 
   const showDatePicker = ref(false);
   const datePickerTs = ref(Date.now());
-  const activeDateFieldKey = ref("");
+  const activeDateField = ref(null);
+  const activeRangePart = ref("start");
+
+  const datePickerMode = computed(() => {
+    const field = activeDateField.value;
+    if (!field) return "date";
+    if (isDatetimerangeField(field)) return "datetime";
+    return getDatePickerMode(field);
+  });
+
+  const showSelectSheet = ref(false);
+  const activeSelectField = ref(null);
+  const pickerUserList = ref([]);
+  const pickerDeptList = ref([]);
 
   const isEditMode = computed(() => !!instanceId.value);
 
-  const pageTitle = computed(() => (isEditMode.value ? "缂栬緫瀹℃壒" : "鍙戣捣瀹℃壒"));
-  const confirmText = computed(() => (isEditMode.value ? "淇濆瓨" : "鎻愪氦瀹℃壒"));
+  const pageTitle = computed(() => (isEditMode.value ? "淇敼瀹℃壒" : "鍙戣捣瀹℃壒"));
+  const confirmText = computed(() => (isEditMode.value ? "淇濆瓨淇敼" : "鎻愪氦瀹℃壒"));
 
   const applicantName = computed(
     () => userStore.nickName || userStore.name || "-"
@@ -173,28 +280,72 @@
     () => detail.value?.templateName || instanceRow.value?.templateName || "-"
   );
 
-  const parseFormConfig = raw => {
-    if (!raw) return { prompt: "", fields: [] };
-    try {
-      const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
-      return {
-        prompt: obj?.prompt || "",
-        fields: Array.isArray(obj?.fields) ? obj.fields : [],
-      };
-    } catch {
-      return { prompt: "", fields: [] };
-    }
-  };
-
   const formConfigData = computed(() => {
-    const raw = isEditMode.value
-      ? instanceRow.value?.formConfig
-      : detail.value?.formConfig;
-    return parseFormConfig(raw);
+    if (isEditMode.value) {
+      return mergeFormConfigForEdit(
+        detail.value?.formConfig,
+        instanceRow.value?.formConfig
+      );
+    }
+    return parseApprovalFormConfig(detail.value?.formConfig);
   });
 
   const levelLabel = n => LEVEL_TEXT[Number(n)] || String(n);
-  const approveTypeText = type => (type === "OR" ? "鎴栫" : "浼氱");
+
+  const selectSheetTitle = computed(
+    () => (activeSelectField.value?.label ? `閫夋嫨${activeSelectField.value.label}` : "璇烽�夋嫨")
+  );
+
+  const selectSheetActions = computed(() => {
+    const field = activeSelectField.value;
+    if (!field) return [];
+    return resolveFieldOptions(field, {
+      users: pickerUserList.value,
+      depts: pickerDeptList.value,
+    }).map(opt => ({
+      name: opt.label,
+      value: opt.value,
+    }));
+  });
+
+  const formItemClass = field => {
+    if (isTextareaField(field)) return "form-item-textarea";
+    if (isDatetimerangeField(field)) return "form-item-daterange";
+    if (isSelectField(field) || isDateLikeField(field)) return "form-item-select";
+    return "form-item-inline";
+  };
+
+  const getRangePartDisplay = (field, part) => {
+    const parts = parseDatetimerangeValue(formValues[field.key]);
+    const val = part === "start" ? parts.start : parts.end;
+    return val ? formatFieldDisplayValue({ type: "datetime" }, val) : "";
+  };
+
+  const openRangePicker = (field, part) => {
+    activeDateField.value = field;
+    activeRangePart.value = part;
+    const parts = parseDatetimerangeValue(formValues[field.key]);
+    const val = part === "start" ? parts.start : parts.end;
+    datePickerTs.value = parseFieldDateToTs(val) ?? Date.now();
+    showDatePicker.value = true;
+  };
+
+  const getSelectDisplayText = field => {
+    const stored = formValues[field.key];
+    const options = resolveFieldOptions(field, {
+      users: pickerUserList.value,
+      depts: pickerDeptList.value,
+    });
+    const matched = options.find(
+      opt =>
+        String(opt.value) === String(stored) || String(opt.label) === String(stored)
+    );
+    return (
+      matched?.label ||
+      getFieldOptionLabel(field, stored) ||
+      (stored !== undefined && stored !== null ? String(stored) : "")
+    );
+  };
 
   const initFormValues = fields => {
     Object.keys(formValues).forEach(key => {
@@ -202,23 +353,60 @@
     });
     fields.forEach(field => {
       if (!field?.key) return;
-      formValues[field.key] = field.value ?? field.defaultValue ?? "";
+      formValues[field.key] = getFieldInitialValue(field);
     });
   };
 
-  const openDatePicker = fieldKey => {
-    activeDateFieldKey.value = fieldKey;
-    const current = formValues[fieldKey];
-    datePickerTs.value = current ? new Date(current).getTime() : Date.now();
+  const openSelectPicker = field => {
+    const options = resolveFieldOptions(field, {
+      users: pickerUserList.value,
+      depts: pickerDeptList.value,
+    });
+    if (!options.length) {
+      uni.showToast({ title: "璇ュ瓧娈垫湭閰嶇疆涓嬫媺閫夐」", icon: "none" });
+      return;
+    }
+    activeSelectField.value = field;
+    showSelectSheet.value = true;
+  };
+
+  const onSelectOption = action => {
+    const key = activeSelectField.value?.key;
+    if (key) {
+      formValues[key] = action.value;
+    }
+    showSelectSheet.value = false;
+    activeSelectField.value = null;
+  };
+
+  const openDatePicker = field => {
+    activeDateField.value = field;
+    const current = formValues[field.key];
+    datePickerTs.value = parseFieldDateToTs(current) ?? Date.now();
     showDatePicker.value = true;
+  };
+
+  const onDatePickerCancel = () => {
+    showDatePicker.value = false;
+    activeDateField.value = null;
   };
 
   const onDateConfirm = e => {
     const ts = e?.value ?? datePickerTs.value;
-    if (activeDateFieldKey.value) {
-      formValues[activeDateFieldKey.value] = formatDateToYMD(ts);
+    const field = activeDateField.value;
+    if (field?.key) {
+      if (isDatetimerangeField(field)) {
+        const parts = parseDatetimerangeValue(formValues[field.key]);
+        const formatted = formatFieldDateValue({ type: "datetime" }, ts);
+        formValues[field.key] = joinDatetimerangeValue(
+          activeRangePart.value === "start" ? formatted : parts.start,
+          activeRangePart.value === "end" ? formatted : parts.end
+        );
+      } else {
+        formValues[field.key] = formatFieldDateValue(field, ts);
+      }
     }
-    showDatePicker.value = false;
+    onDatePickerCancel();
   };
 
   const validateForm = () => {
@@ -230,8 +418,35 @@
       if (!field.required) continue;
       const val = formValues[field.key];
       if (val === undefined || val === null || String(val).trim() === "") {
-        uni.showToast({ title: `璇峰~鍐�${field.label}`, icon: "none" });
+        const action =
+          isSelectField(field) || isDateLikeField(field) || isDatetimerangeField(field)
+            ? "璇烽�夋嫨"
+            : "璇峰~鍐�";
+        uni.showToast({ title: `${action}${field.label}`, icon: "none" });
         return false;
+      }
+      if (isDatetimerangeField(field)) {
+        const { start, end } = parseDatetimerangeValue(val);
+        if (!start || !end) {
+          uni.showToast({ title: `璇峰畬鏁撮�夋嫨${field.label}`, icon: "none" });
+          return false;
+        }
+      }
+      if (isSelectField(field)) {
+        const options = resolveFieldOptions(field, {
+          users: pickerUserList.value,
+          depts: pickerDeptList.value,
+        });
+        if (
+          options.length &&
+          !options.some(
+            opt =>
+              String(opt.value) === String(val) || String(opt.label) === String(val)
+          )
+        ) {
+          uni.showToast({ title: `${field.label}閫夐」鏃犳晥`, icon: "none" });
+          return false;
+        }
       }
     }
     if (!detail.value?.nodes?.length) {
@@ -272,15 +487,23 @@
       templateId: row.templateId ?? detail.value?.id,
       templateName: row.templateName ?? detail.value?.templateName,
       businessId: row.businessId,
-      businessType: row.businessType,
+      businessType: row.businessType ?? detail.value?.businessType,
       title: form.title.trim(),
       status: row.status || "PENDING",
       currentLevel: row.currentLevel,
       applicantId: row.applicantId,
       applicantName: row.applicantName,
       applyTime: row.applyTime,
+      finishTime: row.finishTime,
+      createUser: row.createUser,
+      createTime: row.createTime,
+      updateUser: row.updateUser,
+      updateTime: row.updateTime,
       deptId: row.deptId,
+      deleted: row.deleted,
       formConfig: buildFormConfigPayload(),
+      approveAction: row.approveAction,
+      approveComment: row.approveComment,
     };
   };
 
@@ -296,7 +519,7 @@
     submitApi(payload)
       .then(() => {
         uni.showToast({
-          title: isEditMode.value ? "淇濆瓨鎴愬姛" : "鎻愪氦鎴愬姛",
+          title: isEditMode.value ? "淇敼鎴愬姛" : "鎻愪氦鎴愬姛",
           icon: "success",
         });
         if (isEditMode.value) {
@@ -308,7 +531,7 @@
       })
       .catch(() => {
         uni.showToast({
-          title: isEditMode.value ? "淇濆瓨澶辫触" : "鎻愪氦澶辫触",
+          title: isEditMode.value ? "淇敼澶辫触" : "鎻愪氦澶辫触",
           icon: "none",
         });
       })
@@ -352,9 +575,9 @@
     const row = uni.getStorageSync(EDIT_STORAGE_KEY);
     if (!row || String(row.id) !== String(instanceId.value)) {
       uni.showToast({ title: "鏈幏鍙栧埌瀹℃壒鏁版嵁", icon: "none" });
+      setTimeout(() => uni.navigateBack(), 500);
       return;
     }
-    uni.removeStorageSync(EDIT_STORAGE_KEY);
     instanceRow.value = row;
     templateId.value = row.templateId;
     form.title = row.title || "";
@@ -363,6 +586,7 @@
     detail.value = null;
     try {
       await loadTemplateDetail();
+      if (!detail.value) return;
       initFormValues(formConfigData.value.fields);
     } finally {
       loading.value = false;
@@ -373,7 +597,25 @@
     uni.navigateBack();
   };
 
+  const loadPickerSourceData = () => {
+    userListNoPageByTenantId()
+      .then(res => {
+        pickerUserList.value = res?.data || [];
+      })
+      .catch(() => {
+        pickerUserList.value = [];
+      });
+    getDept()
+      .then(res => {
+        pickerDeptList.value = res?.data || [];
+      })
+      .catch(() => {
+        pickerDeptList.value = [];
+      });
+  };
+
   onLoad(options => {
+    loadPickerSourceData();
     if (options?.id) {
       instanceId.value = options.id;
       loadForEdit();
@@ -389,11 +631,22 @@
 </script>
 
 <style scoped lang="scss">
+  @import "@/static/scss/form-common.scss";
+
+  $primary: #2979ff;
+  $text: #1f2d3d;
+  $text-secondary: #606266;
+  $text-muted: #909399;
+  $bg-page: #f0f3f8;
+  $radius-lg: 12px;
+  $radius-md: 10px;
+  $shadow-card: 0 2px 12px rgba(31, 45, 61, 0.05);
+
   .approve-apply-page {
     display: flex;
     flex-direction: column;
     min-height: 100vh;
-    background: #f0f3f8;
+    background: $bg-page;
   }
 
   .form-scroll {
@@ -412,130 +665,347 @@
 
   .loading-text {
     font-size: 14px;
+    color: $text-muted;
+  }
+
+  .form-section {
+    margin-bottom: 10px;
+    border-radius: $radius-lg;
+    overflow: hidden;
+    box-shadow: $shadow-card;
+  }
+
+  :deep(.form-section .u-cell-group__title) {
+    padding: 12px 16px 8px !important;
+    font-size: 15px !important;
+    font-weight: 600 !important;
+    color: $text !important;
+    background: #fff !important;
+  }
+
+  :deep(.form-section .u-form-item) {
+    padding: 0 16px !important;
+  }
+
+  :deep(.form-section .u-form-item__body) {
+    padding: 10px 0 !important;
+    min-height: auto !important;
+  }
+
+  :deep(.form-item-name .u-form-item__body) {
+    flex-direction: row !important;
+    align-items: center !important;
+  }
+
+  :deep(.form-item-name .u-form-item__content) {
+    flex: 1 !important;
+    min-width: 0 !important;
+    justify-content: flex-end !important;
+  }
+
+  :deep(.name-input-inline),
+  :deep(.name-input-inline .u-input__content) {
+    width: 100% !important;
+    flex: 1 !important;
+  }
+
+  :deep(.name-input-inline input),
+  :deep(.name-input-inline .u-input__content__field-wrapper__field) {
+    width: 100% !important;
+    text-align: right !important;
+    font-size: 15px !important;
+  }
+
+  :deep(.form-item-readonly .u-form-item__body) {
+    align-items: center !important;
+  }
+
+  :deep(.form-item-readonly .u-form-item__content) {
+    flex: 1 !important;
+    min-width: 0 !important;
+    justify-content: flex-end !important;
+  }
+
+  :deep(.form-item-readonly .u-input__content__field-wrapper__field) {
+    text-align: right !important;
+    color: #303133 !important;
+  }
+
+  .dynamic-form {
+    padding: 0 0 4px;
+  }
+
+  :deep(.dynamic-form .u-form-item) {
+    padding: 0 16px !important;
+  }
+
+  :deep(.dynamic-form .u-form-item__body) {
+    padding: 10px 0 !important;
+    min-height: auto !important;
+  }
+
+  :deep(.form-item-inline .u-form-item__body) {
+    flex-direction: row !important;
+    align-items: center !important;
+  }
+
+  :deep(.form-item-inline .u-form-item__content) {
+    flex: 1 !important;
+    min-width: 0 !important;
+    justify-content: flex-end !important;
+  }
+
+  :deep(.form-item-inline input),
+  :deep(.form-item-inline .u-input__content__field-wrapper__field) {
+    text-align: right !important;
+    font-size: 15px !important;
+  }
+
+  :deep(.form-item-select .u-form-item__body) {
+    align-items: center !important;
+  }
+
+  :deep(.form-item-select .u-form-item__content) {
+    flex: 1 !important;
+    min-width: 0 !important;
+    justify-content: flex-end !important;
+  }
+
+  :deep(.form-item-textarea .u-form-item__body) {
+    flex-direction: column !important;
+    align-items: stretch !important;
+    padding: 10px 0 12px !important;
+  }
+
+  :deep(.form-item-textarea .u-form-item__content) {
+    width: 100% !important;
+    justify-content: stretch !important;
+  }
+
+  .field-trigger {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 6px;
+    width: 100%;
+    min-width: 0;
+  }
+
+  :deep(.field-trigger .u-input) {
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+
+  :deep(.field-trigger .u-input__content__field-wrapper__field) {
+    text-align: right !important;
+    font-size: 15px !important;
+  }
+
+  :deep(.form-item-daterange .u-form-item__body) {
+    flex-direction: column !important;
+    align-items: stretch !important;
+  }
+
+  :deep(.form-item-daterange .u-form-item__content) {
+    width: 100% !important;
+    justify-content: stretch !important;
+  }
+
+  .daterange-fill {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .range-fill-row {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 10px;
+    background: #f7f9fc;
+    border: 1px solid #eef1f6;
+    border-radius: 8px;
+  }
+
+  .range-fill-label {
+    flex-shrink: 0;
+    width: 36px;
+    font-size: 13px;
     color: #909399;
   }
 
-  .section {
-    background: #fff;
-    border-radius: 12px;
+  .range-fill-sep {
+    font-size: 12px;
+    color: #c0c4cc;
+    text-align: center;
+  }
+
+  :deep(.range-fill-row .u-input) {
+    flex: 1 !important;
+    min-width: 0 !important;
+  }
+
+  .section-card {
     margin-bottom: 10px;
+    background: #fff;
+    border-radius: $radius-lg;
     overflow: hidden;
-    box-shadow: 0 2px 12px rgba(31, 45, 61, 0.05);
+    box-shadow: $shadow-card;
+  }
+
+  .section-head {
+    padding: 12px 16px;
+    border-bottom: 1px solid #f2f4f7;
   }
 
   .section-title {
-    padding: 12px 16px;
     font-size: 15px;
     font-weight: 600;
-    color: #1f2d3d;
-    border-bottom: 1px solid #f2f4f7;
-    border-left: 3px solid #2979ff;
-    padding-left: 13px;
-  }
-
-  .form-body {
-    padding: 8px 16px 16px;
-  }
-
-  .form-row {
-    padding: 10px 0;
-    border-bottom: 1px solid #f5f7fa;
-
-    &:last-child {
-      border-bottom: none;
-    }
-
-    &--field {
-      flex-direction: column;
-      align-items: stretch;
-    }
-  }
-
-  .form-label {
-    display: block;
-    margin-bottom: 8px;
-    font-size: 14px;
-    color: #606266;
-
-    &.required::before {
-      content: "*";
-      color: #f56c6c;
-      margin-right: 4px;
-    }
-  }
-
-  .form-readonly {
-    font-size: 14px;
-    color: #303133;
+    color: $text;
+    padding-left: 10px;
+    border-left: 3px solid $primary;
+    line-height: 1.2;
   }
 
   .form-prompt {
     margin: 12px 16px 0;
     padding: 10px 12px;
     font-size: 13px;
-    color: #606266;
+    color: $text-secondary;
     background: #f8fafc;
     border-radius: 8px;
     line-height: 1.5;
   }
 
-  .date-trigger {
-    width: 100%;
+  .flow-wrap {
+    padding: 10px 16px 14px;
   }
 
-  .flow-list {
+  .flow-node-block {
+    display: flex;
+    flex-direction: column;
+    align-items: stretch;
+  }
+
+  .flow-node-card {
+    background: #fafbfd;
+    border: 1px solid #e8eef5;
+    border-radius: $radius-md;
     padding: 12px;
   }
 
-  .flow-card {
-    padding: 12px;
-    margin-bottom: 8px;
-    background: #f8fafc;
+  .node-header {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 10px;
+  }
+
+  .node-level-badge {
+    width: 26px;
+    height: 26px;
     border-radius: 8px;
-    border: 1px solid #eef2f6;
+    background: $primary;
+    color: #fff;
+    font-size: 14px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
 
-    &:last-child {
-      margin-bottom: 0;
+  .node-level-text {
+    flex: 1;
+    font-size: 15px;
+    font-weight: 600;
+    color: $text;
+  }
+
+  .approve-type-row {
+    display: flex;
+    background: #f0f3f8;
+    border-radius: 8px;
+    padding: 3px;
+    margin-bottom: 10px;
+
+    &--readonly {
+      pointer-events: none;
     }
   }
 
-  .flow-card-head {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    margin-bottom: 8px;
-  }
-
-  .flow-level {
+  .type-btn {
+    flex: 1;
+    text-align: center;
+    padding: 8px 0;
     font-size: 14px;
-    font-weight: 600;
-    color: #303133;
+    color: $text-secondary;
+    border-radius: 6px;
+
+    &.active {
+      background: #fff;
+      color: $primary;
+      font-weight: 500;
+    }
   }
 
-  .flow-type {
-    font-size: 13px;
-    color: #2979ff;
-  }
-
-  .approver-tags {
+  .approver-list {
     display: flex;
     flex-wrap: wrap;
     gap: 8px;
+    align-items: center;
   }
 
-  .approver-tag {
-    padding: 4px 10px;
-    font-size: 13px;
-    color: #303133;
+  .approver-chip {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 6px 12px 6px 6px;
     background: #fff;
     border: 1px solid #dce8f8;
-    border-radius: 16px;
+    border-radius: 24px;
+    box-shadow: 0 2px 6px rgba(41, 121, 255, 0.06);
+  }
+
+  .approver-avatar {
+    width: 26px;
+    height: 26px;
+    border-radius: 50%;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: #fff;
+    font-size: 12px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .approver-name {
+    font-size: 13px;
+    color: $text;
+    max-width: 120px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .flow-connector {
+    display: flex;
+    justify-content: center;
+    padding: 4px 0;
+  }
+
+  .flow-connector-line {
+    width: 2px;
+    height: 14px;
+    background: #d0dff0;
   }
 
   .empty-hint {
     padding: 12px 16px 16px;
     font-size: 13px;
-    color: #909399;
+    color: $text-muted;
 
     &.inline {
       padding: 0;
diff --git a/src/pages/oa/ApproveManage/approve-list/index.vue b/src/pages/oa/ApproveManage/approve-list/index.vue
index db97060..fd5e142 100644
--- a/src/pages/oa/ApproveManage/approve-list/index.vue
+++ b/src/pages/oa/ApproveManage/approve-list/index.vue
@@ -73,20 +73,22 @@
             </view>
             <view class="detail-row">
               <text class="detail-label">鐢宠鏃堕棿</text>
-              <text class="detail-value">{{ item.applyTime || "-" }}</text>
+              <text class="detail-value">{{ formatDateTime(item.applyTime) }}</text>
             </view>
             <view v-if="item.finishTime"
                   class="detail-row">
               <text class="detail-label">瀹屾垚鏃堕棿</text>
-              <text class="detail-value">{{ item.finishTime }}</text>
+              <text class="detail-value">{{ formatDateTime(item.finishTime) }}</text>
             </view>
           </view>
-          <view v-if="canEdit(item) || item.isApprove"
+          <view v-if="canModify(item) || item.isApprove"
                 class="action-buttons">
-            <up-button v-if="canEdit(item)"
+            <up-button v-if="canModify(item)"
                        class="action-btn"
                        size="small"
-                       @click.stop="goEdit(item)">
+                       type="warning"
+                       plain
+                       @click.stop="goModify(item)">
               缂栬緫
             </up-button>
             <up-button v-if="item.isApprove"
@@ -123,6 +125,7 @@
   import { listApprovalInstancePage } from "@/api/oa/approvalInstance.js";
   import { OA_NAV } from "@/config/oaPaths.js";
   import useUserStore from "@/store/modules/user";
+  import { parseTime } from "@/utils/ruoyi";
 
   const EDIT_STORAGE_KEY = "oa_approve_instance_edit_row";
   const userStore = useUserStore();
@@ -160,6 +163,27 @@
     if (level == null || level === "") return "-";
     return `绗� ${level} 绾;
   };
+
+  const formatDateTime = val => {
+    if (!val) return "-";
+    return parseTime(val, "{y}-{m}-{d} {h}:{i}:{s}") || String(val);
+  };
+
+  /** 鏄惁鏈汉鍙戣捣鐨勫鎵癸紙鍏煎鍒楄〃鏈繑鍥� applicantId锛� */
+  const isOwnApplication = item => {
+    const uid = userStore.id;
+    if (item?.applicantId != null && uid != null && uid !== "") {
+      return String(item.applicantId) === String(uid);
+    }
+    const loginName = userStore.nickName || userStore.name;
+    if (loginName && item?.applicantName) {
+      return String(item.applicantName).trim() === String(loginName).trim();
+    }
+    return false;
+  };
+
+  /** 浠呫�岃繘琛屼腑銆嶄笖鏈汉鍙戣捣鏃跺彲缂栬緫锛堝凡閫氳繃/宸查┏鍥炰笉鏄剧ず缂栬緫锛� */
+  const canModify = item => item?.status === "PENDING" && isOwnApplication(item);
 
   const currentApproverName = item => {
     const tasks = item?.tasks;
@@ -243,11 +267,11 @@
     uni.navigateTo({ url: OA_NAV.approveListTemplateSelect });
   };
 
-  const canEdit = item =>
-    item?.status === "PENDING" &&
-    String(item.applicantId) === String(userStore.id);
-
-  const goEdit = item => {
+  const goModify = item => {
+    if (!canModify(item)) {
+      uni.showToast({ title: "浠呰繘琛屼腑鐨勬湰浜虹敵璇峰彲缂栬緫", icon: "none" });
+      return;
+    }
     if (!item?.id) return;
     uni.setStorageSync(EDIT_STORAGE_KEY, item);
     uni.navigateTo({
diff --git a/src/pages/oa/ApproveManage/approve-template/detail.vue b/src/pages/oa/ApproveManage/approve-template/detail.vue
index 75dba50..40a8958 100644
--- a/src/pages/oa/ApproveManage/approve-template/detail.vue
+++ b/src/pages/oa/ApproveManage/approve-template/detail.vue
@@ -125,6 +125,7 @@
   import PageHeader from "@/components/PageHeader.vue";
   import FooterButtons from "@/components/FooterButtons.vue";
   import { getApprovalTemplateDetail } from "@/api/oa/approvalTemplate.js";
+  import { getFieldEditorTypeLabel } from "../../_utils/approvalFormField.js";
   import {
     buildTypeLabelMap,
     fetchApprovalTemplateTypes,
@@ -133,13 +134,6 @@
 
   const EDIT_STORAGE_KEY = "oa_approve_template_edit_row";
   const LEVEL_TEXT = ["", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�", "涓�", "鍏�", "涔�", "鍗�"];
-
-  const FIELD_TYPE_MAP = {
-    text: "鍗曡鏂囨湰",
-    textarea: "澶氳鏂囨湰",
-    number: "鏁板瓧",
-    date: "鏃ユ湡",
-  };
 
   const templateId = ref("");
   const detail = ref(null);
@@ -179,7 +173,7 @@
     return "";
   };
 
-  const fieldTypeLabel = type => FIELD_TYPE_MAP[type] || type || "-";
+  const fieldTypeLabel = type => getFieldEditorTypeLabel(type);
 
   const approveTypeText = type => (type === "OR" ? "鎴栫" : "浼氱");
 
diff --git a/src/pages/oa/ApproveManage/approve-template/edit.vue b/src/pages/oa/ApproveManage/approve-template/edit.vue
index aba2103..53a2d2e 100644
--- a/src/pages/oa/ApproveManage/approve-template/edit.vue
+++ b/src/pages/oa/ApproveManage/approve-template/edit.vue
@@ -26,17 +26,21 @@
                       class="name-input-inline"
                       placeholder="璇疯緭鍏ユā鏉垮悕绉�"
                       maxlength="50"
-                      clearable />
+                      :disabled="isSystemTemplate"
+                      :clearable="!isSystemTemplate" />
           </up-form-item>
           <up-form-item label="瀹℃壒绫诲瀷"
                         prop="businessType"
                         required
                         class="form-item-select"
+                        :class="{ 'form-item-select--disabled': isSystemTemplate }"
                         @click="openBusinessTypeSheet">
             <up-input :model-value="businessTypeText"
                       placeholder="璇烽�夋嫨瀹℃壒绫诲瀷"
-                      readonly />
-            <template #right>
+                      readonly
+                      :disabled="isSystemTemplate" />
+            <template v-if="!isSystemTemplate"
+                      #right>
               <up-icon name="arrow-right"
                        @click.stop="openBusinessTypeSheet" />
             </template>
@@ -62,12 +66,16 @@
 
         <view class="section-card">
           <view class="section-head section-head--between">
-            <text class="section-title">濉姤閰嶇疆</text>
+            <view class="section-head-left">
+              <text class="section-title">濉姤椤归厤缃�</text>
+              <text class="section-count">鍏� {{ formConfig.fields.length }} 椤�</text>
+            </view>
             <view class="head-actions">
-              <text class="head-link"
-                    @click="showPresetSheet = true">棰勮</text>
+              <text class="head-link head-link--import"
+                    :class="{ 'head-link--disabled': !canImportTemplate }"
+                    @click="openTemplateImport">浠庡凡鏈夋ā鏉垮鍏�</text>
               <text class="head-link head-link--primary"
-                    @click="openFieldEditor()">娣诲姞</text>
+                    @click="openFieldEditor()">+ 娣诲姞濉姤椤�</text>
             </view>
           </view>
           <view class="section-body">
@@ -83,34 +91,46 @@
                   class="field-list">
               <view v-for="(field, index) in formConfig.fields"
                     :key="field.key"
-                    class="field-item">
+                    class="field-item"
+                    :class="{ 'field-item--locked': isFieldLocked(field) }"
+                    @click="onFieldItemClick(field, index)">
+                <view class="field-order">{{ index + 1 }}</view>
                 <view class="field-main">
                   <view class="field-title-row">
                     <text class="field-name">{{ field.label }}</text>
-                    <text class="type-tag"
-                          :class="fieldTypeTagClass(field.type)">
-                      {{ fieldTypeLabel(field.type) }}
-                    </text>
-                    <text v-if="field.required"
-                          class="req-tag">蹇呭~</text>
+                    <view class="field-tags">
+                      <text class="type-tag"
+                            :class="fieldTypeTagClass(field.type)">
+                        {{ fieldTypeLabel(field.type) }}
+                      </text>
+                      <text v-if="field.required"
+                            class="req-tag">蹇呭~</text>
+                    </view>
                   </view>
+                  <text class="field-key">{{ field.key }}</text>
                   <text v-if="field.defaultValue"
-                        class="field-default">榛樿锛歿{ field.defaultValue }}</text>
+                        class="field-default">
+                    榛樿锛歿{ formatFieldDefaultPreview(field) }}
+                  </text>
                 </view>
-                <view class="field-actions">
+                <view v-if="!isFieldLocked(field)"
+                      class="field-actions"
+                      @click.stop>
                   <view class="icon-btn icon-btn--edit"
-                        @click="openFieldEditor(field, index)">
+                        @click.stop="openFieldEditor(field, index)">
                     <up-icon name="edit-pen"
                              size="16"
                              color="#2979ff" />
                   </view>
                   <view class="icon-btn icon-btn--del"
-                        @click="removeField(index)">
+                        @click.stop="removeField(index)">
                     <up-icon name="trash"
                              size="16"
                              color="#f56c6c" />
                   </view>
                 </view>
+                <view v-else
+                      class="field-lock-tag">鍐呯疆</view>
               </view>
             </view>
             <view v-else
@@ -196,11 +216,11 @@
                    @cancel="goBack"
                    @confirm="handleSubmit" />
 
-    <up-action-sheet :show="showPresetSheet"
-                     title="浠庨璁惧鍏�"
-                     :actions="presetActions"
-                     @select="onSelectPreset"
-                     @close="showPresetSheet = false" />
+    <up-action-sheet :show="showTemplateImportSheet"
+                     title="浠庡凡鏈夋ā鏉垮鍏�"
+                     :actions="templateImportActions"
+                     @select="onSelectImportTemplate"
+                     @close="showTemplateImportSheet = false" />
 
     <up-popup :show="showFieldEditor"
               mode="bottom"
@@ -208,71 +228,255 @@
               @close="closeFieldEditor">
       <view class="field-editor">
         <view class="sheet-handle" />
-        <text class="editor-title">{{ editingFieldIndex >= 0 ? "缂栬緫濉姤椤�" : "娣诲姞濉姤椤�" }}</text>
-        <view class="editor-form">
-          <view class="editor-row">
-            <text class="editor-label required">瀛楁鍚嶇О</text>
-            <up-input v-model="fieldDraft.label"
-                      placeholder="璇疯緭鍏�"
-                      clearable />
-          </view>
-          <view class="editor-row editor-row--block">
-            <text class="editor-label required">瀛楁绫诲瀷</text>
-            <view class="type-chip-grid">
-              <view v-for="opt in FIELD_TYPE_OPTIONS"
-                    :key="opt.value"
-                    class="type-chip"
-                    :class="{ active: fieldDraft.type === opt.value }"
-                    @click="selectFieldType(opt.value)">
-                {{ opt.name }}
+        <view class="editor-header">
+          <text class="editor-title">{{ editingFieldIndex >= 0 ? "缂栬緫濉姤椤�" : "娣诲姞濉姤椤�" }}</text>
+          <text class="editor-subtitle">閰嶇疆瀛楁灞炴�с�佹牎楠屼笌榛樿鍊�</text>
+        </view>
+        <scroll-view class="editor-scroll"
+                     scroll-y
+                     :show-scrollbar="false">
+          <view class="editor-form">
+            <view class="editor-section-card">
+              <view class="editor-section-head">
+                <text class="editor-section-title">鍩虹淇℃伅</text>
+              </view>
+              <view class="editor-cell">
+                <text class="editor-label required">鏄剧ず鍚嶇О</text>
+                <view class="editor-input-box">
+                  <up-input v-model="fieldDraft.label"
+                            placeholder="濡傦細鎶ラ攢璇存槑"
+                            border="none"
+                            clearable />
+                </view>
+              </view>
+              <view class="editor-cell">
+                <text class="editor-label required">瀛楁鏍囪瘑</text>
+                <view class="editor-input-box">
+                  <up-input v-model="fieldDraft.key"
+                            placeholder="濡傦細summary"
+                            border="none"
+                            clearable />
+                </view>
+              </view>
+              <view class="editor-cell editor-cell--tap"
+                    @click="openFieldTypePicker">
+                <text class="editor-label required">鎺т欢绫诲瀷</text>
+                <view class="picker-value-row">
+                  <text class="picker-value"
+                        :class="{ 'picker-value--placeholder': !fieldDraft.type }">
+                    {{ fieldDraftTypeText || "璇烽�夋嫨" }}
+                  </text>
+                  <up-icon name="arrow-right"
+                           size="14"
+                           color="#b0b8c4" />
+                </view>
+              </view>
+            </view>
+
+            <view class="editor-section-card">
+              <view class="editor-section-head">
+                <text class="editor-section-title">鏍¢獙涓庢牸寮�</text>
+              </view>
+              <view class="editor-cell editor-cell--switch">
+                <view class="switch-label-wrap">
+                  <text class="editor-label">鏄惁蹇呭~</text>
+                  <text class="switch-hint">鎻愪氦瀹℃壒鏃堕』濉啓璇ラ」</text>
+                </view>
+                <up-switch v-model="fieldDraft.required"
+                           active-color="#2979ff" />
+              </view>
+            </view>
+
+            <view v-if="isSelectDraft"
+                  class="editor-section-card">
+              <view class="editor-section-head">
+                <text class="editor-section-title">涓嬫媺閫夐」</text>
+              </view>
+              <view class="editor-cell editor-cell--tap"
+                    @click="openOptionSourcePicker">
+                <text class="editor-label">閫夐」鏉ユ簮</text>
+                <view class="picker-value-row">
+                  <text class="picker-value">{{ fieldDraftOptionSourceText }}</text>
+                  <up-icon name="arrow-right"
+                           size="14"
+                           color="#b0b8c4" />
+                </view>
+              </view>
+              <view v-if="fieldDraft.optionSource === 'manual'"
+                    class="manual-options">
+                <text class="manual-options-title">鎵嬪姩閫夐」</text>
+                <view class="manual-options-table">
+                  <view class="option-table-head">
+                    <text class="option-col option-col--idx" />
+                    <text class="option-col option-col--label">鏄剧ず鏂囨湰</text>
+                    <text class="option-col option-col--value">閫夐」鍊�</text>
+                    <text class="option-col option-col--action" />
+                  </view>
+                  <view v-for="(opt, optIndex) in fieldDraft.options"
+                        :key="optIndex"
+                        class="option-card">
+                    <text class="option-idx">{{ optIndex + 1 }}</text>
+                    <view class="option-input-wrap">
+                      <up-input v-model="opt.label"
+                                placeholder="濡傦細宸ヤ綔鏃ュ姞鐝�"
+                                border="none"
+                                clearable />
+                    </view>
+                    <view class="option-input-wrap option-input-wrap--value">
+                      <up-input v-model="opt.value"
+                                placeholder="濡傦細0"
+                                border="none"
+                                clearable />
+                    </view>
+                    <view class="option-del"
+                          hover-class="option-del--active"
+                          @click.stop="removeDraftOption(optIndex)">
+                      <up-icon name="trash"
+                               size="16"
+                               color="#f56c6c" />
+                    </view>
+                  </view>
+                </view>
+                <view class="add-option-btn"
+                      hover-class="add-option-btn--active"
+                      @click="addDraftOption">
+                  <up-icon name="plus-circle"
+                           size="16"
+                           color="#2979ff" />
+                  <text>娣诲姞閫夐」</text>
+                </view>
+              </view>
+              <view v-else
+                    class="option-source-tip">
+                <up-icon name="info-circle"
+                         size="14"
+                         color="#909399" />
+                <text>鍙戣捣瀹℃壒鏃跺皢鑷姩鍔犺浇{{ fieldDraftOptionSourceText }}</text>
+              </view>
+            </view>
+
+            <view class="editor-section-card">
+              <view class="editor-section-head">
+                <text class="editor-section-title">榛樿鍊�</text>
+              </view>
+              <text class="default-hint">
+                閫夋嫨璇ユā鏉挎彁浜ゅ鎵规椂鑷姩棰勫~锛岀敤鎴蜂粛鍙慨鏀�
+              </text>
+              <view class="editor-cell editor-cell--value">
+                <up-textarea v-if="fieldDraft.type === 'textarea'"
+                             v-model="fieldDraft.defaultValue"
+                             placeholder="閫夊~"
+                             maxlength="500"
+                             border="surround"
+                             height="72" />
+                <view v-else-if="fieldDraft.type === 'date'"
+                      class="picker-value-row picker-value-row--tap"
+                      @click="openDefaultDatePicker">
+                  <text class="picker-value"
+                        :class="{ 'picker-value--placeholder': !fieldDraft.defaultValue }">
+                    {{ fieldDraft.defaultValue || "閫夋嫨鏃ユ湡" }}
+                  </text>
+                  <up-icon name="calendar"
+                           size="18"
+                           color="#909399" />
+                </view>
+                <view v-else-if="isDatetimerangeDraft"
+                      class="daterange-default-wrap">
+                  <view class="daterange-default-item"
+                        @click="openDefaultRangePicker('start')">
+                    <text class="daterange-default-label">寮�濮嬫椂闂�</text>
+                    <view class="picker-value-row picker-value-row--tap">
+                      <text class="picker-value"
+                            :class="{ 'picker-value--placeholder': !defaultRangeStart }">
+                        {{ defaultRangeStart || "閫夋嫨寮�濮嬫椂闂�" }}
+                      </text>
+                      <up-icon name="calendar"
+                               size="18"
+                               color="#909399" />
+                    </view>
+                  </view>
+                  <view class="daterange-default-item"
+                        @click="openDefaultRangePicker('end')">
+                    <text class="daterange-default-label">缁撴潫鏃堕棿</text>
+                    <view class="picker-value-row picker-value-row--tap">
+                      <text class="picker-value"
+                            :class="{ 'picker-value--placeholder': !defaultRangeEnd }">
+                        {{ defaultRangeEnd || "閫夋嫨缁撴潫鏃堕棿" }}
+                      </text>
+                      <up-icon name="calendar"
+                               size="18"
+                               color="#909399" />
+                    </view>
+                  </view>
+                </view>
+                <view v-else-if="isSelectDraft"
+                      class="picker-value-row picker-value-row--tap"
+                      @click="openDefaultSelectSheet">
+                  <text class="picker-value"
+                        :class="{ 'picker-value--placeholder': !fieldDraft.defaultValue }">
+                    {{ defaultSelectDisplayText || "閫夊~" }}
+                  </text>
+                  <up-icon name="arrow-right"
+                           size="14"
+                           color="#b0b8c4" />
+                </view>
+                <view v-else
+                      class="editor-input-box">
+                  <up-input v-model="fieldDraft.defaultValue"
+                            :type="fieldDraft.type === 'number' ? 'digit' : 'text'"
+                            placeholder="閫夊~"
+                            border="none"
+                            clearable />
+                </view>
               </view>
             </view>
           </view>
-          <view class="editor-row editor-row--block">
-            <text class="editor-label">榛樿鍊�</text>
-            <up-textarea v-if="fieldDraft.type === 'textarea'"
-                         v-model="fieldDraft.defaultValue"
-                         placeholder="閫夊~"
-                         maxlength="500"
-                         height="72" />
-            <view v-else-if="fieldDraft.type === 'date'"
-                  class="default-date-row"
-                  @click="showDefaultDatePicker = true">
-              <up-input :model-value="fieldDraft.defaultValue"
-                        placeholder="閫夋嫨鏃ユ湡"
-                        readonly />
-              <up-icon name="calendar"
-                       size="18"
-                       color="#909399" />
-            </view>
-            <up-input v-else
-                      v-model="fieldDraft.defaultValue"
-                      :type="fieldDraft.type === 'number' ? 'digit' : 'text'"
-                      placeholder="閫夊~"
-                      clearable />
-          </view>
-          <view class="editor-row editor-row--switch">
-            <text class="editor-label">鏄惁蹇呭~</text>
-            <up-switch v-model="fieldDraft.required" />
-          </view>
-        </view>
+        </scroll-view>
         <view class="editor-footer">
           <view class="editor-btn editor-btn--cancel"
                 @click="closeFieldEditor">鍙栨秷</view>
           <view class="editor-btn editor-btn--confirm"
                 @click="confirmFieldEditor">纭畾</view>
         </view>
+
+        <view v-if="inlinePickerShow"
+              class="editor-picker-layer">
+          <view class="editor-picker-mask"
+                @click="closeInlinePicker" />
+          <view class="editor-picker-panel">
+            <view class="editor-picker-head">
+              <text class="editor-picker-cancel"
+                    @click="closeInlinePicker">鍙栨秷</text>
+              <text class="editor-picker-title">{{ inlinePickerTitle }}</text>
+              <text class="editor-picker-placeholder" />
+            </view>
+            <scroll-view class="editor-picker-scroll"
+                         scroll-y>
+              <view v-for="(item, pickerIndex) in inlinePickerOptions"
+                    :key="`${inlinePickerMode}-${pickerIndex}-${item.value}`"
+                    class="editor-picker-item"
+                    :class="{ 'editor-picker-item--active': isInlinePickerItemActive(item) }"
+                    @click="onInlinePickerSelect(item)">
+                <text>{{ item.name }}</text>
+                <up-icon v-if="isInlinePickerItemActive(item)"
+                         name="checkmark"
+                         size="18"
+                         color="#2979ff" />
+              </view>
+            </scroll-view>
+          </view>
+        </view>
       </view>
     </up-popup>
 
     <up-popup :show="showDefaultDatePicker"
               mode="bottom"
-              @close="showDefaultDatePicker = false">
+              @close="closeDefaultDatePicker">
       <up-datetime-picker :show="true"
                           v-model="defaultDateTs"
-                          mode="date"
-                          @confirm="onDefaultDateConfirm"
-                          @cancel="showDefaultDatePicker = false" />
+                          :mode="defaultDatePickerMode"
+                          @confirm="onDefaultDatePickerConfirm"
+                          @cancel="closeDefaultDatePicker" />
     </up-popup>
 
     <up-popup :show="showUserPicker"
@@ -326,67 +530,65 @@
   import FooterButtons from "@/components/FooterButtons.vue";
   import {
     addApprovalTemplate,
+    getApprovalTemplateDetail,
+    listApprovalTemplatePage,
     updateApprovalTemplate,
   } from "@/api/oa/approvalTemplate.js";
+  import { getDept } from "@/api/collaborativeApproval/approvalProcess.js";
   import { userListNoPageByTenantId } from "@/api/system/user";
   import { formatDateToYMD } from "@/utils/ruoyi";
-  import { fetchApprovalTemplateTypes } from "../../_utils/approvalTemplateType.js";
+  import {
+    buildFieldConfigPayload,
+    createEmptyFieldOption,
+    parseApprovalFormConfig,
+    FIELD_EDITOR_TYPE_OPTIONS,
+    FIELD_OPTION_SOURCE_OPTIONS,
+    getFieldEditorTypeLabel,
+    getFieldOptionLabel,
+    getFieldOptionSource,
+    getFieldOptionSourceLabel,
+    isDatetimerangeField,
+    isSelectField,
+    formatDatetimerangeDisplay,
+    formatFieldDateValue,
+    joinDatetimerangeValue,
+    parseDatetimerangeValue,
+    parseFieldDateToTs,
+    resolveFieldOptions,
+  } from "../../_utils/approvalFormField.js";
+  import {
+    fetchApprovalTemplateTypes,
+    isSystemApprovalTemplate,
+  } from "../../_utils/approvalTemplateType.js";
 
   const EDIT_STORAGE_KEY = "oa_approve_template_edit_row";
 
   const LEVEL_TEXT = ["", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�", "涓�", "鍏�", "涔�", "鍗�"];
-
-  const FORM_PRESETS = [
-    {
-      name: "閫氱敤鎶ラ攢",
-      prompt: "璇峰~鍐欐姤閿�浜嬬敱銆侀噾棰濈瓑",
-      fields: [
-        { key: "reason", label: "鎶ラ攢浜嬬敱", type: "textarea", required: true },
-        { key: "amount", label: "鎶ラ攢閲戦(鍏�)", type: "number", required: true },
-        { key: "applyDate", label: "鐢宠鏃ユ湡", type: "date", required: true },
-      ],
-    },
-    {
-      name: "璇峰亣鐢宠",
-      prompt: "璇峰~鍐欒鍋囩被鍨嬨�佽捣姝㈡椂闂寸瓑",
-      fields: [
-        { key: "leaveType", label: "璇峰亣绫诲瀷", type: "text", required: true },
-        { key: "startTime", label: "寮�濮嬫椂闂�", type: "date", required: true },
-        { key: "endTime", label: "缁撴潫鏃堕棿", type: "date", required: true },
-        { key: "reason", label: "璇峰亣浜嬬敱", type: "textarea", required: true },
-      ],
-    },
-    {
-      name: "閲囪喘鐢宠",
-      prompt: "璇峰~鍐欓噰璐簨鐢便�侀浼伴噾棰濈瓑",
-      fields: [
-        { key: "title", label: "閲囪喘浜嬬敱", type: "textarea", required: true },
-        { key: "amount", label: "棰勪及閲戦(鍏�)", type: "number", required: true },
-      ],
-    },
-  ];
-
-  const FIELD_TYPE_OPTIONS = [
-    { name: "鍗曡鏂囨湰", value: "text" },
-    { name: "澶氳鏂囨湰", value: "textarea" },
-    { name: "鏁板瓧", value: "number" },
-    { name: "鏃ユ湡", value: "date" },
-  ];
 
   const formRef = ref();
   const submitting = ref(false);
   const userList = ref([]);
   const templateId = ref(null);
 
-  const showPresetSheet = ref(false);
+  const showTemplateImportSheet = ref(false);
+  const importTemplateList = ref([]);
   const showFieldEditor = ref(false);
+  const inlinePickerShow = ref(false);
+  const inlinePickerTitle = ref("");
+  const inlinePickerOptions = ref([]);
+  const inlinePickerMode = ref("");
   const showUserPicker = ref(false);
   const showDefaultDatePicker = ref(false);
+  const defaultDatePickerMode = ref("date");
+  const defaultRangePickerPart = ref("start");
   const defaultDateTs = ref(Date.now());
+  const deptList = ref([]);
 
   const editingFieldIndex = ref(-1);
   const editingNodeIndex = ref(-1);
   const pickerSelectedIds = ref([]);
+  /** 绯荤粺妯℃澘鍔犺浇鏃堕攣瀹氱殑濉姤椤� key锛屼笉鍙紪杈�/鍒犻櫎 */
+  const lockedFieldKeys = ref(new Set());
 
   const form = reactive({
     templateName: "",
@@ -403,9 +605,12 @@
 
   const fieldDraft = reactive({
     label: "",
+    key: "",
     type: "text",
     defaultValue: "",
     required: true,
+    optionSource: "manual",
+    options: [createEmptyFieldOption()],
   });
 
   let nodeKeySeed = 1;
@@ -451,10 +656,57 @@
     return matched?.name || "";
   });
 
-  const presetActions = FORM_PRESETS.map(item => ({
-    name: item.name,
-    value: item.name,
-  }));
+  const canImportTemplate = computed(() => !isSystemTemplate.value);
+
+  const templateImportActions = computed(() =>
+    importTemplateList.value.map(item => {
+      const typeTag = isSystemApprovalTemplate(item) ? "绯荤粺" : "鑷畾涔�";
+      return {
+        name: `銆�${typeTag}銆�${item.templateName || `妯℃澘${item.id}`}`,
+        value: String(item.id),
+      };
+    })
+  );
+
+  const isSelectDraft = computed(() => isSelectField(fieldDraft));
+
+  const isDatetimerangeDraft = computed(() => isDatetimerangeField(fieldDraft));
+
+  const defaultRangeParts = computed(() =>
+    parseDatetimerangeValue(fieldDraft.defaultValue)
+  );
+
+  const defaultRangeStart = computed(() => defaultRangeParts.value.start);
+
+  const defaultRangeEnd = computed(() => defaultRangeParts.value.end);
+
+  const fieldDraftTypeText = computed(() => getFieldEditorTypeLabel(fieldDraft.type));
+
+  const fieldDraftOptionSourceText = computed(() =>
+    getFieldOptionSourceLabel(fieldDraft.optionSource)
+  );
+
+  const defaultSelectActions = computed(() => {
+    const options = resolveFieldOptions(fieldDraft, {
+      users: userList.value,
+      depts: deptList.value,
+    });
+    return [
+      { name: "涓嶈缃�", value: "" },
+      ...options.map(opt => ({
+        name: opt.label,
+        value: opt.value,
+      })),
+    ];
+  });
+
+  const defaultSelectDisplayText = computed(() => {
+    if (!fieldDraft.defaultValue) return "";
+    return (
+      getFieldOptionLabel(fieldDraft, fieldDraft.defaultValue) ||
+      String(fieldDraft.defaultValue)
+    );
+  });
 
   const enabledBool = computed({
     get: () => form.enabled === "1",
@@ -465,22 +717,14 @@
 
   const isEditMode = computed(() => templateId.value != null && templateId.value !== "");
 
+  const isSystemTemplate = computed(() => isSystemApprovalTemplate(form));
+
+  const isFieldLocked = field =>
+    isSystemTemplate.value && lockedFieldKeys.value.has(field?.key);
+
   const pageTitle = computed(() =>
     isEditMode.value ? "缂栬緫瀹℃壒妯℃澘" : "鏂板缓瀹℃壒妯℃澘"
   );
-
-  const parseFormConfig = raw => {
-    if (!raw) return { prompt: "", fields: [] };
-    try {
-      const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
-      return {
-        prompt: obj?.prompt || "",
-        fields: Array.isArray(obj?.fields) ? obj.fields.map(f => ({ ...f })) : [],
-      };
-    } catch {
-      return { prompt: "", fields: [] };
-    }
-  };
 
   const mapNodesFromRow = nodes => {
     if (!Array.isArray(nodes) || !nodes.length) {
@@ -515,9 +759,12 @@
     form.enabled = String(row.enabled ?? "1");
     form.description = row.description || "";
 
-    const config = parseFormConfig(row.formConfig);
+    const config = parseApprovalFormConfig(row.formConfig);
     formConfig.prompt = config.prompt;
     formConfig.fields = config.fields;
+    lockedFieldKeys.value = isSystemApprovalTemplate(row)
+      ? new Set(config.fields.map(f => f.key).filter(Boolean))
+      : new Set();
     flowNodes.value = mapNodesFromRow(row.nodes);
   };
 
@@ -530,8 +777,14 @@
 
   const levelLabel = n => LEVEL_TEXT[n] || String(n);
 
-  const fieldTypeLabel = type =>
-    FIELD_TYPE_OPTIONS.find(item => item.value === type)?.name || type;
+  const fieldTypeLabel = type => getFieldEditorTypeLabel(type);
+
+  const formatFieldDefaultPreview = field => {
+    if (isDatetimerangeField(field)) {
+      return formatDatetimerangeDisplay(field.defaultValue) || field.defaultValue;
+    }
+    return field.defaultValue;
+  };
 
   const fieldTypeTagClass = type => {
     const map = {
@@ -539,6 +792,8 @@
       textarea: "type-tag--area",
       number: "type-tag--num",
       date: "type-tag--date",
+      datetimerange: "type-tag--date",
+      select: "type-tag--select",
     };
     return map[type] || "type-tag--text";
   };
@@ -548,6 +803,7 @@
   };
 
   const openBusinessTypeSheet = () => {
+    if (isSystemTemplate.value) return;
     if (!businessTypeOptions.value.length) {
       uni.showToast({ title: "瀹℃壒绫诲瀷鍔犺浇涓�", icon: "none" });
       return;
@@ -561,51 +817,316 @@
     formRef.value?.validateField?.("businessType");
   };
 
-  const onSelectPreset = action => {
-    const preset = FORM_PRESETS.find(item => item.name === action.value);
-    if (!preset) return;
-    formConfig.prompt = preset.prompt;
-    formConfig.fields = preset.fields.map(field => ({ ...field }));
-    showPresetSheet.value = false;
-    uni.showToast({ title: "宸插鍏ラ璁�", icon: "success" });
+  const applyImportedFormConfig = (config, sourceName = "") => {
+    const parsed = {
+      prompt: config?.prompt || "",
+      fields: (config?.fields || []).map(field => ({ ...field })),
+    };
+    formConfig.prompt = parsed.prompt;
+    formConfig.fields = parsed.fields;
+    const tip = sourceName ? `宸插鍏ャ��${sourceName}銆峘 : "宸插鍏ュ~鎶ラ厤缃�";
+    uni.showToast({ title: tip, icon: "success" });
   };
 
-  const selectFieldType = type => {
-    if (fieldDraft.type === type) return;
-    fieldDraft.type = type;
+  const doImportFormConfig = (config, sourceName) => {
+    const hasExisting =
+      !!formConfig.prompt?.trim() || formConfig.fields.length > 0;
+    if (!hasExisting) {
+      applyImportedFormConfig(config, sourceName);
+      return;
+    }
+    uni.showModal({
+      title: "瀵煎叆纭",
+      content: `灏嗕娇鐢ㄣ��${sourceName}銆嶇殑濉姤閰嶇疆瑕嗙洊褰撳墠鍐呭锛屾槸鍚︾户缁紵`,
+      success: res => {
+        if (res.confirm) {
+          applyImportedFormConfig(config, sourceName);
+        }
+      },
+    });
+  };
+
+  const applyTemplateImport = templateIdValue => {
+    const row = importTemplateList.value.find(
+      item => String(item.id) === String(templateIdValue)
+    );
+    const sourceName = row?.templateName || "鎵�閫夋ā鏉�";
+    const applyFromDetail = detail => {
+      const config = parseApprovalFormConfig(detail?.formConfig);
+      if (!config.fields.length && !config.prompt) {
+        uni.showToast({ title: "璇ユā鏉挎棤濉姤閰嶇疆", icon: "none" });
+        return;
+      }
+      doImportFormConfig(config, sourceName);
+    };
+
+    if (row?.formConfig) {
+      applyFromDetail(row);
+      return;
+    }
+
+    uni.showLoading({ title: "鍔犺浇閰嶇疆...", mask: true });
+    getApprovalTemplateDetail(templateIdValue)
+      .then(res => applyFromDetail(res?.data))
+      .catch(() => {
+        uni.showToast({ title: "鑾峰彇妯℃澘閰嶇疆澶辫触", icon: "none" });
+      })
+      .finally(() => {
+        uni.hideLoading();
+      });
+  };
+
+  const openTemplateImport = () => {
+    if (!canImportTemplate.value) {
+      uni.showToast({ title: "绯荤粺鍐呯疆妯℃澘涓嶅彲瀵煎叆", icon: "none" });
+      return;
+    }
+    uni.showLoading({ title: "鍔犺浇涓�...", mask: true });
+    listApprovalTemplatePage({
+      page: { current: 1, size: 200 },
+      approvalTemplateDto: {},
+    })
+      .then(res => {
+        const records = res?.data?.records || [];
+        importTemplateList.value = records.filter(
+          item =>
+            item?.id != null && String(item.id) !== String(templateId.value)
+        );
+        if (!importTemplateList.value.length) {
+          uni.showToast({ title: "鏆傛棤鍙鍏ョ殑妯℃澘", icon: "none" });
+          return;
+        }
+        showTemplateImportSheet.value = true;
+      })
+      .catch(() => {
+        uni.showToast({ title: "鍔犺浇妯℃澘鍒楄〃澶辫触", icon: "none" });
+      })
+      .finally(() => {
+        uni.hideLoading();
+      });
+  };
+
+  const onSelectImportTemplate = action => {
+    showTemplateImportSheet.value = false;
+    const value = String(action?.value ?? "");
+    if (!value) return;
+    applyTemplateImport(value);
+  };
+
+  const resetFieldDraft = () => {
+    fieldDraft.label = "";
+    fieldDraft.key = "";
+    fieldDraft.type = "text";
     fieldDraft.defaultValue = "";
+    fieldDraft.required = true;
+    fieldDraft.optionSource = "manual";
+    fieldDraft.options = [createEmptyFieldOption()];
   };
 
-  const onDefaultDateConfirm = e => {
-    fieldDraft.defaultValue = formatDateToYMD(e.value);
+  const resolveActionValue = (action, options) => {
+    if (action?.value !== undefined && action?.value !== null) {
+      return action.value;
+    }
+    const name = action?.name;
+    if (name == null) return undefined;
+    return options.find(opt => opt.name === name)?.value;
+  };
+
+  const onSelectFieldType = action => {
+    const nextType = resolveActionValue(action, FIELD_EDITOR_TYPE_OPTIONS);
+    if (!nextType || fieldDraft.type === nextType) return;
+    fieldDraft.type = nextType;
+    fieldDraft.defaultValue = "";
+    if (!isSelectField(fieldDraft)) {
+      fieldDraft.optionSource = "manual";
+      fieldDraft.options = [createEmptyFieldOption()];
+    } else if (!fieldDraft.options?.length) {
+      fieldDraft.options = [createEmptyFieldOption()];
+    }
+  };
+
+  const openInlinePicker = (title, options, mode) => {
+    inlinePickerTitle.value = title;
+    inlinePickerOptions.value = options;
+    inlinePickerMode.value = mode;
+    inlinePickerShow.value = true;
+  };
+
+  const closeInlinePicker = () => {
+    inlinePickerShow.value = false;
+    inlinePickerMode.value = "";
+    inlinePickerOptions.value = [];
+  };
+
+  const isInlinePickerItemActive = item => {
+    if (inlinePickerMode.value === "fieldType") {
+      return String(fieldDraft.type) === String(item.value);
+    }
+    if (inlinePickerMode.value === "optionSource") {
+      return String(fieldDraft.optionSource) === String(item.value);
+    }
+    if (inlinePickerMode.value === "defaultValue") {
+      const val = fieldDraft.defaultValue;
+      if (val === "" || val === undefined || val === null) {
+        return item.value === "" || item.value === undefined || item.value === null;
+      }
+      return String(val) === String(item.value);
+    }
+    return false;
+  };
+
+  const onInlinePickerSelect = item => {
+    if (inlinePickerMode.value === "fieldType") {
+      onSelectFieldType(item);
+    } else if (inlinePickerMode.value === "optionSource") {
+      onSelectOptionSource(item);
+    } else if (inlinePickerMode.value === "defaultValue") {
+      onSelectDefaultOption(item);
+    }
+    closeInlinePicker();
+  };
+
+  const openFieldTypePicker = () => {
+    openInlinePicker(
+      "鎺т欢绫诲瀷",
+      FIELD_EDITOR_TYPE_OPTIONS.map(item => ({
+        name: item.name,
+        value: item.value,
+      })),
+      "fieldType"
+    );
+  };
+
+  const onSelectOptionSource = action => {
+    const nextSource = resolveActionValue(action, FIELD_OPTION_SOURCE_OPTIONS);
+    if (!nextSource) return;
+    fieldDraft.optionSource = nextSource;
+    fieldDraft.defaultValue = "";
+    if (nextSource === "manual" && !fieldDraft.options?.length) {
+      fieldDraft.options = [createEmptyFieldOption()];
+    }
+  };
+
+  const openOptionSourcePicker = () => {
+    openInlinePicker(
+      "閫夐」鏉ユ簮",
+      FIELD_OPTION_SOURCE_OPTIONS.map(item => ({
+        name: item.name,
+        value: item.value,
+      })),
+      "optionSource"
+    );
+  };
+
+  const addDraftOption = () => {
+    fieldDraft.options.push(createEmptyFieldOption());
+  };
+
+  const removeDraftOption = index => {
+    if (fieldDraft.options.length <= 1) {
+      fieldDraft.options[0] = createEmptyFieldOption();
+      return;
+    }
+    fieldDraft.options.splice(index, 1);
+  };
+
+  const openDefaultSelectSheet = () => {
+    const options = resolveFieldOptions(fieldDraft, {
+      users: userList.value,
+      depts: deptList.value,
+    });
+    if (!options.length) {
+      uni.showToast({ title: "璇峰厛閰嶇疆涓嬫媺閫夐」", icon: "none" });
+      return;
+    }
+    openInlinePicker("榛樿鍊�", defaultSelectActions.value, "defaultValue");
+  };
+
+  const onSelectDefaultOption = action => {
+    fieldDraft.defaultValue =
+      action.value === undefined || action.value === null
+        ? ""
+        : String(action.value);
+  };
+
+  const closeDefaultDatePicker = () => {
     showDefaultDatePicker.value = false;
+    defaultDatePickerMode.value = "date";
+    defaultRangePickerPart.value = "start";
+  };
+
+  const openDefaultDatePicker = () => {
+    defaultDatePickerMode.value = "date";
+    const parsed = Date.parse(fieldDraft.defaultValue);
+    defaultDateTs.value = Number.isNaN(parsed) ? Date.now() : parsed;
+    showDefaultDatePicker.value = true;
+  };
+
+  const openDefaultRangePicker = part => {
+    defaultDatePickerMode.value = "datetime";
+    defaultRangePickerPart.value = part;
+    const parts = parseDatetimerangeValue(fieldDraft.defaultValue);
+    const val = part === "start" ? parts.start : parts.end;
+    defaultDateTs.value = parseFieldDateToTs(val) ?? Date.now();
+    showDefaultDatePicker.value = true;
+  };
+
+  const onDefaultDatePickerConfirm = e => {
+    const ts = e?.value ?? defaultDateTs.value;
+    if (defaultDatePickerMode.value === "datetime") {
+      const parts = parseDatetimerangeValue(fieldDraft.defaultValue);
+      const formatted = formatFieldDateValue({ type: "datetime" }, ts);
+      fieldDraft.defaultValue = joinDatetimerangeValue(
+        defaultRangePickerPart.value === "start" ? formatted : parts.start,
+        defaultRangePickerPart.value === "end" ? formatted : parts.end
+      );
+    } else {
+      fieldDraft.defaultValue = formatDateToYMD(ts);
+    }
+    closeDefaultDatePicker();
+  };
+
+  const onFieldItemClick = (field, index) => {
+    if (isFieldLocked(field)) return;
+    openFieldEditor(field, index);
   };
 
   const openFieldEditor = (field, index = -1) => {
+    if (field && isFieldLocked(field)) {
+      uni.showToast({ title: "绯荤粺鍐呯疆濉姤椤逛笉鍙慨鏀�", icon: "none" });
+      return;
+    }
     editingFieldIndex.value = index;
     if (field) {
-      fieldDraft.label = field.label;
+      fieldDraft.label = field.label || "";
+      fieldDraft.key = field.key || "";
       fieldDraft.type = field.type || "text";
       fieldDraft.defaultValue = field.defaultValue ?? "";
       fieldDraft.required = !!field.required;
+      fieldDraft.optionSource = getFieldOptionSource(field);
+      fieldDraft.options = normalizeDraftOptions(field);
     } else {
-      fieldDraft.label = "";
-      fieldDraft.type = "text";
-      fieldDraft.defaultValue = "";
-      fieldDraft.required = true;
+      resetFieldDraft();
     }
-    if (fieldDraft.type === "date" && fieldDraft.defaultValue) {
-      const parsed = Date.parse(fieldDraft.defaultValue);
-      defaultDateTs.value = Number.isNaN(parsed) ? Date.now() : parsed;
-    } else {
-      defaultDateTs.value = Date.now();
-    }
+    defaultDateTs.value = Date.now();
     showFieldEditor.value = true;
   };
 
   const closeFieldEditor = () => {
+    closeInlinePicker();
     showFieldEditor.value = false;
     editingFieldIndex.value = -1;
+  };
+
+  const normalizeDraftOptions = field => {
+    const options = field?.options;
+    if (!Array.isArray(options) || !options.length) {
+      return [createEmptyFieldOption()];
+    }
+    return options.map(opt => ({
+      label: opt?.label ?? "",
+      value: opt?.value != null ? String(opt.value) : "",
+    }));
   };
 
   const buildFieldKey = label => {
@@ -622,22 +1143,46 @@
   };
 
   const confirmFieldEditor = () => {
-    if (!fieldDraft.label?.trim()) {
-      uni.showToast({ title: "璇疯緭鍏ュ瓧娈靛悕绉�", icon: "none" });
+    if (
+      editingFieldIndex.value >= 0 &&
+      isFieldLocked(formConfig.fields[editingFieldIndex.value])
+    ) {
+      uni.showToast({ title: "绯荤粺鍐呯疆濉姤椤逛笉鍙慨鏀�", icon: "none" });
       return;
     }
-    const defaultValue = String(fieldDraft.defaultValue ?? "").trim();
+    if (!fieldDraft.label?.trim()) {
+      uni.showToast({ title: "璇疯緭鍏ユ樉绀哄悕绉�", icon: "none" });
+      return;
+    }
     const existingKey =
       editingFieldIndex.value >= 0
         ? formConfig.fields[editingFieldIndex.value]?.key
         : null;
-    const payload = {
-      key: existingKey || buildFieldKey(fieldDraft.label),
-      label: fieldDraft.label.trim(),
-      type: fieldDraft.type,
-      required: !!fieldDraft.required,
-      defaultValue,
-    };
+    const draftKey = fieldDraft.key?.trim() || existingKey || buildFieldKey(fieldDraft.label);
+    if (!draftKey) {
+      uni.showToast({ title: "璇疯緭鍏ュ瓧娈垫爣璇�", icon: "none" });
+      return;
+    }
+    const duplicateKey = formConfig.fields.some(
+      (item, idx) => item.key === draftKey && idx !== editingFieldIndex.value
+    );
+    if (duplicateKey) {
+      uni.showToast({ title: "瀛楁鏍囪瘑宸插瓨鍦�", icon: "none" });
+      return;
+    }
+    if (isSelectField(fieldDraft) && fieldDraft.optionSource === "manual") {
+      const validOptions = (fieldDraft.options || []).filter(
+        opt => opt.label?.trim() && opt.value?.trim()
+      );
+      if (!validOptions.length) {
+        uni.showToast({ title: "璇疯嚦灏戦厤缃竴涓笅鎷夐�夐」", icon: "none" });
+        return;
+      }
+    }
+    const payload = buildFieldConfigPayload(
+      { ...fieldDraft, key: draftKey },
+      existingKey
+    );
     if (editingFieldIndex.value >= 0) {
       formConfig.fields.splice(editingFieldIndex.value, 1, payload);
     } else {
@@ -647,6 +1192,11 @@
   };
 
   const removeField = index => {
+    const field = formConfig.fields[index];
+    if (isFieldLocked(field)) {
+      uni.showToast({ title: "绯荤粺鍐呯疆濉姤椤逛笉鍙垹闄�", icon: "none" });
+      return;
+    }
     formConfig.fields.splice(index, 1);
   };
 
@@ -850,6 +1400,13 @@
       .catch(() => {
         userList.value = [];
       });
+    getDept()
+      .then(res => {
+        deptList.value = res?.data || [];
+      })
+      .catch(() => {
+        deptList.value = [];
+      });
   });
 </script>
 
@@ -899,6 +1456,12 @@
     }
   }
 
+  .section-head-left {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+
   .section-title {
     font-size: 15px;
     font-weight: 600;
@@ -906,6 +1469,12 @@
     padding-left: 10px;
     border-left: 3px solid $primary;
     line-height: 1.2;
+  }
+
+  .section-count {
+    font-size: 12px;
+    color: $text-muted;
+    padding-left: 13px;
   }
 
   .head-actions {
@@ -918,10 +1487,34 @@
     font-size: 14px;
     color: $text-secondary;
 
-    &--primary {
-      color: $primary;
-      font-weight: 500;
+    &--import {
+      color: $text-secondary;
+      padding: 6px 12px;
+      border: 1px solid #dce3ed;
+      border-radius: 8px;
+      background: #fff;
+      font-size: 13px;
     }
+
+    &--disabled {
+      color: #c0c4cc;
+      border-color: #ebeef5;
+      background: #f5f7fa;
+    }
+
+    &--primary {
+      color: #fff;
+      font-weight: 500;
+      padding: 6px 14px;
+      border: none;
+      border-radius: 8px;
+      background: linear-gradient(135deg, #4d8dff 0%, #2979ff 100%);
+      box-shadow: 0 2px 8px rgba(41, 121, 255, 0.25);
+    }
+  }
+
+  :deep(.form-item-select--disabled .u-form-item__body) {
+    opacity: 0.65;
   }
 
   .section-body {
@@ -1141,11 +1734,36 @@
   .field-item {
     display: flex;
     align-items: center;
-    gap: 10px;
-    padding: 10px 12px;
-    background: #f8fafc;
+    gap: 12px;
+    padding: 14px;
+    background: #fff;
     border-radius: $radius-md;
-    border: 1px solid #eef2f6;
+    border: 1px solid #e8eef5;
+    box-shadow: 0 1px 4px rgba(31, 45, 61, 0.04);
+    transition: border-color 0.2s, box-shadow 0.2s;
+
+    &:active:not(.field-item--locked) {
+      border-color: #c6daf5;
+      box-shadow: 0 2px 8px rgba(41, 121, 255, 0.08);
+    }
+
+    &--locked {
+      background: #fafbfd;
+    }
+  }
+
+  .field-order {
+    width: 28px;
+    height: 28px;
+    border-radius: 8px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: #fff;
+    font-size: 13px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
   }
 
   .field-main {
@@ -1156,14 +1774,40 @@
   .field-title-row {
     display: flex;
     align-items: center;
-    flex-wrap: wrap;
-    gap: 6px;
+    justify-content: space-between;
+    gap: 8px;
+    margin-bottom: 4px;
   }
 
   .field-name {
     font-size: 15px;
     font-weight: 600;
     color: $text;
+    flex: 1;
+    min-width: 0;
+  }
+
+  .field-tags {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    flex-shrink: 0;
+  }
+
+  .field-key {
+    display: block;
+    font-size: 12px;
+    color: $text-muted;
+    font-family: ui-monospace, monospace;
+  }
+
+  .field-lock-tag {
+    flex-shrink: 0;
+    font-size: 11px;
+    color: #909399;
+    padding: 4px 8px;
+    background: #f0f2f5;
+    border-radius: 4px;
   }
 
   .type-tag {
@@ -1189,6 +1833,11 @@
     &--date {
       color: #18a058;
       background: #e8faf0;
+    }
+
+    &--select {
+      color: #9c27b0;
+      background: #f6edfc;
     }
   }
 
@@ -1231,10 +1880,13 @@
   }
 
   .empty-mini {
-    padding: 20px 0;
+    padding: 32px 16px;
     text-align: center;
     font-size: 13px;
     color: $text-muted;
+    background: #fafbfd;
+    border: 1px dashed #dce8f5;
+    border-radius: 10px;
   }
 
   .flow-wrap {
@@ -1410,65 +2062,433 @@
   }
 
   .sheet-handle {
-    width: 40px;
+    width: 36px;
     height: 4px;
-    margin: 10px auto 6px;
-    background: #e4e7ed;
+    margin: 10px auto 4px;
+    background: #d8dde6;
     border-radius: 2px;
   }
 
-  .field-editor,
+  .field-editor .sheet-handle {
+    background: #c8ced8;
+  }
+
+  .field-editor {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    max-height: 88vh;
+    background: #f5f7fb;
+    border-radius: 16px 16px 0 0;
+    overflow: hidden;
+  }
+
   .user-picker {
+    position: relative;
     padding: 0 18px calc(18px + env(safe-area-inset-bottom));
     background: #fff;
     max-height: 85vh;
   }
 
-  .editor-title {
+  .editor-header {
+    padding: 4px 20px 12px;
+    background: #fff;
+    text-align: center;
+    border-bottom: 1px solid #f0f2f5;
+  }
+
+  .editor-subtitle {
     display: block;
+    margin-top: 4px;
+    font-size: 12px;
+    color: $text-muted;
+  }
+
+  .editor-picker-layer {
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    z-index: 20;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-end;
+  }
+
+  .editor-picker-mask {
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.45);
+  }
+
+  .editor-picker-panel {
+    position: relative;
+    z-index: 1;
+    background: #fff;
+    border-radius: 16px 16px 0 0;
+    max-height: 55vh;
+    padding-bottom: env(safe-area-inset-bottom);
+  }
+
+  .editor-picker-head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 14px 18px;
+    border-bottom: 1px solid #f0f0f0;
+  }
+
+  .editor-picker-cancel {
+    font-size: 15px;
+    color: #909399;
+    min-width: 48px;
+  }
+
+  .editor-picker-title {
     font-size: 16px;
     font-weight: 600;
     color: $text;
-    text-align: center;
-    margin-bottom: 14px;
+  }
+
+  .editor-picker-placeholder {
+    min-width: 48px;
+  }
+
+  .editor-picker-scroll {
+    max-height: calc(55vh - 52px);
+  }
+
+  .editor-picker-item {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 16px 18px;
+    font-size: 16px;
+    color: $text;
+    border-bottom: 1px solid #f5f7fa;
+
+    &--active {
+      color: $primary;
+      background: #f5f9ff;
+    }
+
+    &:last-child {
+      border-bottom: none;
+    }
+  }
+
+  .editor-scroll {
+    flex: 1;
+    height: 0;
+    max-height: 62vh;
   }
 
   .editor-form {
     display: flex;
     flex-direction: column;
-    gap: 12px;
+    gap: 10px;
+    padding: 12px 16px 16px;
   }
 
-  .editor-row {
-    display: flex;
-    flex-direction: column;
-    gap: 10px;
+  .editor-section-card {
+    background: #fff;
+    border-radius: 12px;
+    padding: 14px 14px 4px;
+    box-shadow: 0 1px 6px rgba(31, 45, 61, 0.05);
+  }
+
+  .editor-section-head {
+    margin-bottom: 10px;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #f2f4f7;
+  }
+
+  .editor-section-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: $text;
+    padding-left: 8px;
+    border-left: 3px solid $primary;
+    line-height: 1.2;
+  }
+
+  .editor-cell {
+    margin-bottom: 14px;
+
+    &--tap:active .picker-value-row {
+      background: #eef4ff;
+      border-color: #c6daf5;
+    }
 
     &--switch {
-      flex-direction: row;
+      display: flex;
       align-items: center;
       justify-content: space-between;
-      padding: 4px 0;
+      gap: 12px;
+      padding: 4px 0 10px;
+      margin-bottom: 4px;
+    }
+
+    &--value {
+      margin-bottom: 10px;
+    }
+  }
+
+  .switch-label-wrap {
+    display: flex;
+    flex-direction: column;
+    gap: 2px;
+  }
+
+  .switch-hint {
+    font-size: 12px;
+    color: $text-muted;
+  }
+
+  .editor-input-box {
+    background: #f7f9fc;
+    border: 1px solid #e8ecf2;
+    border-radius: 10px;
+    overflow: hidden;
+  }
+
+  :deep(.editor-input-box .u-input) {
+    background: transparent !important;
+  }
+
+  .default-hint {
+    display: block;
+    font-size: 12px;
+    color: $text-muted;
+    line-height: 1.5;
+    margin: -4px 0 10px;
+    padding: 0 2px;
+  }
+
+  .manual-options {
+    margin: 4px 0 12px;
+    padding-top: 4px;
+  }
+
+  .manual-options-title {
+    display: block;
+    font-size: 12px;
+    color: $text-muted;
+    margin-bottom: 10px;
+  }
+
+  .manual-options-table {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .option-table-head {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 0 4px 4px;
+  }
+
+  .option-col {
+    font-size: 12px;
+    color: $text-muted;
+    font-weight: 500;
+
+    &--idx {
+      width: 22px;
+      flex-shrink: 0;
+    }
+
+    &--label {
+      flex: 1.4;
+      min-width: 0;
+    }
+
+    &--value {
+      flex: 0.9;
+      min-width: 72px;
+    }
+
+    &--action {
+      width: 32px;
+      flex-shrink: 0;
+    }
+  }
+
+  .option-card {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 8px 10px;
+    background: #f8fafc;
+    border: 1px solid #e8ecf2;
+    border-radius: 10px;
+  }
+
+  .option-idx {
+    width: 22px;
+    height: 22px;
+    flex-shrink: 0;
+    border-radius: 6px;
+    background: #eef2f8;
+    color: $text-muted;
+    font-size: 12px;
+    font-weight: 600;
+    line-height: 22px;
+    text-align: center;
+  }
+
+  .option-input-wrap {
+    flex: 1.4;
+    min-width: 0;
+    background: #fff;
+    border: 1px solid #e4e8ef;
+    border-radius: 8px;
+    overflow: hidden;
+
+    &--value {
+      flex: 0.9;
+      min-width: 72px;
+    }
+  }
+
+  :deep(.option-input-wrap .u-input) {
+    background: transparent !important;
+  }
+
+  :deep(.option-input-wrap input),
+  :deep(.option-input-wrap .u-input__content__field-wrapper__field) {
+    font-size: 14px !important;
+    height: 36px !important;
+    min-height: 36px !important;
+    padding: 0 10px !important;
+  }
+
+  .option-del {
+    flex-shrink: 0;
+    width: 32px;
+    height: 32px;
+    border-radius: 8px;
+    background: #fff;
+    border: 1px solid #fde2e2;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .option-del--active {
+    background: #fef0f0;
+  }
+
+  .add-option-btn {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    margin-top: 10px;
+    padding: 11px;
+    border: 1.5px dashed #b8d4ff;
+    border-radius: 10px;
+    background: linear-gradient(180deg, #f8fbff 0%, #f0f6ff 100%);
+    color: $primary;
+    font-size: 14px;
+    font-weight: 500;
+  }
+
+  .add-option-btn--active {
+    background: #e8f2ff;
+    border-color: $primary;
+  }
+
+  .option-source-tip {
+    display: flex;
+    align-items: flex-start;
+    gap: 6px;
+    padding: 10px 12px;
+    margin-bottom: 10px;
+    background: #f5f7fa;
+    border-radius: 8px;
+    font-size: 12px;
+    color: $text-muted;
+    line-height: 1.5;
+  }
+
+  .editor-title {
+    display: block;
+    font-size: 17px;
+    font-weight: 600;
+    color: $text;
+  }
+
+  .picker-value-row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    min-height: 44px;
+    padding: 0 14px;
+    background: #f7f9fc;
+    border: 1px solid #e8ecf2;
+    border-radius: 10px;
+    gap: 8px;
+    transition: background 0.15s, border-color 0.15s;
+
+    &--tap:active {
+      background: #eef4ff;
+      border-color: #c6daf5;
+    }
+  }
+
+  .picker-value {
+    flex: 1;
+    min-width: 0;
+    font-size: 15px;
+    color: $text;
+    text-align: left;
+    line-height: 1.4;
+
+    &--placeholder {
+      color: #c0c4cc;
     }
   }
 
   .editor-label {
-    font-size: 14px;
+    display: block;
+    font-size: 13px;
     font-weight: 500;
     color: $text-secondary;
+    margin-bottom: 8px;
 
     &.required::before {
       content: "*";
       color: #f56c6c;
-      margin-right: 4px;
+      margin-right: 3px;
     }
   }
 
-  .editor-row .input-box,
-  .editor-row .textarea-box {
-    background: #f7f9fc;
-    border-radius: 10px;
-    border: 1px solid #eef1f6;
+  .editor-cell--switch .editor-label {
+    margin-bottom: 0;
+  }
+
+  .daterange-default-wrap {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .daterange-default-item {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .daterange-default-label {
+    font-size: 13px;
+    color: $text-secondary;
   }
 
   .type-chip-grid {
@@ -1494,40 +2514,37 @@
     }
   }
 
-  .default-date-row {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    padding: 0 12px;
-    min-height: 44px;
-    background: #f7f9fc;
-    border-radius: 10px;
-    border: 1px solid #eef1f6;
-  }
-
   .editor-footer {
     display: flex;
-    gap: 10px;
-    margin-top: 16px;
-    padding-top: 14px;
-    border-top: 1px solid #f5f7fa;
+    gap: 12px;
+    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
+    background: #fff;
+    border-top: 1px solid #eef0f4;
+    box-shadow: 0 -4px 12px rgba(31, 45, 61, 0.06);
   }
 
   .editor-btn {
     flex: 1;
     text-align: center;
-    padding: 11px 0;
-    border-radius: 8px;
+    padding: 12px 0;
+    border-radius: 10px;
     font-size: 15px;
+    font-weight: 500;
 
     &--cancel {
       color: $text-secondary;
       background: #f5f7fa;
+      border: 1px solid #e4e7ed;
     }
 
     &--confirm {
       color: #fff;
-      background: $primary;
+      background: linear-gradient(135deg, #4d8dff 0%, #2979ff 100%);
+      box-shadow: 0 4px 12px rgba(41, 121, 255, 0.35);
+    }
+
+    &--confirm:active {
+      opacity: 0.9;
     }
   }
 
diff --git a/src/pages/oa/ApproveManage/approve-template/index.vue b/src/pages/oa/ApproveManage/approve-template/index.vue
index 44b77c9..5215afa 100644
--- a/src/pages/oa/ApproveManage/approve-template/index.vue
+++ b/src/pages/oa/ApproveManage/approve-template/index.vue
@@ -80,7 +80,8 @@
                        @click.stop="goEdit(item)">
               缂栬緫
             </up-button>
-            <up-button class="action-btn"
+            <up-button v-if="!isSystemTemplate(item)"
+                       class="action-btn"
                        size="small"
                        type="error"
                        plain
@@ -119,6 +120,7 @@
     buildTypeLabelMap,
     fetchApprovalTemplateTypes,
     getTemplateTypeLabel,
+    isSystemApprovalTemplate,
   } from "../../_utils/approvalTemplateType.js";
 
   const EDIT_STORAGE_KEY = "oa_approve_template_edit_row";
@@ -163,6 +165,8 @@
 
   const businessTypeText = type =>
     getTemplateTypeLabel(type, typeLabelMap.value);
+
+  const isSystemTemplate = isSystemApprovalTemplate;
 
   const loadTemplateTypes = () =>
     fetchApprovalTemplateTypes()
@@ -250,6 +254,10 @@
 
   const handleDelete = item => {
     if (!item?.id) return;
+    if (isSystemTemplate(item)) {
+      uni.showToast({ title: "绯荤粺鍐呯疆妯℃澘涓嶅彲鍒犻櫎", icon: "none" });
+      return;
+    }
     const name = item.templateName || "璇ユā鏉�";
     uni.showModal({
       title: "鍒犻櫎纭",
diff --git a/src/pages/oa/_utils/approvalFormField.js b/src/pages/oa/_utils/approvalFormField.js
new file mode 100644
index 0000000..1215674
--- /dev/null
+++ b/src/pages/oa/_utils/approvalFormField.js
@@ -0,0 +1,363 @@
+import { parseTime } from "@/utils/ruoyi";
+
+/** 濉姤瀛楁绫诲瀷锛氫笅鎷� */
+export const SELECT_FIELD_TYPES = new Set(["select", "dropdown", "picker"]);
+
+/** 鏃ユ湡鏃堕棿绫� type */
+const DATE_KIND_BY_TYPE = {
+  date: "date",
+  time: "time",
+  datetime: "datetime",
+  datetimerange: "datetime",
+};
+
+const DEFAULT_FORMAT = {
+  date: "YYYY-MM-DD",
+  time: "HH:mm",
+  datetime: "YYYY-MM-DD HH:mm:ss",
+};
+
+/** 瑙f瀽 formConfig JSON */
+export function parseApprovalFormConfig(raw) {
+  if (!raw) return { prompt: "", fields: [] };
+  try {
+    const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
+    return {
+      prompt: obj?.prompt || "",
+      fields: Array.isArray(obj?.fields) ? obj.fields : [],
+    };
+  } catch {
+    return { prompt: "", fields: [] };
+  }
+}
+
+/**
+ * 淇敼瀹℃壒锛氭ā鏉垮瓧娈靛畾涔� + 瀹炰緥宸插~鍊煎悎骞�
+ */
+export function mergeFormConfigForEdit(templateRaw, instanceRaw) {
+  const template = parseApprovalFormConfig(templateRaw);
+  const instance = parseApprovalFormConfig(instanceRaw);
+  const valueMap = {};
+  instance.fields.forEach(field => {
+    if (!field?.key) return;
+    const val = field.value ?? field.defaultValue;
+    if (val !== undefined && val !== null && val !== "") {
+      valueMap[field.key] = val;
+    }
+  });
+  const baseFields = template.fields.length ? template.fields : instance.fields;
+  return {
+    prompt: instance.prompt || template.prompt,
+    fields: baseFields.map(field => ({
+      ...field,
+      value: valueMap[field.key] ?? field.value ?? field.defaultValue ?? "",
+    })),
+  };
+}
+
+/** 鏄惁涓轰笅鎷夌被瀛楁 */
+export function isSelectField(field) {
+  const type = String(field?.type ?? "").toLowerCase();
+  return SELECT_FIELD_TYPES.has(type);
+}
+
+/** 鏄惁涓哄琛屾枃鏈� */
+export function isTextareaField(field) {
+  return String(field?.type ?? "").toLowerCase() === "textarea";
+}
+
+/** 璇诲彇瀛楁閰嶇疆鐨勬棩鏈熸牸寮忥紙鍏煎 format / dateFormat / timeFormat锛� */
+export function getFieldFormatStr(field) {
+  return (
+    field?.format ?? field?.dateFormat ?? field?.timeFormat ?? ""
+  ).trim();
+}
+
+/** 鏃ユ湡鏃堕棿瀛楁绉嶇被锛歞ate | time | datetime | null */
+export function getDateFieldKind(field) {
+  const type = String(field?.type ?? "").toLowerCase();
+  if (DATE_KIND_BY_TYPE[type]) return DATE_KIND_BY_TYPE[type];
+
+  const fmt = getFieldFormatStr(field);
+  if (!fmt) return null;
+  const hasDate = /Y{2,4}|D{1,2}/i.test(fmt);
+  const hasTime = /H{1,2}|h{1,2}|m{1,2}|s{1,2}/i.test(fmt);
+  if (hasDate && hasTime) return "datetime";
+  if (hasTime && !hasDate) return "time";
+  if (hasDate) return "date";
+  return null;
+}
+
+/** 鏄惁涓烘棩鏈�/鏃堕棿绫诲瓧娈碉紙涓嶅惈鏃ユ湡鏃堕棿鑼冨洿锛� */
+export function isDateLikeField(field) {
+  if (isDatetimerangeField(field)) return false;
+  return !!getDateFieldKind(field);
+}
+
+/** @deprecated 浣跨敤 isDateLikeField */
+export function isDateField(field) {
+  return isDateLikeField(field);
+}
+
+/** uView datetime-picker 鐨� mode */
+export function getDatePickerMode(field) {
+  const kind = getDateFieldKind(field);
+  if (kind === "time") return "time";
+  if (kind === "datetime") return "datetime";
+  return "date";
+}
+
+/** moment 椋庢牸鏍煎紡 鈫� parseTime 妯℃澘 */
+export function momentFormatToParsePattern(fmt) {
+  if (!fmt) return null;
+  return fmt
+    .replace(/YYYY/g, "{y}")
+    .replace(/YY/g, "{y}")
+    .replace(/DD/g, "{d}")
+    .replace(/dd/g, "{d}")
+    .replace(/MM/g, "{m}")
+    .replace(/HH/g, "{h}")
+    .replace(/hh/g, "{h}")
+    .replace(/mm/g, "{i}")
+    .replace(/ss/g, "{s}");
+}
+
+/** 灏嗘椂闂存埑/Date 鏍煎紡鍖栦负瀛楁閰嶇疆鏍煎紡 */
+export function formatFieldDateValue(field, dateSource) {
+  const kind = getDateFieldKind(field);
+  if (!kind) return "";
+  const fmt = getFieldFormatStr(field) || DEFAULT_FORMAT[kind];
+  const pattern = momentFormatToParsePattern(fmt);
+  let date;
+  if (typeof dateSource === "number") date = new Date(dateSource);
+  else if (dateSource instanceof Date) date = dateSource;
+  else return String(dateSource ?? "");
+  return parseTime(date, pattern) || "";
+}
+
+/** 灞曠ず鐢細灏嗗凡瀛樺�兼寜閰嶇疆鏍煎紡鍥炴樉 */
+export function formatFieldDisplayValue(field, storedValue) {
+  if (storedValue === undefined || storedValue === null || storedValue === "") {
+    return "";
+  }
+  if (!getDateFieldKind(field)) return String(storedValue);
+  const ts = parseFieldDateToTs(storedValue);
+  if (ts) return formatFieldDateValue(field, ts);
+  return String(storedValue);
+}
+
+/** 灏嗗凡瀛樻棩鏈熷瓧绗︿覆杞负鏃堕棿鎴筹紙渚涢�夋嫨鍣ㄥ垵濮嬪�硷級 */
+export function parseFieldDateToTs(value) {
+  if (value === undefined || value === null || value === "") return null;
+  if (typeof value === "number") return value;
+  const str = String(value).trim();
+  const normalized = str.replace(/-/g, "/").replace("T", " ");
+  const t = new Date(normalized).getTime();
+  return Number.isNaN(t) ? null : t;
+}
+
+/** 鏄惁涓烘暟瀛� */
+export function isNumberField(field) {
+  return String(field?.type ?? "").toLowerCase() === "number";
+}
+
+/**
+ * 灏嗗瓧娈甸厤缃腑鐨勯�夐」瑙勮寖涓� { label, value }[]
+ * 鏀寔锛歰ptions / optionList锛涢」涓哄瓧绗︿覆鎴� { label|name|text, value|key|code }
+ */
+export function normalizeFieldOptions(field) {
+  const raw =
+    field?.options ?? field?.optionList ?? field?.dictOptions ?? field?.items;
+  if (!Array.isArray(raw) || !raw.length) return [];
+
+  return raw
+    .map((item, index) => {
+      if (item == null) return null;
+      if (typeof item === "string" || typeof item === "number") {
+        const text = String(item);
+        return { label: text, value: text };
+      }
+      if (typeof item !== "object") return null;
+
+      const label =
+        item.label ??
+        item.name ??
+        item.text ??
+        item.dictLabel ??
+        item.title;
+      const rawValue =
+        item.value ?? item.key ?? item.code ?? item.dictValue ?? item.id;
+
+      if (label == null && rawValue == null) return null;
+
+      const value =
+        rawValue !== undefined && rawValue !== null ? rawValue : label ?? index;
+      return {
+        label: String(label ?? value),
+        value,
+      };
+    })
+    .filter(Boolean);
+}
+
+/** 鎸夊瓨鍌ㄥ�煎尮閰嶉�夐」灞曠ず鏂囨 */
+export function getFieldOptionLabel(field, storedValue) {
+  if (storedValue === undefined || storedValue === null || storedValue === "") {
+    return "";
+  }
+  const options = normalizeFieldOptions(field);
+  const strVal = String(storedValue);
+  const matched = options.find(
+    opt =>
+      String(opt.value) === strVal ||
+      String(opt.label) === strVal
+  );
+  return matched?.label ?? "";
+}
+
+/** 鍒濆鍖栧~鎶ュ�硷細浼樺厛宸插~ value锛屽叾娆� defaultValue */
+export function getFieldInitialValue(field) {
+  if (field?.value !== undefined && field?.value !== null && field?.value !== "") {
+    return field.value;
+  }
+  if (
+    field?.defaultValue !== undefined &&
+    field?.defaultValue !== null &&
+    field?.defaultValue !== ""
+  ) {
+    return field.defaultValue;
+  }
+  return "";
+}
+
+/** 妯℃澘缂栬緫锛氭帶浠剁被鍨嬮�夐」锛堜笌 Web 绔竴鑷达級 */
+export const FIELD_EDITOR_TYPE_OPTIONS = [
+  { name: "鍗曡鏂囨湰", value: "text" },
+  { name: "澶氳鏂囨湰", value: "textarea" },
+  { name: "鏁板瓧", value: "number" },
+  { name: "鏃ユ湡", value: "date" },
+  { name: "鏃ユ湡鏃堕棿鑼冨洿", value: "datetimerange" },
+  { name: "涓嬫媺閫夋嫨", value: "select" },
+];
+
+/** 涓嬫媺閫夐」鏉ユ簮 */
+export const FIELD_OPTION_SOURCE_OPTIONS = [
+  { name: "鎵嬪姩閰嶇疆", value: "manual" },
+  { name: "浜哄憳鍒楄〃", value: "user" },
+  { name: "閮ㄩ棬鍒楄〃", value: "dept" },
+];
+
+const OPTION_SOURCE_ALIASES = {
+  manual: "manual",
+  user: "user",
+  personnel: "user",
+  userlist: "user",
+  dept: "dept",
+  department: "dept",
+  deptlist: "dept",
+};
+
+export function getFieldEditorTypeLabel(type) {
+  const found = FIELD_EDITOR_TYPE_OPTIONS.find(
+    item => String(item.value) === String(type)
+  );
+  return found?.name || type || "-";
+}
+
+export function getFieldOptionSourceLabel(source) {
+  const key = getFieldOptionSource(source);
+  const found = FIELD_OPTION_SOURCE_OPTIONS.find(item => item.value === key);
+  return found?.name || "鎵嬪姩閰嶇疆";
+}
+
+/** 瑙f瀽閫夐」鏉ユ簮锛歮anual | user | dept */
+export function getFieldOptionSource(fieldOrSource) {
+  const raw =
+    typeof fieldOrSource === "object"
+      ? fieldOrSource?.optionSource
+      : fieldOrSource;
+  const key = String(raw ?? "manual")
+    .trim()
+    .toLowerCase();
+  return OPTION_SOURCE_ALIASES[key] || "manual";
+}
+
+export function isDatetimerangeField(field) {
+  return String(field?.type ?? "").toLowerCase() === "datetimerange";
+}
+
+/** 瑙f瀽鏃ユ湡鏃堕棿鑼冨洿榛樿鍊硷細start,end */
+export function parseDatetimerangeValue(stored) {
+  if (stored === undefined || stored === null || stored === "") {
+    return { start: "", end: "" };
+  }
+  const parts = String(stored)
+    .split(",")
+    .map(s => s.trim());
+  return { start: parts[0] || "", end: parts[1] || "" };
+}
+
+export function joinDatetimerangeValue(start, end) {
+  const s = String(start ?? "").trim();
+  const e = String(end ?? "").trim();
+  if (!s && !e) return "";
+  return `${s},${e}`;
+}
+
+export function formatDatetimerangeDisplay(stored) {
+  const { start, end } = parseDatetimerangeValue(stored);
+  if (!start && !end) return "";
+  if (start && end) return `${start} 鑷� ${end}`;
+  return start || end;
+}
+
+/**
+ * 瑙f瀽涓嬫媺閫夐」锛堝惈浜哄憳/閮ㄩ棬鍔ㄦ�佹潵婧愶級
+ * @param {object} field
+ * @param {{ users?: array, depts?: array }} context
+ */
+export function resolveFieldOptions(field, context = {}) {
+  const source = getFieldOptionSource(field);
+  if (source === "user") {
+    return (context.users || []).map(user => ({
+      label: user.nickName || user.userName || String(user.userId ?? ""),
+      value: user.userId,
+    }));
+  }
+  if (source === "dept") {
+    return (context.depts || []).map(dept => ({
+      label: dept.deptName || dept.name || String(dept.deptId ?? dept.id ?? ""),
+      value: dept.deptId ?? dept.id,
+    }));
+  }
+  return normalizeFieldOptions(field);
+}
+
+export function createEmptyFieldOption() {
+  return { label: "", value: "" };
+}
+
+/** 灏嗙紪杈戣崏绋胯鑼冧负鍙彁浜ょ殑瀛楁瀵硅薄 */
+export function buildFieldConfigPayload(draft, existingKey) {
+  const payload = {
+    key: (draft.key || existingKey || "").trim(),
+    label: (draft.label || "").trim(),
+    type: draft.type || "text",
+    required: !!draft.required,
+    defaultValue: String(draft.defaultValue ?? "").trim(),
+  };
+  if (isSelectField(payload)) {
+    payload.optionSource = getFieldOptionSource(draft.optionSource);
+    if (payload.optionSource === "manual") {
+      payload.options = (draft.options || [])
+        .map(opt => ({
+          label: String(opt?.label ?? "").trim(),
+          value: String(opt?.value ?? "").trim(),
+        }))
+        .filter(opt => opt.label && opt.value);
+    } else {
+      delete payload.options;
+    }
+  }
+  return payload;
+}
diff --git a/src/pages/oa/_utils/approvalTemplateType.js b/src/pages/oa/_utils/approvalTemplateType.js
index fce013e..4113ee8 100644
--- a/src/pages/oa/_utils/approvalTemplateType.js
+++ b/src/pages/oa/_utils/approvalTemplateType.js
@@ -6,6 +6,13 @@
  */
 export const CUSTOM_TEMPLATE_LIST_TYPE = 1;
 
+/** 绯荤粺鍐呯疆妯℃澘锛堜笉鍙垹闄わ紝濉姤椤圭瓑鍙楅檺锛� */
+export const SYSTEM_TEMPLATE_TYPE = 0;
+
+export function isSystemApprovalTemplate(item) {
+  return Number(item?.templateType) === SYSTEM_TEMPLATE_TYPE;
+}
+
 /** 涓氬姟绫诲瀷鏋氫妇鍏滃簳锛坅pproveType锛�1鍏嚭 2璇峰亣 鈥︼級 */
 export const FALLBACK_BUSINESS_TYPE_OPTIONS = [
   { name: "鍏嚭绠$悊", value: 1 },

--
Gitblit v1.9.3