Initial frontend repository commit.
Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
This commit is contained in:
867
src/types/dto.ts
Normal file
867
src/types/dto.ts
Normal file
@@ -0,0 +1,867 @@
|
||||
/**
|
||||
* DTO Types - Data Transfer Object types for API communication
|
||||
* 使用snake_case命名规范
|
||||
*/
|
||||
|
||||
// ==================== User DTOs ====================
|
||||
|
||||
export interface UserDTO {
|
||||
id: string;
|
||||
username: string;
|
||||
nickname: string;
|
||||
avatar: string | null;
|
||||
cover_url: string | null; // 头图URL
|
||||
bio: string | null;
|
||||
website: string | null;
|
||||
location: string | null;
|
||||
phone: string | null;
|
||||
email: string | null;
|
||||
email_verified?: boolean;
|
||||
posts_count: number;
|
||||
followers_count: number;
|
||||
following_count: number;
|
||||
created_at: string;
|
||||
// 额外字段
|
||||
is_following?: boolean; // 当前用户是否关注了该用户
|
||||
is_following_me?: boolean; // 该用户是否关注了当前用户
|
||||
}
|
||||
|
||||
export interface UserDetailDTO {
|
||||
id: string;
|
||||
username: string;
|
||||
nickname: string;
|
||||
email: string;
|
||||
email_verified?: boolean;
|
||||
avatar: string | null;
|
||||
bio: string | null;
|
||||
website: string | null;
|
||||
location: string | null;
|
||||
posts_count: number;
|
||||
followers_count: number;
|
||||
following_count: number;
|
||||
is_verified: boolean;
|
||||
created_at: string;
|
||||
// 额外字段
|
||||
is_following?: boolean; // 当前用户是否关注了该用户
|
||||
is_following_me?: boolean; // 该用户是否关注了当前用户
|
||||
}
|
||||
|
||||
// ==================== Post DTOs ====================
|
||||
|
||||
export interface PostImageDTO {
|
||||
id: string;
|
||||
url: string;
|
||||
thumbnail_url: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface PostDTO {
|
||||
id: string;
|
||||
user_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
images: PostImageDTO[];
|
||||
likes_count: number;
|
||||
comments_count: number;
|
||||
favorites_count: number;
|
||||
shares_count: number;
|
||||
views_count: number;
|
||||
is_pinned: boolean;
|
||||
is_locked: boolean;
|
||||
is_vote: boolean;
|
||||
created_at: string;
|
||||
author: UserDTO | null;
|
||||
is_liked: boolean;
|
||||
is_favorited: boolean;
|
||||
// 额外字段
|
||||
community_id?: string;
|
||||
top_comment?: CommentDTO | null;
|
||||
}
|
||||
|
||||
export interface PostDetailDTO extends PostDTO {
|
||||
status: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// ==================== Comment DTOs ====================
|
||||
|
||||
// 评论图片响应
|
||||
export interface CommentImageDTO {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface CommentDTO {
|
||||
id: string;
|
||||
post_id: string;
|
||||
user_id: string;
|
||||
parent_id: string | null;
|
||||
root_id: string | null;
|
||||
content: string;
|
||||
images: CommentImageDTO[];
|
||||
likes_count: number;
|
||||
replies_count: number;
|
||||
created_at: string;
|
||||
author: UserDTO;
|
||||
is_liked: boolean;
|
||||
target_id?: string | null; // 被回复的评论ID,前端根据此ID找到被回复用户的昵称
|
||||
replies?: CommentDTO[]; // 子回复列表(扁平化,所有层级都在这里)
|
||||
}
|
||||
|
||||
// ==================== Notification DTOs ====================
|
||||
|
||||
export interface NotificationDTO {
|
||||
id: string;
|
||||
user_id: string;
|
||||
type: string;
|
||||
title: string;
|
||||
content: string;
|
||||
data: string;
|
||||
is_read: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ==================== Chat System Types ====================
|
||||
|
||||
// 会话类型
|
||||
export type ConversationType = 'private' | 'group';
|
||||
|
||||
// 消息内容类型
|
||||
export type MessageContentType = 'text' | 'image' | 'video' | 'audio' | 'file';
|
||||
|
||||
// 消息状态
|
||||
export type MessageStatus = 'normal' | 'recalled' | 'deleted';
|
||||
|
||||
// ==================== Message Segment Types ====================
|
||||
|
||||
// Segment类型
|
||||
export type SegmentType = 'text' | 'image' | 'voice' | 'video' | 'file' | 'at' | 'reply' | 'face' | 'link';
|
||||
|
||||
// 文本Segment数据
|
||||
export interface TextSegmentData {
|
||||
content?: string; // 旧格式字段
|
||||
text?: string; // 新格式字段(用于 REST API)
|
||||
}
|
||||
|
||||
// 图片Segment数据
|
||||
export interface ImageSegmentData {
|
||||
url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
thumbnail_url?: string;
|
||||
file_size?: number;
|
||||
}
|
||||
|
||||
// 语音Segment数据
|
||||
export interface VoiceSegmentData {
|
||||
url: string;
|
||||
duration?: number; // 秒
|
||||
file_size?: number;
|
||||
}
|
||||
|
||||
// 视频Segment数据
|
||||
export interface VideoSegmentData {
|
||||
url: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
duration?: number; // 秒
|
||||
thumbnail_url?: string;
|
||||
file_size?: number;
|
||||
}
|
||||
|
||||
// 文件Segment数据
|
||||
export interface FileSegmentData {
|
||||
url: string;
|
||||
name: string;
|
||||
size?: number;
|
||||
mime_type?: string;
|
||||
}
|
||||
|
||||
// @Segment数据
|
||||
export interface AtSegmentData {
|
||||
user_id: string; // "all" 表示@所有人
|
||||
nickname?: string;
|
||||
}
|
||||
|
||||
// 回复Segment数据
|
||||
export interface ReplySegmentData {
|
||||
id: string; // 被回复消息的ID
|
||||
seq?: number; // 被回复消息的序号(可选,用于快速查找)
|
||||
}
|
||||
|
||||
// 表情Segment数据
|
||||
export interface FaceSegmentData {
|
||||
id: number;
|
||||
name?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
// 链接Segment数据
|
||||
export interface LinkSegmentData {
|
||||
url: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
// MessageSegment 联合类型 - 使用 discriminated union
|
||||
export type MessageSegment =
|
||||
| { type: 'text'; data: TextSegmentData }
|
||||
| { type: 'image'; data: ImageSegmentData }
|
||||
| { type: 'voice'; data: VoiceSegmentData }
|
||||
| { type: 'video'; data: VideoSegmentData }
|
||||
| { type: 'file'; data: FileSegmentData }
|
||||
| { type: 'at'; data: AtSegmentData }
|
||||
| { type: 'reply'; data: ReplySegmentData }
|
||||
| { type: 'face'; data: FaceSegmentData }
|
||||
| { type: 'link'; data: LinkSegmentData };
|
||||
|
||||
// 消息响应
|
||||
export interface MessageResponse {
|
||||
id: string; // 雪花算法ID (使用string避免JavaScript精度丢失)
|
||||
conversation_id: string;
|
||||
sender_id: string; // UUID字符串
|
||||
seq: number; // 消息序号
|
||||
segments: MessageSegment[]; // 消息链
|
||||
reply_to_id?: string; // 被回复消息的ID(用于关联查找)
|
||||
status: MessageStatus;
|
||||
category?: string; // 消息类别:chat, notification, announcement
|
||||
created_at: string;
|
||||
sender?: UserDTO;
|
||||
}
|
||||
|
||||
// 会话响应
|
||||
export interface ConversationResponse {
|
||||
id: string; // 雪花算法ID (使用string避免JavaScript精度丢失)
|
||||
type: ConversationType;
|
||||
is_pinned?: boolean;
|
||||
last_seq: number;
|
||||
last_message?: MessageResponse;
|
||||
last_message_at: string;
|
||||
unread_count: number;
|
||||
participants?: UserDTO[]; // 私聊时使用
|
||||
member_count?: number; // 群聊时使用
|
||||
group?: GroupResponse; // 群聊会话的群组信息
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 会话参与者响应
|
||||
export interface ConversationParticipantResponse {
|
||||
user_id: string;
|
||||
last_read_seq: number;
|
||||
muted: boolean;
|
||||
}
|
||||
|
||||
// 会话详情响应
|
||||
export interface ConversationDetailResponse {
|
||||
id: string;
|
||||
type: ConversationType;
|
||||
is_pinned?: boolean;
|
||||
last_seq: number;
|
||||
last_message?: MessageResponse;
|
||||
last_message_at: string;
|
||||
unread_count: number;
|
||||
participants: UserDTO[];
|
||||
my_last_read_seq: number; // 当前用户的已读位置
|
||||
other_last_read_seq: number; // 对方用户的已读位置
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// 创建会话请求
|
||||
export interface CreateConversationRequest {
|
||||
user_id: string; // 目标用户ID (UUID格式)
|
||||
}
|
||||
|
||||
// 发送消息请求
|
||||
export interface SendMessageRequest {
|
||||
segments?: MessageSegment[]; // 消息链(可选)
|
||||
content?: string; // 纯文本内容(可选,用于兼容旧版本)
|
||||
reply_to_id?: string; // 回复的消息ID (string类型)
|
||||
}
|
||||
|
||||
// 标记已读请求
|
||||
export interface MarkReadRequest {
|
||||
last_read_seq: number; // 已读到的seq位置
|
||||
}
|
||||
|
||||
// 未读数响应
|
||||
export interface UnreadCountResponse {
|
||||
total_unread_count: number; // 所有会话的未读总数
|
||||
}
|
||||
|
||||
// 单个会话未读数响应
|
||||
export interface ConversationUnreadCountResponse {
|
||||
conversation_id: string;
|
||||
unread_count: number;
|
||||
}
|
||||
|
||||
// 会话列表响应
|
||||
export interface ConversationListResponse {
|
||||
list: ConversationResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
// 消息列表响应
|
||||
export interface MessageListResponse {
|
||||
messages: MessageResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
// 消息同步响应(增量同步)
|
||||
export interface MessageSyncResponse {
|
||||
messages: MessageResponse[];
|
||||
has_more: boolean;
|
||||
}
|
||||
|
||||
// ==================== Legacy Message DTOs (Deprecated) ====================
|
||||
|
||||
/**
|
||||
* @deprecated 使用 MessageResponse 代替
|
||||
*/
|
||||
export interface MessageDTO {
|
||||
id: string;
|
||||
conversation_id: string;
|
||||
sender_id: string;
|
||||
content: string;
|
||||
message_type: string;
|
||||
is_read: boolean;
|
||||
created_at: string;
|
||||
sender: UserDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 ConversationResponse 代替
|
||||
*/
|
||||
export interface ConversationDTO {
|
||||
id: string;
|
||||
user1_id: string;
|
||||
user2_id: string;
|
||||
last_message: MessageDTO | null;
|
||||
last_message_at: string;
|
||||
unread_count: number;
|
||||
participant: UserDTO;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
// ==================== Auth DTOs ====================
|
||||
|
||||
export interface LoginDTO {
|
||||
user: UserDTO;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface RegisterDTO {
|
||||
user: UserDTO;
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenDTO {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
// ==================== Common DTOs ====================
|
||||
|
||||
export interface SuccessDTO {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface AvailableDTO {
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export interface CountDTO {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface URLDTO {
|
||||
url: string;
|
||||
}
|
||||
|
||||
// ==================== API Response Types ====================
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface PaginatedData<T> {
|
||||
list: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
// ==================== System Message Types ====================
|
||||
|
||||
// 消息分类
|
||||
export type MessageCategory = 'chat' | 'notification' | 'announcement' | 'marketing';
|
||||
|
||||
// 系统消息类型
|
||||
export type SystemMessageType =
|
||||
| 'like_post'
|
||||
| 'like_comment'
|
||||
| 'like_reply'
|
||||
| 'favorite_post'
|
||||
| 'comment'
|
||||
| 'reply'
|
||||
| 'follow'
|
||||
| 'mention'
|
||||
| 'system'
|
||||
| 'announcement'
|
||||
| 'announce'
|
||||
| 'group_invite'
|
||||
| 'group_join_apply'
|
||||
| 'group_join_approved'
|
||||
| 'group_join_rejected';
|
||||
|
||||
// 系统消息额外数据
|
||||
export interface SystemMessageExtraData {
|
||||
// 后端返回的字段(使用 actor_ 前缀)
|
||||
actor_id?: number; // 操作者ID (数字格式,兼容旧数据)
|
||||
actor_id_str?: string; // 操作者ID (UUID字符串格式)
|
||||
actor_name?: string; // 操作者名称
|
||||
avatar_url?: string; // 操作者头像
|
||||
target_id?: string; // 目标ID (UUID字符串格式)
|
||||
target_title?: string; // 目标标题(如帖子标题)
|
||||
target_type?: string; // 目标类型
|
||||
action_url?: string; // 操作URL
|
||||
action_time?: string; // 操作时间
|
||||
group_id?: string;
|
||||
group_name?: string;
|
||||
group_avatar?: string;
|
||||
group_description?: string;
|
||||
flag?: string;
|
||||
request_type?: 'invite' | 'join_apply';
|
||||
request_status?: 'pending' | 'accepted' | 'rejected' | 'cancelled' | 'expired';
|
||||
reason?: string;
|
||||
target_user_id?: string;
|
||||
target_user_name?: string;
|
||||
target_user_avatar?: string;
|
||||
|
||||
// 兼容旧字段(使用 operator_ 前缀)
|
||||
operator_id?: number; // 操作者ID
|
||||
operator_name?: string; // 操作者名称
|
||||
operator_avatar?: string; // 操作者头像
|
||||
post_id?: number; // 帖子ID
|
||||
post_title?: string; // 帖子标题
|
||||
comment_id?: number; // 评论ID
|
||||
comment_preview?: string; // 评论预览
|
||||
reply_id?: number; // 回复ID
|
||||
}
|
||||
|
||||
// 系统消息响应
|
||||
export interface SystemMessageResponse {
|
||||
id: string; // 后端返回字符串格式
|
||||
sender_id: string;
|
||||
receiver_id: string;
|
||||
content: string;
|
||||
category: MessageCategory;
|
||||
system_type: SystemMessageType;
|
||||
extra_data?: SystemMessageExtraData;
|
||||
is_read?: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 系统消息列表响应
|
||||
export interface SystemMessageListResponse {
|
||||
messages: SystemMessageResponse[];
|
||||
total: number;
|
||||
has_more: boolean;
|
||||
}
|
||||
|
||||
// 系统消息未读数响应
|
||||
export interface SystemUnreadCountResponse {
|
||||
unread_count: number;
|
||||
}
|
||||
|
||||
// ==================== Push Device Types ====================
|
||||
|
||||
// 设备类型
|
||||
export type DeviceType = 'ios' | 'android' | 'web';
|
||||
|
||||
// 设备Token响应
|
||||
export interface DeviceTokenResponse {
|
||||
id: number;
|
||||
device_id: string;
|
||||
device_type: DeviceType;
|
||||
is_active: boolean;
|
||||
device_name?: string;
|
||||
last_used_at: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 注册设备请求
|
||||
export interface RegisterDeviceRequest {
|
||||
device_id: string;
|
||||
device_type: DeviceType;
|
||||
push_token?: string;
|
||||
device_name?: string;
|
||||
}
|
||||
|
||||
// 推送记录响应
|
||||
export interface PushRecordResponse {
|
||||
id: number;
|
||||
message_id: number;
|
||||
push_channel: string;
|
||||
push_status: string;
|
||||
retry_count: number;
|
||||
pushed_at?: string;
|
||||
delivered_at?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ==================== Group Chat Types ====================
|
||||
|
||||
// 群组加入类型
|
||||
export type JoinType = 0 | 1 | 2; // 0:允许任何人 1:需要审批 2:不允许
|
||||
|
||||
// 群组成员角色
|
||||
export type GroupRole = 'owner' | 'admin' | 'member';
|
||||
|
||||
// 群组信息
|
||||
export interface GroupResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
avatar: string;
|
||||
description: string;
|
||||
owner_id: string; // UUID字符串
|
||||
member_count: number;
|
||||
max_members: number;
|
||||
join_type: JoinType;
|
||||
mute_all: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 群组成员信息
|
||||
export interface GroupMemberResponse {
|
||||
id: number;
|
||||
group_id: number;
|
||||
user_id: string; // UUID字符串
|
||||
role: GroupRole;
|
||||
nickname: string;
|
||||
muted: boolean;
|
||||
join_time: string;
|
||||
user?: UserDTO;
|
||||
}
|
||||
|
||||
// 群公告信息
|
||||
export interface GroupAnnouncementResponse {
|
||||
id: number;
|
||||
group_id: number;
|
||||
content: string;
|
||||
author_id: string; // UUID字符串
|
||||
is_pinned: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 创建群组请求
|
||||
export interface CreateGroupRequest {
|
||||
name: string;
|
||||
description?: string;
|
||||
member_ids?: string[]; // UUID字符串数组
|
||||
}
|
||||
|
||||
// 更新群组请求
|
||||
export interface UpdateGroupRequest {
|
||||
name?: string;
|
||||
description?: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
// 邀请成员请求
|
||||
export interface InviteMembersRequest {
|
||||
member_ids: string[]; // UUID字符串数组
|
||||
}
|
||||
|
||||
export interface HandleGroupRequestAction {
|
||||
flag: string;
|
||||
approve: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
// 转让群主请求
|
||||
export interface TransferOwnerRequest {
|
||||
new_owner_id: string; // UUID字符串
|
||||
}
|
||||
|
||||
// 设置成员角色请求
|
||||
export interface SetRoleRequest {
|
||||
role: 'admin' | 'member';
|
||||
}
|
||||
|
||||
// 设置群昵称请求
|
||||
export interface SetNicknameRequest {
|
||||
nickname: string;
|
||||
}
|
||||
|
||||
// 禁言成员请求
|
||||
export interface MuteMemberRequest {
|
||||
muted: boolean;
|
||||
}
|
||||
|
||||
// 全员禁言请求
|
||||
export interface SetMuteAllRequest {
|
||||
mute_all: boolean;
|
||||
}
|
||||
|
||||
// 当前用户群成员信息响应
|
||||
export interface MyMemberInfoResponse {
|
||||
member: GroupMemberResponse;
|
||||
mute_all: boolean;
|
||||
is_muted: boolean;
|
||||
can_speak: boolean;
|
||||
}
|
||||
|
||||
// 设置加群方式请求
|
||||
export interface SetJoinTypeRequest {
|
||||
join_type: JoinType;
|
||||
}
|
||||
|
||||
// 设置群头像请求
|
||||
export interface SetGroupAvatarRequest {
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
// 创建群公告请求
|
||||
export interface CreateAnnouncementRequest {
|
||||
content: string;
|
||||
}
|
||||
|
||||
// 群组列表响应
|
||||
export interface GroupListResponse {
|
||||
list: GroupResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
// 群组成员列表响应
|
||||
export interface GroupMemberListResponse {
|
||||
list: GroupMemberResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
// 群公告列表响应
|
||||
export interface GroupAnnouncementListResponse {
|
||||
list: GroupAnnouncementResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
// ==================== WebSocket Event Types ====================
|
||||
|
||||
// WebSocket 事件类型
|
||||
export type WSEventType = 'message' | 'notice' | 'request' | 'meta';
|
||||
|
||||
// WebSocket 消息详细类型
|
||||
export type WSDetailType = 'private' | 'group' | 'follow' | 'like' | 'comment' | 'request' | 'typing' | 'heartbeat' | 'read';
|
||||
|
||||
// WebSocket 事件消息段(与后端 message 字段一致)
|
||||
export interface WSSegment {
|
||||
type: string;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
// WebSocket 事件(后端推送的事件格式)
|
||||
export interface WSEvent {
|
||||
id: string; // 事件唯一ID
|
||||
time: number; // 事件时间戳(毫秒)
|
||||
type: WSEventType; // 事件类型: message(消息), notice(通知), request(请求), meta(元事件)
|
||||
detail_type: WSDetailType; // 详细类型: private(私聊), group(群聊), follow(关注), like(点赞)等
|
||||
seq: string; // 消息序号
|
||||
message?: WSSegment[]; // 消息内容数组
|
||||
conversation_id: string; // 会话ID
|
||||
}
|
||||
|
||||
/**
|
||||
* 从消息segments中提取纯文本内容
|
||||
* 用于消息列表显示、通知等场景
|
||||
*/
|
||||
export function extractTextFromSegments(segments?: MessageSegment[]): string {
|
||||
if (!segments || segments.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let result = '';
|
||||
|
||||
for (const segment of segments) {
|
||||
switch (segment.type) {
|
||||
case 'text':
|
||||
// 兼容 content 和 text 两种字段
|
||||
const textData = segment.data as TextSegmentData;
|
||||
result += textData.content ?? textData.text ?? '';
|
||||
break;
|
||||
case 'at':
|
||||
const atData = segment.data as AtSegmentData;
|
||||
if (atData.user_id === 'all') {
|
||||
result += '@所有人 ';
|
||||
} else if (atData.nickname) {
|
||||
result += `@${atData.nickname} `;
|
||||
} else {
|
||||
result += `@${atData.user_id} `;
|
||||
}
|
||||
break;
|
||||
case 'image':
|
||||
result += '[图片]';
|
||||
break;
|
||||
case 'voice':
|
||||
result += '[语音]';
|
||||
break;
|
||||
case 'video':
|
||||
result += '[视频]';
|
||||
break;
|
||||
case 'file':
|
||||
const fileData = segment.data as FileSegmentData;
|
||||
result += `[文件: ${fileData.name || '未知'}]`;
|
||||
break;
|
||||
case 'face':
|
||||
const faceData = segment.data as FaceSegmentData;
|
||||
result += faceData.name ? `[${faceData.name}]` : '[表情]';
|
||||
break;
|
||||
case 'link':
|
||||
const linkData = segment.data as LinkSegmentData;
|
||||
result += linkData.title ? `[链接: ${linkData.title}]` : '[链接]';
|
||||
break;
|
||||
case 'reply':
|
||||
// 回复不显示在预览中
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
// ==================== Vote DTOs ====================
|
||||
|
||||
// 投票选项
|
||||
export interface VoteOptionDTO {
|
||||
id: string;
|
||||
content: string;
|
||||
votes_count: number;
|
||||
}
|
||||
|
||||
// 投票结果
|
||||
export interface VoteResultDTO {
|
||||
options: VoteOptionDTO[];
|
||||
total_votes: number;
|
||||
has_voted: boolean;
|
||||
voted_option_id?: string;
|
||||
}
|
||||
|
||||
// 创建投票帖子请求
|
||||
export interface CreateVotePostRequest {
|
||||
title: string;
|
||||
content?: string;
|
||||
community_id?: string;
|
||||
images?: string[];
|
||||
vote_options: string[]; // 至少2个,最多10个
|
||||
}
|
||||
|
||||
/**
|
||||
* 从消息segments中提取纯文本内容(异步版本)
|
||||
* 用于会话列表等需要获取AT用户名的场景
|
||||
* 优先从本地缓存获取用户信息,fallback到API
|
||||
*/
|
||||
export async function extractTextFromSegmentsAsync(
|
||||
segments?: MessageSegment[],
|
||||
getUserCache?: (userId: string) => Promise<UserDTO | null>,
|
||||
getUserById?: (userId: string) => Promise<UserDTO | null>
|
||||
): Promise<string> {
|
||||
if (!segments || segments.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let result = '';
|
||||
|
||||
for (const segment of segments) {
|
||||
switch (segment.type) {
|
||||
case 'text':
|
||||
const textData = segment.data as TextSegmentData;
|
||||
result += textData.content ?? textData.text ?? '';
|
||||
break;
|
||||
case 'at':
|
||||
const atData = segment.data as AtSegmentData;
|
||||
if (atData.user_id === 'all') {
|
||||
result += '@所有人 ';
|
||||
} else if (atData.nickname) {
|
||||
// 优先使用已有的 nickname
|
||||
result += `@${atData.nickname} `;
|
||||
} else {
|
||||
// 异步获取用户名
|
||||
let nickname = atData.user_id; // 默认显示 user_id
|
||||
try {
|
||||
// 先尝试从缓存获取
|
||||
if (getUserCache) {
|
||||
const cachedUser = await getUserCache(atData.user_id);
|
||||
if (cachedUser?.nickname) {
|
||||
nickname = cachedUser.nickname;
|
||||
} else if (getUserById) {
|
||||
// 缓存没有,从 API 获取
|
||||
const user = await getUserById(atData.user_id);
|
||||
if (user?.nickname) {
|
||||
nickname = user.nickname;
|
||||
}
|
||||
}
|
||||
} else if (getUserById) {
|
||||
// 没有缓存函数,直接从 API 获取
|
||||
const user = await getUserById(atData.user_id);
|
||||
if (user?.nickname) {
|
||||
nickname = user.nickname;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 获取失败,使用 user_id 作为 fallback
|
||||
console.warn('[extractTextFromSegmentsAsync] 获取用户信息失败:', error);
|
||||
}
|
||||
result += `@${nickname} `;
|
||||
}
|
||||
break;
|
||||
case 'image':
|
||||
result += '[图片]';
|
||||
break;
|
||||
case 'voice':
|
||||
result += '[语音]';
|
||||
break;
|
||||
case 'video':
|
||||
result += '[视频]';
|
||||
break;
|
||||
case 'file':
|
||||
const fileData = segment.data as FileSegmentData;
|
||||
result += `[文件: ${fileData.name || '未知'}]`;
|
||||
break;
|
||||
case 'face':
|
||||
const faceData = segment.data as FaceSegmentData;
|
||||
result += faceData.name ? `[${faceData.name}]` : '[表情]';
|
||||
break;
|
||||
case 'link':
|
||||
const linkData = segment.data as LinkSegmentData;
|
||||
result += linkData.title ? `[链接: ${linkData.title}]` : '[链接]';
|
||||
break;
|
||||
case 'reply':
|
||||
// 回复不显示在预览中
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
248
src/types/index.ts
Normal file
248
src/types/index.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
// ============================================
|
||||
// Carrot BBS - Core Type Definitions
|
||||
// 使用后端DTO类型,保持snake_case命名规范
|
||||
// ============================================
|
||||
|
||||
// 导出DTO类型作为主要类型
|
||||
export * from './dto';
|
||||
|
||||
// 兼容旧类型(用于内部组件)
|
||||
import type {
|
||||
UserDTO,
|
||||
PostDTO,
|
||||
PostImageDTO,
|
||||
// 新的聊天系统类型
|
||||
ConversationType,
|
||||
MessageContentType,
|
||||
MessageStatus,
|
||||
MessageResponse,
|
||||
ConversationResponse,
|
||||
ConversationParticipantResponse,
|
||||
ConversationDetailResponse,
|
||||
CreateConversationRequest,
|
||||
SendMessageRequest,
|
||||
MarkReadRequest,
|
||||
UnreadCountResponse,
|
||||
ConversationUnreadCountResponse,
|
||||
ConversationListResponse,
|
||||
MessageListResponse,
|
||||
MessageSyncResponse,
|
||||
// Segment 类型
|
||||
SegmentType,
|
||||
MessageSegment,
|
||||
TextSegmentData,
|
||||
ImageSegmentData,
|
||||
VoiceSegmentData,
|
||||
VideoSegmentData,
|
||||
FileSegmentData,
|
||||
AtSegmentData,
|
||||
ReplySegmentData,
|
||||
FaceSegmentData,
|
||||
LinkSegmentData,
|
||||
} from './dto';
|
||||
|
||||
// 重新导出新的聊天系统类型
|
||||
export type {
|
||||
ConversationType,
|
||||
MessageContentType,
|
||||
MessageStatus,
|
||||
MessageResponse,
|
||||
ConversationResponse,
|
||||
ConversationParticipantResponse,
|
||||
ConversationDetailResponse,
|
||||
CreateConversationRequest,
|
||||
SendMessageRequest,
|
||||
MarkReadRequest,
|
||||
UnreadCountResponse,
|
||||
ConversationUnreadCountResponse,
|
||||
ConversationListResponse,
|
||||
MessageListResponse,
|
||||
MessageSyncResponse,
|
||||
// Segment 类型
|
||||
SegmentType,
|
||||
MessageSegment,
|
||||
TextSegmentData,
|
||||
ImageSegmentData,
|
||||
VoiceSegmentData,
|
||||
VideoSegmentData,
|
||||
FileSegmentData,
|
||||
AtSegmentData,
|
||||
ReplySegmentData,
|
||||
FaceSegmentData,
|
||||
LinkSegmentData,
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// User Types
|
||||
// ============================================
|
||||
|
||||
export type User = UserDTO;
|
||||
|
||||
export interface UserProfile extends User {
|
||||
favorites_count: number;
|
||||
liked_posts_count: number;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Post Types
|
||||
// ============================================
|
||||
|
||||
export type Post = PostDTO;
|
||||
export type PostDetail = PostDTO;
|
||||
export type PostImage = PostImageDTO;
|
||||
|
||||
// ============================================
|
||||
// Vote Types
|
||||
// ============================================
|
||||
|
||||
export type { VoteOptionDTO as VoteOption, VoteResultDTO as VoteResult, CreateVotePostRequest as CreateVotePostReq } from './dto';
|
||||
|
||||
export interface CreatePostInput {
|
||||
title: string;
|
||||
content: string;
|
||||
images?: string[]; // 本地图片路径或 base64
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Comment Types
|
||||
// ============================================
|
||||
|
||||
// 评论图片
|
||||
export interface CommentImage {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
id: string;
|
||||
post_id: string;
|
||||
user_id: string;
|
||||
parent_id: string | null;
|
||||
root_id?: string | null;
|
||||
content: string;
|
||||
images?: CommentImage[];
|
||||
likes_count: number;
|
||||
replies_count: number;
|
||||
is_liked?: boolean;
|
||||
author: User;
|
||||
replies?: Comment[];
|
||||
target_id?: string | null; // 被回复的评论ID,前端根据此ID找到被回复用户的昵称
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateCommentInput {
|
||||
postId: string;
|
||||
parentId?: string;
|
||||
content: string;
|
||||
images?: string[]; // 图片URL列表
|
||||
replyToUserId?: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Notification Types
|
||||
// ============================================
|
||||
|
||||
export type NotificationType =
|
||||
| 'like_post'
|
||||
| 'like_comment'
|
||||
| 'comment'
|
||||
| 'reply'
|
||||
| 'follow'
|
||||
| 'mention'
|
||||
| 'system';
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
userId: string;
|
||||
type: NotificationType;
|
||||
title: string;
|
||||
content: string;
|
||||
data: {
|
||||
postId?: string;
|
||||
commentId?: string;
|
||||
userId?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
isRead: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface NotificationBadge {
|
||||
total: number;
|
||||
likes: number;
|
||||
comments: number;
|
||||
follows: number;
|
||||
system: number;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Message Types (New Chat System)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 新版会话类型 - 使用雪花算法ID和seq机制
|
||||
*/
|
||||
export type Conversation = ConversationResponse;
|
||||
|
||||
/**
|
||||
* 新版消息类型 - 使用雪花算法ID和seq机制
|
||||
*/
|
||||
export type Message = MessageResponse;
|
||||
|
||||
// ============================================
|
||||
// Legacy Message Types (Deprecated)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* @deprecated 使用 ConversationResponse 代替
|
||||
*/
|
||||
export interface LegacyConversation {
|
||||
id: string;
|
||||
participant: User;
|
||||
lastMessage: LegacyMessage;
|
||||
unreadCount: number;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用 MessageResponse 代替
|
||||
*/
|
||||
export interface LegacyMessage {
|
||||
id: string;
|
||||
conversationId: string;
|
||||
senderId: string;
|
||||
content: string;
|
||||
type: 'text' | 'image' | 'system';
|
||||
isRead: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// API Response Types
|
||||
// ============================================
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean;
|
||||
data: T;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface PaginatedResponse<T> {
|
||||
success: boolean;
|
||||
data: T[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
hasMore: boolean;
|
||||
nextCursor?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ApiError {
|
||||
success: false;
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: Record<string, string>;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user