From 3e09131356762dba242eef91b4bd76f5c8ffac7d Mon Sep 17 00:00:00 2001 From: Jafeng <2998840497@qq.com> Date: Thu, 16 Apr 2026 11:22:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=A2=E9=98=85?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/message/chat.scss | 27 +++++ pages/message/index.vue | 74 ++++++++---- pages/message/message.vue | 56 ++++++++- utils/api.js | 4 +- utils/subscribe-message-config.js | 64 ++++++++++ utils/subscribe-message.js | 194 ++++++++++++++++++++++++++++++ 6 files changed, 393 insertions(+), 26 deletions(-) create mode 100644 utils/subscribe-message-config.js create mode 100644 utils/subscribe-message.js diff --git a/pages/message/chat.scss b/pages/message/chat.scss index b44dbf3..285285d 100644 --- a/pages/message/chat.scss +++ b/pages/message/chat.scss @@ -37,6 +37,13 @@ $primary-color: #0877F1; justify-content: space-between; } +.header-actions { + display: flex; + align-items: center; + gap: 12rpx; + flex-shrink: 0; +} + .patient-basic-info { display: flex; align-items: center; @@ -84,6 +91,26 @@ $primary-color: #0877F1; } } +.remind-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 8rpx 20rpx; + border-radius: 24rpx; + border: 2rpx solid #3876f6; + background: #fff; +} + +.remind-btn:active { + opacity: 0.85; +} + +.remind-btn-text { + font-size: 24rpx; + color: #3876f6; + line-height: 1.4; +} + .badge-processing { background: #d1ecf1; diff --git a/pages/message/index.vue b/pages/message/index.vue index 3dd1fb0..3c7b679 100644 --- a/pages/message/index.vue +++ b/pages/message/index.vue @@ -6,21 +6,26 @@ - - {{ patientInfo.name }} - {{ patientInfo.sex }} · {{ patientInfo.age }}岁 - - - {{ chatStatusInfo.badgeText }} - - - + + {{ patientInfo.name }} + {{ patientInfo.sex }} · {{ patientInfo.age }}岁 + + + + {{ chatStatusInfo.badgeText }} + + + 接收提醒 + + + + { }; // 处理咨询申请 -const handleApplyConsult = async () => { +const handleApplyConsult = async () => { try { uni.showModal({ title: "提示", @@ -1060,8 +1070,26 @@ const handleApplyConsult = async () => { title: error.message || "操作失败", icon: "none", }); - } -}; + } +}; + +const handleSubscribeReminder = async () => { + await requestConversationSubscribeMessage({ + role: SUBSCRIBE_MESSAGE_ROLE.PATIENT, + scene: SUBSCRIBE_MESSAGE_SCENE.CHAT, + conversationId: chatInfo.value.conversationID || "", + groupId: groupId.value || "", + corpId: corpId.value || "", + patientId: patientId.value || "", + userId: openid.value || account.value?.openid || "", + openid: openid.value || account.value?.openid || "", + unionid: account.value?.unionid || "", + extraData: { + orderStatus: orderStatus.value || "", + page: "pages/message/index", + }, + }); +}; // 页面卸载 onUnmounted(() => { diff --git a/pages/message/message.vue b/pages/message/message.vue index 0617fb8..6633d88 100644 --- a/pages/message/message.vue +++ b/pages/message/message.vue @@ -1,6 +1,5 @@ @@ -65,9 +68,16 @@ import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.j import { globalUnreadListenerManager } from "@/utils/global-unread-listener.js"; import useGroupAvatars from "./hooks/use-group-avatars.js"; import GroupAvatar from "@/components/group-avatar.vue"; +import { requestConversationSubscribeMessage } from "@/utils/subscribe-message"; +import { + SUBSCRIBE_MESSAGE_ROLE, + SUBSCRIBE_MESSAGE_SCENE, +} from "@/utils/subscribe-message-config"; // 获取登录状态 -const { account, openid, isIMInitialized, hasImCorpId } = storeToRefs(useAccountStore()); +const { account, openid, isIMInitialized, hasImCorpId, teams } = storeToRefs( + useAccountStore() +); const { initIMAfterLogin } = useAccountStore(); // 状态 @@ -492,6 +502,20 @@ const cleanMessageText = (text) => { return text.replace(/[\r\n]+/g, " ").trim(); }; +const handleSubscribeReminder = async () => { + await requestConversationSubscribeMessage({ + role: SUBSCRIBE_MESSAGE_ROLE.PATIENT, + scene: SUBSCRIBE_MESSAGE_SCENE.LIST, + corpId: teams.value.find((item) => item?.corpId)?.corpId || "", + userId: openid.value || account.value?.openid || "", + openid: openid.value || account.value?.openid || "", + unionid: account.value?.unionid || "", + extraData: { + page: "pages/message/message", + }, + }); +}; + // 页面显示 onShow(async () => { // 页面显示时刷新 tabBar 徽章 @@ -718,4 +742,32 @@ onUnmounted(() => { color: #999; padding-bottom: 10rpx; } + +.subscribe-entry { + position: fixed; + right: 32rpx; + bottom: 180rpx; + width: 116rpx; + height: 116rpx; + border-radius: 58rpx; + background: #fff; + border: 2rpx solid #3876f6; + box-shadow: 0 8rpx 24rpx rgba(56, 118, 246, 0.16); + display: flex; + align-items: center; + justify-content: center; + z-index: 20; +} + +.subscribe-entry:active { + opacity: 0.85; +} + +.subscribe-entry-text { + width: 56rpx; + font-size: 24rpx; + line-height: 1.4; + color: #3876f6; + text-align: center; +} diff --git a/utils/api.js b/utils/api.js index 1798a7c..210f151 100644 --- a/utils/api.js +++ b/utils/api.js @@ -72,7 +72,9 @@ const urlsConfig = { getGroupListByGroupId: "getGroupListByGroupId", createConsultGroup: "createConsultGroup", cancelConsultApplication: "cancelConsultApplication", - getGroupList: "getGroupList" + getGroupList: "getGroupList", + saveConversationSubscribeResult: "saveConversationSubscribeResult", + sendConversationSubscribeEvent: "sendConversationSubscribeEvent" }, survery: { getMiniAppReceivedSurveryList: 'getMiniAppReceivedSurveryList', diff --git a/utils/subscribe-message-config.js b/utils/subscribe-message-config.js new file mode 100644 index 0000000..df935c9 --- /dev/null +++ b/utils/subscribe-message-config.js @@ -0,0 +1,64 @@ +const env = __VITE_ENV__; + +export const SUBSCRIBE_MESSAGE_ROLE = { + PATIENT: "patient", + DOCTOR: "doctor", +}; + +export const SUBSCRIBE_MESSAGE_SCENE = { + DEFAULT: "default", + LIST: "list", + CHAT: "chat", +}; + +export const SUBSCRIBE_MESSAGE_EVENT = { + PATIENT_CONSULT_APPLY: "patient_consult_apply", + PATIENT_CHAT_MESSAGE: "patient_chat_message", + DOCTOR_ACCEPT: "doctor_accept", + DOCTOR_REJECT: "doctor_reject", + DOCTOR_CHAT_MESSAGE: "doctor_chat_message", +}; + +export const SUBSCRIBE_MESSAGE_TEMPLATES = { + consultationReply: { + code: "consultationReply", + role: SUBSCRIBE_MESSAGE_ROLE.PATIENT, + id: + env.MP_SUBSCRIBE_TEMPLATE_CONSULT_REPLY || + "VF9AC-7Rr3E1drbxBCrxbC-rLTnidmlNXopKReSAd_w", + name: "咨询回复通知", + events: [ + SUBSCRIBE_MESSAGE_EVENT.DOCTOR_ACCEPT, + SUBSCRIBE_MESSAGE_EVENT.DOCTOR_REJECT, + SUBSCRIBE_MESSAGE_EVENT.DOCTOR_CHAT_MESSAGE, + ], + fields: ["患者姓名", "回复时间", "回复者", "所属机构"], + }, +}; + +export const SUBSCRIBE_MESSAGE_SCENE_TEMPLATE_MAP = { + [SUBSCRIBE_MESSAGE_ROLE.PATIENT]: { + [SUBSCRIBE_MESSAGE_SCENE.DEFAULT]: ["consultationReply"], + [SUBSCRIBE_MESSAGE_SCENE.LIST]: ["consultationReply"], + [SUBSCRIBE_MESSAGE_SCENE.CHAT]: ["consultationReply"], + }, +}; + +export function resolveSubscribeTemplates({ + role, + scene = SUBSCRIBE_MESSAGE_SCENE.DEFAULT, +} = {}) { + const roleMap = SUBSCRIBE_MESSAGE_SCENE_TEMPLATE_MAP[role] || {}; + const keys = + roleMap[scene] || roleMap[SUBSCRIBE_MESSAGE_SCENE.DEFAULT] || []; + const seen = new Set(); + + return keys + .map((key) => SUBSCRIBE_MESSAGE_TEMPLATES[key]) + .filter((item) => item && item.id) + .filter((item) => { + if (seen.has(item.id)) return false; + seen.add(item.id); + return true; + }); +} diff --git a/utils/subscribe-message.js b/utils/subscribe-message.js new file mode 100644 index 0000000..8309fd8 --- /dev/null +++ b/utils/subscribe-message.js @@ -0,0 +1,194 @@ +import api from "@/utils/api"; +import { toast } from "@/utils/widget"; +import { resolveSubscribeTemplates } from "./subscribe-message-config"; + +const SUBSCRIBE_ACCEPT_STATUS = "accept"; +const SUBSCRIBE_REJECT_STATUS = "reject"; +const SUBSCRIBE_BAN_STATUS = "ban"; +const SUBSCRIBE_FILTER_STATUS = "filter"; +const SUBSCRIBE_CANCEL_STATUS = "cancel"; +const SUBSCRIBE_FAILED_STATUS = "failed"; + +function canUseSubscribeMessage() { + return ( + typeof wx !== "undefined" && + typeof wx.requestSubscribeMessage === "function" + ); +} + +function requestSubscribeMessage(tmplIds = []) { + return new Promise((resolve) => { + wx.requestSubscribeMessage({ + tmplIds, + success(res) { + resolve({ ok: true, res }); + }, + fail(err) { + resolve({ ok: false, err }); + }, + }); + }); +} + +function normalizeFailStatus(err = {}) { + const errCode = Number(err.errCode || 0); + const errMsg = String(err.errMsg || "").toLowerCase(); + + if (errCode === 20004 || errMsg.includes("main switch")) { + return SUBSCRIBE_BAN_STATUS; + } + if (errCode === 20005 || errMsg.includes("ban")) { + return SUBSCRIBE_BAN_STATUS; + } + if (errMsg.includes("filter")) { + return SUBSCRIBE_FILTER_STATUS; + } + if (errMsg.includes("cancel")) { + return SUBSCRIBE_CANCEL_STATUS; + } + return SUBSCRIBE_FAILED_STATUS; +} + +function buildTemplateResultRecords(templates = [], requestResult = {}, context = {}) { + const requestedAt = Date.now(); + + if (requestResult.ok) { + const res = requestResult.res || {}; + return templates.map((template) => ({ + role: context.role || "", + scene: context.scene || "", + conversationId: context.conversationId || "", + groupId: context.groupId || "", + corpId: context.corpId || "", + teamId: context.teamId || "", + patientId: context.patientId || "", + doctorId: context.doctorId || "", + userId: context.userId || "", + openid: context.openid || "", + unionid: context.unionid || "", + templateId: template.id, + templateCode: template.code, + templateName: template.name, + eventTypes: template.events, + status: String(res[template.id] || SUBSCRIBE_FAILED_STATUS), + rawResult: res, + requestedAt, + extraData: context.extraData || {}, + })); + } + + const status = normalizeFailStatus(requestResult.err); + return templates.map((template) => ({ + role: context.role || "", + scene: context.scene || "", + conversationId: context.conversationId || "", + groupId: context.groupId || "", + corpId: context.corpId || "", + teamId: context.teamId || "", + patientId: context.patientId || "", + doctorId: context.doctorId || "", + userId: context.userId || "", + openid: context.openid || "", + unionid: context.unionid || "", + templateId: template.id, + templateCode: template.code, + templateName: template.name, + eventTypes: template.events, + status, + rawResult: requestResult.err || {}, + requestedAt, + extraData: context.extraData || {}, + })); +} + +function buildToastMessage(records = [], reportResult = { success: false }) { + const accepted = records.some((item) => item.status === SUBSCRIBE_ACCEPT_STATUS); + + if (accepted && reportResult?.success === false) { + return reportResult?.message || "提醒开启失败,请稍后再试"; + } + + if (records.some((item) => item.status === SUBSCRIBE_ACCEPT_STATUS)) { + return "会话消息提醒开启"; + } + if (records.some((item) => item.status === SUBSCRIBE_BAN_STATUS)) { + return "请先在微信设置中开启订阅消息提醒"; + } + if (records.some((item) => item.status === SUBSCRIBE_FILTER_STATUS)) { + return "当前提醒模板暂不可用"; + } + if (records.some((item) => item.status === SUBSCRIBE_REJECT_STATUS)) { + return "你已拒绝本次提醒订阅"; + } + if (records.some((item) => item.status === SUBSCRIBE_CANCEL_STATUS)) { + return "你已取消本次提醒订阅"; + } + return "提醒订阅请求失败,请稍后再试"; +} + +async function reportSubscribeResult(records = []) { + if (!records.length) return { success: false }; + try { + return await api( + "saveConversationSubscribeResult", + { + records, + }, + false + ); + } catch (error) { + console.error("保存订阅结果失败:", error); + return { success: false, message: error?.message || "保存失败" }; + } +} + +export async function requestConversationSubscribeMessage(context = {}) { + const templates = resolveSubscribeTemplates({ + role: context.role, + scene: context.scene, + }); + const requestTemplates = templates.slice(0, 1); + + if (!requestTemplates.length) { + await toast("暂未配置提醒模板"); + return { + success: false, + code: "template_missing", + records: [], + }; + } + + if (!canUseSubscribeMessage()) { + await toast("当前微信版本不支持订阅消息"); + return { + success: false, + code: "unsupported", + records: [], + }; + } + + const requestResult = await requestSubscribeMessage( + requestTemplates.map((item) => item.id) + ); + const records = buildTemplateResultRecords( + requestTemplates, + requestResult, + context + ); + + const reportResult = await reportSubscribeResult(records); + await toast(buildToastMessage(records, reportResult)); + + const subscribeSuccess = + records.some((item) => item.status === SUBSCRIBE_ACCEPT_STATUS) && + reportResult?.success !== false; + + return { + success: subscribeSuccess, + reportResult, + records, + acceptedTemplateIds: records + .filter((item) => item.status === SUBSCRIBE_ACCEPT_STATUS) + .map((item) => item.templateId), + }; +}