feat: 增强情绪博物馆项目功能 - 新增用户评论和帖子功能,优化前端架构和WebSocket通信 - 更新文档和部署配置
This commit is contained in:
@@ -0,0 +1,440 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user