441 lines
12 KiB
Vue
441 lines
12 KiB
Vue
<template>
|
||
<div class="dashboard-page">
|
||
<!-- 欢迎区域 -->
|
||
<div class="welcome-section bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg p-6 mb-6 text-white">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h1 class="text-2xl font-bold mb-2">
|
||
{{ getGreeting() }},{{ userNickname }}!
|
||
</h1>
|
||
<p class="text-blue-100">
|
||
今天是您使用情绪博物馆的第 {{ totalDays }} 天
|
||
</p>
|
||
</div>
|
||
|
||
<div class="text-right">
|
||
<div class="text-3xl font-bold">{{ todayMood }}/10</div>
|
||
<div class="text-blue-100 text-sm">今日心情指数</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<div class="stats-grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
||
<div
|
||
v-for="stat in statsData"
|
||
:key="stat.title"
|
||
class="stat-card bg-white rounded-lg shadow-sm p-6 hover:shadow-md transition-shadow"
|
||
>
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-gray-600 text-sm mb-1">{{ stat.title }}</p>
|
||
<p class="text-2xl font-bold text-gray-900">{{ stat.value }}</p>
|
||
<div v-if="stat.trend" class="flex items-center mt-2">
|
||
<el-icon
|
||
:class="stat.trend === 'up' ? 'text-green-500' : 'text-red-500'"
|
||
size="16"
|
||
>
|
||
<ArrowUp v-if="stat.trend === 'up'" />
|
||
<ArrowDown v-else />
|
||
</el-icon>
|
||
<span
|
||
:class="stat.trend === 'up' ? 'text-green-500' : 'text-red-500'"
|
||
class="text-sm ml-1"
|
||
>
|
||
{{ stat.trendValue }}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
class="w-12 h-12 rounded-lg flex items-center justify-center"
|
||
:style="{ backgroundColor: stat.color + '20' }"
|
||
>
|
||
<el-icon :size="24" :style="{ color: stat.color }">
|
||
<component :is="stat.icon" />
|
||
</el-icon>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图表区域 -->
|
||
<div class="charts-section grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
||
<!-- 情绪趋势图 -->
|
||
<div class="chart-card bg-white rounded-lg shadow-sm p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-semibold text-gray-900">情绪趋势</h3>
|
||
<el-select v-model="emotionPeriod" size="small">
|
||
<el-option label="最近7天" value="7d" />
|
||
<el-option label="最近30天" value="30d" />
|
||
<el-option label="最近90天" value="90d" />
|
||
</el-select>
|
||
</div>
|
||
|
||
<div ref="emotionChartRef" class="h-64"></div>
|
||
</div>
|
||
|
||
<!-- 情绪分布图 -->
|
||
<div class="chart-card bg-white rounded-lg shadow-sm p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-semibold text-gray-900">情绪分布</h3>
|
||
<el-button size="small" @click="exportChart">
|
||
<el-icon class="mr-1"><Download /></el-icon>
|
||
导出
|
||
</el-button>
|
||
</div>
|
||
|
||
<div ref="emotionPieChartRef" class="h-64"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 活动和成就 -->
|
||
<div class="activity-section grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
<!-- 最近活动 -->
|
||
<div class="activity-card bg-white rounded-lg shadow-sm p-6 lg:col-span-2">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-semibold text-gray-900">最近活动</h3>
|
||
<el-link type="primary" @click="viewAllActivities">查看全部</el-link>
|
||
</div>
|
||
|
||
<div class="activity-list space-y-4">
|
||
<div
|
||
v-for="activity in recentActivities"
|
||
:key="activity.id"
|
||
class="activity-item flex items-start space-x-3 p-3 hover:bg-gray-50 rounded-lg transition-colors"
|
||
>
|
||
<div
|
||
class="w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0"
|
||
:style="{ backgroundColor: activity.color + '20' }"
|
||
>
|
||
<el-icon :size="16" :style="{ color: activity.color }">
|
||
<component :is="activity.icon" />
|
||
</el-icon>
|
||
</div>
|
||
|
||
<div class="flex-1 min-w-0">
|
||
<p class="text-sm text-gray-900">{{ activity.title }}</p>
|
||
<p class="text-xs text-gray-500">{{ formatTime(activity.time) }}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成就徽章 -->
|
||
<div class="achievements-card bg-white rounded-lg shadow-sm p-6">
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h3 class="text-lg font-semibold text-gray-900">成就徽章</h3>
|
||
<el-link type="primary" @click="viewAllAchievements">查看全部</el-link>
|
||
</div>
|
||
|
||
<div class="achievements-grid grid grid-cols-3 gap-3">
|
||
<div
|
||
v-for="achievement in recentAchievements"
|
||
:key="achievement.id"
|
||
class="achievement-item text-center p-3 hover:bg-gray-50 rounded-lg transition-colors cursor-pointer"
|
||
@click="viewAchievement(achievement)"
|
||
>
|
||
<div class="w-12 h-12 mx-auto mb-2 text-2xl">
|
||
{{ achievement.icon }}
|
||
</div>
|
||
<p class="text-xs text-gray-600 truncate">{{ achievement.name }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-4 text-center">
|
||
<p class="text-sm text-gray-500">
|
||
已获得 {{ totalAchievements }} 个成就
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, computed, onMounted, nextTick } from 'vue'
|
||
import { ElMessage } from 'element-plus'
|
||
import {
|
||
ArrowUp,
|
||
ArrowDown,
|
||
Download,
|
||
ChatDotRound,
|
||
EditPen,
|
||
TrendCharts,
|
||
Calendar
|
||
} from '@element-plus/icons-vue'
|
||
import * as echarts from 'echarts'
|
||
import { useAuthStore } from '@/stores/auth'
|
||
import { EMOTION_COLORS } from '@/config/constants'
|
||
import { formatRelativeTime } from '@/utils/format'
|
||
|
||
const authStore = useAuthStore()
|
||
|
||
// 响应式数据
|
||
const emotionPeriod = ref('7d')
|
||
const emotionChartRef = ref<HTMLElement>()
|
||
const emotionPieChartRef = ref<HTMLElement>()
|
||
let emotionChart: echarts.ECharts | null = null
|
||
let emotionPieChart: echarts.ECharts | null = null
|
||
|
||
// 计算属性
|
||
const userNickname = computed(() => authStore.nickname)
|
||
const totalDays = ref(45)
|
||
const todayMood = ref(7)
|
||
const totalAchievements = ref(12)
|
||
|
||
// 统计数据
|
||
const statsData = [
|
||
{
|
||
title: '总对话数',
|
||
value: '156',
|
||
trend: 'up',
|
||
trendValue: 12,
|
||
icon: ChatDotRound,
|
||
color: '#3b82f6'
|
||
},
|
||
{
|
||
title: '日记篇数',
|
||
value: '23',
|
||
trend: 'up',
|
||
trendValue: 8,
|
||
icon: EditPen,
|
||
color: '#10b981'
|
||
},
|
||
{
|
||
title: '平均心情',
|
||
value: '7.2',
|
||
trend: 'up',
|
||
trendValue: 5,
|
||
icon: TrendCharts,
|
||
color: '#f59e0b'
|
||
},
|
||
{
|
||
title: '连续天数',
|
||
value: '12',
|
||
trend: 'stable',
|
||
trendValue: 0,
|
||
icon: Calendar,
|
||
color: '#8b5cf6'
|
||
}
|
||
]
|
||
|
||
// 最近活动
|
||
const recentActivities = [
|
||
{
|
||
id: '1',
|
||
title: '发布了新的情绪日记《今天的心情》',
|
||
time: Date.now() - 1000 * 60 * 30,
|
||
icon: EditPen,
|
||
color: '#10b981'
|
||
},
|
||
{
|
||
id: '2',
|
||
title: '与AI助手进行了深度对话',
|
||
time: Date.now() - 1000 * 60 * 60 * 2,
|
||
icon: ChatDotRound,
|
||
color: '#3b82f6'
|
||
},
|
||
{
|
||
id: '3',
|
||
title: '获得了"连续记录7天"成就',
|
||
time: Date.now() - 1000 * 60 * 60 * 24,
|
||
icon: TrendCharts,
|
||
color: '#f59e0b'
|
||
}
|
||
]
|
||
|
||
// 最近成就
|
||
const recentAchievements = [
|
||
{ id: '1', name: '初次记录', icon: '🎉' },
|
||
{ id: '2', name: '连续7天', icon: '🔥' },
|
||
{ id: '3', name: '情绪专家', icon: '🎯' },
|
||
{ id: '4', name: '分享达人', icon: '📢' },
|
||
{ id: '5', name: '深度思考', icon: '🤔' },
|
||
{ id: '6', name: '积极向上', icon: '☀️' }
|
||
]
|
||
|
||
// 方法
|
||
const getGreeting = () => {
|
||
const hour = new Date().getHours()
|
||
if (hour < 12) return '早上好'
|
||
if (hour < 18) return '下午好'
|
||
return '晚上好'
|
||
}
|
||
|
||
const formatTime = (timestamp: number) => {
|
||
return formatRelativeTime(timestamp)
|
||
}
|
||
|
||
const initEmotionChart = () => {
|
||
if (!emotionChartRef.value) return
|
||
|
||
emotionChart = echarts.init(emotionChartRef.value)
|
||
|
||
const option = {
|
||
tooltip: {
|
||
trigger: 'axis',
|
||
axisPointer: {
|
||
type: 'cross'
|
||
}
|
||
},
|
||
grid: {
|
||
left: '3%',
|
||
right: '4%',
|
||
bottom: '3%',
|
||
containLabel: true
|
||
},
|
||
xAxis: {
|
||
type: 'category',
|
||
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||
},
|
||
yAxis: {
|
||
type: 'value',
|
||
min: 0,
|
||
max: 10
|
||
},
|
||
series: [
|
||
{
|
||
name: '心情指数',
|
||
type: 'line',
|
||
smooth: true,
|
||
data: [6, 7, 8, 6, 9, 7, 8],
|
||
itemStyle: {
|
||
color: '#3b82f6'
|
||
},
|
||
areaStyle: {
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{ offset: 0, color: 'rgba(59, 130, 246, 0.3)' },
|
||
{ offset: 1, color: 'rgba(59, 130, 246, 0.1)' }
|
||
])
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
emotionChart.setOption(option)
|
||
}
|
||
|
||
const initEmotionPieChart = () => {
|
||
if (!emotionPieChartRef.value) return
|
||
|
||
emotionPieChart = echarts.init(emotionPieChartRef.value)
|
||
|
||
const option = {
|
||
tooltip: {
|
||
trigger: 'item',
|
||
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||
},
|
||
legend: {
|
||
orient: 'vertical',
|
||
left: 'left'
|
||
},
|
||
series: [
|
||
{
|
||
name: '情绪分布',
|
||
type: 'pie',
|
||
radius: '50%',
|
||
data: [
|
||
{ value: 35, name: '开心', itemStyle: { color: EMOTION_COLORS.happy } },
|
||
{ value: 25, name: '平静', itemStyle: { color: EMOTION_COLORS.calm } },
|
||
{ value: 20, name: '兴奋', itemStyle: { color: EMOTION_COLORS.excited } },
|
||
{ value: 15, name: '焦虑', itemStyle: { color: EMOTION_COLORS.anxious } },
|
||
{ value: 5, name: '难过', itemStyle: { color: EMOTION_COLORS.sad } }
|
||
],
|
||
emphasis: {
|
||
itemStyle: {
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 0,
|
||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||
}
|
||
}
|
||
}
|
||
]
|
||
}
|
||
|
||
emotionPieChart.setOption(option)
|
||
}
|
||
|
||
const exportChart = () => {
|
||
ElMessage.info('图表导出功能开发中...')
|
||
}
|
||
|
||
const viewAllActivities = () => {
|
||
ElMessage.info('查看全部活动功能开发中...')
|
||
}
|
||
|
||
const viewAllAchievements = () => {
|
||
ElMessage.info('查看全部成就功能开发中...')
|
||
}
|
||
|
||
const viewAchievement = (achievement: any) => {
|
||
ElMessage.success(`查看成就:${achievement.name}`)
|
||
}
|
||
|
||
// 监听图表周期变化
|
||
watch(emotionPeriod, () => {
|
||
// 重新加载图表数据
|
||
if (emotionChart) {
|
||
// 这里可以根据周期加载不同的数据
|
||
emotionChart.setOption({
|
||
xAxis: {
|
||
data: emotionPeriod.value === '7d'
|
||
? ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
|
||
: ['第1周', '第2周', '第3周', '第4周']
|
||
},
|
||
series: [{
|
||
data: emotionPeriod.value === '7d'
|
||
? [6, 7, 8, 6, 9, 7, 8]
|
||
: [7.2, 6.8, 7.5, 8.1]
|
||
}]
|
||
})
|
||
}
|
||
})
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
initEmotionChart()
|
||
initEmotionPieChart()
|
||
})
|
||
})
|
||
|
||
// 响应式处理
|
||
onUnmounted(() => {
|
||
if (emotionChart) {
|
||
emotionChart.dispose()
|
||
}
|
||
if (emotionPieChart) {
|
||
emotionPieChart.dispose()
|
||
}
|
||
})
|
||
|
||
// 窗口大小变化时重新调整图表
|
||
window.addEventListener('resize', () => {
|
||
if (emotionChart) {
|
||
emotionChart.resize()
|
||
}
|
||
if (emotionPieChart) {
|
||
emotionPieChart.resize()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.stat-card {
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.stat-card:hover {
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.activity-item:hover {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.achievement-item:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
</style>
|