feat: 实现情绪记录功能和聊天历史查看

- 完成情绪记录生成功能,支持AI分析聊天内容生成情绪记录
- 实现聊天页面历史记录查看,支持分页和搜索
- 修改日记页面展示情绪记录而非普通日记
- 添加情绪记录的增删改查API
- 优化前端UI,添加情绪强度显示和详细信息展示
- 修复SCSS变量缺失问题
This commit is contained in:
2025-07-25 01:11:01 +08:00
parent 3292a74698
commit 86c2df4784
25 changed files with 1397 additions and 2210 deletions
+2
View File
@@ -5,6 +5,7 @@ $white: #FFFFFF;
$light-gray: #F7F8FA;
$text-dark: #333333;
$text-medium: #888888;
$border-color: #e8e8e8;
// 间距
$spacing-xs: 4px;
@@ -37,6 +38,7 @@ $breakpoint-xxl: 1536px;
// 字体大小
$font-size-xs: 12px;
$font-size-sm: 14px;
$font-size-md: 16px; // 添加缺失的 md 尺寸
$font-size-base: 16px;
$font-size-lg: 18px;
$font-size-xl: 20px;
+403 -12
View File
@@ -34,6 +34,15 @@
<a-button type="text" @click="showHistory = true" class="action-btn">
<HistoryOutlined />
</a-button>
<a-button
type="text"
@click="generateEmotionSummary"
class="action-btn emotion-btn"
:loading="emotionSummaryLoading"
:title="'生成今日情绪记录'"
>
<HeartOutlined />
</a-button>
</div>
</div>
</header>
@@ -136,7 +145,9 @@
v-model:open="showHistory"
title="聊天记录"
placement="right"
:width="320"
:width="400"
class="history-drawer"
@open="loadHistoryMessages(1)"
>
<div class="history-content">
<div class="search-section">
@@ -144,33 +155,124 @@
v-model:value="searchKeyword"
placeholder="搜索关键词..."
class="search-input"
@press-enter="searchHistoryMessages"
>
<template #prefix>
<SearchOutlined />
</template>
<template #suffix>
<a-button type="text" size="small" @click="searchHistoryMessages" :loading="historyLoading">
搜索
</a-button>
</template>
</a-input>
<a-date-picker
v-model:value="searchDate"
placeholder="按日期查询"
class="date-picker"
style="width: 100%; margin-top: 12px;"
@change="loadHistoryMessages(1)"
/>
</div>
<div class="history-messages">
<div class="history-messages" v-if="!historyLoading || historyMessages.length > 0">
<div
v-for="message in filteredMessages"
:key="message.id"
class="history-message"
:class="{ 'user': message.type === 'user' }"
:class="{ 'user': message.sender === 'user' }"
>
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ formatTime.standard(message.timestamp) }}</div>
<div class="message-time">{{ formatTime.standard(message.createTime) }}</div>
</div>
<!-- 加载更多按钮 -->
<div v-if="historyPagination.current * historyPagination.pageSize < historyPagination.total" class="load-more">
<a-button
type="dashed"
block
@click="loadHistoryMessages(historyPagination.current + 1)"
:loading="historyLoading"
>
加载更多 ({{ historyMessages.length }}/{{ historyPagination.total }})
</a-button>
</div>
<!-- 没有更多数据提示 -->
<div v-else-if="historyMessages.length > 0" class="no-more">
<a-divider>已显示全部记录</a-divider>
</div>
</div>
<!-- 加载状态 -->
<div v-if="historyLoading && historyMessages.length === 0" class="loading-state">
<a-spin size="large" tip="加载中..." />
</div>
<!-- 空状态 -->
<div v-if="!historyLoading && historyMessages.length === 0" class="empty-state">
<a-empty description="暂无聊天记录" />
</div>
</div>
</a-drawer>
<!-- 情绪记录结果模态框 -->
<a-modal
v-model:open="showEmotionResult"
title="今日情绪记录"
:footer="null"
width="600px"
class="emotion-result-modal"
>
<div v-if="emotionResult" class="emotion-result-content">
<div class="emotion-header">
<div class="emotion-icon">
<HeartOutlined />
</div>
<div class="emotion-info">
<h3 class="emotion-type">{{ emotionResult.emotionRecord?.emotionType || '平静' }}</h3>
<div class="emotion-intensity">
<span>情绪强度: </span>
<a-progress
:percent="Math.round((emotionResult.emotionRecord?.intensity || 0.5) * 100)"
:stroke-color="getEmotionColor(emotionResult.emotionRecord?.intensity || 0.5)"
:show-info="true"
size="small"
/>
</div>
</div>
</div>
<div class="emotion-details">
<div class="detail-item">
<h4>触发因素</h4>
<p>{{ emotionResult.emotionRecord?.triggers || '日常对话' }}</p>
</div>
<div class="detail-item">
<h4>AI分析总结</h4>
<div class="summary-content">
{{ emotionResult.summary || '暂无分析总结' }}
</div>
</div>
<div class="detail-item">
<h4>记录信息</h4>
<div class="record-meta">
<p><strong>记录日期:</strong> {{ formatDate(emotionResult.recordDate) }}</p>
<p><strong>分析消息数:</strong> {{ emotionResult.messageCount || 0 }} </p>
</div>
</div>
</div>
<div class="emotion-actions">
<a-button type="primary" @click="showEmotionResult = false">
知道了
</a-button>
</div>
</div>
</a-modal>
</div>
</template>
@@ -181,6 +283,7 @@
HistoryOutlined,
SendOutlined,
SearchOutlined,
HeartOutlined,
} from '@ant-design/icons-vue'
import { useChatStore } from '@/stores'
import { formatTime } from '@/utils'
@@ -194,30 +297,40 @@
const searchKeyword = ref('')
const searchDate = ref<Dayjs | null>(null)
const chatMainRef = ref<HTMLElement>()
const emotionSummaryLoading = ref(false)
const showEmotionResult = ref(false)
const emotionResult = ref<any>(null)
const historyMessages = ref<any[]>([])
const historyLoading = ref(false)
const historyPagination = ref({
current: 1,
pageSize: 20,
total: 0
})
// 开开头像
const kaikaiAvatar = 'https://r2.flowith.net/files/o/1752574406770-thoughtful_kaikai_character_generation_index_1@1024x1024.png'
// 计算属性
const filteredMessages = computed(() => {
let messages = chatStore.messages
let messages = historyMessages.value
// 关键词搜索
if (searchKeyword.value) {
messages = messages.filter(msg =>
messages = messages.filter(msg =>
msg.content.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
}
// 日期筛选
if (searchDate.value) {
const targetDate = searchDate.value.format('YYYY-MM-DD')
messages = messages.filter(msg => {
const msgDate = new Date(msg.timestamp).toISOString().split('T')[0]
const msgDate = new Date(msg.createTime).toISOString().split('T')[0]
return msgDate === targetDate
})
}
return messages
})
@@ -267,6 +380,158 @@
})
}
// 生成情绪记录总结
const generateEmotionSummary = async () => {
if (emotionSummaryLoading.value) return
try {
emotionSummaryLoading.value = true
// 获取当前用户ID(这里需要根据实际的用户管理方式获取)
const userId = chatStore.currentSession?.userId || 'default_user'
// 调用后端API生成情绪记录
const response = await fetch(`/api/emotion-summary/generate/${userId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
if (result.success) {
// 显示成功消息
const emotionRecord = result.data.emotionRecord
const summary = result.data.summary
// 可以显示一个模态框或通知来展示情绪记录结果
showEmotionSummaryResult(result.data)
} else {
console.error('生成情绪记录失败:', result.message)
// 显示错误提示
alert(result.message || '生成情绪记录失败,请稍后再试')
}
} catch (error) {
console.error('生成情绪记录时发生错误:', error)
alert('生成情绪记录失败,请检查网络连接')
} finally {
emotionSummaryLoading.value = false
}
}
// 显示情绪记录结果
const showEmotionSummaryResult = (data: any) => {
emotionResult.value = {
emotionRecord: data.emotionRecord,
summary: data.summary,
recordDate: data.recordDate || new Date(),
messageCount: data.messageCount || 0
}
showEmotionResult.value = true
}
// 获取情绪颜色
const getEmotionColor = (intensity: number) => {
if (intensity >= 0.8) return '#ff4d4f' // 高强度 - 红色
if (intensity >= 0.6) return '#ff7a45' // 中高强度 - 橙红色
if (intensity >= 0.4) return '#ffa940' // 中等强度 - 橙色
if (intensity >= 0.2) return '#52c41a' // 低强度 - 绿色
return '#1890ff' // 很低强度 - 蓝色
}
// 格式化日期
const formatDate = (date: Date | string) => {
const d = new Date(date)
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
}
// 加载历史记录
const loadHistoryMessages = async (page = 1) => {
if (historyLoading.value) return
try {
historyLoading.value = true
// 获取当前用户ID(这里需要根据实际的用户管理方式获取)
const userId = chatStore.currentSession?.userId || 'default_user'
const response = await fetch(`/message/user/${userId}/page?current=${page}&size=${historyPagination.value.pageSize}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
if (result.success) {
const pageData = result.data
if (page === 1) {
historyMessages.value = pageData.records || []
} else {
historyMessages.value.push(...(pageData.records || []))
}
historyPagination.value = {
current: pageData.current || 1,
pageSize: pageData.size || 20,
total: pageData.total || 0
}
console.log('历史记录加载成功:', historyMessages.value.length, '条')
} else {
console.error('加载历史记录失败:', result.message)
}
} catch (error) {
console.error('加载历史记录时发生错误:', error)
} finally {
historyLoading.value = false
}
}
// 搜索历史记录
const searchHistoryMessages = async () => {
if (!searchKeyword.value.trim()) {
await loadHistoryMessages(1)
return
}
try {
historyLoading.value = true
const userId = chatStore.currentSession?.userId || 'default_user'
const response = await fetch(`/message/user/${userId}/search?keyword=${encodeURIComponent(searchKeyword.value)}&limit=100`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
if (result.success) {
historyMessages.value = result.data || []
console.log('搜索历史记录成功:', historyMessages.value.length, '条')
} else {
console.error('搜索历史记录失败:', result.message)
}
} catch (error) {
console.error('搜索历史记录时发生错误:', error)
} finally {
historyLoading.value = false
}
}
// 监听消息变化,自动滚动到底部
watch(
() => chatStore.messages.length,
@@ -376,12 +641,29 @@
}
.header-right {
display: flex;
align-items: center;
gap: $spacing-sm;
.action-btn {
color: $text-medium;
&:hover {
color: $tech-blue;
}
&.emotion-btn {
color: #ff6b6b;
&:hover {
color: #ff5252;
background-color: rgba(255, 107, 107, 0.1);
}
&:focus {
color: #ff5252;
}
}
}
}
@@ -635,5 +917,114 @@
color: $text-medium;
}
}
.load-more {
margin-top: $spacing-md;
}
.no-more {
margin-top: $spacing-md;
text-align: center;
}
.loading-state,
.empty-state {
display: flex;
justify-content: center;
align-items: center;
padding: $spacing-xxl;
text-align: center;
}
}
// 情绪记录模态框样式
:deep(.emotion-result-modal) {
.ant-modal-content {
border-radius: $border-radius-lg;
}
.emotion-result-content {
.emotion-header {
display: flex;
align-items: center;
gap: $spacing-lg;
margin-bottom: $spacing-xl;
padding: $spacing-lg;
background: linear-gradient(135deg, #ff6b6b, #ff8e8e);
border-radius: $border-radius-lg;
color: white;
.emotion-icon {
font-size: 2rem;
opacity: 0.9;
}
.emotion-info {
flex: 1;
.emotion-type {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
margin: 0 0 $spacing-sm 0;
}
.emotion-intensity {
display: flex;
align-items: center;
gap: $spacing-sm;
span {
font-size: $font-size-sm;
opacity: 0.9;
}
}
}
}
.emotion-details {
.detail-item {
margin-bottom: $spacing-lg;
h4 {
color: $text-dark;
font-weight: $font-weight-medium;
margin-bottom: $spacing-sm;
font-size: $font-size-md;
}
p {
color: $text-medium;
line-height: 1.6;
margin: 0;
}
.summary-content {
background: $light-gray;
padding: $spacing-md;
border-radius: $border-radius-md;
color: $text-medium;
line-height: 1.6;
white-space: pre-wrap;
}
.record-meta {
p {
margin-bottom: $spacing-xs;
strong {
color: $text-dark;
}
}
}
}
}
.emotion-actions {
text-align: center;
margin-top: $spacing-xl;
padding-top: $spacing-lg;
border-top: 1px solid $border-color;
}
}
}
</style>
+388 -163
View File
@@ -7,11 +7,11 @@
<a-button type="text" @click="$router.back()" class="back-btn">
<ArrowLeftOutlined />
</a-button>
<h1 class="page-title">情绪</h1>
<h1 class="page-title">情绪记</h1>
</div>
<a-button type="primary" @click="showNewEntryModal = true" class="new-entry-btn">
<PlusOutlined />
写日记
<a-button type="primary" @click="$router.push('/chat')" class="new-entry-btn">
<HeartOutlined />
生成情绪记录
</a-button>
</div>
</header>
@@ -19,36 +19,41 @@
<!-- 主要内容 -->
<main class="page-main">
<div class="container">
<!-- 快速写日记卡片 -->
<div class="quick-entry-section">
<a-card class="quick-entry-card" @click="showNewEntryModal = true">
<div class="quick-entry-content">
<div class="quick-entry-text">
<span class="placeholder-text">记录今天的心情...</span>
<!-- 提示卡片 -->
<div class="tip-section">
<a-card class="tip-card">
<div class="tip-content">
<HeartOutlined class="tip-icon" />
<div class="tip-text">
<h3>如何生成情绪记录</h3>
<p>与开开聊天后点击聊天页面右上角的 按钮AI会分析你的聊天内容并生成情绪记录</p>
</div>
<a-button type="primary" size="small" class="quick-btn">
<PlusOutlined />
<a-button type="primary" @click="$router.push('/chat')" class="tip-btn">
去聊天
</a-button>
</div>
</a-card>
</div>
<!-- 日记列表 -->
<div class="diary-feed">
<!-- 情绪记录列表 -->
<div class="emotion-feed">
<div
v-for="entry in diaryStore.entries"
:key="entry.id"
class="diary-entry"
v-for="record in emotionRecords"
:key="record.id"
class="emotion-entry"
>
<a-card class="entry-card">
<div class="entry-header">
<div class="entry-meta">
<span class="entry-mood" v-if="entry.mood">
{{ getMoodEmoji(entry.mood) }}
</span>
<span class="entry-date">
{{ formatTime.friendly(entry.createTime) }}
<span class="emotion-icon">
{{ getEmotionIcon(record.emotionType) }}
</span>
<div class="emotion-info">
<span class="emotion-type">{{ record.emotionType }}</span>
<span class="emotion-date">
{{ formatTime.friendly(record.createTime) }}
</span>
</div>
</div>
<a-dropdown>
<a-button type="text" size="small">
@@ -56,11 +61,7 @@
</a-button>
<template #overlay>
<a-menu>
<a-menu-item @click="editEntry(entry)">
<EditOutlined />
编辑
</a-menu-item>
<a-menu-item @click="deleteEntry(entry.id)" danger>
<a-menu-item @click="deleteEmotionRecord(record.id)" danger>
<DeleteOutlined />
删除
</a-menu-item>
@@ -68,122 +69,113 @@
</template>
</a-dropdown>
</div>
<div class="entry-content">
<p class="entry-text">{{ entry.content }}</p>
<div class="entry-tags" v-if="entry.tags && entry.tags.length">
<a-tag
v-for="tag in entry.tags"
:key="tag"
color="blue"
class="entry-tag"
>
{{ tag }}
</a-tag>
</div>
</div>
<!-- AI回复 -->
<div class="ai-reply" v-if="entry.aiReply">
<div class="ai-avatar">
<a-avatar
:src="kaikaiAvatar"
:size="32"
<!-- 情绪强度 -->
<div class="emotion-intensity">
<span class="intensity-label">情绪强度:</span>
<a-progress
:percent="Math.round((record.intensity || 0) * 100)"
:stroke-color="getIntensityColor(record.intensity || 0)"
:show-info="true"
size="small"
class="intensity-bar"
/>
</div>
<div class="ai-content">
<div class="ai-name">开开的回复</div>
<p class="ai-text">{{ entry.aiReply }}</p>
<!-- 触发因素 -->
<div v-if="record.triggers" class="emotion-triggers">
<span class="triggers-label">触发因素:</span>
<span class="triggers-text">{{ record.triggers }}</span>
</div>
<!-- 描述 -->
<div v-if="record.description" class="emotion-description">
<p class="description-text">{{ record.description }}</p>
</div>
<!-- 标签 -->
<div class="emotion-tags" v-if="record.tags">
<a-tag
v-for="tag in (typeof record.tags === 'string' ? record.tags.split(',') : record.tags)"
:key="tag"
color="blue"
class="emotion-tag"
>
{{ tag.trim() }}
</a-tag>
</div>
<!-- 其他信息 -->
<div class="emotion-details">
<div v-if="record.weather" class="detail-item">
<span class="detail-label">天气:</span>
<span class="detail-value">{{ record.weather }}</span>
</div>
<div v-if="record.location" class="detail-item">
<span class="detail-label">地点:</span>
<span class="detail-value">{{ record.location }}</span>
</div>
<div v-if="record.activity" class="detail-item">
<span class="detail-label">活动:</span>
<span class="detail-value">{{ record.activity }}</span>
</div>
</div>
</div>
</a-card>
</div>
<!-- 加载更多按钮 -->
<div v-if="pagination.current * pagination.pageSize < pagination.total" class="load-more">
<a-button
type="dashed"
block
@click="loadMoreRecords"
:loading="loading"
size="large"
>
加载更多 ({{ emotionRecords.length }}/{{ pagination.total }})
</a-button>
</div>
<!-- 没有更多数据提示 -->
<div v-else-if="emotionRecords.length > 0" class="no-more">
<a-divider>已显示全部记录</a-divider>
</div>
<!-- 空状态 -->
<div v-if="diaryStore.entries.length === 0 && !diaryStore.isLoading" class="empty-state">
<div v-if="emotionRecords.length === 0 && !loading" class="empty-state">
<a-empty
description="还没有日记记录"
description="还没有情绪记录"
:image="Empty.PRESENTED_IMAGE_SIMPLE"
>
<a-button type="primary" @click="showNewEntryModal = true">
写第一篇日记
</a-button>
<p class="empty-tip">
与开开聊天后点击右上角的 <HeartOutlined /> 按钮生成情绪记录
</p>
</a-empty>
</div>
<!-- 加载状态 -->
<div v-if="diaryStore.isLoading" class="loading-state">
<a-spin size="large" />
<div v-if="loading && emotionRecords.length === 0" class="loading-state">
<a-spin size="large" tip="加载中..." />
</div>
</div>
</div>
</main>
<!-- 新日记模态框 -->
<a-modal
v-model:open="showNewEntryModal"
title="写日记"
:width="600"
@ok="publishEntry"
@cancel="resetNewEntry"
:confirm-loading="diaryStore.isLoading"
:ok-button-props="{ disabled: !newEntryContent.trim() }"
>
<div class="modal-content">
<a-textarea
v-model:value="newEntryContent"
placeholder="今天有什么新鲜事或心里话想对开开说?"
:rows="6"
class="modal-textarea"
/>
<div class="modal-options">
<div class="mood-selector">
<span class="mood-label">心情</span>
<a-radio-group v-model:value="selectedMood" class="mood-options">
<a-radio-button value="happy">😊</a-radio-button>
<a-radio-button value="sad">😢</a-radio-button>
<a-radio-button value="neutral">😐</a-radio-button>
<a-radio-button value="excited">🤩</a-radio-button>
<a-radio-button value="tired">😴</a-radio-button>
</a-radio-group>
</div>
<div class="tags-input">
<span class="tags-label">标签</span>
<a-input
v-model:value="newTagInput"
placeholder="添加标签,按回车确认"
@press-enter="addTag"
class="tag-input"
/>
<div class="selected-tags" v-if="selectedTags.length">
<a-tag
v-for="tag in selectedTags"
:key="tag"
closable
@close="removeTag(tag)"
color="blue"
>
{{ tag }}
</a-tag>
</div>
</div>
</div>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed } from 'vue'
import {
ArrowLeftOutlined,
PlusOutlined,
MoreOutlined,
EditOutlined,
DeleteOutlined,
HeartOutlined,
} from '@ant-design/icons-vue'
import { Empty, message } from 'ant-design-vue'
import { useDiaryStore } from '@/stores'
@@ -198,6 +190,13 @@
const selectedMood = ref<string>('neutral')
const selectedTags = ref<string[]>([])
const newTagInput = ref('')
const emotionRecords = ref<any[]>([])
const loading = ref(false)
const pagination = ref({
current: 1,
pageSize: 10,
total: 0
})
// 开开头像
const kaikaiAvatar = 'https://r2.flowith.net/files/o/1752574406770-thoughtful_kaikai_character_generation_index_1@1024x1024.png'
@@ -273,9 +272,142 @@
}
}
// 加载情绪记录
const loadEmotionRecords = async (page = 1, append = false) => {
if (loading.value) return
try {
loading.value = true
// 获取当前用户ID(这里需要根据实际的用户管理方式获取)
const userId = 'default_user' // 这里应该从用户状态中获取
const response = await fetch(`/api/emotion-records/user/${userId}?current=${page}&size=${pagination.value.pageSize}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
if (result.success) {
const pageData = result.data
if (append) {
emotionRecords.value.push(...(pageData.records || []))
} else {
emotionRecords.value = pageData.records || []
}
pagination.value = {
current: pageData.current || 1,
pageSize: pageData.size || 10,
total: pageData.total || 0
}
console.log('情绪记录加载成功:', emotionRecords.value.length, '条')
} else {
console.error('加载情绪记录失败:', result.message)
message.error(result.message || '加载情绪记录失败')
}
} catch (error) {
console.error('加载情绪记录时发生错误:', error)
message.error('加载情绪记录失败,请检查网络连接')
} finally {
loading.value = false
}
}
// 加载更多情绪记录
const loadMoreRecords = () => {
if (pagination.value.current * pagination.value.pageSize < pagination.value.total) {
loadEmotionRecords(pagination.value.current + 1, true)
}
}
// 删除情绪记录
const deleteEmotionRecord = async (id: string) => {
try {
const response = await fetch(`/api/emotion-records/${id}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
})
const result = await response.json()
if (result.success) {
message.success('情绪记录删除成功')
// 重新加载第一页
await loadEmotionRecords(1)
} else {
message.error(result.message || '删除失败')
}
} catch (error) {
console.error('删除情绪记录时发生错误:', error)
message.error('删除失败,请重试')
}
}
// 获取情绪图标
const getEmotionIcon = (emotionType: string) => {
const emotionIcons: Record<string, string> = {
'joy': '😊',
'happiness': '😊',
'happy': '😊',
'excited': '🤩',
'love': '😍',
'sadness': '😢',
'sad': '😢',
'crying': '😭',
'anger': '😠',
'angry': '😡',
'rage': '🤬',
'fear': '😨',
'scared': '😰',
'anxiety': '😰',
'surprise': '😲',
'shocked': '😱',
'neutral': '😐',
'calm': '😌',
'peaceful': '😌',
'tired': '😴',
'exhausted': '😵',
'confused': '😕',
'disappointed': '😞',
'frustrated': '😤',
'bored': '😑',
'content': '😊',
'grateful': '🙏',
'hopeful': '🌟',
'proud': '😎',
'embarrassed': '😳',
'guilty': '😔',
'lonely': '😞',
'nostalgic': '🥺',
'optimistic': '😄',
'pessimistic': '😟'
}
return emotionIcons[emotionType.toLowerCase()] || '😐'
}
// 获取情绪强度颜色
const getIntensityColor = (intensity: number) => {
if (intensity >= 0.8) return '#ff4d4f' // 高强度 - 红色
if (intensity >= 0.6) return '#ff7a45' // 中高强度 - 橙红色
if (intensity >= 0.4) return '#ffa940' // 中等强度 - 橙色
if (intensity >= 0.2) return '#52c41a' // 低强度 - 绿色
return '#1890ff' // 很低强度 - 蓝色
}
// 组件挂载
onMounted(() => {
diaryStore.loadEntries()
loadEmotionRecords(1)
})
</script>
@@ -336,95 +468,179 @@
margin: 0 auto;
}
.new-entry-section {
.tip-section {
margin-bottom: $spacing-xl;
}
.new-entry-card {
.card-title {
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
color: $text-dark;
margin-bottom: $spacing-md;
}
.entry-textarea {
margin-bottom: $spacing-md;
}
.entry-actions {
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: $spacing-md;
flex-wrap: wrap;
}
.mood-selector {
.tip-card {
.tip-content {
display: flex;
align-items: center;
gap: $spacing-sm;
flex-wrap: wrap;
}
.mood-label {
font-weight: $font-weight-medium;
color: $text-dark;
gap: $spacing-md;
.tip-icon {
font-size: 2rem;
color: #ff6b6b;
}
.tip-text {
flex: 1;
h3 {
margin: 0 0 $spacing-xs 0;
color: $text-dark;
font-size: $font-size-md;
}
p {
margin: 0;
color: $text-medium;
font-size: $font-size-sm;
}
}
.tip-btn {
border-radius: $border-radius-full;
}
}
}
.diary-feed {
.emotion-feed {
display: flex;
flex-direction: column;
gap: $spacing-lg;
}
.diary-entry {
.emotion-entry {
.entry-card {
transition: all $transition-normal;
&:hover {
box-shadow: $shadow-md;
}
}
.entry-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-md;
}
.entry-meta {
display: flex;
align-items: center;
gap: $spacing-sm;
gap: $spacing-md;
}
.entry-mood {
font-size: $font-size-lg;
.emotion-icon {
font-size: 2rem;
}
.entry-date {
.emotion-info {
display: flex;
flex-direction: column;
gap: $spacing-xs;
}
.emotion-type {
font-weight: $font-weight-medium;
color: $text-dark;
font-size: $font-size-md;
text-transform: capitalize;
}
.emotion-date {
color: $text-medium;
font-size: $font-size-sm;
}
.entry-content {
margin-bottom: $spacing-md;
}
.entry-text {
line-height: 1.6;
color: $text-dark;
margin-bottom: $spacing-sm;
.emotion-intensity {
display: flex;
align-items: center;
gap: $spacing-sm;
margin-bottom: $spacing-md;
.intensity-label {
font-weight: $font-weight-medium;
color: $text-dark;
min-width: 80px;
}
.intensity-bar {
flex: 1;
}
}
.entry-tags {
.emotion-triggers {
margin-bottom: $spacing-md;
.triggers-label {
font-weight: $font-weight-medium;
color: $text-dark;
margin-right: $spacing-sm;
}
.triggers-text {
color: $text-medium;
}
}
.emotion-description {
margin-bottom: $spacing-md;
.description-text {
line-height: 1.6;
color: $text-dark;
margin: 0;
padding: $spacing-sm;
background: $light-gray;
border-radius: $border-radius-md;
}
}
.emotion-tags {
display: flex;
flex-wrap: wrap;
gap: $spacing-xs;
margin-bottom: $spacing-md;
}
.emotion-details {
display: flex;
flex-wrap: wrap;
gap: $spacing-md;
.detail-item {
display: flex;
align-items: center;
gap: $spacing-xs;
.detail-label {
font-weight: $font-weight-medium;
color: $text-dark;
font-size: $font-size-sm;
}
.detail-value {
color: $text-medium;
font-size: $font-size-sm;
}
}
}
}
.load-more {
margin-top: $spacing-lg;
}
.no-more {
margin-top: $spacing-lg;
text-align: center;
}
.ai-reply {
@@ -456,9 +672,18 @@
.empty-state,
.loading-state {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: $spacing-xxl;
text-align: center;
.empty-tip {
margin-top: $spacing-md;
color: $text-medium;
font-size: $font-size-sm;
line-height: 1.5;
}
}
// 模态框样式