补齐论坛核心能力:讨论锁定、发帖本地草稿、标签精确筛选,以及私信与通知分流。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
59
frontend/src/utils/composeDraft.ts
Normal file
59
frontend/src/utils/composeDraft.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/** 新建帖本地草稿(localStorage) */
|
||||
|
||||
const STORAGE_KEY = 'j13-compose-draft-v1';
|
||||
|
||||
export type ComposeDraft = {
|
||||
title: string;
|
||||
tags: string;
|
||||
content: string;
|
||||
boardId: string;
|
||||
postType: 'normal' | 'question';
|
||||
savedAt: number;
|
||||
};
|
||||
|
||||
export function loadComposeDraft(): ComposeDraft | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
const data = JSON.parse(raw) as Partial<ComposeDraft>;
|
||||
if (!data || typeof data !== 'object') return null;
|
||||
return {
|
||||
title: typeof data.title === 'string' ? data.title : '',
|
||||
tags: typeof data.tags === 'string' ? data.tags : '',
|
||||
content: typeof data.content === 'string' ? data.content : '',
|
||||
boardId: typeof data.boardId === 'string' ? data.boardId : '',
|
||||
postType: data.postType === 'question' ? 'question' : 'normal',
|
||||
savedAt: typeof data.savedAt === 'number' ? data.savedAt : Date.now(),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function saveComposeDraft(draft: Omit<ComposeDraft, 'savedAt'>): void {
|
||||
try {
|
||||
const payload: ComposeDraft = { ...draft, savedAt: Date.now() };
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
|
||||
} catch {
|
||||
// 配额满或隐私模式:忽略
|
||||
}
|
||||
}
|
||||
|
||||
export function clearComposeDraft(): void {
|
||||
try {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/** 草稿是否有实质内容 */
|
||||
export function composeDraftHasContent(d: ComposeDraft | null | undefined): boolean {
|
||||
if (!d) return false;
|
||||
return !!(
|
||||
d.title.trim()
|
||||
|| d.tags.trim()
|
||||
|| d.content.trim()
|
||||
|| (d.content && d.content.replace(/<[^>]*>/g, '').trim())
|
||||
);
|
||||
}
|
||||
@@ -15,18 +15,18 @@ export type FeedCache = {
|
||||
/** 仅存内存:SPA 内返回可恢复,浏览器刷新自动清空 */
|
||||
const store = new Map<string, FeedCache>();
|
||||
|
||||
function cacheKey(boardId: number, keyword: string, sort: FeedSort) {
|
||||
return `${boardId}:${keyword}:${sort}`;
|
||||
function cacheKey(boardId: number, keyword: string, sort: FeedSort, tag = '') {
|
||||
return `${boardId}:${keyword}:${tag}:${sort}`;
|
||||
}
|
||||
|
||||
/** 读取帖子列表缓存(从详情页返回时恢复浏览位置) */
|
||||
export function getFeedCache(boardId: number, keyword: string, sort: FeedSort): FeedCache | null {
|
||||
return store.get(cacheKey(boardId, keyword, sort)) ?? null;
|
||||
export function getFeedCache(boardId: number, keyword: string, sort: FeedSort, tag = ''): FeedCache | null {
|
||||
return store.get(cacheKey(boardId, keyword, sort, tag)) ?? null;
|
||||
}
|
||||
|
||||
/** 保存帖子列表缓存 */
|
||||
export function setFeedCache(boardId: number, keyword: string, sort: FeedSort, data: FeedCache) {
|
||||
store.set(cacheKey(boardId, keyword, sort), data);
|
||||
export function setFeedCache(boardId: number, keyword: string, sort: FeedSort, data: FeedCache, tag = '') {
|
||||
store.set(cacheKey(boardId, keyword, sort, tag), data);
|
||||
}
|
||||
|
||||
/** 清除所有帖子列表缓存 */
|
||||
|
||||
@@ -14,6 +14,16 @@ export function resolveUserLevel(u?: { level?: number; exp?: number } | null): n
|
||||
return levelFromExp(u?.exp ?? 0);
|
||||
}
|
||||
|
||||
/** 等级视觉档位(纹章 / CSS tone) */
|
||||
export type LevelTone = 'sprout' | 'leaf' | 'crest' | 'crown';
|
||||
|
||||
export function levelToneFromLevel(level: number): LevelTone {
|
||||
if (level >= 9) return 'crown';
|
||||
if (level >= 7) return 'crest';
|
||||
if (level >= 4) return 'leaf';
|
||||
return 'sprout';
|
||||
}
|
||||
|
||||
/** 是否免审发帖/评论 */
|
||||
export function skipsModeration(u?: { role?: string; verified?: boolean } | null): boolean {
|
||||
return !!u && (u.role === 'admin' || !!u.verified);
|
||||
|
||||
Reference in New Issue
Block a user