增强群总结弹窗查看能力
- 后端群总结接口补充完整 summary_text 字段\n- 群运营详情页新增最近群总结全文弹窗\n- 保留原有摘要卡片展示并补充类型与生成时间
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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">
|
||||
<span class="ops-summary-period">{% raw %}{{ item.period_key || '-' }}{% endraw %}</span>
|
||||
<span class="ops-summary-meta">{% raw %}{{ item.source_message_count || 0 }}{% endraw %} 条消息</span>
|
||||
<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; }
|
||||
|
||||
Reference in New Issue
Block a user