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_API_BASE_URL=http://192.168.60.2:8080
MP_IMAGE_URL=https://patient.youcan365.com MP_IMAGE_URL=https://patient.youcan365.com
MP_CACHE_PREFIX=development MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e MP_WX_APP_ID=wx1d8337a40c11d66c
MP_CORP_ID=wwe3fb2faa52cf9dfb MP_CORP_ID=wwe3fb2faa52cf9dfb
MP_TIM_SDK_APP_ID=1600123876 MP_TIM_SDK_APP_ID=1600123876
MP_INVITE_TEAMMATE_QRCODE=https://patient.youcan365.com/invite-teammate MP_INVITE_TEAMMATE_QRCODE=https://patient.youcan365.com/invite-teammate

View File

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

View File

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

View File

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

View File

@ -63,7 +63,7 @@ function remind() {
function toHome() { function toHome() {
uni.switchTab({ 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) { if (res && res.success) {
await toast('加入团队成功'); await toast('加入团队成功');
return uni.switchTab({ return uni.switchTab({
url: '/pages/work/work' url: '/pages/home/work-home'
}) })
} else { } else {
await toast(res?.message || '加入团队失败') await toast(res?.message || '加入团队失败')

View File

@ -111,6 +111,7 @@ import { ref, onMounted } from "vue";
import { onLoad } from "@dcloudio/uni-app"; import { onLoad } from "@dcloudio/uni-app";
import api from "@/utils/api.js"; import api from "@/utils/api.js";
import useAccountStore from "@/store/account.js"; import useAccountStore from "@/store/account.js";
import { sendArticleMessage } from "@/utils/send-message-helper.js";
import EmptyData from "@/components/empty-data.vue"; import EmptyData from "@/components/empty-data.vue";
const accountStore = useAccountStore(); const accountStore = useAccountStore();
@ -349,71 +350,27 @@ const sendArticle = async (article) => {
try { try {
const { doctorInfo } = useAccountStore(); const { doctorInfo } = useAccountStore();
// 1. API await ensureTeamId();
const result = await api("sendArticleMessage", { // 使
groupId: pageParams.value.groupId, const success = await sendArticleMessage(
fromAccount: doctorInfo.userid, {
articleId: article._id, _id: article._id,
title: article.title || "宣教文章", title: article.title || "宣教文章",
imgUrl: article.cover || "", cover: article.cover || "",
desc: "点击查看详情", url: article.url || "",
}); subtitle: article.subtitle || "",
},
if (result.success) { {
// 2. IM articleId: article._id,
try { userId: doctorInfo?.userid,
// IM customerId: pageParams.value.patientId,
const { globalTimChatManager } = await import("@/utils/tim-chat.js"); corpId: corpId,
teamId: pageParams.value.teamId,
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", {
articleId: article._id,
userId: doctorInfo.userid,
customerId: pageParams.value.patientId,
corpId: corpId,
teamId: pageParams.value.teamId,
});
} catch (recordError) {
console.error("记录文章发送失败:", recordError);
} }
);
if (success) {
uni.navigateBack(); uni.navigateBack();
} else {
throw new Error(result.message || "发送失败");
} }
} catch (error) { } catch (error) {
console.error("发送文章失败:", error); console.error("发送文章失败:", error);

View File

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

View File

@ -627,10 +627,14 @@ $primary-gradient-start: #1b5cc8;
$primary-gradient-end: #0877f1; $primary-gradient-end: #0877f1;
.common-phrases-page { .common-phrases-page {
height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: #f5f5f5; 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: "" }, teamId: { type: String, default: "" },
patientId: { type: String, default: "" }, patientId: { type: String, default: "" },
corpId: { type: String, default: "" }, corpId: { type: String, default: "" },
orderStatus: { type: String, default: "" },
}); });
// Emits // Emits
const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult"]); const emit = defineEmits(["messageSent", "scrollToBottom", "endConsult", "openConsult"]);
// //
const inputText = ref(""); const inputText = ref("");
@ -405,34 +406,68 @@ const handleEndConsult = () => {
}); });
}; };
const morePanelButtons = [ //
{ text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker }, const handleOpenConsult = () => {
{ uni.showModal({
text: "回访任务", title: "确认开启会话",
icon: "/static/icon/huifangrenwu.png", content: "确定要重新开启本次会话吗?",
action: showFollowUpTasks, confirmText: "确定开启",
}, cancelText: "取消",
{ success: (res) => {
text: "常用语", if (res.confirm) {
icon: "/static/icon/changyongyu.png", //
action: goToCommonPhrases, showMorePanel.value = false;
}, //
{ emit("openConsult");
text: "宣教", }
icon: "/static/icon/xuanjiaowenzhang.png", },
action: goToArticleList, });
}, };
{
text: "问卷", const morePanelButtons = computed(() => {
icon: "/static/icon/wenjuan.png", const buttons = [
action: goToSurveyList, { text: "照片", icon: "/static/icon/zhaopian.png", action: showImagePicker },
}, {
{ text: "回访任务",
text: "结束问诊", icon: "/static/icon/huifangrenwu.png",
icon: "/static/icon/jieshuzixun.png", action: showFollowUpTasks,
action: handleEndConsult, },
}, {
]; text: "常用语",
icon: "/static/icon/changyongyu.png",
action: goToCommonPhrases,
},
{
text: "宣教",
icon: "/static/icon/xuanjiaowenzhang.png",
action: goToArticleList,
},
{
text: "问卷",
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() { function handleInputFocus() {
console.log("handleInputFocus"); console.log("handleInputFocus");

View File

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

View File

@ -149,25 +149,27 @@
/> />
<!-- 聊天输入组件 --> <!-- 聊天输入组件 -->
<ChatInput <ChatInput
v-if="!isEvaluationPopupOpen && !showConsultAccept" v-if="!isEvaluationPopupOpen && !showConsultAccept"
ref="chatInputRef" ref="chatInputRef"
:timChatManager="timChatManager" :timChatManager="timChatManager"
:formatTime="formatTime" :formatTime="formatTime"
:groupId=" :groupId="
chatInfo.conversationID chatInfo.conversationID
? chatInfo.conversationID.replace('GROUP', '') ? chatInfo.conversationID.replace('GROUP', '')
: '' : ''
" "
:userId="openid" :userId="openid"
:teamId="teamId" :teamId="teamId"
:patientId="patientId" :patientId="patientId"
:corpId="corpId" :corpId="corpId"
:patientInfo="patientInfo" :patientInfo="patientInfo"
@scrollToBottom="() => scrollToBottom(true)" :orderStatus="orderStatus"
@messageSent="() => scrollToBottom(true)" @scrollToBottom="() => scrollToBottom(true)"
@endConsult="handleEndConsult" @messageSent="() => scrollToBottom(true)"
/> @endConsult="handleEndConsult"
@openConsult="handleOpenConsult"
/>
</view> </view>
</template> </template>
@ -247,8 +249,8 @@ const patientInfo = ref({
}); });
// ID // ID
const patientId = ref(""); const patientId = ref("");
const teamId = ref(""); const teamId = ref("");
// - pending // - pending
const showConsultAccept = computed(() => orderStatus.value === "pending"); const showConsultAccept = computed(() => orderStatus.value === "pending");
@ -301,20 +303,20 @@ function isSystemMessage(message) {
} }
// //
const fetchGroupOrderStatus = async () => { const fetchGroupOrderStatus = async () => {
try { try {
const result = await api("getGroupListByGroupId", { const result = await api("getGroupListByGroupId", {
groupId: groupId.value, groupId: groupId.value,
}); });
if (result.success && result.data) { if (result.success && result.data) {
orderStatus.value = result.data.orderStatus || ""; orderStatus.value = result.data.orderStatus || "";
// //
const teamName = result.data.team?.name || "群聊"; const teamName = result.data.team?.name || "群聊";
updateNavigationTitle(teamName); updateNavigationTitle(teamName);
teamId.value = result.data.teamId || result.data.team?.teamId || result.data.team?._id || ""; teamId.value = result.data.teamId || result.data.team?.teamId || result.data.team?._id || "";
// //
if (result.data.patient) { if (result.data.patient) {
@ -330,12 +332,12 @@ const fetchGroupOrderStatus = async () => {
patientId.value = result.data.patientId.toString(); patientId.value = result.data.patientId.toString();
} }
console.log("获取群组订单状态:", { console.log("获取群组订单状态:", {
orderStatus: orderStatus.value, orderStatus: orderStatus.value,
teamName: teamName, teamName: teamName,
patientInfo: patientInfo.value, patientInfo: patientInfo.value,
groupId: groupId.value, groupId: groupId.value,
}); });
} else { } else {
console.error("获取群组订单状态失败:", result.message); console.error("获取群组订单状态失败:", result.message);
} }
@ -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(() => { onUnmounted(() => {
clearMessageCache(); clearMessageCache();
@ -983,4 +1022,4 @@ onUnmounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
@import "./chat.scss"; @import "./chat.scss";
</style> </style>

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
柚康企微客服 柚康企微客服
</view> </view>
<view class="flex justify-center overflow-hidden"> <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>
<view class="mt-10 px-15 text-base text-dark leading-normal text-center"> <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 }} 成员: {{ i.memberList && i.memberList.length ? i.memberList.length : 0 }}
</view> </view>
<view class="min-w-120 text-base text-dark"> <view class="min-w-120 text-base text-dark">
患者: 200 患者: {{ i.customerCount }}
</view> </view>
</view> </view>
</view> </view>
@ -61,13 +61,14 @@ function toDetail(team) {
} }
async function getTeams() { 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 => ({ const arr = res && Array.isArray(res.data) ? res.data.map(i => ({
id: i._id, id: i._id,
teamId: i.teamId, teamId: i.teamId,
name: i.name, name: i.name,
memberList: i.memberList, memberList: i.memberList,
creator: i.creator creator: i.creator,
customerCount: i.customerCount > 0 ? i.customerCount : 0
})) : []; })) : [];
list.value = arr; list.value = arr;
} }

View File

@ -94,6 +94,7 @@ const urlsConfig = {
getChatRecordsByGroupId: "getChatRecordsByGroupId", getChatRecordsByGroupId: "getChatRecordsByGroupId",
sendConsultRejectedMessage: "sendConsultRejectedMessage", sendConsultRejectedMessage: "sendConsultRejectedMessage",
endConsultation: "endConsultation", endConsultation: "endConsultation",
openConsultation: "openConsultation",
getGroupListByGroupId: "getGroupListByGroupId", getGroupListByGroupId: "getGroupListByGroupId",
acceptConsultation: "acceptConsultation", acceptConsultation: "acceptConsultation",
sendArticleMessage: "sendArticleMessage", 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('account')
uni.removeStorageSync('openid') uni.removeStorageSync('openid')
uni.reLaunch({ uni.reLaunch({
url: '/pages-center/login/login' url: '/pages/login/login'
}) })
} }
}) })
@ -1153,6 +1153,9 @@ class TimChatManager {
case "consult_ended": case "consult_ended":
lastMessageText = '已结束当前会话' lastMessageText = '已结束当前会话'
break break
case "consult_reopened":
lastMessageText = '会话已重新开启'
break
default: default:
lastMessageText = '[自定义消息]' lastMessageText = '[自定义消息]'
} }
@ -2580,6 +2583,9 @@ class TimChatManager {
case 'consult_ended': case 'consult_ended':
messageText = '已结束当前会话' messageText = '已结束当前会话'
break break
case 'consult_reopened':
messageText = '会话已重新开启'
break
default: default:
messageText = customData.content || '[自定义消息]' messageText = customData.content || '[自定义消息]'
} }