@@ -30,7 +30,7 @@ import {
type CrossCheckingOpinion ,
type OpinionActionType
} from '../../api/cross-checking/cross-file-result' ;
import { useFetcher } from '@remix-run/react' ;
import { useFetcher , useNavigate } from '@remix-run/react' ;
// import '../../styles/components/TooltipStyles.css';
/**
@@ -159,6 +159,11 @@ interface ScoringProposal {
document_id : string | number ;
}
interface UserInfo {
id : number ;
[ key : string ] : unknown ;
}
interface ReviewPointsListProps {
reviewPoints : ReviewPoint [ ] ;
statistics : Statistics ;
@@ -167,6 +172,7 @@ interface ReviewPointsListProps {
onStatusChange ? : ( id : string , editAuditStatusId : string | number , status : string , message : string ) = > void ;
scoringProposals? : ScoringProposal [ ] ;
jwtToken? : string ; // 添加JWT token参数
userInfo? : UserInfo ; // 添加用户信息参数
}
/**
@@ -424,7 +430,8 @@ export function ReviewPointsList({
activeReviewPointResultId ,
onReviewPointSelect ,
scoringProposals = [ ] ,
jwtToken
jwtToken ,
userInfo
} : ReviewPointsListProps ) {
// 状态管理
const [ searchText , setSearchText ] = useState ( '' ) ; // 搜索文本
@@ -436,7 +443,7 @@ export function ReviewPointsList({
// 将来可以用于显示相关的评分提案信息
useEffect ( ( ) = > {
if ( scoringProposals && scoringProposals . length > 0 ) {
console . log ( '收到评分提案数据:' , scoringProposals . length , '个提案' ) ;
// console.log('收到评分提案数据:', scoringProposals.length, '个提案');
// 获取提案的evaluation_result_id
const evaluationResultIds = scoringProposals . map ( proposal = > Number ( proposal . evaluation_result_id ) ) ;
setEvaluationResultIds ( evaluationResultIds ) ;
@@ -471,27 +478,31 @@ export function ReviewPointsList({
// 监听fetcher状态变化 - 获取意见列表数据
useEffect ( ( ) = > {
if ( fetcher . data && fetcher . state === 'idle' && opinionListLoading ) {
const data = fetcher . data as {
success? : boolean ;
data ? : {
opinions : CrossCheckingOpinion [ ] ;
total : number ;
} ;
error? : string ;
const data = fetcher . data as {
success? : boolean ;
data ? : {
opinions : CrossCheckingOpinion [ ] ;
total : number ;
pagination ? : {
page : number ;
page_size : number ;
total : number ;
total_pages : number ;
} ;
} ;
error? : string ;
} ;
if ( data . success && data . data ) {
console . log ( '意见列表数据 ' , data . data ) ;
console . log ( 'data.data ' , data . data ) ;
setOpinionListData ( data . data . opinions || [ ] ) ;
setOpinionListTotal ( data . data . total || 0 ) ;
// 使用当前状态值而不是依赖项中的值
setOpinionListCurrentPage ( prev = > prev ) ;
setOpinionListPageSize ( prev = > prev ) ;
if ( data . data . pagination ) {
setOpinionListCurrentPage ( data . data . pagination . page ) ;
setOpinionListPageSize ( data . data . pagination . page_size ) ;
}
} else {
console . error ( '加载意见列表失败:' , data . error ) ;
toastService . error ( data . error || '加载意见列表失败' ) ;
}
setOpinionListLoading ( false ) ;
}
} , [ fetcher . data , fetcher . state , opinionListLoading ] ) ;
@@ -568,12 +579,11 @@ export function ReviewPointsList({
const loadOpinionListData = async ( page : number = 1 , pageSize : number = 10 , documentId? : string | number ) = > {
// 使用传入的documentId或者从selectedReviewPoint获取
const targetDocumentId = documentId || selectedReviewPoint ? . documentId ;
console . log ( '加载意见列表数据' , targetDocumentId ) ;
if ( ! targetDocumentId ) return ;
setOpinionListLoading ( true ) ;
try {
console . log ( '加载意见列表数据' , targetDocumentId , page , pageSize ) ;
// 使用 fetcher 调用路由的 action
const formData = new FormData ( ) ;
@@ -595,8 +605,8 @@ export function ReviewPointsList({
* 打开意见列表模态框
*/
const handleOpenOpinionListModal = ( reviewPoint : ReviewPoint ) = > {
console . log ( '查看reviewPoints ' , reviewPoints ) ;
if ( scoringProposals . length + 1 === 0 ) {
console . log ( '查看reviewPoint' , reviewPoint ) ;
if ( scoringProposals . length === 0 ) {
toastService . warning ( '当前文件尚未有人提出过意见' ) ;
return ;
}
@@ -626,7 +636,7 @@ export function ReviewPointsList({
setPerformingAction ( actionKey ) ;
try {
const response = await performOpinionAction ( { opinionId , action } , jwtToken ) ;
const response = await performOpinionAction ( { opinionId , action } , jwtToken , userInfo as { user_id : number } | undefined ) ;
if ( response . error ) {
toastService . error ( response . error ) ;
@@ -634,12 +644,14 @@ export function ReviewPointsList({
}
toastService . success ( response . data ? . message || '操作成功' ) ;
// console.log('即将重新加载数据');
// 重新加载数据
await loadOpinionListData ( opinionListCurrentPage , opinionListPageSize ) ;
} catch ( error ) {
console . error ( '操作失败:' , error ) ;
toastService . error ( '操作失败,请稍后重试' ) ;
toastService . error ( error instanceof Error ? error . message : '操作失败,请稍后重试' ) ;
} finally {
setPerformingAction ( null ) ;
}
@@ -649,6 +661,7 @@ export function ReviewPointsList({
* 处理意见列表分页变化
*/
const handleOpinionListPageChange = ( page : number ) = > {
setOpinionListCurrentPage ( page ) ;
loadOpinionListData ( page , opinionListPageSize ) ;
} ;
@@ -656,6 +669,7 @@ export function ReviewPointsList({
* 处理意见列表每页大小变化
*/
const handleOpinionListPageSizeChange = ( size : number ) = > {
setOpinionListPageSize ( size ) ;
loadOpinionListData ( 1 , size ) ;
} ;
@@ -2550,7 +2564,7 @@ export function ReviewPointsList({
{
title : "问题描述" ,
key : "problem_message" ,
width : "20 %" ,
width : "18 %" ,
render : ( _ : unknown , record : CrossCheckingOpinion ) = > (
< div className = "text-sm text-left" > { record . problem_message } < / div >
)
@@ -2566,7 +2580,7 @@ export function ReviewPointsList({
{
title : "调整分数" ,
key : "proposed_score" ,
width : "8 %" ,
width : "5 %" ,
align : "center" as const ,
render : ( _ : unknown , record : CrossCheckingOpinion ) = > (
< span className = { ` text-sm font-medium ${ record . proposed_score >= 0 ? 'text-green-600' : 'text-red-600' } ` } >
@@ -2576,8 +2590,8 @@ export function ReviewPointsList({
} ,
{
title : "投票人" ,
key : "voter_count " ,
width : "8 %" ,
key : "votes " ,
width : "25 %" ,
align : "center" as const ,
render : ( _ : unknown , record : CrossCheckingOpinion ) = > {
// 投票类型配置
@@ -2604,7 +2618,6 @@ export function ReviewPointsList({
border : "border border-gray-200"
}
] ;
return (
< div className = "flex flex-col gap-1.5 py-1 min-w-[120px]" >
{ voterGroups . map ( ( group ) = > (
@@ -2633,7 +2646,7 @@ export function ReviewPointsList({
{
title : "意见发起人" ,
key : "proposer" ,
width : "10 %" ,
width : "4 %" ,
render : ( _ : unknown , record : CrossCheckingOpinion ) = > (
< div className = "flex items-center justify-center" >
< span
@@ -2644,6 +2657,14 @@ export function ReviewPointsList({
< / div >
)
} ,
{
title : "发起时间" ,
key : "created_at" ,
width : "18%" ,
render : ( _ : unknown , record : CrossCheckingOpinion ) = > (
< div className = "text-sm text-left" > { record . created_at } < / div >
)
} ,
{
title : "操作" ,
key : "operation" ,
@@ -2652,19 +2673,19 @@ export function ReviewPointsList({
render : ( _ : unknown , record : CrossCheckingOpinion ) = > {
const isPerforming = ( action : string ) = > performingAction === ` ${ record . proposal_id } - ${ action } ` ;
return (
< OpinionActions record = { record } isPerforming = { isPerforming } handleOpinionAction = { handleOpinionAction } / >
< OpinionActions record = { record } isPerforming = { isPerforming } handleOpinionAction = { handleOpinionAction } userInfo = { userInfo as { user_id : number } | undefined } / >
) ;
}
}
] }
dataSource = { opinionListData }
rowKey = "id"
rowKey = "proposal_ id"
emptyText = "暂无意见数据"
className = "opinion-list-table"
/ >
{ /* 分页组件 */ }
{ opinionListTotal > opinionListPageSize && (
{ opinionListTotal > 0 && (
< Pagination
currentPage = { opinionListCurrentPage }
total = { opinionListTotal }
@@ -2673,7 +2694,7 @@ export function ReviewPointsList({
onPageSizeChange = { handleOpinionListPageSizeChange }
showTotal = { true }
showPageSizeChanger = { true }
pageSizeOptions = { [ 10 , 20 , 30 , 50 ] }
pageSizeOptions = { [ 5 , 10 , 20 , 30 , 50 ] }
/ >
) }
< / >
@@ -2686,27 +2707,24 @@ export function ReviewPointsList({
}
// 操作按钮区美化+弹窗确认组件
function OpinionActions ( { record , isPerforming , handleOpinionAction } : {
function OpinionActions ( { record , isPerforming , handleOpinionAction , userInfo } : {
record : CrossCheckingOpinion ;
isPerforming : ( action : string ) = > boolean ;
handleOpinionAction : ( id : string | number , action : OpinionActionType ) = > void ;
userInfo ? : { user_id : number } ;
} ) {
const canVote = record . can_vote !== false ;
const [ showWithdrawModal , setShowWithdrawModal ] = useState ( false ) ;
const [ withdrawType , setWithdrawType ] = useState < 'withdraw_vote' | 'withdraw_opinion' | null > ( null ) ;
const [ showModal , setShowModal ] = useState < null | OpinionActionType > ( null ) ;
const [ countdown , setCountdown ] = useState ( 3 ) ;
const [ counting , setCounting ] = useState ( false ) ;
const handleWithdraw = ( type : 'withdraw_vote' | 'withdraw_opinion' ) = > {
setWithdrawType ( type ) ;
setShowWithdrawModal ( true ) ;
setCountdown ( 3 ) ;
setCounting ( true ) ;
} ;
useEffect ( ( ) = > {
let timer : NodeJS.Timeout ;
if ( showWithdrawModal && counting && countdown > 0 ) {
if (
showModal &&
( showModal === 'withdraw_opinion' || showModal === 'withdraw_vote' ) &&
counting &&
countdown > 0
) {
timer = setTimeout ( ( ) = > {
setCountdown ( ( c ) = > c - 1 ) ;
} , 1000 ) ;
@@ -2714,89 +2732,108 @@ function OpinionActions({ record, isPerforming, handleOpinionAction }: {
setCounting ( false ) ;
}
return ( ) = > clearTimeout ( timer ) ;
} , [ showWithdrawModal , counting , countdown ] ) ;
} , [ showModal , counting , countdown ] ) ;
const handleWithdrawConfirm = ( ) = > {
if ( withdrawType && countdown === 0 ) {
handleOpinionAction ( record . proposal_id , withdrawType ) ;
setShowWithdrawModal ( false ) ;
setWithdrawType ( null ) ;
const handleConfirm = ( ) = > {
if ( showModal === 'withdraw_opinion' || showModal === 'withdraw_vote' ) {
if ( countdown === 0 ) {
handleOpinionAction ( record . proposal_id , showModal ) ;
setShowModal ( null ) ;
setCountdown ( 3 ) ;
setCounting ( false ) ;
}
} else {
// 赞同/反对等操作直接执行
handleOpinionAction ( record . proposal_id , showModal ! ) ;
setShowModal ( null ) ;
setCountdown ( 3 ) ;
setCounting ( false ) ;
}
} ;
const handleWithdrawCancel = ( ) = > {
setShowWithdrawModal ( false ) ;
setWithdrawType ( null ) ;
const handleCancel = ( ) = > {
setShowModal ( null ) ;
setCountdown ( 3 ) ;
setCounting ( false ) ;
} ;
// 判断是否是发起人
const isProposer = userInfo && record . proposer_id === userInfo . user_id ;
return (
< div className = "flex gap-3" >
< Button
type = "default"
className = "bg-green-700 hover:bg-green-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > handleOpinionAction ( record . proposal_id , 'agree' ) }
disabled = { isPerforming ( 'agree' ) || ! canVote }
>
{ isPerforming ( 'agree' ) ? '处理中...' : '赞同' }
< / Button >
< Button
type = "default"
className = "bg-red-700 hover:bg-red-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > handleOpinionAction ( record . proposal_id , 'disagree' ) }
disabled = { isPerforming ( 'disagree' ) || ! canVote }
>
{ isPerforming ( 'disagree' ) ? '处理中...' : '反对' }
< / Button >
{ ( ! canVote || record . is_vote ) && (
{ /* 仅当can_vote为true时显示赞同/反对按钮 */ }
{ record . can_vote && (
< >
< Button
type = "default"
className = "bg-green-700 hover:bg-green-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > { setShowModal ( 'agree' ) ; } }
disabled = { isPerforming ( 'agree' ) }
>
{ isPerforming ( 'agree' ) ? '处理中...' : '赞同' }
< / Button >
< Button
type = "default"
className = "bg-red-700 hover:bg-red-800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > { setShowModal ( 'disagree' ) ; } }
disabled = { isPerforming ( 'disagree' ) }
>
{ isPerforming ( 'disagree' ) ? '处理中...' : '反对' }
< / Button >
< / >
) }
{ /* 仅当can_vote为false时显示撤销投票按钮 */ }
{ ! record . can_vote && (
< Button
type = "default"
className = "bg-yellow-600 hover:bg-yellow-700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > handleWithdraw ( 'withdraw_vote' ) }
onClick = { ( ) = > { setShowModal ( 'withdraw_vote' ) ; setCounting ( true ) ; } }
disabled = { isPerforming ( 'withdraw_vote' ) }
>
{ isPerforming ( 'withdraw_vote' ) ? '处理中...' : '撤销投票' }
< / Button >
) }
{ record . current_user_is_proposer && (
{ /* 仅当是发起人才显示撤销意见按钮 */ }
{ isProposer && (
< Button
type = "default"
className = "bg-yellow-600 hover:bg-yellow -700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > handleWithdraw ( 'withdraw_opinion' ) }
className = "bg-yellow-600 hover:bg-red -700 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { ( ) = > { setShowModal ( 'withdraw_opinion' ) ; setCounting ( true ) ; } }
disabled = { isPerforming ( 'withdraw_opinion' ) }
>
{ isPerforming ( 'withdraw_opinion' ) ? '处理中...' : '撤销意见' }
< / Button >
) }
{ showWithdrawModal && (
{ /* 确认操作模态框 */ }
{ showModal && (
< Modal
isOpen = { showWithdrawModal }
onClose = { handleWithdrawCancel }
title = "确认撤销 "
isOpen = { ! ! showModal }
onClose = { handleCancel }
title = "确认操作 "
size = "small"
className = ""
footer = {
< div className = "flex justify-end gap-3" >
< Button
type = "default"
className = "min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap bg-gray-500 hover:bg-gray-600 text-white shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3"
onClick = { handleWithdrawCancel }
onClick = { handleCancel }
>
取 消
< / Button >
< Button
type = "default"
className = { ` bg-red -700 hover:bg-red -800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3 ${ countdown > 0 ? 'opacity-60 cursor-not-allowed' : '' } ` }
onClick = { handleWithdrawConfirm }
disabled = { countdown > 0 }
className = { ` bg-g reen -700 hover:bg-g reen -800 text-white min-w-[80px] h-14 text-base font-medium rounded-lg flex items-center justify-center whitespace-nowrap shadow-md hover:shadow-lg transition-all duration-200 px-4 py-3 ${ ( showModal === 'withdraw_opinion' || showModal === 'withdraw_vote' ) && countdown > 0 ? 'opacity-60 cursor-not-allowed' : '' } ` }
onClick = { handleConfirm }
disabled = { ( showModal === 'withdraw_opinion' || showModal === 'withdraw_vote' ) && countdown > 0 }
>
{ countdown > 0 ? ` 确认撤销 ( ${ countdown } ) ` : '确认撤销 ' }
{ ( showModal === 'withdraw_opinion' || showModal === 'withdraw_vote' ) && countdown > 0 ? ` 确认( ${ countdown } ) ` : '确认' }
< / Button >
< / div >
}
>
< div className = "flex flex-col items-center justify-center text-base text-gray-700 py-4 text-center" >
< div className = "mb-2" > 确 定 要 撤 销 此 操 作 吗 ? < / div >
< div className = "mb-2" > 确 定 要 进 行 此 操 作 吗 ? < / div >
< div className = "text-sm text-gray-500" > 评 查 点 : < span className = "font-bold text-primary" > { record . evaluation_point_name || record . proposal_id } < / span > < / div >
< / div >
< / Modal >
@@ -2820,6 +2857,8 @@ const openResultModal = (recordId: string) => {
// 交叉评查记录操作按钮组件
export function ActionButtons ( { record } : { record : CrossCheckingRecord } ) {
const navigate = useNavigate ( ) ;
// 根据记录状态确定按钮类型
const getButtonConfig = ( ) = > {
switch ( record . status ) {
@@ -2828,22 +2867,14 @@ export function ActionButtons({ record }: { record: CrossCheckingRecord }) {
text : '去评查' ,
bgColor : 'bg-blue-600' ,
hoverColor : 'hover:bg-blue-700' ,
icon : (
< svg xmlns = "http://www.w3.org/2000/svg" className = "h-4 w-4 mr-1" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" / >
< / svg >
)
icon : < span className = "ri-edit-2-line text-lg mr-1" > < / span >
} ;
case 'in_progress' :
return {
text : '进行中' ,
bgColor : 'bg-gray-500' ,
hoverColor : 'hover:bg-gray-600' ,
icon : (
< svg xmlns = "http://www.w3.org/2000/svg" className = "h-4 w-4 mr-1 animate-pulse" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" / >
< / svg >
)
icon : < span className = "ri-loader-4-line text-lg mr-1 animate-spin" > < / span >
} ;
case 'completed' :
default :
@@ -2851,23 +2882,22 @@ export function ActionButtons({ record }: { record: CrossCheckingRecord }) {
text : '查看结果' ,
bgColor : 'bg-green-600' ,
hoverColor : 'hover:bg-green-700' ,
icon : (
< svg xmlns = "http://www.w3.org/2000/svg" className = "h-4 w-4 mr-1" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" >
< path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" / >
< / svg >
)
icon : < span className = "ri-eye-line text-lg mr-1" > < / span >
} ;
}
} ;
const buttonConfig = getButtonConfig ( ) ;
// 处理按钮点击事件
/**
* 处理按钮点击事件
* 使用React Router的navigate方法替代window.location.href,避免页面刷新
*/
const handleAction = ( ) = > {
switch ( record . status ) {
case 'pending' :
// 跳转到评查页面
window . location . href = ` /review/ ${ record . id } ` ;
// 使用navigate跳转到评查页面,避免页面刷新
navigate ( ` /review/ ${ record . id } ` ) ;
break ;
case 'in_progress' :
// 进行中状态不执行操作