更新后端服务UI

客户端重构部分界面UI
This commit is contained in:
QWQLwToo
2026-06-30 11:45:52 +08:00
parent 7745e7a2d4
commit 1d0e862299
13 changed files with 942 additions and 121 deletions
@@ -20,6 +20,7 @@ import LegacyJsonView from "./views/LegacyJsonView.vue";
import ReleasesView from "./views/ReleasesView.vue";
import SourcesView from "./views/SourcesView.vue";
import SystemView from "./views/SystemView.vue";
import AuditLogView from "./views/AuditLogView.vue";
import { adminFetch, toChineseError, uploadAdminFile } from "./api/admin";
import { createAuthStore } from "./stores/auth";
import { createDashboardStore } from "./stores/dashboard";
@@ -78,7 +79,7 @@ const systemStore = createSystemStore();
const { csrf, captcha, bootstrap: authBootstrap, loginForm, passwordForm } = authStore;
const { dashboard, sourceCheckJobs } = dashboardStore;
const { page: feedbackPage, selected: selectedFeedback, filters: feedbackFilters, update: feedbackUpdate, commentDraft } = feedbackStore;
const { page: feedbackPage, selected: selectedFeedback, filters: feedbackFilters, update: feedbackUpdate, commentDraft, selectedCodes: feedbackSelectedCodes, detailTab: feedbackDetailTab, tagInput: feedbackTagInput } = feedbackStore;
const { releases, notices: releaseNotices, selectedNotice, noticeDraft, uploadDraft } = releaseStore;
const { sync: legacySync, documents: legacyDocuments, drafts: legacyDrafts, modal: legacyModal, activeMediaCategoryIndex } = legacyStore;
const { sources, endpoints, draft: sourceDraft } = sourceStore;
@@ -92,6 +93,7 @@ const routes: RouteItem[] = [
{ path: "/admin/legacy/media-types", label: "媒体源 JSON", description: "维护旧客户端媒体源结构", icon: ClipboardList },
{ path: "/admin/sources", label: "来源目录", description: "媒体/数据源目录和健康检测", icon: Network },
{ path: "/admin/endpoints", label: "客户端接口", description: "新版客户端动态接口配置", icon: Code2 },
{ path: "/admin/audit", label: "审计日志", description: "操作审计、登录记录与安全事件", icon: ShieldCheck },
{ path: "/admin/system", label: "系统运维", description: "数据库、旧项目同步、安全、健康与审计", icon: Database },
];
@@ -100,7 +102,7 @@ const navGroups = [
{ label: "反馈", items: routes.filter((item) => ["/admin/feedbacks"].includes(item.path)) },
{ label: "发布与兼容", items: routes.filter((item) => ["/admin/releases", "/admin/legacy/update-info", "/admin/legacy/media-types"].includes(item.path)) },
{ label: "客户端接口", items: routes.filter((item) => ["/admin/sources", "/admin/endpoints"].includes(item.path)) },
{ label: "系统运维", items: routes.filter((item) => ["/admin/system"].includes(item.path)) },
{ label: "系统运维", items: routes.filter((item) => ["/admin/audit", "/admin/system"].includes(item.path)) },
];
const pageMeta = computed(() => routes.find((item) => item.path === currentPath.value) || routes[0]);
@@ -270,6 +272,16 @@ const viewContext = computed(() => ({
feedbackOption: feedbackOption.value,
feedbackPage: feedbackPage.value,
feedbackUpdate,
feedbackSelectedCodes: feedbackSelectedCodes.value,
feedbackDetailTab: feedbackDetailTab.value,
feedbackTagInput: feedbackTagInput.value,
setFeedbackDetailTab: (tab: string) => { feedbackDetailTab.value = tab as any; },
setFeedbackTagInput: (val: string) => { feedbackTagInput.value = val; },
addFeedbackTag,
removeFeedbackTag,
toggleFeedbackCode,
toggleAllFeedbackCodes,
bulkUpdateFeedbacks,
formatBytes,
formatHealthOutput,
healthOption: healthOption.value,
@@ -476,6 +488,7 @@ async function load() {
if (currentPath.value === "/admin/releases") await loadReleases();
if (currentPath.value === "/admin/sources") await loadSources();
if (currentPath.value === "/admin/endpoints") await loadEndpoints();
if (currentPath.value === "/admin/audit") await loadAudit();
if (currentPath.value === "/admin/system") await loadSystem({ preserveForms: true });
const legacyName = activeLegacyName.value;
if (legacyName) await loadLegacy(legacyName);
@@ -500,13 +513,17 @@ async function loadSystem(options: LoadSystemOptions = {}) {
]);
}
async function loadFeedbacks() {
async function loadFeedbacks(page?: number) {
if (page !== undefined) feedbackFilters.page = page;
const params = new URLSearchParams({ page: String(feedbackFilters.page), perPage: String(feedbackFilters.perPage) });
if (feedbackFilters.q) params.set("q", feedbackFilters.q);
if (feedbackFilters.status) params.set("status", feedbackFilters.status);
if (feedbackFilters.priority) params.set("priority", feedbackFilters.priority);
if ((feedbackFilters as any).category) params.set("category", (feedbackFilters as any).category);
if ((feedbackFilters as any).assignee) params.set("assignee", (feedbackFilters as any).assignee);
const data = await api<{ page: any }>(`/api/admin/feedbacks?${params}`);
feedbackPage.value = data.page || { items: [], total: 0, page: 1, perPage: 20 };
feedbackSelectedCodes.value = [];
}
async function openFeedback(item: any) {
@@ -516,6 +533,48 @@ async function openFeedback(item: any) {
feedbackUpdate.priority = data.feedback.priority || "normal";
feedbackUpdate.statusDetail = data.feedback.statusDetail || "";
feedbackUpdate.publicReply = data.feedback.publicReply || "";
feedbackUpdate.assignee = data.feedback.assignee || "";
feedbackUpdate.tags = data.feedback.tags ? [...data.feedback.tags] : [];
feedbackDetailTab.value = "info";
}
function toggleFeedbackCode(code: string) {
const idx = feedbackSelectedCodes.value.indexOf(code);
if (idx >= 0) feedbackSelectedCodes.value.splice(idx, 1);
else feedbackSelectedCodes.value.push(code);
}
function toggleAllFeedbackCodes() {
const items: any[] = feedbackPage.value?.items || [];
if (feedbackSelectedCodes.value.length === items.length) {
feedbackSelectedCodes.value = [];
} else {
feedbackSelectedCodes.value = items.map((item: any) => item.code);
}
}
async function bulkUpdateFeedbacks(payload: { status?: string; assignee?: string; tags?: string[] }) {
if (!feedbackSelectedCodes.value.length) return;
await guarded(async () => {
await api("/api/admin/feedbacks/bulk", {
method: "PATCH",
body: JSON.stringify({ codes: feedbackSelectedCodes.value, ...payload }),
});
setToast(`已批量更新 ${feedbackSelectedCodes.value.length} 条工单`);
feedbackSelectedCodes.value = [];
await loadFeedbacks();
});
}
function addFeedbackTag() {
const tag = feedbackTagInput.value.trim();
if (!tag || feedbackUpdate.tags.includes(tag)) { feedbackTagInput.value = ""; return; }
feedbackUpdate.tags = [...feedbackUpdate.tags, tag];
feedbackTagInput.value = "";
}
function removeFeedbackTag(tag: string) {
feedbackUpdate.tags = feedbackUpdate.tags.filter((t) => t !== tag);
}
async function saveFeedbackUpdate() {
@@ -1597,6 +1656,7 @@ function connectAdminEvents() {
<LegacyJsonView v-else-if="activeLegacyName" :ctx="viewContext" />
<SourcesView v-else-if="currentPath === '/admin/sources'" :ctx="viewContext" />
<EndpointsView v-else-if="currentPath === '/admin/endpoints'" :ctx="viewContext" />
<AuditLogView v-else-if="currentPath === '/admin/audit'" :ctx="viewContext" />
<SystemView v-else-if="currentPath === '/admin/system'" :ctx="viewContext" />
</section>
</main>