From a9d97b150701e634bdb751eab277696abd136cca Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 16 六月 2026 14:39:47 +0800
Subject: [PATCH] 君歌app 1.依照web端功能修改

---
 src/pages/oa/ApproveManage/approve-template/edit.vue | 2634 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 2,634 insertions(+), 0 deletions(-)

diff --git a/src/pages/oa/ApproveManage/approve-template/edit.vue b/src/pages/oa/ApproveManage/approve-template/edit.vue
new file mode 100644
index 0000000..53a2d2e
--- /dev/null
+++ b/src/pages/oa/ApproveManage/approve-template/edit.vue
@@ -0,0 +1,2634 @@
+<!--
+  OA / 瀹℃壒绠$悊 / 鏂板缓瀹℃壒妯℃澘
+  璺敱锛�/pages/oa/ApproveManage/approve-template/edit
+-->
+<template>
+  <view class="template-edit-page">
+    <PageHeader :title="pageTitle"
+                @back="goBack" />
+
+    <scroll-view class="form-scroll"
+                 scroll-y
+                 :show-scrollbar="false">
+      <up-form ref="formRef"
+               :model="form"
+               :rules="rules"
+               label-width="88"
+               input-align="right"
+               error-message-align="right">
+        <u-cell-group title="鍩烘湰淇℃伅"
+                      class="form-section">
+          <up-form-item label="妯℃澘鍚嶇О"
+                        prop="templateName"
+                        required
+                        class="form-item-name">
+            <up-input v-model="form.templateName"
+                      class="name-input-inline"
+                      placeholder="璇疯緭鍏ユā鏉垮悕绉�"
+                      maxlength="50"
+                      :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
+                      :disabled="isSystemTemplate" />
+            <template v-if="!isSystemTemplate"
+                      #right>
+              <up-icon name="arrow-right"
+                       @click.stop="openBusinessTypeSheet" />
+            </template>
+          </up-form-item>
+          <up-form-item label="鍚敤鐘舵��"
+                        class="form-item-switch">
+            <view class="switch-wrap">
+              <up-switch v-model="enabledBool" />
+            </view>
+          </up-form-item>
+          <up-form-item label="妯℃澘璇存槑"
+                        class="form-item-desc"
+                        label-position="top">
+            <view class="desc-input-shell">
+              <up-textarea v-model="form.description"
+                           placeholder="閫夊~"
+                           maxlength="200"
+                           border="none"
+                           height="72" />
+            </view>
+          </up-form-item>
+        </u-cell-group>
+
+        <view class="section-card">
+          <view class="section-head section-head--between">
+            <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 head-link--import"
+                    :class="{ 'head-link--disabled': !canImportTemplate }"
+                    @click="openTemplateImport">浠庡凡鏈夋ā鏉垮鍏�</text>
+              <text class="head-link head-link--primary"
+                    @click="openFieldEditor()">+ 娣诲姞濉姤椤�</text>
+            </view>
+          </view>
+          <view class="section-body">
+            <view class="prompt-row">
+              <text class="prompt-label">濉姤鎻愮ず</text>
+              <up-input v-model="formConfig.prompt"
+                        class="prompt-input"
+                        placeholder="閫夊~"
+                        maxlength="200"
+                        clearable />
+            </view>
+            <view v-if="formConfig.fields.length"
+                  class="field-list">
+              <view v-for="(field, index) in formConfig.fields"
+                    :key="field.key"
+                    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>
+                    <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">
+                    榛樿锛歿{ formatFieldDefaultPreview(field) }}
+                  </text>
+                </view>
+                <view v-if="!isFieldLocked(field)"
+                      class="field-actions"
+                      @click.stop>
+                  <view class="icon-btn icon-btn--edit"
+                        @click.stop="openFieldEditor(field, index)">
+                    <up-icon name="edit-pen"
+                             size="16"
+                             color="#2979ff" />
+                  </view>
+                  <view class="icon-btn icon-btn--del"
+                        @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
+                  class="empty-mini">
+              <text>鏆傛棤濉姤椤�</text>
+            </view>
+          </view>
+        </view>
+
+        <view class="section-card">
+          <view class="section-head">
+            <text class="section-title">瀹℃壒娴佺▼</text>
+          </view>
+          <view class="flow-wrap">
+            <view v-for="(node, nodeIndex) in flowNodes"
+                  :key="node._key"
+                  class="flow-node-block">
+              <view class="flow-node-card">
+                <view class="node-header">
+                  <view class="node-level-badge">{{ nodeIndex + 1 }}</view>
+                  <text class="node-level-text">绗瑊{ levelLabel(nodeIndex + 1) }}绾�</text>
+                  <view v-if="flowNodes.length > 1"
+                        class="node-delete"
+                        @click="removeNode(nodeIndex)">
+                    <up-icon name="trash"
+                             size="16"
+                             color="#f56c6c" />
+                  </view>
+                </view>
+                <view class="approve-type-row">
+                  <view class="type-btn"
+                        :class="{ active: node.approveType === 'AND' }"
+                        @click="node.approveType = 'AND'">
+                    浼氱
+                  </view>
+                  <view class="type-btn"
+                        :class="{ active: node.approveType === 'OR' }"
+                        @click="node.approveType = 'OR'">
+                    鎴栫
+                  </view>
+                </view>
+                <view class="approver-list">
+                  <view v-for="(approver, approverIndex) in node.approvers"
+                        :key="`${node._key}-${approver.approverId}-${approverIndex}`"
+                        class="approver-chip">
+                    <view class="approver-avatar">{{ (approver.approverName || "?").charAt(0) }}</view>
+                    <text class="approver-name">{{ approver.approverName }}</text>
+                    <view class="approver-remove"
+                          hover-class="approver-remove--active"
+                          @tap.stop="removeApprover(nodeIndex, approverIndex)"
+                          @click.stop="removeApprover(nodeIndex, approverIndex)">
+                      <text class="remove-icon">脳</text>
+                    </view>
+                  </view>
+                  <view class="add-approver"
+                        @click="openUserPicker(nodeIndex)">
+                    <up-icon name="plus"
+                             size="14"
+                             color="#2979ff" />
+                    <text>娣诲姞</text>
+                  </view>
+                </view>
+              </view>
+              <view v-if="nodeIndex < flowNodes.length - 1"
+                    class="flow-connector">
+                <view class="flow-connector-line" />
+              </view>
+            </view>
+            <view class="add-node-bar"
+                  @click="addNode">
+              <up-icon name="plus-circle"
+                       size="20"
+                       color="#2979ff" />
+              <text>娣诲姞绾ф</text>
+            </view>
+          </view>
+        </view>
+      </up-form>
+    </scroll-view>
+
+    <FooterButtons :loading="submitting"
+                   confirm-text="淇濆瓨"
+                   @cancel="goBack"
+                   @confirm="handleSubmit" />
+
+    <up-action-sheet :show="showTemplateImportSheet"
+                     title="浠庡凡鏈夋ā鏉垮鍏�"
+                     :actions="templateImportActions"
+                     @select="onSelectImportTemplate"
+                     @close="showTemplateImportSheet = false" />
+
+    <up-popup :show="showFieldEditor"
+              mode="bottom"
+              round="16"
+              @close="closeFieldEditor">
+      <view class="field-editor">
+        <view class="sheet-handle" />
+        <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>
+        </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="closeDefaultDatePicker">
+      <up-datetime-picker :show="true"
+                          v-model="defaultDateTs"
+                          :mode="defaultDatePickerMode"
+                          @confirm="onDefaultDatePickerConfirm"
+                          @cancel="closeDefaultDatePicker" />
+    </up-popup>
+
+    <up-popup :show="showUserPicker"
+              mode="bottom"
+              round="16"
+              @close="closeUserPicker">
+      <view class="user-picker">
+        <view class="sheet-handle" />
+        <view class="picker-head">
+          <text class="picker-cancel"
+                @click="closeUserPicker">鍙栨秷</text>
+          <text class="picker-title">閫夋嫨瀹℃壒浜�</text>
+          <text class="picker-confirm"
+                @click="confirmUserPicker">
+            纭畾{{ pickerSelectedIds.length ? `(${pickerSelectedIds.length})` : "" }}
+          </text>
+        </view>
+        <scroll-view class="user-scroll"
+                     scroll-y>
+          <view v-for="user in availableUsers"
+                :key="user.userId"
+                class="user-item"
+                :class="{ selected: isUserSelected(user.userId) }"
+                @click="toggleUser(user)">
+            <view class="user-avatar">{{ (user.nickName || "?").charAt(0) }}</view>
+            <text class="user-name">{{ user.nickName }}</text>
+            <view class="user-check"
+                  :class="{ checked: isUserSelected(user.userId) }">
+              <up-icon v-if="isUserSelected(user.userId)"
+                       name="checkmark"
+                       size="14"
+                       color="#fff" />
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+    </up-popup>
+
+    <up-action-sheet :show="showBusinessTypeSheet"
+                     title="閫夋嫨瀹℃壒绫诲瀷"
+                     :actions="businessTypeActions"
+                     @select="onSelectBusinessType"
+                     @close="showBusinessTypeSheet = false" />
+  </view>
+</template>
+
+<script setup>
+  import { computed, onMounted, reactive, ref } from "vue";
+  import { onLoad } from "@dcloudio/uni-app";
+  import PageHeader from "@/components/PageHeader.vue";
+  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 {
+    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 formRef = ref();
+  const submitting = ref(false);
+  const userList = ref([]);
+  const templateId = ref(null);
+
+  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: "",
+    businessType: null,
+    templateType: 1,
+    enabled: "1",
+    description: "",
+  });
+
+  const formConfig = reactive({
+    prompt: "",
+    fields: [],
+  });
+
+  const fieldDraft = reactive({
+    label: "",
+    key: "",
+    type: "text",
+    defaultValue: "",
+    required: true,
+    optionSource: "manual",
+    options: [createEmptyFieldOption()],
+  });
+
+  let nodeKeySeed = 1;
+
+  const createNode = () => ({
+    _key: `node_${nodeKeySeed++}`,
+    approveType: "AND",
+    approvers: [],
+  });
+
+  const flowNodes = ref([createNode()]);
+
+  const rules = {
+    templateName: [{ required: true, message: "璇疯緭鍏ユā鏉垮悕绉�", trigger: "blur" }],
+    businessType: [
+      {
+        validator: (_rule, value, callback) => {
+          if (value === "" || value === null || value === undefined) {
+            callback(new Error("璇烽�夋嫨瀹℃壒绫诲瀷"));
+            return;
+          }
+          callback();
+        },
+        trigger: "change",
+      },
+    ],
+  };
+
+  const businessTypeOptions = ref([]);
+  const showBusinessTypeSheet = ref(false);
+
+  const businessTypeActions = computed(() =>
+    businessTypeOptions.value.map(opt => ({
+      name: opt.name,
+      value: opt.value,
+    }))
+  );
+
+  const businessTypeText = computed(() => {
+    const matched = businessTypeOptions.value.find(
+      opt => String(opt.value) === String(form.businessType)
+    );
+    return matched?.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",
+    set: val => {
+      form.enabled = val ? "1" : "0";
+    },
+  });
+
+  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 mapNodesFromRow = nodes => {
+    if (!Array.isArray(nodes) || !nodes.length) {
+      return [createNode()];
+    }
+    return nodes.map(node => ({
+      _key: `node_${nodeKeySeed++}`,
+      id: node.id,
+      templateId: node.templateId,
+      approveType: node.approveType || "AND",
+      approvers: (node.approvers || []).map((approver, idx) => ({
+        id: approver.id,
+        nodeId: approver.nodeId,
+        templateId: approver.templateId,
+        approverId: approver.approverId,
+        approverName: approver.approverName,
+        sortNo: approver.sortNo ?? idx + 1,
+      })),
+    }));
+  };
+
+  const fillFormFromRow = row => {
+    if (!row) return;
+    templateId.value = row.id;
+    form.templateName = row.templateName || "";
+    const parsedBusiness = Number(row.businessType);
+    form.businessType = Number.isNaN(parsedBusiness)
+      ? row.businessType
+      : parsedBusiness;
+    const parsedTemplateType = Number(row.templateType);
+    form.templateType = Number.isNaN(parsedTemplateType) ? 1 : parsedTemplateType;
+    form.enabled = String(row.enabled ?? "1");
+    form.description = row.description || "";
+
+    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);
+  };
+
+  const availableUsers = computed(() => {
+    const node = flowNodes.value[editingNodeIndex.value];
+    if (!node) return userList.value;
+    const selectedIds = new Set(node.approvers.map(a => a.approverId));
+    return userList.value.filter(user => !selectedIds.has(user.userId));
+  });
+
+  const levelLabel = n => LEVEL_TEXT[n] || String(n);
+
+  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 = {
+      text: "type-tag--text",
+      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";
+  };
+
+  const goBack = () => {
+    uni.navigateBack();
+  };
+
+  const openBusinessTypeSheet = () => {
+    if (isSystemTemplate.value) return;
+    if (!businessTypeOptions.value.length) {
+      uni.showToast({ title: "瀹℃壒绫诲瀷鍔犺浇涓�", icon: "none" });
+      return;
+    }
+    showBusinessTypeSheet.value = true;
+  };
+
+  const onSelectBusinessType = action => {
+    form.businessType = action.value;
+    showBusinessTypeSheet.value = false;
+    formRef.value?.validateField?.("businessType");
+  };
+
+  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 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 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.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 {
+      resetFieldDraft();
+    }
+    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 => {
+    const base = (label || "field")
+      .trim()
+      .replace(/\s+/g, "_")
+      .replace(/[^\w\u4e00-\u9fa5]/g, "");
+    let key = base || "field";
+    let index = 1;
+    while (formConfig.fields.some((item, idx) => item.key === key && idx !== editingFieldIndex.value)) {
+      key = `${base}_${index++}`;
+    }
+    return key;
+  };
+
+  const confirmFieldEditor = () => {
+    if (
+      editingFieldIndex.value >= 0 &&
+      isFieldLocked(formConfig.fields[editingFieldIndex.value])
+    ) {
+      uni.showToast({ title: "绯荤粺鍐呯疆濉姤椤逛笉鍙慨鏀�", icon: "none" });
+      return;
+    }
+    if (!fieldDraft.label?.trim()) {
+      uni.showToast({ title: "璇疯緭鍏ユ樉绀哄悕绉�", icon: "none" });
+      return;
+    }
+    const existingKey =
+      editingFieldIndex.value >= 0
+        ? formConfig.fields[editingFieldIndex.value]?.key
+        : null;
+    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 {
+      formConfig.fields.push(payload);
+    }
+    closeFieldEditor();
+  };
+
+  const removeField = index => {
+    const field = formConfig.fields[index];
+    if (isFieldLocked(field)) {
+      uni.showToast({ title: "绯荤粺鍐呯疆濉姤椤逛笉鍙垹闄�", icon: "none" });
+      return;
+    }
+    formConfig.fields.splice(index, 1);
+  };
+
+  const addNode = () => {
+    flowNodes.value.push(createNode());
+  };
+
+  const removeNode = index => {
+    if (flowNodes.value.length <= 1) {
+      uni.showToast({ title: "鑷冲皯淇濈暀涓�涓鎵硅妭鐐�", icon: "none" });
+      return;
+    }
+    flowNodes.value.splice(index, 1);
+  };
+
+  const openUserPicker = nodeIndex => {
+    editingNodeIndex.value = nodeIndex;
+    pickerSelectedIds.value = [];
+    showUserPicker.value = true;
+  };
+
+  const closeUserPicker = () => {
+    showUserPicker.value = false;
+    editingNodeIndex.value = -1;
+    pickerSelectedIds.value = [];
+  };
+
+  const isUserSelected = userId => pickerSelectedIds.value.includes(userId);
+
+  const toggleUser = user => {
+    const ids = pickerSelectedIds.value;
+    const index = ids.indexOf(user.userId);
+    if (index >= 0) {
+      ids.splice(index, 1);
+    } else {
+      ids.push(user.userId);
+    }
+  };
+
+  const confirmUserPicker = () => {
+    const node = flowNodes.value[editingNodeIndex.value];
+    if (!node) {
+      closeUserPicker();
+      return;
+    }
+    const selectedUsers = userList.value.filter(user =>
+      pickerSelectedIds.value.includes(user.userId)
+    );
+    if (!selectedUsers.length) {
+      uni.showToast({ title: "璇烽�夋嫨瀹℃壒浜�", icon: "none" });
+      return;
+    }
+    const startSort = node.approvers.length;
+    selectedUsers.forEach((user, idx) => {
+      node.approvers.push({
+        approverId: user.userId,
+        approverName: user.nickName,
+        sortNo: startSort + idx + 1,
+      });
+    });
+    closeUserPicker();
+  };
+
+  const removeApprover = (nodeIndex, approverIndex) => {
+    const node = flowNodes.value[nodeIndex];
+    if (!node?.approvers?.length) return;
+    const next = node.approvers
+      .filter((_, idx) => idx !== approverIndex)
+      .map((item, idx) => ({
+        ...item,
+        sortNo: idx + 1,
+      }));
+    node.approvers = next;
+  };
+
+  const validateFlow = () => {
+    if (!flowNodes.value.length) {
+      uni.showToast({ title: "璇烽厤缃鎵规祦绋�", icon: "none" });
+      return false;
+    }
+    const emptyNode = flowNodes.value.find(node => !node.approvers.length);
+    if (emptyNode) {
+      uni.showToast({ title: "璇蜂负姣忎釜瀹℃壒鑺傜偣娣诲姞瀹℃壒浜�", icon: "none" });
+      return false;
+    }
+    return true;
+  };
+
+  const buildSubmitPayload = () => {
+    const tid = templateId.value;
+    const payload = {
+      templateName: form.templateName.trim(),
+      enabled: form.enabled,
+      description: form.description?.trim() || "",
+      businessType: form.businessType,
+      templateType: form.templateType,
+      formConfig: JSON.stringify({
+        prompt: formConfig.prompt?.trim() || "",
+        fields: formConfig.fields,
+      }),
+      nodes: flowNodes.value.map((node, index) => {
+        const nodePayload = {
+          levelNo: index + 1,
+          approveType: node.approveType,
+          approvers: node.approvers.map((approver, approverIndex) => {
+            const approverPayload = {
+              approverId: approver.approverId,
+              approverName: approver.approverName,
+              sortNo: approverIndex + 1,
+            };
+            if (isEditMode.value) {
+              if (approver.id != null) approverPayload.id = approver.id;
+              if (approver.nodeId != null) approverPayload.nodeId = approver.nodeId;
+              else if (node.id != null) approverPayload.nodeId = node.id;
+              if (approver.templateId != null) approverPayload.templateId = approver.templateId;
+              else if (tid != null) approverPayload.templateId = tid;
+            }
+            return approverPayload;
+          }),
+        };
+        if (isEditMode.value) {
+          if (node.id != null) nodePayload.id = node.id;
+          if (node.templateId != null) nodePayload.templateId = node.templateId;
+          else if (tid != null) nodePayload.templateId = tid;
+        }
+        return nodePayload;
+      }),
+    };
+
+    if (isEditMode.value) {
+      payload.id = tid;
+    }
+
+    return payload;
+  };
+
+  const handleSubmit = async () => {
+    const valid = await formRef.value.validate().catch(() => false);
+    if (!valid || !validateFlow()) return;
+
+    submitting.value = true;
+    const submitApi = isEditMode.value ? updateApprovalTemplate : addApprovalTemplate;
+    submitApi(buildSubmitPayload())
+      .then(() => {
+        uni.showToast({
+          title: isEditMode.value ? "淇敼鎴愬姛" : "淇濆瓨鎴愬姛",
+          icon: "success",
+        });
+        uni.removeStorageSync(EDIT_STORAGE_KEY);
+        setTimeout(() => {
+          uni.navigateBack();
+        }, 300);
+      })
+      .catch(() => {
+        uni.showToast({
+          title: isEditMode.value ? "淇敼澶辫触" : "淇濆瓨澶辫触",
+          icon: "none",
+        });
+      })
+      .finally(() => {
+        submitting.value = false;
+      });
+  };
+
+  onLoad(options => {
+    if (options?.id) {
+      const row = uni.getStorageSync(EDIT_STORAGE_KEY);
+      if (row && String(row.id) === String(options.id)) {
+        fillFormFromRow(row);
+      } else {
+        templateId.value = options.id;
+        uni.showToast({ title: "鏈幏鍙栧埌妯℃澘鏁版嵁", icon: "none" });
+      }
+      uni.removeStorageSync(EDIT_STORAGE_KEY);
+    }
+  });
+
+  const loadTemplateTypes = () =>
+    fetchApprovalTemplateTypes()
+      .then(opts => {
+        businessTypeOptions.value = opts;
+        if (!templateId.value && opts.length) {
+          const matched = opts.some(
+            opt => String(opt.value) === String(form.businessType)
+          );
+          if (!matched) {
+            form.businessType = opts[0].value;
+          }
+        }
+      })
+      .catch(() => {
+        uni.showToast({ title: "鑾峰彇瀹℃壒绫诲瀷澶辫触", icon: "none" });
+      });
+
+  onMounted(() => {
+    loadTemplateTypes();
+    userListNoPageByTenantId()
+      .then(res => {
+        userList.value = res?.data || [];
+      })
+      .catch(() => {
+        userList.value = [];
+      });
+    getDept()
+      .then(res => {
+        deptList.value = res?.data || [];
+      })
+      .catch(() => {
+        deptList.value = [];
+      });
+  });
+</script>
+
+<style scoped lang="scss">
+  @import "@/static/scss/form-common.scss";
+
+  $primary: #2979ff;
+  $primary-light: #ecf5ff;
+  $text: #1f2d3d;
+  $text-secondary: #606266;
+  $text-muted: #909399;
+  $border: #ebeef5;
+  $bg-page: #f0f3f8;
+  $radius-lg: 12px;
+  $radius-md: 10px;
+  $shadow-card: 0 2px 12px rgba(31, 45, 61, 0.05);
+
+  .template-edit-page {
+    display: flex;
+    flex-direction: column;
+    min-height: 100vh;
+    background: $bg-page;
+  }
+
+  .form-scroll {
+    flex: 1;
+    height: 0;
+    padding: 10px 12px calc(96px + env(safe-area-inset-bottom));
+  }
+
+  .section-card {
+    margin-bottom: 10px;
+    background: #fff;
+    border-radius: $radius-lg;
+    overflow: hidden;
+    box-shadow: $shadow-card;
+  }
+
+  .section-head {
+    padding: 12px 16px;
+    border-bottom: 1px solid #f2f4f7;
+
+    &--between {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+    }
+  }
+
+  .section-head-left {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+  }
+
+  .section-title {
+    font-size: 15px;
+    font-weight: 600;
+    color: $text;
+    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 {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
+
+  .head-link {
+    font-size: 14px;
+    color: $text-secondary;
+
+    &--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 {
+    padding: 2px 16px 14px;
+  }
+
+  .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-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-select .u-input__content__field-wrapper__field) {
+    text-align: right !important;
+  }
+
+  :deep(.form-item-switch .u-form-item__body) {
+    flex-direction: row !important;
+    align-items: center !important;
+  }
+
+  :deep(.form-item-switch .u-form-item__content) {
+    flex: 1 !important;
+    min-width: 0 !important;
+    display: flex !important;
+    justify-content: flex-end !important;
+  }
+
+  .switch-wrap {
+    display: flex;
+    justify-content: flex-end;
+    align-items: center;
+    width: 100%;
+  }
+
+  :deep(.form-item-desc .u-form-item__body) {
+    flex-direction: column !important;
+    align-items: stretch !important;
+    padding: 10px 0 12px !important;
+  }
+
+  :deep(.form-item-desc .u-form-item__content) {
+    justify-content: stretch !important;
+    width: 100% !important;
+  }
+
+  .desc-input-shell {
+    width: 100%;
+    box-sizing: border-box;
+    padding: 8px 12px;
+    background: #fff;
+    border: 1px solid #dcdfe6;
+    border-radius: 6px;
+  }
+
+  :deep(.desc-input-shell .u-textarea),
+  :deep(.desc-input-shell textarea) {
+    width: 100% !important;
+    font-size: 15px !important;
+    text-align: left !important;
+  }
+
+  .form-row-item {
+    margin: 0 !important;
+    padding: 0 !important;
+  }
+
+  :deep(.form-row-item .u-form-item__body) {
+    padding: 0;
+  }
+
+  :deep(.form-row-item .u-form-item__body__right__message) {
+    margin-top: 4px;
+    padding-left: 0;
+  }
+
+  .form-row {
+    padding: 10px 0;
+    border-bottom: 1px solid #f5f7fa;
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    &--column {
+      flex-direction: column;
+      align-items: stretch;
+      gap: 8px;
+    }
+
+    &--compact {
+      padding-top: 8px;
+    }
+  }
+
+  .form-row-label {
+    display: block;
+    font-size: 14px;
+    color: $text-secondary;
+    margin-bottom: 8px;
+
+    &.required::before {
+      content: "*";
+      color: #f56c6c;
+      margin-right: 3px;
+    }
+  }
+
+  .form-row--column .form-row-label {
+    margin-bottom: 0;
+  }
+
+  .prompt-row {
+    display: flex;
+    align-items: center;
+    padding: 12px 0;
+    margin-bottom: 4px;
+    border-bottom: 1px solid #f5f7fa;
+    gap: 8px;
+  }
+
+  .prompt-label {
+    flex-shrink: 0;
+    width: 88px;
+    font-size: 14px;
+    color: $text-secondary;
+  }
+
+  .prompt-input {
+    flex: 1;
+    min-width: 0;
+  }
+
+  :deep(.prompt-input),
+  :deep(.prompt-input .u-input__content) {
+    width: 100% !important;
+  }
+
+  :deep(.prompt-input input),
+  :deep(.prompt-input .u-input__content__field-wrapper__field) {
+    width: 100% !important;
+    text-align: right !important;
+    font-size: 15px !important;
+  }
+
+  .input-box,
+  .textarea-box {
+    background: #f7f9fc;
+    border-radius: 10px;
+    border: 1px solid #eef1f6;
+    overflow: hidden;
+  }
+
+  .textarea-box {
+    padding: 4px 0;
+  }
+
+  .field-list {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    margin-top: 8px;
+  }
+
+  .field-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 14px;
+    background: #fff;
+    border-radius: $radius-md;
+    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 {
+    flex: 1;
+    min-width: 0;
+  }
+
+  .field-title-row {
+    display: flex;
+    align-items: center;
+    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 {
+    font-size: 11px;
+    padding: 2px 8px;
+    border-radius: 4px;
+
+    &--text {
+      color: #2979ff;
+      background: #ecf5ff;
+    }
+
+    &--area {
+      color: #7c5cfc;
+      background: #f3efff;
+    }
+
+    &--num {
+      color: #e6a23c;
+      background: #fdf6ec;
+    }
+
+    &--date {
+      color: #18a058;
+      background: #e8faf0;
+    }
+
+    &--select {
+      color: #9c27b0;
+      background: #f6edfc;
+    }
+  }
+
+  .req-tag {
+    font-size: 11px;
+    padding: 2px 6px;
+    color: #f56c6c;
+    background: #fef0f0;
+    border-radius: 4px;
+  }
+
+  .field-default {
+    display: block;
+    margin-top: 4px;
+    font-size: 12px;
+    color: $text-muted;
+  }
+
+  .field-actions {
+    display: flex;
+    gap: 6px;
+    flex-shrink: 0;
+  }
+
+  .icon-btn {
+    width: 32px;
+    height: 32px;
+    border-radius: 8px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    &--edit {
+      background: #ecf5ff;
+    }
+
+    &--del {
+      background: #fef0f0;
+    }
+  }
+
+  .empty-mini {
+    padding: 32px 16px;
+    text-align: center;
+    font-size: 13px;
+    color: $text-muted;
+    background: #fafbfd;
+    border: 1px dashed #dce8f5;
+    border-radius: 10px;
+  }
+
+  .flow-wrap {
+    padding: 10px 16px 14px;
+  }
+
+  .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;
+  }
+
+  .node-header {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-bottom: 10px;
+  }
+
+  .node-level-badge {
+    width: 26px;
+    height: 26px;
+    border-radius: 8px;
+    background: $primary;
+    color: #fff;
+    font-size: 14px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+
+  .node-level-text {
+    flex: 1;
+    font-size: 15px;
+    font-weight: 600;
+    color: $text;
+  }
+
+  .node-delete {
+    padding: 6px;
+    flex-shrink: 0;
+  }
+
+  .approve-type-row {
+    display: flex;
+    background: #f0f3f8;
+    border-radius: 8px;
+    padding: 3px;
+    margin-bottom: 10px;
+  }
+
+  .type-btn {
+    flex: 1;
+    text-align: center;
+    padding: 8px 0;
+    font-size: 14px;
+    color: $text-secondary;
+    border-radius: 6px;
+
+    &.active {
+      background: #fff;
+      color: $primary;
+      font-weight: 500;
+    }
+  }
+
+  .approver-list {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
+  .approver-chip {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 6px 12px 6px 6px;
+    background: #fff;
+    border: 1px solid #dce8f8;
+    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: 80px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .approver-remove {
+    flex-shrink: 0;
+    width: 22px;
+    height: 22px;
+    margin-left: 2px;
+    border-radius: 50%;
+    background: #f2f3f5;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .approver-remove--active {
+    background: #fde2e2;
+  }
+
+  .remove-icon {
+    font-size: 16px;
+    line-height: 1;
+    color: #909399;
+    font-weight: 300;
+  }
+
+  .add-approver {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    padding: 8px 14px;
+    border: 1.5px dashed #a8cfff;
+    border-radius: 24px;
+    background: $primary-light;
+    color: $primary;
+    font-size: 13px;
+  }
+
+  .flow-connector {
+    display: flex;
+    justify-content: center;
+    padding: 4px 0;
+  }
+
+  .flow-connector-line {
+    width: 2px;
+    height: 14px;
+    background: #d0dff0;
+  }
+
+  .add-node-bar {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 6px;
+    margin-top: 8px;
+    padding: 11px;
+    border: 1px dashed #c6daf5;
+    border-radius: $radius-md;
+    color: $primary;
+    font-size: 14px;
+  }
+
+  .sheet-handle {
+    width: 36px;
+    height: 4px;
+    margin: 10px auto 4px;
+    background: #d8dde6;
+    border-radius: 2px;
+  }
+
+  .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-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;
+  }
+
+  .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: 10px;
+    padding: 12px 16px 16px;
+  }
+
+  .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 {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      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 {
+    display: block;
+    font-size: 13px;
+    font-weight: 500;
+    color: $text-secondary;
+    margin-bottom: 8px;
+
+    &.required::before {
+      content: "*";
+      color: #f56c6c;
+      margin-right: 3px;
+    }
+  }
+
+  .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 {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 10px;
+  }
+
+  .type-chip {
+    text-align: center;
+    padding: 10px 6px;
+    font-size: 13px;
+    color: $text-secondary;
+    background: #f7f9fc;
+    border: 1px solid #eef1f6;
+    border-radius: 8px;
+
+    &.active {
+      color: $primary;
+      background: $primary-light;
+      border-color: $primary;
+      font-weight: 500;
+    }
+  }
+
+  .editor-footer {
+    display: flex;
+    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: 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: linear-gradient(135deg, #4d8dff 0%, #2979ff 100%);
+      box-shadow: 0 4px 12px rgba(41, 121, 255, 0.35);
+    }
+
+    &--confirm:active {
+      opacity: 0.9;
+    }
+  }
+
+  .picker-head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 14px;
+    border-bottom: 1px solid #f5f7fa;
+    margin-bottom: 8px;
+  }
+
+  .picker-title {
+    font-size: 16px;
+    font-weight: 600;
+    color: $text;
+  }
+
+  .picker-cancel {
+    font-size: 15px;
+    color: $text-muted;
+    min-width: 48px;
+  }
+
+  .picker-confirm {
+    font-size: 15px;
+    color: $primary;
+    font-weight: 600;
+    min-width: 48px;
+    text-align: right;
+  }
+
+  .user-scroll {
+    max-height: 52vh;
+  }
+
+  .user-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 14px 4px;
+    border-bottom: 1px solid #f5f7fa;
+    border-radius: 10px;
+    margin-bottom: 4px;
+    transition: background 0.2s;
+
+    &.selected {
+      background: #f5f9ff;
+    }
+  }
+
+  .user-avatar {
+    width: 40px;
+    height: 40px;
+    border-radius: 12px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: #fff;
+    font-size: 16px;
+    font-weight: 600;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+  }
+
+  .user-name {
+    flex: 1;
+    font-size: 15px;
+    color: $text;
+  }
+
+  .user-check {
+    width: 22px;
+    height: 22px;
+    border-radius: 50%;
+    border: 2px solid #dcdfe6;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+
+    &.checked {
+      background: $primary;
+      border-color: $primary;
+    }
+  }
+</style>

--
Gitblit v1.9.3