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/ApproveManage/approve-list/apply.vue |  802 +++++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 636 insertions(+), 166 deletions(-)

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;

--
Gitblit v1.9.3