优化群管理详情页展示与群成员统计口径
1. 群成员列表仅展示仍在群内的成员,排除已退群用户,避免僵尸成员与成员明细口径不一致。 2. 群成员列表按最后发言时间倒序排序,最近活跃成员优先展示,未发言成员排在后面。 3. 群详情启用功能区域的最后消息改为紧凑预览,图片、视频、链接、表情、XML、系统消息统一显示标记,不再直接展示原始内容。 4. 群功能权限区域默认折叠,需手动展开后再查看和操作,降低详情弹窗的信息噪音。 5. 进群欢迎配置区域默认折叠,需手动展开后再查看和编辑群级差异化欢迎配置。
This commit is contained in:
@@ -405,11 +405,19 @@
|
||||
<div class="section-title">
|
||||
<h3>群功能权限</h3>
|
||||
<div class="section-actions">
|
||||
<el-button size="mini" type="success" @click="updateAllGroupPermissions('enabled')">一键启用</el-button>
|
||||
<el-button size="mini" type="danger" @click="updateAllGroupPermissions('disabled')">一键关闭</el-button>
|
||||
<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>
|
||||
<span class="detail-card-sub">当前群:{% raw %}{{ currentGroup.name || currentGroup.wxid || '-' }}{% endraw %}</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>
|
||||
</div>
|
||||
<el-form label-width="110px" size="mini">
|
||||
<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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user