Files
frontend/src/stores/postListSources.ts

138 lines
3.8 KiB
TypeScript
Raw Normal View History

/**
* PostManager
*/
import { Post } from '../types';
import { postService } from '../services/postService';
import type { CursorPaginationRequest } from '../types/dto';
export const POST_LIST_PAGE_SIZE = 20;
/** 帖子列表类型 */
export type PostListTab = 'hot' | 'latest' | 'follow';
/** 单次拉取结果(一页或一批) */
export interface PostListPage {
items: Post[];
/** 在当前源上是否还能再 loadNext 一页 */
hasMore: boolean;
}
/**
* restart loadNext
*/
export interface IPostListPagedSource {
restart(): void;
loadNext(): Promise<PostListPage>;
/** 至少成功 loadNext 过一次且仍有下一页时为 true */
readonly hasMore: boolean;
}
/**
*
*/
export class NetworkCursorPostListPagedSource implements IPostListPagedSource {
private nextCursor: string | null = null;
private hasMoreAfterLastLoad = false;
private loadedOnce = false;
private readonly pageSize: number;
private readonly tab?: PostListTab;
private readonly channelId?: string;
constructor(
options: { tab?: PostListTab; channelId?: string; pageSize?: number } = {}
) {
this.tab = options.tab;
this.channelId = options.channelId;
this.pageSize = options.pageSize ?? POST_LIST_PAGE_SIZE;
}
restart(): void {
this.nextCursor = null;
this.hasMoreAfterLastLoad = false;
this.loadedOnce = false;
}
get hasMore(): boolean {
return this.loadedOnce && this.hasMoreAfterLastLoad;
}
async loadNext(): Promise<PostListPage> {
const params: CursorPaginationRequest = {
cursor: this.nextCursor ?? '',
page_size: this.pageSize,
post_type: this.tab,
};
const result = await postService.getPostsCursor(params);
this.hasMoreAfterLastLoad = result.has_more ?? false;
this.nextCursor = result.next_cursor ?? null;
this.loadedOnce = true;
return { items: result.list ?? [], hasMore: this.hasMoreAfterLastLoad };
}
}
/**
*
*/
export class NetworkOffsetPostListPagedSource implements IPostListPagedSource {
private nextPage = 1;
private hasMoreAfterLastLoad = false;
private loadedOnce = false;
private readonly pageSize: number;
private readonly tab?: PostListTab;
private readonly channelId?: string;
constructor(
options: { tab?: PostListTab; channelId?: string; pageSize?: number } = {}
) {
this.tab = options.tab;
this.channelId = options.channelId;
this.pageSize = options.pageSize ?? POST_LIST_PAGE_SIZE;
}
restart(): void {
this.nextPage = 1;
this.hasMoreAfterLastLoad = false;
this.loadedOnce = false;
}
get hasMore(): boolean {
return this.loadedOnce && this.hasMoreAfterLastLoad;
}
async loadNext(): Promise<PostListPage> {
const result = await postService.getPosts(
this.nextPage,
this.pageSize,
this.tab,
this.channelId
);
const items = result.list ?? [];
const total = result.total ?? 0;
const totalPages = result.total_pages ?? 1;
this.hasMoreAfterLastLoad = this.nextPage < totalPages;
this.nextPage++;
this.loadedOnce = true;
return { items, hasMore: this.hasMoreAfterLastLoad };
}
}
/** 远端帖子列表分页策略 */
export type RemotePostListSourceKind = 'cursor' | 'offset';
export function createRemotePostListSource(
kind: RemotePostListSourceKind = 'cursor',
options: { tab?: PostListTab; channelId?: string; pageSize?: number } = {}
): IPostListPagedSource {
if (kind === 'offset') {
return new NetworkOffsetPostListPagedSource(options);
}
return new NetworkCursorPostListPagedSource(options);
}