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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user