feat(前端界面): 统一按钮与提示文案,精简文字,优化间距和弹窗对齐
build.yaml / build (push) Successful in 37s

This commit is contained in:
xiaoqidun committed 2026-09-17 23:56:46 +08:00
1 parent 2ffbf573ba
commit 7eac28284a
3 files changed
+50 -42

No files matched your search

+5 -5
View File
@@ -41,11 +41,11 @@
<button id="rotateButton" class="button" type="button" data-short="转" title="顺时针旋转90°" aria-label="旋转" disabled>旋转</button> <button id="rotateButton" class="button" type="button" data-short="转" title="顺时针旋转90°" aria-label="旋转" disabled>旋转</button>
<button id="panButton" class="button" type="button" data-short="拖" title="拖动页面" aria-label="拖动" aria-pressed="false" disabled>拖动</button> <button id="panButton" class="button" type="button" data-short="拖" title="拖动页面" aria-label="拖动" aria-pressed="false" disabled>拖动</button>
<button id="continuousButton" class="button" type="button" data-short="连" title="连续显示" aria-label="连续显示" aria-pressed="false" disabled>连续</button> <button id="continuousButton" class="button" type="button" data-short="连" title="连续显示" aria-label="连续显示" aria-pressed="false" disabled>连续</button>
<button id="annotationButton" class="button" type="button" data-short="注" title="关闭注解" aria-label="关闭注解" aria-pressed="true">注解</button> <button id="annotationButton" class="button" type="button" data-short="注" title="隐藏注解" aria-label="隐藏注解" aria-pressed="true">注解</button>
</div> </div>
<div class="tool-group"> <div class="tool-group">
<select id="exportFormat" class="format-select" title="导出格式" aria-label="导出格式" disabled></select> <select id="exportFormat" class="format-select" title="导出格式" aria-label="导出格式" disabled></select>
<select id="imageDPI" class="format-select dpi-select" title="图片 DPI" aria-label="图片 DPI" disabled> <select id="imageDPI" class="format-select dpi-select" title="图片DPI" aria-label="图片DPI" disabled>
<option value="96">96</option> <option value="96">96</option>
<option value="150">150</option> <option value="150">150</option>
<option value="300" selected>300</option> <option value="300" selected>300</option>
@@ -57,7 +57,7 @@
</header> </header>
<div id="editorTools" class="editor-toolbar" role="toolbar" aria-label="编辑工具" hidden> <div id="editorTools" class="editor-toolbar" role="toolbar" aria-label="编辑工具" hidden>
<div class="editor-group editor-file"> <div class="editor-group editor-file">
<button id="saveButton" class="button" type="button" title="另存为OFD" aria-label="保存OFD" aria-keyshortcuts="Control+S Meta+S">保存</button> <button id="saveButton" class="button" type="button" title="另存为OFD" aria-label="另存为OFD" aria-keyshortcuts="Control+S Meta+S">保存</button>
<button id="undoButton" class="button" type="button" disabled>撤销</button> <button id="undoButton" class="button" type="button" disabled>撤销</button>
<button id="redoButton" class="button" type="button" disabled>重做</button> <button id="redoButton" class="button" type="button" disabled>重做</button>
<span>文件</span> <span>文件</span>
@@ -368,7 +368,7 @@
</aside> </aside>
<section class="viewer-panel" tabindex="0" aria-label="阅读区"> <section class="viewer-panel" tabindex="0" aria-label="阅读区">
<div id="emptyState" class="empty-state">选择 OFD 文件</div> <div id="emptyState" class="empty-state">选择OFD文件</div>
<div id="progressPanel" class="progress-panel" role="status" aria-live="polite" hidden> <div id="progressPanel" class="progress-panel" role="status" aria-live="polite" hidden>
<div class="progress-spinner"></div> <div class="progress-spinner"></div>
<div id="progressLabel" class="progress-label">加载中</div> <div id="progressLabel" class="progress-label">加载中</div>
@@ -452,7 +452,7 @@
</section> </section>
<section class="doc-font-panel" aria-labelledby="docFontPanelTitle"> <section class="doc-font-panel" aria-labelledby="docFontPanelTitle">
<div class="panel-row"> <div class="panel-row">
<div id="docFontPanelTitle" class="panel-title compact">OFD 字体</div> <div id="docFontPanelTitle" class="panel-title compact">文档字体</div>
<span id="docFontSummary" class="count-pill">0</span> <span id="docFontSummary" class="count-pill">0</span>
</div> </div>
<div id="docFontList" class="doc-font-list"></div> <div id="docFontList" class="doc-font-list"></div>
+5
View File
@@ -798,6 +798,10 @@ button:not(:disabled):hover {
gap: 8px; gap: 8px;
} }
.form-actions > button:only-child {
grid-column: -2;
}
.form-actions button[type="submit"] { .form-actions button[type="submit"] {
border-color: var(--accent); border-color: var(--accent);
background: var(--accent); background: var(--accent);
@@ -1795,6 +1799,7 @@ button.outline-link:hover {
.annotation-detail { .annotation-detail {
display: grid; display: grid;
gap: 8px; gap: 8px;
margin-bottom: 12px;
white-space: pre-wrap; white-space: pre-wrap;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
+40 -37
View File
@@ -8,7 +8,7 @@ const PAGE_CACHE_LIMIT = 16;
const PAGE_CACHE_BYTES = 32 * 1024 * 1024; const PAGE_CACHE_BYTES = 32 * 1024 * 1024;
const OBJECT_CLIPBOARD_TYPE = "application/x-ofdgo-objects"; const OBJECT_CLIPBOARD_TYPE = "application/x-ofdgo-objects";
const STATUS = { const STATUS = {
ready: "选择 OFD 文件", ready: "选择OFD文件",
opening: "正在打开文档", opening: "正在打开文档",
engine: "正在准备引擎", engine: "正在准备引擎",
recovering: "正在恢复引擎", recovering: "正在恢复引擎",
@@ -1171,7 +1171,7 @@ async function loadImportFile() {
return; return;
} }
state.importPageCount = info?.pageCount || 0; state.importPageCount = info?.pageCount || 0;
el.importPages.title = state.importPageCount ? `页码 1-${state.importPageCount}` : "页码"; el.importPages.title = state.importPageCount ? `页码1-${state.importPageCount}` : "页码";
el.importStatus.textContent = info?.signed ? "保留签章外观,原签名不再有效" : ""; el.importStatus.textContent = info?.signed ? "保留签章外观,原签名不再有效" : "";
el.importSubmit.disabled = !state.importPageCount; el.importSubmit.disabled = !state.importPageCount;
} catch (err) { } catch (err) {
@@ -1306,7 +1306,7 @@ async function toggleEditor() {
function confirmTextReflow(item) { function confirmTextReflow(item) {
return canEditObject(item, "reflow") && (canEditObject(item, "layoutKnown") return canEditObject(item, "reflow") && (canEditObject(item, "layoutKnown")
|| window.confirm("原文排版未知,此操作将重新排版。继续?")); || window.confirm("此操作将重新排版原文,继续?"));
} }
function openCreatePanel() { function openCreatePanel() {
@@ -2177,7 +2177,7 @@ async function boot() {
} }
setProgress("引擎准备完成", 70); setProgress("引擎准备完成", 70);
await loadExportFormats(); await loadExportFormats();
setEmpty("选择 OFD 文件"); setEmpty(STATUS.ready);
updateFontSummary(); updateFontSummary();
renderFontList(); renderFontList();
updateControls(); updateControls();
@@ -2405,7 +2405,7 @@ async function loadWASM() {
} else if (data.completed === data.total) { } else if (data.completed === data.total) {
setProgress("正在收尾", null); setProgress("正在收尾", null);
} else { } else {
setProgress(`正在导出 ${data.completed} / ${data.total} 页`, data.completed / data.total * 100); setProgress(`正在导出${data.completed}/${data.total}页`, data.completed / data.total * 100);
} }
} }
} }
@@ -2499,7 +2499,7 @@ async function openOFD(file) {
} }
if (!isOFDFile(file)) { if (!isOFDFile(file)) {
el.ofdInput.value = ""; el.ofdInput.value = "";
showError(new Error("选择 OFD 文件"), !state.doc); showError(new Error(STATUS.ready), !state.doc);
return; return;
} }
if (!discardChanges()) { if (!discardChanges()) {
@@ -2570,7 +2570,7 @@ async function openSelectedFonts(event) {
try { try {
const fonts = []; const fonts = [];
for (let i = 0; i < files.length; i += 1) { for (let i = 0; i < files.length; i += 1) {
setProgress(`正在读取字体 ${i + 1} / ${files.length}`, 10 + Math.round(i / files.length * 60)); setProgress(`正在读取字体${i + 1}/${files.length}`, 10 + Math.round(i / files.length * 60));
const file = files[i]; const file = files[i];
fonts.push(fontManager.record(file.name, new Uint8Array(await file.arrayBuffer()), "upload")); fonts.push(fontManager.record(file.name, new Uint8Array(await file.arrayBuffer()), "upload"));
} }
@@ -2679,7 +2679,7 @@ async function loadDocumentLocalFonts(available, openSeq = state.openSeq) {
const emptyStatus = available.length === 0 ? "暂无系统字体" : "暂无匹配字体"; const emptyStatus = available.length === 0 ? "暂无系统字体" : "暂无匹配字体";
const fonts = []; const fonts = [];
for (let i = 0; i < selected.length; i += 1) { for (let i = 0; i < selected.length; i += 1) {
setProgress(`正在读取字体 ${i + 1} / ${selected.length}`, 20 + Math.round(i / selected.length * 60)); setProgress(`正在读取字体${i + 1}/${selected.length}`, 20 + Math.round(i / selected.length * 60));
const item = selected[i]; const item = selected[i];
const data = await fontManager.read(item); const data = await fontManager.read(item);
if (openSeq !== state.openSeq) { if (openSeq !== state.openSeq) {
@@ -3225,7 +3225,7 @@ async function exportFile(whole, indices = null, value = el.exportFormat.value)
const dpi = exportFormatUsesDPI(format.value) ? currentImageDPI() : 0; const dpi = exportFormatUsesDPI(format.value) ? currentImageDPI() : 0;
state.exporting = true; state.exporting = true;
updateControls(); updateControls();
setBusy(true, `正在生成 ${label}`, null, whole ? STATUS.exporting : STATUS.pageExporting); setBusy(true, `正在生成${label}`, null, whole ? STATUS.exporting : STATUS.pageExporting);
try { try {
const file = window.showSaveFilePicker ? await window.showSaveFilePicker({ const file = window.showSaveFilePicker ? await window.showSaveFilePicker({
suggestedName: fileName, suggestedName: fileName,
@@ -3240,7 +3240,7 @@ async function exportFile(whole, indices = null, value = el.exportFormat.value)
if (!await canvasEditor.commitText()) return; if (!await canvasEditor.commitText()) return;
if (!await canvasEditor.commitCrop()) return; if (!await canvasEditor.commitCrop()) return;
openSeq = state.openSeq; openSeq = state.openSeq;
setBusy(true, `正在生成 ${label}`, null, whole ? STATUS.exporting : STATUS.pageExporting); setBusy(true, `正在生成${label}`, null, whole ? STATUS.exporting : STATUS.pageExporting);
} }
const result = saving ? await callWASM("ofdgoSaveDocument", indices, file) : whole const result = saving ? await callWASM("ofdgoSaveDocument", indices, file) : whole
? await callWASM("ofdgoExportDocument", format.value, dpi, indices, file) ? await callWASM("ofdgoExportDocument", format.value, dpi, indices, file)
@@ -3311,7 +3311,7 @@ function renderPageFlow() {
const placeholder = document.createElement("div"); const placeholder = document.createElement("div");
placeholder.className = "page-placeholder"; placeholder.className = "page-placeholder";
placeholder.textContent = `第 ${page.index + 1} 页`; placeholder.textContent = `第${page.index + 1}页`;
shell.append(surface, placeholder); shell.append(surface, placeholder);
fragment.append(shell); fragment.append(shell);
@@ -3770,7 +3770,7 @@ async function selectDocumentText() {
const count = state.doc.pageCount; const count = state.doc.pageCount;
try { try {
for (let index = 0; index < count; index += 1) { for (let index = 0; index < count; index += 1) {
setStatus(`正在全选 ${index + 1} / ${count} 页`); setStatus(`正在全选${index + 1}/${count}页`);
const text = await callWASM("ofdgoPageTextString", index); const text = await callWASM("ofdgoPageTextString", index);
if (!active()) { if (!active()) {
return; return;
@@ -3857,7 +3857,7 @@ async function pasteEditorContent(event) {
if (token) { if (token) {
const clipboard = state.objectClipboard; const clipboard = state.objectClipboard;
if (!clipboard || token !== clipboard.token) { if (!clipboard || token !== clipboard.token) {
setStatus("复制内容已失效,请重新复制"); setStatus("内容已失效,请重新复制");
return; return;
} }
const openSeq = state.openSeq; const openSeq = state.openSeq;
@@ -4274,7 +4274,7 @@ async function searchDocument() {
if (seq !== state.searchSeq || openSeq !== state.openSeq) { if (seq !== state.searchSeq || openSeq !== state.openSeq) {
return; return;
} }
el.searchStatus.textContent = `正在搜索 ${page + 1} / ${pageCount} 页`; el.searchStatus.textContent = `正在搜索${page + 1}/${pageCount}页`;
if (!matches?.length) { if (!matches?.length) {
continue; continue;
} }
@@ -4316,7 +4316,7 @@ function renderSearchResults() {
button.dataset.matchIndex = String(i); button.dataset.matchIndex = String(i);
const page = document.createElement("span"); const page = document.createElement("span");
page.className = "search-page"; page.className = "search-page";
page.textContent = `第 ${match.page + 1} 页`; page.textContent = `第${match.page + 1}页`;
const text = document.createElement("span"); const text = document.createElement("span");
const mark = document.createElement("mark"); const mark = document.createElement("mark");
mark.textContent = match.text; mark.textContent = match.text;
@@ -4434,7 +4434,7 @@ function createOutlineList(outlines, path = []) {
const page = document.createElement("span"); const page = document.createElement("span");
page.className = "outline-page"; page.className = "outline-page";
page.textContent = String(outline.page); page.textContent = String(outline.page);
page.setAttribute("aria-label", `第 ${outline.page} 页`); page.setAttribute("aria-label", `第${outline.page}页`);
link.append(page); link.append(page);
} }
if (outline.page || state.editing) { if (outline.page || state.editing) {
@@ -4628,7 +4628,7 @@ function renderPageList() {
button.type = "button"; button.type = "button";
button.className = "page-list-item"; button.className = "page-list-item";
button.dataset.pageIndex = String(page.index); button.dataset.pageIndex = String(page.index);
button.title = `${formatSize(page.width)} x ${formatSize(page.height)} mm`; button.title = `${formatSize(page.width)}×${formatSize(page.height)}mm`;
setPageItemCurrent(button, page.index === state.pageIndex); setPageItemCurrent(button, page.index === state.pageIndex);
layoutThumbnail(button, page); layoutThumbnail(button, page);
@@ -4639,11 +4639,11 @@ function renderPageList() {
const label = document.createElement("span"); const label = document.createElement("span");
label.className = "thumb-label"; label.className = "thumb-label";
label.textContent = `第 ${page.index + 1} 页`; label.textContent = `第${page.index + 1}页`;
const size = document.createElement("span"); const size = document.createElement("span");
size.className = "thumb-size"; size.className = "thumb-size";
size.textContent = `${formatSize(page.width)} x ${formatSize(page.height)} mm`; size.textContent = button.title;
button.append(thumb, label, size); button.append(thumb, label, size);
if (state.editing && pageCan("move", page.index)) enablePageDrag(button, page.index); if (state.editing && pageCan("move", page.index)) enablePageDrag(button, page.index);
@@ -4949,8 +4949,8 @@ function renderAttachments() {
const item = document.createElement("button"); const item = document.createElement("button");
item.type = "button"; item.type = "button";
item.className = "attachment-item"; item.className = "attachment-item";
item.title = `下载 ${attachment.fileName}`; item.title = `下载:${attachment.fileName}`;
item.setAttribute("aria-label", `下载 ${attachment.name}`); item.setAttribute("aria-label", `下载:${attachment.name}`);
const name = document.createElement("span"); const name = document.createElement("span");
name.className = "attachment-name"; name.className = "attachment-name";
name.textContent = attachment.name; name.textContent = attachment.name;
@@ -5043,12 +5043,12 @@ function signatureSummary(signatures) {
const unchecked = signatures.filter((signature) => signature.status === "unchecked").length; const unchecked = signatures.filter((signature) => signature.status === "unchecked").length;
const parts = [String(signatures.length)]; const parts = [String(signatures.length)];
if (invalid) { if (invalid) {
parts.push(`异常 ${invalid}`); parts.push(`异常${invalid}`);
} }
if (unchecked) { if (unchecked) {
parts.push(`未验 ${unchecked}`); parts.push(`未验${unchecked}`);
} }
return parts.length > 1 ? parts.join(" · ") : `通过 ${signatures.length}`; return parts.length > 1 ? parts.join(" · ") : `通过${signatures.length}`;
} }
function signatureNameNode(signature) { function signatureNameNode(signature) {
@@ -5099,7 +5099,7 @@ function signatureReferenceText(signature) {
return ""; return "";
} }
const status = signatureReferenceStatus(signature); const status = signatureReferenceStatus(signature);
return `${status === "ok" ? "通过" : status === "fail" ? "失败" : "未验"} ${passed} / ${count}`; return `${status === "ok" ? "通过" : status === "fail" ? "失败" : "未验"}${passed}/${count}`;
} }
function signatureReferenceStatus(signature) { function signatureReferenceStatus(signature) {
@@ -5290,13 +5290,13 @@ function fontSummary(fonts) {
const missing = fonts.filter((font) => font.status === "missing").length; const missing = fonts.filter((font) => font.status === "missing").length;
const fallback = fonts.filter((font) => font.status === "fallback").length; const fallback = fonts.filter((font) => font.status === "fallback").length;
if (missing || fallback) { if (missing || fallback) {
return `${fonts.length} · 缺失 ${missing} · 回退 ${fallback}`; return [String(fonts.length), missing ? `缺失${missing}` : "", fallback ? `回退${fallback}` : ""].filter(Boolean).join(" · ");
} }
return `共 ${fonts.length}`; return `共${fonts.length}`;
} }
function pageStatus(index, pageCount) { function pageStatus(index, pageCount) {
return `第 ${index + 1} / ${pageCount} 页`; return `第${index + 1}/${pageCount}页`;
} }
function statusText(status) { function statusText(status) {
@@ -5317,11 +5317,11 @@ function statusText(status) {
function fontResult(font) { function fontResult(font) {
const parts = []; const parts = [];
if (font.matched) { if (font.matched) {
parts.push(font.embedded || font.status === "embedded" ? `内嵌 ${font.matched}` : `匹配 ${font.matched}`); parts.push(font.embedded || font.status === "embedded" ? `内嵌:${font.matched}` : `匹配:${font.matched}`);
} }
if (font.matchedFace?.fullName) { if (font.matchedFace?.fullName) {
parts.push(font.matchedFace.fullName); parts.push(font.matchedFace.fullName);
if (font.matchedFace.index > 0) parts.push(`索引 ${font.matchedFace.index}`); if (font.matchedFace.index > 0) parts.push(`索引${font.matchedFace.index}`);
} }
if (font.detail) { if (font.detail) {
parts.push(font.detail); parts.push(font.detail);
@@ -5332,10 +5332,10 @@ function fontResult(font) {
function fontMeta(font) { function fontMeta(font) {
const parts = []; const parts = [];
if (font.familyName && font.familyName !== font.fontName) { if (font.familyName && font.familyName !== font.fontName) {
parts.push(`字族 ${font.familyName}`); parts.push(`字族:${font.familyName}`);
} }
if (font.charset) { if (font.charset) {
parts.push(`字集 ${font.charset}`); parts.push(`字集:${font.charset}`);
} }
return parts.join(" · "); return parts.join(" · ");
} }
@@ -5528,7 +5528,7 @@ function setScale(nextScale, updateStatus = true, fitMode = "free") {
} }
el.zoomLabel.textContent = `${Math.round(state.scale * 100)}%`; el.zoomLabel.textContent = `${Math.round(state.scale * 100)}%`;
if (updateStatus && state.doc && !state.exporting) { if (updateStatus && state.doc && !state.exporting) {
setStatus(`第 ${state.pageIndex + 1} / ${state.doc.pageCount} 页`); setStatus(pageStatus(state.pageIndex, state.doc.pageCount));
} }
updateControls(); updateControls();
} }
@@ -5688,6 +5688,9 @@ function updateObjectControls(item, reset = false) {
el.objectDistribute.disabled = el.objectAlign.disabled || !item.items || item.items.length < 3; el.objectDistribute.disabled = el.objectAlign.disabled || !item.items || item.items.length < 3;
el.editObjectButton.disabled = disabled || Boolean(item.items) || !canEditObject(item, "enter") && (item.type === "PathObject" el.editObjectButton.disabled = disabled || Boolean(item.items) || !canEditObject(item, "enter") && (item.type === "PathObject"
|| !canEditObject(item, item.type === "TextObject" ? "reflow" : item.type === "ImageObject" ? "replaceImage" : "update")); || !canEditObject(item, item.type === "TextObject" ? "reflow" : item.type === "ImageObject" ? "replaceImage" : "update"));
el.editObjectButton.textContent = canEditObject(item, "enter") ? "进入" : item?.type === "ImageObject" ? "替换" : "修改";
el.editObjectButton.title = canEditObject(item, "enter") ? "进入组合" : item?.type === "ImageObject" ? "替换图片" : "修改对象";
el.editObjectButton.setAttribute("aria-label", el.editObjectButton.title);
el.multiSelectButton.disabled = !state.editing || !canvasEditor.enabled || !state.ready || state.exporting; el.multiSelectButton.disabled = !state.editing || !canvasEditor.enabled || !state.ready || state.exporting;
const selection = selectedText(item); const selection = selectedText(item);
const text = selection ? currentTextStyle() : state.textDefaults; const text = selection ? currentTextStyle() : state.textDefaults;
@@ -5753,7 +5756,7 @@ function updateObjectControls(item, reset = false) {
function updateAnnotationButton() { function updateAnnotationButton() {
el.annotationButton.setAttribute("aria-pressed", String(state.renderAnnotations)); el.annotationButton.setAttribute("aria-pressed", String(state.renderAnnotations));
el.annotationButton.title = state.renderAnnotations ? "关闭注解" : "开启注解"; el.annotationButton.title = state.renderAnnotations ? "隐藏注解" : "显示注解";
el.annotationButton.setAttribute("aria-label", el.annotationButton.title); el.annotationButton.setAttribute("aria-label", el.annotationButton.title);
} }
@@ -5904,12 +5907,12 @@ function currentImageDPI() {
function formatBytes(size) { function formatBytes(size) {
if (!Number.isFinite(size) || size <= 0) { if (!Number.isFinite(size) || size <= 0) {
return "0 KB"; return "0KB";
} }
if (size < 1024 * 1024) { if (size < 1024 * 1024) {
return `${Math.round(size / 1024)} KB`; return `${Math.round(size / 1024)}KB`;
} }
return `${(size / 1024 / 1024).toFixed(1)} MB`; return `${(size / 1024 / 1024).toFixed(1)}MB`;
} }
function nextFrame() { function nextFrame() {