diff --git a/.env.development b/.env.development
index 477c818..d8e7453 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,5 @@
-MP_API_BASE_URL=http://192.168.137.1:8080
+MP_API_BASE_URL=http://localhost:8080
+MP_IMAGE_URL=https://patient.youcan365.com
MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e
MP_CORP_ID=wwe3fb2faa52cf9dfb
diff --git a/.env.localhost b/.env.localhost
index c5e7c34..dd49388 100644
--- a/.env.localhost
+++ b/.env.localhost
@@ -1,4 +1,5 @@
MP_API_BASE_URL=http://localhost:8080
MP_CACHE_PREFIX=development
MP_WX_APP_ID=wx93af55767423938e
+MP_CORP_ID=wwe3fb2faa52cf9dfb
MP_TIM_SDK_APP_ID=1600072268
diff --git a/App.vue b/App.vue
index bffc016..0ea7c2d 100644
--- a/App.vue
+++ b/App.vue
@@ -48,6 +48,9 @@ page {
.relative {
position: relative;
}
+.absolute{
+ position: absolute;
+}
.inline-block {
display: inline-block;
diff --git a/components/button-footer.vue b/components/button-footer.vue
index b2e924f..2677c92 100644
--- a/components/button-footer.vue
+++ b/components/button-footer.vue
@@ -1,5 +1,5 @@
-
diff --git a/components/form-template/form-cell/form-select.vue b/components/form-template/form-cell/form-select.vue
index 2a0deae..c7c64e8 100644
--- a/components/form-template/form-cell/form-select.vue
+++ b/components/form-template/form-cell/form-select.vue
@@ -1,5 +1,5 @@
-
+
@@ -49,6 +49,7 @@ const displayRange = computed(() => {
}
return props.range;
})
+
const value = computed(() => {
if (!props.form) return '';
const currentValue = props.form[props.title];
@@ -65,7 +66,7 @@ function change(e) {
const selectedValue = props.range[e.detail.value];
emits('change', {
title: props.title,
- value: selectedValue
+ value: typeof selectedValue === 'object' ? selectedValue.value : selectedValue
})
}
diff --git a/components/form-template/form-cell/form-textarea.vue b/components/form-template/form-cell/form-textarea.vue
index 2a6ab4b..b6c5fd6 100644
--- a/components/form-template/form-cell/form-textarea.vue
+++ b/components/form-template/form-cell/form-textarea.vue
@@ -5,14 +5,15 @@
{{ value && value.length ? value.length : 0 }} / {{ wordLimit }}
@@ -25,6 +26,10 @@ import { computed } from 'vue';
const emits = defineEmits(['change']);
const props = defineProps({
+ border: {
+ type: Boolean,
+ default: true
+ },
form: {
type: Object,
default: () => ({})
@@ -98,12 +103,15 @@ function change(e) {
.form-textarea {
width: 100%;
font-size: 28rpx;
- border: 1px solid #eee;
padding: 20rpx;
border-radius: 8rpx;
box-sizing: border-box;
}
+.form-textarea--border {
+ border: 1px solid #eee;
+}
+
.form-textarea__count {
padding-top: 20rpx;
text-align: right;
diff --git a/components/full-page.vue b/components/full-page.vue
index 0fa427f..45eefc8 100644
--- a/components/full-page.vue
+++ b/components/full-page.vue
@@ -1,9 +1,9 @@
-
+
-
+
@@ -16,7 +16,7 @@
-
+
@@ -27,8 +27,11 @@ import useDebounce from '@/utils/useDebounce';
const emits = defineEmits(['reachBottom']);
const props = defineProps({
customScroll: { type: Boolean, default: false },
+ mainClass: { type: String, default: '' },
mainStyle: { default: '' },
- pageStyle: { default: '' }
+ pageClass: { type: String, default: '' },
+ pageStyle: { default: '' },
+ showSafeArea: { type: Boolean, default: true }
});
const slots = useSlots();
const hasHeader = computed(() => !!slots.header);
diff --git a/hooks/useGuard.js b/hooks/useGuard.js
index 7d0f41b..4dffd6f 100644
--- a/hooks/useGuard.js
+++ b/hooks/useGuard.js
@@ -39,7 +39,9 @@ export default function useGuard() {
async function triggleShowEvents() {
await promise;
- onShowEvents.value.forEach(fn => fn(onShowOptions.value))
+ if (account.value && account.value.openid) {
+ onShowEvents.value.forEach(fn => fn(onShowOptions.value))
+ }
}
function useShow(fn) {
@@ -53,8 +55,8 @@ export default function useGuard() {
const route = routes.find(i => page && i.path === page.route);
const requireLogin = route && route.meta && route.meta.login;
if (requireLogin && !account.value) {
- const res = await login()
- if (res) {
+ await login()
+ if (account.value) {
resolve()
} else {
return toLoginPage(opts, page.route);
@@ -64,7 +66,6 @@ export default function useGuard() {
})
onShow(opts => {
- console.log('onShow')
onShowOptions.value = { ...opts };
triggleShowEvents()
})
diff --git a/pages.json b/pages.json
index d96314c..043c9d0 100644
--- a/pages.json
+++ b/pages.json
@@ -27,9 +27,21 @@
}
},
{
- "path": "pages/case/case",
+ "path": "pages/message/common-phrases",
"style": {
- "navigationBarTitleText": "病例"
+ "navigationBarTitleText": "常用语"
+ }
+ },
+ {
+ "path": "pages/message/article-list",
+ "style": {
+ "navigationBarTitleText": "宣教文章"
+ }
+ },
+ {
+ "path": "pages/message/survey-list",
+ "style": {
+ "navigationBarTitleText": "问卷列表"
}
},
{
@@ -158,6 +170,12 @@
"navigationBarTitleText": "工作台"
}
},
+ {
+ "path": "pages/case/case",
+ "style": {
+ "navigationBarTitleText": "病例"
+ }
+ },
{
"path": "pages/work/profile",
"style": {
@@ -169,6 +187,24 @@
"style": {
"navigationBarTitleText": "选择科室"
}
+ },
+ {
+ "path": "pages/work/verify/assistant",
+ "style": {
+ "navigationBarTitleText": "上传证照"
+ }
+ },
+ {
+ "path": "pages/work/verify/doctor",
+ "style": {
+ "navigationBarTitleText": "上传证照"
+ }
+ },
+ {
+ "path": "pages/login/login",
+ "style": {
+ "navigationBarTitleText": "授权登录"
+ }
}
],
"globalStyle": {
diff --git a/pages/login/login.vue b/pages/login/login.vue
index f574300..73fb316 100644
--- a/pages/login/login.vue
+++ b/pages/login/login.vue
@@ -1,27 +1,11 @@
-
-
- {{
- team.teamName
- }}
- {{ team.corpName }}
- 为您提供团队个性化专属服务
-
-
+
柚健康
生命全周期健康管理伙伴
-
+
+
+
+
+
diff --git a/pages/message/chat.scss b/pages/message/chat.scss
index a885920..4ea23f9 100644
--- a/pages/message/chat.scss
+++ b/pages/message/chat.scss
@@ -1248,4 +1248,112 @@ $primary-color: #0877F1;
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10rpx); }
-}
\ No newline at end of file
+}
+
+/* 文章卡片样式 */
+.article-card {
+ display: flex;
+ align-items: center;
+ background-color: #fff;
+ border-radius: 12rpx;
+ padding: 24rpx;
+ max-width: 500rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+}
+
+.article-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ margin-right: 20rpx;
+ min-width: 0;
+}
+
+.article-title {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+ line-height: 1.4;
+ margin-bottom: 8rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.article-desc {
+ font-size: 24rpx;
+ color: #999;
+ line-height: 1.3;
+}
+
+.article-image {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 8rpx;
+ flex-shrink: 0;
+}
+
+/* 文章卡片在不同消息流中的样式 */
+.message-right .article-card {
+ background-color: #e8f4ff;
+}
+
+.message-left .article-card {
+ background-color: #fff;
+}
+
+/* 问卷卡片样式 */
+.survey-card {
+ display: flex;
+ align-items: center;
+ background-color: #fff;
+ border-radius: 12rpx;
+ padding: 24rpx;
+ max-width: 500rpx;
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+}
+
+.survey-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ margin-right: 20rpx;
+ min-width: 0;
+}
+
+.survey-title {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 500;
+ line-height: 1.4;
+ margin-bottom: 8rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+.survey-desc {
+ font-size: 24rpx;
+ color: #999;
+ line-height: 1.3;
+}
+
+.survey-image {
+ width: 120rpx;
+ height: 120rpx;
+ border-radius: 8rpx;
+ flex-shrink: 0;
+}
+
+/* 问卷卡片在不同消息流中的样式 */
+.message-right .survey-card {
+ background-color: #e8f4ff;
+}
+
+.message-left .survey-card {
+ background-color: #fff;
+}
diff --git a/pages/message/common-phrases.vue b/pages/message/common-phrases.vue
new file mode 100644
index 0000000..c5f549b
--- /dev/null
+++ b/pages/message/common-phrases.vue
@@ -0,0 +1,1058 @@
+
+
+
+
+
+
+
+
+
+
+ {{ phrase.content }}
+
+
+ 编辑
+
+
+ 删除
+
+
+
+
+
+ 暂无常用语
+ 点击下方按钮添加常用语
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/message/components/chat-input.vue b/pages/message/components/chat-input.vue
index 0bdc9be..d4f9c22 100644
--- a/pages/message/components/chat-input.vue
+++ b/pages/message/components/chat-input.vue
@@ -53,7 +53,7 @@
diff --git a/pages/message/components/consult-accept.vue b/pages/message/components/consult-accept.vue
new file mode 100644
index 0000000..c593baa
--- /dev/null
+++ b/pages/message/components/consult-accept.vue
@@ -0,0 +1,88 @@
+
+
+
+
+ 患者已发起咨询申请,请及时接诊
+
+
+ 暂不接受
+ 接受咨询
+
+
+
+
+
+
+
+
diff --git a/pages/message/components/message-types.vue b/pages/message/components/message-types.vue
index 6b22ced..f128132 100644
--- a/pages/message/components/message-types.vue
+++ b/pages/message/components/message-types.vue
@@ -3,6 +3,7 @@
{{ message.payload.text }}
+
-
-
+
+
+
+ {{ getArticleData(message).title }}
+ {{ getArticleData(message).desc }}
+
+
+
+
+
+
+
+ {{ getSurveyData(message).title }}
+ {{ getSurveyData(message).desc }}
+
+
+
+
+
+
+ -->
+
diff --git a/pages/message/components/system-message.vue b/pages/message/components/system-message.vue
index 0d28603..de4a907 100644
--- a/pages/message/components/system-message.vue
+++ b/pages/message/components/system-message.vue
@@ -1,9 +1,9 @@
-
+
{{ text }}
@@ -21,17 +21,111 @@ const props = defineProps({
const payload = computed(() => props.message?.payload || {});
-const extension = computed(() => {
+// 解析系统消息内容
+const systemMessageData = computed(() => {
try {
- return JSON.parse(payload.value.extension);
+ // 尝试从 payload.data 解析系统消息
+ if (payload.value.data) {
+ const data = typeof payload.value.data === 'string'
+ ? JSON.parse(payload.value.data)
+ : payload.value.data;
+
+ if (data.type === 'system_message') {
+ return data;
+ }
+ }
} catch (e) {
- return {};
+ console.error('解析系统消息失败:', e);
}
+ return null;
});
-const text = computed(() => extension.value.patient || payload.value.data || '')
+// 解析扩展信息
+const extension = computed(() => {
+ try {
+ if (payload.value.extension) {
+ return typeof payload.value.extension === 'string'
+ ? JSON.parse(payload.value.extension)
+ : payload.value.extension;
+ }
+ } catch (e) {
+ console.error('解析扩展信息失败:', e);
+ }
+ return {};
+});
-const notifyText = computed(() => extension.value.notifyText || '')
+// 显示的文本内容
+const text = computed(() => {
+ // 优先从系统消息数据中获取文本
+ if (systemMessageData.value?.text) {
+ return systemMessageData.value.text;
+ }
+
+ // 根据消息类型返回默认文本
+ if (systemMessageData.value?.messageType) {
+ const messageType = systemMessageData.value.messageType;
+ switch (messageType) {
+ case 'consult_pending':
+ return '患者已发起咨询申请,请及时接诊';
+ case 'consult_accepted':
+ return '医生已接诊';
+ case 'consult_rejected':
+ return '医生暂时无法接诊';
+ case 'consult_ended':
+ return '问诊已结束';
+ case 'consult_timeout':
+ return '问诊已超时';
+ default:
+ return systemMessageData.value.content || '[系统消息]';
+ }
+ }
+
+ // 兼容旧格式:从 extension 中获取
+ if (extension.value.patient) {
+ return extension.value.patient;
+ }
+
+ // 兼容旧格式:直接从 payload.data 获取
+ if (payload.value.data && typeof payload.value.data === 'string') {
+ // 如果 data 不是 JSON 格式,直接显示
+ try {
+ JSON.parse(payload.value.data);
+ } catch {
+ return payload.value.data;
+ }
+ }
+
+ return '[系统消息]';
+});
+
+// 通知文本(红色提示)
+const notifyText = computed(() => {
+ // 从扩展信息中获取通知文本
+ if (extension.value.notifyText) {
+ return extension.value.notifyText;
+ }
+
+ // 根据系统消息类型显示不同的通知
+ if (systemMessageData.value) {
+ const messageType = systemMessageData.value.messageType;
+ switch (messageType) {
+ case 'consult_pending':
+ return '待接诊';
+ case 'consult_rejected':
+ return '已拒绝';
+ case 'consult_timeout':
+ return '已超时';
+ case 'consult_accepted':
+ return '已接诊';
+ case 'consult_ended':
+ return '已结束';
+ default:
+ return '';
+ }
+ }
+
+ return '';
+});
diff --git a/pages/message/index.vue b/pages/message/index.vue
index 19d0432..495f85a 100644
--- a/pages/message/index.vue
+++ b/pages/message/index.vue
@@ -54,7 +54,9 @@
@@ -62,7 +64,9 @@
@@ -108,9 +112,24 @@
+
+
+
+
+
+
scrollToBottom(true)"
@@ -142,10 +161,13 @@ import {
handleViewDetail,
checkIMConnectionStatus,
} from "@/utils/chat-utils.js";
+import { sendConsultRejectedMessage } from "@/utils/api.js";
import useGroupChat from "./hooks/use-group-chat";
import MessageTypes from "./components/message-types.vue";
import ChatInput from "./components/chat-input.vue";
import SystemMessage from "./components/system-message.vue";
+import ConsultAccept from "./components/consult-accept.vue";
+import RejectReasonModal from "./components/reject-reason-modal.vue";
const timChatManager = globalTimChatManager;
@@ -153,16 +175,16 @@ const timChatManager = globalTimChatManager;
const { account, openid, isIMInitialized } = storeToRefs(useAccountStore());
const { initIMAfterLogin } = useAccountStore();
+// 聊天输入组件引用
+const chatInputRef = ref(null);
+
const groupId = ref("");
-const {
- chatMember,
- getGroupInfo
-} = useGroupChat(groupId);
+const { chatMember, getGroupInfo } = useGroupChat(groupId);
// 动态设置导航栏标题
const updateNavigationTitle = () => {
uni.setNavigationBarTitle({
- title: "群聊"
+ title: "群聊",
});
};
@@ -176,6 +198,12 @@ const chatInfo = ref({
// 评价弹窗状态
const isEvaluationPopupOpen = ref(false);
+// 接受问诊状态
+const showConsultAccept = ref(false);
+
+// 拒绝原因对话框状态
+const showRejectReasonModal = ref(false);
+
// 消息列表相关状态
const messageList = ref([]);
const isLoading = ref(false);
@@ -188,10 +216,73 @@ const lastFirstMessageId = ref("");
// 判断是否为系统消息
function isSystemMessage(message) {
- const description = message.payload?.description;
- return (
- message.type === "TIMCustomElem" && description === "SYSTEM_NOTIFICATION"
- );
+ if (message.type !== "TIMCustomElem") {
+ return false;
+ }
+
+ try {
+ // 检查 payload.data 是否包含系统消息标记
+ if (message.payload?.data) {
+ const data = typeof message.payload.data === 'string'
+ ? JSON.parse(message.payload.data)
+ : message.payload.data;
+
+ // 检查是否为系统消息类型
+ if (data.type === 'system_message') {
+ return true;
+ }
+ }
+
+ // 检查 description 是否为系统消息标记
+ if (message.payload?.description === '系统消息标记') {
+ return true;
+ }
+
+ // 兼容旧的系统消息格式
+ if (message.payload?.description === 'SYSTEM_NOTIFICATION') {
+ return true;
+ }
+ } catch (error) {
+ console.error('判断系统消息失败:', error);
+ }
+
+ return false;
+}
+
+// 检查是否有待接诊的系统消息
+function checkConsultPendingStatus() {
+ // 查找最新的系统消息
+ for (let i = messageList.value.length - 1; i >= 0; i--) {
+ const message = messageList.value[i];
+
+ if (message.type === "TIMCustomElem" && message.payload?.data) {
+ try {
+ const data = typeof message.payload.data === 'string'
+ ? JSON.parse(message.payload.data)
+ : message.payload.data;
+
+ // 如果是 consult_pending 类型的系统消息,显示接受组件
+ if (data.type === 'system_message' && data.messageType === 'consult_pending') {
+ showConsultAccept.value = true;
+ return;
+ }
+
+ // 如果是其他系统消息类型(如已接诊、已结束等),隐藏接受组件
+ if (data.type === 'system_message' &&
+ (data.messageType === 'consult_accepted' ||
+ data.messageType === 'consult_ended' ||
+ data.messageType === 'consult_rejected')) {
+ showConsultAccept.value = false;
+ return;
+ }
+ } catch (error) {
+ console.error('解析系统消息失败:', error);
+ }
+ }
+ }
+
+ // 如果没有找到相关系统消息,隐藏接受组件
+ showConsultAccept.value = false;
}
// 获取消息气泡样式类
@@ -200,7 +291,6 @@ function getBubbleClass(message) {
if (message.type === "TIMImageElem") {
return "image-bubble";
}
-
if (message.type === "TIMCustomElem") {
return message.flow === "out" ? "user-bubble" : "doctor-bubble-blue";
}
@@ -284,6 +374,10 @@ const initTIMCallbacks = async () => {
if (!existingMessage) {
messageList.value.push(message);
console.log("✓ 添加消息到列表,当前消息数量:", messageList.value.length);
+
+ // 检查是否有待接诊的系统消息
+ checkConsultPendingStatus();
+
// 立即滚动到底部,不使用延迟
nextTick(() => {
scrollToBottom(true);
@@ -342,6 +436,9 @@ const initTIMCallbacks = async () => {
isCompleted.value = data.isCompleted || false;
isLoadingMore.value = false;
+ // 检查是否有待接诊的系统消息
+ checkConsultPendingStatus();
+
nextTick(() => {
if (data.isRefresh) {
console.log("后台刷新完成,保持当前滚动位置");
@@ -481,16 +578,16 @@ const scrollToBottom = (immediate = false) => {
if (messageList.value.length > 0) {
const lastMessage = messageList.value[messageList.value.length - 1];
const targetId = `msg-${lastMessage.ID}`;
-
+
if (immediate) {
// 立即滚动:先清空再设置,触发滚动
- scrollIntoView.value = '';
+ scrollIntoView.value = "";
nextTick(() => {
scrollIntoView.value = targetId;
});
} else {
// 正常滚动,使用短延迟确保DOM更新
- scrollIntoView.value = '';
+ scrollIntoView.value = "";
setTimeout(() => {
scrollIntoView.value = targetId;
}, 50);
@@ -590,6 +687,119 @@ onHide(() => {
stopIMMonitoring();
});
+// 发送常用语(从常用语页面返回时调用)
+const sendCommonPhrase = (content) => {
+ if (chatInputRef.value) {
+ chatInputRef.value.sendTextMessageFromPhrase(content);
+ }
+};
+
+// 暴露方法给常用语页面调用
+defineExpose({
+ sendCommonPhrase,
+});
+
+// 处理接受问诊
+const handleAcceptConsult = async () => {
+ try {
+ uni.showLoading({
+ title: '处理中...',
+ });
+
+ // 发送接受问诊的系统消息
+ const customMessage = {
+ data: JSON.stringify({
+ type: 'system_message',
+ messageType: 'consult_accepted',
+ content: '医生已接诊',
+ timestamp: Date.now(),
+ }),
+ description: '系统消息标记',
+ extension: '',
+ };
+
+ const message = timChatManager.tim.createCustomMessage({
+ to: chatInfo.value.conversationID.replace('GROUP', ''),
+ conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
+ payload: customMessage,
+ });
+
+ const sendResult = await timChatManager.tim.sendMessage(message);
+
+ if (sendResult.code === 0) {
+ showConsultAccept.value = false;
+ uni.hideLoading();
+ uni.showToast({
+ title: '已接受问诊',
+ icon: 'success',
+ });
+ } else {
+ throw new Error(sendResult.message || '发送失败');
+ }
+ } catch (error) {
+ console.error('接受问诊失败:', error);
+ uni.hideLoading();
+ uni.showToast({
+ title: error.message || '操作失败',
+ icon: 'none',
+ });
+ }
+};
+
+// 处理拒绝问诊
+const handleRejectConsult = () => {
+ // 显示拒绝原因选择对话框
+ showRejectReasonModal.value = true;
+};
+
+// 处理拒绝原因确认
+const handleRejectReasonConfirm = async (reason) => {
+ try {
+ showRejectReasonModal.value = false;
+
+ uni.showLoading({
+ title: '处理中...',
+ });
+
+ // 获取医生信息
+ const memberName = account.value?.name || '医生';
+
+ // 获取群组ID
+ const groupId = chatInfo.value.conversationID.replace('GROUP', '');
+
+ // 调用后端接口发送拒绝消息
+ const result = await sendConsultRejectedMessage({
+ groupId,
+ memberName,
+ reason,
+ });
+
+ uni.hideLoading();
+
+ if (result.success) {
+ showConsultAccept.value = false;
+ 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',
+ });
+ }
+};
+
+// 处理拒绝原因取消
+const handleRejectReasonCancel = () => {
+ showRejectReasonModal.value = false;
+};
+
// 页面卸载
onUnmounted(() => {
clearMessageCache();
@@ -599,6 +809,172 @@ onUnmounted(() => {
timChatManager.setCallback("onMessageReceived", null);
timChatManager.setCallback("onMessageListLoaded", null);
timChatManager.setCallback("onError", null);
+
+ // 移除文章发送监听
+ uni.$off("sendArticle");
+ // 移除问卷发送监听
+ uni.$off("sendSurvey");
+});
+
+// 监听文章发送事件
+uni.$on("sendArticle", async (data) => {
+ const { article, corpId, userId } = data;
+
+ if (!article || !article._id) {
+ uni.showToast({
+ title: "文章信息不完整",
+ icon: "none",
+ });
+ return;
+ }
+
+ try {
+ // 获取环境变量
+ const env = __VITE_ENV__;
+ const baseUrl = env.VITE_PATIENT_PAGE_BASE_URL || "";
+
+ // 构建文章链接
+ const articleUrl = `${baseUrl}pages/article/index?id=${article._id}&corpId=${corpId}`;
+
+ // 创建自定义消息
+ const customMessage = {
+ data: JSON.stringify({
+ type: "article",
+ title: article.title || "宣教文章",
+ desc: "宣教文章",
+ url: articleUrl,
+ imgUrl:
+ "https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694",
+ }),
+ description: "ARTICLE",
+ extension: "",
+ };
+
+ // 发送自定义消息
+ const message = timChatManager.tim.createCustomMessage({
+ to: chatInfo.value.conversationID.replace("GROUP", ""),
+ conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
+ payload: customMessage,
+ });
+
+ const sendResult = await timChatManager.tim.sendMessage(message);
+
+ if (sendResult.code === 0) {
+ uni.showToast({
+ title: "发送成功",
+ icon: "success",
+ });
+
+ // 记录发送记录(可选)
+ // await addArticleSendRecord({
+ // corpId,
+ // userId,
+ // articleId: article._id,
+ // customerId: chatInfo.value.userID
+ // });
+ } else {
+ throw new Error(sendResult.message || "发送失败");
+ }
+ } catch (error) {
+ console.error("发送文章失败:", error);
+ uni.showToast({
+ title: error.message || "发送失败",
+ icon: "none",
+ });
+ }
+});
+
+// 监听问卷发送事件
+uni.$on("sendSurvey", async (data) => {
+ const { survey, corpId, userId, sendSurveyId } = data;
+
+ if (!survey || !survey._id) {
+ uni.showToast({
+ title: "问卷信息不完整",
+ icon: "none",
+ });
+ return;
+ }
+
+ try {
+ // 获取环境变量
+ const env = __VITE_ENV__;
+ const baseUrl = env.VITE_PATIENT_PAGE_BASE_URL || "";
+ const surveyUrl = env.VITE_SURVEY_URL || "";
+
+ // 获取客户信息
+ const customerId = chatInfo.value.userID || "";
+ const customerName = chatInfo.value.customerName || "";
+
+ // 创建问卷记录
+ const { createSurveyRecord } = await import("@/utils/api.js");
+ const recordRes = await createSurveyRecord({
+ corpId,
+ userId,
+ surveryId: survey._id,
+ memberId: customerId,
+ customer: customerName,
+ sendSurveyId,
+ });
+
+ if (!recordRes.success) {
+ throw new Error(recordRes.message || "创建问卷记录失败");
+ }
+
+ const answerId = recordRes.data?.id || "";
+
+ // 构建问卷链接
+ let surveyLink = "";
+ if (survey.createBy === "system") {
+ // 系统问卷
+ surveyLink = `${surveyUrl}?corpId=${corpId}&surveyId=${survey.surveyId}&memberId=${customerId}&sendSurveyId=${sendSurveyId}&userId=${userId}`;
+ } else {
+ // 自定义问卷
+ surveyLink = `${baseUrl}pages/survery/fill?corpId=${corpId}&surveryId=${
+ survey._id
+ }&memberId=${customerId}&answerId=${answerId}&name=${encodeURIComponent(
+ customerName
+ )}`;
+ }
+
+ // 创建自定义消息
+ const customMessage = {
+ data: JSON.stringify({
+ 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",
+ }),
+ description: "SURVEY",
+ extension: "",
+ };
+
+ // 发送自定义消息
+ const message = timChatManager.tim.createCustomMessage({
+ to: chatInfo.value.conversationID.replace("GROUP", ""),
+ conversationType: timChatManager.TIM.TYPES.CONV_GROUP,
+ payload: customMessage,
+ });
+
+ const sendResult = await timChatManager.tim.sendMessage(message);
+
+ if (sendResult.code === 0) {
+ uni.showToast({
+ title: "发送成功",
+ icon: "success",
+ });
+ } else {
+ throw new Error(sendResult.message || "发送失败");
+ }
+ } catch (error) {
+ console.error("发送问卷失败:", error);
+ uni.showToast({
+ title: error.message || "发送失败",
+ icon: "none",
+ });
+ }
});
diff --git a/pages/message/survey-list.vue b/pages/message/survey-list.vue
new file mode 100644
index 0000000..0115693
--- /dev/null
+++ b/pages/message/survey-list.vue
@@ -0,0 +1,423 @@
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
+ {{ survey.name }}
+ {{ survey.description || '暂无问卷说明' }}
+
+
+
+ 发送
+
+
+
+
+
+
+ 加载中...
+
+
+
+ 没有更多了
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/work/components/cert-popup.vue b/pages/work/components/cert-popup.vue
new file mode 100644
index 0000000..59632b6
--- /dev/null
+++ b/pages/work/components/cert-popup.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+ 认证须知
+
+
+ 1、认证通过后,您个人账号病历档案管理数(含所有团队)上限由10个升级至100个;
+
+
+ 2、认证前请仔细核对个人信息,确保准确无误。认证后部分信息不支持修改,包括姓名、岗位等。如需修改以上信息,请联系客服人工处理
+
+ 点击添加客服
+
+
+
+
+ 提示
+
+
+ 您的认证已通过。
+
+
+ 若需要修改姓名、岗位等信息,请联系客服人工处理
+
+
+ 点击添加客服
+
+
+
+
+ 认证失败原因
+
+
+ {{ reason }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/work/profile copy.vue b/pages/work/profile copy.vue
new file mode 100644
index 0000000..cde3235
--- /dev/null
+++ b/pages/work/profile copy.vue
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formData.mobile }}
+
+
+
+
+
+
+
+
+
+
+ {{ formData.departmentName || "请选择" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/work/profile.vue b/pages/work/profile.vue
index 6b36534..3011bd8 100644
--- a/pages/work/profile.vue
+++ b/pages/work/profile.vue
@@ -1,215 +1,108 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- {{ formData.mobile }}
-
-
-
-
-
-
-
-
-
-
- {{ formData.departmentName || "请选择" }}
-
-
-
-
+
+
+
+
-
-
+
+
+
+
+ {{ jobStr }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
-
diff --git a/pages/work/verify/assistant.vue b/pages/work/verify/assistant.vue
new file mode 100644
index 0000000..b364a2b
--- /dev/null
+++ b/pages/work/verify/assistant.vue
@@ -0,0 +1,179 @@
+
+
+
+
+ 请上传
+ 身份证正反面
+
+
+
+
+
+
+
+
+ 上传人像面
+
+
+
+
+
+
+
+
+
+ 上传国徽面
+
+
+
+
+
+ 身份证示例
+
+
+
+
+
+ 拍摄须知
+
+
+
+
+ 标准
+
+
+
+
+ 缺边
+
+
+
+
+ 模糊
+
+
+
+
+ 闪光
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/work/verify/doctor.vue b/pages/work/verify/doctor.vue
new file mode 100644
index 0000000..541313c
--- /dev/null
+++ b/pages/work/verify/doctor.vue
@@ -0,0 +1,154 @@
+
+
+
+ 请填写执业医院
+
+
+
+
+
+ 请上传
+ 医师执业资格证
+
+
+
+
+
+
+ 上传第一页
+
+
+
+
+
+
+ 上传第二页
+
+
+
+
+ 证书示例
+
+ 1、确保姓名、照片、编号、执业范围、签发机关等清晰可见
+ 2、需上传证书第一、第二页,图片仅供参考,以实际证书为准
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/work/work.vue b/pages/work/work.vue
index 0b2dce0..2274eda 100644
--- a/pages/work/work.vue
+++ b/pages/work/work.vue
@@ -1,80 +1,95 @@
-
-
-
\ No newline at end of file
+// }
\ No newline at end of file
diff --git a/routes/index.js b/routes/index.js
index 94c80dd..bb88465 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -9,8 +9,12 @@ export default [
},
{
path: 'pages/message/message',
- meta: { title: '消息', login: false },
- style: { navigationStyle: 'custom' }
+ meta: { title: '消息' }
+ },
+ {
+ path: 'pages/message/index',
+ meta: { title: '聊天' },
+ style: { enablePullDownRefresh: false }
},
{
path: 'pages/message/index',
@@ -103,14 +107,30 @@ export default [
},
{
path: 'pages/work/work',
- meta: { title: '工作台', login: false }
+ meta: { title: '工作台' }
+ },
+ {
+ path: 'pages/case/case',
+ meta: { title: '病例' }
},
{
path: 'pages/work/profile',
- meta: { title: '完善个人信息', login: false }
+ meta: { title: '完善个人信息' }
},
{
path: 'pages/work/department-select',
- meta: { title: '选择科室', login: false }
- }
+ meta: { title: '选择科室' }
+ },
+ {
+ path: 'pages/work/verify/assistant',
+ meta: { title: '上传证照', login: true }
+ },
+ {
+ path: 'pages/work/verify/doctor',
+ meta: { title: '上传证照', login: true }
+ },
+ {
+ path: 'pages/login/login',
+ meta: { title: '授权登录' }
+ },
]
diff --git a/static/icon/icon-chinese-rx.png b/static/icon/icon-chinese-rx.png
deleted file mode 100644
index 10ee891..0000000
Binary files a/static/icon/icon-chinese-rx.png and /dev/null differ
diff --git a/static/icon/icon-western-rx.png b/static/icon/icon-western-rx.png
deleted file mode 100644
index 00cb9a8..0000000
Binary files a/static/icon/icon-western-rx.png and /dev/null differ
diff --git a/static/icon/kaichufang.png b/static/icon/kaichufang.png
deleted file mode 100644
index 647f267..0000000
Binary files a/static/icon/kaichufang.png and /dev/null differ
diff --git a/static/icon/kaizhongyao.png b/static/icon/kaizhongyao.png
deleted file mode 100644
index f9d649e..0000000
Binary files a/static/icon/kaizhongyao.png and /dev/null differ
diff --git a/static/icon/quxiaobingtuikuan.png b/static/icon/quxiaobingtuikuan.png
deleted file mode 100644
index 97c24a3..0000000
Binary files a/static/icon/quxiaobingtuikuan.png and /dev/null differ
diff --git a/static/work/aIDCard.png b/static/work/aIDCard.png
new file mode 100644
index 0000000..66bdc4f
Binary files /dev/null and b/static/work/aIDCard.png differ
diff --git a/static/work/camera.png b/static/work/camera.png
new file mode 100644
index 0000000..61ede37
Binary files /dev/null and b/static/work/camera.png differ
diff --git a/static/work/cardBack.png b/static/work/cardBack.png
new file mode 100644
index 0000000..337dd1f
Binary files /dev/null and b/static/work/cardBack.png differ
diff --git a/static/work/cardFront.png b/static/work/cardFront.png
new file mode 100644
index 0000000..30d923f
Binary files /dev/null and b/static/work/cardFront.png differ
diff --git a/static/work/error.png b/static/work/error.png
new file mode 100644
index 0000000..1d2dbe0
Binary files /dev/null and b/static/work/error.png differ
diff --git a/static/work/fIDCard.png b/static/work/fIDCard.png
new file mode 100644
index 0000000..99e5b40
Binary files /dev/null and b/static/work/fIDCard.png differ
diff --git a/static/work/flashCard.png b/static/work/flashCard.png
new file mode 100644
index 0000000..0b3a4ed
Binary files /dev/null and b/static/work/flashCard.png differ
diff --git a/static/work/hook.png b/static/work/hook.png
new file mode 100644
index 0000000..c337dc7
Binary files /dev/null and b/static/work/hook.png differ
diff --git a/static/work/lackCard.png b/static/work/lackCard.png
new file mode 100644
index 0000000..a14d205
Binary files /dev/null and b/static/work/lackCard.png differ
diff --git a/static/work/licenseBack.png b/static/work/licenseBack.png
new file mode 100644
index 0000000..bd10e67
Binary files /dev/null and b/static/work/licenseBack.png differ
diff --git a/static/work/licenseFront.png b/static/work/licenseFront.png
new file mode 100644
index 0000000..ec36c2e
Binary files /dev/null and b/static/work/licenseFront.png differ
diff --git a/static/work/more.svg b/static/work/more.svg
new file mode 100644
index 0000000..dae9067
--- /dev/null
+++ b/static/work/more.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/work/pen.svg b/static/work/pen.svg
new file mode 100644
index 0000000..89fe9b4
--- /dev/null
+++ b/static/work/pen.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/work/qrcode.svg b/static/work/qrcode.svg
new file mode 100644
index 0000000..565f66a
--- /dev/null
+++ b/static/work/qrcode.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/work/vagueCard.png b/static/work/vagueCard.png
new file mode 100644
index 0000000..cfad2d3
Binary files /dev/null and b/static/work/vagueCard.png differ
diff --git a/store/account.js b/store/account.js
index 44e6e47..8842dc1 100644
--- a/store/account.js
+++ b/store/account.js
@@ -8,17 +8,29 @@ const env = __VITE_ENV__;
export default defineStore("accountStore", () => {
const appid = env.MP_WX_APP_ID;
+ const corpId = env.MP_CORP_ID;
const account = ref(null);
- const loading = ref(false)
+ const loading = ref(false);
+ const loginPromise = ref(null);
// IM 相关
const openid = ref("");
const isIMInitialized = ref(false);
// 医生信息
const doctorInfo = ref(null);
- async function login(phoneCode = '') {
- if (loading.value) return;
- loading.value = true;
+ function getLoginPromise(phoneCode = '') {
+ if (loginPromise.value) return loginPromise.value;
+ loginPromise.value = loginByCode(phoneCode);
+ return loginPromise.value;
+ }
+
+ async function login(phoneCode) {
+ const res = await getLoginPromise(phoneCode);
+ loginPromise.value = null;
+ return res
+ }
+
+ async function loginByCode(phoneCode = '') {
try {
const { code } = await uni.login({
appid,
@@ -29,8 +41,8 @@ export default defineStore("accountStore", () => {
const res = await api('wxAppLogin', {
phoneCode,
code,
+ corpId,
});
- loading.value = false;
if (res.success && res.data) {
if (!res.data.mobile) {
const target = '/pages/login/login';
@@ -39,27 +51,26 @@ export default defineStore("accountStore", () => {
}
account.value = res.data;
openid.value = res.data.openid;
-
-
// 登录成功后初始化腾讯IM
- try {
- console.log('开始初始化腾讯IM,userID:', res.data.openid);
- await initGlobalTIM(res.data.openid);
- isIMInitialized.value = true;
- console.log('腾讯IM初始化成功');
- } catch (imError) {
- console.error('腾讯IM初始化失败:', imError);
- // IM初始化失败不影响登录流程
- }
+ // try {
+ // console.log('开始初始化腾讯IM,userID:', res.data.openid);
+ // await initGlobalTIM(res.data.openid);
+ // isIMInitialized.value = true;
+ // console.log('腾讯IM初始化成功');
+ // } catch (imError) {
+ // console.error('腾讯IM初始化失败:', imError);
+ // // IM初始化失败不影响登录流程
+ // }
await getDoctorInfo(openid.value);
return res.data
}
}
toast('登录失败,请重新登录');
+
} catch (e) {
toast('登录失败,请重新登录');
}
- loading.value = false
+ return Promise.reject()
}
async function getDoctorInfo() {
diff --git a/utils/api.js b/utils/api.js
index 171a88a..0fe94f3 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -12,13 +12,41 @@ const urlsConfig = {
getHospitalList: 'getRealHospital',
addCorpMember: 'addCorpMember',
getCorpMemberData: 'getCorpMemberData',
- updateCorpMember: 'updateCorpMember'
+ updateCorpMember: 'updateCorpMember',
+ addCorpMemberFromWxapp: "addCorpMemberFromWxapp",
+ updateCorpMemberFromWxapp: "updateCorpMemberFromWxapp",
+ submitCertProfile: 'submitCertProfile',
+ getMemberVerifyStatus: "getMemberVerifyStatus"
},
knowledgeBase: {
getArticleByIds: 'getArticleByIds',
// 诊断库(对齐 ykt-management-mobile/src/api/knowledgeBase.js)
getDisease: 'getDisease',
+ getCommonPhrases: 'getCommonPhrases',
+ saveCommonPhrase: 'saveCommonPhrase',
+ deleteCommonPhrase: 'deleteCommonPhrase',
+ getCommonPhraseCategories: 'getCommonPhraseCategories',
+ saveCommonPhraseCategory: 'saveCommonPhraseCategory',
+ // 个人常用语接口
+ getPersonalPhrases: 'getPersonalPhrases',
+ savePersonalPhrase: 'savePersonalPhrase',
+ deletePersonalPhrase: 'deletePersonalPhrase',
+ getPersonalPhraseCategories: 'getPersonalPhraseCategories',
+ savePersonalPhraseCategory: 'savePersonalPhraseCategory',
+ deletePersonalPhraseCategory: 'deletePersonalPhraseCategory',
+ // 宣教文章接口
+ getArticleCateList: 'getArticleCateList',
+ getArticleList: 'getArticleList',
+ getArticle: 'getArticle',
+ addArticleSendRecord: 'addArticleSendRecord'
+ },
+
+ survery: {
+ getSurveyCateList: 'getSurveryCateList',
+ getSurveyList: 'getList',
+ createSurveyRecord: 'createRecord',
+ getSurveyDetail: 'getDetail'
},
member: {
addCustomer: 'add',
@@ -70,6 +98,8 @@ const urlsConfig = {
addServiceRecord: 'addServiceRecord',
updateServiceRecord: 'updateServiceRecord',
removeServiceRecord: 'removeServiceRecord',
+ getChatRecordsByGroupId: "getChatRecordsByGroupId",
+ sendConsultRejectedMessage: "sendConsultRejectedMessage"
}
}
@@ -102,3 +132,50 @@ export default async function api(urlId, data) {
}
})
}
+
+// 宣教文章相关 API
+export async function getArticleCateList(data) {
+ return api('getArticleCateList', data);
+}
+
+export async function getArticleList(data) {
+ return api('getArticleList', data);
+}
+
+export async function getArticle(data) {
+ return api('getArticle', data);
+}
+
+export async function addArticleSendRecord(data) {
+ return api('addArticleSendRecord', data);
+}
+
+// 问卷相关 API
+export async function getSurveyCateList(data) {
+ return api('getSurveyCateList', data);
+}
+
+export async function getSurveyList(data) {
+ return api('getSurveyList', data);
+}
+
+export async function createSurveyRecord(data) {
+ return api('createSurveyRecord', data);
+}
+
+export async function getSurveyDetail(data) {
+ return api('getSurveyDetail', data);
+}
+
+
+
+// IM 系统消息相关 API
+export async function sendConsultRejectedMessage(data) {
+ return request({
+ url: '/getYoucanData/im',
+ data: {
+ type: 'sendConsultRejectedMessage',
+ ...data
+ }
+ });
+}
diff --git a/utils/chat-utils.js b/utils/chat-utils.js
index 12849c4..c2d5231 100644
--- a/utils/chat-utils.js
+++ b/utils/chat-utils.js
@@ -25,10 +25,10 @@ export const checkConsultationStatus = (waitingForDoctor, consultationEnded) =>
return true;
};
-//
// 检查IM连接状态
export const checkIMConnection = (timChatManager) => {
if (!timChatManager.tim || !timChatManager.isLoggedIn) {
+ // showMessage("IM连接异常,请重新进入");
return false;
}
return true;
@@ -346,133 +346,31 @@ export const chooseMedia = async (options, onSuccess, onFail) => {
};
/**
- * 选择图片(针对 TIM SDK 优化)
+ * 选择图片
* @param {function} onSuccess - 成功回调
* @param {function} onFail - 失败回调
*/
-export const chooseImage = async (onSuccess, onFail) => {
- // 检查权限
- const sourceType = ['album', 'camera'];
- if (sourceType.includes('album')) {
- const hasPermission = await checkAlbumPermission();
- if (!hasPermission) {
- console.log('用户未授予相册权限');
- if (onFail) {
- onFail({ errMsg: '未授权相册权限' });
- }
- return;
- }
- }
-
- // 使用 wx.chooseImage 以确保与 TIM SDK 兼容
- // #ifdef MP-WEIXIN
- wx.chooseImage({
- count: 1,
- sizeType: ['original', 'compressed'],
- sourceType: sourceType,
- success: function (res) {
- console.log('wx.chooseImage 成功,完整返回数据:', JSON.stringify(res));
- console.log('tempFilePaths:', res.tempFilePaths);
- console.log('tempFiles:', res.tempFiles);
-
- // TIM SDK 需要完整的 wx.chooseImage 返回对象,而不是单个文件
- // 直接传递整个 res 对象
- if (onSuccess) onSuccess(res);
- },
- fail: function (err) {
- // 用户取消选择
- if (err.errMsg && err.errMsg.includes('cancel')) {
- console.log('用户取消选择');
- return;
- }
-
- // 权限相关错误
- if (err.errMsg && (err.errMsg.includes('permission') || err.errMsg.includes('auth') || err.errMsg.includes('拒绝'))) {
- console.error('相册权限被拒绝:', err);
- uni.showModal({
- title: '需要相册权限',
- content: '请在设置中开启相册权限后重试',
- confirmText: '去设置',
- success: (modalRes) => {
- if (modalRes.confirm) {
- uni.openSetting();
- }
- }
- });
- if (onFail) {
- onFail(err);
- }
- return;
- }
-
- // 其他错误
- console.error('选择图片失败:', err);
- if (onFail) {
- onFail(err);
- } else {
- showMessage('选择图片失败,请重试');
- }
- }
- });
- // #endif
-
- // #ifndef MP-WEIXIN
- // 非微信小程序环境,使用 uni.chooseMedia
+export const chooseImage = (onSuccess, onFail) => {
chooseMedia({
count: 1,
mediaType: ['image'],
sizeType: ['original', 'compressed'],
- sourceType: sourceType
+ sourceType: ['album', 'camera']
}, onSuccess, onFail);
- // #endif
};
/**
- * 拍照(针对 TIM SDK 优化)
+ * 拍照
* @param {function} onSuccess - 成功回调
* @param {function} onFail - 失败回调
*/
export const takePhoto = (onSuccess, onFail) => {
- // 使用 wx.chooseImage 以确保与 TIM SDK 兼容
- // #ifdef MP-WEIXIN
- wx.chooseImage({
- count: 1,
- sizeType: ['original', 'compressed'],
- sourceType: ['camera'],
- success: function (res) {
- console.log('wx.chooseImage (拍照) 成功,完整返回数据:', JSON.stringify(res));
- console.log('tempFilePaths:', res.tempFilePaths);
- console.log('tempFiles:', res.tempFiles);
-
- // TIM SDK 需要完整的 wx.chooseImage 返回对象
- if (onSuccess) onSuccess(res);
- },
- fail: function (err) {
- // 用户取消
- if (err.errMsg && err.errMsg.includes('cancel')) {
- console.log('用户取消拍照');
- return;
- }
-
- console.error('拍照失败:', err);
- if (onFail) {
- onFail(err);
- } else {
- showMessage('拍照失败,请重试');
- }
- }
- });
- // #endif
-
- // #ifndef MP-WEIXIN
- // 非微信小程序环境
chooseMedia({
count: 1,
mediaType: ['image'],
sizeType: ['original', 'compressed'],
sourceType: ['camera']
}, onSuccess, onFail);
- // #endif
};
// ==================== 录音相关工具方法 ====================
@@ -588,8 +486,6 @@ export const sendCustomMessage = async (messageData, timChatManager, validateBef
* @param {function} onSuccess - 成功回调
*/
export const sendMessage = async (messageType, data, timChatManager, validateBeforeSend, onSuccess, cloudCustomData) => {
- console.log('chat-utils sendMessage 被调用:', { messageType, data });
-
if (!validateBeforeSend()) {
return;
}
@@ -601,9 +497,7 @@ export const sendMessage = async (messageType, data, timChatManager, validateBef
result = await timChatManager.sendTextMessage(data, cloudCustomData);
break;
case 'image':
- console.log('准备发送图片消息,数据:', data);
result = await timChatManager.sendImageMessage(data, cloudCustomData);
- console.log('图片消息发送结果:', result);
break;
case 'voice':
result = await timChatManager.sendVoiceMessage(data.file, data.duration,cloudCustomData);
@@ -614,7 +508,6 @@ export const sendMessage = async (messageType, data, timChatManager, validateBef
}
if (result && result.success) {
- console.log('消息发送成功');
if (onSuccess) onSuccess();
} else {
console.error('发送消息失败:', result?.error);
diff --git a/utils/http.js b/utils/http.js
index e29328e..6a77cd2 100644
--- a/utils/http.js
+++ b/utils/http.js
@@ -21,21 +21,21 @@ let retryQueue = [];
* @param {String} token - 新的 token
*/
function processQueue(token) {
- retryQueue.forEach(({ resolve, reject, config }) => {
- if (token) {
- // 更新 token 并重试请求
- if (!config.header) config.header = {};
- config.header.Authorization = `Bearer ${token}`;
- uni.request({
- ...config,
- success: (res) => resolve(res),
- fail: (err) => reject(err),
- });
- } else {
- reject(new Error("Token 刷新失败"));
- }
- });
- retryQueue = [];
+ retryQueue.forEach(({ resolve, reject, config }) => {
+ if (token) {
+ // 更新 token 并重试请求
+ if (!config.header) config.header = {};
+ config.header.Authorization = `Bearer ${token}`;
+ uni.request({
+ ...config,
+ success: (res) => resolve(res),
+ fail: (err) => reject(err),
+ });
+ } else {
+ reject(new Error("Token 刷新失败"));
+ }
+ });
+ retryQueue = [];
}
/**
@@ -43,114 +43,114 @@ function processQueue(token) {
* @returns {Promise}
*/
async function refreshAccessToken() {
- const refreshToken = uni.getStorageSync("refreshToken");
- if (!refreshToken) {
- return null;
- }
+ const refreshToken = uni.getStorageSync("refreshToken");
+ if (!refreshToken) {
+ return null;
+ }
- if (isRefreshing) {
- // 等待刷新完成
- await new Promise((resolve) => setTimeout(resolve, 100));
- return uni.getStorageSync("accessToken");
- }
+ if (isRefreshing) {
+ // 等待刷新完成
+ await new Promise((resolve) => setTimeout(resolve, 100));
+ return uni.getStorageSync("accessToken");
+ }
- isRefreshing = true;
+ isRefreshing = true;
- try {
- const res = await uni.request({
- url: `${baseUrl}/auth/refresh`,
- method: "POST",
- header: {
- "Content-Type": "application/json",
- },
- data: {
- refreshToken,
- },
- });
+ try {
+ const res = await uni.request({
+ url: `${baseUrl}/auth/refresh`,
+ method: "POST",
+ header: {
+ "Content-Type": "application/json",
+ },
+ data: {
+ refreshToken,
+ },
+ });
- if (res && res.data && res.data.success && res.data.data) {
- const newToken = res.data.data.accessToken;
- uni.setStorageSync("accessToken", newToken);
- processQueue(newToken);
- return newToken;
- } else {
- // 刷新失败,清空队列
- processQueue(null);
- // 清除登录状态
- uni.removeStorageSync("accessToken");
- uni.removeStorageSync("refreshToken");
- uni.removeStorageSync("jwtUserInfo");
- return null;
- }
- } catch (error) {
- console.error("Token 刷新异常:", error);
- processQueue(null);
- uni.removeStorageSync("accessToken");
- uni.removeStorageSync("refreshToken");
- uni.removeStorageSync("jwtUserInfo");
- return null;
- } finally {
- isRefreshing = false;
- }
+ if (res && res.data && res.data.success && res.data.data) {
+ const newToken = res.data.data.accessToken;
+ uni.setStorageSync("accessToken", newToken);
+ processQueue(newToken);
+ return newToken;
+ } else {
+ // 刷新失败,清空队列
+ processQueue(null);
+ // 清除登录状态
+ uni.removeStorageSync("accessToken");
+ uni.removeStorageSync("refreshToken");
+ uni.removeStorageSync("jwtUserInfo");
+ return null;
+ }
+ } catch (error) {
+ console.error("Token 刷新异常:", error);
+ processQueue(null);
+ uni.removeStorageSync("accessToken");
+ uni.removeStorageSync("refreshToken");
+ uni.removeStorageSync("jwtUserInfo");
+ return null;
+ } finally {
+ isRefreshing = false;
+ }
}
const request = async (options = {}, showLoading = true) => {
// 合并用户传入的配置和默认配置
if (!options.data) options.data = {};
- if(!options.data.corpId) {
+ if (!options.data.corpId) {
options.data.corpId = env.MP_CORP_ID;
}
-
-
+
+
const config = {
...defaultOptions,
...options,
url: baseUrl + options.url,
};
-
+
// 添加 token 到请求头
// const accessToken = uni.getStorageSync("accessToken");
// if (accessToken) {
// if (!config.header) config.header = {};
// config.header.Authorization = `Bearer ${accessToken}`;
// }
-
+
const key = `${JSON.stringify(config)}_${Date.now()}`;
if (showLoading) {
recordTask(key)
}
-
+
try {
// 发起请求
const res = await uni.request(config);
if (showLoading) {
removeTask(key)
}
-
+
// 如果返回 401,尝试刷新 token
// if (res.statusCode === 401) {
// if (showLoading) {
// removeTask(key)
// }
-
+
// // 尝试刷新 token
// const newToken = await refreshAccessToken();
-
+
// if (newToken) {
// // 刷新成功,重试原请求
// if (!config.header) config.header = {};
// config.header.Authorization = `Bearer ${newToken}`;
-
+
// if (showLoading) {
// recordTask(key)
// }
-
+
// const retryRes = await uni.request(config);
-
+
// if (showLoading) {
// removeTask(key)
// }
-
+
// const success = retryRes && retryRes.data && retryRes.data.success === true;
// if (success) {
// return retryRes.data;
@@ -166,7 +166,7 @@ const request = async (options = {}, showLoading = true) => {
// };
// }
// }
-
+
const success = res && res.data && res.data.success === true;
if (success) {
return res.data;
@@ -206,5 +206,28 @@ export default request;
export const uploadUrl = `${baseUrl}/upload`;
export function getFullPath(path) {
- return `${baseUrl}${path}`;
+ return `${baseUrl}/${path}`;
}
+
+export function upload(path) {
+ return new Promise((resolve) => {
+ uni.uploadFile({
+ url: uploadUrl, // 替换为你的上传接口地址
+ filePath: path,
+ name: 'file',
+ fileType: 'image',
+ success: (res) => {
+ try {
+ const url = JSON.parse(res.data).filePath;
+ resolve(url ? getFullPath(url) : '')
+ } catch (e) {
+ resolve()
+ }
+ },
+ fail: res => {
+ resolve()
+ }
+ })
+ })
+}
+
diff --git a/utils/tim-chat.js b/utils/tim-chat.js
index 87840bc..4ed5d28 100644
--- a/utils/tim-chat.js
+++ b/utils/tim-chat.js
@@ -363,7 +363,6 @@ class TimChatManager {
}
}
- // 获取用户信息并登录
async getUserInfoAndLogin(userID) {
try {
if (userID) {
@@ -376,7 +375,6 @@ class TimChatManager {
}
this.currentUserID = userInfo.userID
}
-
this.currentUserSig = await this.getUserSig(this.currentUserID)
await this.loginTIM()
} catch (error) {
@@ -711,26 +709,37 @@ class TimChatManager {
// 获取消息所属的会话ID
const messageConversationID = convertedMessage.conversationID
+ // 检查是否为系统消息
+ const isSystemMsg = this.isSystemMessage(convertedMessage)
+
console.log('收到新消息:', {
messageID: convertedMessage.ID,
messageConversationID: messageConversationID,
currentConversationID: this.currentConversationID,
messageType: convertedMessage.type,
- from: convertedMessage.from
+ from: convertedMessage.from,
+ isSystemMessage: isSystemMsg
})
- // 判断是否为当前会话的消息(必须有currentConversationID且匹配才显示)
- const isCurrentConversation = this.currentConversationID &&
- messageConversationID === this.currentConversationID
+
+ // 判断是否为当前会话的消息
+ // 系统消息:只要会话ID匹配就显示(不要求必须有currentConversationID)
+ // 普通消息:必须有currentConversationID且匹配才显示
+ const isCurrentConversation = isSystemMsg
+ ? messageConversationID === this.currentConversationID
+ : (this.currentConversationID && messageConversationID === this.currentConversationID)
+
console.log('消息会话匹配检查:', {
isCurrentConversation,
+ isSystemMessage: isSystemMsg,
hasCurrentConversationID: !!this.currentConversationID,
conversationIDMatch: messageConversationID === this.currentConversationID
})
+
if (isCurrentConversation) {
// 当前会话的消息,触发回调
console.log('✓ 消息属于当前会话,触发显示')
this.triggerCallback('onMessageReceived', convertedMessage)
- // 处理已读状态
+ // 处理已读状态(系统消息也标记为已读)
if (this.currentConversationID) {
this.markConversationAsRead(this.currentConversationID)
}
@@ -1049,7 +1058,7 @@ class TimChatManager {
this.tim.getConversationList()
.then(async (conversationResponse) => {
console.log('获取会话列表成功')
-
+
const groupConversations = conversationResponse.data.conversationList.filter(conversation => {
return conversation.conversationID && conversation.conversationID.startsWith('GROUP')
})
@@ -1093,7 +1102,52 @@ class TimChatManager {
} else if (lastMessage.type === 'TIMSoundElem') {
lastMessageText = '[语音]'
} else if (lastMessage.type === 'TIMCustomElem') {
- lastMessageText = lastMessage.payload.data || '[自定义消息]'
+ // 解析自定义消息
+ try {
+ const customData = JSON.parse(lastMessage.payload.data)
+ const messageType = customData.messageType
+ // 根据消息类型返回不同的预览文本
+ switch (messageType) {
+ case 'system_message':
+ lastMessageText = '[系统消息]'
+ break
+ case 'symptom':
+ lastMessageText = '[病情描述]'
+ break
+ case 'prescription':
+ lastMessageText = '[处方单]'
+ break
+ case 'refill':
+ lastMessageText = '[续方申请]'
+ break
+ case 'survey':
+ lastMessageText = '[问卷调查]'
+ break
+ case 'article':
+ lastMessageText = '[文章]'
+ break
+ case "consult_pending":
+ lastMessageText = '患者向团队发起咨询,请在1小时内接诊,超时将自动关闭会话'
+ break
+ case "consult_rejected":
+ lastMessageText = '患者向团队发起咨询,由于有紧急事务要处理暂时无法接受咨询.本次会话丿关闭'
+ break
+ case "consult_timeout":
+ lastMessageText = '患者向团队发起咨询,团队成员均未接受咨询,本次会话已自动关闭'
+ break
+ case "consult_accepted":
+ lastMessageText = '已接诊,会话已开始'
+ break
+ case "consult_ended":
+ lastMessageText = '已结束当前会话'
+ break
+ default:
+ lastMessageText = '[自定义消息]'
+ }
+ } catch (error) {
+ console.error('解析自定义消息失败:', error)
+ lastMessageText = '[自定义消息]'
+ }
} else {
lastMessageText = '[未知消息类型]'
}
@@ -2105,86 +2159,47 @@ class TimChatManager {
} catch (error) {
console.error('文本消息发送失败:', error)
localMessage.status = 'failed'
-
+
// 如果是因为未登录导致的失败,尝试重连
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
console.log('检测到未登录错误,尝试重连...');
this.isLoggedIn = false;
this.ensureIMConnection();
}
-
+
return { success: false, error }
}
}
// 发送图片消息
async sendImageMessage(imageFile) {
- console.log('sendImageMessage 被调用,参数:', imageFile);
-
if (!this.tim) {
this.triggerCallback('onError', 'IM未初始化')
- return { success: false, error: 'IM未初始化' }
+ return
}
- // 检查登录状态
- if (!this.isLoggedIn) {
- console.error('IM未登录,无法发送消息');
- this.triggerCallback('onError', 'IM未登录,请稍后重试')
- return { success: false, error: 'IM未登录' }
+ if (!this.conversation) {
+ this.triggerCallback('onError', '群聊会话不存在')
+ return { success: false, error: '群聊会话不存在' }
}
- // 优先使用 currentConversationID,如果没有则尝试从 conversation 获取
- let conversationID = this.currentConversationID;
- if (!conversationID && this.conversation) {
- conversationID = this.conversation.conversationID;
- }
-
- if (!conversationID) {
- console.error('会话ID不存在');
- this.triggerCallback('onError', '会话不存在,请重新进入聊天')
- return { success: false, error: '会话ID不存在' }
- }
-
- // 从 conversationID 提取 groupID
- let groupID = null;
- if (conversationID.startsWith('GROUP')) {
- groupID = conversationID.replace('GROUP', '');
- } else if (this.conversation?.groupProfile?.groupID) {
- groupID = this.conversation.groupProfile.groupID;
+ let groupID = null
+ if (this.conversation.groupProfile && this.conversation.groupProfile.groupID) {
+ groupID = this.conversation.groupProfile.groupID
+ } else if (this.conversation.conversationID) {
+ groupID = this.conversation.conversationID.replace('GROUP', '')
}
if (!groupID) {
- console.error('无法获取群聊ID,conversationID:', conversationID);
this.triggerCallback('onError', '无法获取群聊ID')
return { success: false, error: '无法获取群聊ID' }
}
- console.log('发送图片消息,conversationID:', conversationID, 'groupID:', groupID);
+ // 确保使用当前会话的conversationID
+ const conversationID = this.conversation.conversationID || this.currentConversationID
- // imageFile 现在是完整的 wx.chooseImage 返回对象
- console.log('接收到的图片选择结果:', imageFile);
- console.log('类型:', typeof imageFile);
- console.log('keys:', imageFile ? Object.keys(imageFile) : 'null');
-
- // 验证对象
- if (!imageFile) {
- console.error('图片选择结果为空');
- this.triggerCallback('onError', '图片文件无效');
- return { success: false, error: '图片选择结果为空' };
- }
-
- // 获取文件路径用于显示预览
- let previewPath = '';
- if (imageFile.tempFilePaths && imageFile.tempFilePaths.length > 0) {
- previewPath = imageFile.tempFilePaths[0];
- } else if (imageFile.tempFiles && imageFile.tempFiles.length > 0) {
- previewPath = imageFile.tempFiles[0].tempFilePath || imageFile.tempFiles[0].path;
- }
-
- console.log('预览路径:', previewPath);
-
- // 获取图片尺寸信息(用于本地预览)
- const imageInfo = await this.getImageInfo(previewPath);
+ // 获取图片尺寸信息
+ const imageInfo = await this.getImageInfo(imageFile);
const localMessage = {
ID: `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -2192,14 +2207,14 @@ class TimChatManager {
type: 'TIMImageElem',
payload: {
imageInfoArray: [{
- url: previewPath,
+ url: this.getImageUrl(imageFile),
width: imageInfo.width,
height: imageInfo.height
}]
},
lastTime: Date.now(),
status: 'sending',
- avatar: '/static/center/user-avatar.png',
+ avatar: '',
conversationID: conversationID,
from: this.currentUserID
}
@@ -2211,45 +2226,22 @@ class TimChatManager {
// 触发消息接收回调,让UI立即显示
this.triggerCallback('onMessageReceived', localMessage)
- console.log('准备创建 TIM 图片消息,groupID:', groupID, 'imageFile:', imageFile);
-
+ const message = this.tim.createImageMessage({
+ to: groupID,
+ conversationType: TIM.TYPES.CONV_GROUP,
+ payload: { file: imageFile }
+ })
+
try {
- // 创建图片消息 - 直接传递 wx.chooseImage 的完整返回对象
- const message = this.tim.createImageMessage({
- to: groupID,
- conversationType: TIM.TYPES.CONV_GROUP,
- payload: {
- file: imageFile // 传递完整的 wx.chooseImage 返回对象
- }
- })
-
- console.log('TIM 图片消息已创建:', message);
-
- console.log('开始发送图片消息...');
- const sendResult = await this.tim.sendMessage(message);
- console.log('图片消息发送成功:', sendResult);
+ await this.tim.sendMessage(message)
localMessage.status = 'success'
return { success: true, message: localMessage }
} catch (error) {
console.error('图片消息发送失败:', error)
- console.error('错误详情:', {
- message: error.message,
- stack: error.stack,
- imageFile: imageFile
- });
localMessage.status = 'failed'
-
- // 如果是因为未登录导致的失败,尝试重连
- if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
- console.log('检测到未登录错误,尝试重连...');
- this.isLoggedIn = false;
- this.ensureIMConnection();
- }
-
return { success: false, error }
}
}
-
// 发送语音消息
async sendVoiceMessage(voiceFile, duration) {
if (!this.tim) {
@@ -2322,14 +2314,14 @@ class TimChatManager {
} catch (error) {
console.error('语音消息发送失败:', error)
localMessage.status = 'failed'
-
+
// 如果是因为未登录导致的失败,尝试重连
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
console.log('检测到未登录错误,尝试重连...');
this.isLoggedIn = false;
this.ensureIMConnection();
}
-
+
return { success: false, error }
}
}
@@ -2411,19 +2403,54 @@ class TimChatManager {
} catch (error) {
console.error('自定义消息发送失败:', error)
localMessage.status = 'failed'
-
+
// 如果是因为未登录导致的失败,尝试重连
if (error.message && (error.message.includes('not login') || error.message.includes('sdk not ready'))) {
console.log('检测到未登录错误,尝试重连...');
this.isLoggedIn = false;
this.ensureIMConnection();
}
-
+
return { success: false, error }
}
}
// 工具方法
+ // 判断是否为系统消息
+ isSystemMessage(message) {
+ if (message.type !== 'TIMCustomElem') {
+ return false
+ }
+
+ // 检查 payload.data 是否包含系统消息标记
+ try {
+ if (message.payload && message.payload.data) {
+ const data = typeof message.payload.data === 'string'
+ ? JSON.parse(message.payload.data)
+ : message.payload.data
+
+ // 检查是否为系统消息类型
+ if (data.type === 'system_message') {
+ return true
+ }
+ }
+
+ // 检查 description 是否为系统消息标记
+ if (message.payload && message.payload.description === '系统消息标记') {
+ return true
+ }
+
+ // 兼容旧的系统消息格式
+ if (message.payload && message.payload.description === 'SYSTEM_NOTIFICATION') {
+ return true
+ }
+ } catch (error) {
+ console.error('判断系统消息失败:', error)
+ }
+
+ return false
+ }
+
filterMessage(message) {
if (message.type === 'TIMCustomElem' && message.payload && message.payload.data) {
if (message.payload.data === 'group_create' || message.payload.data === 'purchased') {
@@ -2497,40 +2524,42 @@ class TimChatManager {
}
getImageUrl(imageFile) {
- // 支持 tempFilePath 或 path
- if (imageFile?.tempFilePath) {
- return imageFile.tempFilePath;
+ // 处理 tempFiles 数组格式
+ if (imageFile?.tempFiles?.length > 0) {
+ return imageFile.tempFiles[0].tempFilePath
}
- if (imageFile?.path) {
- return imageFile.path;
+ // 处理单个文件对象
+ if (imageFile?.tempFilePath) {
+ return imageFile.tempFilePath
}
// 处理字符串路径
if (typeof imageFile === 'string') {
- return imageFile;
+ return imageFile
}
console.warn('无法获取图片URL,使用默认图片:', imageFile);
- return '/static/home/photo.png';
+ return '/static/home/photo.png'
}
// 获取图片尺寸信息
- getImageInfo(imagePath) {
+ getImageInfo(imageFile) {
return new Promise((resolve) => {
- // 如果传入的是对象,尝试提取路径
- if (typeof imagePath === 'object') {
- if (imagePath?.tempFilePath) {
- imagePath = imagePath.tempFilePath;
- } else if (imagePath?.path) {
- imagePath = imagePath.path;
- } else {
- console.warn('无法从对象中获取图片路径,使用默认尺寸:', imagePath);
- resolve({ width: 400, height: 300 });
- return;
- }
- }
+ let imagePath = '';
- // 如果不是字符串,使用默认尺寸
- if (typeof imagePath !== 'string' || !imagePath) {
- console.warn('图片路径无效,使用默认尺寸:', imagePath);
+ // 获取图片路径 - 处理多种格式
+ if (imageFile?.tempFilePaths?.length > 0) {
+ // wx.chooseImage 返回的对象
+ imagePath = imageFile.tempFilePaths[0];
+ } else if (imageFile?.tempFiles?.length > 0) {
+ // 从 tempFiles 中提取路径
+ const tempFile = imageFile.tempFiles[0];
+ imagePath = tempFile.path || tempFile.tempFilePath;
+ } else if (imageFile?.tempFilePath) {
+ imagePath = imageFile.tempFilePath;
+ } else if (typeof imageFile === 'string') {
+ imagePath = imageFile;
+ } else {
+ console.warn('无法获取图片路径,使用默认尺寸:', imageFile);
+ // 默认尺寸
resolve({ width: 400, height: 300 });
return;
}