import { useState, useEffect } from 'react' import { useNavigate, useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { NavBar, Card, Tag, Button, Dialog, Toast, TextArea } from 'antd-mobile' import { LeftOutline, ClockCircleOutline, CheckCircleOutline, CloseCircleOutline, ExclamationCircleOutline, } from 'antd-mobile-icons' import { approvalApi } from '../../services/api' import { useUserStore } from '@/stores/user' import dayjs from 'dayjs' import styles from './detail.module.css' // 状态配置 const statusConfig: Record = { draft: { color: 'default', icon: , text: '草稿' }, pending: { color: 'warning', icon: , text: '审批中' }, approved: { color: 'success', icon: , text: '已通过' }, rejected: { color: 'danger', icon: , text: '已拒绝' }, cancelled: { color: 'default', icon: , text: '已撤销' }, } // 操作配置 const actionConfig: Record = { submit: { color: '#999', icon: '📝', text: '提交申请' }, approve: { color: '#52c41a', icon: '✅', text: '已同意' }, reject: { color: '#ff4d4f', icon: '❌', text: '已拒绝' }, cancel: { color: '#999', icon: '🔄', text: '已撤销' }, } // 业务类型映射 const businessTypeConfig: Record = { business_trip: '出差申请', expense: '费用报销', leave: '请假申请', purchase: '采购申请', } // 表单字段映射(英文key -> 中文label) const formFieldLabelMap: Record = { reason: '出差事由', itineraries: '行程安排', destination: '目的地', start_date: '开始日期', end_date: '结束日期', estimated_cost: '预计费用', approvers: '审批人', cc_users: '抄送人', attachment: '附件', remark: '备注', } interface ApprovalRecord { id: number node_id: number node_type: string action: string approver_id: number comment: string | null created_at: string approver: { name: string avatar?: string } } interface ApprovalNode { id: number type: string name: string approvers: number[] } interface ApprovalDetail { id: number title: string business_type: string business_id: number status: string created_at: string current_nodes: number[] applicant: { name: string avatar?: string } flow: { name: string nodes?: ApprovalNode[] } form_data: Record records: ApprovalRecord[] } export default function ApprovalDetail() { const { t } = useTranslation() const navigate = useNavigate() const { id } = useParams() const { user } = useUserStore() const [detail, setDetail] = useState(null) const [loading, setLoading] = useState(true) const [commentVisible, setCommentVisible] = useState(false) const [comment, setComment] = useState('') const [actionType, setActionType] = useState<'approve' | 'reject' | 'cancel'>('approve') // 加载详情 const loadDetail = async () => { if (!id) return setLoading(true) try { const data = await approvalApi.getDetail(Number(id)) setDetail(data) } catch (error: any) { Toast.show(error.message || '加载失败') navigate(-1) } finally { setLoading(false) } } useEffect(() => { loadDetail() }, [id]) // 撤销申请 const handleCancel = () => { Dialog.confirm({ content: '确定要撤销此申请吗?', onConfirm: async () => { try { await approvalApi.cancel(Number(id)) Toast.show('已撤销') loadDetail() } catch (error: any) { Toast.show(error.message || '操作失败') } }, }) } // 打开审批意见框 const handleApprove = (action: 'approve' | 'reject') => { console.log('🔘 打开审批弹框:', { action, id }) setActionType(action) setComment('') setCommentVisible(true) } // 提交审批 const handleConfirmApprove = async () => { console.log('📝 提交审批:', { actionType, id, comment }) if (document.activeElement instanceof HTMLElement) { document.activeElement.blur() } try { if (actionType === 'approve') { console.log('✅ 调用同意API:', { id: Number(id), comment }) await approvalApi.approve(Number(id), comment) Toast.show('已同意') } else if (actionType === 'reject') { console.log('❌ 调用拒绝API:', { id: Number(id), comment }) await approvalApi.reject(Number(id), comment) Toast.show('已拒绝') } else if (actionType === 'cancel') { console.log('🔄 调用撤销API:', { id: Number(id) }) await approvalApi.cancel(Number(id)) Toast.show('已撤销') } setCommentVisible(false) loadDetail() } catch (error: any) { console.error('❌ 审批操作失败:', error) Toast.show(error.message || '操作失败') } } // 检查是否是当前审批人 const isCurrentApprover = () => { console.log('🔍 isCurrentApprover 检查:', { hasDetail: !!detail, hasUser: !!user, userId: user?.id, userIdType: typeof user?.id, status: detail?.status, currentNodes: detail?.current_nodes, nodesCount: detail?.flow?.nodes?.length, }) if (!detail || !user) { console.log('❌ 缺少 detail 或 user') return false } if (detail.status !== 'pending') { console.log('❌ 状态不是 pending') return false } // 检查当前用户是否在 current_nodes 对应的节点审批人列表中 const currentNodeIds = detail.current_nodes || [] const nodes = detail.flow?.nodes || [] console.log('🔍 节点信息:', { currentNodeIds, nodes: nodes.map(n => ({ id: n.id, name: n.name, approvers: n.approvers, approversType: typeof n.approvers, approversFirstType: n.approvers?.[0] !== undefined ? typeof n.approvers[0] : 'undefined' })), }) for (const nodeId of currentNodeIds) { const node = nodes.find((n) => n.id === nodeId) console.log(`🔍 检查节点 ${nodeId}:`, { node, approvers: node?.approvers, userId: user.id, userIdType: typeof user.id, isInApprovers: node?.approvers?.includes(user.id), // 尝试类型转换后的检查 isInApproversAsString: node?.approvers?.includes(String(user.id)), isInApproversAsNumber: node?.approvers?.includes(Number(user.id)), }) if (node && node.approvers) { // 尝试多种方式匹配 const matches = node.approvers.some(a => a === user.id || a === String(user.id) || Number(a) === user.id) console.log(`🔍 节点 ${nodeId} 匹配结果:`, { matches }) if (matches) { // 检查是否已审批 - 使用 approver_id 而不是 approver.id const hasApproved = detail.records.some( (r) => r.node_id === nodeId && r.approver_id === user.id ) console.log(`🔍 节点 ${nodeId} 审批状态:`, { hasApproved, records: detail.records.filter(r => r.node_id === nodeId), }) if (!hasApproved) { console.log('✅ 是当前审批人!') return true } } } } console.log('❌ 不是当前审批人') return false } if (loading || !detail) { return
加载中...
} const config = statusConfig[detail.status] || statusConfig.draft const isOwner = detail.applicant?.name === user?.name const canCancel = detail.status === 'pending' && isOwner const canApprove = isCurrentApprover() // 显示审批按钮状态 console.log('🎯 操作按钮状态:', { canCancel, canApprove, isOwner, applicantName: detail.applicant?.name, userName: user?.name, }) // 格式化时间 const formatTime = (timeStr: string) => { return dayjs(timeStr).format('YYYY-MM-DD HH:mm') } // 获取节点类型名称 const getNodeTypeName = (nodeType: string) => { const typeMap: Record = { approver: '审批人', and_approve: '会签', or_approve: '或签', notify: '抄送人', single_approve: '审批人', } return typeMap[nodeType] || nodeType } // 获取节点的审批人姓名 const getNodeApproversNames = (nodeId: number, nodeType: string): string => { // 从审批记录中获取已审批的审批人姓名 const approverRecords = detail.records.filter(r => r.node_id === nodeId && r.action !== 'submit') if (approverRecords.length > 0) { return approverRecords.map(r => r.approver?.name).join('、') } // 未审批的节点,显示节点名称(人名) const nodes = detail.flow?.nodes || [] const node = nodes.find((n) => n.id === nodeId) return node?.name || '待审批' } // 获取节点显示名称(只显示人名) const getNodeDisplayName = (node: any): string => { return getNodeApproversNames(node.id, node.type) } // 获取当前审批人 const getCurrentApprovers = () => { const currentNodeIds = detail.current_nodes || [] const nodes = detail.flow?.nodes || [] const approvers: any[] = [] for (const nodeId of currentNodeIds) { const node = nodes.find((n) => n.id === nodeId) if (node) { approvers.push({ nodeId: node.id, nodeType: node.type, type: getNodeTypeName(node.type), approvers: node.approvers, }) } } return approvers } const currentApprovers = getCurrentApprovers() return (
navigate(-1)}>审批详情
{/* 状态卡片 */}
{detail.title} {config.icon} {config.text}
业务类型: {businessTypeConfig[detail.business_type] || detail.business_type}
申请人: {detail.applicant?.name}
申请时间: {formatTime(detail.created_at)}
审批流程: {detail.flow?.name}
{/* 当前审批节点 */} {detail.status === 'pending' && currentApprovers.length > 0 && ( {currentApprovers.map((item, index) => { const typeLabel = item.nodeType === 'notify' ? '抄送人' : '审批人' const typeClass = item.nodeType === 'notify' ? styles.typeNotify : '' return (
{typeLabel} {getNodeApproversNames(item.nodeId, item.nodeType)}
) })}
)} {/* 表单数据 */} {Object.keys(detail.form_data).length > 0 && (
{Object.entries(detail.form_data) .filter(([key]) => !['approvers', 'cc_users'].includes(key)) .map(([key, value]) => { // 获取字段标签,优先使用映射表,没有则使用原始key const label = formFieldLabelMap[key] || key return (
{label}: {Array.isArray(value) ? value.join(', ') : String(value)}
) })}
)} {/* 审批流程 */} {detail.flow?.nodes && detail.flow.nodes.length > 0 && (
{detail.flow.nodes.map((node, index) => { const isCurrent = detail.current_nodes?.includes(node.id) const records = detail.records.filter(r => r.node_id === node.id && r.action !== 'submit') const hasApproved = records.length > 0 // 判断节点状态 let nodeStatus: 'pending' | 'waiting' | 'done' = 'waiting' if (hasApproved) { nodeStatus = 'done' } else if (isCurrent) { nodeStatus = 'pending' } // 获取节点类型标签 const typeLabel = node.type === 'notify' ? '抄送人' : '审批人' const typeClass = node.type === 'notify' ? styles.typeNotify : styles.typeApprover // 获取显示名称和审批时间 let displayName = node.name || '待定' let approvalTime = '' if (hasApproved && records[0]) { displayName = records[0].approver?.name || displayName approvalTime = formatTime(records[0].created_at) } return (
{nodeStatus === 'done' ? '✓' : nodeStatus === 'pending' ? '⏱' : index + 1}
{typeLabel} {displayName}
{approvalTime && (
{approvalTime}
)} {hasApproved && records[0]?.comment && (
备注: {records[0].comment}
)}
) })}
)} {/* 审批记录 */}
{detail.records && detail.records.length > 0 ? ( detail.records.map((record) => { const recordConfig = actionConfig[record.action] || actionConfig.submit return (
{recordConfig.icon}
{recordConfig.text} {record.approver?.name}
{formatTime(record.created_at)}
{record.comment && (
备注: {record.comment}
)}
) }) ) : (
暂无审批记录
)}
{/* 底部操作栏 */} {(canCancel || canApprove) && (
{canCancel && ( )} {canApprove && ( <> )}
)} {/* 审批意见弹框 */}