Skip to content

Commit 2230439

Browse files
committed
feat(app): 优化图形用户交互界面
1 parent ade727f commit 2230439

3 files changed

Lines changed: 67 additions & 50 deletions

File tree

setup.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from importlib.metadata import distribution
2+
from sys import platform
23

3-
from cx_Freeze import setup, Executable
4+
from cx_Freeze import Executable, setup
45

56

67
def include_distribution_metadata(distribution_name):
@@ -35,6 +36,16 @@ def include_distribution_metadata(distribution_name):
3536
)
3637
]
3738

39+
if platform == "win32":
40+
executables.append(
41+
Executable(
42+
script="main.py",
43+
base="gui",
44+
icon="./static/XHS-Downloader",
45+
target_name="XHS-Downloader-GUI",
46+
)
47+
)
48+
3849
setup(
3950
name="XHS-Downloader",
4051
options={"build_exe": build_exe_options},

source/GUI/ui_strings.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ def get_ui_messages() -> dict[str, str]:
9393
"history.disabled": _("下载记录功能已关闭"),
9494
"history.disabled_description": _("在程序设置中开启“作品下载记录开关”后可用"),
9595
"history.query_id": _("查询 ExploreID.db 中成功下载的作品 ID"),
96-
"history.query_disabled": _("下载记录已关闭,当前不可用"),
9796
"history.found": _("找到 {0} 条记录"),
9897
"history.range": _("第 {0}–{1} 条,共 {2} 条{3}"),
9998
"history.search_results": _("搜索结果"),

static/GUI/app.js

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@
1111

1212
// 页面标题和副标题保存语言键,具体文本由后端加载的翻译包提供。
1313
const viewCopy = {
14-
"new-task": ["nav.new_task", "page.new_task"],
15-
monitor: ["nav.monitor", "monitor.description"],
16-
queue: ["nav.queue", "page.queue"],
17-
history: ["nav.history", "history.query_id"],
18-
logs: ["nav.logs", "page.logs"],
19-
settings: ["nav.settings", "page.settings"],
20-
about: ["nav.about", ""],
14+
"new-task": {title: "nav.new_task", meta: "page.new_task"},
15+
monitor: {title: "nav.monitor", meta: "monitor.description"},
16+
queue: {title: "nav.queue", meta: "page.queue"},
17+
history: {title: "nav.history", meta: "history.query_id"},
18+
logs: {title: "nav.logs", meta: "page.logs"},
19+
settings: {title: "nav.settings", meta: "page.settings"},
20+
about: {title: "nav.about"},
2121
};
2222

2323
let uiTranslations = Object.create(null);
24+
let translatedUiInitialized = false;
2425

2526
function translateText(key) {
26-
// 翻译包尚未加载时返回空字符串,避免将语言键直接呈现给用户。
27-
return uiTranslations[key] || "";
27+
return uiTranslations[key];
2828
}
2929

3030
function formatTranslated(key, values = []) {
@@ -34,11 +34,6 @@
3434
));
3535
}
3636

