返回列表保留原分页页码

master
lion 2 months ago
parent 198e2400e7
commit 8ae2785aaa

@ -0,0 +1,65 @@
/** Persist list pagination across detail → back navigation. */
export function parsePositiveInt(v: unknown, fallback: number): number {
const n = typeof v === 'string' ? Number(v) : typeof v === 'number' ? v : NaN
return Number.isInteger(n) && n > 0 ? n : fallback
}
export type ListPageState = {
page: number
perPage: number
}
export function listPageStateKey(scope: string, slug: string): string {
return `cxxfds.list.${scope}.${slug}`
}
export function saveListPageState(key: string, state: ListPageState): void {
if (!key || typeof sessionStorage === 'undefined') return
try {
sessionStorage.setItem(key, JSON.stringify({ page: state.page, perPage: state.perPage }))
} catch {
/* ignore quota / private mode */
}
}
export function loadListPageState(key: string): ListPageState | null {
if (!key || typeof sessionStorage === 'undefined') return null
try {
const raw = sessionStorage.getItem(key)
if (!raw) return null
const o = JSON.parse(raw) as { page?: unknown; perPage?: unknown }
return {
page: parsePositiveInt(o.page, 1),
perPage: parsePositiveInt(o.perPage, 15),
}
} catch {
return null
}
}
export function listPageStateToQuery(
state: ListPageState,
defaultPerPage = 15,
): Record<string, string> {
const q: Record<string, string> = {}
if (state.page > 1) q.page = String(state.page)
if (state.perPage !== defaultPerPage) q.per_page = String(state.perPage)
return q
}
/** Prefer URL query, then sessionStorage, then defaults. */
export function resolveListPageState(
key: string,
query: Record<string, unknown> | { page?: unknown; per_page?: unknown },
defaults: ListPageState = { page: 1, perPage: 15 },
): ListPageState {
const qPage = parsePositiveInt(query.page, 0)
const qPer = parsePositiveInt(query.per_page, 0)
if (qPage > 0) {
return { page: qPage, perPage: qPer > 0 ? qPer : defaults.perPage }
}
const stored = loadListPageState(key)
if (stored) return stored
return { ...defaults }
}

