Merge remote-tracking branch 'origin/dev-wdb' into dev-hjf

This commit is contained in:
Jafeng 2026-02-05 10:43:56 +08:00
commit 9b2e8e9b1e
20 changed files with 613 additions and 290 deletions

View File

@ -1,7 +1,7 @@
MP_API_BASE_URL=http://192.168.60.2:8080
MP_IMAGE_URL=https://patient.youcan365.com
MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e
MP_WX_APP_ID=wx1d8337a40c11d66c
MP_CORP_ID=wwe3fb2faa52cf9dfb
MP_TIM_SDK_APP_ID=1600123876
MP_INVITE_TEAMMATE_QRCODE=https://patient.youcan365.com/invite-teammate

View File

@ -37,3 +37,6 @@ export const statusClassNames = {
cancelled: "text-gray",
expired: "text-gray",
}
export const titleList = ['主任医师', '副主任医师', '主治医师', '住院医师', '护士长', '主管护师', '护师', '护士', '技师', '其他']

View File

@ -64,7 +64,7 @@
<view class="content">{{ i.taskContent || "暂无内容" }}</view>
<view v-if="i.sendContent || (i.fileList && i.fileList.length > 0)" class="send-content-wrapper">
<view class="send-content-section">
<view class="send-content-label">发送内容</view>
<view class="send-content-label">发送内容:</view>
<view class="send-content-body">
<view v-if="i.sendContent" class="send-text">{{ i.sendContent }}</view>
<view v-if="i.fileList && i.fileList.length > 0" class="file-list">
@ -204,6 +204,7 @@ import dayjs from "dayjs";
import api from "@/utils/api";
import useAccountStore from "@/store/account";
import { toast } from "@/utils/widget";
import { handleFollowUpMessages } from "@/utils/send-message-helper";
import {
getTodoEventTypeLabel,
getTodoEventTypeOptions,
@ -221,6 +222,15 @@ const accountStore = useAccountStore();
const { account, doctorInfo } = storeToRefs(accountStore);
const { getDoctorInfo } = accountStore;
function getUserId() {
return doctorInfo.value?.userid || "";
}
function getCorpId() {
const team = uni.getStorageSync("ykt_case_current_team") || {};
return team.corpId || doctorInfo.value?.corpId || "";
}
const statusTabs = [
{ label: "全部", value: "all" },
{ label: "待处理", value: "processing" },
@ -275,23 +285,6 @@ const typeSelectedMap = computed(() => {
}, {});
});
function getUserId() {
const d = doctorInfo.value || {};
const a = account.value || {};
return (
String(
d.userid || d.userId || d.corpUserId || a.userid || a.userId || ""
) || ""
);
}
function getCorpId() {
const t = uni.getStorageSync("ykt_case_current_team") || {};
const a = account.value || {};
const d = doctorInfo.value || {};
return String(t.corpId || a.corpId || d.corpId || "") || "";
}
async function ensureDoctor() {
if (doctorInfo.value) return;
if (!account.value?.openid) return;
@ -537,9 +530,12 @@ async function sendFollowUp(todo) {
messages.push({
type: "article",
content: {
_id: file.file?._id || file._id,
title: file.file?.name || file.name || "宣教文章",
url: file.file?.url || file.URL,
desc: file.file?.subtitle || "",
subtitle: file.file?.subtitle || "",
cover: file.file?.cover || "",
articleId: file.file?._id || file._id,
},
});
} else if (file.type === "questionnaire" && file.file?.surveryId) {
@ -547,7 +543,8 @@ async function sendFollowUp(todo) {
messages.push({
type: "questionnaire",
content: {
title: file.file?.name || file.name || "问卷",
_id: file.file?._id || file._id,
name: file.file?.name || file.name || "问卷",
surveryId: file.file?.surveryId || file.surveryId,
url: file.file?.url || file.URL,
},
@ -556,15 +553,19 @@ async function sendFollowUp(todo) {
}
}
//
uni.$emit("send-followup-message", {
messages,
followupId: todo._id,
followupData: todo,
//
const success = await handleFollowUpMessages(messages, {
userId: getUserId(),
customerId: props.archiveId,
customerName: props.data?.name || "",
corpId: getCorpId(),
env: __VITE_ENV__,
});
if (success) {
toast("消息已发送");
uni.navigateBack();
}
}
// ---- filter popup ----
@ -863,6 +864,7 @@ watch(
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
width: 330rpx;
}
.file-type-image .file-name {
color: #0877f1;

View File

@ -93,6 +93,9 @@ function confirm() {
}
function toService() {
uni.navigateTo({
url: '/pages/work/service/contact-service'
})
close()
}

View File

@ -63,7 +63,7 @@ function remind() {
function toHome() {
uni.switchTab({
url: "/pages/work/work",
url: "/pages/home/work-home",
});
}

View File

@ -39,7 +39,7 @@ async function toJoinTeam(teamId) {
if (res && res.success) {
await toast('加入团队成功');
return uni.switchTab({
url: '/pages/work/work'
url: '/pages/home/work-home'
})
} else {
await toast(res?.message || '加入团队失败')

View File

@ -111,6 +111,7 @@ import { ref, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import api from "@/utils/api.js";
import useAccountStore from "@/store/account.js";
import { sendArticleMessage } from "@/utils/send-message-helper.js";
import EmptyData from "@/components/empty-data.vue";
const accountStore = useAccountStore();
@ -349,71 +350,27 @@ const sendArticle = async (article) => {
try {
const { doctorInfo } = useAccountStore();
// 1. API
const result = await api("sendArticleMessage", {
groupId: pageParams.value.groupId,
fromAccount: doctorInfo.userid,
articleId: article._id,
title: article.title || "宣教文章",
imgUrl: article.cover || "",
desc: "点击查看详情",
});
if (result.success) {
// 2. IM
try {
// IM
const { globalTimChatManager } = await import("@/utils/tim-chat.js");
if (globalTimChatManager && globalTimChatManager.tim && globalTimChatManager.isLoggedIn) {
// ID
const conversationID = `GROUP${pageParams.value.groupId}`;
globalTimChatManager.currentConversationID = conversationID;
console.log("设置当前会话ID:", conversationID);
//
const customMessageData = {
messageType: "article",
title: article.title || "宣教文章",
articleId: article._id,
cover: article.cover || "",
desc: "点击查看详情",
content: article.title || "宣教文章"
};
//
const sendResult = await globalTimChatManager.sendCustomMessage(customMessageData);
if (sendResult && sendResult.success) {
console.log("✓ 文章消息已通过IM系统发送");
} else {
console.warn("⚠️ 文章消息发送失败:", sendResult?.error);
}
} else {
console.warn("⚠️ IM系统未就绪消息可能不会显示在聊天列表");
}
} catch (imError) {
console.error("通过IM系统发送消息失败:", imError);
// IM
}
// 3.
try {
await ensureTeamId();
await api("addArticleSendRecord", {
// 使
const success = await sendArticleMessage(
{
_id: article._id,
title: article.title || "宣教文章",
cover: article.cover || "",
url: article.url || "",
subtitle: article.subtitle || "",
},
{
articleId: article._id,
userId: doctorInfo.userid,
userId: doctorInfo?.userid,
customerId: pageParams.value.patientId,
corpId: corpId,
teamId: pageParams.value.teamId,
});
} catch (recordError) {
console.error("记录文章发送失败:", recordError);
}
);
if (success) {
uni.navigateBack();
} else {
throw new Error(result.message || "发送失败");
}
} catch (error) {
console.error("发送文章失败:", error);

View File

@ -10,7 +10,7 @@ $primary-color: #0877F1;
top: 0;
left: 0;
right: 0;
bottom: 0;
bottom: 20rpx;
display: flex;
flex-direction: column;
background-color: #f5f5f5;

View File

@ -627,10 +627,14 @@ $primary-gradient-start: #1b5cc8;
$primary-gradient-end: #0877f1;
.common-phrases-page {
height: 100vh;
display: flex;
flex-direction: column;
background-color: #f5f5f5;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 20rpx;
}
//

View File

@ -79,10 +79,11 @@ const props = defineProps({
teamId: { type: String, default: "" },
patientId: { type: String, default: "" },
corpId: { type: String, default: "" },
orderStatus: { type: String, default: "" },
});
// Emits
const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult"]);
const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult", "openConsult"]);
//
const inputText = ref("");
@ -405,7 +406,26 @@ const handleEndConsult = () => {
});
};
const morePanelButtons = [
//
const handleOpenConsult = () => {
uni.showModal({
title: "确认开启会话",
content: "确定要重新开启本次会话吗?",
confirmText: "确定开启",
cancelText: "取消",
success: (res) => {
if (res.confirm) {
//
showMorePanel.value = false;
//
emit("openConsult");
}
},
});
};
const morePanelButtons = computed(() => {
const buttons = [
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
{
text: "回访任务",
@ -427,12 +447,27 @@ const morePanelButtons = [
icon: "/static/icon/wenjuan.png",
action: goToSurveyList,
},
{
];
//
if (props.orderStatus === "finished") {
// ""
buttons.push({
text: "开启会话",
icon: "/static/icon/kaiqihuihua.png",
action: handleOpenConsult,
});
} else {
// ""
buttons.push({
text: "结束问诊",
icon: "/static/icon/jieshuzixun.png",
action: handleEndConsult,
},
];
});
}
return buttons;
});
function handleInputFocus() {
console.log("handleInputFocus");

View File

@ -75,6 +75,8 @@ const text = computed(() => {
return '问诊已结束';
case 'consult_timeout':
return '问诊已超时';
case 'consult_reopened':
return '会话已重新开启';
default:
return systemMessageData.value.content || '[系统消息]';
}

View File

@ -164,9 +164,11 @@
:patientId="patientId"
:corpId="corpId"
:patientInfo="patientInfo"
:orderStatus="orderStatus"
@scrollToBottom="() => scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)"
@endConsult="handleEndConsult"
@openConsult="handleOpenConsult"
/>
</view>
</template>
@ -966,6 +968,43 @@ const handleEndConsult = async () => {
}
};
//
const handleOpenConsult = async () => {
try {
uni.showLoading({
title: "处理中...",
});
//
const result = await api("openConsultation", {
groupId: groupId.value,
adminAccount: account.value?.userId || "",
extraData: {
openedBy: account.value?.userId || "",
openedByName: account.value?.name || "医生",
openReason: "重新开启会话",
},
});
uni.hideLoading();
if (result.success) {
//
await fetchGroupOrderStatus();
uni.showToast({
title: "会话已开启",
icon: "success",
});
} else {
throw new Error(result.message || "操作失败");
}
} catch (error) {
console.error("开启会话失败:", error);
uni.hideLoading();
uni.showToast({
title: error.message || "操作失败",
icon: "none",
});
}
};
//
onUnmounted(() => {
clearMessageCache();

View File

@ -90,6 +90,7 @@ import { ref, onMounted } from "vue";
import api from "@/utils/api.js";
import useAccountStore from "@/store/account.js";
import { globalTimChatManager } from "@/utils/tim-chat.js";
import { sendSurveyMessage } from "@/utils/send-message-helper.js";
import EmptyData from "@/components/empty-data.vue";
import { onLoad } from "@dcloudio/uni-app";
const env = __VITE_ENV__;
@ -354,51 +355,25 @@ const sendSurvey = async (survey) => {
const doctorInfo = accountStore.doctorInfo;
userId.value = doctorInfo?.userid;
// ID
const sendSurveyId = generateRandomString(10);
await ensureTeamId();
//
const createRecordRes = await api("createSurveyRecord", {
corpId: corpId,
// 使
const success = await sendSurveyMessage(
{
_id: survey._id,
name: survey.name || "问卷",
surveryId: survey.surveryId || survey._id,
url: survey.url || "",
},
{
userId: userId.value,
surveryId: survey._id,
memberId: customerId.value,
customer: customerName.value,
sendSurveyId: sendSurveyId,
customerId: customerId.value,
customerName: customerName.value,
corpId: corpId,
teamId: teamId.value,
});
if (!createRecordRes.success) {
uni.showToast({
title: createRecordRes.message || "创建问卷记录失败",
icon: "none",
});
loading.value = false;
return;
}
const answerId = createRecordRes?.id || "";
//
const sendLink = generateSendLink(
survey,
answerId,
customerId.value,
customerName.value,
sendSurveyId
);
//
const customMessage = buildSurveyMessage(survey, sendLink);
// IM
const result = await timChatManager.sendCustomMessage(
customMessage,
"SURVEY"
);
if (result.success) {
if (success) {
uni.showToast({
title: "发送成功",
icon: "success",
@ -407,8 +382,6 @@ const sendSurvey = async (survey) => {
setTimeout(() => {
uni.navigateBack();
}, 500);
} else {
throw new Error(result.error || "发送失败");
}
} catch (error) {
console.error("发送问卷失败:", error);

View File

@ -71,7 +71,7 @@
<form-textarea
name="个人介绍"
:form="formData"
title="intro"
title="memberTroduce"
:word-limit="500"
@change="handleFieldChange"
/>
@ -110,7 +110,7 @@ const formData = ref({
department: "",
departmentName: "",
departmentId: "",
intro: "",
memberTroduce: "",
});
//

View File

@ -2,97 +2,53 @@
<full-page>
<view class="p-15">
<view class="bg-white px-10 mb-10 rounded">
<form-input
:form="formData"
:required="rule.anotherName.required"
wordLimit="10"
title="anotherName"
:name="rule.anotherName.name"
@change="onChange($event)"
/>
<form-input :form="formData" :required="rule.anotherName.required" wordLimit="10" title="anotherName"
:name="rule.anotherName.name" @change="onChange($event)" />
<common-cell title="avatar" name="头像">
<view
class="flex-grow flex items-center justify-end"
@click="chooseAvatar()"
>
<image
v-if="formData.avatar"
class="avatar mr-5 rounded-full"
:src="formData.avatar"
/>
<image
v-else
class="avatar mr-5 rounded-full"
src="/static/home/avatar.svg"
/>
<view class="flex-grow flex items-center justify-end" @click="chooseAvatar()">
<image v-if="formData.avatar" class="avatar mr-5 rounded-full" :src="formData.avatar" />
<image v-else class="avatar mr-5 rounded-full" src="/static/home/avatar.svg" />
<uni-icons color="#999" type="right" size="16" />
</view>
</common-cell>
<form-select
:form="formData"
name="性别"
title="gender"
:range="genderOptions"
@change="onChange($event)"
/>
<form-input
:form="formData"
disableChange
wordLimit="11"
title="mobile"
name="手机号 (不可修改)"
/>
<form-select :form="formData" name="性别" title="gender" :range="genderOptions" @change="onChange($event)" />
<form-input :form="formData" disableChange wordLimit="11" title="mobile" name="手机号 (不可修改)" />
</view>
<view class="bg-white px-10 mb-10 rounded">
<!-- 填写认证资料的时候岗位必填 -->
<common-cell
:required="type === 'cert'"
title="job"
:name="rule.job.name"
>
<view
class="flex-grow flex items-center justify-end"
@click="selectJob()"
>
<common-cell :required="type === 'cert'" title="job" :name="rule.job.name">
<picker mode="selector" :disable="rule.job.disable" :range="jobOptions" range-key="name"
@change="changeJob($event)">
<view class="flex-grow flex items-center justify-end">
<view v-if="jobStr" class="text-base text-base">{{ jobStr }}</view>
<!-- <view class="mr-5 rounded-full" style="width: 64rpx;height: 64rpx;background: red;"></view> -->
<uni-icons color="#999" type="right" size="16" />
</view>
</picker>
</common-cell>
<common-cell title="title" :name="rule.title.name">
<picker mode="selector" :disable="rule.title.disable" :range="titleOptions"
@change="changeTitle($event)">
<view class="flex-grow flex items-center justify-end">
<!-- <view class="mr-5 rounded-full" style="width: 64rpx;height: 64rpx;background: red;"></view> -->
<view class="text-base text-base">{{ formData.title }}</view>
<uni-icons color="#999" type="right" size="16" />
</view>
</picker>
</common-cell>
<common-cell title="dept" :name="rule.dept.name">
<view class="flex-grow flex items-center justify-end">
<!-- <view class="mr-5 rounded-full" style="width: 64rpx;height: 64rpx;background: red;"></view> -->
<uni-icons color="#999" type="right" size="16" />
</view>
</common-cell>
</view>
<view class="bg-white rounded">
<form-textarea
autoHeight
:border="false"
:form="formData"
title="intro"
name="个人介绍"
:wordLimit="300"
@change="onChange($event)"
/>
<form-textarea autoHeight :border="false" :form="formData" title="memberTroduce" name="个人介绍" :wordLimit="300"
@change="onChange($event)" />
</view>
</view>
<template #footer>
<button-footer
:cancelText="cancelText"
:confirmText="confirmText"
@confirm="save()"
@cancel="back()"
/>
<button-footer :cancelText="cancelText" :confirmText="confirmText" @confirm="save()" @cancel="back()" />
</template>
</full-page>
</template>
@ -100,6 +56,7 @@
<script setup>
import { computed, ref } from "vue";
import { storeToRefs } from "pinia";
import { titleList } from "@/baseData";
import useGuard from "@/hooks/useGuard.js";
import useAccountStore from "@/store/account.js";
import api from "@/utils/api.js";
@ -118,6 +75,8 @@ const { useLoad, useShow } = useGuard();
const { getDoctorInfo } = useAccountStore();
const job = { assistant: "医生助理", doctor: "医生" };
const jobOptions = [{ name: '医生', value: 'doctor' }, { name: '医生助理', value: 'assistant' }, { name: '无', value: '' }];
const titleOptions = [...titleList, '无'];
const form = ref({});
const inviteTeamId = ref("");
@ -183,7 +142,7 @@ function back() {
uni.navigateBack();
} else {
uni.switchTab({
url: "/pages/work/work",
url: "/pages/home/work-home",
});
}
}
@ -207,15 +166,14 @@ function onChange({ title, value }) {
form.value[title] = value;
}
function selectJob() {
if (rule.value.job.disabled) return;
uni.showActionSheet({
itemList: ["医生", "医生助理", "无"],
success: ({ tapIndex }) => {
const job = ["doctor", "assistant"][tapIndex];
form.value.job = job ? [job] : [];
},
});
function changeJob(e) {
const data = jobOptions[e.detail.value];
form.value.job = data.value;
}
function changeTitle(e) {
const data = titleList[e.detail.value];
form.value.title = data || '';
}
function toCert() {

View File

@ -5,7 +5,7 @@
柚康企微客服
</view>
<view class="flex justify-center overflow-hidden">
<uqrcode :canvas-id="`qrcode-${idx}`" value="https://uqrcode.cn/doc" :options="options"></uqrcode>
<uqrcode canvas-id="qrcode" value="暂无二维码" :options="options"></uqrcode>
</view>
<view class="mt-10 px-15 text-base text-dark leading-normal text-center">
扫码或长按添加柚康企微客服

View File

@ -18,7 +18,7 @@
成员: {{ i.memberList && i.memberList.length ? i.memberList.length : 0 }}
</view>
<view class="min-w-120 text-base text-dark">
患者: 200
患者: {{ i.customerCount }}
</view>
</view>
</view>
@ -61,13 +61,14 @@ function toDetail(team) {
}
async function getTeams() {
const res = await api('getJoinedTeams', { corpId: account.value?.corpId, mateId: doctorInfo.value?.userid });
const res = await api('getJoinedTeams', { corpId: account.value?.corpId, mateId: doctorInfo.value?.userid, countCustomer: 'YES' });
const arr = res && Array.isArray(res.data) ? res.data.map(i => ({
id: i._id,
teamId: i.teamId,
name: i.name,
memberList: i.memberList,
creator: i.creator
creator: i.creator,
customerCount: i.customerCount > 0 ? i.customerCount : 0
})) : [];
list.value = arr;
}

View File

@ -94,6 +94,7 @@ const urlsConfig = {
getChatRecordsByGroupId: "getChatRecordsByGroupId",
sendConsultRejectedMessage: "sendConsultRejectedMessage",
endConsultation: "endConsultation",
openConsultation: "openConsultation",
getGroupListByGroupId: "getGroupListByGroupId",
acceptConsultation: "acceptConsultation",
sendArticleMessage: "sendArticleMessage",

View File

@ -0,0 +1,339 @@
/**
* 消息发送助手 - 统一处理不同类型消息的发送
*/
import { globalTimChatManager } from './tim-chat.js';
import api from './api.js';
import { toast } from './widget.js';
/**
* 发送文字消息
* @param {string} content - 文字内容
* @returns {Promise<boolean>} 发送是否成功
*/
export async function sendTextMessage(content) {
if (!content || !content.trim()) {
toast('文字内容不能为空');
return false;
}
try {
if (!globalTimChatManager?.isLoggedIn) {
toast('IM系统未就绪请稍后重试');
return false;
}
// 直接调用 tim-chat 的 sendTextMessage 方法
const result = await globalTimChatManager.sendTextMessage(content.trim());
if (result?.success) {
return true;
} else {
toast(result?.error || '发送文字消息失败');
return false;
}
} catch (error) {
console.error('发送文字消息异常:', error);
toast('发送文字消息失败');
return false;
}
}
/**
* 发送图片消息
* @param {string} imageUrl - 图片URL字符串
* @param {string} imageName - 图片名称可选
* @returns {Promise<boolean>} 发送是否成功
*/
export async function sendImageMessage(imageUrl, imageName = '图片') {
if (!imageUrl) {
toast('图片URL不能为空');
return false;
}
try {
if (!globalTimChatManager?.isLoggedIn) {
toast('IM系统未就绪请稍后重试');
return false;
}
// 直接调用 tim-chat 的 sendImageMessage 方法
// tim-chat.js 中的 getImageUrl 方法可以处理 URL 字符串
const result = await globalTimChatManager.sendImageMessage(imageUrl);
if (result?.success) {
return true;
} else {
toast(result?.error || '发送图片消息失败');
return false;
}
} catch (error) {
console.error('发送图片消息异常:', error);
toast('发送图片消息失败');
return false;
}
}
/**
* 发送宣教文章消息
* @param {Object} article - 文章对象 { _id, title, cover, url }
* @param {Object} options - 额外选项 { groupId, patientId, corpId }
* @returns {Promise<boolean>} 发送是否成功
*/
export async function sendArticleMessage(article, options = {}) {
if (!article || !article._id) {
toast('文章信息不完整');
return false;
}
try {
if (!globalTimChatManager?.isLoggedIn) {
toast('IM系统未就绪请稍后重试');
return false;
}
// 构建自定义消息 - 直接传递对象sendCustomMessage会处理JSON序列化
const customMessageData = {
type: 'article',
title: article.title || '宣教文章',
articleId: article._id,
cover: article.cover || '',
desc: article.subtitle || '点击查看详情',
url: article.url || '',
messageType: 'article',
};
// 发送自定义消息
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
if (result?.success) {
// 记录文章发送记录(异步,不阻塞)
if (options.articleId && options.userId && options.customerId && options.corpId) {
const params = {
articleId: options.articleId,
userId: options.userId,
customerId: options.customerId,
corpId: options.corpId,
};
if (options.teamId) {
params.teamId = options.teamId;
}
api('addArticleSendRecord', params).catch((err) => {
console.error('记录文章发送失败:', err);
});
}
return true;
} else {
toast(result?.error || '发送文章消息失败');
return false;
}
} catch (error) {
console.error('发送文章消息异常:', error);
toast('发送文章消息失败');
return false;
}
}
/**
* 发送问卷消息
* @param {Object} survey - 问卷对象 { _id, name, surveryId, url, createBy }
* @param {Object} options - 额外选项 { userId, customerId, customerName, corpId, env }
* @returns {Promise<boolean>} 发送是否成功
*/
export async function sendSurveyMessage(survey, options = {}) {
if (!survey || !survey._id) {
toast('问卷信息不完整');
return false;
}
try {
if (!globalTimChatManager?.isLoggedIn) {
toast('IM系统未就绪请稍后重试');
return false;
}
// 生成发送ID
const sendSurveyId = generateRandomString(10);
const recordParams = {
corpId: options.corpId,
userId: options.userId,
surveryId: survey._id,
memberId: options.customerId,
customer: options.customerName,
sendSurveyId: sendSurveyId,
};
if (options.teamId) {
recordParams.teamId = options.teamId;
}
// 创建问卷记录
const createRecordRes = await api('createSurveyRecord', recordParams);
if (!createRecordRes?.success) {
toast(createRecordRes?.message || '创建问卷记录失败');
return false;
}
const answerId = createRecordRes?.id || '';
// 生成问卷链接
const surveyLink = generateSendLink(
survey,
answerId,
options.customerId,
options.customerName,
sendSurveyId,
{
corpId: options.corpId,
userId: options.userId,
env: options.env,
}
);
// 构建自定义消息
const customMessageData = buildSurveyMessage(survey, surveyLink);
// 发送自定义消息 - 直接传递消息对象,不再进行额外序列化
const result = await globalTimChatManager.sendCustomMessage(customMessageData);
if (result?.success) {
return true;
} else {
toast(result?.error || '发送问卷消息失败');
return false;
}
} catch (error) {
console.error('发送问卷消息异常:', error);
toast('发送问卷消息失败');
return false;
}
}
/**
* 生成随机字符串
* @param {number} length - 字符串长度
* @returns {string} 随机字符串
*/
function generateRandomString(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
/**
* 生成问卷发送链接
* @param {Object} survey - 问卷对象
* @param {string} answerId - 答卷ID
* @param {string} customerId - 客户ID
* @param {string} customerName - 客户名称
* @param {string} sendSurveyId - 发送问卷ID
* @param {Object} context - 上下文信息 { corpId, userId, env }
* @returns {string} 问卷链接
*/
function generateSendLink(survey, answerId, customerId, customerName, sendSurveyId, context = {}) {
const { corpId, userId, env } = context;
const isSystem = survey.createBy === 'system';
let url = '';
if (isSystem) {
// 系统问卷:使用 VITE_SURVEY_URL
url = `${env?.MP_SURVEY_URL}?corpId=${corpId}&surveryId=${survey.surveryId}&memberId=${customerId}&sendSurveyId=${sendSurveyId}&userId=${userId}`;
} else {
// 自定义问卷:使用 VITE_PATIENT_PAGE_BASE_URL
url = `${env?.MP_PATIENT_PAGE_BASE_URL}pages/survery/fill?corpId=${corpId}&surveryId=${survey._id}&memberId=${customerId}&unionid=unionid&answerId=${answerId}&name=${customerName || ''}`;
}
// 如果已有链接,直接使用并拼接额外参数
if (survey.url && survey.url.trim()) {
const separator = survey.url.includes('?') ? '&' : '?';
const params = [];
if (answerId) params.push(`answerId=${answerId}`);
if (sendSurveyId) params.push(`sendSurveyId=${sendSurveyId}`);
if (userId) params.push(`userId=${userId}`);
if (customerId) params.push(`memberId=${customerId}`);
if (params.length > 0) {
url = `${survey.url}${separator}${params.join('&')}`;
} else {
url = survey.url;
}
}
return url;
}
/**
* 构建问卷自定义消息
* @param {Object} survey - 问卷对象
* @param {string} surveyLink - 问卷链接
* @returns {Object} 自定义消息对象
*/
function buildSurveyMessage(survey, surveyLink) {
return {
type: 'survey',
title: survey.name || '填写问卷',
desc: '请填写问卷',
url: surveyLink,
imgUrl:
'https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694',
messageType: 'survey',
};
}
/**
* 处理回访任务消息发送
* @param {Object} messages - 消息数组
* @param {Object} context - 上下文信息 { userId, customerId, customerName, corpId }
* @returns {Promise<boolean>} 是否全部发送成功
*/
export async function handleFollowUpMessages(messages, context = {}) {
if (!Array.isArray(messages) || messages.length === 0) {
toast('没有要发送的消息');
return false;
}
try {
let allSuccess = true;
for (const msg of messages) {
let success = false;
if (msg.type === 'text') {
success = await sendTextMessage(msg.content);
} else if (msg.type === 'image') {
success = await sendImageMessage(msg.content, msg.name);
} else if (msg.type === 'article') {
success = await sendArticleMessage(msg.content, {
articleId: msg.content.articleId,
userId: context.userId,
customerId: context.customerId,
corpId: context.corpId,
});
} else if (msg.type === 'questionnaire') {
success = await sendSurveyMessage(msg.content, {
userId: context.userId,
customerId: context.customerId,
customerName: context.customerName,
corpId: context.corpId,
env: context.env,
});
}
if (!success) {
allSuccess = false;
// 继续发送其他消息,不中断
}
}
return allSuccess;
} catch (error) {
console.error('处理回访任务消息异常:', error);
toast('消息发送过程中出现错误');
return false;
}
}

View File

@ -911,7 +911,7 @@ class TimChatManager {
uni.removeStorageSync('account')
uni.removeStorageSync('openid')
uni.reLaunch({
url: '/pages-center/login/login'
url: '/pages/login/login'
})
}
})
@ -1153,6 +1153,9 @@ class TimChatManager {
case "consult_ended":
lastMessageText = '已结束当前会话'
break
case "consult_reopened":
lastMessageText = '会话已重新开启'
break
default:
lastMessageText = '[自定义消息]'
}
@ -2580,6 +2583,9 @@ class TimChatManager {
case 'consult_ended':
messageText = '已结束当前会话'
break
case 'consult_reopened':
messageText = '会话已重新开启'
break
default:
messageText = customData.content || '[自定义消息]'
}