Files
zkxw/web-admin/src/utils/oa-approval-print-flow.ts
T
zlwu b220298f89 chore: 清理历史资料并规范工程目录与开发规则
为减少仓库噪声和重复规则,整理当前开发入口并移除已废弃资料。

- 精简根 AGENTS,补充四个应用规则与 README,统一规范入口
- 归整部署文件、SOP 原稿和参考资源,修正迁移后的脚本路径
- 取消虚拟环境、工具状态、业务导出及备份的跟踪,本机所需原件保留
- 移除停用的 graphify 配置、脚本与产物
- 删除历史业务文档、执行计划、OpenSpec 业务变更和移动 UI 原型
- 清理失效引用,历史细胞产品导入改为必须显式指定 --file

验证:git diff --cached --check、OpenSpec 严格校验、导入参数隔离检查 3/3 通过;
迁移文件完整性和现行链接已核对。未执行部署、业务数据导入或服务启停。
2026-10-29 23:08:29 +08:00

199 lines
6.2 KiB
TypeScript

/**
* OA 打印「审批流程」行组装(无 Vue/canvas 副作用,便于单测)
*/
import { formatProgressPersonLines } from '@/utils/oa-progress-person-lines'
export type OaPrintPersonLine = {
left: string
right: string
}
export type OaPrintFlowItem = {
left: string
right: string
/** @deprecated 多人节点改用 personLines;单人/发起人可保留 */
comment?: string
/** 或签/会签按人行:左侧文案 + 右侧时间(右对齐) */
personLines?: OaPrintPersonLine[]
}
const STATUS_TEXT: Record<string, string> = {
pending: '待处理',
running: '进行中',
approved: '已同意',
rejected: '已拒绝',
completed: '已完成',
cancelled: '已撤销',
done: '已结束',
submitted: '已提交',
not_reached: '未到达',
returned: '已打回',
transferred: '已转交'
}
function statusText(s: string) {
return STATUS_TEXT[s] || s || '-'
}
/** 任务节点 cancelled=系统清待办 →「无需处理」;终态 cancelled 仍为「已撤销」 */
function progressNodeStatusText(p: any): string {
const kind = String(p?.kind || '')
const s = String(p?.status || '').trim()
if (kind !== 'terminal' && s === 'cancelled') return '无需处理'
return statusText(s)
}
function peopleDisplayNames(p: any): string[] {
const people = Array.isArray(p?.people) ? p.people : []
const fromPeople = people
.map((x: any) => String(x?.nickName || x?.id || '').trim())
.filter(Boolean)
if (fromPeople.length) return fromPeople
const name = String(p?.assigneeUserName || p?.assigneeUserId || '').trim()
return name ? [name] : []
}
function progressPersonLinesFromItem(p: any): OaPrintPersonLine[] {
const people = Array.isArray(p?.people) ? p.people : []
if (!people.length) return []
return formatProgressPersonLines(people).map((x) => ({
left: x.body || x.text,
right: x.timestamp || ''
}))
}
function isCcProgressItem(p: any): boolean {
const taskType = String(p?.taskType || p?.task_type || '').trim()
const signMode = String(p?.signMode || p?.sign_mode || '').trim()
const title = String(p?.title || p?.nodeName || '').trim()
return (
taskType === 'cc' ||
signMode === 'cc' ||
title === '抄送人' ||
/^抄送/.test(title)
)
}
/**
* 从 progress 组装审批流程行;抄送节点聚合为「抄送A, B, C」对齐钉钉导出。
* 若 progress 漏抄送,则用 tasks 兜底补一行。
*/
export function buildFlowItems(detail: any): OaPrintFlowItem[] {
const list = Array.isArray(detail?.progress) ? detail.progress : []
const items: OaPrintFlowItem[] = []
let ccBuf: { names: string[]; ts: string } | null = null
const flushCc = () => {
if (!ccBuf) return
const uniq = Array.from(new Set(ccBuf.names.map((n) => n.trim()).filter(Boolean)))
items.push({
left: uniq.length ? `抄送: ${uniq.join(', ')}` : '抄送:',
right: ccBuf.ts || ''
})
ccBuf = null
}
for (const p of list) {
const kind = String(p.kind || '')
const title = String(p.title || '-').trim()
const names = peopleDisplayNames(p)
const name = names.join(', ')
const ts = String(p.timestamp || '').trim()
const comment = String(p.commentText || '').trim()
const st = progressNodeStatusText(p)
const signMode = String(p.signMode || p.sign_mode || '')
const signLabel = signMode === 'and' ? '会签' : signMode === 'or' ? '或签' : ''
if (isCcProgressItem(p)) {
if (!ccBuf) ccBuf = { names: [], ts }
if (names.length) ccBuf.names.push(...names)
if (ts) ccBuf.ts = ts
continue
}
flushCc()
if (kind === 'submit') {
items.push({
left: name ? `【${title}】 ${name} ${st}` : `【${title}】 ${st}`,
right: ts,
comment: comment || undefined
})
continue
}
if (kind === 'terminal') {
if (String(p.status || '') === 'returned' || title === '已打回') {
continue
}
const withdrawName = String(p.assigneeUserName || p.assignee || '').trim()
const withdrawTip = comment || (String(p.status || '') === 'cancelled' ? '发起人撤销' : '')
const leftExtra =
String(p.status || '') === 'cancelled' && (withdrawName || withdrawTip)
? ` ${[withdrawName, withdrawTip].filter(Boolean).join(' ')}`
: ''
items.push({
left: `${title || st}${leftExtra}`,
right: ts
})
continue
}
if (kind === 'pending_node') {
const personLines = progressPersonLinesFromItem(p)
const head = signLabel ? `【${title}·${signLabel}】` : `【${title}】`
items.push({
left: personLines.length
? `${head} ${st}`
: name
? `${head} ${name} ${st}`
: `${head} ${st}`,
right: personLines.length ? '' : ts,
personLines: personLines.length ? personLines : undefined
})
continue
}
const head = signLabel ? `【${title}·${signLabel}】` : `【${title}】`
const personLines = progressPersonLinesFromItem(p)
if (personLines.length) {
items.push({
left: `${head} ${st}`,
right: '',
personLines
})
continue
}
items.push({
left: name ? `${head} ${name} ${st}` : `${head} ${st}`,
right: ts,
comment: comment || undefined
})
}
flushCc()
const hasCcLine = items.some((it) => it.left.startsWith('抄送'))
if (!hasCcLine) {
const tasks = Array.isArray(detail?.tasks) ? detail.tasks : []
const ccTasks = tasks.filter((t: any) => {
const tt = String(t?.taskType || t?.task_type || '').trim()
const sm = String(t?.signMode || t?.sign_mode || '').trim()
return tt === 'cc' || sm === 'cc'
})
if (ccTasks.length) {
const names = ccTasks
.map((t: any) =>
String(t?.assigneeUserName || t?.assignee_user_name || t?.assigneeUserId || '').trim()
)
.filter(Boolean)
const uniq = Array.from(new Set(names))
let ts = ''
for (const t of ccTasks) {
const tTs = String(t?.updateTime || t?.createTime || '').trim()
if (tTs && (!ts || tTs > ts)) ts = tTs
}
if (uniq.length) {
items.push({ left: `抄送: ${uniq.join(', ')}`, right: ts })
}
}
}
return items
}