Files
smt/pages/ai_summary/index.vue
T
你çšnepiedg d101515d8d refactor: rename AI summary page and update navigation path
- Changed the path of the AI summary page from "pages/ai-summary/index" to "pages/ai_summary/index"
- Updated the navigation in the profile page to reflect the new path for the AI summary page
2026-03-16 15:34:37 +08:00

406 lines
9.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="page">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="container">
<view class="hero-card">
<text class="hero-label">AI 总结</text>
<text class="hero-title">按日期生成复盘</text>
<text class="hero-desc">选择任意历史日期生成当日抽烟总结关键发现和次日建议</text>
</view>
<view class="toolbar-card">
<picker mode="date" :value="selectedDate" :end="today" @change="handleDateChange">
<view class="date-picker">
<text class="date-picker-label">总结日期</text>
<text class="date-picker-value">{{ selectedDate }}</text>
</view>
</picker>
<view class="primary-btn" @tap="handleGenerateSummary">
<text class="primary-btn-text">{{ summaryLoading ? '生成中...' : actionText }}</text>
</view>
</view>
<view v-if="summaryLoading" class="summary-card loading-card">
<text class="loading-text">AI 正在分析 {{ selectedDate }} 的数据...</text>
</view>
<view v-else-if="summaryData" class="summary-card">
<text class="summary-date">{{ summaryData.date }}</text>
<text class="summary-text">{{ parsedSummary.summary }}</text>
<view class="highlights-card" v-if="parsedSummary.highlights && parsedSummary.highlights.length">
<text class="block-title">关键发现</text>
<view class="highlight-item" v-for="(item, idx) in parsedSummary.highlights" :key="idx">
<text class="highlight-dot">·</text>
<text class="highlight-text">{{ item }}</text>
</view>
</view>
<view class="suggestion-card" v-if="parsedSummary.suggestion">
<text class="block-title">明日建议</text>
<text class="suggestion-text">{{ parsedSummary.suggestion }}</text>
</view>
</view>
<view v-else class="summary-card empty-card">
<text class="empty-title">{{ emptyTitle }}</text>
<text class="empty-desc">{{ emptyDesc }}</text>
</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import * as api from '@/api'
import { useLogin } from '@/hooks/useLogin'
const { waitForLogin } = useLogin()
const rewardAdUnitId = 'adunit-36e13d77e185f757'
const statusBarHeight = ref(0)
const homeData = ref(null)
const selectedDate = ref(formatLocalDate())
const summaryLoading = ref(false)
const summaryData = ref(null)
const summaryState = ref('empty')
const today = computed(() => formatLocalDate())
const actionText = computed(() => (summaryData.value ? '刷新' : '生成'))
const parsedSummary = computed(() => parseSummaryContent(summaryData.value?.content))
const emptyTitle = computed(() => {
if (summaryState.value === 'locked') return '当前日期尚未解锁'
if (summaryState.value === 'no_data') return '当天还没有可总结的记录'
if (summaryState.value === 'unavailable') return 'AI 服务暂时不可用'
return '还没有生成总结'
})
const emptyDesc = computed(() => {
if (summaryState.value === 'locked') return '完成激励广告后可生成该日期的 AI 总结。'
if (summaryState.value === 'no_data') return '先确认当天是否有抽烟记录,再重新生成。'
if (summaryState.value === 'unavailable') return '稍后重试,或查看后端日志里的提示词和输入数据。'
return '选择日期后点击生成,系统会结合当天记录做总结。'
})
function formatLocalDate(date = new Date()) {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
function extractJSONObject(text = '') {
const start = text.indexOf('{')
const end = text.lastIndexOf('}')
if (start === -1 || end === -1 || end <= start) return ''
return text.slice(start, end + 1)
}
function parseSummaryContent(content = '') {
if (!content) return {}
const jsonText = extractJSONObject(content)
if (jsonText) {
try {
return JSON.parse(jsonText)
} catch (e) {
console.error('parseSummaryContent json error:', e)
}
}
return {
summary: content,
highlights: [],
suggestion: ''
}
}
async function fetchHomeData() {
const res = await api.getHome()
homeData.value = res.data || {}
if (selectedDate.value === today.value && homeData.value?.daily_summary?.status === 'available') {
summaryData.value = homeData.value.daily_summary
summaryState.value = 'available'
}
}
function handleDateChange(event) {
selectedDate.value = event.detail.value
if (selectedDate.value === today.value && homeData.value?.daily_summary?.status === 'available') {
summaryData.value = homeData.value.daily_summary
summaryState.value = 'available'
return
}
summaryData.value = null
summaryState.value = 'empty'
}
async function runRewardedUnlock(onUnlocked) {
// #ifdef MP-WEIXIN
try {
const videoAd = wx.createRewardedVideoAd({
adUnitId: rewardAdUnitId
})
videoAd.onClose(async (res) => {
if (res && res.isEnded) {
await onUnlocked()
} else {
uni.showToast({ title: '需要看完广告哦', icon: 'none' })
}
})
videoAd.onError(async () => {
await onUnlocked()
})
await videoAd.show().catch(async () => {
await videoAd.load()
await videoAd.show()
})
return
} catch (e) {
await onUnlocked()
return
}
// #endif
// #ifndef MP-WEIXIN
await onUnlocked()
// #endif
}
async function unlockSummaryDate() {
try {
await api.unlockAiAdvice({ date: selectedDate.value })
} catch (e) {
console.error('unlockSummaryDate error:', e)
}
}
async function fetchSummaryByDate() {
summaryLoading.value = true
try {
const res = await api.getAIDailySummary({ date: selectedDate.value })
const data = res.data || {}
summaryData.value = {
date: data.date || selectedDate.value,
content: data.content || '',
model: data.model || '',
status: 'available'
}
summaryState.value = 'available'
if (selectedDate.value === today.value && homeData.value) {
homeData.value.daily_summary = summaryData.value
}
uni.showToast({ title: '总结已生成', icon: 'success' })
} catch (e) {
console.error('fetchSummaryByDate error:', e)
const msg = e?.data?.message || '生成失败,请稍后重试'
if (msg.includes('解锁')) {
summaryState.value = 'locked'
} else if (msg.includes('没有抽烟记录')) {
summaryState.value = 'no_data'
} else {
summaryState.value = 'unavailable'
}
summaryData.value = null
uni.showToast({ title: msg, icon: 'none' })
} finally {
summaryLoading.value = false
}
}
async function handleGenerateSummary() {
if (summaryLoading.value) return
if (summaryData.value) {
await fetchSummaryByDate()
return
}
await runRewardedUnlock(async () => {
await unlockSummaryDate()
await fetchSummaryByDate()
})
}
onShow(async () => {
try {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 0
await waitForLogin()
await fetchHomeData()
} catch (e) {
console.error('ai summary onShow error:', e)
}
})
</script>
<style scoped>
.page {
min-height: 100vh;
background: linear-gradient(to bottom, #D1FAE5 0%, #F0FDF4 45%, #FFFFFF 100%);
}
.status-bar {
background: linear-gradient(to bottom, #D1FAE5, #E9FDF2);
}
.container {
padding: 24rpx 32rpx 80rpx;
}
.hero-card,
.toolbar-card,
.summary-card,
.highlights-card,
.suggestion-card {
background-color: #FFFFFF;
border-radius: 28rpx;
border: 2rpx solid #D9FBE7;
box-shadow: 0 10rpx 24rpx rgba(16, 185, 129, 0.08);
}
.hero-card {
padding: 32rpx;
margin-bottom: 24rpx;
}
.hero-label {
font-size: 22rpx;
color: #059669;
display: block;
margin-bottom: 12rpx;
}
.hero-title {
font-size: 42rpx;
font-weight: 700;
color: #111827;
display: block;
margin-bottom: 12rpx;
}
.hero-desc {
font-size: 24rpx;
color: #6B7280;
line-height: 1.6;
}
.toolbar-card {
padding: 24rpx;
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 24rpx;
}
.date-picker {
flex: 1;
padding: 18rpx 22rpx;
border-radius: 20rpx;
background: #F0FDF4;
border: 2rpx solid #D1FAE5;
}
.date-picker-label {
font-size: 22rpx;
color: #6B7280;
display: block;
margin-bottom: 6rpx;
}
.date-picker-value {
font-size: 28rpx;
font-weight: 700;
color: #111827;
}
.primary-btn {
padding: 14rpx 24rpx;
border-radius: 999rpx;
background: linear-gradient(135deg, #34D399, #10B981);
}
.primary-btn-text {
font-size: 22rpx;
font-weight: 600;
color: #FFFFFF;
}
.summary-card {
padding: 28rpx;
}
.loading-card {
display: flex;
justify-content: center;
}
.loading-text {
font-size: 24rpx;
color: #6B7280;
}
.summary-date {
font-size: 22rpx;
color: #059669;
display: block;
margin-bottom: 12rpx;
}
.summary-text {
font-size: 28rpx;
color: #111827;
line-height: 1.7;
display: block;
}
.highlights-card,
.suggestion-card {
margin-top: 20rpx;
padding: 22rpx;
background-color: #F9FFFB;
}
.block-title {
font-size: 24rpx;
font-weight: 700;
color: #059669;
display: block;
margin-bottom: 10rpx;
}
.highlight-item {
display: flex;
align-items: flex-start;
gap: 8rpx;
margin-bottom: 8rpx;
}
.highlight-item:last-child {
margin-bottom: 0;
}
.highlight-dot {
font-size: 28rpx;
color: #10B981;
font-weight: 700;
line-height: 1.5;
}
.highlight-text,
.suggestion-text,
.empty-desc {
font-size: 24rpx;
color: #374151;
line-height: 1.6;
}
.empty-card {
text-align: center;
}
.empty-title {
font-size: 28rpx;
font-weight: 700;
color: #111827;
display: block;
margin-bottom: 12rpx;
}
</style>