feat: 增强情绪博物馆项目功能 - 新增用户评论和帖子功能,优化前端架构和WebSocket通信 - 更新文档和部署配置

This commit is contained in:
2025-07-29 07:38:47 +08:00
parent cc886cd4d5
commit 2f3d39fb00
142 changed files with 45645 additions and 0 deletions
@@ -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>