feat: fix the problem in the Operator Market frontend pages

This commit is contained in:
root
2025-12-29 11:38:47 +08:00
parent 29e4a333a9
commit 844add27ea
213 changed files with 45547 additions and 45537 deletions

View File

@@ -1,407 +1,407 @@
import { useState, useEffect } from "react";
import { Button, Card, Badge, Input, Typography, Breadcrumb } from "antd";
import {
LeftOutlined,
RightOutlined,
SaveOutlined,
ScissorOutlined,
AimOutlined,
CalendarOutlined,
FileTextOutlined,
StarFilled,
DatabaseOutlined,
} from "@ant-design/icons";
import { mockTasks, presetEvaluationDimensions } from "@/mock/evaluation";
import { useNavigate } from "react-router";
import DetailHeader from "@/components/DetailHeader";
const { TextArea } = Input;
const { Title } = Typography;
// 生成切片内容
const generateSliceContent = (index: number) => {
const contents = [
"用户咨询产品退换货政策的相关问题,希望了解具体的退货流程和时间限制。客服详细解释了7天无理由退货政策,包括商品需要保持原包装完整的要求。这个回答涵盖了用户关心的主要问题,提供了明确的时间限制和条件说明。",
"客服回复关于质量问题商品的处理方式,说明15天内免费换货服务,并承诺承担相关物流费用。用户对此表示满意,认为这个政策很合理。回答中明确区分了质量问题和非质量问题的不同处理方式。",
"用户询问特殊商品的退换货政策,客服解释个人定制商品不支持退货的规定,并建议用户在购买前仔细确认商品信息。这个回答帮助用户理解了特殊商品的限制条件。",
"关于退货流程的详细说明,客服介绍了在线申请退货的步骤,包括订单页面操作和快递上门取件服务。整个流程描述清晰,用户可以轻松按照步骤操作。",
"用户对物流费用承担问题提出疑问,客服明确说明质量问题导致的退换货由公司承担物流费用,非质量问题由用户承担。这个回答消除了用户的疑虑。",
];
return contents[index % contents.length];
};
const slices: EvaluationSlice[] = Array.from(
{ length: mockTasks[0].sliceConfig?.sampleCount || 50 },
(_, index) => ({
id: `slice_${index + 1}`,
content: generateSliceContent(index),
sourceFile: `file_${Math.floor(index / 5) + 1}.txt`,
sliceIndex: index % 5,
sliceType: ["paragraph", "sentence", "semantic"][index % 3],
metadata: {
startPosition: index * 200,
endPosition: (index + 1) * 200,
pageNumber: Math.floor(index / 10) + 1,
section: `Section ${Math.floor(index / 5) + 1}`,
processingMethod: mockTasks[0].sliceConfig?.method || "语义分割",
},
})
);
const ManualEvaluatePage = () => {
const navigate = useNavigate();
const taskId = mockTasks[0].id;
// 人工评估状态
const [currentEvaluationTask, setCurrentEvaluationTask] =
useState<EvaluationTask | null>(mockTasks[0]);
const [evaluationSlices, setEvaluationSlices] =
useState<EvaluationSlice[]>(slices);
const [currentSliceIndex, setCurrentSliceIndex] = useState(0);
const [sliceScores, setSliceScores] = useState<{
[key: string]: { [dimensionId: string]: number };
}>({});
const [sliceComments, setSliceComments] = useState<{ [key: string]: string }>(
{}
);
const currentSlice = evaluationSlices[currentSliceIndex];
const currentScores = sliceScores[currentSlice?.id] || {};
const progress =
evaluationSlices.length > 0
? ((currentSliceIndex + 1) / evaluationSlices.length) * 100
: 0;
// 获取任务的所有维度
const getTaskAllDimensions = (task: EvaluationTask) => {
const presetDimensions = presetEvaluationDimensions.filter((d) =>
task.dimensions.includes(d.id)
);
return [...presetDimensions, ...(task.customDimensions || [])];
};
const allDimensions = getTaskAllDimensions(mockTasks[0]);
// 更新切片评分
const updateSliceScore = (
sliceId: string,
dimensionId: string,
score: number
) => {
setSliceScores((prev) => ({
...prev,
[sliceId]: {
...prev[sliceId],
[dimensionId]: score,
},
}));
};
// 保存当前切片评分并进入下一个
const handleSaveAndNext = () => {
const currentSlice = evaluationSlices[currentSliceIndex];
if (!currentSlice) return;
// 检查是否所有维度都已评分
const allDimensions = getTaskAllDimensions(currentEvaluationTask!);
const currentScores = sliceScores[currentSlice.id] || {};
const hasAllScores = allDimensions.every(
(dim) => currentScores[dim.id] > 0
);
if (!hasAllScores) {
window.alert("请为所有维度评分后再保存");
return;
}
// 如果是最后一个切片,完成评估
if (currentSliceIndex === evaluationSlices.length - 1) {
handleCompleteEvaluation();
} else {
setCurrentSliceIndex(currentSliceIndex + 1);
}
};
// 完成评估
const handleCompleteEvaluation = () => {
navigate(`/data/evaluation/task-report/${mockTasks[0].id}`);
};
// 星星评分组件
const StarRating = ({
value,
onChange,
dimension,
}: {
value: number;
onChange: (value: number) => void;
dimension: EvaluationDimension;
}) => {
return (
<div style={{ marginBottom: 8 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span style={{ fontWeight: 500 }}>{dimension.name}</span>
<span style={{ fontSize: 13, color: "#888" }}>{value}/5</span>
</div>
<div style={{ fontSize: 12, color: "#888", marginBottom: 4 }}>
{dimension.description}
</div>
<div>
{[1, 2, 3, 4, 5].map((star) => (
<Button
key={star}
type="text"
icon={
<StarFilled
style={{
color: star <= value ? "#fadb14" : "#d9d9d9",
fontSize: 22,
transition: "color 0.2s",
}}
/>
}
onClick={() => onChange(star)}
style={{ padding: 0, marginRight: 2 }}
/>
))}
</div>
</div>
);
};
// 头部统计信息
const statistics = [
{
icon: <DatabaseOutlined className="text-gray-500" />,
label: "数据集",
value: currentEvaluationTask?.datasetName || "",
},
{
icon: <ScissorOutlined className="text-gray-500" />,
label: "切片方法",
value: currentEvaluationTask?.sliceConfig?.method || "",
},
{
icon: <AimOutlined className="text-gray-500" />,
label: "样本数量",
value: evaluationSlices.length,
},
{
icon: <CalendarOutlined className="text-gray-500" />,
label: "创建时间",
value: currentEvaluationTask?.createdAt || "",
},
];
return (
<div className="space-y-4">
<Breadcrumb
items={[
{
title: (
<span onClick={() => navigate("/data/evaluation")}></span>
),
},
{ title: "人工评估", key: "manual-evaluate" },
]}
/>
{/* 头部信息 */}
<DetailHeader
data={{
name: currentEvaluationTask?.name || "",
description: "人工评估任务",
icon: <FileTextOutlined />,
createdAt: currentEvaluationTask?.createdAt,
lastUpdated: currentEvaluationTask?.createdAt,
}}
statistics={statistics}
operations={[]}
/>
{/* 进度条 */}
<div className="flex justify-between items-center mt-4 mb-6">
<div className="text-xs text-gray-500">
: {currentSliceIndex + 1} / {evaluationSlices.length}
</div>
<div className="flex items-center gap-4">
<span className="text-xs text-gray-500">
{Math.round(progress)}%
</span>
<div className="w-48 bg-gray-200 rounded h-2">
<div
className="bg-blue-600 h-2 rounded transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-2xl font-bold text-blue-600">
{Math.round(progress)}%
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{/* 左侧:切片内容 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4 flex justify-between items-center">
<span className="text-base font-semibold flex items-center gap-2">
<FileTextOutlined />
</span>
<Badge
count={`切片 ${currentSliceIndex + 1}`}
style={{ background: "#fafafa", color: "#333" }}
/>
</div>
<div className="flex flex-col gap-2">
{currentSlice && (
<>
{/* 切片元信息 */}
<div className="bg-gray-50 rounded p-4 text-sm">
<div className="grid grid-cols-2 gap-3">
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.sourceFile}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.processingMethod}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.startPosition}-
{currentSlice.metadata.endPosition}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.section}
</span>
</div>
</div>
</div>
{/* 切片内容 */}
<div className="border border-gray-100 rounded p-4 min-h-[180px]">
<div className="text-xs text-gray-500 mb-2"></div>
<div className="text-gray-900 leading-relaxed">
{currentSlice.content}
</div>
</div>
{/* 导航按钮 */}
<div className="flex items-center justify-between border-t border-gray-100 pt-4 mt-2">
<Button
type="default"
icon={<LeftOutlined />}
onClick={() =>
setCurrentSliceIndex(Math.max(0, currentSliceIndex - 1))
}
disabled={currentSliceIndex === 0}
>
</Button>
<span className="text-xs text-gray-500">
{currentSliceIndex + 1} / {evaluationSlices.length}
</span>
<Button
type="default"
icon={<RightOutlined />}
onClick={() =>
setCurrentSliceIndex(
Math.min(
evaluationSlices.length - 1,
currentSliceIndex + 1
)
)
}
disabled={currentSliceIndex === evaluationSlices.length - 1}
>
</Button>
</div>
</>
)}
</div>
</Card>
{/* 右侧:评估维度 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4">
<span className="text-base font-semibold flex items-center gap-2">
<StarFilled className="text-yellow-400" />
</span>
<div className="text-xs text-gray-500 mt-1">
1-5
</div>
</div>
<div className="flex flex-col gap-4">
{allDimensions.map((dimension) => (
<div
key={dimension.id}
className="border border-gray-100 rounded p-4"
>
<StarRating
value={currentScores[dimension.id] || 0}
onChange={(score) =>
updateSliceScore(
currentSlice?.id || "",
dimension.id,
score
)
}
dimension={dimension}
/>
</div>
))}
{/* 评论区域 */}
<div className="border border-gray-100 rounded p-4">
<span className="font-medium mb-2 block"></span>
<TextArea
placeholder="请输入对该切片的评估备注和建议..."
value={sliceComments[currentSlice?.id || ""] || ""}
onChange={(e) =>
setSliceComments((prev) => ({
...prev,
[currentSlice?.id || ""]: e.target.value,
}))
}
rows={3}
/>
</div>
{/* 保存按钮 */}
<div className="border-t border-gray-100 pt-4">
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSaveAndNext}
block
size="large"
>
{currentSliceIndex === evaluationSlices.length - 1
? "完成评估"
: "保存并下一个"}
</Button>
</div>
</div>
</Card>
</div>
</div>
);
};
export default ManualEvaluatePage;
import { useState, useEffect } from "react";
import { Button, Card, Badge, Input, Typography, Breadcrumb } from "antd";
import {
LeftOutlined,
RightOutlined,
SaveOutlined,
ScissorOutlined,
AimOutlined,
CalendarOutlined,
FileTextOutlined,
StarFilled,
DatabaseOutlined,
} from "@ant-design/icons";
import { mockTasks, presetEvaluationDimensions } from "@/mock/evaluation";
import { useNavigate } from "react-router";
import DetailHeader from "@/components/DetailHeader";
const { TextArea } = Input;
const { Title } = Typography;
// 生成切片内容
const generateSliceContent = (index: number) => {
const contents = [
"用户咨询产品退换货政策的相关问题,希望了解具体的退货流程和时间限制。客服详细解释了7天无理由退货政策,包括商品需要保持原包装完整的要求。这个回答涵盖了用户关心的主要问题,提供了明确的时间限制和条件说明。",
"客服回复关于质量问题商品的处理方式,说明15天内免费换货服务,并承诺承担相关物流费用。用户对此表示满意,认为这个政策很合理。回答中明确区分了质量问题和非质量问题的不同处理方式。",
"用户询问特殊商品的退换货政策,客服解释个人定制商品不支持退货的规定,并建议用户在购买前仔细确认商品信息。这个回答帮助用户理解了特殊商品的限制条件。",
"关于退货流程的详细说明,客服介绍了在线申请退货的步骤,包括订单页面操作和快递上门取件服务。整个流程描述清晰,用户可以轻松按照步骤操作。",
"用户对物流费用承担问题提出疑问,客服明确说明质量问题导致的退换货由公司承担物流费用,非质量问题由用户承担。这个回答消除了用户的疑虑。",
];
return contents[index % contents.length];
};
const slices: EvaluationSlice[] = Array.from(
{ length: mockTasks[0].sliceConfig?.sampleCount || 50 },
(_, index) => ({
id: `slice_${index + 1}`,
content: generateSliceContent(index),
sourceFile: `file_${Math.floor(index / 5) + 1}.txt`,
sliceIndex: index % 5,
sliceType: ["paragraph", "sentence", "semantic"][index % 3],
metadata: {
startPosition: index * 200,
endPosition: (index + 1) * 200,
pageNumber: Math.floor(index / 10) + 1,
section: `Section ${Math.floor(index / 5) + 1}`,
processingMethod: mockTasks[0].sliceConfig?.method || "语义分割",
},
})
);
const ManualEvaluatePage = () => {
const navigate = useNavigate();
const taskId = mockTasks[0].id;
// 人工评估状态
const [currentEvaluationTask, setCurrentEvaluationTask] =
useState<EvaluationTask | null>(mockTasks[0]);
const [evaluationSlices, setEvaluationSlices] =
useState<EvaluationSlice[]>(slices);
const [currentSliceIndex, setCurrentSliceIndex] = useState(0);
const [sliceScores, setSliceScores] = useState<{
[key: string]: { [dimensionId: string]: number };
}>({});
const [sliceComments, setSliceComments] = useState<{ [key: string]: string }>(
{}
);
const currentSlice = evaluationSlices[currentSliceIndex];
const currentScores = sliceScores[currentSlice?.id] || {};
const progress =
evaluationSlices.length > 0
? ((currentSliceIndex + 1) / evaluationSlices.length) * 100
: 0;
// 获取任务的所有维度
const getTaskAllDimensions = (task: EvaluationTask) => {
const presetDimensions = presetEvaluationDimensions.filter((d) =>
task.dimensions.includes(d.id)
);
return [...presetDimensions, ...(task.customDimensions || [])];
};
const allDimensions = getTaskAllDimensions(mockTasks[0]);
// 更新切片评分
const updateSliceScore = (
sliceId: string,
dimensionId: string,
score: number
) => {
setSliceScores((prev) => ({
...prev,
[sliceId]: {
...prev[sliceId],
[dimensionId]: score,
},
}));
};
// 保存当前切片评分并进入下一个
const handleSaveAndNext = () => {
const currentSlice = evaluationSlices[currentSliceIndex];
if (!currentSlice) return;
// 检查是否所有维度都已评分
const allDimensions = getTaskAllDimensions(currentEvaluationTask!);
const currentScores = sliceScores[currentSlice.id] || {};
const hasAllScores = allDimensions.every(
(dim) => currentScores[dim.id] > 0
);
if (!hasAllScores) {
window.alert("请为所有维度评分后再保存");
return;
}
// 如果是最后一个切片,完成评估
if (currentSliceIndex === evaluationSlices.length - 1) {
handleCompleteEvaluation();
} else {
setCurrentSliceIndex(currentSliceIndex + 1);
}
};
// 完成评估
const handleCompleteEvaluation = () => {
navigate(`/data/evaluation/task-report/${mockTasks[0].id}`);
};
// 星星评分组件
const StarRating = ({
value,
onChange,
dimension,
}: {
value: number;
onChange: (value: number) => void;
dimension: EvaluationDimension;
}) => {
return (
<div style={{ marginBottom: 8 }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<span style={{ fontWeight: 500 }}>{dimension.name}</span>
<span style={{ fontSize: 13, color: "#888" }}>{value}/5</span>
</div>
<div style={{ fontSize: 12, color: "#888", marginBottom: 4 }}>
{dimension.description}
</div>
<div>
{[1, 2, 3, 4, 5].map((star) => (
<Button
key={star}
type="text"
icon={
<StarFilled
style={{
color: star <= value ? "#fadb14" : "#d9d9d9",
fontSize: 22,
transition: "color 0.2s",
}}
/>
}
onClick={() => onChange(star)}
style={{ padding: 0, marginRight: 2 }}
/>
))}
</div>
</div>
);
};
// 头部统计信息
const statistics = [
{
icon: <DatabaseOutlined className="text-gray-500" />,
label: "数据集",
value: currentEvaluationTask?.datasetName || "",
},
{
icon: <ScissorOutlined className="text-gray-500" />,
label: "切片方法",
value: currentEvaluationTask?.sliceConfig?.method || "",
},
{
icon: <AimOutlined className="text-gray-500" />,
label: "样本数量",
value: evaluationSlices.length,
},
{
icon: <CalendarOutlined className="text-gray-500" />,
label: "创建时间",
value: currentEvaluationTask?.createdAt || "",
},
];
return (
<div className="space-y-4">
<Breadcrumb
items={[
{
title: (
<span onClick={() => navigate("/data/evaluation")}></span>
),
},
{ title: "人工评估", key: "manual-evaluate" },
]}
/>
{/* 头部信息 */}
<DetailHeader
data={{
name: currentEvaluationTask?.name || "",
description: "人工评估任务",
icon: <FileTextOutlined />,
createdAt: currentEvaluationTask?.createdAt,
lastUpdated: currentEvaluationTask?.createdAt,
}}
statistics={statistics}
operations={[]}
/>
{/* 进度条 */}
<div className="flex justify-between items-center mt-4 mb-6">
<div className="text-xs text-gray-500">
: {currentSliceIndex + 1} / {evaluationSlices.length}
</div>
<div className="flex items-center gap-4">
<span className="text-xs text-gray-500">
{Math.round(progress)}%
</span>
<div className="w-48 bg-gray-200 rounded h-2">
<div
className="bg-blue-600 h-2 rounded transition-all"
style={{ width: `${progress}%` }}
/>
</div>
<span className="text-2xl font-bold text-blue-600">
{Math.round(progress)}%
</span>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
{/* 左侧:切片内容 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4 flex justify-between items-center">
<span className="text-base font-semibold flex items-center gap-2">
<FileTextOutlined />
</span>
<Badge
count={`切片 ${currentSliceIndex + 1}`}
style={{ background: "#fafafa", color: "#333" }}
/>
</div>
<div className="flex flex-col gap-2">
{currentSlice && (
<>
{/* 切片元信息 */}
<div className="bg-gray-50 rounded p-4 text-sm">
<div className="grid grid-cols-2 gap-3">
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.sourceFile}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.processingMethod}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.startPosition}-
{currentSlice.metadata.endPosition}
</span>
</div>
<div>
<span className="text-gray-500">:</span>
<span className="ml-2 font-medium">
{currentSlice.metadata.section}
</span>
</div>
</div>
</div>
{/* 切片内容 */}
<div className="border border-gray-100 rounded p-4 min-h-[180px]">
<div className="text-xs text-gray-500 mb-2"></div>
<div className="text-gray-900 leading-relaxed">
{currentSlice.content}
</div>
</div>
{/* 导航按钮 */}
<div className="flex items-center justify-between border-t border-gray-100 pt-4 mt-2">
<Button
type="default"
icon={<LeftOutlined />}
onClick={() =>
setCurrentSliceIndex(Math.max(0, currentSliceIndex - 1))
}
disabled={currentSliceIndex === 0}
>
</Button>
<span className="text-xs text-gray-500">
{currentSliceIndex + 1} / {evaluationSlices.length}
</span>
<Button
type="default"
icon={<RightOutlined />}
onClick={() =>
setCurrentSliceIndex(
Math.min(
evaluationSlices.length - 1,
currentSliceIndex + 1
)
)
}
disabled={currentSliceIndex === evaluationSlices.length - 1}
>
</Button>
</div>
</>
)}
</div>
</Card>
{/* 右侧:评估维度 */}
<Card>
<div className="border-b border-gray-100 pb-4 mb-4">
<span className="text-base font-semibold flex items-center gap-2">
<StarFilled className="text-yellow-400" />
</span>
<div className="text-xs text-gray-500 mt-1">
1-5
</div>
</div>
<div className="flex flex-col gap-4">
{allDimensions.map((dimension) => (
<div
key={dimension.id}
className="border border-gray-100 rounded p-4"
>
<StarRating
value={currentScores[dimension.id] || 0}
onChange={(score) =>
updateSliceScore(
currentSlice?.id || "",
dimension.id,
score
)
}
dimension={dimension}
/>
</div>
))}
{/* 评论区域 */}
<div className="border border-gray-100 rounded p-4">
<span className="font-medium mb-2 block"></span>
<TextArea
placeholder="请输入对该切片的评估备注和建议..."
value={sliceComments[currentSlice?.id || ""] || ""}
onChange={(e) =>
setSliceComments((prev) => ({
...prev,
[currentSlice?.id || ""]: e.target.value,
}))
}
rows={3}
/>
</div>
{/* 保存按钮 */}
<div className="border-t border-gray-100 pt-4">
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSaveAndNext}
block
size="large"
>
{currentSliceIndex === evaluationSlices.length - 1
? "完成评估"
: "保存并下一个"}
</Button>
</div>
</div>
</Card>
</div>
</div>
);
};
export default ManualEvaluatePage;