fix: 优化整个项目内容
This commit is contained in:
192
frontend/dist/assets/index-B6wMcdCx.js
vendored
192
frontend/dist/assets/index-B6wMcdCx.js
vendored
File diff suppressed because one or more lines are too long
192
frontend/dist/assets/index-DyP_J9zM.js
vendored
Normal file
192
frontend/dist/assets/index-DyP_J9zM.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@@ -8,8 +8,8 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<title>公考助手</title>
|
||||
<script type="module" crossorigin src="/assets/index-B6wMcdCx.js"></script>
|
||||
<title>学习伙伴</title>
|
||||
<script type="module" crossorigin src="/assets/index-DyP_J9zM.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-C-dSv0ph.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
6
frontend/dist/manifest.webmanifest
vendored
6
frontend/dist/manifest.webmanifest
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "公考助手",
|
||||
"short_name": "公考助手",
|
||||
"description": "公考学习资源、错题与过程管理",
|
||||
"name": "学习伙伴",
|
||||
"short_name": "学习伙伴",
|
||||
"description": "学习资源、错题与过程管理",
|
||||
"display": "standalone",
|
||||
"background_color": "#e8ecf1",
|
||||
"theme_color": "#2563eb",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<title>公考助手</title>
|
||||
<title>学习伙伴</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "公考助手",
|
||||
"short_name": "公考助手",
|
||||
"description": "公考学习资源、错题与过程管理",
|
||||
"name": "学习伙伴",
|
||||
"short_name": "学习伙伴",
|
||||
"description": "学习资源、错题与过程管理",
|
||||
"display": "standalone",
|
||||
"background_color": "#e8ecf1",
|
||||
"theme_color": "#2563eb",
|
||||
|
||||
@@ -614,9 +614,7 @@ function ResourceModule() {
|
||||
|
||||
function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
const [items, setItems] = useState([]);
|
||||
const [selectedId, setSelectedId] = useState(null);
|
||||
const [selectedExportIds, setSelectedExportIds] = useState([]);
|
||||
const [analysis, setAnalysis] = useState("");
|
||||
const [categoryFilter, setCategoryFilter] = useState("");
|
||||
const [keyword, setKeyword] = useState("");
|
||||
const [sortKey, setSortKey] = useState("time_desc");
|
||||
@@ -688,9 +686,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
const res = await api.get("/api/mistakes", { params: apiFilters });
|
||||
setItems(res.data);
|
||||
setSelectedExportIds((prev) => prev.filter((id) => res.data.some((x) => x.id === id)));
|
||||
if (selectedId && !res.data.some((x) => x.id === selectedId)) {
|
||||
setSelectedId(null);
|
||||
}
|
||||
setDetailItem((prev) => (prev && !res.data.some((x) => x.id === prev.id) ? null : prev));
|
||||
} catch (error) {
|
||||
show(getApiErrorMessage(error, "加载错题失败"));
|
||||
}
|
||||
@@ -734,6 +730,56 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
runOcr(imageUrl).catch(() => {});
|
||||
};
|
||||
|
||||
const normalizeCaptureFile = async (file) => {
|
||||
if (!file) return file;
|
||||
const type = String(file.type || "").toLowerCase();
|
||||
const isAlreadySupported = ["image/jpeg", "image/png", "image/webp"].includes(type);
|
||||
const hasKnownExt = /\.(jpe?g|png|webp)$/i.test(file.name || "");
|
||||
if (isAlreadySupported && hasKnownExt) return file;
|
||||
|
||||
if (!type.startsWith("image/")) return file;
|
||||
|
||||
try {
|
||||
const dataUrl = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve(String(reader.result || ""));
|
||||
reader.onerror = () => reject(new Error("读取图片失败"));
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
const img = await new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
image.onload = () => resolve(image);
|
||||
image.onerror = () => reject(new Error("图片解码失败"));
|
||||
image.src = dataUrl;
|
||||
});
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return file;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
const blob = await new Promise((resolve, reject) => {
|
||||
canvas.toBlob(
|
||||
(b) => {
|
||||
if (!b) reject(new Error("图片转换失败"));
|
||||
else resolve(b);
|
||||
},
|
||||
"image/jpeg",
|
||||
0.92
|
||||
);
|
||||
});
|
||||
|
||||
const baseName = String(file.name || "capture").replace(/\.[^.]+$/, "");
|
||||
return new File([blob], `${baseName || "capture"}-${Date.now()}.jpg`, { type: "image/jpeg" });
|
||||
} catch {
|
||||
// If conversion fails, fallback to original file; backend still has suffix+mime fallback.
|
||||
return file;
|
||||
}
|
||||
};
|
||||
|
||||
const uploadImageBlob = async (blob, fileName = `scan-${Date.now()}.jpg`, autoParse = true) => {
|
||||
const fd = new FormData();
|
||||
fd.append("file", new File([blob], fileName, { type: blob.type || "image/jpeg" }));
|
||||
@@ -753,8 +799,9 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
|
||||
const uploadImage = async (file, autoParse = true) => {
|
||||
if (!file) return;
|
||||
const normalized = await normalizeCaptureFile(file);
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
fd.append("file", normalized);
|
||||
setUploading(true);
|
||||
try {
|
||||
const res = await api.post("/api/upload", fd, { headers: { "Content-Type": "multipart/form-data" } });
|
||||
@@ -771,8 +818,9 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
|
||||
const replaceEditingImage = async (file) => {
|
||||
if (!file) return;
|
||||
const normalized = await normalizeCaptureFile(file);
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
fd.append("file", normalized);
|
||||
setUploading(true);
|
||||
try {
|
||||
const res = await api.post("/api/upload", fd, { headers: { "Content-Type": "multipart/form-data" } });
|
||||
@@ -787,8 +835,9 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
|
||||
const uploadImageFileOnly = async (file) => {
|
||||
if (!file) return "";
|
||||
const normalized = await normalizeCaptureFile(file);
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
fd.append("file", normalized);
|
||||
const res = await api.post("/api/upload", fd, { headers: { "Content-Type": "multipart/form-data" } });
|
||||
return res.data.url;
|
||||
};
|
||||
@@ -891,7 +940,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
const remove = async (id) => {
|
||||
if (!window.confirm("确认删除该错题?")) return;
|
||||
await api.delete(`/api/mistakes/${id}`);
|
||||
if (selectedId === id) setSelectedId(null);
|
||||
setDetailItem((prev) => (prev?.id === id ? null : prev));
|
||||
show("错题已删除");
|
||||
load();
|
||||
};
|
||||
@@ -934,17 +983,6 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
show("已开始下载");
|
||||
};
|
||||
|
||||
const askAi = async () => {
|
||||
if (!selectedId) return show("请先点击列表中的一条错题");
|
||||
try {
|
||||
const res = await api.post(`/api/ai/mistakes/${selectedId}/analyze`);
|
||||
setAnalysis(res.data.analysis);
|
||||
show("解析完成");
|
||||
} catch (error) {
|
||||
show(getApiErrorMessage(error, "AI 错题解析失败"));
|
||||
}
|
||||
};
|
||||
|
||||
const toggleExportSelected = (id) => {
|
||||
setSelectedExportIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
|
||||
};
|
||||
@@ -978,7 +1016,11 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
|
||||
if (quickCaptureTask.mode === "single") {
|
||||
setShowAdd(true);
|
||||
await uploadImage(files[0], true);
|
||||
try {
|
||||
await uploadImage(files[0], true);
|
||||
} catch (error) {
|
||||
show(getApiErrorMessage(error, "快速拍题上传失败,请重试"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1040,9 +1082,6 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
>
|
||||
<FileDown size={18} /> 导出错题
|
||||
</button>
|
||||
<button type="button" className="btn btn-outline btn-pill" onClick={askAi} disabled={!selectedId}>
|
||||
AI 解析
|
||||
</button>
|
||||
</div>
|
||||
<div className="toolbar-right">
|
||||
<select className="select-min" value={categoryFilter} onChange={(e) => setCategoryFilter(e.target.value)} aria-label="分类">
|
||||
@@ -1093,17 +1132,11 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={`mistake-card ${selectedId === item.id ? "is-selected" : ""}`}
|
||||
onClick={() => {
|
||||
setSelectedId(item.id);
|
||||
setAnalysis("");
|
||||
setDetailItem(item);
|
||||
}}
|
||||
className={`mistake-card ${detailItem?.id === item.id ? "is-selected" : ""}`}
|
||||
onClick={() => setDetailItem(item)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
setSelectedId(item.id);
|
||||
setAnalysis("");
|
||||
setDetailItem(item);
|
||||
}
|
||||
}}
|
||||
@@ -1144,13 +1177,6 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{analysis && (
|
||||
<div className="panel ai-result">
|
||||
<h4 className="panel-subtitle">AI 解析</h4>
|
||||
<pre className="pre-wrap">{analysis}</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAdd && (
|
||||
<Modal title="添加错题" onClose={() => setShowAdd(false)}>
|
||||
<form onSubmit={submit}>
|
||||
@@ -1753,7 +1779,7 @@ function ScoreModule() {
|
||||
|
||||
function AiModule() {
|
||||
const { message, show } = useToast();
|
||||
const [form, setForm] = useState({ goal: "30天内行测稳定到70分以上", days_left: 30, daily_hours: 2 });
|
||||
const [form, setForm] = useState({ goal: "30天内模考成绩稳定达到目标分", days_left: 30, daily_hours: 2 });
|
||||
const [plan, setPlan] = useState("");
|
||||
|
||||
const submit = async (e) => {
|
||||
@@ -1839,7 +1865,7 @@ export default function App() {
|
||||
<GraduationCap size={28} strokeWidth={2} />
|
||||
</span>
|
||||
<div>
|
||||
<h1 className="brand-title">公考助手</h1>
|
||||
<h1 className="brand-title">学习伙伴</h1>
|
||||
<p className="brand-sub">智能错题整理 · 科学分数管理</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user