fix:优化试题内容和样式排版
This commit is contained in:
@@ -4,6 +4,7 @@ import { Line, LineChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContai
|
|||||||
import {
|
import {
|
||||||
BookOpen,
|
BookOpen,
|
||||||
Bot,
|
Bot,
|
||||||
|
Camera,
|
||||||
ChartNoAxesCombined,
|
ChartNoAxesCombined,
|
||||||
FileDown,
|
FileDown,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
@@ -553,7 +554,7 @@ function ResourceModule() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MistakeModule() {
|
function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [selectedId, setSelectedId] = useState(null);
|
const [selectedId, setSelectedId] = useState(null);
|
||||||
const [selectedExportIds, setSelectedExportIds] = useState([]);
|
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 rerunOcrForEditing = async () => {
|
||||||
const imageUrl = String(editingItem?.image_url || "").trim();
|
const imageUrl = String(editingItem?.image_url || "").trim();
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
@@ -900,6 +909,59 @@ function MistakeModule() {
|
|||||||
show("已清空勾选");
|
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: "难" };
|
const diffLabel = { easy: "易", medium: "中", hard: "难" };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1680,6 +1742,29 @@ function AiModule() {
|
|||||||
export default function App() {
|
export default function App() {
|
||||||
const [mainTab, setMainTab] = useState("mistake");
|
const [mainTab, setMainTab] = useState("mistake");
|
||||||
const [extraTab, setExtraTab] = useState("resource");
|
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 (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
@@ -1748,7 +1833,14 @@ export default function App() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<main className="app-main">
|
<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 === "score" && <ScoreModule />}
|
||||||
{mainTab === "more" && (
|
{mainTab === "more" && (
|
||||||
<>
|
<>
|
||||||
@@ -1757,6 +1849,48 @@ export default function App() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</main>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -824,6 +824,32 @@ textarea {
|
|||||||
width: auto;
|
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 {
|
.toast {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
|||||||
Reference in New Issue
Block a user