/** * Post Entity - 帖子领域实体 * 定义帖子的核心属性和行为,不依赖任何外部框架 */ // ==================== 帖子状态枚举 ==================== export type PostStatus = 'published' | 'draft' | 'deleted'; // ==================== 值对象 ==================== /** * 帖子作者信息 */ export interface PostAuthor { id: string; username: string; nickname?: string; avatar?: string; is_following?: boolean; is_following_me?: boolean; } /** * 帖子图片信息 */ export interface PostImage { url: string; width?: number; height?: number; thumbnailUrl?: string; } /** * 帖子标签 */ export interface PostTag { id: string; name: string; } // ==================== 主实体 ==================== /** * 帖子实体 */ export interface Post { /** 帖子唯一标识 */ id: string; /** 作者ID */ authorId: string; /** 作者信息 */ author?: PostAuthor; /** 帖子标题 */ title: string; /** 帖子内容 */ content: string; /** 图片列表 */ images: PostImage[]; /** 点赞数 (camelCase) */ likesCount: number; /** 评论数 (camelCase) */ commentsCount: number; /** 分享数 (camelCase) */ sharesCount: number; /** 浏览数 (camelCase) */ viewsCount: number; /** 收藏数 (camelCase) */ favoritesCount: number; /** 当前用户是否已点赞 (camelCase) */ isLiked: boolean; /** 当前用户是否已收藏 (camelCase) */ isFavorited: boolean; /** 是否置顶 (camelCase) */ isPinned: boolean; /** 帖子状态 */ status: PostStatus; /** 所属频道ID */ channelId?: string; /** 频道摘要(列表 API 填充,供 PostCard 等展示) */ channel?: { id: string; name: string } | null; /** 标签列表 */ tags: string[]; /** 创建时间 */ createdAt: string; /** 更新时间 */ updatedAt: string; /** 用户编辑内容的时间(与统计类更新解耦) */ contentEditedAt?: string; // ==================== 向后兼容字段 (snake_case) ==================== // 这些字段用于与旧代码兼容,新代码应使用 camelCase 版本 /** @deprecated 请使用 likesCount */ likes_count?: number; /** @deprecated 请使用 commentsCount */ comments_count?: number; /** @deprecated 请使用 favoritesCount */ favorites_count?: number; /** @deprecated 请使用 sharesCount */ shares_count?: number; /** @deprecated 请使用 viewsCount */ views_count?: number; /** @deprecated 请使用 isLiked */ is_liked?: boolean; /** @deprecated 请使用 isFavorited */ is_favorited?: boolean; /** @deprecated 请使用 isPinned */ is_pinned?: boolean; /** @deprecated 请使用 authorId */ user_id?: string; /** @deprecated 请使用 createdAt */ created_at?: string; /** @deprecated 请使用 updatedAt */ updated_at?: string; /** @deprecated 请使用 contentEditedAt */ content_edited_at?: string; /** @deprecated 请使用 channelId */ channel_id?: string; /** @deprecated 请使用 status */ status_str?: string; /** 投票帖子标识 (部分旧代码使用) */ is_vote?: boolean; /** 锁定状态 (部分旧代码使用) */ is_locked?: boolean; /** 置顶评论 (部分旧代码使用) */ top_comment?: any; } // ==================== 工厂函数 ==================== /** * 创建帖子作者信息 */ export const createPostAuthor = (data: Partial): PostAuthor => ({ id: data.id || '', username: data.username || '', nickname: data.nickname, avatar: data.avatar, is_following: data.is_following, is_following_me: data.is_following_me, }); /** * 创建帖子图片信息 */ export const createPostImage = (data: Partial): PostImage => ({ url: data.url || '', width: data.width, height: data.height, thumbnailUrl: data.thumbnailUrl, }); /** * 创建帖子实体 */ export const createPost = (data: Partial): Post => ({ id: data.id || '', authorId: data.authorId || '', author: data.author, title: data.title || '', content: data.content || '', images: data.images || [], likesCount: data.likesCount || 0, commentsCount: data.commentsCount || 0, sharesCount: data.sharesCount || 0, viewsCount: data.viewsCount || 0, favoritesCount: data.favoritesCount || 0, isLiked: data.isLiked || false, isFavorited: data.isFavorited || false, isPinned: data.isPinned || false, status: data.status || 'published', channelId: data.channelId, tags: data.tags || [], createdAt: data.createdAt || new Date().toISOString(), updatedAt: data.updatedAt || new Date().toISOString(), }); // ==================== 类型守卫 ==================== /** * 检查是否为有效的帖子状态 */ export const isValidPostStatus = (status: string): status is PostStatus => { return ['published', 'draft', 'deleted'].includes(status); }; /** * 检查帖子是否已发布 */ export const isPostPublished = (post: Post): boolean => { return post.status === 'published'; }; /** * 检查帖子是否已删除 */ export const isPostDeleted = (post: Post): boolean => { return post.status === 'deleted'; }; /** * 检查帖子是否为草稿 */ export const isPostDraft = (post: Post): boolean => { return post.status === 'draft'; }; // ==================== 辅助函数 ==================== /** * 检查当前用户是否为帖子作者 */ export const isPostAuthor = (post: Post, userId: string): boolean => { return post.authorId === userId; }; /** * 获取帖子的主要图片(第一张图片) */ export const getPostThumbnail = (post: Post): PostImage | undefined => { return post.images[0]; }; /** * 检查帖子是否包含图片 */ export const hasPostImages = (post: Post): boolean => { return post.images.length > 0; }; /** * 获取帖子的显示标题(优先使用标题,否则使用内容摘要) */ export const getPostDisplayTitle = (post: Post, maxLength: number = 50): string => { if (post.title) { return post.title; } if (post.content.length > maxLength) { return post.content.substring(0, maxLength) + '...'; } return post.content; }; /** * 计算帖子的互动总数 */ export const getPostTotalEngagement = (post: Post): number => { return post.likesCount + post.commentsCount + post.sharesCount + post.favoritesCount; }; /** * 根据创建时间 ISO 字符串格式化为列表用相对时间(与历史 formatPostTime 行为一致) */ export const formatPostCreatedAtString = (createdAt: string | undefined | null): string => { if (!createdAt) return ''; const createdAtDate = new Date(createdAt); if (Number.isNaN(createdAtDate.getTime())) return ''; const now = new Date(); const diffMs = now.getTime() - createdAtDate.getTime(); if (diffMs < 0) { return createdAtDate.toLocaleDateString('zh-CN'); } const diffSeconds = Math.floor(diffMs / 1000); const diffMinutes = Math.floor(diffSeconds / 60); const diffHours = Math.floor(diffMinutes / 60); const diffDays = Math.floor(diffHours / 24); if (diffSeconds < 60) { return '刚刚'; } if (diffMinutes < 60) { return `${diffMinutes}分钟前`; } if (diffHours < 24) { return `${diffHours}小时前`; } if (diffDays < 7) { return `${diffDays}天前`; } return createdAtDate.toLocaleDateString('zh-CN'); }; /** * 格式化帖子创建时间为相对时间描述 */ export const formatPostTime = (post: Post): string => { return formatPostCreatedAtString(post.createdAt); };