Files
smt/pages/stats/index.vue
T

416 lines
7.9 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="tabs">
<view
v-for="tab in tabs"
:key="tab.value"
class="tab"
:class="{ 'tab-active': currentTab === tab.value }"
@tap="currentTab = tab.value"
>
{{ tab.label }}
</view>
</view>
<view class="insight-card">
<view class="insight-icon"></view>
<view class="insight-content">
<text class="insight-title">每周洞察</text>
<text class="insight-desc">你在周末的吸烟量明显减少非常棒试着在这周一保持这个良好的势头</text>
</view>
</view>
<view class="section">
<view class="section-header">
<text class="section-title">吸烟趋势</text>
<text class="section-change"> 减少 20%</text>
</view>
<view class="chart-card">
<view class="chart-header">
<text class="chart-label">日均吸烟量</text>
<view class="chart-value-row">
<text class="chart-value">4</text>
<text class="chart-unit">/</text>
</view>
</view>
<view class="chart-bars">
<view v-for="(item, index) in weeklyData" :key="index" class="chart-bar-wrapper">
<view class="chart-bar" :style="{ height: item.height }"></view>
<text class="chart-bar-label" :class="{ 'chart-bar-label-active': index === 3 }">{{ item.label }}</text>
</view>
</view>
</view>
</view>
<view class="section">
<text class="section-title">健康与储蓄</text>
<view class="health-row">
<view class="health-card">
<view class="health-ring">
<view class="health-ring-inner">
<text class="health-prefix">已省</text>
<text class="health-value">¥145</text>
</view>
</view>
<text class="health-label">节省金额</text>
<text class="health-sub">目标 ¥200</text>
</view>
<view class="health-card">
<view class="health-ring health-ring-purple">
<view class="health-ring-inner">
<text class="health-value">40%</text>
</view>
</view>
<text class="health-label">肺部功能恢复</text>
<text class="health-sub">当前进度</text>
</view>
</view>
<view class="stats-grid">
<view class="mini-stat">
<text class="mini-stat-icon">🔥</text>
<text class="mini-stat-label">连续记录</text>
<view class="mini-stat-value-row">
<text class="mini-stat-value">12</text>
<text class="mini-stat-unit"></text>
</view>
<text class="mini-stat-sub">未吸烟</text>
</view>
<view class="mini-stat">
<text class="mini-stat-icon">🚫</text>
<text class="mini-stat-label">已拒绝</text>
<view class="mini-stat-value-row">
<text class="mini-stat-value">24</text>
<text class="mini-stat-unit"></text>
</view>
<text class="mini-stat-sub">对抗烟瘾</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useLogin } from '@/hooks/useLogin'
import * as api from '@/api'
const { waitForLogin } = useLogin()
const loading = ref(true)
const tabs = [
{ label: '周', value: 'week' },
{ label: '月', value: 'month' },
{ label: '年', value: 'year' }
]
const currentTab = ref('week')
const weeklyData = ref([
{ label: '一', height: '60%', count: 3 },
{ label: '二', height: '40%', count: 2 },
{ label: '三', height: '80%', count: 4 },
{ label: '四', height: '100%', count: 5 },
{ label: '五', height: '60%', count: 3 },
{ label: '六', height: '20%', count: 1 },
{ label: '日', height: '40%', count: 2 }
])
async function initPage() {
loading.value = true
try {
await waitForLogin()
const res = await api.getDashboard()
if (res.data?.weekly) {
const maxCount = Math.max(...res.data.weekly.map(d => d.count), 1)
const weekLabels = ['一', '二', '三', '四', '五', '六', '日']
weeklyData.value = res.data.weekly.map((d, i) => ({
label: weekLabels[i] || '',
height: `${Math.max((d.count / maxCount) * 100, 5)}%`,
count: d.count
}))
}
} catch (e) {
console.error('initPage error:', e)
} finally {
loading.value = false
}
}
onMounted(() => {
initPage()
})
</script>
<style scoped>
.page {
min-height: 100vh;
background-color: #0D1F17;
padding: 32rpx;
padding-bottom: 180rpx;
box-sizing: border-box;
}
.tabs {
display: flex;
background-color: #1A3325;
border-radius: 16rpx;
padding: 8rpx;
margin-bottom: 32rpx;
}
.tab {
flex: 1;
text-align: center;
padding: 20rpx;
border-radius: 12rpx;
font-size: 28rpx;
color: #9CA3AF;
}
.tab-active {
background-color: #4ADE80;
color: #0D1F17;
font-weight: 600;
}
.insight-card {
display: flex;
gap: 24rpx;
background-color: rgba(74, 222, 128, 0.1);
border: 2rpx solid rgba(74, 222, 128, 0.3);
border-radius: 24rpx;
padding: 32rpx;
margin-bottom: 32rpx;
}
.insight-icon {
font-size: 36rpx;
background-color: #4ADE80;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.insight-content { flex: 1; }
.insight-title {
font-weight: 600;
color: #4ADE80;
display: block;
margin-bottom: 8rpx;
}
.insight-desc {
font-size: 26rpx;
color: #9CA3AF;
line-height: 1.5;
}
.section { margin-bottom: 32rpx; }
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 600;
color: #FFFFFF;
}
.section-change {
font-size: 26rpx;
color: #4ADE80;
}
.chart-card {
background-color: #1A3325;
border-radius: 24rpx;
padding: 32rpx;
}
.chart-header { margin-bottom: 32rpx; }
.chart-label {
font-size: 24rpx;
color: #9CA3AF;
display: block;
}
.chart-value-row {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.chart-value {
font-size: 56rpx;
font-weight: 700;
color: #FFFFFF;
}
.chart-unit {
font-size: 24rpx;
color: #9CA3AF;
}
.chart-bars {
display: flex;
justify-content: space-between;
align-items: flex-end;
height: 240rpx;
padding-top: 24rpx;
}
.chart-bar-wrapper {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.chart-bar {
width: 36rpx;
background: linear-gradient(to top, #4ADE80, rgba(74, 222, 128, 0.4));
border-radius: 8rpx 8rpx 0 0;
min-height: 8rpx;
}
.chart-bar-label {
font-size: 24rpx;
color: #9CA3AF;
margin-top: 12rpx;
}
.chart-bar-label-active {
color: #0D1F17;
background-color: #4ADE80;
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
.health-row {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.health-card {
flex: 1;
background-color: #1A3325;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.health-ring {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
background: conic-gradient(#4ADE80 0deg 252deg, rgba(74, 222, 128, 0.2) 252deg 360deg);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.health-ring-purple {
background: conic-gradient(#A78BFA 0deg 144deg, rgba(167, 139, 250, 0.2) 144deg 360deg);
}
.health-ring-inner {
width: 120rpx;
height: 120rpx;
border-radius: 50%;
background-color: #1A3325;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.health-prefix {
font-size: 20rpx;
color: #9CA3AF;
}
.health-value {
font-size: 28rpx;
font-weight: 700;
color: #FFFFFF;
}
.health-label {
font-size: 26rpx;
color: #FFFFFF;
margin-bottom: 4rpx;
}
.health-sub {
font-size: 22rpx;
color: #9CA3AF;
}
.stats-grid {
display: flex;
gap: 24rpx;
}
.mini-stat {
flex: 1;
background-color: #1A3325;
border-radius: 24rpx;
padding: 24rpx;
}
.mini-stat-icon {
font-size: 36rpx;
display: block;
margin-bottom: 12rpx;
}
.mini-stat-label {
font-size: 24rpx;
color: #9CA3AF;
display: block;
margin-bottom: 8rpx;
}
.mini-stat-value-row {
display: flex;
align-items: baseline;
gap: 8rpx;
}
.mini-stat-value {
font-size: 48rpx;
font-weight: 700;
color: #FFFFFF;
}
.mini-stat-unit {
font-size: 24rpx;
color: #9CA3AF;
}
.mini-stat-sub {
font-size: 22rpx;
color: #6B7280;
display: block;
margin-top: 4rpx;
}
</style>