| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973 |
- <template>
- <view class="matchmaker-message">
- <!-- 顶部导航栏 -->
- <view class="header">
- <view class="back-btn" @click="goBack"></view>
- <text class="header-title">消息</text>
- <view class="placeholder"></view>
- </view>
- <scroll-view scroll-y class="content">
- <!-- 加载中 -->
- <view v-if="loading" class="loading-container">
- <text>加载中...</text>
- </view>
-
- <!-- 消息内容 -->
- <view v-else-if="conversationList.length > 0">
- <!-- 系统通知卡片 -->
- <view class="message-item system-notification" v-if="systemNotification" @click="openSystemMessages">
- <text class="message-type">{{ systemNotification.title }}</text>
- <text class="message-time">{{ systemNotification.timeText }}</text>
- <text class="message-content">{{ systemNotification.content }}</text>
- <text class="message-footer">{{ systemNotification.footer }}</text>
- </view>
-
- <!-- 撮合成功通知卡片 -->
- <view class="message-item match-success" v-if="matchSuccessNotification">
- <view class="message-icon heart"></view>
- <view class="message-body">
- <text class="message-type">{{ matchSuccessNotification.title }}</text>
- <text class="message-content">{{ matchSuccessNotification.content }}</text>
- </view>
- <text class="message-time">{{ matchSuccessNotification.timeText }}</text>
- </view>
-
- <!-- 今天 -->
- <view v-if="todayConversations.length > 0">
- <view class="time-group">
- <text class="time-label">今天</text>
- </view>
- <view
- v-for="conversation in todayConversations"
- :key="conversation.conversationID"
- class="message-item user-message"
- @click="openChat(conversation)"
- >
- <image
- v-if="conversation.userProfile.avatar"
- :src="conversation.userProfile.avatar"
- class="message-avatar-img"
- />
- <view v-else class="message-avatar">
- {{ conversation.userProfile.nick || conversation.userProfile.userID.charAt(0) }}
- </view>
- <view class="message-body">
- <text class="message-type">{{ conversation.userProfile.nick || conversation.userProfile.userID }}</text>
- <text class="message-content">{{ getLastMessageText(conversation.lastMessage) }}</text>
- </view>
- <view class="message-right">
- <text class="message-time">{{ formatTime(conversation.lastMessage.lastTime) }}</text>
- <view v-if="conversation.unreadCount > 0" class="unread-badge">
- {{ conversation.unreadCount > 99 ? '99+' : conversation.unreadCount }}
- </view>
- </view>
- </view>
- </view>
-
- <!-- 昨日 -->
- <view v-if="yesterdayConversations.length > 0">
- <view class="time-group">
- <text class="time-label">昨日</text>
- </view>
- <view
- v-for="conversation in yesterdayConversations"
- :key="conversation.conversationID"
- class="message-item user-message"
- @click="openChat(conversation)"
- >
- <image
- v-if="conversation.userProfile.avatar"
- :src="conversation.userProfile.avatar"
- class="message-avatar-img"
- />
- <view v-else class="message-avatar">
- {{ conversation.userProfile.nick || conversation.userProfile.userID.charAt(0) }}
- </view>
- <view class="message-body">
- <text class="message-type">{{ conversation.userProfile.nick || conversation.userProfile.userID }}</text>
- <text class="message-content">{{ getLastMessageText(conversation.lastMessage) }}</text>
- </view>
- <view class="message-right">
- <text class="message-time">{{ formatTime(conversation.lastMessage.lastTime) }}</text>
- <view v-if="conversation.unreadCount > 0" class="unread-badge">
- {{ conversation.unreadCount > 99 ? '99+' : conversation.unreadCount }}
- </view>
- </view>
- </view>
- </view>
-
- <!-- 更早 -->
- <view v-if="earlierConversations.length > 0">
- <view class="time-group">
- <text class="time-label">更早</text>
- </view>
- <view
- v-for="conversation in earlierConversations"
- :key="conversation.conversationID"
- class="message-item user-message"
- @click="openChat(conversation)"
- >
- <image
- v-if="conversation.userProfile.avatar"
- :src="conversation.userProfile.avatar"
- class="message-avatar-img"
- />
- <view v-else class="message-avatar">
- {{ conversation.userProfile.nick || conversation.userProfile.userID.charAt(0) }}
- </view>
- <view class="message-body">
- <text class="message-type">{{ conversation.userProfile.nick || conversation.userProfile.userID }}</text>
- <text class="message-content">{{ getLastMessageText(conversation.lastMessage) }}</text>
- </view>
- <view class="message-right">
- <text class="message-time">{{ formatTime(conversation.lastMessage.lastTime) }}</text>
- <view v-if="conversation.unreadCount > 0" class="unread-badge">
- {{ conversation.unreadCount > 99 ? '99+' : conversation.unreadCount }}
- </view>
- </view>
- </view>
- </view>
- </view>
-
- <!-- 空状态 -->
- <view v-else class="empty-container">
- <text>暂无消息</text>
- </view>
- </scroll-view>
- <!-- 底部导航 -->
- <view class="tabbar">
- <view class="tabbar-item home" @click="navigateToWorkbench">
- <view class="tabbar-icon"></view>
- <text class="tabbar-text">工作台</text>
- </view>
- <view class="tabbar-item resources" @click="navigateToMyResources">
- <view class="tabbar-icon"></view>
- <text class="tabbar-text">我的资源</text>
- </view>
- <view class="tabbar-item trophy" @click="navigateToRanking">
- <view class="tabbar-icon"></view>
- <text class="tabbar-text">排行榜</text>
- </view>
- <view class="tabbar-item message active" @click="navigateToMessage">
- <view class="tabbar-icon">
- <view v-if="totalUnreadCount > 0" class="badge">
- {{ totalUnreadCount > 99 ? '99+' : totalUnreadCount }}
- </view>
- </view>
- <text class="tabbar-text">消息</text>
- </view>
- <view class="tabbar-item mine" @click="navigateToMine">
- <view class="tabbar-icon"></view>
- <text class="tabbar-text">我的</text>
- </view>
- </view>
- </view>
- </template>
- <script>
- import timManager from '@/utils/tim-manager.js'
- import TIM from 'tim-wx-sdk'
- import api from '@/utils/api.js'
-
- export default {
- data() {
- return {
- loading: true,
- conversationList: [],
- matchmakerInfo: null,
- imUserId: '',
- totalUnreadCount: 0,
- // 系统消息未读数
- systemUnread: 0,
-
- // 顶部系统通知占位(后续可从后端加载)
- systemNotification: {
- title: '系统通知',
- content: '您的线索审核已通过,获得20积分奖励,当期积分可兑换【资源查看权限】x1',
- footer: '完成线索录采集,积分+20(距离黄金级还差72分)',
- timeText: '刚刚'
- },
- // 撮合成功通知占位
- matchSuccessNotification: {
- title: '撮合成功通知',
- content: '您推荐的李先生和王女士已成功匹配,获得50积分+100元现金奖励',
- timeText: '10分钟前'
- }
- }
- },
- computed: {
- // 今天的会话
- todayConversations() {
- const today = new Date()
- return this.conversationList.filter(conv => this.isSameDay(conv.lastMessage, today))
- },
- // 昨日会话
- yesterdayConversations() {
- const yesterday = new Date()
- yesterday.setDate(yesterday.getDate() - 1)
- return this.conversationList.filter(conv => this.isSameDay(conv.lastMessage, yesterday))
- },
- // 更早会话
- earlierConversations() {
- const today = new Date()
- const yesterday = new Date()
- yesterday.setDate(yesterday.getDate() - 1)
- return this.conversationList.filter(conv => {
- const msg = conv.lastMessage
- if (!msg || !msg.lastTime) return false
- const isToday = this.isSameDay(msg, today)
- const isYesterday = this.isSameDay(msg, yesterday)
- return !isToday && !isYesterday
- })
- }
- },
-
- onLoad() {
- // 为避免误用用户端 IM 账号,这里不再复用当前 TIM 登录状态,进入页面即按红娘流程重新初始化
- console.log('🔍 红娘消息页强制按红娘流程初始化 IM')
- this.initIM()
- },
-
- onShow() {
- // 页面显示时刷新会话列表
- if (this.imUserId) {
- this.loadConversationList()
- }
- // 同步系统消息未读
- this.loadSystemUnread()
- },
-
- onUnload() {
- // 页面卸载时移除监听
- this.removeListeners()
- },
-
- methods: {
- // 判断消息是否与给定日期同一天
- isSameDay(lastMessage, baseDate) {
- if (!lastMessage || !lastMessage.lastTime) return false
- const msgDate = new Date(lastMessage.lastTime * 1000)
- const d = baseDate instanceof Date ? baseDate : new Date(baseDate)
- return (
- msgDate.getFullYear() === d.getFullYear() &&
- msgDate.getMonth() === d.getMonth() &&
- msgDate.getDate() === d.getDate()
- )
- },
- // 加载系统消息未读数,并同步最新一条系统通知到顶部卡片
- async loadSystemUnread() {
- try {
- const userId = uni.getStorageSync('userId')
- if (!userId) return
- const count = await api.message.getSystemUnreadCount(userId)
- // getSystemUnreadCount 可能返回 {code,data} 或直接 number,这里统一兼容
- if (typeof count === 'number') {
- this.systemUnread = count
- } else if (count && typeof count.data === 'number') {
- this.systemUnread = count.data
- }
-
- // 同时拉取最新一条系统通知,用于顶部卡片展示
- const res = await api.message.getSystemList(userId, 1, 1)
- const list = (res && (res.list || res.data?.list)) || []
- if (list.length > 0) {
- const item = list[0]
- // 计算时间文案
- let timeText = ''
- const t = item.createdAt || item.created_at
- if (t) {
- const d2 = new Date(t)
- const now = new Date()
- const diff = now - d2
- if (diff < 60000) {
- timeText = '刚刚'
- } else if (diff < 3600000) {
- timeText = Math.floor(diff / 60000) + '分钟前'
- } else {
- const mm = String(d2.getMonth() + 1).padStart(2, '0')
- const dd = String(d2.getDate()).padStart(2, '0')
- const hh = String(d2.getHours()).padStart(2, '0')
- const mi = String(d2.getMinutes()).padStart(2, '0')
- timeText = `${mm}-${dd} ${hh}:${mi}`
- }
- }
-
- // 用最新系统通知更新卡片文案(小字显示最新内容)
- this.systemNotification = {
- ...this.systemNotification,
- title: '系统通知',
- content: item.title || this.systemNotification.content,
- footer: item.content || this.systemNotification.footer,
- timeText: timeText || this.systemNotification.timeText
- }
- }
- } catch (e) {
- console.log('红娘消息页加载系统未读失败', e)
- }
- },
- // 跳转系统通知列表
- openSystemMessages() {
- uni.navigateTo({
- url: '/pages/matchmaker-workbench/system-messages'
- })
- },
- // 初始化 IM
- async initIM() {
- try {
- this.loading = true
-
- // 1. 获取当前登录用户ID
- const userId = uni.getStorageSync('userId')
- if (!userId) {
- uni.showToast({ title: '请先登录', icon: 'none' })
- return
- }
-
- // 2. 获取红娘信息
- const res = await uni.request({
- url: 'http://localhost:8081/api/matchmaker/current',
- method: 'GET',
- data: { userId }
- })
-
- if (res[1].data.code !== 200) {
- uni.showToast({ title: '获取红娘信息失败', icon: 'none' })
- return
- }
-
- this.matchmakerInfo = res[1].data.data
- this.imUserId = this.matchmakerInfo.imUserId // m_1
-
- console.log('✅ 红娘信息:', this.matchmakerInfo)
-
- // 3. 获取 UserSig
- const sigRes = await uni.request({
- url: `http://localhost:8083/api/im/getUserSig?userId=${this.imUserId}`,
- method: 'GET'
- })
-
- if (sigRes[1].data.code !== 200) {
- uni.showToast({ title: '获取UserSig失败', icon: 'none' })
- return
- }
-
- const userSig = sigRes[1].data.data.userSig
- console.log('✅ 获取到 UserSig:', userSig ? '成功' : '失败')
-
- // 4. 先登出当前账号(如果已登录)
- try {
- await timManager.logout()
- console.log('✅ 已登出之前的账号')
- } catch (e) {
- console.log('⚠️ 登出失败或未登录:', e.message)
- }
-
- // 5. 以红娘身份登录 IM
- await timManager.login(this.imUserId, userSig)
- console.log('✅ 红娘已登录 IM:', this.imUserId)
-
- // 6. 监听事件
- this.addListeners()
-
- // 7. 加载会话列表
- // 6. 加载会话列表
- await this.loadConversationList()
-
- } catch (error) {
- console.error('❌ 初始化 IM 失败:', error)
- uni.showToast({ title: '初始化失败', icon: 'none' })
- } finally {
- this.loading = false
- }
- },
-
- // 加载会话列表
- async loadConversationList() {
- try {
- // SDK 未登录 / 未 ready 时不调用会话列表接口,避免报“接口调用时机不合理”
- if (!timManager.isLogin || !timManager.tim) {
- console.log('⚠️ TIM 未 ready,暂不加载会话列表')
- return
- }
- const tim = timManager.getTim()
- const { data } = await tim.getConversationList()
- this.conversationList = data.conversationList || []
-
- // 补全用户头像和昵称
- await this.loadUserAvatars()
-
- // 计算总未读数
- this.totalUnreadCount = this.conversationList.reduce((total, conv) => {
- return total + (conv.unreadCount || 0)
- }, 0)
-
- console.log('✅ 会话列表:', this.conversationList)
- } catch (error) {
- console.error('❌ 加载会话列表失败:', error)
- }
- },
-
- // 添加事件监听
- addListeners() {
- const tim = timManager.getTim()
-
- // 监听新消息
- tim.on(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived)
-
- // 监听会话列表更新
- tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated)
- },
-
- // 移除事件监听
- removeListeners() {
- const tim = timManager.getTim()
- tim.off(TIM.EVENT.MESSAGE_RECEIVED, this.onMessageReceived)
- tim.off(TIM.EVENT.CONVERSATION_LIST_UPDATED, this.onConversationListUpdated)
- },
-
- // 收到新消息
- onMessageReceived(event) {
- console.log('📩 收到新消息:', event.data)
- // 刷新会话列表
- this.loadConversationList()
- },
-
- // 会话列表更新
- onConversationListUpdated(event) {
- console.log('🔄 会话列表更新:', event.data)
- this.conversationList = event.data || []
-
- // 补全头像昵称后再计算未读
- this.loadUserAvatars().then(() => {
- this.totalUnreadCount = this.conversationList.reduce((total, conv) => {
- return total + (conv.unreadCount || 0)
- }, 0)
- })
- },
-
- // 批量补全用户头像和昵称
- async loadUserAvatars() {
- try {
- if (!this.conversationList || this.conversationList.length === 0) {
- return
- }
-
- // 收集对端用户ID(排除红娘 m_ 开头的ID)
- const userIds = []
- this.conversationList.forEach(conv => {
- let targetUserId = ''
- if (conv.userProfile && conv.userProfile.userID) {
- targetUserId = String(conv.userProfile.userID)
- } else if (conv.conversationID && conv.conversationID.indexOf('C2C') === 0) {
- targetUserId = conv.conversationID.replace('C2C', '')
- }
- if (!targetUserId) return
- if (targetUserId.startsWith('m_')) return
- userIds.push(targetUserId)
- })
-
- const uniqueIds = Array.from(new Set(userIds))
- if (uniqueIds.length === 0) return
-
- console.log('🔄 红娘消息页批量获取用户信息,数量:', uniqueIds.length)
-
- const res = await uni.request({
- url: 'http://localhost:8083/api/user/batch',
- method: 'GET',
- data: {
- userIds: uniqueIds.join(',')
- }
- })
-
- if (!res[1] || res[1].statusCode !== 200 || res[1].data.code !== 200) {
- console.warn('⚠️ 红娘消息页批量获取用户信息失败')
- return
- }
-
- const list = res[1].data.data || []
- const userMap = {}
- list.forEach(user => {
- if (!user || user.userId == null) return
- userMap[String(user.userId)] = {
- nickname: user.nickname,
- avatarUrl: user.avatarUrl
- }
- })
-
- // 回填到会话列表的 userProfile 中
- this.conversationList = this.conversationList.map(conv => {
- let targetUserId = ''
- if (conv.userProfile && conv.userProfile.userID) {
- targetUserId = String(conv.userProfile.userID)
- } else if (conv.conversationID && conv.conversationID.indexOf('C2C') === 0) {
- targetUserId = conv.conversationID.replace('C2C', '')
- }
- const info = targetUserId ? userMap[targetUserId] : null
- if (info) {
- conv.userProfile = conv.userProfile || {}
- conv.userProfile.nick = info.nickname || conv.userProfile.nick || `用户${targetUserId}`
- conv.userProfile.avatar = info.avatarUrl || conv.userProfile.avatar || ''
- }
- return conv
- })
- } catch (error) {
- console.error('❌ 红娘消息页加载用户头像失败:', error)
- }
- },
-
- // 打开聊天页面
- openChat(conversation) {
- const targetUserId = conversation.userProfile.userID
- const targetUserName = encodeURIComponent(conversation.userProfile.nick || `用户${targetUserId}`)
- const targetUserAvatar = encodeURIComponent(conversation.userProfile.avatar || '')
- uni.navigateTo({
- url: `/pages/message/chat?targetUserId=${targetUserId}&targetUserName=${targetUserName}&targetUserAvatar=${targetUserAvatar}&fromMatchmaker=true`
- })
- },
-
- // 获取最后一条消息的文本
- getLastMessageText(lastMessage) {
- if (!lastMessage) return ''
-
- switch (lastMessage.type) {
- case TIM.TYPES.MSG_TEXT:
- return lastMessage.payload.text
- case TIM.TYPES.MSG_IMAGE:
- return '[图片]'
- case TIM.TYPES.MSG_AUDIO:
- return '[语音]'
- case TIM.TYPES.MSG_VIDEO:
- return '[视频]'
- case TIM.TYPES.MSG_FILE:
- return '[文件]'
- default:
- return '[消息]'
- }
- },
-
- // 格式化时间
- formatTime(timestamp) {
- if (!timestamp) return ''
-
- const now = new Date()
- const msgTime = new Date(timestamp * 1000)
- const diff = now - msgTime
-
- // 一分钟内
- if (diff < 60000) {
- return '刚刚'
- }
-
- // 一小时内
- if (diff < 3600000) {
- return Math.floor(diff / 60000) + '分钟前'
- }
-
- // 今天
- if (msgTime.toDateString() === now.toDateString()) {
- return msgTime.getHours() + ':' + String(msgTime.getMinutes()).padStart(2, '0')
- }
-
- // 昨天
- const yesterday = new Date(now)
- yesterday.setDate(yesterday.getDate() - 1)
- if (msgTime.toDateString() === yesterday.toDateString()) {
- return '昨天'
- }
-
- // 更早
- return (msgTime.getMonth() + 1) + '-' + msgTime.getDate()
- },
-
- // 返回上一页
- goBack() {
- uni.navigateBack()
- },
-
- // 导航到工作台
- navigateToWorkbench() {
- uni.redirectTo({
- url: '/pages/matchmaker-workbench/index'
- })
- },
-
- // 导航到我的资源
- navigateToMyResources() {
- uni.redirectTo({
- url: '/pages/matchmaker-workbench/my-resources'
- })
- },
-
- // 导航到排行榜
- navigateToRanking() {
- uni.redirectTo({
- url: '/pages/matchmaker-workbench/ranking'
- })
- },
-
- // 导航到消息
- navigateToMessage() {
- // 已在消息页面,无需跳转
- },
-
- // 导航到我的
- navigateToMine() {
- uni.redirectTo({
- url: '/pages/matchmaker-workbench/mine'
- })
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .matchmaker-message {
- min-height: 100vh;
- /* 顶部淡粉,中部淡紫,越到底部越接近白色 */
- background: linear-gradient(180deg, #fff1f7 0%, #f6ebff 45%, #fbf7ff 75%, #ffffff 100%);
- display: flex;
- flex-direction: column;
- }
- /* 顶部导航栏 */
- .header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 25rpx 30rpx;
- padding-top: calc(25rpx + env(safe-area-inset-top));
- background: transparent;
- border-bottom-width: 0;
- .back-btn {
- width: 70rpx;
- height: 70rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(240, 240, 240, 0.5);
- border-radius: 50%;
- background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23333"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>');
- background-size: 40rpx 40rpx;
- background-repeat: no-repeat;
- background-position: center;
- }
- .header-title {
- font-size: 38rpx;
- font-weight: bold;
- color: #333;
- }
- .placeholder {
- width: 70rpx;
- }
- }
- .content {
- flex: 1;
- padding: 20rpx 24rpx 140rpx;
- box-sizing: border-box;
- }
- /* 消息项 */
- .message-item {
- background: #ffffff;
- border-radius: 24rpx;
- padding: 28rpx;
- margin-bottom: 20rpx;
- box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.05);
- position: relative;
- &.system-notification {
- background: #ffffff;
- border-radius: 28rpx;
- border: 2rpx solid rgba(255, 128, 171, 0.85);
- box-shadow: 0 10rpx 26rpx rgba(255, 128, 171, 0.28);
- padding: 26rpx 30rpx;
- .message-type {
- display: block;
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 12rpx;
- }
- .message-time {
- position: absolute;
- top: 26rpx;
- right: 30rpx;
- font-size: 24rpx;
- color: #b88bb0;
- }
- .message-content {
- display: block;
- font-size: 26rpx;
- color: #555;
- line-height: 1.6;
- margin-bottom: 12rpx;
- }
- .message-footer {
- display: block;
- font-size: 24rpx;
- color: #b27aa0;
- line-height: 1.4;
- }
- }
- &.match-success {
- display: flex;
- background: linear-gradient(135deg, #f8f3ff 0%, #f4e6ff 45%, #ffeaf7 100%);
- border: 0;
- box-shadow: 0 8rpx 20rpx rgba(186, 104, 200, 0.25);
- .message-icon {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- margin-right: 20rpx;
- background-size: 40rpx 40rpx;
- background-repeat: no-repeat;
- background-position: center;
- }
- .message-icon.heart {
- background-color: #f8d9ef;
- background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23e573ab"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>');
- }
- .message-body {
- flex: 1;
- }
- .message-type {
- display: block;
- font-size: 30rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 10rpx;
- }
- .message-content {
- display: block;
- font-size: 26rpx;
- color: #666;
- line-height: 1.4;
- }
- .message-time {
- font-size: 24rpx;
- color: #a691c0;
- margin-top: auto;
- }
- }
- &.user-message {
- display: flex;
- background: #ffffff;
- border: 0;
- box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.04);
- align-items: center;
- .message-avatar {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- background: linear-gradient(135deg, #b39ddb 0%, #ce93d8 100%);
- color: #FFFFFF;
- font-size: 28rpx;
- font-weight: bold;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 20rpx;
- flex-shrink: 0;
- }
- .message-avatar-img {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- margin-right: 20rpx;
- flex-shrink: 0;
- }
- .message-body {
- flex: 1;
- min-width: 0;
- }
- .message-type {
- display: block;
- font-size: 30rpx;
- font-weight: 500;
- color: #333;
- margin-bottom: 6rpx;
- }
- .message-content {
- display: block;
- font-size: 26rpx;
- color: #777;
- line-height: 1.4;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .message-right {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- margin-left: 20rpx;
- }
- .message-time {
- font-size: 24rpx;
- color: #b0a6c5;
- margin-bottom: 10rpx;
- }
- .unread-badge {
- background: #ff5c9a;
- color: #FFFFFF;
- font-size: 20rpx;
- padding: 4rpx 10rpx;
- border-radius: 20rpx;
- min-width: 36rpx;
- text-align: center;
- }
- }
- }
- /* 加载和空状态 */
- .loading-container,
- .empty-container {
- display: flex;
- justify-content: center;
- align-items: center;
- padding: 100rpx 0;
- color: #999;
- font-size: 28rpx;
- }
- /* 时间分组 */
- .time-group {
- display: flex;
- justify-content: center;
- margin: 20rpx 0;
- .time-label {
- display: inline-block;
- background: #ffe6f0;
- color: #e573ab;
- font-size: 24rpx;
- padding: 8rpx 20rpx;
- border-radius: 20rpx;
- font-weight: bold;
- }
- }
- /* 底部导航 */
- .tabbar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: 100rpx;
- background: #FFFFFF;
- border-top: 1rpx solid #F0F0F0;
- display: flex;
- justify-content: space-around;
- align-items: center;
- padding-bottom: env(safe-area-inset-bottom);
- .tabbar-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8rpx;
- padding: 10rpx 0;
- .tabbar-icon {
- width: 44rpx;
- height: 44rpx;
- background-size: contain;
- background-repeat: no-repeat;
- background-position: center;
- position: relative;
- .badge {
- position: absolute;
- top: -8rpx;
- right: -8rpx;
- background: #FF4444;
- color: #FFFFFF;
- font-size: 20rpx;
- font-weight: bold;
- width: 32rpx;
- height: 32rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 16rpx;
- }
- }
- .tabbar-text {
- font-size: 20rpx;
- color: #999;
- }
- &.active .tabbar-text {
- color: #9C27B0;
- font-weight: bold;
- }
- &.home .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
- }
- &.home.active .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>');
- }
- &.resources .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
- }
- &.resources.active .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>');
- }
- &.trophy .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
- }
- &.trophy.active .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M19 5h-2V3H7v2H5c-1.1 0-2 .9-2 2v1c0 2.55 1.92 4.63 4.39 4.94.63 1.5 1.98 2.63 3.61 2.96V19H7v2h10v-2h-4v-3.1c1.63-.33 2.98-1.46 3.61-2.96C19.08 12.63 21 10.55 21 8V7c0-1.1-.9-2-2-2zM5 8V7h2v3.82C5.84 10.4 5 9.3 5 8zm14 0c0 1.3-.84 2.4-2 2.82V7h2v1z"/></svg>');
- }
- &.message .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
- }
- &.message.active .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/></svg>');
- }
- &.mine .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23999"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
- }
- &.mine.active .tabbar-icon {
- background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%239C27B0"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>');
- }
- }
- }
- </style>
|