feat(admin): 商品管理列表持久化筛选/页码/滚动位置,详情页返回智能回退

This commit is contained in:
yml2213
2026-08-15 13:20:35 +08:00
parent ac47006711
commit 429ce74eff
2 changed files with 120 additions and 7 deletions
@@ -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) {
</div>
<div class="toolbar-actions">
<el-button :icon="CopyDocument" @click="copyListingCode">复制编号</el-button>
<RouterLink :to="adminPath('listings')">
<el-button>返回列表</el-button>
</RouterLink>
<el-button @click="goBackToList">返回列表</el-button>
<el-button
v-if="canAdjustExternal"
type="primary"
@@ -1,7 +1,7 @@
<script setup lang="ts">
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 {