Files
medical-mall/pages/mall/consumer/messages.uvue

1330 lines
30 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="messages-page">
<!-- 智能顶部导航栏 - 与主页保持一致 -->
<view class="smart-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-container">
<text class="nav-title">消息中心</text>
<view class="nav-actions">
<view class="action-btn" @click="clearAllUnread">
<text class="action-icon">🧹</text>
<text class="action-text">一键已读</text>
</view>
</view>
</view>
</view>
<!-- 导航栏占位符 -->
<view class="navbar-placeholder" :style="{ height: (statusBarHeight + 10) + 'px' }"></view>
<!-- 消息分类标签 - 固定在顶部,方便随时切换 -->
<view class="tabs-container">
<view class="message-tabs">
<view
v-for="tab in messageTabs"
:key="tab.id"
:class="['tab-item', { active: activeTab === tab.id }]"
@click="switchTab(tab.id)"
>
<text class="tab-name">{{ tab.name }}</text>
<text v-if="tab.unread > 0" class="tab-badge">{{ tab.unread > 99 ? '99+' : tab.unread }}</text>
</view>
</view>
</view>
<!-- 消息列表内容区 -->
<scroll-view
scroll-y
class="messages-content"
refresher-enabled
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
:scroll-top="scrollTop"
>
<!-- 客服消息 -->
<view v-if="activeTab === 'service'" class="message-section">
<!-- 在线客服卡片 (hidden) -->
<!-- <view class="customer-service-info">
<view class="service-header">
<text class="service-title">康乐医药在线客服</text>
<text class="service-status online">在线</text>
</view>
<text class="service-desc">专业医药顾问在线解答,服务时间 9:00-22:00</text>
<view class="service-categories">
<view class="category-item" @click="startQuickService('用药咨询')">
<text class="category-icon">💊</text>
<text class="category-name">用药咨询</text>
</view>
<view class="category-item" @click="startQuickService('处方咨询')">
<text class="category-icon">📋</text>
<text class="category-name">处方咨询</text>
</view>
<view class="category-item" @click="startQuickService('副作用咨询')">
<text class="category-icon">⚠️</text>
<text class="category-name">副作用咨询</text>
</view>
<view class="category-item" @click="startQuickService('药品配送')">
<text class="category-icon">🚚</text>
<text class="category-name">药品配送</text>
</view>
</view>
</view> -->
<!-- 客服消息列表 -->
<view
v-for="message in serviceMessages"
:key="message.id"
:class="['message-item', { unread: !message.read, active: message.active }]"
@click="startChatWithService(message)"
>
<view class="message-icon-wrapper">
<image
v-if="message.avatar"
class="message-avatar"
:src="message.avatar"
mode="aspectFill"
/>
<view v-else class="message-icon-default" :style="{ backgroundColor: message.color }">
<text>{{ message.icon }}</text>
</view>
<view v-if="message.online" class="online-dot"></view>
</view>
<view class="message-content">
<view class="message-header">
<view class="message-title-wrapper">
<text class="message-title">{{ message.title }}</text>
<text v-if="message.role" class="message-role">{{ message.role }}</text>
</view>
<view class="message-header-right">
<text class="message-time">{{ message.time }}</text>
<text v-if="message.unreadCount > 0" class="message-unread-count">{{ message.unreadCount }}</text>
</view>
</view>
<view class="message-preview-wrapper">
<text class="message-preview">{{ message.content }}</text>
<text v-if="message.lastMessage" class="last-message">{{ message.lastMessage }}</text>
</view>
<view v-if="message.tags" class="message-tags">
<text v-for="tag in message.tags" :key="tag" class="message-tag">{{ tag }}</text>
</view>
</view>
</view>
<!-- 客服系统提示 -->
<view class="service-tips">
<text class="tip-icon">💡</text>
<text class="tip-text">温馨提示:请勿相信任何要求转账、付款的信息,谨防诈骗</text>
</view>
</view>
<!-- 系统通知 -->
<view v-if="activeTab === 'system'" class="message-section">
<view
v-for="message in systemMessages"
:key="message.id"
:class="['message-item', { unread: !message.read }]"
@click="viewSystemMessage(message)"
>
<view class="message-icon-wrapper">
<text class="message-icon">📢</text>
</view>
<view class="message-content">
<view class="message-header">
<text class="message-title">{{ message.title }}</text>
<text class="message-time">{{ message.time }}</text>
</view>
<text class="message-preview">{{ message.content }}</text>
<view v-if="message.important" class="important-tag">重要</view>
</view>
</view>
</view>
<!-- 订单消息 -->
<view v-if="activeTab === 'order'" class="message-section">
<view
v-for="message in orderMessages"
:key="message.id"
:class="['message-item', { unread: !message.read }]"
@click="viewOrderMessage(message)"
>
<view class="message-icon-wrapper">
<text class="message-icon">📦</text>
</view>
<view class="message-content">
<view class="message-header">
<text class="message-title">{{ message.title }}</text>
<text class="message-time">{{ message.time }}</text>
</view>
<text class="message-preview">{{ message.content }}</text>
<text class="order-info" v-if="message.order_no">订单号: {{ message.order_no }}</text>
<view v-if="message.status" class="order-status" :class="message.status">
{{ message.statusText }}
</view>
</view>
</view>
</view>
<!-- 优惠活动 -->
<view v-if="activeTab === 'promo'" class="message-section">
<view
v-for="message in promoMessages"
:key="message.id"
:class="['message-item', { unread: !message.read }]"
@click="viewPromoMessage(message)"
>
<view class="message-icon-wrapper">
<text class="message-icon">🎁</text>
</view>
<view class="message-content">
<view class="message-header">
<text class="message-title">{{ message.title }}</text>
<text class="message-time">{{ message.time }}</text>
</view>
<text class="message-preview">{{ message.content }}</text>
<view v-if="message.coupon" class="coupon-info" @click.stop="claimCoupon(message)">
<text class="coupon-text">{{ message.coupon }}优惠券</text>
<text class="coupon-expiry">有效期至 {{ message.expiry }}</text>
<text class="coupon-action">{{ message.claimed ? '已领取' : '点击领取' }}</text>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="!loading && currentMessages.length === 0 && activeTab !== 'service'" class="empty-messages">
<text class="empty-icon">💬</text>
<text class="empty-title">暂无消息</text>
<text class="empty-desc">暂时没有新消息</text>
</view>
<!-- 底部安全区域 -->
<view class="safe-area"></view>
</scroll-view>
<!-- 底部固定按钮 -->
<view class="floating-action">
<button class="action-button" @click="startNewChat">
<text class="button-icon">✏️</text>
<text class="button-text">新建聊天</text>
</button>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { supabaseService, type Notification, type ChatMessage } from '@/utils/supabaseService.uts'
// 响应式数据
const activeTab = ref<string>('service')
const refreshing = ref<boolean>(false)
const loading = ref<boolean>(false)
const unreadCount = ref<number>(12)
const statusBarHeight = ref(0)
const scrollTop = ref(0)
const scrollHeight = ref(0)
// 初始化页面布局数据
const initPage = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight || 0
// 计算滚动区域高度:屏幕高度 - 状态栏 - 导航栏(44) - 标签栏(42)
const windowHeight = systemInfo.windowHeight
scrollHeight.value = windowHeight - statusBarHeight.value - 44 - 42
}
// 消息分类标签
const messageTabs = reactive([
{ id: 'service', name: '客服消息', unread: 5 },
{ id: 'system', name: '系统通知', unread: 3 },
{ id: 'order', name: '订单消息', unread: 2 },
{ id: 'promo', name: '优惠活动', unread: 2 }
])
// Mock 客服消息数据
const serviceMessages = reactive<any[]>([])
const systemMessages = reactive<any[]>([])
const orderMessages = reactive<any[]>([])
// Mock 优惠活动数据
const promoMessages = reactive<any[]>([])
// 计算当前显示的消息
const currentMessages = computed(() => {
switch (activeTab.value) {
case 'system': return systemMessages
case 'order': return orderMessages
case 'service': return serviceMessages
case 'promo': return promoMessages
default: return []
}
})
// 生命周期
onMounted(() => {
console.log('Messages Page Mounted')
initPage()
// loadMessages() // 移至 onShow 调用
})
onShow(() => {
console.log('Messages Page Show')
loadMessages()
})
// 简单的日期格式化
const formatTime = (isoString: string): string => {
if (!isoString) return ''
try {
return isoString.split('T')[0]
} catch(e) {
return isoString
}
}
// 加载消息
const loadMessages = async () => {
loading.value = true
try {
// 清空现有Mock数据
serviceMessages.length = 0
systemMessages.length = 0
orderMessages.length = 0
promoMessages.length = 0
// 1. 获取通知 (系统、订单、优惠)
const notes = await supabaseService.getUserNotifications()
notes.forEach((note: Notification) => {
// 这里使用 any 类型构建对象,以匹配 reactive 数组的结构
const item = {
id: note.id,
title: note.title,
content: note.content,
time: formatTime(note.created_at || ''),
read: note.is_read,
type: note.type, // 'system', 'order', 'promotion' => 'promo'
// 默认填充字段以避免渲染报错
avatar: note.icon_url,
important: note.type === 'system', // 简单逻辑
coupon: '点击查看',
expiry: '',
claimed: false,
order_no: '',
status: '',
statusText: '',
role: '',
lastMessage: '',
online: false,
unreadCount: 0,
tags: [],
icon: '',
color: ''
}
if (note.type === 'system') {
systemMessages.push(item)
} else if (note.type === 'order') {
orderMessages.push(item)
} else if (note.type === 'promotion') {
// map type 'promotion' to 'promo' for tab
item.type = 'promo'
promoMessages.push(item)
}
})
// 2. 获取客服消息 (Chat)
const chats = await supabaseService.getUserChatMessages()
// console.log('Raw chats:', chats)
if (chats.length > 0) {
const currentUserId = supabaseService.getCurrentUserId()
const conversations = new Map<string, any>()
// 1. Group by conversation partner
for (const msg of chats) {
const partnerId = (msg.sender_id == currentUserId) ? msg.receiver_id : msg.sender_id
// Skip if partner is null/invalid
if (!partnerId) continue;
if (!conversations.has(partnerId)) {
conversations.set(partnerId, {
partnerId: partnerId,
lastMessage: msg,
unreadCount: 0
})
}
const conv = conversations.get(partnerId)
// Since chats are likely sorted desc, the first one seen is the latest.
// Just count unread: if I am the receiver and it's not read
if (msg.receiver_id == currentUserId && !msg.is_read) {
conv.unreadCount++
}
}
console.log('Conversations found:', conversations.size)
// 2. Fetch shop details for each conversation
const convList = Array.from(conversations.values())
const promises = convList.map(async (conv) => {
const shop = await supabaseService.getShopByMerchantId(conv.partnerId)
const shopName = shop ? shop.shop_name : '未知商家'
const shopAvatar = (shop && shop.logo_url) ? shop.logo_url : '/static/icons/shop-default.png'
return {
id: conv.partnerId, // Use partnerId as the ID for navigation
title: shopName,
role: '商家客服',
content: conv.lastMessage.content,
lastMessage: conv.lastMessage.content,
time: formatTime(conv.lastMessage.created_at || ''),
read: conv.unreadCount === 0,
type: 'service',
avatar: shopAvatar,
online: true,
unreadCount: conv.unreadCount,
tags: shop ? ['官方认证'] : [],
icon: '🏪',
color: '#FF9800',
important: false,
coupon: '',
expiry: '',
claimed: false,
order_no: '',
status: '',
statusText: ''
}
})
const renderedMessages = await Promise.all(promises)
serviceMessages.push(...renderedMessages)
}
// 如果没有消息,为了演示效果(或者真的需要),可以保留一个默认的系统客服
if (serviceMessages.length === 0) {
serviceMessages.push({
id: 'default_service',
title: '平台客服',
role: '智能助手',
content: '有问题请随时联系我们',
lastMessage: '欢迎咨询',
time: '刚刚',
read: true,
type: 'service',
avatar: '/static/icons/service-avatar.png',
online: true,
unreadCount: 0,
tags: ['自动回复'],
icon: '🤖',
color: '#2196F3',
important: false,
coupon: '',
expiry: '',
claimed: false,
order_no: '',
status: '',
statusText: ''
})
}
} catch (e) {
console.error('加载消息失败', e)
} finally {
updateUnreadCount()
loading.value = false
}
}
// 更新未读数量
const updateUnreadCount = () => {
let totalUnread = 0
const serviceUnread = serviceMessages.filter(msg => !msg.read).length
messageTabs[0].unread = serviceUnread
totalUnread += serviceUnread
const systemUnread = systemMessages.filter(msg => !msg.read).length
messageTabs[1].unread = systemUnread
totalUnread += systemUnread
const orderUnread = orderMessages.filter(msg => !msg.read).length
messageTabs[2].unread = orderUnread
totalUnread += orderUnread
const promoUnread = promoMessages.filter(msg => !msg.read).length
messageTabs[3].unread = promoUnread
totalUnread += promoUnread
unreadCount.value = totalUnread
}
// 切换标签
const switchTab = (tabId: string) => {
activeTab.value = tabId
// 切换标签时回到顶部,使用微小变化触发滚动更新
scrollTop.value = scrollTop.value === 0 ? 0.01 : 0
}
// 开始与客服聊天
const startChatWithService = (message: any) => {
message.read = true
message.unreadCount = 0
updateUnreadCount()
// 这里的 message.id 已经被我们修改为 conversation partner (merchantId)
// 所以参数传递需要调整
const merchantId = message.id === 'default_service' ? '' : message.id
uni.navigateTo({
url: `/pages/mall/consumer/chat?merchantId=${merchantId}&merchantName=${encodeURIComponent(message.title)}`
})
}
// 快速开始服务
const startQuickService = (category: string) => {
uni.navigateTo({
url: `/pages/mall/consumer/chat?category=${encodeURIComponent(category)}`
})
}
// 新建聊天
const startNewChat = () => {
uni.showActionSheet({
itemList: ['用药咨询', '处方咨询', '副作用咨询', '药品配送', '其他问题'],
success: (res) => {
const categories = ['用药咨询', '处方咨询', '副作用咨询', '药品配送', '其他问题']
const category = categories[res.tapIndex]
startQuickService(category)
}
})
}
// 查看系统消息
const viewSystemMessage = (message: any) => {
message.read = true
updateUnreadCount()
uni.navigateTo({
url: `/pages/mall/consumer/message-detail?id=${message.id}&type=system`
})
}
// 查看订单消息
const viewOrderMessage = (message: any) => {
message.read = true
updateUnreadCount()
uni.navigateTo({
url: `/pages/mall/consumer/order-detail?id=${message.order_no}`
})
}
// 查看优惠活动
const viewPromoMessage = (message: any) => {
message.read = true
updateUnreadCount()
uni.navigateTo({
url: `/pages/mall/consumer/coupons`
})
}
// 领取优惠券
const claimCoupon = (message: any) => {
if (message.claimed) {
uni.showToast({
title: '您已领取该优惠券',
icon: 'none'
})
return
}
message.claimed = true
// 保存领取状态到本地存储,供个人页读取
const claimedCouponsCount = uni.getStorageSync('claimedCoupons') || 0
uni.setStorageSync('claimedCoupons', (claimedCouponsCount as number) + 1)
// 保存详细的优惠券信息到 myCoupons 列表
const myCoupons = uni.getStorageSync('myCoupons')
let couponsList: any[] = []
if (myCoupons) {
try {
couponsList = JSON.parse(myCoupons as string) as any[]
} catch (e) {
console.error('Failed to parse myCoupons', e)
}
}
couponsList.push({
title: message.title,
amount: message.coupon,
expiry: message.expiry,
id: message.id
})
uni.setStorageSync('myCoupons', JSON.stringify(couponsList))
uni.showToast({
title: '领取成功',
icon: 'success'
})
}
// 清除所有未读
const clearAllUnread = () => {
uni.showModal({
title: '确认操作',
content: '确定要标记所有消息为已读吗?',
success: (res) => {
if (res.confirm) {
serviceMessages.forEach(msg => {
msg.read = true
msg.unreadCount = 0
})
systemMessages.forEach(msg => msg.read = true)
orderMessages.forEach(msg => msg.read = true)
promoMessages.forEach(msg => msg.read = true)
messageTabs.forEach(tab => tab.unread = 0)
unreadCount.value = 0
uni.showToast({
title: '已标记所有消息为已读',
icon: 'success'
})
}
}
})
}
// 下拉刷新
const onRefresh = () => {
refreshing.value = true
setTimeout(() => {
loadMessages()
refreshing.value = false
uni.showToast({
title: '刷新成功',
icon: 'success'
})
}, 1000)
}
</script>
<style>
/* 页面结构优化 - 避免双滚动条 */
.messages-page {
width: 100%;
height: 100vh;
background-color: #f8fafc;
display: flex;
flex-direction: column;
overflow: hidden; /* 关键防止body滚动 */
}
/* 智能导航栏 */
.smart-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 100%);
z-index: 1000;
box-shadow: 0 2px 12px rgba(76, 175, 80, 0.15);
/* height: 50px; 移除固定高度,由内容决定 */
display: flex;
flex-direction: row; /* 显式设置行方向 */
align-items: center;
justify-content: center;
flex-shrink: 0; /* 防止被压缩 */
}
.nav-container {
padding: 0 16px;
display: flex;
flex-direction: row; /* 关键修复UVUE默认是column必须显式设置为row才能横向排列 */
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 1400px;
margin: 0 auto;
height: 44px; /* 调整为标准高度 44px */
}
.nav-title {
font-size: 18px;
font-weight: bold;
color: white;
flex: 1; /* 自适应宽度 */
}
.nav-actions {
display: flex;
flex-direction: row; /* 显式设置行方向 */
align-items: center;
}
.action-btn {
display: flex;
flex-direction: row; /* 显式设置行方向 */
align-items: center;
background: rgba(255, 255, 255, 0.2);
padding: 4px 12px;
border-radius: 20px;
cursor: pointer;
transition: all 0.2s ease;
}
.action-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.action-icon {
font-size: 14px;
margin-right: 4px;
}
.action-text {
font-size: 12px;
color: white;
font-weight: 500;
}
/* 导航栏占位符 */
.navbar-placeholder {
width: 100%;
flex-shrink: 0;
}
/* 消息分类标签容器 */
.tabs-container {
background: white;
padding: 0 10px;
border-bottom: 1px solid #e0e0e0;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
z-index: 900;
height: 42px; /* 减小高度,更紧凑 */
flex-shrink: 0;
}
.message-tabs {
display: flex;
flex-direction: row; /* 横向排列 */
height: 100%;
/* overflow-x: auto; 移除自动滚动,改为自适应宽度 */
max-width: 1400px;
margin: 0 auto;
gap: 4px; /* 减小间距 */
justify-content: space-between; /* 均匀分布 */
}
.message-tabs::-webkit-scrollbar {
display: none;
}
.tab-item {
padding: 0 4px;
display: flex; /* 改为 flex 布局 */
flex-direction: row; /* 关键:横向排列 文字和数字 */
align-items: center; /* 垂直居中 */
justify-content: center;
position: relative;
height: 100%;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
white-space: nowrap; /* 防止换行 */
flex: 1; /* 关键:均分宽度,消除滚动条 */
min-width: 0; /* 允许压缩 */
}
.tab-item.active {
color: #4CAF50;
border-bottom-color: #4CAF50;
font-weight: bold;
}
.tab-name {
font-size: 14px; /* 微调字体大小适配小屏 */
overflow: hidden;
text-overflow: ellipsis;
}
/* 徽标样式优化 - 放在文字右边 */
.tab-badge {
background-color: #FF5722;
color: white;
font-size: 10px;
padding: 1px 5px;
border-radius: 10px;
min-width: 16px;
text-align: center;
font-weight: bold;
margin-left: 4px; /* 距离文字的间距 */
display: flex;
align-items: center;
justify-content: center;
height: 16px;
}
/* 消息内容区 */
.messages-content {
flex: 1; /* 关键:自适应剩余高度 */
height: 0; /* 关键强制flex item计算高度 */
width: 100%;
max-width: 1400px;
margin: 0 auto;
padding-bottom: 20px;
}
/* 客服信息区域 */
.customer-service-info {
background: white;
border-radius: 12px;
padding: 20px;
margin: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.service-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.service-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.service-status {
font-size: 12px;
padding: 4px 10px;
border-radius: 12px;
font-weight: 500;
}
.service-status.online {
background: #E8F5E9;
color: #4CAF50;
}
.service-desc {
font-size: 14px;
color: #666;
line-height: 1.5;
margin-bottom: 20px;
}
.service-categories {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.category-item {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: all 0.3s ease;
}
.category-item:hover {
background: #e8f5e9;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.2);
}
.category-icon {
font-size: 24px;
margin-bottom: 8px;
}
.category-name {
font-size: 13px;
color: #333;
font-weight: 500;
}
/* 消息项 */
.message-section {
padding: 10px;
}
.message-item {
background-color: white;
border-radius: 12px;
padding: 15px;
margin-bottom: 10px;
display: flex;
align-items: flex-start;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
cursor: pointer;
}
.message-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
.message-item.unread {
background-color: #f0f9f0;
border-left: 3px solid #4CAF50;
}
.message-item.active {
border: 1px solid #4CAF50;
}
.message-icon-wrapper {
width: 50px;
height: 50px;
border-radius: 25px;
background-color: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
flex-shrink: 0;
position: relative;
}
.message-icon-default {
width: 100%;
height: 100%;
border-radius: 25px;
display: flex;
align-items: center;
justify-content: center;
}
.message-icon-default text {
font-size: 24px;
color: white;
}
.message-avatar {
width: 100%;
height: 100%;
border-radius: 25px;
}
.online-dot {
position: absolute;
bottom: 2px;
right: 2px;
width: 12px;
height: 12px;
background-color: #4CAF50;
border-radius: 6px;
border: 2px solid white;
}
.message-content {
flex: 1;
min-width: 0;
}
.message-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.message-title-wrapper {
flex: 1;
min-width: 0;
margin-right: 10px;
}
.message-title {
font-size: 16px;
color: #333;
font-weight: bold;
display: block;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.message-role {
font-size: 12px;
color: #4CAF50;
background: #E8F5E9;
padding: 2px 8px;
border-radius: 10px;
display: inline-block;
}
.message-header-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.message-time {
font-size: 12px;
color: #999;
white-space: nowrap;
}
.message-unread-count {
font-size: 11px;
color: white;
background: #FF5722;
padding: 2px 6px;
border-radius: 10px;
min-width: 18px;
text-align: center;
font-weight: bold;
}
.message-preview-wrapper {
margin-bottom: 8px;
}
.message-preview {
font-size: 14px;
color: #666;
line-height: 1.4;
margin-bottom: 4px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.last-message {
font-size: 13px;
color: #999;
display: block;
}
.message-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.message-tag {
font-size: 11px;
color: #666;
background: #f0f0f0;
padding: 3px 8px;
border-radius: 10px;
}
.order-info {
font-size: 12px;
color: #4CAF50;
background-color: #E8F5E9;
padding: 4px 10px;
border-radius: 4px;
display: inline-block;
margin-top: 8px;
}
.order-status {
display: inline-block;
font-size: 12px;
padding: 4px 10px;
border-radius: 12px;
margin-top: 8px;
margin-left: 8px;
}
.order-status.shipping {
background: #E3F2FD;
color: #2196F3;
}
.order-status.processing {
background: #FFF3E0;
color: #FF9800;
}
.order-status.completed {
background: #E8F5E9;
color: #4CAF50;
}
.important-tag {
display: inline-block;
background-color: #FF5722;
color: white;
font-size: 11px;
padding: 3px 8px;
border-radius: 10px;
margin-top: 8px;
}
.coupon-info {
background: linear-gradient(135deg, #FF9800, #FF5722);
border-radius: 8px;
padding: 10px;
margin-top: 8px;
color: white;
}
.coupon-text {
font-size: 14px;
font-weight: bold;
display: block;
margin-bottom: 4px;
}
.coupon-expiry {
font-size: 12px;
opacity: 0.9;
}
.coupon-action {
font-size: 12px;
color: #fff;
background-color: rgba(255, 255, 255, 0.2);
padding: 2px 8px;
border-radius: 10px;
margin-top: 4px;
align-self: flex-start;
}
/* 客服系统提示 */
.service-tips {
background: #FFF3E0;
border-radius: 10px;
padding: 15px;
margin: 15px;
display: flex;
align-items: flex-start;
gap: 10px;
}
.tip-icon {
font-size: 18px;
color: #FF9800;
flex-shrink: 0;
margin-top: 2px;
}
.tip-text {
font-size: 13px;
color: #666;
line-height: 1.5;
}
/* 空状态 */
.empty-messages {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 20px;
text-align: center;
}
.empty-icon {
font-size: 80px;
color: #ddd;
margin-bottom: 20px;
}
.empty-title {
font-size: 18px;
color: #666;
margin-bottom: 10px;
}
.empty-desc {
font-size: 14px;
color: #999;
}
/* 底部浮动按钮 */
.floating-action {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 100;
}
.action-button {
background: linear-gradient(135deg, #4CAF50, #2E7D32);
color: white;
border: none;
border-radius: 25px;
padding: 12px 20px;
display: flex;
align-items: center;
box-shadow: 0 4px 12px rgba(76, 175, 80, 0.3);
font-weight: 500;
}
.button-icon {
font-size: 18px;
margin-right: 8px;
}
.button-text {
font-size: 14px;
font-weight: bold;
}
.safe-area {
height: 80px;
}
/* 响应式适配 */
@media screen and (max-width: 320px) {
.tab-name {
font-size: 13px;
}
.service-categories {
grid-template-columns: 1fr;
}
}
@media screen and (min-width: 415px) {
.message-item {
padding: 20px;
}
.message-icon-wrapper {
width: 60px;
height: 60px;
border-radius: 30px;
}
.message-icon-default text {
font-size: 28px;
}
.customer-service-info {
padding: 25px;
}
.service-categories {
grid-template-columns: repeat(4, 1fr);
}
/* 平板和桌面端优化标签栏显示 */
.message-tabs {
justify-content: flex-start; /* 左对齐 */
}
.tab-item {
padding: 0 24px; /* 增加点击区域 */
}
}
/* 平板设备 */
@media screen and (min-width: 768px) {
.smart-navbar {
padding: 0 30px;
}
.tabs-container {
padding: 0 30px;
}
.message-section {
padding: 20px 30px;
}
.customer-service-info {
margin: 20px 30px;
}
.service-tips {
margin: 20px 30px;
}
}
/* 暗黑模式适配 */
@media (prefers-color-scheme: dark) {
.messages-page {
background-color: #121212;
}
.smart-navbar {
background: linear-gradient(135deg, #2E7D32 0%, #1B5E20 100%);
}
.tabs-container {
background-color: #1e1e1e;
border-bottom-color: #333;
}
.tab-item.active {
color: #4CAF50;
}
.customer-service-info,
.message-item,
.service-tips {
background-color: #1e1e1e;
}
.message-item.unread {
background-color: #2d2d2d;
}
.category-item {
background: #2d2d2d;
}
.category-name {
color: #fff;
}
.service-title,
.message-title {
color: #fff;
}
.service-desc,
.message-preview,
.last-message,
.tip-text {
color: #aaa;
}
.message-icon-wrapper {
background-color: #2d2d2d;
}
.order-info {
background-color: #1b5e20;
color: #a5d6a7;
}
.coupon-info {
background: linear-gradient(135deg, #ff8f00, #ef6c00);
}
.message-tag {
background: #333;
color: #ccc;
}
.action-btn {
background: rgba(255, 255, 255, 0.1);
}
.action-btn:hover {
background: rgba(255, 255, 255, 0.2);
}
}
</style>