|
|
@@ -49,9 +49,30 @@ import {
|
|
|
fetchOaInstanceDraftDetail,
|
|
|
fetchSubmitOaInstanceDraft,
|
|
|
fetchStartOaInstance,
|
|
|
- fetchUserList
|
|
|
+ fetchUserList,
|
|
|
+ fetchCurrentUser,
|
|
|
+ fetchCnHolidayRange
|
|
|
} from '../api/oa.js'
|
|
|
import { prepareMobileFormSchema } from '../oa/schema-prep.js'
|
|
|
+import {
|
|
|
+ applyLeaveKitDurationsToFormData,
|
|
|
+ LEAVE_MANUAL_PREFIX,
|
|
|
+ listLeaveKitBindings,
|
|
|
+ markLeaveDurationManual,
|
|
|
+ applyOvertimeKitDefaults,
|
|
|
+ applyOvertimeKitDurationsToFormData,
|
|
|
+ listOvertimeKitBindings,
|
|
|
+ markOvertimeDurationManual,
|
|
|
+ OVERTIME_MANUAL_PREFIX,
|
|
|
+ applyTripKitDaysToFormData,
|
|
|
+ listTripKits,
|
|
|
+ TRIP_MANUAL_PREFIX,
|
|
|
+ applyConfirmationKitDateLinkage,
|
|
|
+ applyConfirmationKitDefaults,
|
|
|
+ listConfirmationKitBindings,
|
|
|
+ applyResignationKitDefaults,
|
|
|
+ applyOwnerFieldDefaults
|
|
|
+} from '../oa/kit-runtime.js'
|
|
|
import OaFlowPreview from '../oa/components/OaFlowPreview.vue'
|
|
|
|
|
|
const route = useRoute()
|
|
|
@@ -70,9 +91,19 @@ const flowResolved = ref(false)
|
|
|
const flowPendingMessage = ref('必填信息填写后,流程将自动显示')
|
|
|
const initiatorSelectByNode = ref({})
|
|
|
const userOptions = ref([])
|
|
|
+const currentUser = ref(null)
|
|
|
|
|
|
let previewTimer = null
|
|
|
let skipPreviewWatch = false
|
|
|
+let leaveKitSyncing = false
|
|
|
+let leaveKitPrevSnapshot = {}
|
|
|
+let tripKitSyncing = false
|
|
|
+let tripKitPrevSnapshot = {}
|
|
|
+let confirmationKitSyncing = false
|
|
|
+let confirmationKitPrevSnapshot = {}
|
|
|
+let overtimeKitSyncing = false
|
|
|
+let overtimeKitPrevSnapshot = {}
|
|
|
+const overtimeRestMap = {}
|
|
|
|
|
|
function closeToMini(payload) {
|
|
|
try {
|
|
|
@@ -87,6 +118,14 @@ function closeToMini(payload) {
|
|
|
showToast((payload && payload.message) || '已完成(非小程序环境)')
|
|
|
}
|
|
|
|
|
|
+async function loadCurrentUser() {
|
|
|
+ try {
|
|
|
+ currentUser.value = (await fetchCurrentUser()) || null
|
|
|
+ } catch {
|
|
|
+ currentUser.value = null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
async function loadUsers() {
|
|
|
try {
|
|
|
const pageSize = 100
|
|
|
@@ -101,11 +140,13 @@ async function loadUsers() {
|
|
|
page += 1
|
|
|
if (page > 50) break
|
|
|
}
|
|
|
- userOptions.value = raw.map((u) => ({
|
|
|
- id: String(u.id || ''),
|
|
|
- nickName: u.nickName || '',
|
|
|
- userNo: u.userNo || ''
|
|
|
- })).filter((u) => u.id)
|
|
|
+ userOptions.value = raw
|
|
|
+ .map((u) => ({
|
|
|
+ id: String(u.id || ''),
|
|
|
+ nickName: u.nickName || '',
|
|
|
+ userNo: u.userNo || ''
|
|
|
+ }))
|
|
|
+ .filter((u) => u.id)
|
|
|
} catch {
|
|
|
userOptions.value = []
|
|
|
}
|
|
|
@@ -155,7 +196,227 @@ function scheduleFlowPreview() {
|
|
|
}, 300)
|
|
|
}
|
|
|
|
|
|
-async function loadByAppId(appId) {
|
|
|
+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])
|
|
|
+ if (changed || manualKeysChanged || daysFieldChanged) {
|
|
|
+ tripKitSyncing = true
|
|
|
+ formData.value = data
|
|
|
+ queueMicrotask(() => {
|
|
|
+ tripKitSyncing = false
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function syncConfirmationKitDates() {
|
|
|
+ if (confirmationKitSyncing) return
|
|
|
+ if (!formRule.value.length) return
|
|
|
+ const schema = { rule: formRule.value }
|
|
|
+ if (!listConfirmationKitBindings(schema).length) return
|
|
|
+ const curr = { ...formData.value }
|
|
|
+ const { data, changed } = applyConfirmationKitDateLinkage(schema, curr)
|
|
|
+ confirmationKitPrevSnapshot = { ...data }
|
|
|
+ if (changed) {
|
|
|
+ confirmationKitSyncing = true
|
|
|
+ formData.value = data
|
|
|
+ queueMicrotask(() => {
|
|
|
+ confirmationKitSyncing = false
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function ensureOvertimeHolidayRange(startYmd, endYmd) {
|
|
|
+ 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 || '').slice(0, 10)
|
|
|
+ if (key) overtimeRestMap[key] = !!day.isRestDay
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ /* 失败保留已有 map,计算回退周末规则 */
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function isRestDayResolver(ymd) {
|
|
|
+ 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
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function applyKitDefaults(baseData) {
|
|
|
+ let data = { ...(baseData || {}) }
|
|
|
+ const user = currentUser.value
|
|
|
+ data = await applyOwnerFieldDefaults(formRule.value, data, user)
|
|
|
+ data = await applyConfirmationKitDefaults({ rule: formRule.value }, data, user)
|
|
|
+ data = applyResignationKitDefaults({ rule: formRule.value }, data, user)
|
|
|
+ data = applyOvertimeKitDefaults({ rule: formRule.value }, data, user)
|
|
|
+ const linked = applyConfirmationKitDateLinkage({ rule: formRule.value }, data)
|
|
|
+ data = linked.data
|
|
|
+ confirmationKitPrevSnapshot = { ...data }
|
|
|
+ overtimeKitPrevSnapshot = { ...data }
|
|
|
+ leaveKitPrevSnapshot = { ...data }
|
|
|
+ tripKitPrevSnapshot = { ...data }
|
|
|
+ return data
|
|
|
+}
|
|
|
+
|
|
|
+async function loadByAppId(appId, { seedData = null, applyDefaults = true } = {}) {
|
|
|
const detail = await fetchOaTemplateDetail(appId)
|
|
|
appMeta.value = {
|
|
|
id: appId,
|
|
|
@@ -164,6 +425,16 @@ async function loadByAppId(appId) {
|
|
|
const prepared = prepareMobileFormSchema(detail?.formSchemaJson)
|
|
|
formRule.value = prepared.rule
|
|
|
formOption.value = prepared.option
|
|
|
+ if (applyDefaults) {
|
|
|
+ formData.value = await applyKitDefaults(seedData || {})
|
|
|
+ await syncOvertimeKitDurations()
|
|
|
+ } else if (seedData) {
|
|
|
+ formData.value = { ...seedData }
|
|
|
+ leaveKitPrevSnapshot = { ...formData.value }
|
|
|
+ tripKitPrevSnapshot = { ...formData.value }
|
|
|
+ confirmationKitPrevSnapshot = { ...formData.value }
|
|
|
+ overtimeKitPrevSnapshot = { ...formData.value }
|
|
|
+ }
|
|
|
await loadFlowPreview()
|
|
|
}
|
|
|
|
|
|
@@ -171,6 +442,7 @@ async function bootstrap() {
|
|
|
detailLoading.value = true
|
|
|
skipPreviewWatch = true
|
|
|
try {
|
|
|
+ await loadCurrentUser()
|
|
|
const appId = String(route.query.appId || '').trim()
|
|
|
const draftId = String(route.query.draftId || '').trim()
|
|
|
if (draftId) {
|
|
|
@@ -178,17 +450,18 @@ async function bootstrap() {
|
|
|
editingDraftId.value = draftId
|
|
|
const aid = String(d?.appId || '').trim()
|
|
|
if (!aid) throw new Error('草稿缺少 appId')
|
|
|
- formData.value = d.formData && typeof d.formData === 'object' ? { ...d.formData } : {}
|
|
|
initiatorSelectByNode.value =
|
|
|
d.initiatorSelectByNode && typeof d.initiatorSelectByNode === 'object'
|
|
|
? { ...d.initiatorSelectByNode }
|
|
|
: {}
|
|
|
- await loadByAppId(aid)
|
|
|
+ const seed =
|
|
|
+ d.formData && typeof d.formData === 'object' ? { ...d.formData } : {}
|
|
|
+ // 草稿:保留已填值,仅补空的 owner 等默认
|
|
|
+ await loadByAppId(aid, { seedData: seed, applyDefaults: true })
|
|
|
} else if (appId) {
|
|
|
- formData.value = {}
|
|
|
initiatorSelectByNode.value = {}
|
|
|
editingDraftId.value = ''
|
|
|
- await loadByAppId(appId)
|
|
|
+ await loadByAppId(appId, { seedData: {}, applyDefaults: true })
|
|
|
} else {
|
|
|
showToast('缺少 appId')
|
|
|
}
|
|
|
@@ -288,7 +561,20 @@ async function submit() {
|
|
|
}
|
|
|
|
|
|
watch(
|
|
|
- [formData, initiatorSelectByNode],
|
|
|
+ formData,
|
|
|
+ () => {
|
|
|
+ if (skipPreviewWatch) return
|
|
|
+ syncLeaveKitDurations()
|
|
|
+ syncTripKitDurations()
|
|
|
+ syncConfirmationKitDates()
|
|
|
+ void syncOvertimeKitDurations()
|
|
|
+ scheduleFlowPreview()
|
|
|
+ },
|
|
|
+ { deep: true }
|
|
|
+)
|
|
|
+
|
|
|
+watch(
|
|
|
+ initiatorSelectByNode,
|
|
|
() => {
|
|
|
if (skipPreviewWatch) return
|
|
|
scheduleFlowPreview()
|