Merge remote-tracking branch 'origin/dev-订阅消息' into dev-2.3

This commit is contained in:
huxuejian 2026-04-18 16:00:55 +08:00
commit ba49a144ab
6 changed files with 428 additions and 57 deletions

View File

@ -86,6 +86,10 @@
}}</text>
</view>
</scroll-view>
<view class="subscribe-entry" @click="handleSubscribeReminder">
<text class="subscribe-entry-text">接收提醒</text>
</view>
</view>
</template>
@ -99,6 +103,11 @@ import useInfoCheck from "@/hooks/useInfoCheck.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
import { mergeConversationWithGroupDetails } from "@/utils/conversation-merger.js";
import MessageHeader from "../home/components/message-header.vue";
import { requestConversationSubscribeMessage } from "@/utils/subscribe-message";
import {
SUBSCRIBE_MESSAGE_ROLE,
SUBSCRIBE_MESSAGE_SCENE,
} from "@/utils/subscribe-message-config";
//
const { account, openid, isIMInitialized, doctorInfo } = storeToRefs(
@ -642,6 +651,21 @@ const handleRefresh = async () => {
}
};
const handleSubscribeReminder = async () => {
await requestConversationSubscribeMessage({
role: SUBSCRIBE_MESSAGE_ROLE.DOCTOR,
scene: SUBSCRIBE_MESSAGE_SCENE.LIST,
userId: doctorInfo.value?.userid || account.value?.userId || "",
openid: openid.value || account.value?.openid || "",
unionid: account.value?.unionid || "",
doctorId: doctorInfo.value?.userid || "",
extraData: {
teamId: currentTeamId.value || "",
page: "pages/home/message-home",
},
});
};
//
onLoad(() => {
console.log("消息列表页面加载");
@ -844,4 +868,32 @@ onHide(() => {
font-size: 24rpx;
color: #999;
}
.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;
}
</style>

View File

@ -29,6 +29,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;
@ -76,6 +83,26 @@ $primary-color: #0877F1;
font-weight: 500;
}
.remind-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 10rpx 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;
}
.arrow-icon {
font-size: 32rpx;
color: #fff;

View File

@ -9,8 +9,13 @@
>{{ patientInfo.sex }} · {{ patientInfo.age }}</text
>
</view>
<view class="patient-detail-btn" @click="handleViewPatientDetail">
<text class="detail-btn-text">查看档案</text>
<view class="header-actions">
<view class="patient-detail-btn" @click="handleViewPatientDetail">
<text class="detail-btn-text">查看档案</text>
</view>
<view class="remind-btn" @click="handleSubscribeReminder">
<text class="remind-btn-text">接收提醒</text>
</view>
</view>
</view>
</view>
@ -227,6 +232,11 @@ import AIAssistantButtons from "./components/ai-assistant-buttons.vue";
import MedicalCaseTypeSelector from "./components/medical-case-type-selector.vue";
import MedicalCaseProgress from "./components/medical-case-progress.vue";
import request from "@/utils/http.js";
import { requestConversationSubscribeMessage } from "@/utils/subscribe-message";
import {
SUBSCRIBE_MESSAGE_ROLE,
SUBSCRIBE_MESSAGE_SCENE,
} from "@/utils/subscribe-message-config";
const timChatManager = globalTimChatManager;
@ -298,7 +308,9 @@ const env = __VITE_ENV__;
const corpId = env.MP_CORP_ID || "";
//
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
const { account, openid, isIMInitialized, doctorInfo } = storeToRefs(
useAccountStore()
);
const { initIMAfterLogin } = useAccountStore();
//
@ -1255,6 +1267,27 @@ const handleViewPatientDetail = () => {
url: `/pages/case/archive-detail?id=${patientId.value}`,
});
};
const handleSubscribeReminder = async () => {
await requestConversationSubscribeMessage({
role: SUBSCRIBE_MESSAGE_ROLE.DOCTOR,
scene: SUBSCRIBE_MESSAGE_SCENE.CHAT,
conversationId: chatInfo.value.conversationID || "",
groupId: groupId.value || "",
corpId: corpId || "",
teamId: teamId.value || "",
patientId: patientId.value || "",
doctorId: doctorInfo.value?.userid || "",
userId: doctorInfo.value?.userid || account.value?.userId || "",
openid: openid.value || account.value?.openid || "",
unionid: account.value?.unionid || "",
extraData: {
orderStatus: orderStatus.value || "",
page: "pages/message/index",
},
});
};
//
const handleEndConsult = async () => {
try {

View File

@ -111,7 +111,9 @@ const urlsConfig = {
getGroupList: "getGroupList",
followUpInquiry: "followUpInquiry",
supplementMedicalCase: "supplementMedicalCase",
rejectConsultation: "rejectConsultation"
rejectConsultation: "rejectConsultation",
saveConversationSubscribeResult: "saveConversationSubscribeResult",
sendConversationSubscribeEvent: "sendConversationSubscribeEvent"
},
todo: {
getCustomerTodos: 'getCustomerTodos',

View File

@ -0,0 +1,63 @@
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.DOCTOR,
id:
env.MP_SUBSCRIBE_TEMPLATE_CONSULT_REPLY ||
"1etY1Mdfz9c0xJlI8kx79Re6uKmc3BoHJBHsrXeiCm4",
name: "咨询回复通知",
events: [
SUBSCRIBE_MESSAGE_EVENT.PATIENT_CONSULT_APPLY,
SUBSCRIBE_MESSAGE_EVENT.PATIENT_CHAT_MESSAGE,
],
fields: ["咨询人", "回复时间", "回复内容"],
},
};
export const SUBSCRIBE_MESSAGE_SCENE_TEMPLATE_MAP = {
[SUBSCRIBE_MESSAGE_ROLE.DOCTOR]: {
[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;
});
}

194
utils/subscribe-message.js Normal file
View File

@ -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),
};
}