Просмотр исходного кода

refactor(web-admin): wire start page to OaStartFormDialog.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 3 месяцев назад
Родитель
Сommit
cf009de58a
1 измененных файлов с 27 добавлено и 588 удалено
  1. 27 588
      web-admin/src/views/oa-approval/start/index.vue

+ 27 - 588
web-admin/src/views/oa-approval/start/index.vue

@@ -84,166 +84,35 @@
         </section>
       </div>
 
-      <el-dialog
-        v-model="dlgVisible"
-        :title="picked?.appName || '发起审批'"
-        width="920px"
-        class="dialog-form-layout"
-        align-center
-        append-to-body
-        modal-class="protocol-dialog-mask"
-        destroy-on-close
-        :close-on-click-modal="false"
-      >
-        <div v-loading="detailLoading">
-          <form-create
-            v-if="formRule.length"
-            v-model="formData"
-            v-model:api="fapi"
-            :rule="formRule"
-            :option="formOption"
-          />
-          <p
-            v-if="confirmationDateManualFields.length"
-            class="oa-start__tip oa-start__confirm-relink"
-          >
-            <el-button link type="primary" @click="restoreConfirmationDateAuto">
-              恢复转正日期自动计算
-            </el-button>
-          </p>
-          <p v-else-if="!formRule.length && !detailLoading" class="oa-start__tip">
-            该模板暂无表单字段,仍可直接提交。
-          </p>
-          <OaFlowPreview
-            v-if="!detailLoading"
-            v-model:select-by-node="initiatorSelectByNode"
-            :steps="flowSteps"
-            :loading="previewLoading"
-            :resolved="flowResolved"
-            :pending-message="flowPendingMessage"
-            :user-options="userOptions"
-            @select-change="scheduleFlowPreview"
-          />
-        </div>
-        <template #footer>
-          <el-button class="btn-ghost" @click="closeDialog">
-            <el-icon><Close /></el-icon>
-            取消
-          </el-button>
-          <el-button class="btn-ghost" :loading="savingDraft" @click="saveDraft">
-            <el-icon><Document /></el-icon>
-            {{ editingDraftId ? '保存草稿' : '暂存草稿' }}
-          </el-button>
-          <el-button class="btn-main" type="primary" :loading="submitting" @click="submit">
-            <el-icon><Check /></el-icon>
-            提交
-          </el-button>
-        </template>
-      </el-dialog>
+      <OaStartFormDialog
+        ref="startDialogRef"
+        @saved="onDialogSaved"
+        @submitted="onDialogSubmitted"
+      />
     </section>
   </div>
 </template>
 
 <script setup lang="ts">
-  import { computed, onMounted, ref, watch } from 'vue'
+  import { computed, onMounted, ref } from 'vue'
   import { useRoute, useRouter } from 'vue-router'
-  import { Check, Close, Document, Promotion, Search } from '@element-plus/icons-vue'
+  import { Promotion, Search } from '@element-plus/icons-vue'
   import { ElMessage } from 'element-plus'
-  import { fetchUserList } from '@/api/admin'
-  import {
-    fetchCreateOaInstanceDraft,
-    fetchOaFlowPreview,
-    fetchOaGroupList,
-    fetchOaInstanceDetail,
-    fetchOaInstanceDraftDetail,
-    fetchOaTemplateDetail,
-    fetchOaTemplates,
-    fetchStartOaInstance,
-    fetchSubmitOaInstanceDraft,
-    fetchUpdateOaInstanceDraft,
-    type OaFlowPreviewStep
-  } from '@/api/oaApproval'
+  import { fetchOaGroupList, fetchOaTemplates } from '@/api/oaApproval'
   import OaAppIcon from '@/components/oa-approval/OaAppIcon.vue'
-  import OaFlowPreview from '@/components/oa-approval/OaFlowPreview.vue'
-  import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
+  import OaStartFormDialog from '@/components/oa-approval/OaStartFormDialog.vue'
   import { refreshOaMenuBadges } from '@/utils/oa-menu-badge'
