update
This commit is contained in:
@ -20,9 +20,19 @@ export interface HistoryData {
|
||||
}
|
||||
|
||||
export interface HistoryEntry {
|
||||
id: string // 文章对外编码串:r=xxx(走秀) / s=xxx(街拍)
|
||||
id: string // 对外编码串(服务端存储的原样值):r=xxx(走秀) / s=xxx(街拍)。删除时必须原样回传
|
||||
viewedAt: number
|
||||
data?: HistoryData // 账户页展示时回查详情获得;本地/回查失败时缺失(卡片占位渲染)
|
||||
canonId?: string // 回查得到的「规范 id」(带类型前缀)。早期数据可能缺前缀(如 0008CZYI),
|
||||
// 同一篇文章因此会存成两行;链接统一用规范 id,保证只指向一个地址。
|
||||
}
|
||||
|
||||
// 去掉一个「类型前缀」后的串,作为「同一篇文章」的判据:r=走秀 / s=街拍 / i=走秀单图 / j=街拍单图。
|
||||
// 早期历史数据没有类型前缀(0008CZYI),与新数据(r0008CZYI)指向同一篇,只按原样 id 去重会漏,
|
||||
// 故本地缓存按「去前缀」后的串判同。只剥已知前缀,避免误伤本身以字母开头的 hashid 本体。
|
||||
function dedupeKey(id: string): string {
|
||||
const s = String(id || "")
|
||||
return /^[rsij]/.test(s) ? s.slice(1) : s
|
||||
}
|
||||
|
||||
// 后端 HistoryItem 响应结构({data:{items:[...]}} 解包后,仅 id + viewed_at)
|
||||
@ -71,8 +81,9 @@ function serverMode(): boolean {
|
||||
export function recordHistory(id: string): void {
|
||||
if (!id) return
|
||||
const entry: HistoryEntry = { id, viewedAt: Date.now() }
|
||||
// 本地去重(同 id 只保留一条并提到最前)
|
||||
const list = getLocal().filter((h) => h.id !== id)
|
||||
// 本地去重:按「去类型前缀」后的串判同,避免同一篇文章因 id 形式不同(旧数据无前缀)留两条并排
|
||||
const key = dedupeKey(id)
|
||||
const list = getLocal().filter((h) => dedupeKey(h.id) !== key)
|
||||
list.unshift(entry)
|
||||
if (list.length > 200) list.length = 200
|
||||
setLocal(list)
|
||||
@ -90,12 +101,26 @@ export interface HistPageResult {
|
||||
lastPage: number
|
||||
}
|
||||
|
||||
// 按「规范 id」收敛同一篇文章,保留首次出现的那条(服务端已按 viewed_at 倒序 → 即最新的一条)。
|
||||
// 用于消除「同一篇文章两行并排」:早期历史数据没有类型前缀,与带前缀的新数据指向同一篇。
|
||||
function dedupeEntries(list: HistoryEntry[]): HistoryEntry[] {
|
||||
const seen = new Set<string>()
|
||||
const out: HistoryEntry[] = []
|
||||
for (const h of list) {
|
||||
const k = h.canonId || dedupeKey(h.id)
|
||||
if (seen.has(k)) continue
|
||||
seen.add(k)
|
||||
out.push(h)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取浏览历史的一页(登录态走服务端分页 + 逐篇回查公开详情;未登录回落本地缓存)。
|
||||
*/
|
||||
export async function fetchHistoryPage(page = 1, perPage = 24): Promise<HistPageResult> {
|
||||
if (!serverMode()) {
|
||||
const local = getLocal()
|
||||
const local = dedupeEntries(getLocal())
|
||||
return { items: local, total: local.length, lastPage: 1 }
|
||||
}
|
||||
try {
|
||||
@ -113,23 +138,29 @@ export async function fetchHistoryPage(page = 1, perPage = 24): Promise<HistPage
|
||||
base.map(async (h) => {
|
||||
try {
|
||||
const meta = await getHistoryMeta(h.id)
|
||||
if (meta) h.data = meta
|
||||
if (meta) {
|
||||
h.data = meta
|
||||
h.canonId = meta.id // 回查同时拿到规范 id(无前缀的旧 id 也能拿到带前缀的结果)
|
||||
}
|
||||
} catch {
|
||||
/* 单条回查失败:保留无 data,卡片走占位渲染 */
|
||||
}
|
||||
return h
|
||||
}),
|
||||
)
|
||||
return { items, total: Number(r?.total || 0), lastPage: Number(r?.last_page || 1) }
|
||||
// 收敛同一篇文章的历史行(旧数据缺前缀会与正常行并存);删除仍用原样 id,故不影响删除。
|
||||
return { items: dedupeEntries(items), total: Number(r?.total || 0), lastPage: Number(r?.last_page || 1) }
|
||||
} catch {
|
||||
const local = getLocal()
|
||||
const local = dedupeEntries(getLocal())
|
||||
return { items: local, total: local.length, lastPage: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除一条历史(按文章 id)。 */
|
||||
export function removeHistory(id: string): void {
|
||||
setLocal(getLocal().filter((h) => h.id !== id))
|
||||
// 本地按「去类型前缀」判同删除,避免旧数据留下同篇的孪生行
|
||||
const key = dedupeKey(id)
|
||||
setLocal(getLocal().filter((h) => dedupeKey(h.id) !== key))
|
||||
if (serverMode()) {
|
||||
authJson("DELETE", `/me/history/${encodeURIComponent(id)}`).catch(() => {
|
||||
/* 失败不回滚本地 */
|
||||
|
||||
Reference in New Issue
Block a user