添加交叉评查任务的文档列表,评查详情的意见列表
This commit is contained in:
@@ -21,7 +21,18 @@ import { toastService } from '../ui/Toast';
|
||||
import { createPortal } from 'react-dom'; // 导入React Portal API,用于将组件渲染到DOM树的不同位置
|
||||
import { Tooltip } from '../ui/Tooltip';
|
||||
import { Modal } from '../ui/Modal';
|
||||
import { submitCrossCheckingOpinion, type SubmitOpinionRequest } from '../../api/cross-checking/cross-file-result';
|
||||
import { Table } from '../ui/Table';
|
||||
import { Pagination } from '../ui/Pagination';
|
||||
import { Button } from '../ui/Button';
|
||||
import { LoadingIndicator } from '../ui/SkeletonScreen';
|
||||
import {
|
||||
submitCrossCheckingOpinion,
|
||||
getCrossCheckingOpinions,
|
||||
performOpinionAction,
|
||||
type SubmitOpinionRequest,
|
||||
type CrossCheckingOpinion,
|
||||
type OpinionActionType
|
||||
} from '../../api/cross-checking/cross-file-result';
|
||||
// import '../../styles/components/TooltipStyles.css';
|
||||
|
||||
/**
|
||||
@@ -106,6 +117,8 @@ export interface ReviewPoint {
|
||||
actionContent?: string;
|
||||
failMessage?: string;
|
||||
passMessage?: string;
|
||||
score?: number; // 评查点满分
|
||||
finalScore?: number; // 评查点已获得分数
|
||||
evaluationConfig?: {
|
||||
rules?: Array<{
|
||||
type: string;
|
||||
@@ -445,6 +458,15 @@ export function ReviewPointsList({
|
||||
});
|
||||
const [isSubmittingOpinion, setIsSubmittingOpinion] = useState(false);
|
||||
|
||||
// 意见列表模态框相关状态
|
||||
const [isOpinionListModalOpen, setIsOpinionListModalOpen] = useState(false);
|
||||
const [opinionListData, setOpinionListData] = useState<CrossCheckingOpinion[]>([]);
|
||||
const [opinionListLoading, setOpinionListLoading] = useState(false);
|
||||
const [opinionListTotal, setOpinionListTotal] = useState(0);
|
||||
const [opinionListCurrentPage, setOpinionListCurrentPage] = useState(1);
|
||||
const [opinionListPageSize, setOpinionListPageSize] = useState(10);
|
||||
const [performingAction, setPerformingAction] = useState<string | null>(null);
|
||||
|
||||
// 存放评查点ID与有效页码的映射
|
||||
const [effectivePages, setEffectivePages] = useState<Record<string, number>>({});
|
||||
|
||||
@@ -491,6 +513,110 @@ export function ReviewPointsList({
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 加载意见列表数据
|
||||
*/
|
||||
const loadOpinionListData = async (page: number = 1, pageSize: number = 10) => {
|
||||
console.log('加载意见列表数据', selectedReviewPoint);
|
||||
if (!selectedReviewPoint?.documentId) return;
|
||||
|
||||
setOpinionListLoading(true);
|
||||
try {
|
||||
console.log('加载意见列表数据', selectedReviewPoint.documentId, page, pageSize);
|
||||
const response = await getCrossCheckingOpinions(selectedReviewPoint.documentId, page, pageSize);
|
||||
|
||||
console.log('意见列表数据', response);
|
||||
if (response.error) {
|
||||
toastService.error(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
setOpinionListData(response.data?.opinions || []);
|
||||
setOpinionListTotal(response.data?.total || 0);
|
||||
setOpinionListCurrentPage(page);
|
||||
setOpinionListPageSize(pageSize);
|
||||
} catch (error) {
|
||||
console.error('加载意见列表失败:', error);
|
||||
toastService.error('加载意见列表失败');
|
||||
} finally {
|
||||
setOpinionListLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开意见列表模态框
|
||||
*/
|
||||
const handleOpenOpinionListModal = (reviewPoint: ReviewPoint) => {
|
||||
console.log('查看reviewPoints', reviewPoints);
|
||||
if (scoringProposals.length+1 === 0) {
|
||||
toastService.warning('当前文件尚未有人提出过意见');
|
||||
return;
|
||||
}
|
||||
setSelectedReviewPoint(reviewPoint);
|
||||
setIsOpinionListModalOpen(true);
|
||||
console.log('打开意见列表模态框');
|
||||
loadOpinionListData(1, 10);
|
||||
};
|
||||
|
||||
/**
|
||||
* 关闭意见列表模态框
|
||||
*/
|
||||
const handleCloseOpinionListModal = () => {
|
||||
setIsOpinionListModalOpen(false);
|
||||
setOpinionListData([]);
|
||||
setOpinionListTotal(0);
|
||||
setOpinionListCurrentPage(1);
|
||||
setOpinionListPageSize(10);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理意见操作(赞同、反对、撤销投票、撤销意见)
|
||||
*/
|
||||
const handleOpinionAction = async (opinionId: string | number, action: OpinionActionType) => {
|
||||
const actionKey = `${opinionId}-${action}`;
|
||||
setPerformingAction(actionKey);
|
||||
|
||||
try {
|
||||
const response = await performOpinionAction({ opinionId, action });
|
||||
|
||||
if (response.error) {
|
||||
toastService.error(response.error);
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.success(response.data?.message || '操作成功');
|
||||
|
||||
// 重新加载数据
|
||||
await loadOpinionListData(opinionListCurrentPage, opinionListPageSize);
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error);
|
||||
toastService.error('操作失败,请稍后重试');
|
||||
} finally {
|
||||
setPerformingAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理意见列表分页变化
|
||||
*/
|
||||
const handleOpinionListPageChange = (page: number) => {
|
||||
loadOpinionListData(page, opinionListPageSize);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理意见列表每页大小变化
|
||||
*/
|
||||
const handleOpinionListPageSizeChange = (size: number) => {
|
||||
loadOpinionListData(1, size);
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新意见列表
|
||||
*/
|
||||
const handleRefreshOpinionList = () => {
|
||||
loadOpinionListData(opinionListCurrentPage, opinionListPageSize);
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交意见
|
||||
*/
|
||||
@@ -2110,7 +2236,12 @@ export function ReviewPointsList({
|
||||
<>
|
||||
<div className="relative">
|
||||
{/* 悬浮的意见数量显示 - 固定在左侧 */}
|
||||
<div className="absolute left-[-35px] top-16 z-10 group cursor-pointer">
|
||||
<button
|
||||
className="absolute left-[-35px] top-16 z-10 group cursor-pointer"
|
||||
onClick={() => handleOpenOpinionListModal(reviewPoints[0])}
|
||||
type="button"
|
||||
aria-label="查看意见列表"
|
||||
>
|
||||
{/* 默认状态:竖向排列,窄宽度 */}
|
||||
<div className="flex flex-col items-center bg-blue-50 px-2 py-2 rounded-lg border border-blue-200 shadow-md transition-all duration-300 group-hover:scale-0 group-hover:opacity-0 origin-top-right">
|
||||
<i className="ri-chat-1-line text-blue-600 text-base"></i>
|
||||
@@ -2132,7 +2263,7 @@ export function ReviewPointsList({
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div className="review-points-panel select-text">
|
||||
<TooltipPortal />
|
||||
@@ -2292,7 +2423,9 @@ export function ReviewPointsList({
|
||||
{/* 扣分 */}
|
||||
<div>
|
||||
<label htmlFor="deduction-score" className="block text-sm font-medium text-gray-700 mb-2">
|
||||
扣分 <span className="text-red-500">*</span>
|
||||
评分(+/-)
|
||||
<span className="text-red-500 ml-1">*</span>
|
||||
<span className="text-xs text-gray-500 ml-1">该评查点满分 {selectedReviewPoint?.score} 分,已获得 {selectedReviewPoint?.finalScore} 分</span>
|
||||
</label>
|
||||
<input
|
||||
id="deduction-score"
|
||||
@@ -2300,23 +2433,201 @@ export function ReviewPointsList({
|
||||
value={opinionForm.deductionScore}
|
||||
onChange={(e) => {
|
||||
const value = parseFloat(e.target.value);
|
||||
if (!isNaN(value) && value >= 0) {
|
||||
if (!isNaN(value)) {
|
||||
// 限制到1位小数
|
||||
const roundedValue = Math.round(value * 10) / 10;
|
||||
handleOpinionFormChange('deductionScore', roundedValue);
|
||||
}
|
||||
}}
|
||||
step="0.1"
|
||||
min="0"
|
||||
min="-100"
|
||||
max="100"
|
||||
placeholder="0.0"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:ring-1 focus:ring-green-700 focus:border-green-700 focus:outline-none"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">分数最多保留1位小数</p>
|
||||
<p className="mt-1 text-xs text-gray-500">可以加分,也可以减分,最多保留1位小数</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
|
||||
{/* 意见列表模态框 */}
|
||||
<Modal
|
||||
isOpen={isOpinionListModalOpen}
|
||||
onClose={handleCloseOpinionListModal}
|
||||
title="意见列表"
|
||||
size="full"
|
||||
className="opinion-list-modal"
|
||||
>
|
||||
<div className="px-6 py-4">
|
||||
{/* 刷新按钮 */}
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<i className="ri-chat-1-line text-primary text-lg mr-2"></i>
|
||||
<span className="text-sm text-secondary">共有</span>
|
||||
<span className="text-base font-normal text-primary ml-1 mr-1">{opinionListTotal}</span>
|
||||
<span className="text-sm text-secondary">条意见</span>
|
||||
</div>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
icon="ri-refresh-line"
|
||||
onClick={handleRefreshOpinionList}
|
||||
disabled={opinionListLoading}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{opinionListLoading ? (
|
||||
<div className="py-8">
|
||||
<LoadingIndicator text="正在加载意见列表..." />
|
||||
</div>
|
||||
) : opinionListData.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
暂无意见数据
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Table
|
||||
columns={[
|
||||
{
|
||||
title: "审查点名称",
|
||||
key: "audit_point",
|
||||
width: "15%",
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => (
|
||||
<div className="text-sm">{record.audit_point}</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "发现问题",
|
||||
key: "found_issue",
|
||||
width: "20%",
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => (
|
||||
<div className="text-sm text-left">{record.found_issue}</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "审查意见",
|
||||
key: "audit_opinion",
|
||||
width: "25%",
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => (
|
||||
<div className="text-sm text-left">{record.audit_opinion}</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "评分",
|
||||
key: "deduction_score",
|
||||
width: "8%",
|
||||
align: "center" as const,
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => (
|
||||
<span className={`text-sm font-medium ${record.deduction_score >= 0 ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{record.deduction_score > 0 ? '+' : ''}{record.deduction_score}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "投票人",
|
||||
key: "voter_count",
|
||||
width: "8%",
|
||||
align: "center" as const,
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => (
|
||||
<span className="text-sm">{record.voter_count}</span>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "意见发起人",
|
||||
key: "proposer_name",
|
||||
width: "10%",
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => (
|
||||
<div className="text-sm">{record.proposer_name}</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
width: "14%",
|
||||
align: "center" as const,
|
||||
render: (_: unknown, record: CrossCheckingOpinion) => {
|
||||
const isPerforming = (action: string) => performingAction === `${record.id}-${action}`;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{/* 根据is_vote字段显示不同按钮 */}
|
||||
{!record.is_vote ? (
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
onClick={() => handleOpinionAction(record.id, 'agree')}
|
||||
disabled={isPerforming('agree')}
|
||||
className="text-green-600 border-green-600 hover:bg-green-50"
|
||||
>
|
||||
{isPerforming('agree') ? '处理中...' : '赞同'}
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
onClick={() => handleOpinionAction(record.id, 'disagree')}
|
||||
disabled={isPerforming('disagree')}
|
||||
className="text-red-600 border-red-600 hover:bg-red-50"
|
||||
>
|
||||
{isPerforming('disagree') ? '处理中...' : '反对'}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
onClick={() => handleOpinionAction(record.id, 'withdraw_vote')}
|
||||
disabled={isPerforming('withdraw_vote')}
|
||||
className="text-orange-600 border-orange-600 hover:bg-orange-50"
|
||||
>
|
||||
{isPerforming('withdraw_vote') ? '处理中...' : '撤销投票'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* 如果当前用户是意见发起人,显示撤销意见按钮 */}
|
||||
{record.current_user_is_proposer && (
|
||||
<Button
|
||||
type="danger"
|
||||
size="small"
|
||||
onClick={() => handleOpinionAction(record.id, 'withdraw_opinion')}
|
||||
disabled={isPerforming('withdraw_opinion')}
|
||||
>
|
||||
{isPerforming('withdraw_opinion') ? '处理中...' : '撤销意见'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
]}
|
||||
dataSource={opinionListData}
|
||||
rowKey="id"
|
||||
emptyText="暂无意见数据"
|
||||
className="opinion-list-table"
|
||||
/>
|
||||
|
||||
{/* 分页组件 */}
|
||||
{opinionListTotal > opinionListPageSize && (
|
||||
<Pagination
|
||||
currentPage={opinionListCurrentPage}
|
||||
total={opinionListTotal}
|
||||
pageSize={opinionListPageSize}
|
||||
onChange={handleOpinionListPageChange}
|
||||
onPageSizeChange={handleOpinionListPageSizeChange}
|
||||
showTotal={true}
|
||||
showPageSizeChanger={true}
|
||||
pageSizeOptions={[10, 20, 30, 50]}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user