fix:优化试题内容和样式排版

This commit is contained in:
Daniel
2026-04-18 20:25:53 +08:00
parent 7cb9b89cb0
commit b4e1774feb
2 changed files with 162 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ import { Line, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContai
import {
BookOpen,
Bot,
Camera,
ChartNoAxesCombined,
FileDown,
FolderOpen,
@@ -553,7 +554,7 @@ function ResourceModule() {
);
}
function MistakeModule() {
function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
const [items, setItems] = useState([]);
const [selectedId, setSelectedId] = useState(null);
const [selectedExportIds, setSelectedExportIds] = useState([]);
@@ -726,6 +727,14 @@ function MistakeModule() {
}
};
const uploadImageFileOnly = async (file) => {
if (!file) return "";
const fd = new FormData();
fd.append("file", file);
const res = await api.post("/api/upload", fd, { headers: { "Content-Type": "multipart/form-data" } });
return res.data.url;
};
const rerunOcrForEditing = async () => {
const imageUrl = String(editingItem?.image_url || "").trim();
if (!imageUrl) {
@@ -900,6 +909,59 @@ function MistakeModule() {
show("已清空勾选");
};
useEffect(() => {
if (!quickCaptureTask?.id) return;
let cancelled = false;
const run = async () => {
try {
const files = Array.isArray(quickCaptureTask.files) ? quickCaptureTask.files : [];
if (!files.length) return;
if (quickCaptureTask.mode === "single") {
setShowAdd(true);
await uploadImage(files[0], true);
return;
}
let ok = 0;
let fail = 0;
for (const file of files) {
try {
const url = await uploadImageFileOnly(file);
await api.post("/api/mistakes", {
title: "待补录图片错题",
image_url: url,
category: "其他",
difficulty: "medium",
question_content: "",
answer: "",
explanation: "",
note: "",
wrong_count: 1
});
ok += 1;
} catch {
fail += 1;
}
}
if (!cancelled) {
await load();
show(`连拍导入完成:成功 ${ok},失败 ${fail}`);
}
} finally {
if (!cancelled) {
onQuickCaptureHandled?.(quickCaptureTask.id);
}
}
};
run();
return () => {
cancelled = true;
};
}, [quickCaptureTask?.id]);
const diffLabel = { easy: "易", medium: "中", hard: "难" };
return (
@@ -1680,6 +1742,29 @@ function AiModule() {
export default function App() {
const [mainTab, setMainTab] = useState("mistake");
const [extraTab, setExtraTab] = useState("resource");
const [showQuickCamera, setShowQuickCamera] = useState(false);
const [quickCaptureMode, setQuickCaptureMode] = useState("single");
const [quickCaptureTask, setQuickCaptureTask] = useState(null);
const quickCaptureInputRef = useRef(null);
const triggerQuickCapture = () => {
quickCaptureInputRef.current?.click();
};
const onQuickCapturePicked = (filesLike) => {
const files = Array.from(filesLike || []);
if (!files.length) return;
setMainTab("mistake");
setQuickCaptureTask({
id: Date.now(),
mode: quickCaptureMode,
files
});
setShowQuickCamera(false);
if (quickCaptureInputRef.current) {
quickCaptureInputRef.current.value = "";
}
};
return (
<div className="app-shell">
@@ -1748,7 +1833,14 @@ export default function App() {
)}
<main className="app-main">
{mainTab === "mistake" && <MistakeModule />}
{mainTab === "mistake" && (
<MistakeModule
quickCaptureTask={quickCaptureTask}
onQuickCaptureHandled={(taskId) => {
setQuickCaptureTask((prev) => (prev?.id === taskId ? null : prev));
}}
/>
)}
{mainTab === "score" && <ScoreModule />}
{mainTab === "more" && (
<>
@@ -1757,6 +1849,48 @@ export default function App() {
</>
)}
</main>
<input
ref={quickCaptureInputRef}
style={{ display: "none" }}
type="file"
accept="image/*"
capture="environment"
multiple={quickCaptureMode === "burst"}
onChange={(e) => onQuickCapturePicked(e.target.files)}
/>
<button type="button" className="quick-camera-fab" onClick={() => setShowQuickCamera(true)} aria-label="快速拍照录题">
<Camera size={20} />
快速拍题
</button>
{showQuickCamera && (
<Modal title="快速拍照录题" onClose={() => setShowQuickCamera(false)}>
<div className="stack-gap-sm">
<label className="field-label">拍照模式</label>
<div className="radio-row">
<label>
<input type="radio" checked={quickCaptureMode === "single"} onChange={() => setQuickCaptureMode("single")} /> 单拍
</label>
<label>
<input type="radio" checked={quickCaptureMode === "burst"} onChange={() => setQuickCaptureMode("burst")} /> 连拍
</label>
</div>
<div className="text-muted small">
单拍拍一张后自动打开新增错题并识别连拍可一次选择多张批量生成待补录错题
</div>
</div>
<div className="btn-row" style={{ marginTop: 14 }}>
<button type="button" className="btn btn-primary" onClick={triggerQuickCapture}>
开始拍照
</button>
<button type="button" className="btn btn-ghost" onClick={() => setShowQuickCamera(false)}>
取消
</button>
</div>
</Modal>
)}
</div>
);
}

View File

@@ -824,6 +824,32 @@ textarea {
width: auto;
}
.quick-camera-fab {
position: fixed;
right: 18px;
bottom: calc(18px + env(safe-area-inset-bottom));
z-index: 90;
display: inline-flex;
align-items: center;
gap: 8px;
border: none;
border-radius: 999px;
padding: 12px 16px;
background: linear-gradient(135deg, #0ea5e9, #2563eb);
color: #fff;
font-weight: 700;
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.32);
cursor: pointer;
}
@media (max-width: 860px) {
.quick-camera-fab {
right: 14px;
bottom: calc(76px + env(safe-area-inset-bottom));
padding: 11px 14px;
}
}
.toast {
position: fixed;
left: 50%;