-  import {
-    OA_FC_DEFAULT_GUTTER,
-    ensureOaUploadRuleProps
-  } from '@/utils/oa-form-create-upload'
-  import {
-    applyLeaveKitDurationsToFormData,
-    ensureLeaveKitRuleProps,
-    LEAVE_MANUAL_PREFIX,
-    listLeaveKitBindings,
-    markLeaveDurationManual
-  } from '@/utils/oa-leave-kit-runtime'
-  import { applyOvertimeKitDefaults, ensureOvertimeKitRuleProps } from '@/utils/oa-overtime-kit-runtime'
-  import { applyResignationKitDefaults } from '@/utils/oa-resignation-kit-runtime'
-  import {
-    applyConfirmationKitDateLinkage,
-    applyConfirmationKitDefaults,
-    clearConfirmationDateManualAndRelink,
-    CONFIRMATION_MANUAL_PREFIX,
-    ensureConfirmationKitVisibility,
-    listConfirmationKitBindings,
-    listManualConfirmationDateFields,
-    markConfirmationDateManual
-  } from '@/utils/oa-confirmation-kit-runtime'
-  import {
-    applyOwnerFieldDefaults,
-    ensureOwnerFieldRuleProps
-  } from '@/utils/oa-owner-field-defaults'
-  import {
-    applyTripKitDaysToFormData,
-    listTripKits,
-    TRIP_MANUAL_PREFIX
-  } from '@/utils/oa-trip-kit-runtime'
-  import { useUserStore } from '@/store/modules/user'
 
   defineOptions({ name: 'OaApprovalStart' })
 
   const router = useRouter()
   const route = useRoute()
-  const userStore = useUserStore()
   const loading = ref(false)
   const templates = ref<any[]>([])
   const groupOptions = ref<Array<{ id: string; groupName: string }>>([])
   const searchKeyword = ref('')
   const activeGroupId = ref('')
-  const dlgVisible = ref(false)
-  const detailLoading = ref(false)
-  const picked = ref<any>(null)
-  const formData = ref<Record<string, any>>({})
-  const formRule = ref<any[]>([])
-  const formOption = ref<Record<string, any>>({
-    submitBtn: false,
-    resetBtn: false,
-    row: { gutter: OA_FC_DEFAULT_GUTTER }
-  })
-  const fapi = ref<any>(null)
-  const submitting = ref(false)
-  const savingDraft = ref(false)
-  const editingDraftId = ref('')
-  const needInitiatorSelect = ref(false)
-  const initiatorSelectByNode = ref<Record<string, string[]>>({})
-  const userOptions = ref<Array<{ id: string; nickName: string; userNo: string }>>([])
-  const flowSteps = ref<OaFlowPreviewStep[]>([])
-  const flowResolved = ref(true)
-  const flowPendingMessage = ref('必填信息填写后,流程将自动显示')
-  const previewLoading = ref(false)
-  let previewTimer: ReturnType<typeof setTimeout> | null = null
-  /** 避免时长自动回写再次触发手改判定 */
-  let leaveKitSyncing = false
-  let leaveKitPrevSnapshot: Record<string, unknown> = {}
-  let tripKitSyncing = false
-  let tripKitPrevSnapshot: Record<string, unknown> = {}
-  let confirmationKitSyncing = false
-  let confirmationKitPrevSnapshot: Record<string, unknown> = {}
-
-  const confirmationDateManualFields = computed(() =>
-    listManualConfirmationDateFields({ rule: formRule.value }, formData.value)
-  )
+  const startDialogRef = ref<InstanceType<typeof OaStartFormDialog> | null>(null)
 
   const displayGroups = computed(() => {
     const kw = (searchKeyword.value || '').trim().toLowerCase()
@@ -252,7 +121,11 @@
       list = list.filter((a) => a.groupId === activeGroupId.value)
     }
     if (kw) {
-      list = list.filter((a) => String(a.appName || '').toLowerCase().includes(kw))
+      list = list.filter((a) =>
+        String(a.appName || '')
+          .toLowerCase()
+          .includes(kw)
+      )
     }
 
     const bucket = new Map<string, { id: string; name: string; apps: any[] }>()
@@ -283,433 +156,18 @@
     return sections
   })
 
