feat: add admin user detail insights

This commit is contained in:
2026-07-31 16:30:39 +08:00
parent 763c78bd7e
commit 88ed92d78c
6 changed files with 361 additions and 5 deletions

View File

@@ -12,6 +12,7 @@ import { api, clearToken, getToken, saveToken } from "./services/api";
import type {
AdminProfile,
AdminUser,
AdminUserDetail,
AiLogRecord,
ChatDetail,
ChatRecord,
@@ -41,6 +42,9 @@ const dashboardFilters = reactive({
end: "",
});
const users = ref<AdminUser[]>([]);
const selectedUserDetail = ref<AdminUserDetail | null>(null);
const userDetailOpen = ref(false);
const userDetailLoading = ref(false);
const userKeyword = ref("");
const entitlementPlans = ref<EntitlementPlan[]>([]);
const editingEntitlementPlanId = ref<number | null>(null);
@@ -401,6 +405,17 @@ async function assignUserEntitlement(row: AdminUser, planId: number) {
ElMessage.success("用户权益已更新");
}
async function openUserDetail(row: AdminUser) {
userDetailOpen.value = true;
selectedUserDetail.value = null;
userDetailLoading.value = true;
try {
selectedUserDetail.value = await api.userDetail(row.id);
} finally {
userDetailLoading.value = false;
}
}
async function loadRecordTab(tab = recordTab.value) {
loading.value = true;
try {
@@ -1138,10 +1153,20 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
</template>
</el-table-column>
<el-table-column prop="lastLoginAt" label="最近登录" width="180" />
<el-table-column label="操作" width="160" fixed="right">
<el-table-column label="操作" width="210" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="saveUser(row)">保存</el-button>
<el-button size="small" type="danger" @click="deleteUser(row)">删除</el-button>
<TableRowActions>
<el-button size="small" @click="openUserDetail(row)">详情</el-button>
<el-button size="small" type="primary" @click="saveUser(row)">保存</el-button>
<el-dropdown trigger="click">
<el-button size="small">更多</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="deleteUser(row)">删除用户</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</TableRowActions>
</template>
</el-table-column>
</el-table>
@@ -1610,6 +1635,125 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
</section>
</section>
<el-drawer v-model="userDetailOpen" size="860px" title="用户详情">
<div v-loading="userDetailLoading" class="user-detail-body">
<template v-if="selectedUserDetail">
<section class="user-detail-head">
<div>
<h3>{{ selectedUserDetail.user.name || selectedUserDetail.user.nickname || selectedUserDetail.user.phone }}</h3>
<p>{{ selectedUserDetail.user.phone }} · {{ selectedUserDetail.user.status === 1 ? '已启用' : '已停用' }}</p>
</div>
<div class="user-detail-tags">
<el-tag v-if="selectedUserDetail.metrics.isHighFrequency" type="warning" effect="plain">高频使用</el-tag>
<el-tag v-if="selectedUserDetail.metrics.isInactive" type="info" effect="plain">近30天低活跃</el-tag>
<el-tag type="success" effect="plain">{{ selectedUserDetail.user.entitlement?.name || '默认基础版' }}</el-tag>
</div>
</section>
<section class="chat-summary user-metric-grid">
<div><span>本月主题</span><strong>{{ selectedUserDetail.metrics.monthTopics }}/{{ selectedUserDetail.user.entitlement?.monthlyTopicLimit ?? '不限' }}</strong></div>
<div><span>近30天活跃</span><strong>{{ selectedUserDetail.metrics.recentActiveDays }} </strong></div>
<div><span>总会话</span><strong>{{ selectedUserDetail.metrics.totalSessions }}</strong></div>
<div><span>总消息</span><strong>{{ selectedUserDetail.metrics.totalMessages }}</strong></div>
<div><span>主题总数</span><strong>{{ selectedUserDetail.metrics.totalTopics }}</strong></div>
<div><span>AI 请求</span><strong>{{ selectedUserDetail.metrics.aiRequestCount }}</strong></div>
<div><span> Token</span><strong>{{ selectedUserDetail.metrics.totalToken }}</strong></div>
<div><span>估算成本</span><strong>{{ formatMoney(selectedUserDetail.metrics.estimatedCost, selectedUserDetail.metrics.costCurrency) }}</strong></div>
</section>
<section class="audit-detail-grid">
<div><span>权益有效期</span><strong>{{ selectedUserDetail.user.entitlement?.expiredAt || selectedUserDetail.user.expiredAt || '长期有效' }}</strong></div>
<div><span>最近登录</span><strong>{{ selectedUserDetail.user.lastLoginAt || '-' }}</strong></div>
<div><span>最近发言</span><strong>{{ selectedUserDetail.metrics.lastMessageAt || '-' }}</strong></div>
<div><span>生成记录</span><strong>求助卡 {{ selectedUserDetail.metrics.helpCardCount }} / 分享稿 {{ selectedUserDetail.metrics.shareDraftCount }}</strong></div>
</section>
<h3 class="detail-title">成长档案摘要</h3>
<section v-if="selectedUserDetail.growthProfile" class="topic-summary-card">
<pre>{{ selectedUserDetail.growthProfile.profileText || '暂无成长档案摘要' }}</pre>
<div class="topic-summary-fields">
<p v-if="selectedUserDetail.growthProfile.recurringTopics"><strong>反复议题</strong>{{ selectedUserDetail.growthProfile.recurringTopics }}</p>
<p v-if="selectedUserDetail.growthProfile.commonEmotions"><strong>常见情绪</strong>{{ selectedUserDetail.growthProfile.commonEmotions }}</p>
<p v-if="selectedUserDetail.growthProfile.bodyPatterns"><strong>身体模式</strong>{{ selectedUserDetail.growthProfile.bodyPatterns }}</p>
<p v-if="selectedUserDetail.growthProfile.homeworkDone"><strong>做过功课</strong>{{ selectedUserDetail.growthProfile.homeworkDone }}</p>
<p v-if="selectedUserDetail.growthProfile.effectiveHomework"><strong>有效功课</strong>{{ selectedUserDetail.growthProfile.effectiveHomework }}</p>
<p v-if="selectedUserDetail.growthProfile.recentProgress"><strong>近期变化</strong>{{ selectedUserDetail.growthProfile.recentProgress }}</p>
</div>
</section>
<el-empty v-else description="该用户暂无成长档案沉淀" :image-size="64" />
<h3 class="detail-title">最近主题</h3>
<el-collapse v-if="selectedUserDetail.recentTopics.length" class="topic-summary-collapse">
<el-collapse-item
v-for="topic in selectedUserDetail.recentTopics"
:key="topic.id"
:title="`#${topic.id} ${topic.title} / ${topic.status}`"
>
<section class="topic-summary-card">
<div class="topic-summary-meta">
<span>核心问题{{ topic.coreQuestion }}</span>
<span>消息数{{ topic.messageCount }}</span>
<span>Token{{ topic.tokenInput }}/{{ topic.tokenOutput }}</span>
<span>额度{{ topic.quotaDeducted ? '已计入' : '不计入' }}</span>
<span>开始{{ topic.startedAt }}</span>
<span>更新{{ topic.updatedAt }}</span>
</div>
<template v-if="topic.summary">
<pre>{{ topic.summary.summary }}</pre>
<div class="topic-summary-fields">
<p v-if="topic.summary.emotions"><strong>情绪</strong>{{ topic.summary.emotions }}</p>
<p v-if="topic.summary.bodyFeelings"><strong>身体感受</strong>{{ topic.summary.bodyFeelings }}</p>
<p v-if="topic.summary.recommendedHomework"><strong>推荐功课</strong>{{ topic.summary.recommendedHomework }}</p>
<p v-if="topic.summary.nextObservation"><strong>下一步观察</strong>{{ topic.summary.nextObservation }}</p>
</div>
</template>
<el-empty v-else description="该主题尚未沉淀摘要" :image-size="60" />
</section>
</el-collapse-item>
</el-collapse>
<el-empty v-else description="该用户暂无主题记录" :image-size="64" />
<h3 class="detail-title">最近老师求助卡{{ selectedUserDetail.recentHelpCards.length }}</h3>
<el-collapse v-if="selectedUserDetail.recentHelpCards.length" class="topic-summary-collapse">
<el-collapse-item
v-for="card in selectedUserDetail.recentHelpCards"
:key="card.id"
:title="`#${card.id} / 主题 #${card.topicSessionId} / ${card.copied ? '用户已复制' : '未复制'}`"
>
<section class="topic-summary-card">
<div class="topic-summary-meta">
<span>生成{{ card.createdAt }}</span>
<span>复制{{ card.copiedAt || '-' }}</span>
<span>来源{{ card.source }}</span>
</div>
<pre>{{ card.content }}</pre>
</section>
</el-collapse-item>
</el-collapse>
<el-empty v-else description="该用户暂无求助卡生成记录" :image-size="64" />
<h3 class="detail-title">最近班级分享稿{{ selectedUserDetail.recentShareDrafts.length }}</h3>
<el-collapse v-if="selectedUserDetail.recentShareDrafts.length" class="topic-summary-collapse">
<el-collapse-item
v-for="draft in selectedUserDetail.recentShareDrafts"
:key="draft.id"
:title="`#${draft.id} / 主题 #${draft.topicSessionId} / ${draft.copied ? '用户已复制' : '未复制'}`"
>
<section class="topic-summary-card">
<div class="topic-summary-meta">
<span>生成{{ draft.createdAt }}</span>
<span>复制{{ draft.copiedAt || '-' }}</span>
<span>来源{{ draft.source }}</span>
</div>
<pre>{{ draft.content }}</pre>
</section>
</el-collapse-item>
</el-collapse>
<el-empty v-else description="该用户暂无分享稿生成记录" :image-size="64" />
</template>
</div>
</el-drawer>
<el-drawer v-model="retrievalDetailOpen" size="820px" title="检索日志详情">
<template v-if="selectedRetrievalLog">
<dl class="knowledge-meta">