feat: 完成情绪博物馆项目重构和功能增强 - 新增日记评论和帖子功能 - 重构前端架构,优化用户体验 - 完善WebSocket通信机制 - 更新项目文档和部署配置
This commit is contained in:
+130
-569
@@ -1,590 +1,151 @@
|
||||
<template>
|
||||
<div class="messages-page">
|
||||
<!-- 头部 -->
|
||||
<header class="page-header">
|
||||
<div class="header-content">
|
||||
<div class="header-left">
|
||||
<a-button type="text" @click="$router.back()" class="back-btn">
|
||||
<ArrowLeftOutlined />
|
||||
</a-button>
|
||||
<h1 class="page-title">消息中心</h1>
|
||||
<div class="bg-light-gray font-sans text-text-dark">
|
||||
<div id="app-container" class="antialiased">
|
||||
<!-- App Header -->
|
||||
<header class="fixed top-0 left-0 right-0 z-40 bg-white/90 backdrop-blur-md border-b border-gray-200/80">
|
||||
<div class="container mx-auto px-4 h-16 flex items-center justify-between relative">
|
||||
<button @click="goBack" class="text-text-medium hover:text-tech-blue transition-colors">
|
||||
<i data-lucide="chevron-left" class="w-6 h-6"></i>
|
||||
</button>
|
||||
<h1 class="text-lg font-semibold text-text-dark absolute left-1/2 -translate-x-1/2">消息中心</h1>
|
||||
<router-link to="/" class="text-text-medium hover:text-tech-blue transition-colors">
|
||||
<i data-lucide="home" class="w-6 h-6"></i>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a-button type="text" @click="markAllAsRead" :disabled="unreadCount === 0">
|
||||
全部已读
|
||||
</a-button>
|
||||
<a-button type="text" @click="clearAllMessages" danger>
|
||||
<DeleteOutlined />
|
||||
清空
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<main class="page-main">
|
||||
<div class="container">
|
||||
<!-- 筛选标签 -->
|
||||
<div class="filter-tabs">
|
||||
<a-radio-group v-model:value="activeTab" @change="handleTabChange">
|
||||
<a-radio-button value="all">
|
||||
全部 <a-badge :count="messages.length" :show-zero="false" />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="unread">
|
||||
未读 <a-badge :count="unreadCount" :show-zero="false" />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="system">
|
||||
系统消息 <a-badge :count="systemCount" :show-zero="false" />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="notification">
|
||||
通知 <a-badge :count="notificationCount" :show-zero="false" />
|
||||
</a-radio-button>
|
||||
<a-radio-button value="reminder">
|
||||
提醒 <a-badge :count="reminderCount" :show-zero="false" />
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div class="messages-list">
|
||||
<div
|
||||
v-for="message in filteredMessages"
|
||||
:key="message.id"
|
||||
class="message-item"
|
||||
:class="{ 'unread': message.status === 'unread' }"
|
||||
@click="handleMessageClick(message)"
|
||||
>
|
||||
<div class="message-icon">
|
||||
<div class="icon-wrapper" :class="`type-${message.type}`">
|
||||
<component :is="getMessageIcon(message.type)" />
|
||||
<main class="pt-20 pb-8 bg-light-gray min-h-screen">
|
||||
<div class="container mx-auto px-6">
|
||||
<div id="message-list" class="max-w-3xl mx-auto space-y-4">
|
||||
<div
|
||||
v-for="(msg, index) in messages"
|
||||
:key="index"
|
||||
class="bg-white p-5 rounded-xl shadow-sm border border-gray-200/80 flex items-start space-x-4 hover:shadow-md hover:border-tech-blue/30 transition-all duration-300 animate-fade-in-up"
|
||||
:style="{ animationDelay: `${index * 0.1}s` }"
|
||||
>
|
||||
<div class="flex-shrink-0 w-10 h-10 rounded-full bg-light-gray flex items-center justify-center border">
|
||||
<i :data-lucide="msg.icon" class="w-5 h-5" :class="msg.color"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-content">
|
||||
<div class="message-header">
|
||||
<h3 class="message-title">{{ message.title }}</h3>
|
||||
<div class="message-meta">
|
||||
<a-tag :color="getTypeColor(message.type)" size="small">
|
||||
{{ getTypeText(message.type) }}
|
||||
</a-tag>
|
||||
<span class="message-time">{{ formatTime.friendly(message.createTime) }}</span>
|
||||
<div class="flex-grow">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="font-bold text-text-dark">{{ msg.title }}</h3>
|
||||
<span class="text-xs text-text-medium whitespace-nowrap">{{ msg.timestamp }}</span>
|
||||
</div>
|
||||
<p class="text-text-medium mt-1 pr-4">{{ msg.content }}</p>
|
||||
</div>
|
||||
|
||||
<p class="message-text">{{ message.content }}</p>
|
||||
|
||||
<div class="message-actions" v-if="message.actionUrl">
|
||||
<a-button type="link" size="small" @click.stop="handleAction(message)">
|
||||
查看详情
|
||||
<RightOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="message-controls">
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
@click.stop="toggleReadStatus(message)"
|
||||
:title="message.status === 'read' ? '标记为未读' : '标记为已读'"
|
||||
>
|
||||
<EyeOutlined v-if="message.status === 'unread'" />
|
||||
<EyeInvisibleOutlined v-else />
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="text"
|
||||
size="small"
|
||||
danger
|
||||
@click.stop="deleteMessage(message.id)"
|
||||
title="删除消息"
|
||||
>
|
||||
<DeleteOutlined />
|
||||
</a-button>
|
||||
<button class="flex-shrink-0 text-text-medium hover:text-tech-blue self-center">
|
||||
<i data-lucide="chevron-right" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="filteredMessages.length === 0" class="empty-state">
|
||||
<a-empty
|
||||
:description="getEmptyDescription()"
|
||||
:image="Empty.PRESENTED_IMAGE_SIMPLE"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<div class="load-more" v-if="hasMore">
|
||||
<a-button @click="loadMore" :loading="isLoading" block>
|
||||
加载更多
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- 消息详情模态框 -->
|
||||
<a-modal
|
||||
v-model:open="showDetailModal"
|
||||
:title="selectedMessage?.title"
|
||||
:footer="null"
|
||||
width="600px"
|
||||
>
|
||||
<div v-if="selectedMessage" class="message-detail">
|
||||
<div class="detail-header">
|
||||
<a-tag :color="getTypeColor(selectedMessage.type)">
|
||||
{{ getTypeText(selectedMessage.type) }}
|
||||
</a-tag>
|
||||
<span class="detail-time">{{ formatTime.standard(selectedMessage.createTime) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-content">
|
||||
<p>{{ selectedMessage.content }}</p>
|
||||
</div>
|
||||
|
||||
<div class="detail-actions" v-if="selectedMessage.actionUrl">
|
||||
<a-button type="primary" @click="handleAction(selectedMessage)">
|
||||
查看详情
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import {
|
||||
ArrowLeftOutlined,
|
||||
DeleteOutlined,
|
||||
RightOutlined,
|
||||
EyeOutlined,
|
||||
EyeInvisibleOutlined,
|
||||
BellOutlined,
|
||||
InfoCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { Empty, message } from 'ant-design-vue'
|
||||
import { formatTime } from '@/utils'
|
||||
import type { Message } from '@/types'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 响应式数据
|
||||
const activeTab = ref('all')
|
||||
const showDetailModal = ref(false)
|
||||
const selectedMessage = ref<Message | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const hasMore = ref(false)
|
||||
const router = useRouter()
|
||||
|
||||
// 消息数据
|
||||
const messages = ref<Message[]>([
|
||||
{
|
||||
id: '1',
|
||||
title: '欢迎使用开心APP',
|
||||
content: '感谢您注册开心APP!我是您的情绪陪伴使者开开,很高兴认识您。让我们一起开始这段美好的情绪陪伴之旅吧!',
|
||||
type: 'system',
|
||||
status: 'unread',
|
||||
createTime: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
|
||||
actionUrl: '/chat'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '每日心情记录提醒',
|
||||
content: '今天还没有记录心情哦~花几分钟写下今天的感受,让开开更好地了解您的情绪变化。',
|
||||
type: 'reminder',
|
||||
status: 'unread',
|
||||
createTime: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(),
|
||||
actionUrl: '/diary'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '新功能上线通知',
|
||||
content: '话题追踪功能已上线!现在您可以创建和管理感兴趣的话题,让开开帮您更好地整理思路。',
|
||||
type: 'notification',
|
||||
status: 'read',
|
||||
createTime: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
||||
actionUrl: '/topic-tracker'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '系统维护通知',
|
||||
content: '系统将于今晚23:00-24:00进行例行维护,期间可能会影响部分功能的使用,请您谅解。',
|
||||
type: 'system',
|
||||
status: 'read',
|
||||
createTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString()
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: '个人展板完善提醒',
|
||||
content: '完善您的个人展板信息,让开开更好地了解您的兴趣爱好和生活技能,提供更个性化的陪伴。',
|
||||
type: 'reminder',
|
||||
status: 'read',
|
||||
createTime: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
actionUrl: '/dashboard'
|
||||
// 消息数据
|
||||
const messages = ref([
|
||||
{
|
||||
type: 'ai',
|
||||
icon: 'sparkles',
|
||||
color: 'text-warm-orange',
|
||||
title: '开开的每周心情总结',
|
||||
content: '你好呀!上周我们聊了很多关于"新工作的挑战",你表现出了很棒的适应能力和积极心态。记得给自己一些放松的时间哦,比如看看你喜欢的电影。',
|
||||
timestamp: '2025年7月15日 09:30'
|
||||
},
|
||||
{
|
||||
type: 'system',
|
||||
icon: 'bell',
|
||||
color: 'text-tech-blue',
|
||||
title: '系统通知:欢迎使用日记功能',
|
||||
content: '现在,你可以在日记区记录下你的生活点滴,开开会阅读你的日记并给你温暖的回复和鼓励哦。',
|
||||
timestamp: '2025年7月14日 18:00'
|
||||
},
|
||||
{
|
||||
type: 'ai',
|
||||
icon: 'sparkles',
|
||||
color: 'text-warm-orange',
|
||||
title: '开开的话题追踪提醒',
|
||||
content: '我发现你最近经常提到"学吉他",我已经为你创建了一个话题追踪卡片,帮你记录学习进度和心得。一起加油吧!',
|
||||
timestamp: '2025年7月12日 11:25'
|
||||
},
|
||||
{
|
||||
type: 'system',
|
||||
icon: 'award',
|
||||
color: 'text-green-500',
|
||||
title: '成就解锁:初次见面',
|
||||
content: '恭喜你完成了与开开的第一次对话,这是共同成长的第一步。',
|
||||
timestamp: '2025年7月10日 20:45'
|
||||
}
|
||||
])
|
||||
|
||||
// 返回上一页
|
||||
const goBack = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
// 初始化Lucide图标
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
|
||||
// 延迟初始化图标
|
||||
setTimeout(() => {
|
||||
if (window.lucide) {
|
||||
window.lucide.createIcons()
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const filteredMessages = computed(() => {
|
||||
switch (activeTab.value) {
|
||||
case 'unread':
|
||||
return messages.value.filter(msg => msg.status === 'unread')
|
||||
case 'system':
|
||||
return messages.value.filter(msg => msg.type === 'system')
|
||||
case 'notification':
|
||||
return messages.value.filter(msg => msg.type === 'notification')
|
||||
case 'reminder':
|
||||
return messages.value.filter(msg => msg.type === 'reminder')
|
||||
default:
|
||||
return messages.value
|
||||
}
|
||||
})
|
||||
|
||||
const unreadCount = computed(() =>
|
||||
messages.value.filter(msg => msg.status === 'unread').length
|
||||
)
|
||||
|
||||
const systemCount = computed(() =>
|
||||
messages.value.filter(msg => msg.type === 'system').length
|
||||
)
|
||||
|
||||
const notificationCount = computed(() =>
|
||||
messages.value.filter(msg => msg.type === 'notification').length
|
||||
)
|
||||
|
||||
const reminderCount = computed(() =>
|
||||
messages.value.filter(msg => msg.type === 'reminder').length
|
||||
)
|
||||
|
||||
// 方法
|
||||
const getMessageIcon = (type: Message['type']) => {
|
||||
const icons = {
|
||||
system: SettingOutlined,
|
||||
notification: BellOutlined,
|
||||
reminder: ClockCircleOutlined
|
||||
}
|
||||
return icons[type] || InfoCircleOutlined
|
||||
}
|
||||
|
||||
const getTypeColor = (type: Message['type']) => {
|
||||
const colors = {
|
||||
system: 'blue',
|
||||
notification: 'green',
|
||||
reminder: 'orange'
|
||||
}
|
||||
return colors[type] || 'default'
|
||||
}
|
||||
|
||||
const getTypeText = (type: Message['type']) => {
|
||||
const texts = {
|
||||
system: '系统消息',
|
||||
notification: '通知',
|
||||
reminder: '提醒'
|
||||
}
|
||||
return texts[type] || type
|
||||
}
|
||||
|
||||
const getEmptyDescription = () => {
|
||||
switch (activeTab.value) {
|
||||
case 'unread':
|
||||
return '暂无未读消息'
|
||||
case 'system':
|
||||
return '暂无系统消息'
|
||||
case 'notification':
|
||||
return '暂无通知消息'
|
||||
case 'reminder':
|
||||
return '暂无提醒消息'
|
||||
default:
|
||||
return '暂无消息'
|
||||
}
|
||||
}
|
||||
|
||||
const handleTabChange = () => {
|
||||
// 标签切换逻辑已在计算属性中处理
|
||||
}
|
||||
|
||||
const handleMessageClick = (msg: Message) => {
|
||||
// 标记为已读
|
||||
if (msg.status === 'unread') {
|
||||
msg.status = 'read'
|
||||
}
|
||||
|
||||
// 显示详情
|
||||
selectedMessage.value = msg
|
||||
showDetailModal.value = true
|
||||
}
|
||||
|
||||
const toggleReadStatus = (msg: Message) => {
|
||||
msg.status = msg.status === 'read' ? 'unread' : 'read'
|
||||
message.success(`已${msg.status === 'read' ? '标记为已读' : '标记为未读'}`)
|
||||
}
|
||||
|
||||
const deleteMessage = (id: string) => {
|
||||
const index = messages.value.findIndex(msg => msg.id === id)
|
||||
if (index > -1) {
|
||||
messages.value.splice(index, 1)
|
||||
message.success('消息删除成功')
|
||||
}
|
||||
}
|
||||
|
||||
const markAllAsRead = () => {
|
||||
messages.value.forEach(msg => {
|
||||
if (msg.status === 'unread') {
|
||||
msg.status = 'read'
|
||||
}
|
||||
})
|
||||
message.success('所有消息已标记为已读')
|
||||
}
|
||||
|
||||
const clearAllMessages = () => {
|
||||
messages.value.length = 0
|
||||
message.success('所有消息已清空')
|
||||
}
|
||||
|
||||
const handleAction = (msg: Message) => {
|
||||
if (msg.actionUrl) {
|
||||
// 这里可以使用路由跳转
|
||||
message.info(`跳转到:${msg.actionUrl}`)
|
||||
}
|
||||
}
|
||||
|
||||
const loadMore = () => {
|
||||
isLoading.value = true
|
||||
// 模拟加载更多
|
||||
setTimeout(() => {
|
||||
isLoading.value = false
|
||||
hasMore.value = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
// 初始化消息数据
|
||||
})
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use "@/assets/styles/variables.scss" as *;
|
||||
.messages-page {
|
||||
min-height: 100vh;
|
||||
background: $light-gray;
|
||||
<style scoped>
|
||||
/* 导入原始样式变量 */
|
||||
:root {
|
||||
--tech-blue: #4A90E2;
|
||||
--warm-orange: #F5A623;
|
||||
--white: #FFFFFF;
|
||||
--light-gray: #F7F8FA;
|
||||
--text-dark: #333333;
|
||||
--text-medium: #888888;
|
||||
}
|
||||
|
||||
/* 应用原始样式类 */
|
||||
.bg-tech-blue { background-color: var(--tech-blue); }
|
||||
.bg-warm-orange { background-color: var(--warm-orange); }
|
||||
.bg-light-gray { background-color: var(--light-gray); }
|
||||
.text-tech-blue { color: var(--tech-blue); }
|
||||
.text-warm-orange { color: var(--warm-orange); }
|
||||
.text-text-dark { color: var(--text-dark); }
|
||||
.text-text-medium { color: var(--text-medium); }
|
||||
.border-tech-blue { border-color: var(--tech-blue); }
|
||||
|
||||
.animate-fade-in-up {
|
||||
animation: fade-in-up 0.8s ease-out forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
background: white;
|
||||
box-shadow: $shadow-sm;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: $spacing-md $spacing-lg;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
color: $text-medium;
|
||||
|
||||
&:hover {
|
||||
color: $tech-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
.page-main {
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
:deep(.ant-radio-group) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $spacing-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.messages-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
background: white;
|
||||
border-radius: $border-radius-lg;
|
||||
padding: $spacing-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
cursor: pointer;
|
||||
transition: all $transition-normal;
|
||||
border-left: 4px solid transparent;
|
||||
|
||||
&:hover {
|
||||
box-shadow: $shadow-md;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&.unread {
|
||||
border-left-color: $tech-blue;
|
||||
background: linear-gradient(90deg, rgba(74, 144, 226, 0.02) 0%, white 100%);
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
flex-shrink: 0;
|
||||
|
||||
.icon-wrapper {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: $font-size-lg;
|
||||
|
||||
&.type-system {
|
||||
background: #1890ff;
|
||||
}
|
||||
|
||||
&.type-notification {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
&.type-reminder {
|
||||
background: #fa8c16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.message-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: $spacing-sm;
|
||||
gap: $spacing-md;
|
||||
|
||||
.message-title {
|
||||
font-size: $font-size-base;
|
||||
font-weight: $font-weight-semibold;
|
||||
color: $text-dark;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.message-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
flex-shrink: 0;
|
||||
|
||||
.message-time {
|
||||
font-size: $font-size-sm;
|
||||
color: $text-medium;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-text {
|
||||
color: $text-medium;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 $spacing-sm 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-actions {
|
||||
.ant-btn-link {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.message-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-xs;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: $spacing-xxl;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
margin-top: $spacing-xl;
|
||||
}
|
||||
|
||||
// 消息详情模态框
|
||||
.message-detail {
|
||||
.detail-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-lg;
|
||||
padding-bottom: $spacing-md;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.detail-time {
|
||||
color: $text-medium;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
p {
|
||||
color: $text-dark;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-actions {
|
||||
padding-top: $spacing-md;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
/* 全局样式 */
|
||||
body {
|
||||
font-family: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user