增强群总结弹窗查看能力

- 后端群总结接口补充完整 summary_text 字段\n- 群运营详情页新增最近群总结全文弹窗\n- 保留原有摘要卡片展示并补充类型与生成时间
This commit is contained in:
liuwei
2026-05-06 11:56:15 +08:00
parent 63d5a5e716
commit 14aa2ba067
2 changed files with 113 additions and 4 deletions

View File

@@ -97,8 +97,9 @@ def _load_recent_group_summaries(server, group_id: str, limit: int = 3) -> list:
设计说明:
1. 群画像适合给“结论”,但运营者往往还想看到最近几期总结到底说了什么;
2. 这里不把整段长文原样吐给前端,而是裁成可扫读的短摘要,避免弹窗过重
3. 若没有总结记录,返回空列表,由前端优雅降级。
2. 默认卡片仍使用短摘要,避免详情页被大段文本挤占
3. 同时补充完整总结正文,供前端弹窗按需展开查看;
4. 若没有总结记录,返回空列表,由前端优雅降级。
"""
summary_db = getattr(server, "message_summary_db", None)
if summary_db is None:
@@ -124,6 +125,9 @@ def _load_recent_group_summaries(server, group_id: str, limit: int = 3) -> list:
"summary_type": str(row.get("summary_type") or "").strip(),
"source_message_count": int(row.get("source_message_count") or 0),
"last_generated_at": str(row.get("last_generated_at") or "").strip(),
# 详情弹窗需要查看完整总结正文;这里保留原文,不做截断。
"summary_text": raw_summary,
# 卡片区域继续展示压缩后的摘要,避免详情首屏信息密度过高。
"summary_excerpt": normalized_summary[:180] + ("..." if len(normalized_summary) > 180 else ""),
})
return result

View File

@@ -349,10 +349,24 @@
<div class="ops-summary-timeline" v-if="(groupInsight.recent_summaries || []).length">
<div v-for="item in groupInsight.recent_summaries" :key="`${item.summary_type}-${item.period_key}`" class="ops-summary-item">
<div class="ops-summary-head">
<div class="ops-summary-head-main">
<span class="ops-summary-period">{% raw %}{{ item.period_key || '-' }}{% endraw %}</span>
<span class="ops-summary-type">{% raw %}{{ formatSummaryTypeLabel(item.summary_type) }}{% endraw %}</span>
</div>
<div class="ops-summary-head-side">
<span class="ops-summary-meta">{% raw %}{{ item.source_message_count || 0 }}{% endraw %} 条消息</span>
<el-button
size="mini"
type="primary"
plain
:disabled="!item.summary_text"
@click="openSummaryDetailDialog(item)">
查看全文
</el-button>
</div>
</div>
<div class="ops-summary-excerpt">{% raw %}{{ item.summary_excerpt || '暂无摘要内容' }}{% endraw %}</div>
<div class="ops-summary-time">{% raw %}{{ item.last_generated_at || '暂无生成时间' }}{% endraw %}</div>
</div>
</div>
<div v-else class="empty-inline">当前还没有可展示的群总结记录</div>
@@ -850,6 +864,21 @@
</div>
</el-dialog>
<el-dialog title="群总结详情" :visible.sync="summaryDetailDialogVisible" width="56%">
<div class="summary-detail-dialog" v-if="currentSummaryRecord">
<el-descriptions :column="2" border>
<el-descriptions-item label="总结周期">{% raw %}{{ currentSummaryRecord.period_key || '-' }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="总结类型">{% raw %}{{ formatSummaryTypeLabel(currentSummaryRecord.summary_type) }}{% endraw %}</el-descriptions-item>
<el-descriptions-item label="消息样本">{% raw %}{{ currentSummaryRecord.source_message_count || 0 }}{% endraw %} 条</el-descriptions-item>
<el-descriptions-item label="生成时间">{% raw %}{{ currentSummaryRecord.last_generated_at || '-' }}{% endraw %}</el-descriptions-item>
</el-descriptions>
<div class="summary-detail-body">
<div class="summary-detail-title">完整总结</div>
<div class="summary-detail-text">{% raw %}{{ currentSummaryRecord.summary_text || '暂无可展示的总结正文' }}{% endraw %}</div>
</div>
</div>
</el-dialog>
<el-dialog title="公众号详情" :visible.sync="officialDetailDialogVisible" width="50%">
<div class="detail-avatar-wrap">
<el-avatar size="large" :src="getHeadImage(currentOfficial.wxid)" @error="() => true" class="detail-avatar">
@@ -1053,6 +1082,9 @@
groupAnnouncementSyncing: false,
groupInsight: null,
groupInsightLoading: false,
// 群总结正文采用弹窗承载,避免详情区被长文本撑满。
summaryDetailDialogVisible: false,
currentSummaryRecord: null,
groupMembersList: [], groupMembersCurrentPage: 1, groupMembersPageSize: 10, groupMemberSearchQuery: '', groupMembersLoading: false,
memberContextDialogVisible: false, memberContextLoading: false, memberContext: null, currentContextMember: {},
memberContextEnabled: false,
@@ -1217,6 +1249,9 @@
// 切换群时恢复默认折叠态,保持“手动展开再看”的交互习惯。
this.groupPermissionsCollapsed = true;
this.groupWelcomeCollapsed = true;
// 群总结详情挂在当前群上下文里,因此切群时要清空旧弹窗与旧正文。
this.summaryDetailDialogVisible = false;
this.currentSummaryRecord = null;
this.groupDetailDialogVisible = true;
// 进入群详情时先加载群资料,保证群主/公告/管理员信息第一时间可见。
this.loadGroupProfile(group.wxid);
@@ -1225,6 +1260,27 @@
this.loadGroupInsights(group.wxid);
this.loadGroupWelcomeConfig(group.wxid);
},
formatSummaryTypeLabel(summaryType) {
const normalizedType = String(summaryType || '').trim().toLowerCase();
// 这里把后端枚举名映射成更像运营视角的文案,避免把内部字段直接暴露给页面。
if (!normalizedType) return '常规总结';
const labelMap = {
daily: '日总结',
weekly: '周总结',
monthly: '月总结',
manual: '手动总结'
};
return labelMap[normalizedType] || summaryType;
},
openSummaryDetailDialog(summaryItem) {
if (!summaryItem || !summaryItem.summary_text) {
this.$message.info('当前总结还没有完整内容可查看');
return;
}
// 只读弹窗复制一份当前记录,避免后续列表刷新时把弹窗内容一并抖掉。
this.currentSummaryRecord = { ...summaryItem };
this.summaryDetailDialogVisible = true;
},
viewUserDetails(user) { this.currentUser = user; this.userDetailDialogVisible = true; },
viewOfficialDetails(official) { this.currentOfficial = official; this.officialDetailDialogVisible = true; },
viewPublicDetails(publicFriend) { this.currentPublic = publicFriend; this.publicDetailDialogVisible = true; },
@@ -2106,11 +2162,33 @@
gap: 10px;
margin-bottom: 8px;
}
.ops-summary-head-main {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.ops-summary-head-side {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.ops-summary-period {
font-size: 13px;
font-weight: 700;
color: #0f172a;
}
.ops-summary-type {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 999px;
background: rgba(37,99,235,0.08);
color: #1d4ed8;
font-size: 11px;
line-height: 1.4;
}
.ops-summary-meta {
font-size: 11px;
color: #94a3b8;
@@ -2120,6 +2198,33 @@
color: #475569;
line-height: 1.7;
}
.ops-summary-time {
margin-top: 8px;
font-size: 11px;
color: #94a3b8;
}
.summary-detail-body {
margin-top: 16px;
}
.summary-detail-title {
margin-bottom: 10px;
font-size: 13px;
font-weight: 700;
color: #334155;
}
.summary-detail-text {
padding: 14px 16px;
border-radius: 14px;
border: 1px solid rgba(148,163,184,0.14);
background: rgba(248,250,252,0.92);
color: #334155;
line-height: 1.8;
font-size: 13px;
white-space: pre-wrap;
word-break: break-word;
max-height: 420px;
overflow-y: auto;
}
.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-wrap { display: flex; gap: 12px; align-items: flex-start; justify-content: space-between; }