fix: 优化个相机功能与样式
This commit is contained in:
192
frontend/dist/assets/index-9iYNjrsg.js
vendored
192
frontend/dist/assets/index-9iYNjrsg.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
204
frontend/dist/assets/index-Dw7fpRnj.js
vendored
Normal file
204
frontend/dist/assets/index-Dw7fpRnj.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
@@ -9,8 +9,8 @@
|
|||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<title>学习伙伴</title>
|
<title>学习伙伴</title>
|
||||||
<script type="module" crossorigin src="/assets/index-9iYNjrsg.js"></script>
|
<script type="module" crossorigin src="/assets/index-Dw7fpRnj.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C-dSv0ph.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CoOKrmS9.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
FileDown,
|
FileDown,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
GraduationCap,
|
GraduationCap,
|
||||||
|
Maximize2,
|
||||||
|
Minimize2,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
NotebookPen,
|
NotebookPen,
|
||||||
Plus,
|
Plus,
|
||||||
@@ -17,6 +19,24 @@ import {
|
|||||||
|
|
||||||
const api = axios.create({ baseURL: "/" });
|
const api = axios.create({ baseURL: "/" });
|
||||||
|
|
||||||
|
const QUICK_CAMERA_FAB_COMPACT_KEY = "studyBuddy_quickCameraFabCompact";
|
||||||
|
|
||||||
|
function readQuickCameraFabCompact() {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(QUICK_CAMERA_FAB_COMPACT_KEY) === "1";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeQuickCameraFabCompact(value) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(QUICK_CAMERA_FAB_COMPACT_KEY, value ? "1" : "0");
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const CATEGORIES = ["常识", "数量关系", "言语理解", "判断推理", "资料分析"];
|
const CATEGORIES = ["常识", "数量关系", "言语理解", "判断推理", "资料分析"];
|
||||||
const MISTAKE_CATEGORIES = ["常识", "言语", "数量", "判断", "资料", "科学", "其他"];
|
const MISTAKE_CATEGORIES = ["常识", "言语", "数量", "判断", "资料", "科学", "其他"];
|
||||||
|
|
||||||
@@ -616,6 +636,7 @@ function ResourceModule() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
||||||
|
const PENDING_IMAGE_TITLE = "待补录图片错题";
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [selectedExportIds, setSelectedExportIds] = useState([]);
|
const [selectedExportIds, setSelectedExportIds] = useState([]);
|
||||||
const [categoryFilter, setCategoryFilter] = useState("");
|
const [categoryFilter, setCategoryFilter] = useState("");
|
||||||
@@ -632,6 +653,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
const [exportScope, setExportScope] = useState("all");
|
const [exportScope, setExportScope] = useState("all");
|
||||||
const [exportContentMode, setExportContentMode] = useState("full");
|
const [exportContentMode, setExportContentMode] = useState("full");
|
||||||
const [exportRange, setExportRange] = useState({ start_date: "", end_date: "" });
|
const [exportRange, setExportRange] = useState({ start_date: "", end_date: "" });
|
||||||
|
const [batchRecognizeScope, setBatchRecognizeScope] = useState("all_pending");
|
||||||
|
|
||||||
const uploadInputRef = useRef(null);
|
const uploadInputRef = useRef(null);
|
||||||
const captureInputRef = useRef(null);
|
const captureInputRef = useRef(null);
|
||||||
@@ -651,6 +673,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
});
|
});
|
||||||
const [ocrText, setOcrText] = useState("");
|
const [ocrText, setOcrText] = useState("");
|
||||||
const [ocrLoading, setOcrLoading] = useState(false);
|
const [ocrLoading, setOcrLoading] = useState(false);
|
||||||
|
const [batchRecognizing, setBatchRecognizing] = useState(false);
|
||||||
const [editingItem, setEditingItem] = useState(null);
|
const [editingItem, setEditingItem] = useState(null);
|
||||||
const [detailItem, setDetailItem] = useState(null);
|
const [detailItem, setDetailItem] = useState(null);
|
||||||
|
|
||||||
@@ -660,6 +683,15 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
return source.slice(0, 40);
|
return source.slice(0, 40);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isPendingImageTitle = (title) => String(title || "").trim() === PENDING_IMAGE_TITLE;
|
||||||
|
|
||||||
|
const resolveTitleAfterOcr = (currentTitle, data) => {
|
||||||
|
const recognizedTitle = data?.title_suggestion || buildTitleFromQuestion(data?.question_content || data?.text);
|
||||||
|
const current = String(currentTitle || "").trim();
|
||||||
|
if (!current || isPendingImageTitle(current)) return recognizedTitle || current || PENDING_IMAGE_TITLE;
|
||||||
|
return current;
|
||||||
|
};
|
||||||
|
|
||||||
/** 模型常把全文放在 text,question_content 只有短问句;题目内容应取更完整的一份 */
|
/** 模型常把全文放在 text,question_content 只有短问句;题目内容应取更完整的一份 */
|
||||||
const mergeQuestionContent = (questionContent, fullText) => {
|
const mergeQuestionContent = (questionContent, fullText) => {
|
||||||
const q = String(questionContent || "").trim();
|
const q = String(questionContent || "").trim();
|
||||||
@@ -689,7 +721,11 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
const res = await api.get("/api/mistakes", { params: apiFilters });
|
const res = await api.get("/api/mistakes", { params: apiFilters });
|
||||||
setItems(res.data);
|
setItems(res.data);
|
||||||
setSelectedExportIds((prev) => prev.filter((id) => res.data.some((x) => x.id === id)));
|
setSelectedExportIds((prev) => prev.filter((id) => res.data.some((x) => x.id === id)));
|
||||||
setDetailItem((prev) => (prev && !res.data.some((x) => x.id === prev.id) ? null : prev));
|
setDetailItem((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
const latest = res.data.find((x) => x.id === prev.id);
|
||||||
|
return latest || null;
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
show(getApiErrorMessage(error, "加载错题失败"));
|
show(getApiErrorMessage(error, "加载错题失败"));
|
||||||
}
|
}
|
||||||
@@ -708,7 +744,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
setOcrText(data.text || "");
|
setOcrText(data.text || "");
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
title: prev.title || data.title_suggestion || buildTitleFromQuestion(data.question_content || data.text),
|
title: resolveTitleAfterOcr(prev.title, data),
|
||||||
category: MISTAKE_CATEGORIES.includes(data.category_suggestion) ? data.category_suggestion : prev.category,
|
category: MISTAKE_CATEGORIES.includes(data.category_suggestion) ? data.category_suggestion : prev.category,
|
||||||
difficulty: ["easy", "medium", "hard"].includes(data.difficulty_suggestion) ? data.difficulty_suggestion : prev.difficulty,
|
difficulty: ["easy", "medium", "hard"].includes(data.difficulty_suggestion) ? data.difficulty_suggestion : prev.difficulty,
|
||||||
question_content: (
|
question_content: (
|
||||||
@@ -868,7 +904,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
if (!prev) return prev;
|
if (!prev) return prev;
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
title: prev.title || data.title_suggestion || buildTitleFromQuestion(data.question_content || data.text),
|
title: resolveTitleAfterOcr(prev.title, data),
|
||||||
category: MISTAKE_CATEGORIES.includes(data.category_suggestion) ? data.category_suggestion : prev.category,
|
category: MISTAKE_CATEGORIES.includes(data.category_suggestion) ? data.category_suggestion : prev.category,
|
||||||
difficulty: ["easy", "medium", "hard"].includes(data.difficulty_suggestion) ? data.difficulty_suggestion : prev.difficulty,
|
difficulty: ["easy", "medium", "hard"].includes(data.difficulty_suggestion) ? data.difficulty_suggestion : prev.difficulty,
|
||||||
question_content: (mergeQuestionContent(data.question_content, data.text) || prev.question_content || "").slice(0, 8000),
|
question_content: (mergeQuestionContent(data.question_content, data.text) || prev.question_content || "").slice(0, 8000),
|
||||||
@@ -885,6 +921,60 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchRecognizePending = async () => {
|
||||||
|
setBatchRecognizing(true);
|
||||||
|
try {
|
||||||
|
let candidates = [];
|
||||||
|
if (batchRecognizeScope === "current_filtered") {
|
||||||
|
candidates = (items || []).filter((item) => isPendingImageTitle(item.title) && String(item.image_url || "").trim());
|
||||||
|
} else {
|
||||||
|
const res = await api.get("/api/mistakes", {
|
||||||
|
params: {
|
||||||
|
keyword: PENDING_IMAGE_TITLE,
|
||||||
|
sort_by: "created_at",
|
||||||
|
order: "desc"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
candidates = (res.data || []).filter((item) => isPendingImageTitle(item.title) && String(item.image_url || "").trim());
|
||||||
|
}
|
||||||
|
if (!candidates.length) {
|
||||||
|
show(batchRecognizeScope === "current_filtered" ? "当前筛选结果没有可批量识别的待补录图片错题" : "没有可批量识别的待补录图片错题");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ok = 0;
|
||||||
|
let fail = 0;
|
||||||
|
|
||||||
|
for (const item of candidates) {
|
||||||
|
try {
|
||||||
|
const parsed = await api.post("/api/ocr/parse", { image_url: item.image_url });
|
||||||
|
const data = parsed.data;
|
||||||
|
await api.put(`/api/mistakes/${item.id}`, {
|
||||||
|
...item,
|
||||||
|
title: resolveTitleAfterOcr(item.title, data),
|
||||||
|
category: MISTAKE_CATEGORIES.includes(data.category_suggestion) ? data.category_suggestion : item.category,
|
||||||
|
difficulty: ["easy", "medium", "hard"].includes(data.difficulty_suggestion) ? data.difficulty_suggestion : item.difficulty || "medium",
|
||||||
|
question_content: (mergeQuestionContent(data.question_content, data.text) || item.question_content || "").slice(0, 8000),
|
||||||
|
answer: (data.answer || item.answer || "").slice(0, 4000),
|
||||||
|
explanation: (data.explanation || item.explanation || "").slice(0, 8000),
|
||||||
|
note: [item.note, data.text].filter(Boolean).join("\n\n").slice(0, 4000),
|
||||||
|
wrong_count: Number(item.wrong_count || 1)
|
||||||
|
});
|
||||||
|
ok += 1;
|
||||||
|
} catch {
|
||||||
|
fail += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await load();
|
||||||
|
show(`批量识别完成:成功 ${ok},失败 ${fail}`);
|
||||||
|
} catch (error) {
|
||||||
|
show(getApiErrorMessage(error, "批量识别失败"));
|
||||||
|
} finally {
|
||||||
|
setBatchRecognizing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async (e) => {
|
const submit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!form.category) return show("请选择分类");
|
if (!form.category) return show("请选择分类");
|
||||||
@@ -897,7 +987,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
title:
|
title:
|
||||||
form.title ||
|
form.title ||
|
||||||
buildTitleFromQuestion(form.question_content) ||
|
buildTitleFromQuestion(form.question_content) ||
|
||||||
(hasImage ? "待补录图片错题" : "") ||
|
(hasImage ? PENDING_IMAGE_TITLE : "") ||
|
||||||
`错题-${Date.now()}`
|
`错题-${Date.now()}`
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
@@ -937,7 +1027,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
title:
|
title:
|
||||||
editingItem.title ||
|
editingItem.title ||
|
||||||
buildTitleFromQuestion(editingItem.question_content) ||
|
buildTitleFromQuestion(editingItem.question_content) ||
|
||||||
(hasImage ? "待补录图片错题" : "") ||
|
(hasImage ? PENDING_IMAGE_TITLE : "") ||
|
||||||
`错题-${editingItem.id}`,
|
`错题-${editingItem.id}`,
|
||||||
wrong_count: Number(editingItem.wrong_count || 1)
|
wrong_count: Number(editingItem.wrong_count || 1)
|
||||||
});
|
});
|
||||||
@@ -1042,7 +1132,7 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
try {
|
try {
|
||||||
const url = await uploadImageFileOnly(file);
|
const url = await uploadImageFileOnly(file);
|
||||||
await api.post("/api/mistakes", {
|
await api.post("/api/mistakes", {
|
||||||
title: "待补录图片错题",
|
title: PENDING_IMAGE_TITLE,
|
||||||
image_url: url,
|
image_url: url,
|
||||||
category: "其他",
|
category: "其他",
|
||||||
difficulty: "medium",
|
difficulty: "medium",
|
||||||
@@ -1094,6 +1184,18 @@ function MistakeModule({ quickCaptureTask, onQuickCaptureHandled }) {
|
|||||||
>
|
>
|
||||||
<FileDown size={18} /> 导出错题
|
<FileDown size={18} /> 导出错题
|
||||||
</button>
|
</button>
|
||||||
|
<select
|
||||||
|
className="select-min"
|
||||||
|
value={batchRecognizeScope}
|
||||||
|
onChange={(e) => setBatchRecognizeScope(e.target.value)}
|
||||||
|
aria-label="批量识别范围"
|
||||||
|
>
|
||||||
|
<option value="all_pending">识别范围:全部待补录</option>
|
||||||
|
<option value="current_filtered">识别范围:当前筛选</option>
|
||||||
|
</select>
|
||||||
|
<button type="button" className="btn btn-outline btn-pill" onClick={batchRecognizePending} disabled={batchRecognizing}>
|
||||||
|
{batchRecognizing ? "批量识别中…" : "批量识别待补录"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="toolbar-right">
|
<div className="toolbar-right">
|
||||||
<select className="select-min" value={categoryFilter} onChange={(e) => setCategoryFilter(e.target.value)} aria-label="分类">
|
<select className="select-min" value={categoryFilter} onChange={(e) => setCategoryFilter(e.target.value)} aria-label="分类">
|
||||||
@@ -1846,6 +1948,7 @@ 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 [showQuickCamera, setShowQuickCamera] = useState(false);
|
||||||
|
const [quickCameraFabCompact, setQuickCameraFabCompact] = useState(() => readQuickCameraFabCompact());
|
||||||
const [quickCaptureMode, setQuickCaptureMode] = useState("single");
|
const [quickCaptureMode, setQuickCaptureMode] = useState("single");
|
||||||
const [quickCaptureTask, setQuickCaptureTask] = useState(null);
|
const [quickCaptureTask, setQuickCaptureTask] = useState(null);
|
||||||
const quickCaptureInputRef = useRef(null);
|
const quickCaptureInputRef = useRef(null);
|
||||||
@@ -1854,6 +1957,11 @@ export default function App() {
|
|||||||
quickCaptureInputRef.current?.click();
|
quickCaptureInputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setQuickCameraFabCompactPersist = (value) => {
|
||||||
|
setQuickCameraFabCompact(value);
|
||||||
|
writeQuickCameraFabCompact(value);
|
||||||
|
};
|
||||||
|
|
||||||
const onQuickCapturePicked = (filesLike) => {
|
const onQuickCapturePicked = (filesLike) => {
|
||||||
const files = Array.from(filesLike || []);
|
const files = Array.from(filesLike || []);
|
||||||
if (!files.length) return;
|
if (!files.length) return;
|
||||||
@@ -1963,10 +2071,46 @@ export default function App() {
|
|||||||
onChange={(e) => onQuickCapturePicked(e.target.files)}
|
onChange={(e) => onQuickCapturePicked(e.target.files)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button type="button" className="quick-camera-fab" onClick={() => setShowQuickCamera(true)} aria-label="快速拍照录题">
|
<div className="quick-camera-fab-shell">
|
||||||
<Camera size={20} />
|
{quickCameraFabCompact ? (
|
||||||
快速拍题
|
<div className="quick-camera-fab-cluster">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="quick-camera-fab-expand"
|
||||||
|
onClick={() => setQuickCameraFabCompactPersist(false)}
|
||||||
|
aria-label="展开快速拍题标签"
|
||||||
|
title="展开标签"
|
||||||
|
>
|
||||||
|
<Maximize2 size={13} strokeWidth={2.25} aria-hidden />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="quick-camera-fab quick-camera-fab--compact"
|
||||||
|
onClick={() => setShowQuickCamera(true)}
|
||||||
|
aria-label="快速拍照录题(已收纳为图标)"
|
||||||
|
title="快速拍题"
|
||||||
|
>
|
||||||
|
<Camera size={20} strokeWidth={2} aria-hidden />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="quick-camera-fab quick-camera-fab--split" role="group" aria-label="快速拍题">
|
||||||
|
<button type="button" className="quick-camera-fab__open" onClick={() => setShowQuickCamera(true)}>
|
||||||
|
<Camera size={20} strokeWidth={2} aria-hidden />
|
||||||
|
<span>快速拍题</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="quick-camera-fab__collapse"
|
||||||
|
onClick={() => setQuickCameraFabCompactPersist(true)}
|
||||||
|
aria-label="收纳为图标,减少遮挡"
|
||||||
|
title="收纳为图标"
|
||||||
|
>
|
||||||
|
<Minimize2 size={14} strokeWidth={2.25} aria-hidden />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{showQuickCamera && (
|
{showQuickCamera && (
|
||||||
<Modal title="快速拍照录题" onClose={() => setShowQuickCamera(false)}>
|
<Modal title="快速拍照录题" onClose={() => setShowQuickCamera(false)}>
|
||||||
|
|||||||
@@ -846,14 +846,18 @@ textarea {
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-camera-fab {
|
.quick-camera-fab-shell {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 18px;
|
right: 18px;
|
||||||
bottom: calc(18px + env(safe-area-inset-bottom));
|
bottom: calc(18px + env(safe-area-inset-bottom));
|
||||||
z-index: 90;
|
z-index: 90;
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
@@ -864,11 +868,125 @@ textarea {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab--split {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab__open {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 4px 12px 16px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab__collapse {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 12px 14px 12px 10px;
|
||||||
|
border: none;
|
||||||
|
border-left: 1px solid rgba(255, 255, 255, 0.28);
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
color: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab__collapse:hover,
|
||||||
|
.quick-camera-fab__collapse:focus-visible {
|
||||||
|
background: rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab__open:focus-visible,
|
||||||
|
.quick-camera-fab__collapse:focus-visible,
|
||||||
|
.quick-camera-fab--compact:focus-visible,
|
||||||
|
.quick-camera-fab-expand:focus-visible {
|
||||||
|
outline: 2px solid #93c5fd;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 收纳态:展开键 + 相机融为一条胶囊,单阴影 */
|
||||||
|
.quick-camera-fab-cluster {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 42px;
|
||||||
|
border-radius: 21px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 20px rgba(37, 99, 235, 0.26);
|
||||||
|
background: linear-gradient(135deg, #0ea5e9, #2563eb);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab-cluster .quick-camera-fab-expand {
|
||||||
|
flex: 0 0 24px;
|
||||||
|
width: 24px;
|
||||||
|
min-width: 24px;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.07);
|
||||||
|
color: rgba(255, 255, 255, 0.92);
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab-cluster .quick-camera-fab-expand:hover,
|
||||||
|
.quick-camera-fab-cluster .quick-camera-fab-expand:focus-visible {
|
||||||
|
background: rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab-cluster .quick-camera-fab--compact {
|
||||||
|
width: 44px;
|
||||||
|
min-width: 44px;
|
||||||
|
height: auto;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 860px) {
|
@media (max-width: 860px) {
|
||||||
.quick-camera-fab {
|
.quick-camera-fab-shell {
|
||||||
right: 14px;
|
right: 14px;
|
||||||
bottom: calc(76px + env(safe-area-inset-bottom));
|
bottom: calc(76px + env(safe-area-inset-bottom));
|
||||||
padding: 11px 14px;
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab__open {
|
||||||
|
padding: 11px 4px 11px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab__collapse {
|
||||||
|
padding: 11px 12px 11px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab-cluster {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab-cluster .quick-camera-fab-expand {
|
||||||
|
flex-basis: 22px;
|
||||||
|
width: 22px;
|
||||||
|
min-width: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-camera-fab-cluster .quick-camera-fab--compact {
|
||||||
|
width: 42px;
|
||||||
|
min-width: 42px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user