37-
function resolveText(value) {
38-
// 动态消息通常传入语言键;后端返回的错误文本不属于语言包时保留原文。
39-
return uiTranslations[value] ?? value;
40-
}
41-
4237
function applyStaticTranslations() {
4338
// 根据当前语言包更新 HTML 中标记 data-i18n 的文本及属性。
4439
document.querySelectorAll("[data-i18n]").forEach((element) => {
@@ -65,7 +60,7 @@
6560
toast.className = `toast ${type}`;
6661
toast.innerHTML = `
6762
<span class="toast-icon">${icon(type === "warning" ? "info" : "check")}</span>
68-
<div><strong>${resolveText(title)}</strong><span>${resolveText(message)}</span></div>
63+
<div><strong>${title}</strong><span>${message}</span></div>
6964
`;
7065
toastRegion.appendChild(toast);
7166
window.setTimeout(() => {
@@ -81,10 +76,18 @@
8176

8277
let monitorMode = false;
8378

79+
function updateViewHeader(name) {
80+
const copy = viewCopy[name];
81+
const metaKey = copy.meta;
82+
viewTitle.textContent = translateText(copy.title);
83+
viewMeta.textContent = metaKey ? translateText(metaKey) : "";
84+
}
85+
8486
function setView(name, options = {}) {
8587
// 监听运行期间锁定其他页面,避免在监听模式下修改配置或创建冲突任务。
8688
if (monitorMode && name !== "monitor" && !options.force) {
87-
showToast("toast.exit_monitor_first", "toast.monitor_switch_locked", "warning");
89+
showToast(
90+
translateText("toast.exit_monitor_first"), translateText("toast.monitor_switch_locked"), "warning");
8891
return;
8992
}
9093
const page = document.querySelector(`.view[data-page="${name}"]`);
@@ -93,7 +96,7 @@
9396
document.querySelectorAll(".view").forEach((item) => item.classList.toggle("active", item === page));
9497
document.querySelectorAll(".nav-item")
9598
.forEach((item) => item.classList.toggle("active", item.dataset.view === name));
96-
[viewTitle.textContent, viewMeta.textContent] = viewCopy[name].map(translateText);
99+
updateViewHeader(name);
97100
if (window.location.hash !== `#${name}`) {
98101
window.history.replaceState(null, "", `#${name}`);
99102
}
@@ -129,7 +132,7 @@
129132
if (document.querySelector(".view.active")?.dataset.page === "monitor") {
130133
viewMeta.textContent = active
131134
? translateText("monitor.reading")
132-
: translateText(viewCopy.monitor[1]);
135+
: translateText(viewCopy.monitor.meta);
133136
}
134137
monitorState.classList.toggle("inactive", !active);
135138
monitorState.innerHTML = `<i></i><span>${translateText(active ? "monitor.active" : "monitor.inactive")}</span>`;
@@ -380,11 +383,6 @@
380383
historyEnabled = Boolean(enabled);
381384
historyView.classList.toggle("is-disabled", !historyEnabled);
382385
historySearch.disabled = !historyEnabled;
383-
viewCopy.history[1] = historyEnabled
384-
? "history.query_id"
385-
: "history.query_disabled";
386-
if (document.querySelector(".view.active") === historyView) viewMeta.textContent = translateText(
387-
viewCopy.history[1]);
388386
if (!historyEnabled) {
389387
historyRequestId += 1;
390388
historyBody.replaceChildren();
@@ -447,7 +445,8 @@
447445
if (requestId !== historyRequestId) return;
448446
renderNativeHistoryPage(result);
449447
} catch (error) {
450-
if (requestId === historyRequestId) showToast("history.read_failed", String(error), "warning");
448+
if (requestId === historyRequestId) showToast(
449+
translateText("history.read_failed"), String(error), "warning");
451450
}
452451
}
453452

@@ -666,14 +665,17 @@
666665
});
667666
});
668667

669-
applyNameFormat("");
670-
671-
refreshQueue();
672-
refreshMonitorQueue();
673-
updateRecordSelection();
674-
renderMonitorState(false);
675-
const initialView = window.location.hash.slice(1);
676-
if (viewCopy[initialView]) setView(initialView);
668+
function initializeTranslatedUi() {
669+
refreshQueue();
670+
refreshMonitorQueue();
671+
updateRecordSelection();
672+
renderMonitorState(monitorMode);
673+
if (!translatedUiInitialized) {
674+
const initialView = window.location.hash.slice(1);
675+
if (viewCopy[initialView]) setView(initialView);
676+
translatedUiInitialized = true;
677+
}
678+
}
677679

678680
// PyWebView 负责所有数据变更,DOM 仅渲染快照;以下变量用于控制轮询和请求去重。
679681
let nativeApi = null;
@@ -736,9 +738,9 @@
736738
stateLine.className = "queue-state-line";
737739
const state = document.createElement("span");
738740
state.className = `status-text ${statusClass(task.state)}`;
739-
state.textContent = translateText(statusLabels[task.state] || task.state);
741+
state.textContent = translateText(statusLabels[task.state]);
740742
const message = document.createElement("span");
741-
message.textContent = task.error || translateText(statusDetails[task.state] || "");
743+
message.textContent = task.error ? task.error : translateText(statusDetails[task.state]);
742744
stateLine.append(state, message);
743745
detail.append(link, stateLine);
744746
item.append(symbol, detail);
@@ -772,7 +774,7 @@
772774
state.className = `status-text ${statusClass(task.state)}`;
773775
if (task.state === "success") state.innerHTML = icon("check");
774776
else state.append(document.createElement("i"));
775-
state.append(document.createTextNode(translateText(statusLabels[task.state] || task.state)));
777+
state.append(document.createTextNode(translateText(statusLabels[task.state])));
776778
item.append(symbol, primary, state);
777779
return item;
778780
}
@@ -900,8 +902,9 @@
900902
applyNameFormat(nameFormat);
901903
const activeView = document.querySelector(".view.active")?.dataset.page;
902904
if (viewCopy[activeView]) {
903-
[viewTitle.textContent, viewMeta.textContent] = viewCopy[activeView].map(translateText);
905+
updateViewHeader(activeView);
904906
}
907+
initializeTranslatedUi();
905908
}
906909

