优化群管理详情页展示与群成员统计口径

1. 群成员列表仅展示仍在群内的成员,排除已退群用户,避免僵尸成员与成员明细口径不一致。

2. 群成员列表按最后发言时间倒序排序,最近活跃成员优先展示,未发言成员排在后面。

3. 群详情启用功能区域的最后消息改为紧凑预览,图片、视频、链接、表情、XML、系统消息统一显示标记,不再直接展示原始内容。

4. 群功能权限区域默认折叠,需手动展开后再查看和操作,降低详情弹窗的信息噪音。

5. 进群欢迎配置区域默认折叠,需手动展开后再查看和编辑群级差异化欢迎配置。
This commit is contained in:
liuwei
2026-04-30 14:12:01 +08:00
parent 889afecde6
commit 23b5d5bef0
2 changed files with 97 additions and 10 deletions

View File

@@ -405,11 +405,19 @@
<div class="section-title">
<h3>群功能权限</h3>
<div class="section-actions">
<el-button size="mini" @click="groupPermissionsCollapsed = !groupPermissionsCollapsed">
{% raw %}{{ groupPermissionsCollapsed ? '展开查看' : '收起' }}{% endraw %}
</el-button>
<template v-if="!groupPermissionsCollapsed">
<el-button size="mini" type="success" @click="updateAllGroupPermissions('enabled')">一键启用</el-button>
<el-button size="mini" type="danger" @click="updateAllGroupPermissions('disabled')">一键关闭</el-button>
</template>
</div>
</div>
<el-table :data="groupPermissions" style="width: 100%" v-loading="groupPermissionsLoading" size="mini">
<div v-if="groupPermissionsCollapsed" class="collapsed-placeholder">
群功能权限默认折叠,点击“展开查看”后再进行配置。
</div>
<el-table v-else :data="groupPermissions" style="width: 100%" v-loading="groupPermissionsLoading" size="mini">
<el-table-column prop="feature_id" label="功能ID" width="90"></el-table-column>
<el-table-column prop="feature_description" label="功能描述"></el-table-column>
<el-table-column label="当前状态" width="120" align="center">
@@ -434,9 +442,17 @@
<el-card class="detail-card welcome-config-card" shadow="never" v-loading="groupWelcomeConfigLoading">
<div slot="header" class="detail-card-header">
<span>进群欢迎配置(群级差异化)</span>
<div class="detail-card-header-actions">
<span class="detail-card-sub">当前群:{% raw %}{{ currentGroup.name || currentGroup.wxid || '-' }}{% endraw %}</span>
<el-button size="mini" @click="groupWelcomeCollapsed = !groupWelcomeCollapsed">
{% raw %}{{ groupWelcomeCollapsed ? '展开查看' : '收起' }}{% endraw %}
</el-button>
</div>
<el-form label-width="110px" size="mini">
</div>
<div v-if="groupWelcomeCollapsed" class="collapsed-placeholder">
进群欢迎配置默认折叠,点击“展开查看”后再编辑当前群的欢迎文案。
</div>
<el-form v-else label-width="110px" size="mini">
<el-form-item label="文本欢迎开关">
<el-switch v-model="groupWelcomeConfig.welcome_text_enabled"></el-switch>
</el-form-item>
@@ -835,7 +851,11 @@
managedGroupMap: {},
groupPermissions: [],
groupPermissionsLoading: false,
// 群权限区域默认折叠,避免详情弹窗一打开就被大量开关占满视线。
groupPermissionsCollapsed: true,
groupWelcomeConfigLoading: false,
// 欢迎配置表单默认折叠,需要用户主动展开后再查看或编辑。
groupWelcomeCollapsed: true,
groupWelcomeConfig: {
welcome_text_enabled: true,
welcome_text_template: '👏欢迎 {nickname} 加入群聊!🎉',
@@ -990,6 +1010,9 @@
handleCurrentChange(page) { this.currentPage = page; },
viewGroupDetails(group) {
this.currentGroup = { ...group };
// 切换群时恢复默认折叠态,保持“手动展开再看”的交互习惯。
this.groupPermissionsCollapsed = true;
this.groupWelcomeCollapsed = true;
this.groupDetailDialogVisible = true;
// 进入群详情时先加载群资料,保证群主/公告/管理员信息第一时间可见。
this.loadGroupProfile(group.wxid);
@@ -1249,7 +1272,19 @@
if (response.data.success) {
const members = response.data.data.members;
this.memberContextEnabled = !!response.data.data.member_context_enabled;
this.groupMembersList = members.map(item => ({ wxid: item.wxid, name: item.nick_name, display_name: item.display_name, status: item.status, latest_active_time: item.latest_active_time, small_head_img_url: item.small_head_img_url, activity_level: item.activity_level, response_style_hint: item.response_style_hint, summary_text: item.summary_text, last_profiled_at: item.last_profiled_at }));
// 后端已按最后发言时间倒序返回,这里保持字段展开写法,方便后续继续扩展成员画像字段。
this.groupMembersList = members.map(item => ({
wxid: item.wxid,
name: item.nick_name,
display_name: item.display_name,
status: item.status,
latest_active_time: item.latest_active_time,
small_head_img_url: item.small_head_img_url,
activity_level: item.activity_level,
response_style_hint: item.response_style_hint,
summary_text: item.summary_text,
last_profiled_at: item.last_profiled_at
}));
} else { this.$message.error('获取群成员失败'); }
}).catch(error => { console.error('加载群成员数据失败:', error); this.$message.error('加载群成员数据失败'); }).finally(() => { this.groupMembersLoading = false; });
},
@@ -1416,9 +1451,37 @@
if (!lastMessage) return '暂无消息';
const sender = lastMessage.sender || '未知成员';
const time = lastMessage.timestamp || '';
const content = lastMessage.content || '[非文本消息]';
const content = this.getCompactLastMessageContent(lastMessage);
return `${sender} · ${time} · ${content}`;
},
getCompactLastMessageContent(lastMessage) {
// 群详情顶部只需要紧凑预览,不需要把 XML、链接卡片原文或媒体原始内容完整展开。
const messageType = String((lastMessage && lastMessage.message_type) || '').trim();
const rawContent = String((lastMessage && lastMessage.content) || '').trim();
const compactMap = {
'3': '[图片]',
'43': '[视频]',
'62': '[视频]',
'47': '[表情]',
'1048625': '[表情]',
'1090519089': '[表情]',
'49': '[链接]'
};
if (compactMap[messageType]) {
return compactMap[messageType];
}
if (rawContent.startsWith('<sysmsg')) {
return '[系统消息]';
}
if (rawContent.startsWith('<') || /<[^>]+>/.test(rawContent)) {
return '[XML消息]';
}
if (!rawContent) {
return '[非文本消息]';
}
const singleLineContent = rawContent.replace(/\s+/g, ' ').trim();
return singleLineContent.length > 60 ? `${singleLineContent.slice(0, 60)}...` : singleLineContent;
},
openChatDialog(user) {
this.currentChatUser = user;
this.chatType = user && user.wxid && user.wxid.endsWith('@chatroom') ? 'group' : 'personal';
@@ -1715,6 +1778,11 @@
.group-permission-section { margin-top: 20px; }
.welcome-config-card { margin-top: 14px; }
.group-members-section { margin-top: 20px; }
.detail-card-header-actions { display: flex; align-items: center; gap: 10px; }
.collapsed-placeholder {
padding: 14px 16px; border-radius: 12px; background: #f8fafc; color: #64748b;
border: 1px dashed rgba(148, 163, 184, 0.45); font-size: 13px;
}
.section-title {
margin: 20px 0 15px 0; border-bottom: 1px solid rgba(148,163,184,0.12); padding-bottom: 10px;
display: flex; justify-content: space-between; align-items: center;

View File

@@ -732,12 +732,23 @@ class ContactsDBOperator(BaseDBOperator):
return []
# 新增获取群成员列表接口
def get_chatroom_small_member_list(self, chatroom_id: str) -> List[dict]:
"""获取群成员列表"""
"""获取群成员列表
说明:
1. 群详情页的成员列表只展示当前仍在群内的成员,已退群成员不再返回;
2. 成员需要按最后发言时间倒序展示,方便后台优先看到最近活跃的人;
3. 从未发言的成员排在后面,避免把“无活跃记录”的成员顶到前面。
"""
try:
sql = """
SELECT wxid, nick_name, display_name, status, latest_active_time, small_head_img_url
FROM t_chatroom_member
WHERE chatroom_id = %s
AND status = 1
ORDER BY
CASE WHEN latest_active_time IS NULL THEN 1 ELSE 0 END ASC,
latest_active_time DESC,
COALESCE(NULLIF(display_name, ''), nick_name, wxid) ASC
"""
results = self.execute_query(sql, (chatroom_id,))
@@ -916,6 +927,7 @@ class ContactsDBOperator(BaseDBOperator):
, CASE WHEN latest_active_time IS NULL THEN 1 ELSE 0 END AS never_spoken
FROM t_chatroom_member
WHERE chatroom_id = %s
AND status = 1
AND (latest_active_time IS NULL OR latest_active_time <= DATE_SUB(NOW(), INTERVAL %s DAY))
ORDER BY inactivity_days DESC
LIMIT %s
@@ -931,12 +943,18 @@ class ContactsDBOperator(BaseDBOperator):
return []
def get_group_member_summary(self, chatroom_id: str, inactive_days: int = 30) -> Dict[str, Any]:
"""获取群成员概览摘要"""
"""获取群成员概览摘要
说明:
1. 群运营看板里的成员统计口径应当只基于“当前仍在群内”的成员;
2. 已退群成员如果继续参与统计,会导致僵尸成员数、活跃覆盖率等指标失真;
3. 因此这里统一过滤 status = 1确保详情页数字与成员列表保持一致。
"""
try:
sql = """
SELECT
COUNT(*) AS total_members,
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS in_group_members,
COUNT(*) AS in_group_members,
SUM(CASE WHEN is_owner = 1 THEN 1 ELSE 0 END) AS owner_count,
SUM(CASE WHEN is_admin = 1 THEN 1 ELSE 0 END) AS admin_count,
SUM(CASE WHEN latest_active_time IS NOT NULL THEN 1 ELSE 0 END) AS spoken_members,
@@ -964,6 +982,7 @@ class ContactsDBOperator(BaseDBOperator):
) AS inactive_members
FROM t_chatroom_member
WHERE chatroom_id = %s
AND status = 1
"""
result = self.execute_query(sql, (inactive_days, chatroom_id), fetch_one=True) or {}
return {