@ -5,6 +5,12 @@ import { ElMessage } from 'element-plus'
import ApplicationSignupDetailFields from '../../components/portal/ApplicationSignupDetailFields.vue'
import { downloadAdminApplicationFile, getAdminApplication } from '../../api/admin/applications'
import type { AdminApplicationDetail, AdminApplicationFileRow } from '../../api/admin/types'
import {
listPageStateKey,
listPageStateToQuery,
loadListPageState,
parsePositiveInt,
} from '../../utils/listPageState'
const route = useRoute()
const router = useRouter()
@ -97,14 +103,22 @@ async function load() {
}
function goBack() {
const query: Record<string, string> = {}
if (typeof route.query.page === 'string' && route.query.page) query.page = route.query.page
if (typeof route.query.per_page === 'string' && route.query.per_page) {
query.per_page = route.query.per_page
const slug = competitionSlug.value
const fromRoutePage = parsePositiveInt(route.query.page, 0)
const fromRoutePer = parsePositiveInt(route.query.per_page, 0)
let query: Record<string, string> = {}
if (fromRoutePage > 0) {
query = listPageStateToQuery({
page: fromRoutePage,
perPage: fromRoutePer > 0 ? fromRoutePer : 15,
})
} else {
const stored = loadListPageState(listPageStateKey('manage-projects', slug))
if (stored) query = listPageStateToQuery(stored)
}
void router.push({
name: 'manage-projects',
params: { slug: competitionSlug.value },
params: { slug },
query,
})
}

@ -20,6 +20,12 @@ import type {
PublicSourceChannelRow,
SignupChannelRow,
} from '../../api/admin/types'
import {
listPageStateKey,
listPageStateToQuery,
resolveListPageState,
saveListPageState,
} from '../../utils/listPageState'
const route = useRoute()
const router = useRouter()
@ -30,31 +36,37 @@ const loading = ref(false)
const rows = ref<AdminApplicationRow[]>([])
const pager = reactive({ page: 1, perPage: 15, total: 0 })
function parsePositiveInt(v: unknown, fallback: number): number {
const n = typeof v === 'string' ? Number(v) : typeof v === 'number' ? v : NaN
return Number.isInteger(n) && n > 0 ? n : fallback
function listStateStorageKey(): string {
return listPageStateKey('manage-projects', competitionSlug.value)
}
function listReturnQuery(): Record<string, string> {
const q: Record<string, string> = {}
if (pager.page > 1) q.page = String(pager.page)
if (pager.perPage !== 15) q.per_page = String(pager.perPage)
return q
return listPageStateToQuery({ page: pager.page, perPage: pager.perPage })
}
function applyListQueryFromRoute(): void {
pager.page = parsePositiveInt(route.query.page, 1)
pager.perPage = parsePositiveInt(route.query.per_page, 15)
function persistListState(): void {
const key = listStateStorageKey()
if (!key.endsWith('.')) {
saveListPageState(key, { page: pager.page, perPage: pager.perPage })
}
}
function applyListState(): void {
const next = resolveListPageState(listStateStorageKey(), route.query)
pager.page = next.page
pager.perPage = next.perPage
}
function syncListQueryToRoute(): void {
const next = listReturnQuery()
const curPage = typeof route.query.page === 'string' ? route.query.page : undefined
const curPer = typeof route.query.per_page === 'string' ? route.query.per_page : undefined
const nextPage = next.page
const nextPer = next.per_page
if (curPage === nextPage && curPer === nextPer) return
void router.replace({ query: next })
if (curPage === next.page && curPer === next.per_page) return
void router.replace({
name: 'manage-projects',
params: { slug: competitionSlug.value },
query: next,
})
}
const tracks = ref<CompetitionTrackRow[]>([])
const signupChannels = ref<SignupChannelRow[]>([])
@ -233,6 +245,7 @@ async function refresh(page = pager.page) {
if (!cid) return
loading.value = true
pager.page = page
persistListState()
syncListQueryToRoute()
try {
const res = await listAdminApplications(cid, buildListParams(page))
@ -240,6 +253,7 @@ async function refresh(page = pager.page) {
pager.total = res.meta.total
if (res.meta.current_page && res.meta.current_page !== pager.page) {
pager.page = res.meta.current_page
persistListState()
syncListQueryToRoute()
}
} finally {
@ -316,9 +330,10 @@ async function confirmBatch(result: 'passed' | 'rejected') {
}
function openDetail(row: AdminApplicationRow) {
persistListState()
void router.push({
name: 'manage-project-detail',
params: { slug: competitionSlug.value, id: row.id },
params: { slug: competitionSlug.value, id: String(row.id) },
query: listReturnQuery(),
})
}
@ -326,7 +341,7 @@ function openDetail(row: AdminApplicationRow) {
watch(competitionId, async (cid) => {
if (!cid) return
await loadFilters()
applyListQueryFromRoute()
applyListState()
await refresh(pager.page)
}, { immediate: true })
</script>

@ -10,6 +10,12 @@ import {
import { ElMessage } from 'element-plus'
import { Modal } from 'bootstrap'
import ApplicationSignupDetailFields from '../../components/portal/ApplicationSignupDetailFields.vue'
import {
listPageStateKey,
listPageStateToQuery,
loadListPageState,
parsePositiveInt,
} from '../../utils/listPageState'
interface FileItem {
id: number
@ -329,10 +335,17 @@ function goBack() {
void router.push('/c')
return
}
const query: Record<string, string> = {}
if (typeof route.query.page === 'string' && route.query.page) query.page = route.query.page
if (typeof route.query.per_page === 'string' && route.query.per_page) {
query.per_page = route.query.per_page
const fromRoutePage = parsePositiveInt(route.query.page, 0)
const fromRoutePer = parsePositiveInt(route.query.per_page, 0)
let query: Record<string, string> = {}
if (fromRoutePage > 0) {
query = listPageStateToQuery({
page: fromRoutePage,
perPage: fromRoutePer > 0 ? fromRoutePer : 15,
})
} else {
const stored = loadListPageState(listPageStateKey('reviewer-projects', s))
if (stored) query = listPageStateToQuery(stored)
}
void router.push({ name: 'reviewer-projects', params: { slug: s }, query })
}

@ -7,6 +7,12 @@ import {
REVIEW_APPLICATIONS_INDEX_PATH,
} from '../../config/api'
import PortalPagination from '../../components/portal/PortalPagination.vue'
import {
listPageStateKey,
listPageStateToQuery,
resolveListPageState,
saveListPageState,
} from '../../utils/listPageState'
export interface ReviewApplicationRow {
id: number
@ -48,21 +54,25 @@ const meta = ref({
const page = ref(1)
const perPage = ref(15)
function parsePositiveInt(v: unknown, fallback: number): number {
const n = typeof v === 'string' ? Number(v) : typeof v === 'number' ? v : NaN
return Number.isInteger(n) && n > 0 ? n : fallback
function listStateStorageKey(): string {
return listPageStateKey('reviewer-projects', slug.value)
}
function listReturnQuery(): Record<string, string> {
const q: Record<string, string> = {}
if (page.value > 1) q.page = String(page.value)
if (perPage.value !== 15) q.per_page = String(perPage.value)
return q
return listPageStateToQuery({ page: page.value, perPage: perPage.value })
}
function persistListState(): void {
const key = listStateStorageKey()
if (!key.endsWith('.')) {
saveListPageState(key, { page: page.value, perPage: perPage.value })
}
}
function applyListQueryFromRoute(): void {
page.value = parsePositiveInt(route.query.page, 1)
perPage.value = parsePositiveInt(route.query.per_page, 15)
function applyListState(): void {
const next = resolveListPageState(listStateStorageKey(), route.query)
page.value = next.page
perPage.value = next.perPage
}
function syncListQueryToRoute(): void {
@ -70,7 +80,12 @@ function syncListQueryToRoute(): void {
const curPage = typeof route.query.page === 'string' ? route.query.page : undefined
const curPer = typeof route.query.per_page === 'string' ? route.query.per_page : undefined
if (curPage === next.page && curPer === next.per_page) return
void router.replace({ query: next })
if (!slug.value) return
void router.replace({
name: 'reviewer-projects',
params: { slug: slug.value },
query: next,
})
}
function buildListParams(): URLSearchParams {
@ -159,9 +174,10 @@ function resetFilters() {
refresh(1)
}
applyListQueryFromRoute()
applyListState()
watch([slug, page, perPage], () => {
persistListState()
syncListQueryToRoute()
void fetchList()
}, { immediate: true })
@ -266,6 +282,7 @@ function actionLabel(row: ReviewApplicationRow): string {
params: { slug, id: String(row.id) },
query: listReturnQuery(),
}"
@click="persistListState"
>
{{ actionLabel(row) }}
</router-link>

Loading…
Cancel
Save