fix:优化试题内容和样式排版
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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%;
|
||||
|
||||
Reference in New Issue
Block a user