From 429ce74eff4c2109851c26173a2b547e31cc1953 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 15 Aug 2026 13:20:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E5=95=86=E5=93=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=88=97=E8=A1=A8=E6=8C=81=E4=B9=85=E5=8C=96=E7=AD=9B?= =?UTF-8?q?=E9=80=89/=E9=A1=B5=E7=A0=81/=E6=BB=9A=E5=8A=A8=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E8=AF=A6=E6=83=85=E9=A1=B5=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E5=9B=9E=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/views/AdminListingDetailView.vue | 19 ++- .../admin/views/AdminListingsView.vue | 108 +++++++++++++++++- 2 files changed, 120 insertions(+), 7 deletions(-) diff --git a/frontend/src/features/admin/views/AdminListingDetailView.vue b/frontend/src/features/admin/views/AdminListingDetailView.vue index 59644a3..e98b51b 100644 --- a/frontend/src/features/admin/views/AdminListingDetailView.vue +++ b/frontend/src/features/admin/views/AdminListingDetailView.vue @@ -3,12 +3,12 @@ import { readError } from '@/shared/utils/error' import { CopyDocument, EditPen, Search } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' import { computed, onMounted, reactive, ref } from 'vue' -import { useRoute } from 'vue-router' +import { useRoute, useRouter } from 'vue-router' import { fetchAdminUsers, type AdminUserItem } from '@/features/admin/api/adminUsers' import { fetchAdminFileBlob } from '@/shared/api/files' import AuthImage from '@/shared/components/business/AuthImage.vue' -import { adminPath } from '@/shared/utils/adminPath' +import { adminPath, isAdminPath } from '@/shared/utils/adminPath' import { centToYuan, formatCentWithSymbol, @@ -59,6 +59,17 @@ import { calculatePlatformPricing, roundRatio as roundPricingRatio } from '@/sha const adminSession = useAdminSessionStore() const route = useRoute() +const router = useRouter() + +// 返回列表:有站内后台历史则回退(保留列表页筛选与滚动恢复),否则直接回列表页。 +function goBackToList() { + const back = (window.history.state as { back?: string | null } | null)?.back + if (back && isAdminPath(back)) { + router.back() + return + } + void router.push(adminPath('listings')) +} const loading = ref(false) const submitting = ref(false) const userSearching = ref(false) @@ -596,9 +607,7 @@ async function openScreenshot(url: string) {
复制编号 - - 返回列表 - + 返回列表 import { ArrowDown, Search } from '@element-plus/icons-vue' import { ElMessage } from 'element-plus' -import { computed, onMounted, reactive, ref } from 'vue' +import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue' import { emptyListingPublishOptions, @@ -70,6 +70,8 @@ const { page: 1, page_size: 10, }, + // 首次加载由 onMounted 控制:先恢复持久化的筛选/页码再查询 + immediate: false, }) const listings = computed(() => listingPage.value.items) @@ -162,7 +164,109 @@ function resetFilters() { void queryListings() } -onMounted(loadPublishOptions) +// ========== 列表状态持久化 ========== +// 详情页返回/刷新后恢复筛选条件、页码与滚动位置,避免筛选丢失。 +const adminListingsStateKey = 'hfb.admin.listings.state' +let stateSaveTimer: number | undefined +let scrollSaveTimer: number | undefined + +function adminMainEl() { + return document.querySelector('.admin-main') +} + +function adminMainScrollTop() { + return adminMainEl()?.scrollTop || 0 +} + +function saveListState() { + try { + window.sessionStorage.setItem( + adminListingsStateKey, + JSON.stringify({ + filters: { ...filters }, + page: currentPage.value, + pageSize: pageSize.value, + scrollTop: adminMainScrollTop(), + }) + ) + } catch { + // sessionStorage 不可用时仅损失状态恢复 + } +} + +function restoreListState() { + try { + const raw = window.sessionStorage.getItem(adminListingsStateKey) + if (!raw) return + const state = JSON.parse(raw) as { + filters?: AdminListingQuery + page?: number + pageSize?: number + } + if (state.filters && typeof state.filters === 'object') { + Object.assign(filters, state.filters) + } + if (typeof state.page === 'number' && state.page > 0) { + currentPage.value = state.page + } + if ( + typeof state.pageSize === 'number' && + [10, 20, 50, 100].includes(state.pageSize) + ) { + pageSize.value = state.pageSize + } + } catch { + // 损坏的状态忽略,按默认条件加载 + } +} + +function restoreListScroll() { + try { + const raw = window.sessionStorage.getItem(adminListingsStateKey) + if (!raw) return + const state = JSON.parse(raw) as { scrollTop?: number } + const top = Number(state.scrollTop) + if (!Number.isFinite(top) || top <= 0) return + requestAnimationFrame(() => { + requestAnimationFrame(() => { + const main = adminMainEl() + if (main) main.scrollTop = top + }) + }) + } catch { + // 忽略 + } +} + +function handleAdminMainScroll() { + if (scrollSaveTimer !== undefined) window.clearTimeout(scrollSaveTimer) + scrollSaveTimer = window.setTimeout(saveListState, 300) +} + +// 筛选/页码变化时防抖保存;滚动位置随每次保存一起写入 +watch( + [filters, currentPage, pageSize], + () => { + if (stateSaveTimer !== undefined) window.clearTimeout(stateSaveTimer) + stateSaveTimer = window.setTimeout(saveListState, 200) + }, + { deep: true } +) + +onMounted(async () => { + restoreListState() + void loadPublishOptions() + await loadListings() + restoreListScroll() + adminMainEl()?.addEventListener('scroll', handleAdminMainScroll, { passive: true }) +}) + +onBeforeUnmount(() => { + saveListState() + adminMainEl()?.removeEventListener('scroll', handleAdminMainScroll) + if (stateSaveTimer !== undefined) window.clearTimeout(stateSaveTimer) + if (scrollSaveTimer !== undefined) window.clearTimeout(scrollSaveTimer) +}) async function loadPublishOptions() { try {