docs: 整理项目问题文档,修复审批列表数据混乱

- 新增 PROJECT_ISSUES.md 统一管理所有问题和解决方案
- 精简 CLAUDE.md,移除重复的问题记录部分
- 修复审批列表数据混乱问题(React 18 StrictMode + 状态管理)
- 修复首次加载不显示数据问题(requestIdRef 清理时机)
- 修复页码初始值错误(hasMore 默认值)
- 修复后端响应序列化问题(Eloquent 模型转数组)
- 添加审批人和抄送人重复校验
- 修复双 header 问题
- 调整申请页面 padding-bottom

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-02-26 09:12:21 +08:00
parent 214992b163
commit b7919910f1
9 changed files with 559 additions and 220 deletions
@@ -9,7 +9,7 @@ export default function MobileLayout() {
const location = useLocation()
const mainRef = useRef<HTMLDivElement>(null)
const showHeader = location.pathname !== '/login'
const showHeader = false // 所有页面都不显示 Header
const showTabBar = false // 暂时隐藏底部 Tab
// 路由变化时滚动到顶部
+62 -12
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { List, Card, Tag, Tabs, PullToRefresh, InfiniteScroll } from 'antd-mobile'
@@ -49,17 +49,42 @@ export default function ApprovalList() {
const [activeTab, setActiveTab] = useState<'my' | 'pending' | 'approved'>('my')
const [list, setList] = useState<ApprovalItem[]>([])
const [loading, setLoading] = useState(false)
const [hasMore, setHasMore] = useState(true)
const [hasMore, setHasMore] = useState(false) // 改为 false,避免挂载时立即触发加载更多
const [page, setPage] = useState(1)
const currentTabRef = useRef(activeTab) // 用于追踪当前有效的 tab
const loadingRef = useRef(false) // 用于同步追踪 loading 状态
const requestIdRef = useRef<string | null>(null) // 用于追踪当前请求 ID
const mountedRef = useRef(true) // 组件挂载时立即设为 true
// 更新 ref 当 activeTab 变化时
useEffect(() => {
currentTabRef.current = activeTab
}, [activeTab])
// 加载数据 - 接收 tab 参数避免使用旧的 activeTab
const loadData = async (pageNum: number = 1, isRefresh: boolean = false, tab?: string) => {
if (loading) return
// 使用 ref 检查 loading,避免 React 18 StrictMode 双重调用
if (loadingRef.current) {
console.log('⏸️ Request blocked - already loading')
return
}
// 使用传入的 tab 参数,如果没有则使用当前 activeTab
const currentTab = tab || activeTab
console.log('🔍 Loading approval list:', { currentTab, activeTab, tab, pageNum, isRefresh })
const requestId = `${currentTab}-${pageNum}-${Date.now()}`
requestIdRef.current = requestId
console.log('🔍 Loading approval list START:', {
requestId,
currentTab,
activeTab,
tab,
pageNum,
isRefresh,
currentTabRef: currentTabRef.current
})
loadingRef.current = true
setLoading(true)
try {
const result = await approvalApi.getList({
@@ -68,21 +93,36 @@ export default function ApprovalList() {
page_size: 20,
})
console.log('📦 API response:', { type: currentTab, count: result?.list?.length, result })
console.log('📦 API response received:', {
requestId,
type: currentTab,
count: result?.list?.length,
currentTabRefNow: currentTabRef.current
})
// 检查是否是最新的请求
if (requestIdRef.current !== requestId) {
console.log('⏭️ Skipping outdated response:', { requestId, latestRequestId: requestIdRef.current })
return
}
console.log('📦 API response validated - processing data')
const newList = result?.list || []
if (isRefresh || pageNum === 1) {
// 只在当前 tab 与请求的 tab 一致时才更新数据
if (currentTabRef.current === currentTab) {
console.log('✅ Updating list for tab:', currentTab, 'items:', newList.length)
setList(newList)
setHasMore(newList.length >= 20)
setPage(pageNum)
} else {
setList([...list, ...newList])
console.log('⏭️ Skipping update - tab changed from', currentTab, 'to', currentTabRef.current)
}
setHasMore(newList.length >= 20)
setPage(pageNum)
} catch (error: any) {
console.error('Failed to load approvals:', error)
} finally {
loadingRef.current = false
setLoading(false)
}
}
@@ -90,6 +130,7 @@ export default function ApprovalList() {
// 切换标签
const handleTabChange = (key: string) => {
console.log('🔄 Tab changing from', activeTab, 'to', key)
currentTabRef.current = key as 'my' | 'pending' | 'approved' // 立即更新 ref
setActiveTab(key as 'my' | 'pending' | 'approved')
setList([])
setHasMore(true)
@@ -100,17 +141,26 @@ export default function ApprovalList() {
// 刷新
const handleRefresh = async () => {
await loadData(1, true)
await loadData(1, true, activeTab)
}
// 加载更多
const handleLoadMore = async () => {
await loadData(page + 1, false)
await loadData(page + 1, false, activeTab)
}
// 初始加载
useEffect(() => {
mountedRef.current = true
// 强制使用页码 1,忽略当前 page 状态
console.log('🚀 Initial load effect, forcing page=1')
loadData(1, true)
return () => {
console.log('🧹 Cleanup function called')
mountedRef.current = false
// 不清空 requestIdRef,让异步请求能够正常完成处理
}
}, [])
// 查看详情
@@ -2,7 +2,7 @@
min-height: 100vh;
background: #f5f5f5;
padding: 12px;
padding-bottom: 80px;
padding-bottom: 1.5rem;
}
.form {
+17 -2
View File
@@ -96,8 +96,23 @@ export const businessTripApi = {
// ========== 审批 API ==========
export const approvalApi = {
getList: (params: { type?: string; page?: number; page_size?: number }) =>
api.get('/approval/list', { params }),
getList: (params: { type?: string; page?: number; page_size?: number }) => {
console.log('🚀 API Request approval/list:', params)
return api.get('/approval/list', { params })
.then(res => {
console.log('✅ API Response approval/list:', {
requestParams: params,
responseData: res,
listCount: res?.list?.length,
listIds: res?.list?.map((item: any) => ({ id: item.id, title: item.title, status: item.status }))
})
return res
})
.catch(err => {
console.error('❌ API Error approval/list:', err)
throw err
})
},
getDetail: (id: number) => api.get(`/approval/detail/${id}`),