1. App.vue 启动时立即预加载 uiConfig,杜绝首屏网络延时导致瞬间闪现付费会员入口 2. 会员中心 (member/index.vue):在提审防审模式下直接展示纯净【便民服务指南】卡片,杜绝任何 VIP 皇冠、会员标价、折扣与充值入口 3. 个人中心 (mine/index.vue):提审防审模式下彻底隐藏会员卡片与我的余额,紧凑上移卡片布局 4. 订单明细、充值页与消息中心增加防审合规兼容,避免误触发任何虚拟支付诱导
741 行
18 KiB
Vue
741 行
18 KiB
Vue
<template>
|
||
<view class="msg fc-page" @touchstart="onGlobalTouch">
|
||
<!-- 规范化顶部导航:与首页、分类、我的统一沉浸式渐变顶栏 -->
|
||
<fc-navbar title="通知中心" theme="gradient" />
|
||
|
||
<!-- 分类 Tab -->
|
||
<view class="msg__tabs">
|
||
<view
|
||
v-for="t in tabs"
|
||
:key="t.value"
|
||
class="msg__tab"
|
||
:class="{ 'msg__tab--active': type === t.value }"
|
||
@click="changeType(t.value)"
|
||
>
|
||
<text class="msg__tab-label">{{ t.label }}</text>
|
||
<view v-if="type === t.value" class="msg__tab-line"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 操作工具条:未读统计 + 一键已读 + 一键清理 -->
|
||
<view class="msg__toolbar">
|
||
<view class="msg__stats">
|
||
<text class="msg__stats-text">共 {{ list.length }} 条</text>
|
||
<text v-if="unreadCount > 0" class="msg__stats-sep">·</text>
|
||
<text v-if="unreadCount > 0" class="msg__stats-unread">{{ unreadCount }} 条未读</text>
|
||
</view>
|
||
<view class="msg__actions">
|
||
<view
|
||
class="msg__btn"
|
||
:class="{ 'msg__btn--disabled': unreadCount === 0 }"
|
||
@click="handleReadAll"
|
||
>
|
||
<text class="msg__btn-text">一键已读</text>
|
||
</view>
|
||
<view
|
||
class="msg__btn msg__btn--clear"
|
||
:class="{ 'msg__btn--disabled': !list.length }"
|
||
@click="handleClearAll"
|
||
>
|
||
<text class="msg__btn-text">一键清理</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空数据提示 -->
|
||
<!-- 未登录空状态引导 -->
|
||
<view v-if="!isLogin" class="msg__unlogin-box">
|
||
<fc-empty icon="inbox" text="登录后查看最新互动与系统通知" />
|
||
<button class="msg__unlogin-btn" @click="goLogin">
|
||
<text class="msg__unlogin-btn-txt">立即登录</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 已登录空数据提示 -->
|
||
<fc-empty v-else-if="!loading && !list.length" icon="inbox" text="暂无通知" />
|
||
|
||
<!-- 通知卡片列表(支持左滑显示删除按钮) -->
|
||
<view v-else class="msg__list">
|
||
<view
|
||
v-for="(m, index) in list"
|
||
:key="m.id"
|
||
class="msg__swipe-box"
|
||
>
|
||
<!-- 底层动作区:删除按钮 -->
|
||
<view class="msg__swipe-actions">
|
||
<view class="msg__delete-btn" @click.stop="handleDelete(m, index)">
|
||
<text class="msg__delete-text">删除</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 表层滑动内容卡片 -->
|
||
<view
|
||
class="msg__swipe-content"
|
||
:class="{
|
||
'msg__swipe-content--unread': !m.read,
|
||
'msg__swipe-content--no-transition': m.isSwiping
|
||
}"
|
||
:style="{ transform: 'translateX(' + (m.offset || 0) + 'px)' }"
|
||
@touchstart="onTouchStart($event, m)"
|
||
@touchmove="onTouchMove($event, m)"
|
||
@touchend="onTouchEnd($event, m)"
|
||
@touchcancel="onTouchEnd($event, m)"
|
||
@click="handleCardClick(m)"
|
||
>
|
||
<view class="msg__icon">
|
||
<image class="fc-icon--badge-sm" :src="iconUrl(meta(m.type).iconKey)" mode="aspectFit" />
|
||
</view>
|
||
<view class="msg__body">
|
||
<view class="msg__head">
|
||
<text class="msg__title fc-ellipsis">{{ m.title }}</text>
|
||
<text class="msg__time">{{ timeText(m.createdAt) }}</text>
|
||
</view>
|
||
<text class="msg__content fc-ellipsis-2">{{ m.content }}</text>
|
||
</view>
|
||
<view v-if="!m.read" class="msg__dot"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部 Tabbar 占位 -->
|
||
<view class="msg__tabbar-space"></view>
|
||
<fc-tabbar :current="3" />
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FcNavbar from '@/components/fc-navbar/fc-navbar.vue'
|
||
import FcEmpty from '@/components/fc-empty/fc-empty.vue'
|
||
import FcTabbar from '@/components/fc-tabbar/fc-tabbar.vue'
|
||
import messageApi from '@/common/api/message.js'
|
||
import { MESSAGE_TYPE } from '@/common/constants/ui.js'
|
||
import { fromNow } from '@/common/utils/format.js'
|
||
import { iconUrl } from '@/common/store/ui.js'
|
||
|
||
export default {
|
||
components: { FcNavbar, FcEmpty, FcTabbar },
|
||
data() {
|
||
return {
|
||
type: 'system',
|
||
list: [],
|
||
loading: false,
|
||
tabs: [
|
||
{ label: '系统通知', value: 'system' },
|
||
{ label: '互动提醒', value: 'interact' },
|
||
],
|
||
// 手势滑动追踪状态
|
||
touchState: {
|
||
startX: 0,
|
||
startY: 0,
|
||
startTime: 0,
|
||
direction: '',
|
||
initOffset: 0,
|
||
activeId: null,
|
||
},
|
||
}
|
||
},
|
||
computed: {
|
||
/** 是否已登录 */
|
||
isLogin() {
|
||
const token = uni.getStorageSync('token') || uni.getStorageSync('fc_token') || ''
|
||
return !!token
|
||
},
|
||
/**
|
||
* 计算当前列表中的未读通知总数
|
||
* @returns {number} 未读数量
|
||
*/
|
||
unreadCount() {
|
||
return this.list.filter(m => !m.read).length
|
||
},
|
||
/**
|
||
* 当前选中的分类名称
|
||
* @returns {string} 分类标签名
|
||
*/
|
||
currentTabLabel() {
|
||
const found = this.tabs.find(t => t.value === this.type)
|
||
return found ? found.label : '通知'
|
||
},
|
||
},
|
||
onLoad() {
|
||
this.fetch()
|
||
},
|
||
onShow() {
|
||
this.fetch()
|
||
},
|
||
onPullDownRefresh() {
|
||
this.fetch().finally(() => {
|
||
uni.stopPullDownRefresh()
|
||
})
|
||
},
|
||
methods: {
|
||
/** 前往登录页 */
|
||
goLogin() {
|
||
uni.navigateTo({ url: '/pages/auth/login' })
|
||
},
|
||
/** 暴露给模板:获取图标完整地址 */
|
||
iconUrl,
|
||
/**
|
||
* 获取指定通知类型的元信息
|
||
* @param {string} t 通知类型 system|interact
|
||
* @returns {Object} 包含 iconKey 与标题配置的对象
|
||
*/
|
||
meta(t) {
|
||
return MESSAGE_TYPE[t] || MESSAGE_TYPE.system
|
||
},
|
||
/**
|
||
* 格式化相对时间文本
|
||
* @param {string|number} t 时间戳或日期字符串
|
||
* @returns {string} 相对时间文本,如“刚刚”、“5分钟前”
|
||
*/
|
||
timeText(t) {
|
||
return fromNow(t)
|
||
},
|
||
/**
|
||
* 获取左滑操作区最大展开宽度(px)
|
||
* @returns {number} 展开距离,设计稿 140rpx 转真实 px
|
||
*/
|
||
getMaxSwipePx() {
|
||
try {
|
||
return uni.upx2px(140)
|
||
} catch (e) {
|
||
return 70
|
||
}
|
||
},
|
||
/**
|
||
* 全局触摸事件:当点击其它位置时关闭所有已滑开的卡片
|
||
*/
|
||
onGlobalTouch() {
|
||
// 若有滑开的卡片且当前非手势拖拽中,不做强制重置,由具体区域判定收起
|
||
},
|
||
/**
|
||
* 拉取当前类型的通知列表
|
||
*/
|
||
async fetch() {
|
||
const token = uni.getStorageSync('token') || uni.getStorageSync('fc_token') || ''
|
||
if (!token) {
|
||
this.loading = false
|
||
this.list = []
|
||
return
|
||
}
|
||
this.loading = true
|
||
try {
|
||
const res = await messageApi.list({ type: this.type, page: 1, size: 50 })
|
||
const rows = (res && res.list) || []
|
||
this.list = rows.map(item => ({
|
||
...item,
|
||
offset: 0,
|
||
isSwiping: false,
|
||
}))
|
||
} catch (e) {
|
||
// 静默置空,杜绝未登录或接口偶发波动时弹出生硬的 Toast 提示
|
||
this.list = []
|
||
} finally {
|
||
this.loading = false
|
||
}
|
||
},
|
||
/**
|
||
* 切换通知频道
|
||
* @param {string} v 频道类型值 system|interact
|
||
*/
|
||
changeType(v) {
|
||
if (this.type === v) return
|
||
this.closeAllSwipe()
|
||
this.type = v
|
||
this.fetch()
|
||
},
|
||
/**
|
||
* 收起所有已滑开的卡片
|
||
* @param {number|null} excludeId 排除收起的消息ID(可选)
|
||
*/
|
||
closeAllSwipe(excludeId = null) {
|
||
this.list.forEach(item => {
|
||
if (item.id !== excludeId && item.offset !== 0) {
|
||
this.$set(item, 'offset', 0)
|
||
this.$set(item, 'isSwiping', false)
|
||
}
|
||
})
|
||
},
|
||
/**
|
||
* 一键标记全部已读
|
||
*/
|
||
async handleReadAll() {
|
||
if (this.unreadCount === 0) {
|
||
uni.showToast({ title: '暂无未读通知', icon: 'none' })
|
||
return
|
||
}
|
||
this.closeAllSwipe()
|
||
uni.showLoading({ title: '处理中...', mask: true })
|
||
try {
|
||
await messageApi.readAll({ type: this.type })
|
||
this.list.forEach(m => {
|
||
m.read = true
|
||
})
|
||
uni.$emit('refreshUnreadCount')
|
||
uni.showToast({ title: '已全部标为已读', icon: 'success' })
|
||
} catch (e) {
|
||
uni.showToast({ title: '操作失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
},
|
||
/**
|
||
* 一键清理当前分类通知
|
||
*/
|
||
handleClearAll() {
|
||
if (!this.list.length) {
|
||
uni.showToast({ title: '暂无可清理通知', icon: 'none' })
|
||
return
|
||
}
|
||
this.closeAllSwipe()
|
||
uni.showModal({
|
||
title: '一键清理',
|
||
content: `确定清空当前${this.currentTabLabel}的全部通知记录吗?`,
|
||
confirmText: '清空',
|
||
confirmColor: '#FF4D4F',
|
||
cancelText: '取消',
|
||
success: async (res) => {
|
||
if (!res.confirm) return
|
||
uni.showLoading({ title: '清理中...', mask: true })
|
||
try {
|
||
await messageApi.clear({ type: this.type })
|
||
this.list = []
|
||
uni.$emit('refreshUnreadCount')
|
||
uni.showToast({ title: '已清空通知', icon: 'success' })
|
||
} catch (e) {
|
||
uni.showToast({ title: '清理失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
},
|
||
})
|
||
},
|
||
/**
|
||
* 卡片触摸开始手势
|
||
* @param {TouchEvent} e 触摸事件对象
|
||
* @param {Object} m 当前触摸的通知对象
|
||
*/
|
||
onTouchStart(e, m) {
|
||
if (!e.touches || !e.touches[0]) return
|
||
const touch = e.touches[0]
|
||
this.touchState = {
|
||
startX: touch.clientX,
|
||
startY: touch.clientY,
|
||
startTime: Date.now(),
|
||
direction: '',
|
||
initOffset: m.offset || 0,
|
||
activeId: m.id,
|
||
}
|
||
},
|
||
/**
|
||
* 卡片触摸滑动中手势
|
||
* @param {TouchEvent} e 触摸事件对象
|
||
* @param {Object} m 当前触摸的通知对象
|
||
*/
|
||
onTouchMove(e, m) {
|
||
if (!this.touchState.activeId || this.touchState.activeId !== m.id) return
|
||
if (!e.touches || !e.touches[0]) return
|
||
const touch = e.touches[0]
|
||
const deltaX = touch.clientX - this.touchState.startX
|
||
const deltaY = touch.clientY - this.touchState.startY
|
||
|
||
// 滑动方向判定锁:避免干扰页面垂直滚动
|
||
if (!this.touchState.direction) {
|
||
if (Math.abs(deltaX) > 6 || Math.abs(deltaY) > 6) {
|
||
if (Math.abs(deltaX) >= Math.abs(deltaY)) {
|
||
this.touchState.direction = 'horizontal'
|
||
// 确定为横向滑动时,收起其他已滑开的卡片
|
||
this.closeAllSwipe(m.id)
|
||
} else {
|
||
this.touchState.direction = 'vertical'
|
||
}
|
||
}
|
||
}
|
||
|
||
// 纵向滚动不阻拦、不处理位移
|
||
if (this.touchState.direction !== 'horizontal') return
|
||
|
||
// 标记当前处于手势拖拽中,关闭 CSS 动效以达到 60fps 灵敏跟手
|
||
if (!m.isSwiping) {
|
||
this.$set(m, 'isSwiping', true)
|
||
}
|
||
|
||
const maxSwipePx = this.getMaxSwipePx()
|
||
let targetOffset = this.touchState.initOffset + deltaX
|
||
|
||
// 边界限制:不能向右拉出卡片(最大为 0)
|
||
if (targetOffset > 0) {
|
||
targetOffset = 0
|
||
}
|
||
// 向左滑动超出最大展开宽度时加阻尼效果
|
||
if (targetOffset < -maxSwipePx) {
|
||
const overstep = -maxSwipePx - targetOffset
|
||
targetOffset = -maxSwipePx - overstep * 0.2
|
||
}
|
||
|
||
this.$set(m, 'offset', targetOffset)
|
||
},
|
||
/**
|
||
* 卡片触摸结束手势
|
||
* @param {TouchEvent} e 触摸事件对象
|
||
* @param {Object} m 当前触摸的通知对象
|
||
*/
|
||
onTouchEnd(e, m) {
|
||
if (!this.touchState.activeId || this.touchState.activeId !== m.id) return
|
||
this.$set(m, 'isSwiping', false)
|
||
|
||
const currentOffset = m.offset || 0
|
||
const maxSwipePx = this.getMaxSwipePx()
|
||
const moveTime = Date.now() - this.touchState.startTime
|
||
const movedX = currentOffset - this.touchState.initOffset
|
||
|
||
// 判定是否展开:
|
||
// 1. 快速轻扫手势(时间短且位移达到一定幅度)
|
||
// 2. 释放位置超过一半阈值
|
||
let shouldOpen = false
|
||
if (moveTime < 280 && movedX < -25) {
|
||
shouldOpen = true
|
||
} else if (moveTime < 280 && movedX > 25) {
|
||
shouldOpen = false
|
||
} else if (currentOffset <= -maxSwipePx / 2) {
|
||
shouldOpen = true
|
||
}
|
||
|
||
if (shouldOpen) {
|
||
this.$set(m, 'offset', -maxSwipePx)
|
||
} else {
|
||
this.$set(m, 'offset', 0)
|
||
}
|
||
|
||
this.touchState.activeId = null
|
||
this.touchState.direction = ''
|
||
},
|
||
/**
|
||
* 卡片点击事件:包含已滑开卡片的安全收起判断与详情查看
|
||
* @param {Object} m 当前点击的通知对象
|
||
*/
|
||
handleCardClick(m) {
|
||
// 若当前卡片处于滑开状态,则先收起卡片,不触发打开详情
|
||
if (m.offset && m.offset < 0) {
|
||
this.$set(m, 'offset', 0)
|
||
return
|
||
}
|
||
// 若存在其他卡片处于滑开状态,收起其他卡片并拦截点击
|
||
let hasOtherOpen = false
|
||
this.list.forEach(item => {
|
||
if (item.offset && item.offset < 0) {
|
||
this.$set(item, 'offset', 0)
|
||
hasOtherOpen = true
|
||
}
|
||
})
|
||
if (hasOtherOpen) return
|
||
|
||
// 正常打开通知详情
|
||
this.openMsg(m)
|
||
},
|
||
/**
|
||
* 删除单条通知
|
||
* @param {Object} m 待删除的通知对象
|
||
* @param {number} index 当前项在列表中的索引
|
||
*/
|
||
handleDelete(m, index) {
|
||
uni.showModal({
|
||
title: '删除通知',
|
||
content: '确定要删除此条通知吗?',
|
||
confirmText: '删除',
|
||
confirmColor: '#FF4D4F',
|
||
cancelText: '取消',
|
||
success: async (res) => {
|
||
if (!res.confirm) {
|
||
// 取消则收起滑开的卡片
|
||
this.$set(m, 'offset', 0)
|
||
return
|
||
}
|
||
uni.showLoading({ title: '删除中...', mask: true })
|
||
try {
|
||
await messageApi.delete(m.id)
|
||
this.list.splice(index, 1)
|
||
uni.$emit('refreshUnreadCount')
|
||
uni.showToast({ title: '已删除', icon: 'success' })
|
||
} catch (e) {
|
||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||
} finally {
|
||
uni.hideLoading()
|
||
}
|
||
},
|
||
})
|
||
},
|
||
/**
|
||
* 点击查看通知详情并标记已读
|
||
* @param {Object} m 通知项对象
|
||
*/
|
||
async openMsg(m) {
|
||
if (!m.read) {
|
||
m.read = true
|
||
try {
|
||
await messageApi.read(m.id)
|
||
uni.$emit('refreshUnreadCount')
|
||
} catch (e) {}
|
||
}
|
||
uni.showModal({
|
||
title: m.title || '通知详情',
|
||
content: m.content || '',
|
||
showCancel: false,
|
||
confirmText: '我知道了',
|
||
confirmColor: '#00A854',
|
||
})
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.msg {
|
||
min-height: 100vh;
|
||
background: #F2FEF7;
|
||
}
|
||
|
||
/* 分类 Tab */
|
||
.msg__tabs {
|
||
display: flex;
|
||
background: #ffffff;
|
||
padding: 0 32rpx;
|
||
border-bottom: 1rpx solid rgba(0, 210, 106, 0.08);
|
||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
.msg__tab {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 30rpx;
|
||
color: #64748B;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
position: relative;
|
||
&--active {
|
||
color: $fc-primary-text;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
.msg__tab-line {
|
||
position: absolute;
|
||
bottom: 0;
|
||
width: 56rpx;
|
||
height: 6rpx;
|
||
border-radius: 4rpx;
|
||
background: $fc-gradient-primary;
|
||
}
|
||
|
||
/* 顶部操作工具条 */
|
||
.msg__toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20rpx 28rpx 12rpx;
|
||
}
|
||
.msg__stats {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 24rpx;
|
||
color: #64748B;
|
||
}
|
||
.msg__stats-sep {
|
||
margin: 0 8rpx;
|
||
opacity: 0.6;
|
||
}
|
||
.msg__stats-unread {
|
||
color: $fc-primary-text;
|
||
font-weight: 600;
|
||
}
|
||
.msg__actions {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.msg__btn {
|
||
padding: 8rpx 22rpx;
|
||
margin-left: 14rpx;
|
||
border-radius: 26rpx;
|
||
background: #ffffff;
|
||
border: 1rpx solid rgba(0, 210, 106, 0.3);
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 210, 106, 0.06);
|
||
transition: all 0.2s ease;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
&:active {
|
||
transform: scale(0.96);
|
||
background: #E6F9F0;
|
||
}
|
||
&--disabled {
|
||
opacity: 0.45;
|
||
pointer-events: none;
|
||
}
|
||
&--clear {
|
||
border-color: rgba(100, 116, 139, 0.2);
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.02);
|
||
.msg__btn-text {
|
||
color: #64748B;
|
||
}
|
||
&:active {
|
||
background: #F1F5F9;
|
||
}
|
||
}
|
||
}
|
||
.msg__btn-text {
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
color: $fc-primary-text;
|
||
}
|
||
|
||
/* 消息列表 */
|
||
.msg__list {
|
||
padding: 10rpx 0 20rpx;
|
||
}
|
||
|
||
/* 左滑卡片外层容器 */
|
||
.msg__swipe-box {
|
||
position: relative;
|
||
margin: 0 24rpx 20rpx;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 80, 40, 0.04);
|
||
}
|
||
|
||
/* 底层操作区:删除按钮 */
|
||
.msg__swipe-actions {
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 140rpx;
|
||
background: linear-gradient(135deg, #FF6262, #FF3D3D);
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.msg__delete-btn {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
&:active {
|
||
opacity: 0.88;
|
||
}
|
||
}
|
||
.msg__delete-text {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
|
||
/* 表层卡片内容 */
|
||
.msg__swipe-content {
|
||
position: relative;
|
||
z-index: 2;
|
||
width: 100%;
|
||
padding: 24rpx;
|
||
box-sizing: border-box;
|
||
background: #ffffff;
|
||
border-radius: 20rpx;
|
||
border: 1rpx solid rgba(4, 48, 30, 0.05);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
cursor: pointer;
|
||
transition: transform 0.22s cubic-bezier(0.25, 1, 0.5, 1);
|
||
&--no-transition {
|
||
transition: none !important;
|
||
}
|
||
&--unread {
|
||
background: #F8FFF9;
|
||
border-left: 8rpx solid $fc-primary;
|
||
}
|
||
&:active {
|
||
background-color: #FAFCFA;
|
||
}
|
||
}
|
||
|
||
.msg__icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
flex: 0 0 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.msg__body {
|
||
flex: 1;
|
||
margin-left: 20rpx;
|
||
min-width: 0;
|
||
}
|
||
.msg__head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
.msg__title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #1E293B;
|
||
margin-right: 16rpx;
|
||
}
|
||
.msg__time {
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
white-space: nowrap;
|
||
}
|
||
.msg__content {
|
||
display: block;
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
color: #334155;
|
||
line-height: 1.5;
|
||
}
|
||
.msg__dot {
|
||
position: absolute;
|
||
top: 24rpx;
|
||
right: 24rpx;
|
||
width: 16rpx;
|
||
height: 16rpx;
|
||
border-radius: 50%;
|
||
background: $fc-primary;
|
||
box-shadow: 0 0 8rpx rgba(0, 224, 112, 0.6);
|
||
}
|
||
|
||
.msg__tabbar-space {
|
||
height: 140rpx;
|
||
}
|
||
/* 未登录引导样式 */
|
||
.msg__unlogin-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 40rpx 32rpx;
|
||
}
|
||
.msg__unlogin-btn {
|
||
margin-top: -20rpx;
|
||
height: 72rpx;
|
||
padding: 0 48rpx;
|
||
border-radius: 36rpx;
|
||
background: #00D26A;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: none;
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 210, 106, 0.25);
|
||
}
|
||
.msg__unlogin-btn-txt {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #ffffff;
|
||
}
|
||
</style>
|