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 01/18] =?UTF-8?q?feat:=20=E7=BB=B4=E5=9F=BA=E9=98=85?= =?UTF-8?q?=E8=AF=BB=E9=A1=B5=E8=A1=A5=E5=8E=86=E5=8F=B2=E4=B8=8E=E7=89=88?= =?UTF-8?q?=E6=9C=AC=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": "取消"} } From 1a4fd54b68e2187f17b4f517a694193e5a47308c Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sun, 18 Oct 2026 15:32:06 +0800 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20=E7=BB=B4=E5=9F=BA=E9=A1=B5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=90=9C=E7=B4=A2=E3=80=81=E6=9C=80=E8=BF=91?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=8E=E5=88=86=E7=B1=BB=E6=B5=8F=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ui/skin-site/WikiView.vue | 3 + .../ui/skin-site/wiki/WikiBrowser.vue | 238 ++++++++++++++++++ .../app-frontend/src/helpers/skin-site-api.ts | 77 ++++++ .../app-frontend/src/locales/en-US/index.json | 13 +- .../app-frontend/src/locales/zh-CN/index.json | 13 +- 5 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 apps/app-frontend/src/components/ui/skin-site/wiki/WikiBrowser.vue 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 e05644a..68aec7b 100644 --- a/apps/app-frontend/src/components/ui/skin-site/WikiView.vue +++ b/apps/app-frontend/src/components/ui/skin-site/WikiView.vue @@ -16,6 +16,7 @@ import { } from '@/helpers/skin-site-api' import WikiNodeRenderer from './wiki/WikiNodeRenderer.vue' +import WikiBrowser from './wiki/WikiBrowser.vue' const { formatMessage } = useVIntl() const { handleError, addNotification } = injectNotificationManager() @@ -259,6 +260,8 @@ onMounted(() => {
+ +
{{ formatMessage(messages.loading) }} diff --git a/apps/app-frontend/src/components/ui/skin-site/wiki/WikiBrowser.vue b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiBrowser.vue new file mode 100644 index 0000000..e168c33 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin-site/wiki/WikiBrowser.vue @@ -0,0 +1,238 @@ + + + \ No newline at end of file diff --git a/apps/app-frontend/src/helpers/skin-site-api.ts b/apps/app-frontend/src/helpers/skin-site-api.ts index 7c03acb..21cdf2a 100644 --- a/apps/app-frontend/src/helpers/skin-site-api.ts +++ b/apps/app-frontend/src/helpers/skin-site-api.ts @@ -2605,3 +2605,80 @@ export function skinSiteWikiRevert(data: { body: data, }) } +// ─── Wiki: 搜索 / 列表 / 最近 / 分类 ─────────────────────────────────────── + +export interface SkinSiteWikiPageSummary { + title: string + updatedAt: number + authorName: string + readPolicy: SkinSiteWikiReadPolicy + editPolicy: SkinSiteWikiEditPolicy + redirectTarget?: string | null +} + +export interface SkinSiteWikiPageListItem { + id: number + title: string + namespace: string + revisionId: number + updatedAt: number + authorName: string + readPolicy: SkinSiteWikiReadPolicy + editPolicy: SkinSiteWikiEditPolicy + redirectTarget?: string | null +} + +export interface SkinSiteWikiPageList { + items: SkinSiteWikiPageListItem[] + nextCursor?: string | null +} + +export interface SkinSiteWikiSearchPage { + items: SkinSiteWikiPageSummary[] + nextCursor?: number | null +} + +export function skinSiteWikiSearch( + query: string, +): Promise> { + const q = query.trim() + if (!q) { + return Promise.resolve({ + success: true, + payload: [], + timestamp: Date.now(), + } as SkinSiteResponse) + } + return skinSiteRequest('wiki/search?q=' + encodeURIComponent(q)) +} + +export function skinSiteWikiPages( + params: { + prefix?: string + namespace?: string + limit?: number + cursor?: string + } = {}, +): Promise> { + const query = new URLSearchParams() + if (params.prefix) query.set('prefix', params.prefix) + if (params.namespace != null) query.set('namespace', params.namespace) + if (params.limit != null) query.set('limit', String(params.limit)) + if (params.cursor) query.set('cursor', params.cursor) + return skinSiteRequest('wiki/pages?' + query.toString()) +} + +export function skinSiteWikiRecent(before?: number): Promise> { + const query = new URLSearchParams() + if (before != null) query.set('before', String(before)) + return skinSiteRequest('wiki/recent?' + query.toString()) +} + +export function skinSiteWikiCategory( + name: string, + before?: number, +): Promise> { + const query = new URLSearchParams({ name }) + if (before != null) query.set('before', String(before)) + return skinSiteRequest('wiki/category?' + query.toString()) +} \ No newline at end of file diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 71eb579..d79e407 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -12447,5 +12447,16 @@ "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"} + "app.skin-site.wiki.cancel": {"message": "Cancel"}, + "app.skin-site.wiki.browser.search-placeholder": {"message": "Search wiki pages…"}, + "app.skin-site.wiki.browser.tab-search": {"message": "Search"}, + "app.skin-site.wiki.browser.tab-recent": {"message": "Recent"}, + "app.skin-site.wiki.browser.tab-category": {"message": "Category"}, + "app.skin-site.wiki.browser.category-placeholder": {"message": "Category name"}, + "app.skin-site.wiki.browser.load-more": {"message": "Load more"}, + "app.skin-site.wiki.browser.empty-search": {"message": "No pages match your search."}, + "app.skin-site.wiki.browser.empty-list": {"message": "Nothing here yet."}, + "app.skin-site.wiki.browser.hint-search": {"message": "Type a keyword to search."}, + "app.skin-site.wiki.browser.hint-category": {"message": "Enter a category name to browse."}, + "app.skin-site.wiki.browser.by-author": {"message": "by {author}"} } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index 2ad0192..ec048b2 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -12278,5 +12278,16 @@ "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": "取消"} + "app.skin-site.wiki.cancel": {"message": "取消"}, + "app.skin-site.wiki.browser.search-placeholder": {"message": "搜索 Wiki 页面…"}, + "app.skin-site.wiki.browser.tab-search": {"message": "搜索"}, + "app.skin-site.wiki.browser.tab-recent": {"message": "最近"}, + "app.skin-site.wiki.browser.tab-category": {"message": "分类"}, + "app.skin-site.wiki.browser.category-placeholder": {"message": "分类名称"}, + "app.skin-site.wiki.browser.load-more": {"message": "加载更多"}, + "app.skin-site.wiki.browser.empty-search": {"message": "没有匹配的页面。"}, + "app.skin-site.wiki.browser.empty-list": {"message": "这里还没有内容。"}, + "app.skin-site.wiki.browser.hint-search": {"message": "输入关键词开始搜索。"}, + "app.skin-site.wiki.browser.hint-category": {"message": "输入分类名称开始浏览。"}, + "app.skin-site.wiki.browser.by-author": {"message": "由 {author} 编辑"} } From 2542f6dcb6680b915c87177aff34726a1ca90164 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Mon, 19 Oct 2026 21:36:40 +0800 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=E7=BB=B4=E5=9F=BA=E9=A1=B5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=85=B3=E6=B3=A8=E4=B8=8E=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ui/skin-site/WikiView.vue | 53 +++++++++++++++++-- .../app-frontend/src/helpers/skin-site-api.ts | 24 ++++++++- .../app-frontend/src/locales/en-US/index.json | 9 +++- .../app-frontend/src/locales/zh-CN/index.json | 9 +++- 4 files changed, 89 insertions(+), 6 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 68aec7b..7265f76 100644 --- a/apps/app-frontend/src/components/ui/skin-site/WikiView.vue +++ b/apps/app-frontend/src/components/ui/skin-site/WikiView.vue @@ -281,9 +281,56 @@ onMounted(() => {