This commit is contained in:
zlwu committed 2026-08-13 14:31:57 +08:00
1 parent 0e1b750457
commit 8c49021d38
93 files changed
+22731 -12107

No files matched your search

@@ -15,14 +15,14 @@
<div class="oa-flow-card__row">
<span class="oa-flow-card__name">{{ item.title }}</span>
<span v-if="statusBadge(item)" class="oa-flow-badge" :class="statusBadge(item)">
{{ statusText(item.status) }}
{{ nodeStatusText(item) }}
</span>
<span v-if="signBadge(item)" class="oa-flow-badge" :class="signBadge(item)">
{{ signBadgeText(item) }}
</span>
<span v-else-if="item.taskType === 'cc'" class="oa-flow-badge cc">抄送</span>
<span v-else-if="item.taskType === 'end'" class="oa-flow-badge mute">结束</span>
<template v-if="item.timestamp">
<template v-if="showNodeTimestamp(item)">
<span class="oa-flow-card__sep">·</span>
<span class="oa-flow-card__meta" :title="item.timestamp">{{ item.timestamp }}</span>
</template>
@@ -66,6 +66,12 @@
{{ line.text }}
</p>
</div>
<div
v-else-if="item.kind === 'terminal' && item.status === 'cancelled' && terminalWithdrawText(item)"
class="oa-person-lines"
>
<p class="oa-person-line is-danger">{{ terminalWithdrawText(item) }}</p>
</div>
<div v-else-if="showNodeComment(item)" class="oa-flow-card__comment">
意见:{{ item.commentText }}
</div>
@@ -134,9 +140,17 @@
return map[s] || s
}
/** 任务节点 cancelled=系统清待办 →「无需处理」;终态 cancelled 仍为「已撤销」 */
function nodeStatusText(item: OaProgressChainItem) {
const s = item.status || ''
if (item.kind !== 'terminal' && s === 'cancelled') return '无需处理'
return statusText(s)
}
/** 徽章色:与 Fresh Mint 语义一致 */
function statusBadge(item: OaProgressChainItem) {
const s = item.status || ''
if (item.kind !== 'terminal' && s === 'cancelled') return 'mute'
if (s === 'approved' || s === 'completed' || s === 'done' || s === 'submitted') return 'ok'
if (s === 'pending' || s === 'running') return 'or'
if (s === 'rejected' || s === 'cancelled' || s === 'returned') return 'danger'
@@ -183,15 +197,31 @@
function showNodeComment(item: OaProgressChainItem): boolean {
// 有按人行时不再展示节点级拼接意见,避免双份
if (personLines(item).length) return false
if (item.kind === 'terminal' && item.status === 'cancelled') return false
return Boolean(item.commentText)
}
function terminalWithdrawText(item: OaProgressChainItem): string {
const name = (item.assignee || '').trim()
const tip = (item.commentText || '').trim() || '发起人撤销'
return name ? `${name} ${tip}` : tip
}
/** 任务节点 cancelled 为系统清待办,不展示时间(时间归终态「已撤销」) */
function showNodeTimestamp(item: OaProgressChainItem): boolean {
const ts = (item.timestamp || '').trim()
if (!ts) return false
if (item.kind !== 'terminal' && item.status === 'cancelled') return false
return true
}
function personChipClass(status?: string) {
const s = status || ''
if (s === 'approved' || s === 'completed') return 'is-ok'
if (s === 'pending' || s === 'running') return 'is-pending'
if (s === 'rejected' || s === 'returned' || s === 'cancelled') return 'is-danger'
if (s === 'done' || s === 'not_reached' || s === 'transferred') return 'is-mute'
if (s === 'rejected' || s === 'returned') return 'is-danger'
if (s === 'done' || s === 'not_reached' || s === 'transferred' || s === 'cancelled')
return 'is-mute'
return ''
}
@@ -201,7 +231,7 @@
if (s === 'rejected') return '已拒绝'
if (s === 'returned') return '已打回'
if (s === 'pending' || s === 'running') return '待处理'
if (s === 'done') return '无需处理'
if (s === 'done' || s === 'cancelled') return '无需处理'
if (s === 'not_reached') return '未到达'
if (s === 'transferred') return '已转交'
return ''
@@ -212,13 +242,17 @@
const statusLabel =
p.status === 'transferred' && target
? `转交给${target}`
: statusText(p.status || '')
const parts = [p.nickName || '', statusLabel, p.timestamp || ''].filter(Boolean)
: personStatusHint(p.status) || statusText(p.status || '')
// cancelled/done 人员行不展示操作时间
const ts =
p.status === 'cancelled' || p.status === 'done' ? '' : String(p.timestamp || '').trim()
const parts = [p.nickName || '', statusLabel, ts].filter(Boolean)
return parts.join(' · ')
}
function dotClass(item: OaProgressChainItem) {
const s = item.status || ''
if (item.kind !== 'terminal' && s === 'cancelled') return ''
if (s === 'approved' || s === 'completed' || s === 'done' || s === 'submitted') return 'ok'
if (s === 'pending' || s === 'running') return 'pending'
if (s === 'rejected' || s === 'cancelled' || s === 'returned') return 'danger'
@@ -129,6 +129,12 @@
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' })
@@ -162,6 +168,12 @@
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> = {}
@@ -242,23 +254,56 @@
}
async function loadFlowPreview(appId: string) {
previewLoading.value = true
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 {
previewLoading.value = false
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)
@@ -335,6 +380,7 @@
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()