feat: AI 摘要持久化 — DB 缓存 + 自动加载

后端:
- 新增 ai_summaries 表 (week_start/week_end/generated_by/summary)
- get_cached_summary(): 按周+用户查缓存
- delete_cached_summary(): 生成前清理旧缓存
- generate_summary(): 自动保存到 DB
- API: GET /ai/summary(加载) POST(生成) DELETE(清除)

前端:
- onMounted 自动 GET 缓存摘要, 有则直接展示
- 按钮: 无缓存→'AI 生成摘要', 有缓存→'重新生成'
- 标题栏显示生成时间戳
This commit is contained in:
2026-06-25 11:45:04 +08:00
parent e681beba24
commit 5b38246801
6 changed files with 175 additions and 19 deletions
+19 -2
View File
@@ -16,6 +16,8 @@ const loading = ref(false)
const aiLoading = ref(false)
const aiSummary = ref('')
const aiError = ref('')
const aiCached = ref(false)
const aiCreatedAt = ref('')
const activeTab = ref('visits')
const filterManagerId = ref('')
const filterCustomerId = ref('')
@@ -47,6 +49,17 @@ onMounted(async () => {
managers.value = mRes.data
customers.value = cRes.data.items || cRes.data
} catch (_) {}
// Auto-load cached AI summary
if (auth.isDirector || auth.isLeader) {
try {
const cached = await aiApi.getSummary({ reference_date: getRefDate(), period: 'week' })
if (cached.data?.summary) {
aiSummary.value = cached.data.summary
aiCached.value = !!cached.data.cached
aiCreatedAt.value = cached.data.created_at || ''
}
} catch (_) {}
}
})
function changeWeek(delta: number) { weekOffset.value += delta; loadReport() }
@@ -106,9 +119,12 @@ async function generateAISummary() {
aiLoading.value = true
aiSummary.value = ''
aiError.value = ''
aiCached.value = false
try {
const res = await aiApi.generateSummary({ reference_date: getRefDate(), period: 'week' })
aiSummary.value = res.data.summary
aiCached.value = !!res.data.cached
aiCreatedAt.value = res.data.created_at || ''
} catch (e: any) {
const detail = e.response?.data?.detail || 'AI 摘要生成失败,请检查 AI 服务配置或稍后重试'
aiError.value = detail
@@ -202,7 +218,7 @@ const notesByDate = computed(() => {
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px">
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
</svg>
AI 生成摘要
{{ aiCached ? '重新生成' : 'AI 生成摘要' }}
</el-button>
<el-button v-if="auth.isDirector" type="success" @click="handleExport">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right:4px">
@@ -245,7 +261,8 @@ const notesByDate = computed(() => {
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>
</svg>
<span class="ai-header-title">AI 周报摘要</span>
<span class="ai-header-hint">基于本周拜访数据自动生成仅供参考</span>
<span v-if="aiCached && aiCreatedAt" class="ai-header-hint">生成于 {{ new Date(aiCreatedAt).toLocaleString('zh-CN') }} · 已缓存</span>
<span v-else class="ai-header-hint">基于本周拜访数据自动生成仅供参考</span>
</div>
<div class="ai-header-actions">
<el-button v-if="aiSummary" size="small" text @click="copySummary">