From 77fc15d1fdaec1490518ec8f86a6555795b4dc48 Mon Sep 17 00:00:00 2001 From: liuwei Date: Thu, 16 Apr 2026 17:04:55 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=80=9A=E8=AE=AF=E5=BD=95?= =?UTF-8?q?=E7=BE=A4=E8=AF=A6=E6=83=85=E5=B1=95=E7=A4=BA=EF=BC=9A=E8=A1=A5?= =?UTF-8?q?=E9=BD=90=E7=BE=A4=E5=85=AC=E5=91=8A/=E7=BE=A4=E4=B8=BB/?= =?UTF-8?q?=E7=BE=A4=E7=AE=A1=E7=90=86=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 变更项: 1. 数据层新增群资料聚合查询,直接复用 is_owner/is_admin 字段返回群主与管理员列表。 2. 同步群信息时兼容提取群公告并落库,历史库启动时自动补齐 chat_room_announcement 字段。 3. 新增 /contacts/api/group_profile/ 接口,统一返回群公告、群主、管理员、成员数。 4. 通讯录群详情弹窗新增群主/群成员数/群管理/群公告展示,并在打开详情时自动加载。 5. 补充群成员精简查询头像字段,更新初始化 SQL 中 t_chatrooms 公告字段定义。 --- admin/dashboard/blueprints/contacts.py | 17 ++++ .../templates/contacts_management.html | 46 +++++++++++ db/contacts_db.py | 81 ++++++++++++++++++- db/scripts/init.sql | 1 + 4 files changed, 144 insertions(+), 1 deletion(-) diff --git a/admin/dashboard/blueprints/contacts.py b/admin/dashboard/blueprints/contacts.py index 9de8a32..1f1da7d 100644 --- a/admin/dashboard/blueprints/contacts.py +++ b/admin/dashboard/blueprints/contacts.py @@ -427,6 +427,23 @@ def api_group_members(roomid): return jsonify({"success": False, "error": str(e)}), 500 +@contacts_bp.route('/api/group_profile/', methods=['GET']) +@login_required +def api_group_profile(roomid): + """获取指定群的资料信息(群公告、群主、管理员、成员数)""" + try: + server = current_app.dashboard_server + # 直接复用联系人库中已有身份字段,按群聚合成页面可展示的资料结构。 + profile = server.contact_db.get_chatroom_profile(roomid) + return jsonify({ + "success": True, + "data": profile + }) + except Exception as e: + logger.error(f"获取群资料失败: {e}") + return jsonify({"success": False, "error": str(e)}), 500 + + @contacts_bp.route('/api/group_member_context//', methods=['GET']) @login_required def api_group_member_context(roomid, wxid): diff --git a/admin/dashboard/templates/contacts_management.html b/admin/dashboard/templates/contacts_management.html index 5e3234c..7d31469 100644 --- a/admin/dashboard/templates/contacts_management.html +++ b/admin/dashboard/templates/contacts_management.html @@ -187,6 +187,31 @@ {% raw %}{{ currentGroup.wxid }}{% endraw %} {% raw %}{{ currentGroup.name }}{% endraw %} + + {% raw %}{{ currentGroupProfile.owner_name || currentGroupProfile.owner_wxid || '未知' }}{% endraw %} + + ({% raw %}{{ currentGroupProfile.owner_wxid }}{% endraw %}) + + + + {% raw %}{{ currentGroupProfile.member_count || 0 }}{% endraw %} + + +
+ + {% raw %}{{ admin.display_name }}{% endraw %} + + 暂无管理员数据 +
+
+ + {% raw %}{{ currentGroupProfile.announcement || '暂无群公告' }}{% endraw %} + {% raw %}{{ currentGroup.robot_status === 'enabled' ? '已启用' : '未启用' }}{% endraw %} @@ -751,6 +776,8 @@ managedGroupMap: {}, groupPermissions: [], groupPermissionsLoading: false, + // 当前群基础资料:用于展示群主、群公告、管理员、成员数等信息。 + currentGroupProfile: { owner_wxid: '', owner_name: '', announcement: '', member_count: 0, admin_count: 0, admins: [] }, groupInsight: null, groupInsightLoading: false, groupMembersList: [], groupMembersCurrentPage: 1, groupMembersPageSize: 10, groupMemberSearchQuery: '', groupMembersLoading: false, @@ -866,6 +893,8 @@ viewGroupDetails(group) { this.currentGroup = { ...group }; this.groupDetailDialogVisible = true; + // 进入群详情时先加载群资料,保证群主/公告/管理员信息第一时间可见。 + this.loadGroupProfile(group.wxid); this.loadGroupMembers(group.wxid); this.loadGroupPermissions(group.wxid); this.loadGroupInsights(group.wxid); @@ -915,6 +944,22 @@ }) .finally(() => { this.groupInsightLoading = false; }); }, + loadGroupProfile(groupId) { + // 每次进入详情都拉最新资料,避免“更新通讯录后页面仍是旧数据”的错觉。 + this.currentGroupProfile = { owner_wxid: '', owner_name: '', announcement: '', member_count: 0, admin_count: 0, admins: [] }; + axios.get(`/contacts/api/group_profile/${groupId}`) + .then(response => { + if (response.data.success) { + this.currentGroupProfile = response.data.data || this.currentGroupProfile; + } else { + this.$message.error('加载群资料失败'); + } + }) + .catch(error => { + console.error('加载群资料失败:', error); + this.$message.error('加载群资料失败'); + }); + }, toggleGroupPermission(permission) { const newStatus = permission.statusBool ? 'enabled' : 'disabled'; axios.post(`/robot/api/group/${this.currentGroup.wxid}/permissions`, { @@ -1494,6 +1539,7 @@ .detail-card-sub { font-size: 12px; color: #94a3b8; font-weight: 500; } .feature-chip-list { display: flex; gap: 8px; flex-wrap: wrap; min-height: 60px; align-items: flex-start; } .empty-inline, .detail-inline-note { font-size: 12px; color: #64748b; } + .group-announcement { white-space: pre-wrap; line-height: 1.7; color: #334155; } .detail-inline-note { margin-top: 12px; line-height: 1.6; } .suggestion-list { display: flex; flex-direction: column; gap: 12px; } .suggestion-item { border-radius: 14px; } diff --git a/db/contacts_db.py b/db/contacts_db.py index 3e5a7d4..8bec6c7 100644 --- a/db/contacts_db.py +++ b/db/contacts_db.py @@ -101,6 +101,7 @@ class ContactsDBOperator(BaseDBOperator): remark_quan_pin VARCHAR(256) COMMENT '备注全拼', chat_room_notify TINYINT COMMENT '群通知', chat_room_owner VARCHAR(64) COMMENT '群主微信ID', + chat_room_announcement TEXT COMMENT '群公告内容', small_head_img_url TEXT COMMENT '群头像URL', member_list TEXT COMMENT '成员列表(JSON)', create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', @@ -108,6 +109,11 @@ class ContactsDBOperator(BaseDBOperator): UNIQUE KEY `idx_chatroom_id` (`chatroom_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='微信群信息表'; """) + # 兼容历史库:老版本表可能没有群公告字段,这里启动时自动补齐。 + self.execute_update(""" + ALTER TABLE t_chatrooms + ADD COLUMN IF NOT EXISTS chat_room_announcement TEXT COMMENT '群公告内容' + """) self.LOG.info("成功创建或确认微信群信息表存在") except Exception as e: self.LOG.error(f"创建微信联系人表或群成员表失败: {e}") @@ -516,6 +522,15 @@ class ContactsDBOperator(BaseDBOperator): return val.get("string", default) return val if val is not None else default + # 群公告在不同协议/版本里字段名不一致,这里做一次兼容提取,避免前端拿不到内容。 + announcement = ( + get_str('ChatRoomAnnouncement') + or get_str('Announcement') + or get_str('Annoucement') + or get_str('AnnouncementContent') + or get_str('chatRoomAnnouncement') + ) + data = { 'chatroom_id': get_str('UserName'), 'nick_name': get_str('NickName',"未知群名"), @@ -527,6 +542,7 @@ class ContactsDBOperator(BaseDBOperator): 'remark_quan_pin': get_str('RemarkQuanPin'), 'chat_room_notify': chatroom_data.get('ChatRoomNotify', 0), 'chat_room_owner': chatroom_data.get('ChatRoomOwner', ''), + 'chat_room_announcement': announcement, 'small_head_img_url': chatroom_data.get('SmallHeadImgUrl', ''), # 成员列表可选存储为JSON字符串 'member_list': json.dumps(chatroom_data.get('NewChatroomData', {}).get('ChatRoomMember', []), @@ -673,7 +689,11 @@ class ContactsDBOperator(BaseDBOperator): def get_chatroom_small_member_list(self, chatroom_id: str) -> List[dict]: """获取群成员列表""" try: - sql = "SELECT wxid,nick_name,display_name,status,latest_active_time FROM t_chatroom_member WHERE chatroom_id = %s" + sql = """ + SELECT wxid, nick_name, display_name, status, latest_active_time, small_head_img_url + FROM t_chatroom_member + WHERE chatroom_id = %s + """ results = self.execute_query(sql, (chatroom_id,)) for row in results: @@ -685,6 +705,65 @@ class ContactsDBOperator(BaseDBOperator): self.LOG.error(f"获取群{chatroom_id}成员列表失败: {e}") return [] + def get_chatroom_profile(self, chatroom_id: str) -> Dict[str, Any]: + """获取群资料(群公告、群主、管理员、成员数)用于通讯录详情展示。""" + try: + # 先取群基础信息 + 群主展示名 + 成员数。 + info_sql = """ + SELECT + c.chatroom_id, + c.nick_name, + c.chat_room_owner, + c.chat_room_announcement, + COALESCE(NULLIF(owner_member.display_name, ''), owner_member.nick_name, c.chat_room_owner, '') AS owner_name, + ( + SELECT COUNT(*) + FROM t_chatroom_member m + WHERE m.chatroom_id = c.chatroom_id AND m.status = 1 + ) AS member_count + FROM t_chatrooms c + LEFT JOIN t_chatroom_member owner_member + ON owner_member.chatroom_id = c.chatroom_id + AND owner_member.wxid = c.chat_room_owner + WHERE c.chatroom_id = %s + LIMIT 1 + """ + info = self.execute_query(info_sql, (chatroom_id,), fetch_one=True) or {} + + # 使用既有身份字段 is_admin 组装管理员列表,直接复用现有数据。 + admin_sql = """ + SELECT + wxid, + COALESCE(NULLIF(display_name, ''), nick_name, wxid) AS display_name + FROM t_chatroom_member + WHERE chatroom_id = %s AND is_admin = 1 AND status = 1 + ORDER BY display_name + """ + admins = self.execute_query(admin_sql, (chatroom_id,)) or [] + + return { + "chatroom_id": chatroom_id, + "nick_name": info.get("nick_name", ""), + "owner_wxid": info.get("chat_room_owner", "") or "", + "owner_name": info.get("owner_name", "") or "", + "announcement": info.get("chat_room_announcement", "") or "", + "member_count": int(info.get("member_count") or 0), + "admin_count": len(admins), + "admins": admins, + } + except Exception as e: + self.LOG.error(f"获取群{chatroom_id}资料失败: {e}") + return { + "chatroom_id": chatroom_id, + "nick_name": "", + "owner_wxid": "", + "owner_name": "", + "announcement": "", + "member_count": 0, + "admin_count": 0, + "admins": [], + } + # 获取群成员的昵称信息 def get_chatroom_member_list_name_all(self) -> List[dict]: """获取群成员列表""" diff --git a/db/scripts/init.sql b/db/scripts/init.sql index ef58306..5f3e123 100644 --- a/db/scripts/init.sql +++ b/db/scripts/init.sql @@ -121,6 +121,7 @@ create or replace table message_archive.t_chatrooms remark_quan_pin varchar(256) null comment '备注全拼', chat_room_notify tinyint null comment '群通知', chat_room_owner varchar(64) null comment '群主微信ID', + chat_room_announcement text null comment '群公告内容', small_head_img_url text null comment '群头像URL', member_list text null comment '成员列表(JSON)', create_time datetime default current_timestamp() not null comment '创建时间',