Remove outdated UI components and screens for activity history, AI quit assistant, home dashboard, profile & settings, and smoking statistics. Update API to include a new endpoint for home dashboard data retrieval. Enhance smoke record dialog with improved layout and styling adjustments.
This commit is contained in:
+110
-47
@@ -16,8 +16,8 @@
|
||||
<view class="user-info">
|
||||
<image class="avatar" :src="userAvatar" mode="aspectFill"></image>
|
||||
<view class="greeting">
|
||||
<text class="greeting-text">{{ greeting }},{{ userName }}</text>
|
||||
<text class="greeting-sub">保持连胜纪录!🔥</text>
|
||||
<text class="greeting-text">{{ greetingTitle }}</text>
|
||||
<text class="greeting-sub">{{ greetingSubtitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="settings-btn" @tap="goSettings">
|
||||
@@ -25,10 +25,10 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="showAiTip" class="ai-tip">
|
||||
<view v-if="shouldShowAiTip" class="ai-tip">
|
||||
<view class="ai-tip-icon">🤖</view>
|
||||
<view class="ai-tip-content">
|
||||
<text class="ai-tip-title">发现规律</text>
|
||||
<text class="ai-tip-title">{{ aiTipTitle }}</text>
|
||||
<text class="ai-tip-desc">{{ aiTipText }}</text>
|
||||
</view>
|
||||
<view class="ai-tip-close" @tap="closeAiTip">×</view>
|
||||
@@ -55,7 +55,7 @@
|
||||
<view class="stat-value-row">
|
||||
<text class="stat-value">{{ todayCount }}</text>
|
||||
<text class="stat-target">/ {{ dailyTarget }}</text>
|
||||
<view class="stat-change stat-change-down">{{ changeText }}</view>
|
||||
<view class="stat-change" :class="changeClass">{{ changeText }}</view>
|
||||
</view>
|
||||
<view class="stat-progress">
|
||||
<view class="stat-progress-bar stat-progress-red" :style="{ width: progressWidth }"></view>
|
||||
@@ -98,27 +98,27 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useDashboardStore } from '@/stores/dashboard'
|
||||
import { useProfileStore } from '@/stores/profile'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useLogin } from '@/hooks/useLogin'
|
||||
import * as api from '@/api'
|
||||
|
||||
const dashboardStore = useDashboardStore()
|
||||
const profileStore = useProfileStore()
|
||||
const userStore = useUserStore()
|
||||
const { waitForLogin } = useLogin()
|
||||
|
||||
const loading = ref(true)
|
||||
const showAiTip = ref(true)
|
||||
const aiTipText = ref('你的烟瘾通常在下午2点达到高峰。我们为你准备了一个快速呼吸练习。')
|
||||
const resistedCount = ref(5)
|
||||
const statusBarHeight = ref(0)
|
||||
const showDialog = ref(false)
|
||||
const dialogType = ref('smoke') // 'smoke' 或 'resisted'
|
||||
const homeData = ref(null)
|
||||
|
||||
let timerInterval = null
|
||||
const timerSeconds = ref(0)
|
||||
const timerBaseSeconds = ref(-1)
|
||||
|
||||
const aiTipFallback = '你的烟瘾通常在下午2点达到高峰。我们为你准备了一个快速呼吸练习。'
|
||||
|
||||
const greeting = computed(() => {
|
||||
const hour = new Date().getHours()
|
||||
@@ -130,18 +130,50 @@ const greeting = computed(() => {
|
||||
})
|
||||
|
||||
const userName = computed(() => {
|
||||
return userStore.user?.nickname || 'Alex'
|
||||
return homeData.value?.greeting?.nickname || userStore.user?.nickname || 'Alex'
|
||||
})
|
||||
|
||||
const userAvatar = computed(() => {
|
||||
return userStore.user?.avatar_url || '/static/images/default-avatar.png'
|
||||
return homeData.value?.greeting?.avatar_url || userStore.user?.avatar_url || '/static/images/default-avatar.png'
|
||||
})
|
||||
|
||||
const todayCount = computed(() => dashboardStore.todayCount || 3)
|
||||
const dailyTarget = computed(() => profileStore.profile?.baseline_cigs_per_day || 10)
|
||||
const greetingTitle = computed(() => {
|
||||
return homeData.value?.greeting?.title || `${greeting.value},${userName.value}`
|
||||
})
|
||||
|
||||
const greetingSubtitle = computed(() => {
|
||||
return homeData.value?.greeting?.subtitle || '保持连胜纪录!🔥'
|
||||
})
|
||||
|
||||
const aiTipTitle = computed(() => {
|
||||
return homeData.value?.advice_card?.title || '发现规律'
|
||||
})
|
||||
|
||||
const aiTipText = computed(() => {
|
||||
const advice = homeData.value?.advice_card
|
||||
if (advice?.message) return advice.message
|
||||
if (advice?.status === 'locked') return '看广告解锁 AI 建议'
|
||||
const motivation = homeData.value?.motivation?.message
|
||||
return motivation || aiTipFallback
|
||||
})
|
||||
|
||||
const shouldShowAiTip = computed(() => showAiTip.value && !!aiTipText.value)
|
||||
|
||||
const homeSummary = computed(() => homeData.value?.summary || {})
|
||||
const homeTimer = computed(() => homeData.value?.timer || {})
|
||||
|
||||
const todayCount = computed(() => homeSummary.value.today_count ?? 0)
|
||||
const dailyTarget = computed(() => {
|
||||
const target = homeSummary.value.daily_target
|
||||
if (target !== undefined && target !== null) return target
|
||||
return profileStore.profile?.baseline_cigs_per_day || 0
|
||||
})
|
||||
const resistedCount = computed(() => homeSummary.value.resisted_count ?? 0)
|
||||
|
||||
const progressWidth = computed(() => {
|
||||
const percent = Math.min((todayCount.value / dailyTarget.value) * 100, 100)
|
||||
const target = dailyTarget.value
|
||||
if (!target) return '0%'
|
||||
const percent = Math.min((todayCount.value / target) * 100, 100)
|
||||
return `${percent}%`
|
||||
})
|
||||
|
||||
@@ -151,12 +183,21 @@ const resistedProgressWidth = computed(() => {
|
||||
})
|
||||
|
||||
const changeText = computed(() => {
|
||||
return '较昨日 -2'
|
||||
const reduced = homeSummary.value.reduced_from_yesterday
|
||||
if (reduced === undefined || reduced === null) return '较昨日 --'
|
||||
if (reduced === 0) return '较昨日 0'
|
||||
return homeSummary.value.exceeded_yesterday ? `较昨日 +${reduced}` : `较昨日 -${reduced}`
|
||||
})
|
||||
|
||||
const changeClass = computed(() => {
|
||||
return homeSummary.value.exceeded_yesterday ? 'stat-change-up' : 'stat-change-down'
|
||||
})
|
||||
|
||||
const timerDisplay = computed(() => {
|
||||
const baseMinutes = dashboardStore.minutesSinceLast ?? 165
|
||||
const totalSeconds = baseMinutes * 60 + timerSeconds.value
|
||||
if (timerBaseSeconds.value < 0) {
|
||||
return '--:--:--'
|
||||
}
|
||||
const totalSeconds = timerBaseSeconds.value + timerSeconds.value
|
||||
const hours = Math.floor(totalSeconds / 3600)
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60)
|
||||
const seconds = totalSeconds % 60
|
||||
@@ -168,11 +209,16 @@ const timerGradient = computed(() => {
|
||||
})
|
||||
|
||||
const nextSmokeTimeText = computed(() => {
|
||||
if (dashboardStore.nextSmokeTime?.suggested_at) {
|
||||
const date = new Date(dashboardStore.nextSmokeTime.suggested_at)
|
||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
||||
const timer = homeTimer.value
|
||||
if (!timer) return ''
|
||||
if (timer.next_suggested_clock) return timer.next_suggested_clock
|
||||
if (timer.next_suggested_at) {
|
||||
const date = new Date(timer.next_suggested_at)
|
||||
if (!isNaN(date.getTime())) {
|
||||
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
return '16:30'
|
||||
return ''
|
||||
})
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
@@ -180,6 +226,10 @@ const dialogTitle = computed(() => {
|
||||
})
|
||||
|
||||
function startTimer() {
|
||||
stopTimer()
|
||||
if (timerBaseSeconds.value < 0) {
|
||||
return
|
||||
}
|
||||
timerInterval = setInterval(() => {
|
||||
timerSeconds.value++
|
||||
}, 1000)
|
||||
@@ -202,29 +252,48 @@ function openResistedDialog() {
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
function applyHomeData(data) {
|
||||
homeData.value = data
|
||||
const seconds = data?.timer?.seconds_since_last
|
||||
timerBaseSeconds.value = typeof seconds === 'number' ? seconds : -1
|
||||
timerSeconds.value = 0
|
||||
startTimer()
|
||||
}
|
||||
|
||||
async function fetchHomeData() {
|
||||
const res = await api.getHome()
|
||||
const data = res.data || {}
|
||||
applyHomeData(data)
|
||||
return data
|
||||
}
|
||||
|
||||
async function handleSubmit(submitData) {
|
||||
try {
|
||||
if (dialogType.value === 'smoke') {
|
||||
await api.createLog(submitData)
|
||||
dashboardStore.resetTimer()
|
||||
timerBaseSeconds.value = 0
|
||||
timerSeconds.value = 0
|
||||
startTimer()
|
||||
uni.showToast({ title: '记录成功', icon: 'success' })
|
||||
try {
|
||||
await Promise.all([
|
||||
dashboardStore.fetchDashboard(true),
|
||||
dashboardStore.fetchNextSmokeTime()
|
||||
])
|
||||
await fetchHomeData()
|
||||
} catch (e) {
|
||||
console.error('refreshAfterSmokeLog error:', e)
|
||||
console.error('refreshHomeData error:', e)
|
||||
}
|
||||
} else {
|
||||
await api.createResistedLog({
|
||||
smoke_time: submitData.smoke_time,
|
||||
smoke_at: submitData.smoke_at,
|
||||
remark: submitData.remark
|
||||
remark: submitData.remark,
|
||||
level: submitData.level,
|
||||
num: submitData.num
|
||||
})
|
||||
resistedCount.value++
|
||||
uni.showToast({ title: '太棒了!', icon: 'success' })
|
||||
try {
|
||||
await fetchHomeData()
|
||||
} catch (e) {
|
||||
console.error('refreshHomeData error:', e)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('handleSubmit error:', e)
|
||||
@@ -242,33 +311,27 @@ async function initPage() {
|
||||
try {
|
||||
await waitForLogin()
|
||||
|
||||
const [profileRes, dashboardRes, nextTimeRes] = await Promise.all([
|
||||
api.getProfile(),
|
||||
api.getDashboard(),
|
||||
api.getNextSmokeTime()
|
||||
])
|
||||
|
||||
const profile = profileRes.data.profile
|
||||
const isCompleted = profileRes.data.is_completed ||
|
||||
const home = await fetchHomeData()
|
||||
const profileData = home.profile || {}
|
||||
const profile = profileData.profile
|
||||
const isCompleted = profileData.is_completed ||
|
||||
(profile && profile.onboarding_completed_at) ||
|
||||
(profile && profile.baseline_cigs_per_day > 0)
|
||||
|
||||
if (!profileRes.data.exists || !isCompleted) {
|
||||
if (!profileData.exists || !isCompleted) {
|
||||
uni.redirectTo({ url: '/pages/onboarding/index' })
|
||||
return
|
||||
}
|
||||
|
||||
profileStore.exists = profileRes.data.exists
|
||||
profileStore.isCompleted = profileRes.data.is_completed
|
||||
profileStore.profile = profileRes.data.profile
|
||||
|
||||
dashboardStore.setDashboard(dashboardRes.data)
|
||||
dashboardStore.setNextSmokeTime(nextTimeRes.data)
|
||||
|
||||
startTimer()
|
||||
profileStore.exists = !!profileData.exists
|
||||
profileStore.isCompleted = !!isCompleted
|
||||
profileStore.awakeMinutes = profileData.awake_minutes || 960
|
||||
profileStore.baselineIntervalMinutes = profileData.baseline_interval_minutes || 60
|
||||
if (profile) {
|
||||
profileStore.profile = profile
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('initPage error:', e)
|
||||
startTimer()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user