diff --git a/electron/main/ipc/chat.ts b/electron/main/ipc/chat.ts index 27a5726..80a9435 100644 --- a/electron/main/ipc/chat.ts +++ b/electron/main/ipc/chat.ts @@ -603,6 +603,25 @@ export function registerChatHandlers(ctx: IpcContext): void { } }) + /** + * 获取成员列表(分页版本) + */ + ipcMain.handle( + 'chat:getMembersPaginated', + async ( + _, + sessionId: string, + params: { page: number; pageSize: number; search?: string; sortOrder?: 'asc' | 'desc' } + ) => { + try { + return await worker.getMembersPaginated(sessionId, params) + } catch (error) { + console.error('获取成员列表(分页)失败:', error) + return { members: [], total: 0, page: 1, pageSize: 20, totalPages: 0 } + } + } + ) + /** * 更新成员别名 */ diff --git a/electron/main/worker/dbWorker.ts b/electron/main/worker/dbWorker.ts index 73fed3e..b98ec90 100644 --- a/electron/main/worker/dbWorker.ts +++ b/electron/main/worker/dbWorker.ts @@ -45,6 +45,7 @@ import { getMessagesAfter, // 成员管理 getMembers, + getMembersPaginated, updateMemberAliases, deleteMember, // SQL 实验室 @@ -109,6 +110,7 @@ const syncHandlers: Record any> = { // 成员管理 getMembers: (p) => getMembers(p.sessionId), + getMembersPaginated: (p) => getMembersPaginated(p.sessionId, p.params), updateMemberAliases: (p) => updateMemberAliases(p.sessionId, p.memberId, p.aliases), deleteMember: (p) => deleteMember(p.sessionId, p.memberId), diff --git a/electron/main/worker/query/basic.ts b/electron/main/worker/query/basic.ts index f8608e0..619cf78 100644 --- a/electron/main/worker/query/basic.ts +++ b/electron/main/worker/query/basic.ts @@ -734,6 +734,122 @@ export function getMembers(sessionId: string): MemberWithStats[] { })) } +/** + * 分页参数类型 + */ +export interface MembersPaginationParams { + page: number + pageSize: number + search?: string + sortOrder?: 'asc' | 'desc' +} + +/** + * 分页结果类型 + */ +export interface MembersPaginatedResult { + members: MemberWithStats[] + total: number + page: number + pageSize: number + totalPages: number +} + +/** + * 获取成员列表(分页版本,支持搜索和排序) + */ +export function getMembersPaginated( + sessionId: string, + params: MembersPaginationParams +): MembersPaginatedResult { + const { page = 1, pageSize = 20, search = '', sortOrder = 'desc' } = params + + // 先确保数据库有 aliases 和 avatar 字段(兼容旧数据库) + ensureAliasesColumn(sessionId) + ensureAvatarColumn(sessionId) + + const db = openDatabase(sessionId) + if (!db) { + return { members: [], total: 0, page, pageSize, totalPages: 0 } + } + + // 构建搜索条件 + const searchCondition = search + ? `AND ( + m.group_nickname LIKE '%' || @search || '%' COLLATE NOCASE + OR m.account_name LIKE '%' || @search || '%' COLLATE NOCASE + OR m.platform_id LIKE '%' || @search || '%' COLLATE NOCASE + OR m.aliases LIKE '%' || @search || '%' COLLATE NOCASE + )` + : '' + + // 排序方向 + const orderDirection = sortOrder === 'asc' ? 'ASC' : 'DESC' + + // 计算总数 + const countResult = db + .prepare( + ` + SELECT COUNT(*) as total FROM ( + SELECT m.id + FROM member m + LEFT JOIN message msg ON m.id = msg.sender_id + WHERE COALESCE(m.group_nickname, m.account_name, m.platform_id) != '系统消息' + ${searchCondition} + GROUP BY m.id + ) + ` + ) + .get({ search }) as { total: number } + + const total = countResult?.total || 0 + const totalPages = Math.ceil(total / pageSize) + const offset = (page - 1) * pageSize + + // 查询分页数据 + const rows = db + .prepare( + ` + SELECT + m.id, + m.platform_id as platformId, + m.account_name as accountName, + m.group_nickname as groupNickname, + m.aliases, + m.avatar, + COUNT(msg.id) as messageCount + FROM member m + LEFT JOIN message msg ON m.id = msg.sender_id + WHERE COALESCE(m.group_nickname, m.account_name, m.platform_id) != '系统消息' + ${searchCondition} + GROUP BY m.id + ORDER BY messageCount ${orderDirection} + LIMIT @pageSize OFFSET @offset + ` + ) + .all({ search, pageSize, offset }) as Array<{ + id: number + platformId: string + accountName: string | null + groupNickname: string | null + aliases: string | null + avatar: string | null + messageCount: number + }> + + const members = rows.map((row) => ({ + id: row.id, + platformId: row.platformId, + accountName: row.accountName, + groupNickname: row.groupNickname, + aliases: row.aliases ? JSON.parse(row.aliases) : [], + messageCount: row.messageCount, + avatar: row.avatar, + })) + + return { members, total, page, pageSize, totalPages } +} + /** * 更新成员别名 */ diff --git a/electron/main/worker/query/index.ts b/electron/main/worker/query/index.ts index 51f9a31..a934035 100644 --- a/electron/main/worker/query/index.ts +++ b/electron/main/worker/query/index.ts @@ -20,10 +20,14 @@ export { getSession, // 成员管理 getMembers, + getMembersPaginated, updateMemberAliases, deleteMember, } from './basic' +// 成员分页类型 +export type { MembersPaginationParams, MembersPaginatedResult } from './basic' + // 高级分析 export { getRepeatAnalysis, diff --git a/electron/main/worker/workerManager.ts b/electron/main/worker/workerManager.ts index 8379bee..26d78e6 100644 --- a/electron/main/worker/workerManager.ts +++ b/electron/main/worker/workerManager.ts @@ -351,6 +351,27 @@ export async function getMembers(sessionId: string): Promise return sendToWorker('getMembers', { sessionId }) } +/** + * 获取成员列表(分页版本) + */ +export async function getMembersPaginated( + sessionId: string, + params: { + page: number + pageSize: number + search?: string + sortOrder?: 'asc' | 'desc' + } +): Promise<{ + members: MemberWithStats[] + total: number + page: number + pageSize: number + totalPages: number +}> { + return sendToWorker('getMembersPaginated', { sessionId, params }) +} + /** * 更新成员别名 */ diff --git a/electron/preload/index.d.ts b/electron/preload/index.d.ts index 43fa284..1bcfcd5 100644 --- a/electron/preload/index.d.ts +++ b/electron/preload/index.d.ts @@ -141,6 +141,16 @@ interface ChatApi { getCheckInAnalysis: (sessionId: string, filter?: TimeFilter) => Promise // 成员管理 getMembers: (sessionId: string) => Promise + getMembersPaginated: ( + sessionId: string, + params: { page: number; pageSize: number; search?: string; sortOrder?: 'asc' | 'desc' } + ) => Promise<{ + members: MemberWithStats[] + total: number + page: number + pageSize: number + totalPages: number + }> updateMemberAliases: (sessionId: string, memberId: number, aliases: string[]) => Promise deleteMember: (sessionId: string, memberId: number) => Promise // SQL 实验室 diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 733d4de..839e82e 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -362,6 +362,22 @@ const chatApi = { return ipcRenderer.invoke('chat:getMembers', sessionId) }, + /** + * 获取成员列表(分页版本) + */ + getMembersPaginated: ( + sessionId: string, + params: { page: number; pageSize: number; search?: string; sortOrder?: 'asc' | 'desc' } + ): Promise<{ + members: MemberWithStats[] + total: number + page: number + pageSize: number + totalPages: number + }> => { + return ipcRenderer.invoke('chat:getMembersPaginated', sessionId, params) + }, + /** * 更新成员别名 */ diff --git a/src/pages/group-chat/components/member/MemberList.vue b/src/pages/group-chat/components/member/MemberList.vue index 89e927e..96bae08 100644 --- a/src/pages/group-chat/components/member/MemberList.vue +++ b/src/pages/group-chat/components/member/MemberList.vue @@ -1,5 +1,5 @@ @@ -186,14 +195,14 @@ onMounted(() => {

{{ t('title') }}

- {{ t('description', { count: members.length }) }} + {{ t('description', { count: total }) }}

- +
@@ -217,7 +226,7 @@ onMounted(() => {
-
+

{{ searchQuery ? t('noMatch') : t('empty') }} @@ -250,7 +259,7 @@ onMounted(() => { @@ -325,14 +334,14 @@ onMounted(() => { class="flex items-center justify-between border-t border-gray-200 px-6 py-4 dark:border-gray-700" >

- {{ t('pagination', { start: (currentPage - 1) * pageSize + 1, end: Math.min(currentPage * pageSize, filteredAndSortedMembers.length), total: filteredAndSortedMembers.length }) }} + {{ t('pagination', { start: (currentPage - 1) * pageSize + 1, end: Math.min(currentPage * pageSize, total), total: total }) }}

@@ -342,7 +351,7 @@ onMounted(() => { icon="i-heroicons-chevron-right" variant="outline" size="sm" - :disabled="currentPage >= totalPages" + :disabled="currentPage >= totalPages || isLoading" @click="currentPage++" />