增强群运营分析2.0首版展示能力

变更项:
1. 在现有群详情接口中追加群画像摘要、成员分层、动作建议和最近群总结数据,保留原有健康度、趋势、排行与运营建议结构。
2. 为后台服务补充 message_summary 数据访问对象,复用现有群总结数据作为群运营分析输入。
3. 在通讯录管理的群详情面板中新增群画像摘要、成员分层和可执行动作建议卡片,保持旧页面内容不删除,仅做加法增强。
This commit is contained in:
liuwei
2026-05-06 11:39:01 +08:00
parent a691e150ce
commit b618bcc30d
3 changed files with 653 additions and 0 deletions

View File

@@ -262,6 +262,140 @@
</el-col>
</el-row>
<!--
群运营分析 2.0 先走“加法增强”:
1. 老的健康度、趋势、排行继续保留;
2. 新增群画像摘要,用更接近运营语言的方式解释群状态;
3. 这样即使后面要调整 2.0 结构,也不会影响旧洞察面板。
-->
<div class="detail-section">
<div class="section-title">
<h3>群画像摘要</h3>
</div>
<el-row :gutter="16" class="detail-panels">
<el-col :span="14">
<el-card class="detail-card detail-card--profile" shadow="never">
<div slot="header" class="detail-card-header">
<span>群定位与讨论风格</span>
<span class="detail-card-sub">2.0 增强摘要</span>
</div>
<div class="ops-profile-title">{% raw %}{{ groupInsight.ops_profile.group_identity || '综合交流型群' }}{% endraw %}</div>
<div class="feature-chip-list">
<el-tag
v-for="tag in (groupInsight.ops_profile.profile_tags || [])"
:key="tag"
size="small"
type="info"
effect="plain">
{% raw %}{{ tag }}{% endraw %}
</el-tag>
<span v-if="!(groupInsight.ops_profile.profile_tags || []).length" class="empty-inline">暂无群画像标签</span>
</div>
<div class="ops-profile-summary">{% raw %}{{ groupInsight.ops_profile.summary_text || '暂无群画像摘要' }}{% endraw %}</div>
<div class="ops-topic-block">
<div class="ops-subtitle">高频主题</div>
<div class="feature-chip-list">
<el-tag
v-for="topic in (groupInsight.ops_profile.focus_topics || [])"
:key="topic"
size="small"
type="success"
effect="plain">
{% raw %}{{ topic }}{% endraw %}
</el-tag>
<span v-if="!(groupInsight.ops_profile.focus_topics || []).length" class="empty-inline">暂无高频主题</span>
</div>
</div>
<div class="ops-topic-block" v-if="(groupInsight.ops_profile.unresolved_points || []).length">
<div class="ops-subtitle">待跟进问题</div>
<div class="ops-bullet-list">
<div v-for="item in groupInsight.ops_profile.unresolved_points" :key="item" class="ops-bullet-item">
{% raw %}{{ item }}{% endraw %}
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="10">
<el-card class="detail-card" shadow="never">
<div slot="header" class="detail-card-header">
<span>最近群总结</span>
<span class="detail-card-sub">复用现有 message_summary</span>
</div>
<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>
<div class="ops-summary-excerpt">{% raw %}{{ item.summary_excerpt || '暂无摘要内容' }}{% endraw %}</div>
</div>
</div>
<div v-else class="empty-inline">当前还没有可展示的群总结记录</div>
</el-card>
</el-col>
</el-row>
</div>
<!--
成员分层主要服务“下一步看谁”:
1. 核心成员强调谁在带讨论;
2. 高价值成员强调谁值得重点关注;
3. 待激活成员强调谁最适合做轻量召回。
-->
<div class="detail-section">
<div class="section-title">
<h3>成员分层</h3>
</div>
<el-row :gutter="16" class="detail-panels detail-panels--triple">
<el-col :span="8">
<el-card class="detail-card" shadow="never">
<div slot="header" class="detail-card-header">
<span>核心成员</span>
<span class="detail-card-sub">近30天发言核心</span>
</div>
<div class="ops-member-list" v-if="(groupInsight.ops_members.core_members || []).length">
<div v-for="item in groupInsight.ops_members.core_members" :key="item.wxid" class="ops-member-item">
<div class="ops-member-name">{% raw %}{{ item.display_name }}{% endraw %}</div>
<div class="ops-member-meta">消息数 {% raw %}{{ item.message_count }}{% endraw %} · {% raw %}{{ item.activity_level || '未分层' }}{% endraw %}</div>
</div>
</div>
<div v-else class="empty-inline">暂无核心成员数据</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="detail-card" shadow="never">
<div slot="header" class="detail-card-header">
<span>高价值成员</span>
<span class="detail-card-sub">最近一期身价快照</span>
</div>
<div class="ops-member-list" v-if="(groupInsight.ops_members.value_rank_members || []).length">
<div v-for="item in groupInsight.ops_members.value_rank_members" :key="item.wxid" class="ops-member-item">
<div class="ops-member-name">{% raw %}{{ item.display_name }}{% endraw %}</div>
<div class="ops-member-meta">#{% raw %}{{ item.rank_no }}{% endraw %} · {% raw %}{{ item.title || '成员' }}{% endraw %} · 分值 {% raw %}{{ item.score }}{% endraw %}</div>
</div>
</div>
<div v-else class="empty-inline">暂无高价值成员快照</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card class="detail-card" shadow="never">
<div slot="header" class="detail-card-header">
<span>待激活成员</span>
<span class="detail-card-sub">近7~30天边缘活跃</span>
</div>
<div class="ops-member-list" v-if="(groupInsight.ops_members.activation_candidates || []).length">
<div v-for="item in groupInsight.ops_members.activation_candidates" :key="item.wxid" class="ops-member-item">
<div class="ops-member-name">{% raw %}{{ item.display_name }}{% endraw %}</div>
<div class="ops-member-meta">距今 {% raw %}{{ item.inactivity_days }}{% endraw %} 天未发言</div>
</div>
</div>
<div v-else class="empty-inline">暂无待激活成员</div>
</el-card>
</el-col>
</el-row>
</div>
<div class="detail-section">
<div class="section-title">
<h3>运营建议</h3>
@@ -280,6 +414,27 @@
</div>
</div>
<!--
旧版运营建议更偏诊断提示;
新增的动作建议更偏“下一步怎么做”。
两层同时保留,便于后续比较哪种信息对你更有用。
-->
<div class="detail-section">
<div class="section-title">
<h3>可执行动作建议</h3>
</div>
<el-row :gutter="16" class="detail-panels detail-panels--double">
<el-col :span="12" v-for="(item, index) in (groupInsight.ops_actions || [])" :key="`${item.type}-${index}`">
<el-card class="detail-card action-card" shadow="never">
<div class="action-card-type">{% raw %}{{ item.type || 'action' }}{% endraw %}</div>
<div class="action-card-title">{% raw %}{{ item.title }}{% endraw %}</div>
<div class="action-card-summary">{% raw %}{{ item.summary }}{% endraw %}</div>
<div class="action-card-detail">{% raw %}{{ item.detail }}{% endraw %}</div>
</el-card>
</el-col>
</el-row>
</div>
<el-row :gutter="16" class="detail-panels">
<el-col :span="12">
<el-card class="detail-card" shadow="never">
@@ -1810,11 +1965,87 @@
display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px;
}
.detail-panels .el-col { margin-bottom: 16px; }
.detail-panels--triple .el-col,
.detail-panels--double .el-col { margin-bottom: 16px; }
.detail-card { border-radius: 18px; }
.detail-card--profile {
background: linear-gradient(180deg, rgba(14,165,233,0.08), rgba(255,255,255,0.98));
}
.detail-card-header {
display: flex; align-items: center; justify-content: space-between; font-weight: 600; color: #0f172a;
}
.detail-card-sub { font-size: 12px; color: #94a3b8; font-weight: 500; }
.ops-profile-title {
font-size: 22px;
font-weight: 700;
color: #0f172a;
margin-bottom: 12px;
}
.ops-profile-summary {
margin-top: 14px;
padding: 14px 16px;
border-radius: 14px;
background: rgba(255,255,255,0.78);
border: 1px solid rgba(148,163,184,0.12);
color: #334155;
line-height: 1.8;
font-size: 13px;
}
.ops-topic-block {
margin-top: 16px;
}
.ops-subtitle {
font-size: 13px;
font-weight: 700;
color: #475569;
margin-bottom: 10px;
}
.ops-bullet-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.ops-bullet-item {
padding: 10px 12px;
border-radius: 12px;
background: rgba(248,250,252,0.9);
color: #475569;
border: 1px solid rgba(148,163,184,0.12);
line-height: 1.6;
font-size: 12px;
}
.ops-summary-timeline {
display: flex;
flex-direction: column;
gap: 12px;
}
.ops-summary-item {
padding: 14px 14px 12px;
border-radius: 14px;
background: linear-gradient(180deg, rgba(248,250,252,0.9), rgba(255,255,255,0.98));
border: 1px solid rgba(148,163,184,0.12);
}
.ops-summary-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.ops-summary-period {
font-size: 13px;
font-weight: 700;
color: #0f172a;
}
.ops-summary-meta {
font-size: 11px;
color: #94a3b8;
}
.ops-summary-excerpt {
font-size: 12px;
color: #475569;
line-height: 1.7;
}
.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; }
@@ -1822,6 +2053,66 @@
.detail-inline-note { margin-top: 12px; line-height: 1.6; }
.suggestion-list { display: flex; flex-direction: column; gap: 12px; }
.suggestion-item { border-radius: 14px; }
.ops-member-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.ops-member-item {
padding: 12px 14px;
border-radius: 14px;
background: rgba(248,250,252,0.9);
border: 1px solid rgba(148,163,184,0.12);
}
.ops-member-name {
font-size: 13px;
font-weight: 700;
color: #0f172a;
margin-bottom: 6px;
}
.ops-member-meta {
font-size: 12px;
color: #64748b;
line-height: 1.6;
}
.action-card {
min-height: 180px;
background: linear-gradient(180deg, rgba(249,115,22,0.06), rgba(255,255,255,0.98));
}
.action-card-type {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 24px;
padding: 0 10px;
border-radius: 999px;
background: rgba(249,115,22,0.12);
color: #c2410c;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .04em;
margin-bottom: 10px;
}
.action-card-title {
font-size: 16px;
font-weight: 700;
color: #0f172a;
margin-bottom: 10px;
line-height: 1.5;
}
.action-card-summary {
font-size: 13px;
font-weight: 600;
color: #334155;
line-height: 1.7;
margin-bottom: 10px;
}
.action-card-detail {
font-size: 12px;
color: #64748b;
line-height: 1.8;
}
.peak-hour-list { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.peak-hour-item {
min-width: 132px; padding: 12px 14px; border-radius: 14px;
@@ -1946,6 +2237,8 @@
.page-hero-actions, .detail-tags { justify-content: flex-start; }
.hero-search, .group-search { width: 100%; }
.diagnosis-grid { grid-template-columns: 1fr; }
.detail-panels--triple .el-col,
.detail-panels--double .el-col { width: 100%; }
.chat-header-card { flex-direction: column; align-items: flex-start; }
.chat-header-actions { justify-content: flex-start; }
.message-content { max-width: 92%; }