907910
function collectNativeSettings() {
@@ -956,8 +959,7 @@
956959
// 关于页的版本、发布类型和仓库信息均来自后端常量,避免在 HTML 中固化。
957960
const version = String(about.version);
958961
const release = String(about.release);
959-
const displayVersion = [version, release].filter(Boolean).join(" ");
960-
document.getElementById("aboutVersion").textContent = displayVersion;
962+
document.getElementById("aboutVersion").textContent = [version, release].filter(Boolean).join(" ");
961963
document.getElementById("aboutAuthor").textContent = about.author;
962964
document.getElementById("aboutLicense").textContent = about.license;
963965
document.getElementById("aboutRepository").textContent = String(about.repository).replace(/^https?:\/\//, "");
@@ -990,7 +992,7 @@
990992
applyNativeState(await nativeApi.get_state());
991993
return true;
992994
} catch (error) {
993-
showToast("update.failed", String(error), "warning");
995+
showToast(translateText("update.failed"), String(error), "warning");
994996
return false;
995997
} finally {
996998
bridgeRefreshBusy = false;
@@ -1004,7 +1006,7 @@
10041006
await refreshNativeState();
10051007
} catch (error) {
10061008
const message = error instanceof Error ? error.message : String(error);
1007-
showToast(message || translateText("toast.operation_failed"), "", "warning");
1009+
showToast(message, "", "warning");
10081010
}
10091011
}
10101012

@@ -1031,7 +1033,7 @@
10311033
startStatePolling();
10321034
} catch (error) {
10331035
const message = error instanceof Error ? error.message : String(error);
1034-
showToast(message || translateText("toast.operation_failed"), "", "warning");
1036+
showToast(message, "", "warning");
10351037
}
10361038
})();
10371039
});
@@ -1042,7 +1044,7 @@
10421044
await nativeApi.decline_disclaimer();
10431045
} catch (error) {
10441046
const message = error instanceof Error ? error.message : String(error);
1045-
showToast(message || translateText("toast.operation_failed"), "", "warning");
1047+
showToast(message, "", "warning");
10461048
}
10471049
})();
10481050
});
@@ -1060,7 +1062,8 @@
10601062
event.stopImmediatePropagation();
10611063
void runNativeAction(async () => {
10621064
const cancelled = await nativeApi.cancel_task(taskId);
1063-
if (!cancelled) showToast("toast.cannot_cancel", "toast.started_task", "warning");
1065+
if (!cancelled) showToast(
1066+
translateText("toast.cannot_cancel"), translateText("toast.started_task"), "warning");
10641067
});
10651068
return;
10661069
}
@@ -1104,15 +1107,15 @@
11041107
if (!result.ok) throw new Error(result.error);
11051108
settingsLoaded = false;
11061109
await refreshUiTranslations();
1107-
showToast("settings.saved", "settings.reloaded");
1110+
showToast(translateText("settings.saved"), translateText("settings.reloaded"));
11081111
});
11091112
} else if (id === "discardSettings") {
11101113
event.preventDefault();
11111114
event.stopImmediatePropagation();
11121115
void runNativeAction(async () => {
11131116
settingsLoaded = false;
11141117
applyNativeSettings(await nativeApi.get_settings());
1115-
showToast("settings.discarded");
1118+
showToast(translateText("settings.discarded"));
11161119
});
11171120
} else if (id === "openRepository") {
11181121
event.preventDefault();
@@ -1130,7 +1133,11 @@
11301133
void (async () => {
11311134
try {
11321135
const result = await nativeApi.check_update();
1133-
if (result.status !== "ok") throw new Error(result.message);
1136+
if (result.status !== "ok") {
1137+
setUpdateResult(result.message, "warning");
1138+
showToast(translateText("update.failed"), result.message, "warning");
1139+
return;
1140+
}
11341141
const updateAvailable = ["update_available", "stable_available"].includes(result.kind);
11351142
const tone = updateAvailable ? "warning" : "success";
11361143
const title = result.title;
@@ -1139,7 +1146,7 @@
11391146
} catch (error) {
11401147
const message = error?.message || String(error);
11411148
setUpdateResult(`${translateText("update.failed")}: ${message}`, "error");
1142-
showToast("update.failed", message, "warning");
1149+
showToast(translateText("update.failed"), message, "warning");
11431150
} finally {
11441151
setUpdateChecking(false);
11451152
}
@@ -1162,7 +1169,7 @@
11621169
startStatePolling();
11631170
})().catch((error) => {
11641171
startupScreen.hidden = true;
1165-
showToast("toast.language_load_failed", String(error), "warning");
1172+
showToast(translateText("toast.language_load_failed"), String(error), "warning");
11661173
});
11671174
}
11681175

0 commit comments

Comments
 (0)