更新后端服务UI
客户端重构部分界面UI
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user