feat: 实现情绪记录功能和聊天历史查看
- 完成情绪记录生成功能,支持AI分析聊天内容生成情绪记录 - 实现聊天页面历史记录查看,支持分页和搜索 - 修改日记页面展示情绪记录而非普通日记 - 添加情绪记录的增删改查API - 优化前端UI,添加情绪强度显示和详细信息展示 - 修复SCSS变量缺失问题
This commit is contained in:
+403
-12
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user