feat: add admin user detail insights
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user