From a325298eb6af21b597f46b0507813152d67cf25e Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sun, 18 Oct 2026 15:04:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=BB=B4=E5=9F=BA=E9=98=85=E8=AF=BB?= =?UTF-8?q?=E9=A1=B5=E8=A1=A5=E5=8E=86=E5=8F=B2=E4=B8=8E=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=AF=B9=E6=AF=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 阅读页新增阅读/历史模式切换 - 历史列表展示修订号、作者、摘要、时间与小修改标记 - 支持查看任意历史修订的渲染内容 - 与当前版本的逐行差异对比,新增/删除行高亮 - 支持撤销到指定修订,带二次确认 - 修复导航栈与修订列表变量重名 - 补充维基历史相关 API:历史列表、修订详情、差异对比、撤销 - 中英文各补 13 个文案 --- .../src/components/ui/skin-site/WikiView.vue | 265 +++++++++++++++++- .../app-frontend/src/helpers/skin-site-api.ts | 66 +++++ .../app-frontend/src/locales/en-US/index.json | 15 +- .../app-frontend/src/locales/zh-CN/index.json | 15 +- 4 files changed, 352 insertions(+), 9 deletions(-) diff --git a/apps/app-frontend/src/components/ui/skin-site/WikiView.vue b/apps/app-frontend/src/components/ui/skin-site/WikiView.vue index 4ad2544..e05644a 100644 --- a/apps/app-frontend/src/components/ui/skin-site/WikiView.vue +++ b/apps/app-frontend/src/components/ui/skin-site/WikiView.vue @@ -6,12 +6,19 @@ import { computed, onMounted, ref } from 'vue' import { type SkinSiteWikiPageDetail, skinSiteWikiPage, + skinSiteWikiDiff, + skinSiteWikiHistory, + skinSiteWikiRevert, + skinSiteWikiRevision, + type SkinSiteWikiDiffLine, + type SkinSiteWikiNode, + type SkinSiteWikiRevisionInfo, } from '@/helpers/skin-site-api' import WikiNodeRenderer from './wiki/WikiNodeRenderer.vue' const { formatMessage } = useVIntl() -const { handleError } = injectNotificationManager() +const { handleError, addNotification } = injectNotificationManager() const props = withDefaults(defineProps<{ initialTitle?: string }>(), { initialTitle: '', @@ -40,13 +47,50 @@ const messages = defineMessages({ revision: { id: 'app.skin-site.wiki.revision', defaultMessage: 'Revision #{id}' }, categories: { id: 'app.skin-site.wiki.categories', defaultMessage: 'Categories' }, back: { id: 'app.skin-site.wiki.back', defaultMessage: 'Back' }, + history: { id: 'app.skin-site.wiki.history', defaultMessage: 'History' }, + read: { id: 'app.skin-site.wiki.read', defaultMessage: 'Read' }, + historyEmpty: { + id: 'app.skin-site.wiki.history-empty', + defaultMessage: 'No revisions yet.', + }, + noSummary: { + id: 'app.skin-site.wiki.no-summary', + defaultMessage: 'No edit summary', + }, + minor: { id: 'app.skin-site.wiki.minor', defaultMessage: 'Minor' }, + viewRevision: { id: 'app.skin-site.wiki.view-revision', defaultMessage: 'View' }, + revertTo: { + id: 'app.skin-site.wiki.revert-to', + defaultMessage: 'Revert to this', + }, + confirmRevert: { + id: 'app.skin-site.wiki.confirm-revert', + defaultMessage: 'Confirm revert', + }, + revertDone: { + id: 'app.skin-site.wiki.revert-done', + defaultMessage: 'Page reverted to the selected revision.', + }, + revisionPreview: { + id: 'app.skin-site.wiki.revision-preview', + defaultMessage: 'Preview of revision #{id}', + }, + closePreview: { + id: 'app.skin-site.wiki.close-preview', + defaultMessage: 'Close preview', + }, + diffTitle: { + id: 'app.skin-site.wiki.diff-title', + defaultMessage: 'Line diff against the current revision', + }, + cancel: { id: 'app.skin-site.wiki.cancel', defaultMessage: 'Cancel' }, }) const titleInput = ref(props.initialTitle) const page = ref(null) const loading = ref(false) const notFound = ref(false) -const history = ref([]) +const navStack = ref([]) const currentTitle = computed(() => page.value?.title ?? '') @@ -64,8 +108,8 @@ async function openPage(title: string) { return } page.value = res.payload - if (currentTitle.value && history.value[history.value.length - 1] !== currentTitle.value) { - history.value.push(currentTitle.value) + if (currentTitle.value && navStack.value[navStack.value.length - 1] !== currentTitle.value) { + navStack.value.push(currentTitle.value) } } catch (error) { handleError(error) @@ -77,7 +121,7 @@ async function openPage(title: string) { } function goBack() { - const prev = history.value.pop() + const prev = navStack.value.pop() if (prev) void openPage(prev) } @@ -89,6 +133,77 @@ function formatDate(ts: number): string { } } +const mode = ref<'read' | 'history'>('read') +const history = ref([]) +const historyLoading = ref(false) + +const selectedRevisionId = ref(null) +const selectedRevisionNodes = ref(null) +const diffLines = ref([]) +const pendingRevertId = ref(null) +const reverting = ref(false) + +async function showHistory() { + if (!page.value) return + mode.value = 'history' + historyLoading.value = true + try { + const res = await skinSiteWikiHistory(page.value.title) + if (!res.success) throw new Error(res.errorMessage) + history.value = res.payload.items ?? [] + } catch (error) { + handleError(error) + history.value = [] + } finally { + historyLoading.value = false + } +} + +function backToRead() { + mode.value = 'read' + selectedRevisionNodes.value = null + diffLines.value = [] + pendingRevertId.value = null +} + +async function viewRevision(revisionId: number) { + if (!page.value) return + try { + const res = await skinSiteWikiRevision(page.value.title, revisionId) + if (!res.success) throw new Error(res.errorMessage) + selectedRevisionId.value = revisionId + selectedRevisionNodes.value = res.payload.nodes ?? [] + diffLines.value = [] + if (revisionId !== page.value.revisionId) { + const diff = await skinSiteWikiDiff(page.value.title, revisionId, page.value.revisionId) + if (diff.success) diffLines.value = diff.payload ?? [] + } + } catch (error) { + handleError(error) + } +} + +async function revertToRevision(revisionId: number) { + if (!page.value || reverting.value) return + reverting.value = true + try { + const res = await skinSiteWikiRevert({ + title: page.value.title, + revisionId, + baseRevisionId: page.value.revisionId, + }) + if (!res.success) throw new Error(res.errorMessage) + page.value = res.payload + pendingRevertId.value = null + backToRead() + addNotification({ type: 'success', title: formatMessage(messages.revertDone) }) + } catch (error) { + handleError(error) + } finally { + reverting.value = false + } +} + function onNavigate(title: string) { if (title) void openPage(title) } @@ -102,7 +217,7 @@ onMounted(() => {
+
@@ -142,7 +275,7 @@ onMounted(() => { > {{ formatMessage(messages.empty) }}

- diff --git a/apps/app-frontend/src/helpers/skin-site-api.ts b/apps/app-frontend/src/helpers/skin-site-api.ts index c3bba36..7c03acb 100644 --- a/apps/app-frontend/src/helpers/skin-site-api.ts +++ b/apps/app-frontend/src/helpers/skin-site-api.ts @@ -2539,3 +2539,69 @@ export function skinSiteWikiAttachment( export function skinSiteWikiAttachmentUrl(id: number): string { return `${SKIN_SITE_ORIGIN}${REQUEST_TYPE_PATH.starlight}/wiki/attachment/${id}/content` } + +export interface SkinSiteWikiRevisionInfo { + id: number + authorName: string + summary: string + minor: boolean + createdAt: number + sourceWiki?: string | null + sourceRevisionId?: number | null +} + +export interface SkinSiteWikiRevisionPage { + items: SkinSiteWikiRevisionInfo[] + nextCursor?: number | null +} + +export interface SkinSiteWikiRevisionDetail { + revision: SkinSiteWikiRevisionInfo + source: string + nodes: SkinSiteWikiNode[] +} + +export interface SkinSiteWikiDiffLine { + type: 'UNCHANGED' | 'REMOVED' | 'ADDED' + oldLine?: number | null + newLine?: number | null + text: string +} + +export function skinSiteWikiHistory( + title: string, + before?: number, +): Promise> { + const query = new URLSearchParams({ title }) + if (before) query.set('before', String(before)) + return skinSiteRequest('wiki/history?' + query.toString()) +} + +export function skinSiteWikiRevision( + title: string, + revisionId: number, +): Promise> { + return skinSiteRequest( + 'wiki/revision/' + revisionId + '?title=' + encodeURIComponent(title), + ) +} + +export function skinSiteWikiDiff( + title: string, + older: number, + newer: number, +): Promise> { + const query = new URLSearchParams({ title, older: String(older), newer: String(newer) }) + return skinSiteRequest('wiki/diff?' + query.toString()) +} + +export function skinSiteWikiRevert(data: { + title: string + revisionId: number + baseRevisionId: number +}): Promise> { + return skinSiteRequest('wiki/revert', { + method: 'POST', + body: data, + }) +} diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 9a3ae7f..71eb579 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -12434,5 +12434,18 @@ "app.skin-site.wiki.updated-at": {"message": "Last edited {date} by {author}"}, "app.skin-site.wiki.revision": {"message": "Revision #{id}"}, "app.skin-site.wiki.categories": {"message": "Categories"}, - "app.skin-site.wiki.back": {"message": "Back"} + "app.skin-site.wiki.back": {"message": "Back"}, + "app.skin-site.wiki.history": {"message": "History"}, + "app.skin-site.wiki.read": {"message": "Read"}, + "app.skin-site.wiki.history-empty": {"message": "No revisions yet."}, + "app.skin-site.wiki.no-summary": {"message": "No edit summary"}, + "app.skin-site.wiki.minor": {"message": "Minor"}, + "app.skin-site.wiki.view-revision": {"message": "View"}, + "app.skin-site.wiki.revert-to": {"message": "Revert to this"}, + "app.skin-site.wiki.confirm-revert": {"message": "Confirm revert"}, + "app.skin-site.wiki.revert-done": {"message": "Page reverted to the selected revision."}, + "app.skin-site.wiki.revision-preview": {"message": "Preview of revision #{id}"}, + "app.skin-site.wiki.close-preview": {"message": "Close preview"}, + "app.skin-site.wiki.diff-title": {"message": "Line diff against the current revision"}, + "app.skin-site.wiki.cancel": {"message": "Cancel"} } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index 0e33964..2ad0192 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -12265,5 +12265,18 @@ "app.skin-site.wiki.updated-at": {"message": "最后编辑于 {date},编辑者 {author}"}, "app.skin-site.wiki.revision": {"message": "修订 #{id}"}, "app.skin-site.wiki.categories": {"message": "分类"}, - "app.skin-site.wiki.back": {"message": "返回"} + "app.skin-site.wiki.back": {"message": "返回"}, + "app.skin-site.wiki.history": {"message": "历史"}, + "app.skin-site.wiki.read": {"message": "阅读"}, + "app.skin-site.wiki.history-empty": {"message": "暂无修订记录。"}, + "app.skin-site.wiki.no-summary": {"message": "未填写编辑摘要"}, + "app.skin-site.wiki.minor": {"message": "小修改"}, + "app.skin-site.wiki.view-revision": {"message": "查看"}, + "app.skin-site.wiki.revert-to": {"message": "撤销至此版本"}, + "app.skin-site.wiki.confirm-revert": {"message": "确认撤销"}, + "app.skin-site.wiki.revert-done": {"message": "页面已撤销到指定修订。"}, + "app.skin-site.wiki.revision-preview": {"message": "修订 #{id} 预览"}, + "app.skin-site.wiki.close-preview": {"message": "关闭预览"}, + "app.skin-site.wiki.diff-title": {"message": "与当前版本的逐行差异"}, + "app.skin-site.wiki.cancel": {"message": "取消"} }