2026-02-06 17:30:16 +08:00

589 lines
14 KiB
Vue
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="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
:title="'消息'"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template v-slot:right>
<view class="nav-right" >
<view class="collect-img" @click="clearMsg" >
<image class="img-icon" :src="clearImg" mode="aspectFill" />
</view>
</view>
</template>
</uni-nav-bar>
</view>
<view class="msg-page">
<!-- 四宫格入口 -->
<view class="shortcut-grid">
<view class="grid-item" @click="goBenefit">
<view class="icon-wrap">
<image :src="fuliImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Welfare > 0">{{ badgeData.Module_Welfare }}</view>
</view>
<text class="label">福利</text>
</view>
<view class="grid-item" @click="goOrder">
<view class="icon-wrap ">
<image :src="orderImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Order > 0">{{ badgeData.Module_Order }}</view>
</view>
<text class="label">订单</text>
</view>
<view class="grid-item" @click="goFollow">
<view class="icon-wrap">
<image :src="followImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Relation > 0">{{ badgeData.Module_Relation }}</view>
</view>
<text class="label">随访</text>
</view>
<view class="grid-item" @click="goReply">
<view class="icon-wrap">
<image :src="replyImg" class="imgcell"/>
<view class="badge" v-if="badgeData.Module_Comment > 0">{{ badgeData.Module_Comment }}</view>
</view>
<text class="label">回复我的</text>
</view>
</view>
<!-- 消息列表 -->
<scroll-view
class="msg-list"
scroll-y="true"
:show-scrollbar="false"
:refresher-enabled="true"
:refresher-triggered="refreshing"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
:lower-threshold="100"
>
<!-- 回复我的模块 - 聊天列表样式 -->
<view v-if="currentModule === 4" class="chat-list">
<view class="chat-item" v-for="(msg, idx) in msgList" :key="msg.id" @click="goMsgDetail(msg)">
<view class="avatar">
<image :src="msg.avatar || '/static/default-avatar.png'" mode="aspectFill" @error="handleImageError"></image>
</view>
<view class="chat-content">
<view class="chat-header">
<text class="sender-name">{{ msg.sender_name || msg.title }}</text>
<text class="chat-time">{{ msg.create_date }}</text>
</view>
<view class="message-preview">{{ msg.content }}</view>
</view>
</view>
</view>
<!-- 其他模块 - 卡片样式 -->
<view v-else>
<view class="card" v-for="(msg, idx) in msgList" :key="msg.id" @click="goMsgDetail(msg)">
<view class="card-title" :class="{'active':msg.is_read==0}">{{ msg.title }}</view>
<view class="card-content">{{ msg.content }}</view>
<view class="card-time">{{ msg.create_date }}</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="msgList.length === 0 && !loading && !refreshing" class="empty-state">
<empty></empty>
</view>
<!-- 加载更多提示 -->
<view v-if="loading && msgList.length > 0" class="loading-more">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 没有更多数据提示 -->
<view v-if="noMore && msgList.length > 0 && !loading" class="no-more">
<text class="no-more-text"> 没有更多数据了 </text>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import api from '@/api/api.js';
import docUrl from '@/utils/docUrl';
import navTo from '@/utils/navTo';
import clearImg from '@/static/clear_message.png';
import empty from '@/components/empty/empty.vue';
import fuliImg from '@/static/fu_message_big.png';
import orderImg from '@/static/ding_message_big.png';
import followImg from '@/static/sui_message_big.png';
import replyImg from '@/static/hui_message_big.png';
import {onBackPress} from '@dcloudio/uni-app';
onBackPress(() => {
plus.runtime.quit();
return true;
});
// 角标数据
const badgeData = ref({
Module_Order: 0, // 订单
Module_Comment: 0, // 回复我的
Module_Welfare: 0, // 福利
Module_Relation: 0 // 随访
});
// 消息列表数据
const msgList = ref([]);
const currentModule = ref(1); // 当前选中的模块默认显示模块1的消息
// 刷新/加载状态
const refreshing = ref(false);
const loading = ref(false);
const noMore = ref(false);
const page = ref(1);
const pageSize = 10;
// 重置分页状态
const resetPagination = () => {
page.value = 1;
noMore.value = false;
msgList.value = [];
};
const goBack = () => {
plus.runtime.quit();
};
// 切换模块
const switchModule = (module) => {
if (currentModule.value === module && msgList.value.length > 0) return; // 避免重复加载
currentModule.value = module;
resetPagination();
getAppMesageList(true);
};
const goBenefit = () => switchModule(1);
const goOrder = () => switchModule(2);
const goFollow = () => switchModule(3);
const goReply = () => switchModule(4);
const readMsg = (id) => {
api.appMesageRead({
id: id
}).then(res => {
if(res.code==200){
msgList.value.forEach(item => {
if(item.id==id){
item.is_read=1;
}
});
}
});
}
const clearMsg = () => {
uni.showModal({
title: '提醒',
content: '是否要清除所有未读消息?',
success: (res) => {
if (res.confirm) {
api.appMesageRead().then(res => {
console.log(res);
});
getUnReadList();
}
}
});
}
const goMsgDetail = (msg) => {
console.log(msg);
if(msg.is_read==0){
readMsg(msg.id);
};
//readMsg(msg.id);
if(msg.extra.module==4){
uni.sendNativeEvent('goCourseDetail', {
msg: {
id:msg.extra.id
}
});
}else{
let url="";
if(msg.extra.module==1 && msg.extra.type==36){
url=`/pages_app/myPoint/myPoint?from=msg`
}else if(msg.extra.module==1 && msg.extra.type==31){
url=`/pages_app/myPoint/myPoint?from=msg`
}else if(msg.extra.module==1 && msg.extra.type==35){
uni.setStorageSync('lookWelfare', 'useWelfare');
url=`/pages_app/myWelfare/myWelfare?from=msg`
}else if(msg.extra.module==3 && msg.extra.type==12){
url=`/pages_app/myPatient/myPatient`
}
navTo({
url: url
})
}
}
// 下拉刷新
const onRefresh = async () => {
if (refreshing.value) return;
refreshing.value = true;
try {
// 重置分页并重新获取数据
resetPagination();
await Promise.all([
getAppMesageList(true),
getUnReadList()
]);
} catch (err) {
console.error('刷新失败:', err);
uni.showToast({
title: '刷新失败',
icon: 'none'
});
} finally {
refreshing.value = false;
}
};
// 上拉加载更多
const onLoadMore = async () => {
if (loading.value || noMore.value || refreshing.value) return;
loading.value = true;
try {
page.value++;
await getAppMesageList(false);
} catch (err) {
// 加载失败时回退页码
page.value = Math.max(1, page.value - 1);
console.error('加载更多失败:', err);
uni.showToast({
title: '加载失败',
icon: 'none'
});
} finally {
loading.value = false;
}
};
const getUnReadList = () => {
return api.unReadList({}).then(res => {
console.log(res);
if (res.code === 200 && res.data) {
// 更新角标数据
badgeData.value = {
Module_Order: parseInt(res.data.Module_Order) || 0,
Module_Comment: parseInt(res.data.Module_Comment) || 0,
Module_Welfare: parseInt(res.data.Module_Welfare) || 0,
Module_Relation: parseInt(res.data.Module_Relation) || 0
};
}
return res;
}).catch(err => {
console.error('获取未读消息列表失败:', err);
throw err;
});
};
// 获取消息列表
// isRefresh: true 表示刷新false 表示加载更多
const getAppMesageList = (isRefresh = false) => {
return api.appMesageList({
module: currentModule.value,
page: page.value,
pageSize: pageSize
}).then(res => {
console.log(res);
if (res.code === 200 && res.data && res.data.list) {
const newList = res.data.list.map(item => ({
id: item.id,
title: item.title,
content: item.content,
create_date: item.create_date,
is_read: item.is_read,
extra: item.extra,
// 从extra字段提取用户信息
sender_name: item.extra?.user_name || item.title,
avatar: item.extra?.user_photo ?
(item.extra.user_photo.startsWith('http') ?
item.extra.user_photo :
`${getBaseUrl()}${item.extra.user_photo}`) :
'/static/default-avatar.png'
}));
// 刷新时替换数据,加载更多时追加数据
if (isRefresh || page.value === 1) {
msgList.value = newList;
} else {
msgList.value = [...msgList.value, ...newList];
}
// 判断是否还有更多数据
if (res.isLastPage || newList.length < pageSize) {
noMore.value = true;
} else {
noMore.value = false;
}
} else if (res.code === 200 && (!res.data || !res.data.list || res.data.list.length === 0)) {
// 没有数据的情况
if (isRefresh || page.value === 1) {
msgList.value = [];
}
noMore.value = true;
}
return res;
}).catch(err => {
console.error('获取消息列表失败:', err);
throw err;
});
};
// 获取基础URL
const getBaseUrl = () => {
return docUrl;
};
// 图片加载错误处理
const handleImageError = (e) => {
// 设置默认头像
e.target.src = '/static/default-avatar.png';
};
onMounted(() => {
getUnReadList();
getAppMesageList(true); // 初始加载视为刷新
});
</script>
<style lang="scss" scoped>
$bg: #f5f6f7;
$text-primary: #333;
$text-secondary: #666;
$muted: #999;
$divider: #eeeeee;
$theme: #8B2316;
$nav-h: 140rpx; // 与 uni-nav-bar 保持一致
$grid-h: 200rpx; // 四宫格区域高度
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.imgcell{
width:74rpx;
height: 74rpx;
}
.nav-right{
display: flex;
align-items: center;
.collect-img{
width: 34rpx;
height: 34rpx;
}
}
.msg-page {
min-height: 100vh;
background: $bg;
}
.shortcut-grid {
position: fixed;
top: calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
z-index: 9;
height: $grid-h;
background: #fff;
display: grid;
grid-template-columns: repeat(4, 1fr);
padding: 30rpx 20rpx 10rpx;
column-gap: 10rpx;
row-gap: 10rpx;
border-bottom: 1rpx solid $divider;
.grid-item {
@include flex-center;
flex-direction: column;
gap: 12rpx;
.icon-wrap {
position: relative;
width: 92rpx;
height: 92rpx;
border-radius: 20rpx;
@include flex-center;
&.gift { background: linear-gradient(180deg, #ff6680, #ff4d6a); }
&.order { background: linear-gradient(180deg, #ffcc66, #ffb300); }
&.visit { background: linear-gradient(180deg, #4dd0e1, #2ec4b6); }
&.reply { background: linear-gradient(180deg, #6edc82, #2ecc71); }
.badge {
position: absolute;
right: -6rpx;
top: -6rpx;
min-width: 32rpx;
height: 32rpx;
border-radius: 50rpx;
background: #ff3b30;
color: #fff;
font-size: 22rpx;
@include flex-center;
padding: 0 6rpx;
}
}
.label {
font-size: 26rpx;
color: $text-secondary;
}
}
}
.msg-list {
position: fixed;
// 减去导航与宫格的高度
top: 360rpx; // 让列表起始位置在宫格下方
padding: 20rpx 24rpx 40rpx;
box-sizing: border-box;
bottom: 0rpx;
// 加载状态样式
.loading-more, .no-more, .empty-state {
@include flex-center;
padding: 28rpx 0;
.loading-text, .no-more-text, .empty-text {
font-size: 26rpx;
color: $muted;
}
}
// 空状态样式
.empty-state {
flex-direction: column;
padding: 100rpx 0;
gap: 20rpx;
}
// 加载动画
.loading-more {
gap: 16rpx;
.loading-spinner {
width: 32rpx;
height: 32rpx;
border: 3rpx solid #eee;
border-top-color: $theme;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 26rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.04);
margin-top: 26rpx;
.card-title {
font-size: 30rpx;
color: $text-primary;
font-weight: 600;
margin-bottom: 14rpx;
&.active{
color: red;
}
}
.card-content {
font-size: 28rpx;
color: $text-secondary;
line-height: 1.6;
margin-bottom: 14rpx;
}
.card-time {
font-size: 24rpx;
color: $muted;
text-align: right;
}
}
// 聊天列表样式
.chat-list {
.chat-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
background: #fff;
margin-top: 24rpx;
&:last-child {
border-bottom: none;
}
.avatar {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
overflow: hidden;
margin-right: 24rpx;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
}
.chat-content {
flex: 1;
min-width: 0;
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8rpx;
.sender-name {
font-size: 30rpx;
color: $text-primary;
font-weight: 500;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-time {
font-size: 24rpx;
color: $muted;
flex-shrink: 0;
margin-left: 16rpx;
}
}
.message-preview {
font-size: 28rpx;
color: $text-secondary;
line-height: 1.4;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
</style>