Files
frontend/src/services/message/groupService.ts
lan ea9e51b0b0
All checks were successful
Frontend CI / ota-ios (push) Successful in 1m33s
Frontend CI / ota-android (push) Successful in 1m34s
Frontend CI / build-and-push-web (push) Successful in 2m31s
Frontend CI / build-android-apk (push) Successful in 1h2m56s
feat(ui): implement keyword highlighting and improve search experience
- Add keyword highlighting in `PostCard` and `PostContentRenderer` using `HighlightText`.
- Implement smart excerpt logic in `PostCard` to show relevant context around search keywords.
- Update `SearchBar` styles for better visual feedback and consistency.
- Enhance `SearchScreen` with modern tab variants and improved tag styling.
- Add support for updating group descriptions in `GroupInfoScreen` and `GroupService`.
- Refactor various UI components for improved layout stability and typography.
2026-05-07 01:08:58 +08:00

475 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 群组服务
* 处理群组管理、成员管理、群设置、群公告等功能
* 使用 RESTful API: /api/v1/groups
*/
import { api } from '../core/api';
import {
GroupResponse,
GroupMemberResponse,
GroupAnnouncementResponse,
GroupListResponse,
GroupMemberListResponse,
GroupAnnouncementListResponse,
CreateGroupRequest,
InviteMembersRequest,
TransferOwnerRequest,
SetRoleRequest,
SetNicknameRequest,
SetMuteAllRequest,
SetJoinTypeRequest,
CreateAnnouncementRequest,
MyMemberInfoResponse,
SetGroupAvatarRequest,
HandleGroupRequestAction,
CursorPaginationRequest,
CursorPaginationResponse,
} from '@/types/dto';
function asGroupId(raw: unknown): string {
return String(raw);
}
function normalizeGroup(g: GroupResponse): GroupResponse {
return { ...g, id: asGroupId(g.id) };
}
function normalizeMember(m: GroupMemberResponse): GroupMemberResponse {
return { ...m, group_id: asGroupId(m.group_id) };
}
function normalizeAnnouncement(a: GroupAnnouncementResponse): GroupAnnouncementResponse {
return { ...a, group_id: asGroupId(a.group_id) };
}
function normalizeGroupListResponse(r: GroupListResponse): GroupListResponse {
return { ...r, list: r.list.map(normalizeGroup) };
}
function normalizeMemberListResponse(r: GroupMemberListResponse): GroupMemberListResponse {
return { ...r, list: r.list.map(normalizeMember) };
}
function normalizeAnnouncementListResponse(
r: GroupAnnouncementListResponse
): GroupAnnouncementListResponse {
return { ...r, list: r.list.map(normalizeAnnouncement) };
}
function normalizeGroupCursor(
r: CursorPaginationResponse<GroupResponse>
): CursorPaginationResponse<GroupResponse> {
return { ...r, list: r.list.map(normalizeGroup) };
}
function normalizeMemberCursor(
r: CursorPaginationResponse<GroupMemberResponse>
): CursorPaginationResponse<GroupMemberResponse> {
return { ...r, list: r.list.map(normalizeMember) };
}
function normalizeAnnouncementCursor(
r: CursorPaginationResponse<GroupAnnouncementResponse>
): CursorPaginationResponse<GroupAnnouncementResponse> {
return { ...r, list: r.list.map(normalizeAnnouncement) };
}
// 群组服务类(纯 API 层)
// SQLite/内存缓存编排由 groupManager 统一处理
class GroupService {
// ==================== 群组管理 ====================
/**
* 获取群组列表
* GET /api/v1/groups
*/
async getGroups(page = 1, pageSize = 20): Promise<GroupListResponse> {
const response = await api.get<GroupListResponse>('/groups', {
page,
page_size: pageSize,
});
return normalizeGroupListResponse(response.data);
}
/**
* 获取群组详情
* GET /api/v1/groups/:id
*/
async getGroup(id: string): Promise<GroupResponse> {
const response = await api.get<GroupResponse>(`/groups/${encodeURIComponent(id)}`);
return normalizeGroup(response.data);
}
/**
* 获取群组成员列表
* GET /api/v1/groups/:id/members
*/
async getMembers(groupId: string, page = 1, pageSize = 50): Promise<GroupMemberListResponse> {
const response = await api.get<GroupMemberListResponse>(
`/groups/${encodeURIComponent(groupId)}/members`,
{
page,
page_size: pageSize,
}
);
return normalizeMemberListResponse(response.data);
}
/**
* 创建群组
* POST /api/v1/groups
*/
async createGroup(data: CreateGroupRequest): Promise<GroupResponse> {
const response = await api.post<GroupResponse>('/groups', data);
return normalizeGroup(response.data);
}
/**
* 获取当前用户在群组中的成员信息
* GET /api/v1/groups/:id/me
*/
async getMyMemberInfo(groupId: string): Promise<MyMemberInfoResponse> {
const response = await api.get<MyMemberInfoResponse>(
`/groups/${encodeURIComponent(groupId)}/me`
);
return {
...response.data,
member: normalizeMember(response.data.member),
};
}
/**
* 解散群组
* DELETE /api/v1/groups/:id
*/
async dissolveGroup(id: string): Promise<void> {
await api.delete(`/groups/${encodeURIComponent(id)}`);
}
/**
* 转让群主
* POST /api/v1/groups/:id/transfer
*/
async transferOwner(id: string, data: TransferOwnerRequest): Promise<void> {
await api.post(`/groups/${encodeURIComponent(id)}/transfer`, data);
}
// ==================== 成员管理 ====================
/**
* 邀请成员加入群组
* POST /api/v1/groups/:id/invitations
*/
async inviteMembers(groupId: string, data: InviteMembersRequest): Promise<void> {
await api.post(`/groups/${encodeURIComponent(groupId)}/invitations`, data);
}
/**
* 申请加入群组
* POST /api/v1/groups/:id/join-requests
*/
async joinGroup(groupId: string): Promise<void> {
await api.post(`/groups/${encodeURIComponent(groupId)}/join-requests`);
}
/**
* 响应群邀请/申请
* POST /api/v1/groups/:id/join-requests/respond
*/
async respondInvite(groupId: string, data: HandleGroupRequestAction): Promise<void> {
await api.post(
`/groups/${encodeURIComponent(groupId)}/join-requests/respond`,
data
);
}
/**
* 审批主动加群申请
* POST /api/v1/groups/:id/join-requests/handle
*/
async reviewJoinRequest(groupId: string, data: HandleGroupRequestAction): Promise<void> {
await api.post(
`/groups/${encodeURIComponent(groupId)}/join-requests/handle`,
data
);
}
/**
* 退出群组
* POST /api/v1/groups/:id/leave
*/
async leaveGroup(groupId: string): Promise<void> {
await api.post(`/groups/${encodeURIComponent(groupId)}/leave`);
}
/**
* 移除群成员(踢人)
* POST /api/v1/groups/:id/members/kick
*/
async removeMember(groupId: string, userId: string): Promise<void> {
await api.post(`/groups/${encodeURIComponent(groupId)}/members/kick`, {
user_id: userId,
reject_add_request: false,
});
}
/**
* 设置成员角色(设置/取消管理员)
* PUT /api/v1/groups/:id/members/:user_id/admin
*/
async setMemberRole(
groupId: string,
userId: string,
data: SetRoleRequest
): Promise<void> {
await api.put(
`/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userId)}/admin`,
{
enable: data.role === 'admin',
}
);
}
/**
* 设置群昵称
* PUT /api/v1/groups/:id/members/me/nickname
*/
async setNickname(groupId: string, data: SetNicknameRequest): Promise<void> {
await api.put(`/groups/${encodeURIComponent(groupId)}/members/me/nickname`, data);
}
/**
* 禁言/解禁成员
* POST /api/v1/groups/:id/members/ban
* @param groupId 群组ID
* @param userId 用户ID
* @param duration 禁言时长0表示解除禁言
*/
async muteMember(
groupId: string,
userId: string,
duration: number = 0
): Promise<void> {
await api.post(`/groups/${encodeURIComponent(groupId)}/members/ban`, {
user_id: userId,
duration: duration,
});
}
// ==================== 群设置 ====================
/**
* 设置全员禁言
* PUT /api/v1/groups/:id/ban
*/
async setMuteAll(groupId: string, data: SetMuteAllRequest): Promise<void> {
await api.put(`/groups/${encodeURIComponent(groupId)}/ban`, {
enable: data.mute_all,
});
}
/**
* 设置加群方式
* PUT /api/v1/groups/:id/join-type
*/
async setJoinType(groupId: string, data: SetJoinTypeRequest): Promise<void> {
await api.put(`/groups/${encodeURIComponent(groupId)}/join-type`, data);
}
/**
* 设置群名称
* PUT /api/v1/groups/:id/name
*/
async setGroupName(groupId: string, groupName: string): Promise<void> {
await api.put(`/groups/${encodeURIComponent(groupId)}/name`, {
group_name: groupName,
});
}
/**
* 设置群头像
* PUT /api/v1/groups/:id/avatar
*/
async setGroupAvatar(groupId: string, avatarUrl: string): Promise<GroupResponse> {
const response = await api.put<GroupResponse>(
`/groups/${encodeURIComponent(groupId)}/avatar`,
{
avatar: avatarUrl,
}
);
return normalizeGroup(response.data);
}
/**
* 设置群描述
* PUT /api/v1/groups/:id/description
*/
async setGroupDescription(groupId: string, description: string): Promise<GroupResponse> {
const response = await api.put<GroupResponse>(
`/groups/${encodeURIComponent(groupId)}/description`,
{
description,
}
);
return normalizeGroup(response.data);
}
// ==================== 群公告 ====================
/**
* 创建群公告
* POST /api/v1/groups/:id/announcements
*/
async createAnnouncement(
groupId: string,
data: CreateAnnouncementRequest
): Promise<GroupAnnouncementResponse> {
const response = await api.post<GroupAnnouncementResponse>(
`/groups/${encodeURIComponent(groupId)}/announcements`,
data
);
return normalizeAnnouncement(response.data);
}
/**
* 获取群公告列表
* GET /api/v1/groups/:id/announcements
*/
async getAnnouncements(
groupId: string,
page = 1,
pageSize = 20
): Promise<GroupAnnouncementListResponse> {
try {
const response = await api.get<GroupAnnouncementListResponse>(
`/groups/${encodeURIComponent(groupId)}/announcements`,
{
page,
page_size: pageSize,
}
);
return normalizeAnnouncementListResponse(response.data);
} catch (error) {
console.error('获取群公告列表失败:', error);
return {
list: [],
total: 0,
page: 1,
page_size: pageSize,
total_pages: 0,
};
}
}
/**
* 删除群公告
* DELETE /api/v1/groups/:id/announcements/:announcement_id
*/
async deleteAnnouncement(groupId: string, announcementId: number): Promise<void> {
await api.delete(
`/groups/${encodeURIComponent(groupId)}/announcements/${encodeURIComponent(String(announcementId))}`
);
}
// ==================== 游标分页方法 ====================
/**
* 获取群组列表(游标分页)
* GET /api/v1/groups/cursor
* @param params 游标分页请求参数
*/
async getGroupsCursor(
params: CursorPaginationRequest = {}
): Promise<CursorPaginationResponse<GroupResponse>> {
try {
const response = await api.get<CursorPaginationResponse<GroupResponse>>('/groups/cursor', params);
return normalizeGroupCursor(response.data);
} catch (error) {
console.error('获取群组列表失败:', error);
return {
list: [],
next_cursor: null,
prev_cursor: null,
has_more: false,
};
}
}
/**
* 获取群组成员列表(游标分页)
* GET /api/v1/groups/:id/members/cursor
* @param groupId 群组ID
* @param params 游标分页请求参数
*/
async getGroupMembersCursor(
groupId: string,
params: CursorPaginationRequest = {}
): Promise<CursorPaginationResponse<GroupMemberResponse>> {
try {
const response = await api.get<CursorPaginationResponse<GroupMemberResponse>>(
`/groups/${encodeURIComponent(groupId)}/members/cursor`,
params
);
return normalizeMemberCursor(response.data);
} catch (error) {
console.error('获取群组成员列表失败:', error);
return {
list: [],
next_cursor: null,
prev_cursor: null,
has_more: false,
};
}
}
/**
* 获取群公告列表(游标分页)
* GET /api/v1/groups/:id/announcements/cursor
* @param groupId 群组ID
* @param params 游标分页请求参数
*/
async getGroupAnnouncementsCursor(
groupId: string,
params: CursorPaginationRequest = {}
): Promise<CursorPaginationResponse<GroupAnnouncementResponse>> {
try {
const response = await api.get<CursorPaginationResponse<GroupAnnouncementResponse>>(
`/groups/${encodeURIComponent(groupId)}/announcements/cursor`,
params
);
return normalizeAnnouncementCursor(response.data);
} catch (error) {
console.error('获取群公告列表失败:', error);
return {
list: [],
next_cursor: null,
prev_cursor: null,
has_more: false,
};
}
}
}
// 导出群组服务实例
export const groupService = new GroupService();
// 导出类型以方便使用
export type {
GroupResponse,
GroupMemberResponse,
GroupAnnouncementResponse,
GroupListResponse,
GroupMemberListResponse,
GroupAnnouncementListResponse,
CreateGroupRequest,
InviteMembersRequest,
TransferOwnerRequest,
SetRoleRequest,
SetNicknameRequest,
SetMuteAllRequest,
SetJoinTypeRequest,
CreateAnnouncementRequest,
SetGroupAvatarRequest,
};