Files
zkxw/web-admin/src/components/oa-approval/OaStartFormDialog.vue
T
2026-08-13 14:31:57 +08:00

858 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 发起审批 / 草稿编辑共用弹窗 -->
<template>
<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"
@closed="onDialogClosed"
>
<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-if="leaveDurationManualFields.length" class="oa-start__tip">
<el-button link type="primary" @click="restoreLeaveAuto">
<el-icon><RefreshRight /></el-icon>
恢复请假时长自动计算
</el-button>
</p>
<p v-if="overtimeDurationManualFields.length" class="oa-start__tip">
<el-button link type="primary" @click="restoreOvertimeAuto">
<el-icon><RefreshRight /></el-icon>
恢复加班时长自动计算
</el-button>
</p>
<p v-if="tripDaysManualFields.length" class="oa-start__tip">
<el-button link type="primary" @click="restoreTripDaysAuto">
<el-icon><RefreshRight /></el-icon>
恢复出差天数自动计算
</el-button>
</p>
<p v-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>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { Check, Close, Document, RefreshRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { fetchUserList } from '@/api/admin'
import { fetchCnHolidayRange } from '@/api/cnHoliday'
import {
fetchCreateOaInstanceDraft,
fetchOaFlowPreview,
fetchOaInstanceDetail,
fetchOaInstanceDraftDetail,
fetchOaTemplateDetail,
fetchStartOaInstance,
fetchSubmitOaInstanceDraft,
fetchUpdateOaInstanceDraft,
type OaFlowPreviewStep
} from '@/api/oaApproval'
import OaFlowPreview from '@/components/oa-approval/OaFlowPreview.vue'
import { MAX_ADMIN_LIST_PAGE_SIZE } from '@/utils/fetch-paged-list'
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,
applyOvertimeKitDurationsToFormData,
ensureOvertimeKitRuleProps,
listOvertimeKitBindings,
markOvertimeDurationManual,
OVERTIME_MANUAL_PREFIX
} 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 {
buildFlowPreviewRequestKey,
collectConditionFields,
shouldShowFlowPreviewSpinner,
shouldSkipFlowPreviewRequest
} from '@/utils/oa-flow-preview-refresh'
import { useUserStore } from '@/store/modules/user'
defineOptions({ name: 'OaStartFormDialog' })
const emit = defineEmits<{
saved: []
submitted: [instance: any]
closed: []
}>()
const userStore = useUserStore()
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
/** 流程条件字段:仅这些 formData 变化才重拉预览 */
let flowConditionFields: string[] = []
let lastFlowPreviewRequestKey: string | null = null
let inFlightFlowPreviewKey: string | null = null
let hasCompletedFlowPreview = false
let flowPreviewReqSeq = 0
/** 避免时长自动回写再次触发手改判定 */
let leaveKitSyncing = false
let leaveKitPrevSnapshot: Record<string, unknown> = {}
let tripKitSyncing = false
let tripKitPrevSnapshot: Record<string, unknown> = {}
let confirmationKitSyncing = false
let confirmationKitPrevSnapshot: Record<string, unknown> = {}
let overtimeKitSyncing = false
let overtimeKitPrevSnapshot: Record<string, unknown> = {}
const overtimeRestMap: Record<string, boolean> = {}
const confirmationDateManualFields = computed(() =>
listManualConfirmationDateFields({ rule: formRule.value }, formData.value)
)
const leaveDurationManualFields = computed(() => {
const fields: string[] = []
for (const kit of listLeaveKitBindings({ rule: formRule.value })) {
if (formData.value[LEAVE_MANUAL_PREFIX + kit.durationField]) {
fields.push(kit.durationField)
}
}
return fields
})
const overtimeDurationManualFields = computed(() => {
const fields: string[] = []
for (const kit of listOvertimeKitBindings({ rule: formRule.value })) {
if (formData.value[OVERTIME_MANUAL_PREFIX + kit.durationField]) {
fields.push(kit.durationField)
}
}
return fields
})
const tripDaysManualFields = computed(() => {
const fields: string[] = []
for (const kit of listTripKits({ rule: formRule.value })) {
if (formData.value[TRIP_MANUAL_PREFIX + kit.daysField]) {
fields.push(kit.daysField)
}
}
return fields
})
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) {
if (!appId) return
const requestKey = buildFlowPreviewRequestKey({
formData: formData.value,
initiatorSelectByNode: initiatorSelectByNode.value,
conditionFields: flowConditionFields
})
if (
shouldSkipFlowPreviewRequest(lastFlowPreviewRequestKey, requestKey, {
inFlightKey: inFlightFlowPreviewKey
})
) {
return
}
const seq = ++flowPreviewReqSeq
inFlightFlowPreviewKey = requestKey
const showSpinner = shouldShowFlowPreviewSpinner({
hasCompletedPreview: hasCompletedFlowPreview
})
if (showSpinner) previewLoading.value = true
try {
const preview = await fetchOaFlowPreview(appId, {
formData: { ...formData.value },
initiatorSelectByNode: { ...initiatorSelectByNode.value }
})
if (seq !== flowPreviewReqSeq) return
lastFlowPreviewRequestKey = requestKey
flowResolved.value = preview?.resolved !== false
flowPendingMessage.value = preview?.message || '必填信息填写后,流程将自动显示'
flowSteps.value = flowResolved.value ? preview?.steps || [] : []
} catch {
if (seq !== flowPreviewReqSeq) return
flowSteps.value = []
flowResolved.value = true
} finally {
if (seq === flowPreviewReqSeq) {
inFlightFlowPreviewKey = null
hasCompletedFlowPreview = true
previewLoading.value = false
}
}
}
function resetFlowPreviewState(flowRoot: unknown) {
flowConditionFields = collectConditionFields(flowRoot)
lastFlowPreviewRequestKey = null
inFlightFlowPreviewKey = null
hasCompletedFlowPreview = false
flowPreviewReqSeq += 1
}
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 = {}
overtimeKitPrevSnapshot = {}
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 }
overtimeKitPrevSnapshot = { ...linked.data }
void syncOvertimeKitDurations()
const flow = detail?.flowTreeJson || {}
const root = flow.root || flow
resetFlowPreviewState(root)
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 = ''
}
function onDialogClosed() {
editingDraftId.value = ''
emit('closed')
}
async function openForApp(appRow: any) {
await onPick(appRow)
}
async function openForDraft(draftId: string, templatesOptional?: any[]) {
const detail = await fetchOaInstanceDraftDetail(draftId)
const appId = String(detail?.appId || '').trim()
if (!appId) {
ElMessage.error('草稿缺少审批模板')
return
}
const list = templatesOptional || []
let row = list.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 }
overtimeKitPrevSnapshot = { ...formData.value }
void syncOvertimeKitDurations()
const sel = detail?.initiatorSelectByNode
initiatorSelectByNode.value =
sel && typeof sel === 'object' && !Array.isArray(sel) ? { ...sel } : {}
await loadFlowPreview(appId)
}
async function openForAppWithCopy(appRow: any, copyFromInstanceId: string) {
await openForApp(appRow)
const copyId = String(copyFromInstanceId || '').trim()
if (!copyId) return
try {
const old = await fetchOaInstanceDetail(copyId)
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
)
overtimeKitPrevSnapshot = { ...formData.value }
void syncOvertimeKitDurations()
} catch {
/* 预填失败仍可手工填 */
}
}
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
}
emit('saved')
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
})
}
}
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 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
})
}
async function ensureOvertimeHolidayRange(startYmd: string, endYmd: string) {
const start = String(startYmd || '').trim().slice(0, 10)
const end = String(endYmd || '').trim().slice(0, 10)
if (!start || !end) return
let lo = start
let hi = end
if (lo > hi) {
const t = lo
lo = hi
hi = t
}
const begin = new Date(lo + 'T00:00:00')
const finish = new Date(hi + 'T00:00:00')
if (Number.isNaN(begin.getTime()) || Number.isNaN(finish.getTime())) return
let allPresent = true
for (let d = new Date(begin); d <= finish; d.setDate(d.getDate() + 1)) {
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const key = `${y}-${m}-${day}`
if (!(key in overtimeRestMap)) {
allPresent = false
break
}
}
if (allPresent) return
try {
const res = await fetchCnHolidayRange(lo, hi)
const days = res?.days || []
for (const day of days) {
const key = String(day?.dayDate || '').trim().slice(0, 10)
if (!key) continue
overtimeRestMap[key] = !!day.isRestDay
}
} catch {
/* 失败保留已有 map,计算回退周末规则 */
}
}
function isRestDayResolver(ymd: string): boolean {
if (ymd in overtimeRestMap) return overtimeRestMap[ymd]
const d = new Date(ymd + 'T00:00:00')
const w = d.getDay()
return w === 0 || w === 6
}
async function syncOvertimeKitDurations() {
if (overtimeKitSyncing) return
if (!formRule.value.length) return
const schema = { rule: formRule.value }
const bindings = listOvertimeKitBindings(schema)
if (!bindings.length) return
overtimeKitSyncing = true
try {
let minYmd = ''
let maxYmd = ''
for (const kit of bindings) {
for (const field of [kit.startField, kit.endField]) {
const ymd = String(formData.value[field] || '')
.trim()
.slice(0, 10)
if (ymd.length < 10) continue
if (!minYmd || ymd < minYmd) minYmd = ymd
if (!maxYmd || ymd > maxYmd) maxYmd = ymd
}
}
if (minYmd && maxYmd) {
await ensureOvertimeHolidayRange(minYmd, maxYmd)
}
const curr = { ...formData.value }
for (const kit of bindings) {
const prevDur = overtimeKitPrevSnapshot[kit.durationField]
const currDur = curr[kit.durationField]
const startChanged = overtimeKitPrevSnapshot[kit.startField] !== curr[kit.startField]
const endChanged = overtimeKitPrevSnapshot[kit.endField] !== curr[kit.endField]
const durChanged = prevDur !== currDur
if (
durChanged &&
!startChanged &&
!endChanged &&
currDur !== undefined &&
currDur !== ''
) {
Object.assign(curr, markOvertimeDurationManual(curr, kit.durationField, true))
}
}
const { data, changed } = applyOvertimeKitDurationsToFormData(
schema,
curr,
isRestDayResolver
)
overtimeKitPrevSnapshot = { ...data }
const manualKeysChanged = bindings.some(
(k) =>
!!data[OVERTIME_MANUAL_PREFIX + k.durationField] !==
!!formData.value[OVERTIME_MANUAL_PREFIX + k.durationField]
)
const durationChanged = bindings.some(
(k) => data[k.durationField] !== formData.value[k.durationField]
)
if (changed || manualKeysChanged || durationChanged) {
formData.value = data
}
} finally {
queueMicrotask(() => {
overtimeKitSyncing = false
})
}
}
function restoreOvertimeAuto() {
let data = { ...formData.value }
for (const kit of listOvertimeKitBindings({ rule: formRule.value })) {
data = markOvertimeDurationManual(data, kit.durationField, false)
}
formData.value = data
void syncOvertimeKitDurations()
}
function restoreLeaveAuto() {
let data = { ...formData.value }
for (const kit of listLeaveKitBindings({ rule: formRule.value })) {
data = markLeaveDurationManual(data, kit.durationField, false)
}
formData.value = data
syncLeaveKitDurations()
}
function restoreTripDaysAuto() {
let data = { ...formData.value }
for (const kit of listTripKits({ rule: formRule.value })) {
delete data[TRIP_MANUAL_PREFIX + kit.daysField]
}
formData.value = data
syncTripKitDurations()
}
watch(
formData,
() => {
syncLeaveKitDurations()
syncTripKitDurations()
syncConfirmationKitDates()
void syncOvertimeKitDurations()
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
})
}
emit('submitted', data)
closeDialog()
} finally {
submitting.value = false
}
}
defineExpose({ openForApp, openForDraft, openForAppWithCopy })
</script>
<style scoped lang="scss">
.oa-start__tip {
margin: 8px 0 0;
color: var(--mint-text-muted, #94a3b8);
font-size: 13px;
}
</style>