-  function flowNeedsInitiatorSelect(node: any): boolean {
-    if (!node || typeof node !== 'object') return false
-    const cfg = node.approverConfig || {}
-    if ((cfg.assigneeType || '') === 'initiator_select') return true
-    if (node.child && flowNeedsInitiatorSelect(node.child)) return true
-    for (const g of node.conditionGroups || []) {
-      if (g?.child && flowNeedsInitiatorSelect(g.child)) return true
-    }
-    return false
-  }
-
-  async function loadUsers() {
-    try {
-      const pageSize = MAX_ADMIN_LIST_PAGE_SIZE
-      const raw: any[] = []
-      let page = 1
-      for (;;) {
-        const res = await fetchUserList({ page, pageSize, status: '1' })
-        const list = res?.list || []
-        raw.push(...list)
-        if (list.length < pageSize) break
-        if (typeof res?.total === 'number' && raw.length >= res.total) break
-        page += 1
-      }
-      userOptions.value = raw.map((u) => ({
-        id: u.id,
-        nickName: u.nickName || '',
-        userNo: u.userNo || ''
-      }))
-    } catch {
-      userOptions.value = []
-    }
-  }
-
-  async function loadFlowPreview(appId: string) {
-    previewLoading.value = true
-    try {
-      const preview = await fetchOaFlowPreview(appId, {
-        formData: { ...formData.value },
-        initiatorSelectByNode: { ...initiatorSelectByNode.value }
-      })
-      flowResolved.value = preview?.resolved !== false
-      flowPendingMessage.value =
-        preview?.message || '必填信息填写后,流程将自动显示'
-      flowSteps.value = flowResolved.value ? preview?.steps || [] : []
-    } catch {
-      flowSteps.value = []
-      flowResolved.value = true
-    } finally {
-      previewLoading.value = false
-    }
-  }
-
-  function scheduleFlowPreview() {
-    if (!dlgVisible.value || !picked.value?.id) return
-    if (previewTimer) clearTimeout(previewTimer)
-    previewTimer = setTimeout(() => {
-      loadFlowPreview(picked.value.id)
-    }, 300)
-  }
-
-  async function onPick(t: any) {
-    editingDraftId.value = ''
-    picked.value = t
-    formData.value = {}
-    formRule.value = []
-    leaveKitPrevSnapshot = {}
-    tripKitPrevSnapshot = {}
-    confirmationKitPrevSnapshot = {}
-    needInitiatorSelect.value = false
-    initiatorSelectByNode.value = {}
-    flowSteps.value = []
-    flowResolved.value = false
-    flowPendingMessage.value = '必填信息填写后,流程将自动显示'
-    dlgVisible.value = true
-    detailLoading.value = true
-    previewLoading.value = true
-    try {
-      const detail = await fetchOaTemplateDetail(t.id)
-      const schema = detail?.formSchemaJson || {}
-      const rawRule = Array.isArray(schema.rule)
-        ? schema.rule
-        : Array.isArray(schema)
-          ? schema
-          : []
-      formRule.value = ensureConfirmationKitVisibility(
-        ensureOwnerFieldRuleProps(
-          ensureOvertimeKitRuleProps(
-            ensureLeaveKitRuleProps(ensureOaUploadRuleProps(rawRule))
-          )
-        )
-      )
-      const savedGutter = Number(schema.option?.row?.gutter)
-      formOption.value = {
-        ...(schema.option || {}),
-        form: {
-          ...(schema.option?.form || {}),
-          labelPosition: 'top'
-        },
-        // 顶层字段 col.span 布局用;栅格组件 fcRow 由 ensureOaUploadRuleProps 处理
-        row: {
-          ...(schema.option?.row || {}),
-          gutter: savedGutter > 0 ? savedGutter : OA_FC_DEFAULT_GUTTER
-        },
-        submitBtn: false,
-        resetBtn: false
-      }
-      let data = await applyOwnerFieldDefaults(
-        formRule.value,
-        {},
-        (userStore.info || userStore.getUserInfo) as any
-      )
-      data = await applyConfirmationKitDefaults(
-        { rule: formRule.value },
-        data,
-        (userStore.info || userStore.getUserInfo) as any
-      )
-      data = applyResignationKitDefaults(
-        { rule: formRule.value },
-        data,
-        (userStore.info || userStore.getUserInfo) as any
-      )
-      data = applyOvertimeKitDefaults(
-        { rule: formRule.value },
-        data,
-        (userStore.info || userStore.getUserInfo) as any
-      )
-      const linked = applyConfirmationKitDateLinkage({ rule: formRule.value }, data)
-      formData.value = linked.data
-      confirmationKitPrevSnapshot = { ...linked.data }
-      const flow = detail?.flowTreeJson || {}
-      const root = flow.root || flow
-      needInitiatorSelect.value = flowNeedsInitiatorSelect(root)
-      if (needInitiatorSelect.value && !userOptions.value.length) {
-        await loadUsers()
-      }
-      await loadFlowPreview(t.id)
-    } catch {
-      formRule.value = []
-      flowSteps.value = []
-      flowResolved.value = true
-      previewLoading.value = false
-    } finally {
-      detailLoading.value = false
-    }
-  }
-
-  function closeDialog() {
-    dlgVisible.value = false
-    editingDraftId.value = ''
-  }
-
-  async function openDraft(draftId: string) {
-    const detail = await fetchOaInstanceDraftDetail(draftId)
-    const appId = String(detail?.appId || '').trim()
-    if (!appId) {
-      ElMessage.error('草稿缺少审批模板')
-      return
-    }
-    let row = templates.value.find((t: any) => t.id === appId)
-    if (!row) {
-      row = {
-        id: appId,
-        appName: detail?.appName || '发起审批'
-      }
-    }
-    await onPick(row)
-    editingDraftId.value = draftId
-    const saved = { ...(detail?.formData || {}) }
-    formData.value = await applyOwnerFieldDefaults(
-      formRule.value,
-      saved,
-      (userStore.info || userStore.getUserInfo) as any
-    )
-    formData.value = applyOvertimeKitDefaults(
-      { rule: formRule.value },
-      formData.value,
-      (userStore.info || userStore.getUserInfo) as any
-    )
-    const linked = applyConfirmationKitDateLinkage(
-      { rule: formRule.value },
-      formData.value
-    )
-    formData.value = linked.data
-    confirmationKitPrevSnapshot = { ...linked.data }
-    leaveKitPrevSnapshot = { ...formData.value }
-    tripKitPrevSnapshot = { ...formData.value }
-    const sel = detail?.initiatorSelectByNode
-    initiatorSelectByNode.value =
-      sel && typeof sel === 'object' && !Array.isArray(sel) ? { ...sel } : {}
-    await loadFlowPreview(appId)
-  }
-
-  async function saveDraft() {
-    if (!picked.value?.id) return
-    savingDraft.value = true
-    try {
-      const payload = {
-        formData: { ...formData.value },
-        initiatorSelectByNode: { ...initiatorSelectByNode.value },
-        title: picked.value.appName || ''
-      }
-      if (editingDraftId.value) {
-        await fetchUpdateOaInstanceDraft(editingDraftId.value, payload)
-      } else {
-        const created: any = await fetchCreateOaInstanceDraft({
-          appId: picked.value.id,
-          ...payload
-        })
-        if (created?.id) editingDraftId.value = created.id
-      }
-      void refreshOaMenuBadges()
-      closeDialog()
-    } finally {
-      savingDraft.value = false
-    }
-  }
-
-  function syncLeaveKitDurations() {
-    if (leaveKitSyncing) return
-    if (!formRule.value.length) return
-    const schema = { rule: formRule.value }
-    const bindings = listLeaveKitBindings(schema)
-    if (!bindings.length) return
-
-    const curr = { ...formData.value }
-    for (const kit of bindings) {
-      const prevDur = leaveKitPrevSnapshot[kit.durationField]
-      const currDur = curr[kit.durationField]
-      const startChanged =
-        leaveKitPrevSnapshot[kit.startField] !== curr[kit.startField]
-      const endChanged = leaveKitPrevSnapshot[kit.endField] !== curr[kit.endField]
-      const durChanged = prevDur !== currDur
-      if (durChanged && !startChanged && !endChanged && currDur !== undefined && currDur !== '') {
-        Object.assign(curr, markLeaveDurationManual(curr, kit.durationField, true))
-      }
-    }
-
-    const { data, changed } = applyLeaveKitDurationsToFormData(schema, curr)
-    leaveKitPrevSnapshot = { ...data }
-    const manualKeysChanged = bindings.some(
-      (k) =>
-        !!data[LEAVE_MANUAL_PREFIX + k.durationField] !==
-        !!formData.value[LEAVE_MANUAL_PREFIX + k.durationField]
-    )
-    const durationChanged = bindings.some(
-      (k) => data[k.durationField] !== formData.value[k.durationField]
-    )
-    if (changed || manualKeysChanged || durationChanged) {
-      leaveKitSyncing = true
-      formData.value = data
-      queueMicrotask(() => {
-        leaveKitSyncing = false
-      })
-    }
-  }
-
-  function syncTripKitDurations() {
-    if (tripKitSyncing) return
-    if (!formRule.value.length) return
-    const schema = { rule: formRule.value }
-    const kits = listTripKits(schema)
-    if (!kits.length) return
-
-    const curr = { ...formData.value }
-    for (const kit of kits) {
-      const prevDays = tripKitPrevSnapshot[kit.daysField]
-      const currDays = curr[kit.daysField]
-      const daysChanged = prevDays !== currDays
-      const listChanged =
-        JSON.stringify(tripKitPrevSnapshot[kit.listField]) !==
-        JSON.stringify(curr[kit.listField])
-      if (daysChanged && !listChanged && currDays !== undefined && currDays !== '') {
-        curr[TRIP_MANUAL_PREFIX + kit.daysField] = true
-      }
-    }
-
-    const { data, changed } = applyTripKitDaysToFormData(schema, curr)
-    tripKitPrevSnapshot = { ...data }
-    const manualKeysChanged = kits.some(
-      (k) =>
-        !!data[TRIP_MANUAL_PREFIX + k.daysField] !==
-        !!formData.value[TRIP_MANUAL_PREFIX + k.daysField]
-    )
-    const daysFieldChanged = kits.some(
-      (k) => data[k.daysField] !== formData.value[k.daysField]
-    )
-    const listFieldChanged = kits.some(
-      (k) =>
-        JSON.stringify(data[k.listField]) !== JSON.stringify(formData.value[k.listField])
-    )
-    if (changed || manualKeysChanged || daysFieldChanged || listFieldChanged) {
-      tripKitSyncing = true
-      formData.value = data
-      queueMicrotask(() => {
-        tripKitSyncing = false
-      })
-    }
+  async function onPick(row: any) {
+    await startDialogRef.value?.openForApp(row)
   }
 
-  function syncConfirmationKitDates() {
-    if (confirmationKitSyncing) return
-    if (!formRule.value.length) return
-    const schema = { rule: formRule.value }
-    const kits = listConfirmationKitBindings(schema)
-    if (!kits.length) return
-
-    let curr = { ...formData.value }
-    for (const kit of kits) {
-      const confField = kit.confirmationDateField
-      const hireField = kit.hireDateField
-      const probField = kit.probationMonthsField
-      if (!confField || !hireField || !probField) continue
-      const confChanged =
-        confirmationKitPrevSnapshot[confField] !== curr[confField]
-      const hireChanged = confirmationKitPrevSnapshot[hireField] !== curr[hireField]
-      const probChanged = confirmationKitPrevSnapshot[probField] !== curr[probField]
-      if (
-        confChanged &&
-        !hireChanged &&
-        !probChanged &&
-        curr[confField] !== undefined &&
-        curr[confField] !== ''
-      ) {
-        curr = markConfirmationDateManual(curr, confField, true)
-      }
-    }
-
-    const { data, changed } = applyConfirmationKitDateLinkage(schema, curr)
-    confirmationKitPrevSnapshot = { ...data }
-    const anyDiff =
-      changed ||
-      kits.some((k) => {
-        const f = k.confirmationDateField
-        if (!f) return false
-        return (
-          data[f] !== formData.value[f] ||
-          !!data[CONFIRMATION_MANUAL_PREFIX + f] !==
-            !!formData.value[CONFIRMATION_MANUAL_PREFIX + f]
-        )
-      })
-    if (anyDiff) {
-      confirmationKitSyncing = true
-      formData.value = data
-      queueMicrotask(() => {
-        confirmationKitSyncing = false
-      })
-    }
+  function onDialogSaved() {
+    void refreshOaMenuBadges()
   }
 
-  function restoreConfirmationDateAuto() {
-    const schema = { rule: formRule.value }
-    let data = { ...formData.value }
-    for (const field of listManualConfirmationDateFields(schema, data)) {
-      data = clearConfirmationDateManualAndRelink(schema, data, field)
-    }
-    confirmationKitSyncing = true
-    formData.value = data
-    confirmationKitPrevSnapshot = { ...data }
-    queueMicrotask(() => {
-      confirmationKitSyncing = false
-    })
-  }
-
-  watch(
-    formData,
-    () => {
-      syncLeaveKitDurations()
-      syncTripKitDurations()
-      syncConfirmationKitDates()
-      scheduleFlowPreview()
-    },
-    { deep: true }
-  )
-
-  async function submit() {
-    if (!picked.value?.id) return
-    for (const s of flowSteps.value) {
-      if (s.type === 'approval') {
-        const types =
-          Array.isArray(s.assigneeTypes) && s.assigneeTypes.length
-            ? s.assigneeTypes
-            : s.assigneeType
-              ? [s.assigneeType]
-              : []
-        if (types.includes('initiator_select')) {
-          const ids = initiatorSelectByNode.value[s.id] || []
-          if (!ids.length) {
-            ElMessage.warning(`请为「${s.name || '审批人'}」选择审批人`)
-            return
-          }
-        }
-      }
-    }
-    if (fapi.value?.validate) {
-      try {
-        await fapi.value.validate()
-      } catch {
-        ElMessage.warning('请完善表单必填项')
-        return
-      }
-    }
-    submitting.value = true
-    try {
-      const payload = {
-        formData: { ...formData.value },
-        initiatorSelectByNode: { ...initiatorSelectByNode.value }
-      }
-      let data: any
-      if (editingDraftId.value) {
-        data = await fetchSubmitOaInstanceDraft(editingDraftId.value, {
-          ...payload,
-          title: picked.value.appName || ''
-        })
-      } else {
-        data = await fetchStartOaInstance({
-          appId: picked.value.id,
-          ...payload
-        })
-      }
-      closeDialog()
-      void refreshOaMenuBadges()
-      if (data?.id) {
-        router.push({ path: `/system-manage/oa-approval/instances/${data.id}` })
-      }
-    } finally {
-      submitting.value = false
+  function onDialogSubmitted(data: any) {
+    void refreshOaMenuBadges()
+    if (data?.id) {
+      router.push({ path: `/system-manage/oa-approval/instances/${data.id}` })
     }
   }
 
@@ -724,30 +182,17 @@
       const qCopyFrom = String(route.query.copyFromInstanceId || '').trim()
       if (qDraftId) {
         try {
-          await openDraft(qDraftId)
+          await startDialogRef.value?.openForDraft(qDraftId, templates.value)
         } catch {
           ElMessage.error('无法打开草稿')
         }
       } else if (qAppId) {
         const row = templates.value.find((t: any) => t.id === qAppId)
         if (row) {
-          await onPick(row)
           if (qCopyFrom) {
-            try {
-              const old = await fetchOaInstanceDetail(qCopyFrom)
-              formData.value = await applyOwnerFieldDefaults(
-                formRule.value,
-                { ...(old?.formDataJson || {}) },
-                (userStore.info || userStore.getUserInfo) as any
-              )
-              formData.value = applyOvertimeKitDefaults(
-                { rule: formRule.value },
-                formData.value,
-                (userStore.info || userStore.getUserInfo) as any
-              )
-            } catch {
-              /* 预填失败仍可手工填 */
-            }
+            await startDialogRef.value?.openForAppWithCopy(row, qCopyFrom)
+          } else {
+            await startDialogRef.value?.openForApp(row)
           }
         }
       }
@@ -972,12 +417,6 @@
     }
   }
 
-  .oa-start__tip {
-    margin: 8px 0 0;
-    color: var(--mint-text-muted, #94a3b8);
-    font-size: 13px;
-  }
-
   @media (max-width: 640px) {
     .oa-app-card {
       width: 100%;