feat: add entitlement plans and topic sessions
This commit is contained in:
@@ -16,6 +16,7 @@ import type {
|
||||
ChatDetail,
|
||||
ChatRecord,
|
||||
DashboardStats,
|
||||
EntitlementPlan,
|
||||
KnowledgeItem,
|
||||
RetrievalLogItem,
|
||||
AttentionItem,
|
||||
@@ -41,6 +42,8 @@ const dashboardFilters = reactive({
|
||||
});
|
||||
const users = ref<AdminUser[]>([]);
|
||||
const userKeyword = ref("");
|
||||
const entitlementPlans = ref<EntitlementPlan[]>([]);
|
||||
const editingEntitlementPlanId = ref<number | null>(null);
|
||||
const models = ref<ModelItem[]>([]);
|
||||
const configs = ref<SystemConfigItem[]>([]);
|
||||
const chats = ref<ChatRecord[]>([]);
|
||||
@@ -88,6 +91,21 @@ const studentImportFile = ref<File | null>(null);
|
||||
const studentImportInput = ref<HTMLInputElement | null>(null);
|
||||
const studentImportResult = ref<UserImportResult | null>(null);
|
||||
|
||||
const entitlementPlanForm = reactive({
|
||||
name: "",
|
||||
planType: "basic" as "basic" | "deep" | "addon" | "teacher",
|
||||
description: "",
|
||||
validityDays: null as number | null,
|
||||
monthlyTopicLimit: 30 as number | null,
|
||||
enableGrowthProfile: 0,
|
||||
enablePeriodicReports: 0,
|
||||
allowHelpCard: 1,
|
||||
allowShareDraft: 1,
|
||||
deductQuota: 1,
|
||||
status: 1,
|
||||
sortOrder: 10,
|
||||
});
|
||||
|
||||
const knowledgeForm = reactive({
|
||||
name: "",
|
||||
feishuSpaceId: "",
|
||||
@@ -278,7 +296,8 @@ async function loadCurrentMenu() {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (activeMenu.value === "dashboard") await loadDashboard();
|
||||
if (activeMenu.value === "users") await loadUsers(pagers.users.page, pagers.users.pageSize);
|
||||
if (activeMenu.value === "users") { await loadEntitlementPlans(); await loadUsers(pagers.users.page, pagers.users.pageSize); }
|
||||
if (activeMenu.value === "entitlements") await loadEntitlementPlans();
|
||||
if (activeMenu.value === "models") models.value = await api.models();
|
||||
if (activeMenu.value === "configs") {
|
||||
configs.value = await api.configs();
|
||||
@@ -298,6 +317,78 @@ async function loadUsers(page = 1, pageSize = pagers.users.pageSize) {
|
||||
Object.assign(pagers.users, { page: result.page, pageSize: result.pageSize, total: result.total });
|
||||
}
|
||||
|
||||
async function loadEntitlementPlans() {
|
||||
entitlementPlans.value = await api.entitlementPlans(true);
|
||||
}
|
||||
|
||||
function planTypeLabel(type: string) {
|
||||
return {
|
||||
basic: "基础版",
|
||||
deep: "深度陪伴版",
|
||||
addon: "高频加购包",
|
||||
teacher: "老师工作版",
|
||||
}[type] || type;
|
||||
}
|
||||
|
||||
async function saveEntitlementPlan() {
|
||||
if (!entitlementPlanForm.name.trim()) {
|
||||
ElMessage.warning("请填写权益版本名称");
|
||||
return;
|
||||
}
|
||||
const payload = { ...entitlementPlanForm };
|
||||
if (editingEntitlementPlanId.value) {
|
||||
await api.updateEntitlementPlan(editingEntitlementPlanId.value, payload);
|
||||
ElMessage.success("权益版本已更新");
|
||||
} else {
|
||||
await api.createEntitlementPlan(payload);
|
||||
ElMessage.success("权益版本已新增");
|
||||
}
|
||||
resetEntitlementPlanForm();
|
||||
await loadEntitlementPlans();
|
||||
}
|
||||
|
||||
function editEntitlementPlan(row: EntitlementPlan) {
|
||||
editingEntitlementPlanId.value = row.id;
|
||||
Object.assign(entitlementPlanForm, {
|
||||
name: row.name,
|
||||
planType: row.planType,
|
||||
description: row.description ?? "",
|
||||
validityDays: row.validityDays ?? null,
|
||||
monthlyTopicLimit: row.monthlyTopicLimit ?? null,
|
||||
enableGrowthProfile: row.enableGrowthProfile ? 1 : 0,
|
||||
enablePeriodicReports: row.enablePeriodicReports ? 1 : 0,
|
||||
allowHelpCard: row.allowHelpCard ? 1 : 0,
|
||||
allowShareDraft: row.allowShareDraft ? 1 : 0,
|
||||
deductQuota: row.deductQuota ? 1 : 0,
|
||||
status: row.status,
|
||||
sortOrder: row.sortOrder,
|
||||
});
|
||||
}
|
||||
|
||||
function resetEntitlementPlanForm() {
|
||||
editingEntitlementPlanId.value = null;
|
||||
Object.assign(entitlementPlanForm, {
|
||||
name: "",
|
||||
planType: "basic",
|
||||
description: "",
|
||||
validityDays: null,
|
||||
monthlyTopicLimit: 30,
|
||||
enableGrowthProfile: 0,
|
||||
enablePeriodicReports: 0,
|
||||
allowHelpCard: 1,
|
||||
allowShareDraft: 1,
|
||||
deductQuota: 1,
|
||||
status: 1,
|
||||
sortOrder: 10,
|
||||
});
|
||||
}
|
||||
|
||||
async function assignUserEntitlement(row: AdminUser, planId: number) {
|
||||
const entitlement = await api.assignUserEntitlement(row.id, { planId });
|
||||
row.entitlement = entitlement;
|
||||
ElMessage.success("用户权益已更新");
|
||||
}
|
||||
|
||||
async function loadRecordTab(tab = recordTab.value) {
|
||||
loading.value = true;
|
||||
try {
|
||||
@@ -866,6 +957,7 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<div class="sidebar-title">大本营千问千答</div>
|
||||
<button :class="{ active: activeMenu === 'dashboard' }" @click="switchMenu('dashboard')">数据看板</button>
|
||||
<button :class="{ active: activeMenu === 'users' }" @click="switchMenu('users')">用户管理</button>
|
||||
<button :class="{ active: activeMenu === 'entitlements' }" @click="switchMenu('entitlements')">权益管理</button>
|
||||
<button :class="{ active: activeMenu === 'knowledge' }" @click="switchMenu('knowledge')">知识库管理</button>
|
||||
<button :class="{ active: activeMenu === 'prompt' }" @click="switchMenu('prompt')">Agent 管理</button>
|
||||
<button :class="{ active: activeMenu === 'models' }" @click="switchMenu('models')">模型管理</button>
|
||||
@@ -984,6 +1076,30 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<template #default="{ row }"><el-input-number v-model="row.dailyChatLimit" :min="0" size="small" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dailyChatUsed" label="已用" width="90" />
|
||||
<el-table-column label="权益版本" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<div class="user-entitlement-cell">
|
||||
<el-select
|
||||
:model-value="row.entitlement?.planId"
|
||||
placeholder="默认基础版"
|
||||
size="small"
|
||||
@change="(planId: number) => assignUserEntitlement(row, planId)"
|
||||
>
|
||||
<el-option
|
||||
v-for="plan in entitlementPlans.filter((item) => item.status === 1)"
|
||||
:key="plan.id"
|
||||
:label="plan.name"
|
||||
:value="plan.id"
|
||||
/>
|
||||
</el-select>
|
||||
<small>
|
||||
{{ row.entitlement?.name || '默认基础版' }}
|
||||
· 本月主题
|
||||
{{ row.entitlement?.monthlyTopicUsed ?? 0 }}/{{ row.entitlement?.monthlyTopicLimit ?? '不限' }}
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="lastLoginAt" label="最近登录" width="180" />
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template #default="{ row }">
|
||||
@@ -995,6 +1111,92 @@ function formatRecordDateTime(value: string, boundary: "start" | "end") {
|
||||
<AdminPagination :page="pagers.users.page" :page-size="pagers.users.pageSize" :total="pagers.users.total" @change="loadUsers" />
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'entitlements'">
|
||||
<div class="page-head">
|
||||
<h2>权益管理</h2>
|
||||
<p>配置不同服务版本的本月主题额度和可用能力;学员可在用户管理里直接分配。</p>
|
||||
</div>
|
||||
|
||||
<section class="entitlement-editor">
|
||||
<div class="entitlement-editor-head">
|
||||
<div>
|
||||
<h3>{{ editingEntitlementPlanId ? '编辑权益版本' : '新增权益版本' }}</h3>
|
||||
<p>一期先接入主题额度、成长档案/周期报告开关和转人工卡片/分享草稿能力位。</p>
|
||||
</div>
|
||||
<el-button @click="resetEntitlementPlanForm">清空</el-button>
|
||||
</div>
|
||||
<el-form label-position="top" :model="entitlementPlanForm">
|
||||
<div class="entitlement-form-grid">
|
||||
<el-form-item label="版本名称">
|
||||
<el-input v-model="entitlementPlanForm.name" placeholder="例如:五个月深度陪伴版" />
|
||||
</el-form-item>
|
||||
<el-form-item label="版本类型">
|
||||
<el-select v-model="entitlementPlanForm.planType">
|
||||
<el-option label="基础版" value="basic" />
|
||||
<el-option label="深度陪伴版" value="deep" />
|
||||
<el-option label="高频加购包" value="addon" />
|
||||
<el-option label="老师工作版" value="teacher" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="有效天数">
|
||||
<el-input-number v-model="entitlementPlanForm.validityDays" :min="1" :max="3650" placeholder="留空长期有效" />
|
||||
</el-form-item>
|
||||
<el-form-item label="本月主题额度">
|
||||
<el-input-number v-model="entitlementPlanForm.monthlyTopicLimit" :min="0" :max="99999" placeholder="留空不限" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="entitlementPlanForm.sortOrder" :min="0" :max="9999" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="说明">
|
||||
<el-input v-model="entitlementPlanForm.description" type="textarea" :rows="2" placeholder="给后台管理员看的说明,不展示给学员" />
|
||||
</el-form-item>
|
||||
<div class="entitlement-switch-grid">
|
||||
<label><span>成长档案</span><el-switch v-model="entitlementPlanForm.enableGrowthProfile" :active-value="1" :inactive-value="0" /></label>
|
||||
<label><span>周期报告</span><el-switch v-model="entitlementPlanForm.enablePeriodicReports" :active-value="1" :inactive-value="0" /></label>
|
||||
<label><span>允许求助卡片</span><el-switch v-model="entitlementPlanForm.allowHelpCard" :active-value="1" :inactive-value="0" /></label>
|
||||
<label><span>允许分享草稿</span><el-switch v-model="entitlementPlanForm.allowShareDraft" :active-value="1" :inactive-value="0" /></label>
|
||||
<label><span>占用主题额度</span><el-switch v-model="entitlementPlanForm.deductQuota" :active-value="1" :inactive-value="0" /></label>
|
||||
<label><span>启用版本</span><el-switch v-model="entitlementPlanForm.status" :active-value="1" :inactive-value="0" /></label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="saveEntitlementPlan">{{ editingEntitlementPlanId ? '保存权益版本' : '新增权益版本' }}</el-button>
|
||||
<el-button @click="resetEntitlementPlanForm">取消编辑</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</section>
|
||||
|
||||
<el-table :data="entitlementPlans" stripe>
|
||||
<el-table-column prop="name" label="版本名称" min-width="180" />
|
||||
<el-table-column label="类型" width="130">
|
||||
<template #default="{ row }">{{ planTypeLabel(row.planType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本月主题额度" width="130">
|
||||
<template #default="{ row }">{{ row.monthlyTopicLimit ?? '不限' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="有效期" width="110">
|
||||
<template #default="{ row }">{{ row.validityDays ? `${row.validityDays} 天` : '长期' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="能力" min-width="260">
|
||||
<template #default="{ row }">
|
||||
<div class="entitlement-capabilities">
|
||||
<el-tag v-if="row.enableGrowthProfile" type="success" effect="plain">成长档案</el-tag>
|
||||
<el-tag v-if="row.enablePeriodicReports" type="success" effect="plain">周期报告</el-tag>
|
||||
<el-tag v-if="row.allowHelpCard" effect="plain">求助卡片</el-tag>
|
||||
<el-tag v-if="row.allowShareDraft" effect="plain">分享草稿</el-tag>
|
||||
<el-tag :type="row.deductQuota ? 'warning' : 'info'" effect="plain">{{ row.deductQuota ? '计入额度' : '不计额度' }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90">
|
||||
<template #default="{ row }"><el-tag :type="row.status === 1 ? 'success' : 'info'">{{ row.status === 1 ? '启用' : '停用' }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row }"><el-button size="small" @click="editEntitlementPlan(row)">编辑</el-button></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'knowledge'">
|
||||
<KnowledgeManagementView @preview="previewKnowledge" />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user