From 107cbbe971431a1fef0731938ff2fc381026f4a2 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 8 Oct 2026 16:28:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=A8=A1=E5=9D=97=E5=AE=8C?= =?UTF-8?q?=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/admin/lang/zh-cn/messages.php | 3 +- .../application/api/controller/Message.php | 47 +++++++++ backend/application/common/model/Messages.php | 2 +- .../common/service/EasyWeChatService.php | 12 +++ .../common/service/MessageService.php | 53 ++++++++++- .../service/SubscribeMessageService.php | 35 ++++++- frontend/miniapp/app.js | 35 +++++++ frontend/miniapp/config/api.js | 1 + frontend/miniapp/custom-tab-bar/data.js | 6 +- frontend/miniapp/custom-tab-bar/index.js | 57 +++++++++++ frontend/miniapp/custom-tab-bar/index.wxml | 6 +- frontend/miniapp/custom-tab-bar/index.wxss | 18 ++++ frontend/miniapp/pages/message/list/index.js | 95 +++++++++++++++---- .../miniapp/pages/message/list/index.json | 1 + .../miniapp/pages/message/list/index.wxml | 24 ++++- .../miniapp/pages/message/list/index.wxss | 29 ++++++ .../miniapp/services/message/fetchMessage.js | 5 + 17 files changed, 401 insertions(+), 28 deletions(-) diff --git a/backend/application/admin/lang/zh-cn/messages.php b/backend/application/admin/lang/zh-cn/messages.php index f8cf33d..e2b6643 100644 --- a/backend/application/admin/lang/zh-cn/messages.php +++ b/backend/application/admin/lang/zh-cn/messages.php @@ -4,7 +4,8 @@ return [ 'Id' => '主键ID', 'Message_type' => '消息类型', 'Message_type 0' => '系统消息', - 'Message_type 1' => '聊天消息', + 'Message_type 1' => '公告消息', + 'Message_type 2' => '需求消息', 'From_user_id' => '发消息用户ID', 'To_user_id' => '收消息用户ID', 'Title' => '消息标题', diff --git a/backend/application/api/controller/Message.php b/backend/application/api/controller/Message.php index 52bc326..9dd2169 100644 --- a/backend/application/api/controller/Message.php +++ b/backend/application/api/controller/Message.php @@ -19,12 +19,32 @@ class Message extends Api $user_id = $this->auth->id ?? -1; $page = $this->request->get('page', 1); $page_size = $this->request->get('pageSize', 10); + //消息类型筛选:0=系统消息,1=公告消息,2=需求消息,不传=全部 + $message_type = $this->request->get('message_type', ''); $where = [ 'to_user_id' => $user_id ]; + if ($message_type !== '' && $message_type !== null) { + $where['message_type'] = $message_type; + } + //总未读数(兼容旧字段)+ 分类未读数(前端tab角标用) $unreadCount = MessageService::where($where + ['is_read' => CommonService::STATUS_OFF])->count(); + $unreadList = MessageService::where(['to_user_id' => $user_id, 'is_read' => CommonService::STATUS_OFF]) + ->field('message_type, count(*) as total') + ->group('message_type') + ->select()->toArray(); + $unreadMap = [ + MessageService::TYPE_SYSTEM => 0, + MessageService::TYPE_NOTICE => 0, + MessageService::TYPE_DEMAND => 0, + ]; + foreach ($unreadList as $item) { + $unreadMap[$item['message_type']] = (int)$item['total']; + } + $list = MessageService::getList($where, $page_size, 'id desc')->toArray(); $list['unread_count'] = $unreadCount; + $list['unread_map'] = $unreadMap; $this->success('', $list); } catch (\Exception $e) { @@ -36,6 +56,27 @@ class Message extends Api } + /** + * 未读消息数(供底部tabbar红点等轻量场景) + * @return void + */ + public function unreadCount() + { + try { + $user_id = $this->auth->id ?? -1; + $count = MessageService::where([ + 'to_user_id' => $user_id, + 'is_read' => CommonService::STATUS_OFF + ])->count(); + $this->success('', ['unread_count' => (int)$count]); + } catch (\Exception $e) { + if ($e instanceof HttpResponseException) { + throw $e; + } + $this->error($e->getMessage()); + } + } + /** * 详情 * @return void @@ -50,6 +91,7 @@ class Message extends Api } /** + * 设为已读 * @return void */ public function setRead() @@ -58,9 +100,14 @@ class Message extends Api $user_id = $this->auth->id ?? -1; $id = $this->request->post('id', 0); $isAll = $this->request->post('isAll', 0); + //isAll 时可携带 message_type,仅将该类型全部设为已读 + $message_type = $this->request->post('message_type', ''); $where = ['id' => $id, 'to_user_id' => $user_id]; if ($isAll) { unset($where['id']); + if ($message_type !== '' && $message_type !== null) { + $where['message_type'] = $message_type; + } } $res = MessageService::where($where)->update(['is_read' => CommonService::STATUS_ON]); if ($res === false) { diff --git a/backend/application/common/model/Messages.php b/backend/application/common/model/Messages.php index 0c3bfa7..f97ff8f 100644 --- a/backend/application/common/model/Messages.php +++ b/backend/application/common/model/Messages.php @@ -38,7 +38,7 @@ class Messages extends Model public function getMessageTypeList() { - return ['0' => __('Message_type 0'), '1' => __('Message_type 1')]; + return ['0' => __('Message_type 0'), '1' => __('Message_type 1'), '2' => __('Message_type 2')]; } diff --git a/backend/application/common/service/EasyWeChatService.php b/backend/application/common/service/EasyWeChatService.php index d84c8ac..7b2af73 100644 --- a/backend/application/common/service/EasyWeChatService.php +++ b/backend/application/common/service/EasyWeChatService.php @@ -112,6 +112,7 @@ class EasyWeChatService /** * 发送小程序订阅消息 + * 只要走了订阅消息,就同步发送一条站内消息(失败不影响订阅消息主流程) * @param $user_id * @param $template_id * @param array $msgData @@ -123,6 +124,17 @@ class EasyWeChatService $response = $this->app->getClient()->postJson('/cgi-bin/message/subscribe/send', $data); $res = $response->toArray(false); Log::info('发送订阅消息-res' . json_encode(['data' => $data, 'res' => $res], JSON_UNESCAPED_UNICODE)); + + //同步写站内消息(extra_json.page 直接复用订阅消息的跳转页面) + try { + MessageService::createBySubscribe($user_id, $template_id, $msgData, $data['page'] ?? ''); + } catch (\Exception $e) { + Log::error('订阅消息同步站内消息失败:' . json_encode([ + 'user_id' => $user_id, + 'template_id' => $template_id, + 'error' => $e->getMessage(), + ], JSON_UNESCAPED_UNICODE)); + } } /** diff --git a/backend/application/common/service/MessageService.php b/backend/application/common/service/MessageService.php index 345133d..6b965d9 100644 --- a/backend/application/common/service/MessageService.php +++ b/backend/application/common/service/MessageService.php @@ -13,6 +13,11 @@ class MessageService /** @var Messages */ public static $Model = Messages::class; + // 消息类型 + const TYPE_SYSTEM = '0'; //系统消息:账户、审核、安全类 + const TYPE_NOTICE = '1'; //公告消息:平台运营公告 + const TYPE_DEMAND = '2'; //需求消息:需求/工单/合同/订单等业务流程 + /** * @param $to_user_id @@ -23,10 +28,10 @@ class MessageService * @return mixed * @throws \Exception */ - public static function createData($to_user_id, $title, $content, $extra_json = [], $from_user_id = 0) + public static function createData($to_user_id, $title, $content, $extra_json = [], $from_user_id = 0, $message_type = self::TYPE_SYSTEM) { $data = [ - 'message_type' => CommonService::NO, + 'message_type' => $message_type, 'from_user_id' => $from_user_id, 'to_user_id' => $to_user_id, 'title' => $title, @@ -43,4 +48,48 @@ class MessageService return $res; } + + /** + * 发送订阅消息时同步写入站内消息 + * 数据来源:MSG_MAP 中模板的 name / message_type / valueNames / remark / page + * @param int $to_user_id 收件用户 + * @param string $template_id 订阅消息模板ID + * @param array $msgData sendMsg 的原始参数 ['values'=>[], 'param'=>[], 'page'=>''] + * @param string $page 打包后的跳转页面(含query),如 pages/demand/list/index?opType=my&demandId=1 + * @return mixed + */ + public static function createBySubscribe($to_user_id, $template_id, array $msgData = [], $page = '') + { + $template = SubscribeMessageService::MSG_MAP[$template_id] ?? []; + if (!$template) { + //未定义的模板不写站内消息 + return false; + } + + //按模板字段名组装可读内容:工单号:xxx;服务项目:xxx;...;备注:xxx + $values = $msgData['values'] ?? []; + $valueNames = $template['valueNames'] ?? []; + $lines = []; + foreach ($valueNames as $key => $name) { + if (isset($values[$key]) && $values[$key] !== '') { + $lines[] = $name . ':' . $values[$key]; + } + } + if (!empty($template['remark'])) { + $lines[] = '备注:' . $template['remark']; + } + $content = implode("\n", $lines); + + return self::createData( + $to_user_id, + $template['name'] ?? '系统通知', + $content, + [ + 'page' => $page ?: ($template['page'] ?? ''), + 'template_id' => $template_id, + ], + 0, //系统发出 + $template['message_type'] ?? self::TYPE_SYSTEM + ); + } } \ No newline at end of file diff --git a/backend/application/common/service/SubscribeMessageService.php b/backend/application/common/service/SubscribeMessageService.php index 667d508..f631e36 100644 --- a/backend/application/common/service/SubscribeMessageService.php +++ b/backend/application/common/service/SubscribeMessageService.php @@ -24,6 +24,8 @@ class SubscribeMessageService const MSG_MAP = [ self::MSG_TEMP_DEMAND_RECEIVE => [ + 'name' => '接单成功通知', //站内消息标题 + 'message_type' => MessageService::TYPE_DEMAND, //站内消息类型 'page' => self::PAGE_DEMAND_LIST . '?opType=myQuote', //我的报价需求列表 'remark' => '恭喜中标,请到我投标的需求确认接单!', /* @@ -32,9 +34,12 @@ class SubscribeMessageService 任务名称:共享精英团队logo设计 备注:接单成功,请尽快安排排产 */ - 'fields' => ['character_string1', 'thing9', 'thing10', 'thing4'] + 'fields' => ['character_string1', 'thing9', 'thing10', 'thing4'], + 'valueNames' => ['工单号', '服务项目', '任务名称', '备注'], //与fields一一对应,用于站内消息内容组装 ], self::MSG_TEMP_DEMAND_MATCH_SUCCESS => [ + 'name' => '工单匹配成功通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_LIST . '?opType=my', //我发布的需求列表 'remark' => '匹配工程师成功,请到需求详情查看沟通', /* @@ -43,8 +48,11 @@ class SubscribeMessageService 温馨提醒:您的工单已成功匹配,请您电话保持畅通。 */ 'fields' => ['thing1', 'phrase2', 'thing5'], + 'valueNames' => ['匹配服务方', '服务类别', '温馨提醒'], ], self::MSG_TEMP_DEMAND_SETTLE => [ + 'name' => '工单结算通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_LIST . '?opType=myQuote', //我报价的需求列表 'remark' => '该工单已结算完成', /* @@ -53,8 +61,11 @@ class SubscribeMessageService 结算说明:订单已收货结算完成 */ 'fields' => ['character_string4', 'amount1', 'thing2'], + 'valueNames' => ['订单编号', '结算金额', '结算说明'], ], self::MSG_TEMP_WORK_START => [ + 'name' => '项目开工通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_LIST . '?opType=myQuote', //我报价的需求列表 'remark' => '客户已支付预付款,可以开始工作', /* @@ -63,8 +74,11 @@ class SubscribeMessageService 备注:请在上下班时间安排对工人进行考勤 */ 'fields' => ['thing1', 'time2', 'thing4'], + 'valueNames' => ['项目名称', '工作日期', '备注'], ], self::MSG_TEMP_WORK_ACCEPT => [ + 'name' => '工单验收通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_LIST . '?opType=my', //我发布的需求列表 'remark' => '需求开发工作已完成,请前去验收成果', /* @@ -74,8 +88,11 @@ class SubscribeMessageService 温馨提醒:登录平台确保验收,给予工程师中肯评价吧~ */ 'fields' => ['character_string1', 'thing8', 'thing2', 'thing5'], + 'valueNames' => ['工单编号', '工单标题', '验收内容', '温馨提醒'], ], self::MSG_TEMP_WORK_ACCEPT_RESULT => [ + 'name' => '验收结果通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_LIST . '?opType=myQuote', //我报价的需求列表 'remark' => '客户已验收并支付尾款,可以发送源码', 'remarkFail' => '客户验收未通过,请根据要求修改', @@ -86,8 +103,11 @@ class SubscribeMessageService 原因:不符合需求描述的内容 */ 'fields' => ['thing1', 'thing3', 'thing5', 'thing4'], + 'valueNames' => ['验收阶段', '验收结果', '备注', '原因'], ], self::MSG_TEMP_CONTRACT_PAY => [ + 'name' => '合同付款通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_ORDER_LIST, //我的订单列表 'remark' => '请到我的订单完成合同尾款支付', /* @@ -98,8 +118,11 @@ class SubscribeMessageService 备注:您有以上合同需要付款,请及时发起流程 */ 'fields' => ['thing1', 'character_string2', 'amount5', 'time4', 'thing3'], + 'valueNames' => ['项目名称', '合同编号', '结算金额', '结算周期', '备注'], ], self::MSG_TEMP_CONTRACT_SIGN => [ + 'name' => '合同签约通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_LIST . '?opType=my', //我发布的需求列表 'remark' => '需求已确认,请前去完成合同签约', /* @@ -109,8 +132,11 @@ class SubscribeMessageService 备注:【签约成功】请及时缴纳首期款办理入住 */ 'fields' => ['thing1', 'thing2', 'amount12', 'thing5'], + 'valueNames' => ['合同编号', '项目名称', '签约金额', '备注'], ], self::MSG_TEMP_ORDER_SHIPPED => [ + 'name' => '订单发货通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_ORDER_LIST, //订单列表 'remark' => '发货成功,请到订单详情查看激活码', /* @@ -120,8 +146,11 @@ class SubscribeMessageService 备注:快递配送中,请留意电话或者短信 */ 'fields' => ['character_string2', 'thing1', 'phrase19', 'thing6'], + 'valueNames' => ['订单号', '商品名称', '订单状态', '备注'], ], self::MSG_TEMP_CONFIRM_RECEIPT => [ + 'name' => '确认收货通知', + 'message_type' => MessageService::TYPE_DEMAND, 'page' => self::PAGE_DEMAND_DETAIL, //需求详情(发送时携带需求id) 'remark' => '开发成果已交付,请查看交付内容并确认收货', /* @@ -131,8 +160,11 @@ class SubscribeMessageService 备注:开发成果已交付,请查看交付内容并确认收货 */ 'fields' => ['character_string1', 'character_string2', 'amount3', 'thing5'], + 'valueNames' => ['产品名称', '订单号', '付款金额', '备注'], ], self::MSG_TEMP_DEVELOPER_JOIN => [ + 'name' => '入驻审核通知', + 'message_type' => MessageService::TYPE_SYSTEM, //入驻审核属系统消息 'page' => self::PAGE_HOME, //首页 'remark' => '开发者入驻成功,前去接单大厅接单', /* @@ -142,6 +174,7 @@ class SubscribeMessageService 备注:备注说明 */ 'fields' => ['thing2', 'time3', 'phrase4', 'thing5'], + 'valueNames' => ['申请人', '申请时间', '状态', '备注'], ], ]; diff --git a/frontend/miniapp/app.js b/frontend/miniapp/app.js index 48d90b1..d0544b7 100644 --- a/frontend/miniapp/app.js +++ b/frontend/miniapp/app.js @@ -25,8 +25,43 @@ App({ }); }, }); + //启动未读消息轮询(tabbar红点) + this.startUnreadPolling(); }, onShow: function () { updateManager(); + //从后台切回前台,立即刷新未读红点 + this.refreshUnread(); + }, + onHide: function () {}, + globalData: { + unread: 0, //未读消息数(tabbar红点共享缓存) + }, + + /** + * 未读消息轮询:小程序前台每10s刷新一次底部tabbar红点 + * (切后台后JS执行会被系统挂起,定时器自然暂停,回前台由onShow立即补一次) + */ + startUnreadPolling: function () { + if (this.unreadTimer) { + return; + } + this.unreadTimer = setInterval(() => { + this.refreshUnread(); + }, 10000); + }, + + /** + * 刷新当前 tab 页 tabbar 实例的红点(非tab页无tabbar实例,自动跳过) + */ + refreshUnread: function () { + if (!wx.getStorageSync('token')) { + return; + } + const page = getCurrentPages().pop(); + const tabBar = page && typeof page.getTabBar === 'function' ? page.getTabBar() : null; + if (tabBar && typeof tabBar.refreshUnread === 'function') { + tabBar.refreshUnread(); + } }, }); diff --git a/frontend/miniapp/config/api.js b/frontend/miniapp/config/api.js index 0b52cbe..46add54 100644 --- a/frontend/miniapp/config/api.js +++ b/frontend/miniapp/config/api.js @@ -107,5 +107,6 @@ module.exports = { MessageList: ApiRootUrl + 'message/list', //消息列表 MessageDetail: ApiRootUrl + 'message/detail', //消息详情 MessageSetRead: ApiRootUrl + 'message/setRead', //设为已读 + MessageUnreadCount: ApiRootUrl + 'message/unreadCount', //未读消息数(tabbar红点) }; \ No newline at end of file diff --git a/frontend/miniapp/custom-tab-bar/data.js b/frontend/miniapp/custom-tab-bar/data.js index 9162680..3e844ff 100644 --- a/frontend/miniapp/custom-tab-bar/data.js +++ b/frontend/miniapp/custom-tab-bar/data.js @@ -1,21 +1,25 @@ export default [{ + key: 'home', icon: 'home', text: '首页', url: 'pages/home/home', }, { + key: 'goods', icon: 'sort', text: '软件', url: 'pages/goods/list/index', }, { + key: 'message', icon: 'comment', text: '消息', url: 'pages/message/list/index', }, { + key: 'usercenter', icon: 'person', text: '个人中心', url: 'pages/usercenter/index', }, -]; \ No newline at end of file +]; diff --git a/frontend/miniapp/custom-tab-bar/index.js b/frontend/miniapp/custom-tab-bar/index.js index 90c58df..37fc7a5 100644 --- a/frontend/miniapp/custom-tab-bar/index.js +++ b/frontend/miniapp/custom-tab-bar/index.js @@ -1,8 +1,28 @@ import TabMenu from './data'; +import { fetchMessageUnreadCount } from '../services/message/fetchMessage'; Component({ data: { active: 0, list: TabMenu, + unread: 0, //未读消息数,>0 时消息tab显示红点 + }, + + lifetimes: { + attached() { + //先用 globalData 缓存渲染,避免切换 tab 时红点闪没,再拉取最新 + const app = getApp(); + if (app && Number(app.globalData?.unread) > 0) { + this.setData({ unread: Number(app.globalData.unread) }); + } + this.refreshUnread(); + }, + }, + + //所在 tab 页面显示时刷新(切换 tab、从其他页面返回均会触发) + pageLifetimes: { + show() { + this.refreshUnread(); + }, }, methods: { @@ -25,5 +45,42 @@ Component({ ); this.setData({ active }); }, + + /** + * 从服务端拉取未读消息数 + * 未登录/请求失败时保持 0(不显示红点),不影响 tabbar 正常使用 + * 5s 内重复触发只发一次请求(app轮询/页面onShow/tabbar show 多处触发) + */ + refreshUnread(force = false) { + if (!wx.getStorageSync('token')) { + this.setData({ unread: 0 }); + const app = getApp(); + if (app) app.globalData.unread = 0; + return; + } + const now = Date.now(); + if (!force && now - (this._lastFetchAt || 0) < 5000) { + return; + } + this._lastFetchAt = now; + fetchMessageUnreadCount().then((res) => { + if (res.code == 1) { + const unread = Number(res.data?.unread_count) || 0; + this.setData({ unread }); + const app = getApp(); + if (app) app.globalData.unread = unread; + } + }).catch(() => {}); + }, + + /** + * 供消息页主动同步未读数(读消息/全部已读后红点实时消失) + */ + setUnread(count) { + const unread = Number(count) || 0; + this.setData({ unread }); + const app = getApp(); + if (app) app.globalData.unread = unread; + }, }, }); diff --git a/frontend/miniapp/custom-tab-bar/index.wxml b/frontend/miniapp/custom-tab-bar/index.wxml index 187a519..6fab572 100644 --- a/frontend/miniapp/custom-tab-bar/index.wxml +++ b/frontend/miniapp/custom-tab-bar/index.wxml @@ -11,7 +11,11 @@ wx:key="index" > - + + + + + {{ item.text }} diff --git a/frontend/miniapp/custom-tab-bar/index.wxss b/frontend/miniapp/custom-tab-bar/index.wxss index c2fd45d..a65e9c6 100644 --- a/frontend/miniapp/custom-tab-bar/index.wxss +++ b/frontend/miniapp/custom-tab-bar/index.wxss @@ -11,6 +11,24 @@ margin-top: 4rpx; } +/* tab 图标容器:红点定位基准 */ +.tab-icon-wrap { + position: relative; +} + +/* 消息tab未读红点 */ +.unread-dot { + position: absolute; + top: -2rpx; + right: -14rpx; + width: 16rpx; + height: 16rpx; + border-radius: 50%; + background: #fa5151; + border: 2rpx solid #ffffff; + box-sizing: content-box; +} + /* 底部导航整体:顶部细分隔线 + 安全区自适应 */ .custom-tab-bar { background: rgba(255, 255, 255, 0.96); diff --git a/frontend/miniapp/pages/message/list/index.js b/frontend/miniapp/pages/message/list/index.js index 0d79a2c..8e6edf1 100644 --- a/frontend/miniapp/pages/message/list/index.js +++ b/frontend/miniapp/pages/message/list/index.js @@ -1,7 +1,21 @@ // pages/message/index.js import { fetchMessageList, messageSetRead } from '../../../services/message/fetchMessage'; + +// 消息类型 tab:与后端 message_type 对齐(''=全部) +const TYPE_TABS = [ + { key: '', label: '全部' }, + { key: '0', label: '系统消息' }, + { key: '1', label: '公告消息' }, + { key: '2', label: '需求消息' }, +]; + Page({ data: { + tabs: TYPE_TABS, + currentType: '', //当前筛选的消息类型:''=全部 + currentTabIndex: 0, + unreadMap: { '0': 0, '1': 0, '2': 0 }, //分类未读数 + totalUnread: 0, //总未读数 list: [], page: 1, pageSize: 10, @@ -18,51 +32,99 @@ Page({ }, loadList(refresh = false) { - let { pageSize, list } = this.data; + let { pageSize, list, currentType } = this.data; const page = refresh ? 1 : this.data.page; let params = { page: page, - message_type: 0, pageSize: pageSize, }; + //全部时不传 message_type,由后端返回全类型 + if (currentType !== '') { + params.message_type = currentType; + } fetchMessageList(params).then((res) => { - console.log(res) let newList = res.data.data; let hasMore = res.data.current_page < res.data.last_page; + let unreadMap = res.data.unread_map || { '0': 0, '1': 0, '2': 0 }; + let totalUnread = Number(unreadMap['0'] || 0) + Number(unreadMap['1'] || 0) + Number(unreadMap['2'] || 0); this.setData({ list: refresh ? newList : list.concat(newList), page: page + 1, unreadCount: res.data.unread_count, + unreadMap: unreadMap, + totalUnread: totalUnread, hasMore: hasMore, loading: false }); + //同步底部tabbar红点 + this.syncTabBarUnread(totalUnread); }); }, + /** + * 同步底部tabbar消息红点 + * @param {number} count 总未读数 + */ + syncTabBarUnread(count) { + const tabBar = typeof this.getTabBar === 'function' && this.getTabBar(); + if (tabBar && typeof tabBar.setUnread === 'function') { + tabBar.setUnread(count); + } + }, + + //切换消息类型tab + onTabsChange(e) { + const index = Number(e.detail.value); + const tab = this.data.tabs[index] || this.data.tabs[0]; + this.setData({ + currentTabIndex: index, + currentType: tab.key + }); + this.loadList(true); + }, + showDialog(e) { const id = e.currentTarget.dataset.id; const item = this.data.list.find(msg => msg.id == id); + const jumpPage = item?.extra_json?.page || ''; this.setData({ showTextAndTitle: true, - currentMessage: item || {} + currentMessage: item || {}, + //携带跳转页面时提供「前往查看」 + confirmBtn: jumpPage ? '前往查看' : '关闭' }); - messageSetRead({ id: id }).then((res) => { - console.log(res) - // 把查找到的列表中的元素的is_read字段设为1 - if (item) { - const idx = this.data.list.findIndex(msg => msg.id == id); - if (idx !== -1) { - const key = `list[${idx}].is_read`; - this.setData({ [key]: 1 }); - } + messageSetRead({ id: id }).then(() => { + if (!item) { + return; } + const idx = this.data.list.findIndex(msg => msg.id == id); + if (idx === -1) { + return; + } + //原来未读的消息:本地未读数-1并同步tabbar红点 + if (!this.data.list[idx].is_read) { + const typeKey = String(item.message_type); + const unreadMap = { ...this.data.unreadMap }; + unreadMap[typeKey] = Math.max(Number(unreadMap[typeKey] || 0) - 1, 0); + const totalUnread = Math.max(this.data.totalUnread - 1, 0); + this.setData({ unreadMap, totalUnread }); + this.syncTabBarUnread(totalUnread); + } + this.setData({ [`list[${idx}].is_read`]: 1 }); }); }, - closeDialog() { + //弹窗确认:有跳转页面则前往,否则仅关闭 + onDialogConfirm() { + const jumpPage = this.data.currentMessage?.extra_json?.page || ''; this.setData({ showTextAndTitle: false }); + if (jumpPage) { + wx.navigateTo({ + url: '/' + String(jumpPage).replace(/^\//, '') + }); + } }, onPullDownRefresh() { @@ -76,8 +138,7 @@ Page({ }, onClearUnreadTap() { - messageSetRead({ isAll: 1 }).then((res) => { - console.log(res) + messageSetRead({ isAll: 1 }).then(() => { wx.showToast({ title: '全部已读', icon: 'success' }); this.loadList(true); }); @@ -95,4 +156,4 @@ Page({ }, onShareAppMessage() { } -}); \ No newline at end of file +}); diff --git a/frontend/miniapp/pages/message/list/index.json b/frontend/miniapp/pages/message/list/index.json index 2896fff..de8f570 100644 --- a/frontend/miniapp/pages/message/list/index.json +++ b/frontend/miniapp/pages/message/list/index.json @@ -10,6 +10,7 @@ "t-badge": "tdesign-miniprogram/badge/badge", "t-dialog": "tdesign-miniprogram/dialog/dialog", "t-empty": "tdesign-miniprogram/empty/empty", + "t-tag": "tdesign-miniprogram/tag/tag", "t-divider": "tdesign-miniprogram/divider/divider" }, "navigationBarTitleText": "消息列表" diff --git a/frontend/miniapp/pages/message/list/index.wxml b/frontend/miniapp/pages/message/list/index.wxml index ee23846..a499b3d 100644 --- a/frontend/miniapp/pages/message/list/index.wxml +++ b/frontend/miniapp/pages/message/list/index.wxml @@ -10,11 +10,26 @@ - + + + + + + - + + + {{item.title}} + {{item.message_type_text}} + {{_index.ellipsis(item.content, 22)}} {{item.createtime_text}} @@ -25,11 +40,12 @@ - + + {{currentMessage.message_type_text}} {{currentMessage.content}} - \ No newline at end of file + diff --git a/frontend/miniapp/pages/message/list/index.wxss b/frontend/miniapp/pages/message/list/index.wxss index 4b73e6b..06c225a 100644 --- a/frontend/miniapp/pages/message/list/index.wxss +++ b/frontend/miniapp/pages/message/list/index.wxss @@ -43,4 +43,33 @@ .empty-container { min-height: 100vh; padding-top: 50%; +} + +/* 消息标题行:标题 + 类型标签 */ +.msg-title { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; +} + +.msg-title-text { + margin-right: 12rpx; +} + +.msg-type-tag { + flex-shrink: 0; +} + +/* 弹窗内容:类型标识 + 正文换行 */ +.content-container { + white-space: pre-line; + word-break: break-all; + text-align: left; +} + +.content-type { + font-size: 22rpx; + color: var(--text-secondary, #999); + margin-bottom: 12rpx; } \ No newline at end of file diff --git a/frontend/miniapp/services/message/fetchMessage.js b/frontend/miniapp/services/message/fetchMessage.js index c24779b..f0bbec8 100644 --- a/frontend/miniapp/services/message/fetchMessage.js +++ b/frontend/miniapp/services/message/fetchMessage.js @@ -11,4 +11,9 @@ export function fetchMessageDetail(id) { /** 获取提现申请 */ export function messageSetRead(param) { return wx.$util.request(wx.$api.MessageSetRead, param, 'POST'); +} + +/** 获取未读消息数(tabbar红点) */ +export function fetchMessageUnreadCount() { + return wx.$util.request(wx.$api.MessageUnreadCount, {}, 'GET'); } \ No newline at end of file