fix: 修复快速审批页面路由和错误处理
修复内容: 1. router/index.tsx - 将 QuickAction 路由移到 MobileLayout 外部 2. quick-action.tsx - 修复导入错误,增强错误处理和调试信息 问题分析: - QuickAction 不需要 MobileLayout,应该作为独立页面 - 添加详细的调试日志便于排查问题 - 增强页面关闭逻辑(支持多种方式) 工作流程: 1. 用户点击飞书消息按钮 2. 打开快速审批页面 3. 检查登录状态,未登录则跳转登录页 4. 已登录则自动执行审批操作 5. 显示操作结果 6. 尝试关闭页面或跳转到列表 相关文件: - frontend/src/router/index.tsx - frontend/src/pages/approval/quick-action.tsx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,81 +1,157 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { SpinLoading, Toast } from 'antd-mobile'
|
import { SpinLoading, Toast } from 'antd-mobile'
|
||||||
import { approvalApi } from '@/services/api'
|
import { approvalApi } from '@/services/api'
|
||||||
|
|
||||||
export default function QuickAction() {
|
export default function QuickAction() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [status, setStatus] = useState('processing')
|
const [status, setStatus] = useState('processing')
|
||||||
|
const [message, setMessage] = useState('处理中...')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('🚀 QuickAction 页面加载')
|
||||||
|
|
||||||
const processAction = async () => {
|
const processAction = async () => {
|
||||||
try {
|
try {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
const action = urlParams.get('action') as 'approve' | 'reject' | null
|
const action = urlParams.get('action')
|
||||||
const orderId = urlParams.get('order_id')
|
const orderId = urlParams.get('order_id')
|
||||||
const token = localStorage.getItem('token')
|
const token = localStorage.getItem('token')
|
||||||
|
|
||||||
console.log('🚀 快速审批:', { action, orderId, hasToken: !!token })
|
console.log('🚀 快速审批参数:', { action, orderId, hasToken: !!token })
|
||||||
|
|
||||||
if (!action || !orderId) {
|
if (!action || !orderId) {
|
||||||
setStatus('error')
|
setStatus('error')
|
||||||
|
setMessage('参数错误')
|
||||||
Toast.show('参数错误')
|
Toast.show('参数错误')
|
||||||
setTimeout(() => navigate('/login', { replace: true }), 1500)
|
setTimeout(() => {
|
||||||
|
window.location.href = '/login'
|
||||||
|
}, 2000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否登录
|
// 检查是否登录
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.log('⚠️ 未登录,跳转到登录页')
|
console.log('⚠️ 未登录')
|
||||||
setStatus('unauthorized')
|
setStatus('unauthorized')
|
||||||
|
setMessage('请先登录')
|
||||||
Toast.show('请先登录')
|
Toast.show('请先登录')
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname + window.location.search)
|
const currentUrl = window.location.pathname + window.location.search
|
||||||
}, 1500)
|
window.location.href = '/login?redirect=' + encodeURIComponent(currentUrl)
|
||||||
|
}, 2000)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setStatus('processing')
|
setStatus('processing')
|
||||||
|
setMessage('正在执行审批...')
|
||||||
|
|
||||||
// 执行审批操作
|
// 执行审批操作
|
||||||
|
console.log('📝 执行审批操作:', action, 'order_id:', orderId)
|
||||||
|
|
||||||
|
let result
|
||||||
if (action === 'approve') {
|
if (action === 'approve') {
|
||||||
console.log('✅ 执行通过操作')
|
console.log('✅ 调用 approve API')
|
||||||
await approvalApi.approve(Number(orderId), '')
|
result = await approvalApi.approve(Number(orderId), '')
|
||||||
Toast.show('已通过')
|
setMessage('已通过')
|
||||||
} else if (action === 'reject') {
|
} else if (action === 'reject') {
|
||||||
console.log('❌ 执行拒绝操作')
|
console.log('❌ 调用 reject API')
|
||||||
await approvalApi.reject(Number(orderId), '已拒绝')
|
result = await approvalApi.reject(Number(orderId), '已拒绝')
|
||||||
Toast.show('已拒绝')
|
setMessage('已拒绝')
|
||||||
|
} else {
|
||||||
|
throw new Error('未知操作类型')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('✅ 审批操作完成:', result)
|
||||||
setStatus('success')
|
setStatus('success')
|
||||||
|
|
||||||
// 操作成功后关闭页面(在飞书客户端中)
|
// 操作成功后关闭页面
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('✅ 操作完成,关闭页面')
|
console.log('✅ 操作完成,尝试关闭页面')
|
||||||
// 在飞书客户端中,关闭当前页面
|
|
||||||
if ((window as any).tt && (window as any).tt.close) {
|
// 尝试关闭页面(在飞书客户端或新窗口中)
|
||||||
(window as any).tt.close()
|
try {
|
||||||
|
// 尝试飞书关闭方法
|
||||||
|
if ((window as any).tt && typeof (window as any).tt.close === 'function') {
|
||||||
|
console.log('🔒 使用 tt.close() 关闭页面')
|
||||||
|
;(window as any).tt.close()
|
||||||
|
} else if ((window as any).webkit && typeof (window as any).webkit.messageHandlers.postMessage === 'object') {
|
||||||
|
console.log('📱 尝试 iOS WKWebView 关闭')
|
||||||
|
;(window as any).webkit.messageHandlers.postMessage({ action: 'close' })
|
||||||
|
} else if (typeof window.close === 'function') {
|
||||||
|
console.log('🌐 使用 window.close() 关闭')
|
||||||
|
window.close()
|
||||||
} else {
|
} else {
|
||||||
// 其他情况跳转到审批列表
|
console.log('📋 无法关闭页面,跳转到审批列表')
|
||||||
navigate('/approval', { replace: true })
|
navigate('/approval', { replace: true })
|
||||||
}
|
}
|
||||||
}, 1000)
|
} catch (e) {
|
||||||
|
console.log('⚠️ 关闭页面失败,跳转到列表')
|
||||||
|
navigate('/approval', { replace: true })
|
||||||
|
}
|
||||||
|
}, 1500)
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('❌ 操作失败:', error)
|
console.error('❌ 操作失败:', error)
|
||||||
setStatus('error')
|
setStatus('error')
|
||||||
Toast.show(error.message || '操作失败')
|
setMessage('操作失败: ' + (error.message || '未知错误'))
|
||||||
setTimeout(() => navigate('/approval', { replace: true }), 2000)
|
Toast.show('操作失败: ' + (error.message || '未知错误'))
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate('/approval', { replace: true })
|
||||||
|
}, 2000)
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processAction()
|
// 延迟一点执行,确保页面完全加载
|
||||||
}, [])
|
setTimeout(processAction, 100)
|
||||||
|
}, [navigate])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
background: '#f5f5f5',
|
||||||
|
padding: '20px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{loading && (
|
||||||
|
<>
|
||||||
|
<SpinLoading color='primary' />
|
||||||
|
<p style={{ marginTop: '16px', color: '#999' }}>{message}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'success' && (
|
||||||
|
<>
|
||||||
|
<div style={{ fontSize: '64px' }}>✅</div>
|
||||||
|
<p style={{ marginTop: '16px', color: '#52c41a', fontSize: '18px' }}>{message}</p>
|
||||||
|
<p style={{ fontSize: '14px', color: '#999' }}>页面即将关闭...</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'error' && (
|
||||||
|
<>
|
||||||
|
<div style={{ fontSize: '64px' }}>❌</div>
|
||||||
|
<p style={{ marginTop: '16px', color: '#ff4d4f', fontSize: '18px' }}>{message}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{status === 'unauthorized' && (
|
||||||
|
<>
|
||||||
|
<div style={{ fontSize: '64px }}>🔒</div>
|
||||||
|
<p style={{ marginTop: '16px', color: '#ff8800', fontSize: '18px' }}>需要登录</p>
|
||||||
|
<p style={{ fontSize: '14px', color: '#999' }}>正在跳转到登录页面...</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ const router = createBrowserRouter([
|
|||||||
{ path: 'business-trip/:id', element: <BusinessTripDetail /> },
|
{ path: 'business-trip/:id', element: <BusinessTripDetail /> },
|
||||||
{ path: 'approval', element: <ApprovalList /> },
|
{ path: 'approval', element: <ApprovalList /> },
|
||||||
{ path: 'approval/:id', element: <ApprovalDetail /> },
|
{ path: 'approval/:id', element: <ApprovalDetail /> },
|
||||||
{ path: 'approval/quick-action', element: <QuickAction /> },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ path: '/login', element: <Login /> },
|
{ path: '/login', element: <Login /> },
|
||||||
{ path: '/login-simple', element: <SimpleLogin /> },
|
{ path: '/login-simple', element: <SimpleLogin /> },
|
||||||
{ path: '/debug-feishu', element: <DebugFeishu /> },
|
{ path: '/debug-feishu', element: <DebugFeishu /> },
|
||||||
|
{ path: '/approval/quick-action', element: <QuickAction /> },
|
||||||
{ path: '*', element: <Navigate to='/login' replace /> },
|
{ path: '*', element: <Navigate to='/login' replace /> },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user