diff --git a/.env.development b/.env.development
index c00ccb1..40eeb46 100644
--- a/.env.development
+++ b/.env.development
@@ -1,7 +1,7 @@
MP_API_BASE_URL=http://localhost:8080
MP_IMAGE_URL=https://patient.youcan365.com
MP_CACHE_PREFIX=development
-MP_WX_APP_ID=wx93af55767423938e
+MP_WX_APP_ID=wx1d8337a40c11d66c
MP_CORP_ID=wwe3fb2faa52cf9dfb
MP_TIM_SDK_APP_ID=1600123876
MP_INVITE_TEAMMATE_QRCODE=https://patient.youcan365.com/invite-teammate
diff --git a/.env.localhost b/.env.localhost
index c00ccb1..40eeb46 100644
--- a/.env.localhost
+++ b/.env.localhost
@@ -1,7 +1,7 @@
MP_API_BASE_URL=http://localhost:8080
MP_IMAGE_URL=https://patient.youcan365.com
MP_CACHE_PREFIX=development
-MP_WX_APP_ID=wx93af55767423938e
+MP_WX_APP_ID=wx1d8337a40c11d66c
MP_CORP_ID=wwe3fb2faa52cf9dfb
MP_TIM_SDK_APP_ID=1600123876
MP_INVITE_TEAMMATE_QRCODE=https://patient.youcan365.com/invite-teammate
diff --git a/baseData/index.js b/baseData/index.js
new file mode 100644
index 0000000..0b54e87
--- /dev/null
+++ b/baseData/index.js
@@ -0,0 +1,39 @@
+export const ToDoEventType = {
+ followUpNoShow: "未到院回访",
+ followUpNoDeal: "未成交回访",
+ followUp: "诊后回访",
+ followUpPostSurgery: "术后回访",
+ followUpPostTreatment: "治疗后回访",
+ appointmentReminder: "就诊提醒",
+ followUpReminder: "复诊提醒",
+ medicationReminder: "用药提醒",
+ serviceSummary: "咨询服务",
+ eventNotification: "活动通知",
+ ContentReminder: "宣教发送",
+ questionnaire: "问卷调查",
+ followUpComplaint: "投诉回访",
+ followUpActivity: "活动回访",
+ other: "其他",
+ Feedback: "意见反馈",
+ // 预约相关服务类型
+ treatmentAppointment: "治疗预约",
+ followupAppointment: "复诊预约",
+ confirmArrival: "确认到院",
+ prenatalFollowUp: "孕期回访",
+};
+
+export const statusNames = {
+ notStart: "未开始",
+ treated: "已完成",
+ processing: "待处理",
+ cancelled: "已取消",
+ expired: "已过期",
+};
+
+export const statusClassNames = {
+ notStart: "text-primary",
+ treated: "text-success",
+ processing: "text-danger",
+ cancelled: "text-gray",
+ expired: "text-gray",
+}
\ No newline at end of file
diff --git a/components/full-page.vue b/components/full-page.vue
index 45eefc8..4453441 100644
--- a/components/full-page.vue
+++ b/components/full-page.vue
@@ -7,8 +7,14 @@
-
+
@@ -16,22 +22,22 @@
-
+
diff --git a/hooks/usePageList.js b/hooks/usePageList.js
new file mode 100644
index 0000000..5792d48
--- /dev/null
+++ b/hooks/usePageList.js
@@ -0,0 +1,33 @@
+import { computed, ref, watch } from "vue";
+import useDebounce from '@/utils/useDebounce'
+
+export default function usePageList(callback, options = {}) {
+ const keyword = ref('')
+ const list = ref([])
+ const page = ref(1)
+ const pageSize = ref(options.pageSize || 20)
+ const pages = ref(0);
+ const loading = ref(false)
+ const total = ref(0)
+
+ const hasMore = computed(() => page.value < pages.value)
+
+ const handleKeywordChange = useDebounce(() => {
+ getList()
+ }, options.debounce || 1000)
+
+ function changePage(p) {
+ if (loading.value) return
+ page.value = p
+ getList()
+ }
+
+ function getList() {
+ typeof callback === 'function' && callback()
+ }
+
+ watch(keyword, handleKeywordChange);
+
+ return { total, page, pageSize, keyword, list, pages, changePage, loading, hasMore }
+
+}
diff --git a/manifest.json b/manifest.json
index dbbc303..36d7ad9 100644
--- a/manifest.json
+++ b/manifest.json
@@ -50,7 +50,7 @@
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
- "appid" : "wx93af55767423938e",
+ "appid" : "wx1d8337a40c11d66c",
"setting" : {
"urlCheck" : false
},
diff --git a/pages.json b/pages.json
index a665207..c2a03ed 100644
--- a/pages.json
+++ b/pages.json
@@ -115,6 +115,12 @@
"navigationBarTitleText": "病历详情"
}
},
+ {
+ "path": "pages/case/medical-case-form",
+ "style": {
+ "navigationBarTitleText": "添加病历"
+ }
+ },
{
"path": "pages/case/service-record-detail",
"style": {
diff --git a/pages/case/medical-case-form.vue b/pages/case/medical-case-form.vue
new file mode 100644
index 0000000..532e89b
--- /dev/null
+++ b/pages/case/medical-case-form.vue
@@ -0,0 +1,473 @@
+
+
+
+
+
+ {{ field.label }}
+
+
+
+
+ {{ formData[field.key] || "暂无" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/login/login.vue b/pages/login/login.vue
index d339408..6cd4ed2 100644
--- a/pages/login/login.vue
+++ b/pages/login/login.vue
@@ -5,12 +5,12 @@
生命全周期健康管理伙伴
-
-
-
+
@@ -25,7 +26,6 @@
{{ btn.text }}
-
@@ -75,6 +75,7 @@ const props = defineProps({
formatTime: { type: Function, required: true },
groupId: { type: String, default: '' },
userId: { type: String, default: '' },
+ patientId: { type: String, default: '' },
corpId: { type: String, default: '' },
});
@@ -95,6 +96,11 @@ const cloudCustomData = computed(() => {
return arr.filter(Boolean).join("|");
});
+// 流式输入文本
+const appendStreamText = (char) => {
+ inputText.value += char;
+};
+
// 录音相关扩展状态(特效 + 取消逻辑)
const recordingDuration = ref(0);
let recordingTimer = null;
@@ -165,9 +171,22 @@ const sendTextMessageFromPhrase = async (content) => {
});
};
+// 设置输入框文本(覆盖原内容)
+const setInputText = (text) => {
+ inputText.value = text;
+};
+
+// 清空输入框
+const clearInputText = () => {
+ inputText.value = '';
+};
+
// 暴露方法给父组件调用
defineExpose({
- sendTextMessageFromPhrase
+ sendTextMessageFromPhrase,
+ appendStreamText,
+ setInputText,
+ clearInputText
});
// 发送图片消息
@@ -367,7 +386,7 @@ const goToCommonPhrases = () => {
// 跳转到宣教文章页面
const goToArticleList = () => {
uni.navigateTo({
- url: `/pages/message/article-list?groupId=${props.groupId}&userId=${props.userId}&corpId=${props.corpId}`
+ url: `/pages/message/article-list?groupId=${props.groupId}&patientId=${props.patientId}&corpId=${props.corpId}`
});
};
@@ -432,6 +451,13 @@ function handleInputFocus() {
});
}
+function handleInput(e) {
+ // textarea 输入时触发,可以在这里处理额外逻辑
+ nextTick().then(() => {
+ emit("scrollToBottom");
+ });
+}
+
onMounted(() => {
// 初始化录音管理器
initRecorderManager();
diff --git a/pages/message/components/medical-case-progress.vue b/pages/message/components/medical-case-progress.vue
new file mode 100644
index 0000000..c2803ab
--- /dev/null
+++ b/pages/message/components/medical-case-progress.vue
@@ -0,0 +1,378 @@
+
+
+
+
+ ✕
+
+
+
+ {{ progressTitle }}
+
+
+
+
+
+ {{ progress }}%
+
+
+
+ 检测到以下{{ caseTypeName }}信息:
+
+
+ ✓
+
+ {{ item.label }}:{{ item.value }}
+
+
+
+
+
+
+ 正在生成结构化{{ caseTypeName }}
+
+
+
+
+
+ 重新生成
+
+
+ 下一步
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/message/components/medical-case-type-selector.vue b/pages/message/components/medical-case-type-selector.vue
new file mode 100644
index 0000000..6c5cab6
--- /dev/null
+++ b/pages/message/components/medical-case-type-selector.vue
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+ {{ type.name }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/message/components/message-header.vue b/pages/message/components/message-header.vue
new file mode 100644
index 0000000..1cf3a52
--- /dev/null
+++ b/pages/message/components/message-header.vue
@@ -0,0 +1,317 @@
+
+
+
+
+
+
+
diff --git a/pages/message/hooks/use-group-chat.js b/pages/message/hooks/use-group-chat.js
index e550907..73d7a8e 100644
--- a/pages/message/hooks/use-group-chat.js
+++ b/pages/message/hooks/use-group-chat.js
@@ -1,5 +1,7 @@
import { ref, computed } from 'vue'
import { onShow, onUnload } from '@dcloudio/uni-app'
+import api from '@/utils/api.js'
+import useTeamStore from '@/store/team.js'
/**
* 简单的群聊hook
@@ -8,6 +10,9 @@ import { onShow, onUnload } from '@dcloudio/uni-app'
export default function useGroupChat(groupID) {
const groupInfo = ref({})
const members = ref([])
+ const teamMemberIds = ref([]) // 存储团队成员的userId列表
+ const patientId = ref('') // 存储患者ID
+ const teamStore = useTeamStore()
// 群聊成员映射
const chatMember = computed(() => {
@@ -15,30 +20,79 @@ export default function useGroupChat(groupID) {
members.value.forEach(member => {
res[member.id] = {
name: member.name,
- avatar: member.avatar || '/static/default-avatar.png'
+ avatar: member.avatar,
+ isTeamMember: member.isTeamMember // 标记是否为团队成员
}
})
return res
})
- // 获取群聊信息
+ // 判断某个userId是否为团队成员
+ const isTeamMember = (userId) => {
+ return teamMemberIds.value.includes(userId)
+ }
+
+ // 获取用户头像(根据是否为团队成员返回不同的默认头像)
+ const getUserAvatar = (userId) => {
+ const member = chatMember.value[userId]
+ if (!member) {
+ // 如果找不到成员信息,根据是否为团队成员返回默认头像
+ return isTeamMember(userId) ? '/static/home/avatar.svg' : '/static/default-patient-avatar.png'
+ }
+
+ // 如果有头像且不为空字符串,返回头像
+ if (member.avatar && member.avatar.trim() !== '') {
+ return member.avatar
+ }
+
+ // 否则根据是否为团队成员返回默认头像
+ return member.isTeamMember ? '/static/home/avatar.svg' : '/static/default-patient-avatar.png'
+ }
+
+ // 获取群聊信息和成员头像
async function getGroupInfo() {
const gid = typeof groupID === 'string' ? groupID : groupID.value
if (!gid) return
try {
- // 这里可以调用API获取群聊信息
- // const res = await getGroupDetail(gid)
- // if (res && res.success) {
- // groupInfo.value = res.data
- // members.value = res.data.members || []
- // }
+ // 1. 获取群聊基本信息
+ const groupResult = await api('getGroupListByGroupId', { groupId: gid })
- // 暂时使用本地数据
- groupInfo.value = {
- groupID: gid,
- name: '群聊',
- status: 'active'
+ if (groupResult && groupResult.success && groupResult.data) {
+ groupInfo.value = {
+ groupID: gid,
+ name: groupResult.data.team?.name || '群聊',
+ status: groupResult.data.orderStatus || 'active',
+ teamId: groupResult.data.teamId
+ }
+
+ // 2. 如果有teamId,获取团队成员头像
+ if (groupResult.data.teamId) {
+ const avatarMap = await teamStore.getTeamMemberAvatars(groupResult.data.teamId)
+
+ // 3. 存储团队成员ID列表
+ teamMemberIds.value = Object.keys(avatarMap)
+
+ // 4. 构建团队成员列表
+ members.value = teamMemberIds.value.map(userId => ({
+ id: userId,
+ name: userId, // 这里可以从其他地方获取真实姓名
+ avatar: avatarMap[userId] || '',
+ isTeamMember: true
+ }))
+
+ // 5. 添加患者信息(使用默认患者头像)
+ if (groupResult.data.patient) {
+ const pid = groupResult.data.patientId?.toString() || ''
+ patientId.value = pid
+ members.value.push({
+ id: pid,
+ name: groupResult.data.patient.name || '患者',
+ avatar: '', // 患者不设置头像,使用默认
+ isTeamMember: false
+ })
+ }
+ }
}
} catch (error) {
console.error('获取群聊信息失败:', error)
@@ -57,6 +111,8 @@ export default function useGroupChat(groupID) {
groupInfo,
members,
chatMember,
- getGroupInfo
+ getGroupInfo,
+ isTeamMember,
+ getUserAvatar
}
}
diff --git a/pages/message/index.vue b/pages/message/index.vue
index 131a069..a2d1c8f 100644
--- a/pages/message/index.vue
+++ b/pages/message/index.vue
@@ -1,5 +1,20 @@
+
+
+
+
+ {{ patientInfo.name }}
+ {{ patientInfo.sex }} · {{ patientInfo.age }}岁
+
+
+ 查看档案
+
+
+
+
-
-
-
@@ -125,6 +133,21 @@
@cancel="handleRejectReasonCancel"
/>
+
+
+
scrollToBottom(true)"
@messageSent="() => scrollToBottom(true)"
@@ -175,6 +199,7 @@ 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";
+import AIAssistantButtons from "./components/ai-assistant-buttons.vue";
const timChatManager = globalTimChatManager;
@@ -190,12 +215,12 @@ const { initIMAfterLogin } = useAccountStore();
const chatInputRef = ref(null);
const groupId = ref("");
-const { chatMember, getGroupInfo } = useGroupChat(groupId);
+const { chatMember, getGroupInfo, getUserAvatar } = useGroupChat(groupId);
// 动态设置导航栏标题
-const updateNavigationTitle = () => {
+const updateNavigationTitle = (title = "群聊") => {
uni.setNavigationBarTitle({
- title: "群聊",
+ title: title,
});
};
@@ -211,6 +236,17 @@ const isEvaluationPopupOpen = ref(false);
// 订单状态
const orderStatus = ref("");
+// 患者信息
+const patientInfo = ref({
+ name: "",
+ sex: "",
+ age: "",
+ mobile: "",
+});
+
+// 患者ID
+const patientId = ref("");
+
// 计算弹框显示状态 - 只有 pending 状态才显示接受问诊组件
const showConsultAccept = computed(() => orderStatus.value === "pending");
@@ -270,8 +306,29 @@ const fetchGroupOrderStatus = async () => {
if (result.success && result.data) {
orderStatus.value = result.data.orderStatus || "";
+
+ // 更新导航栏标题为团队名称
+ const teamName = result.data.team?.name || "群聊";
+ updateNavigationTitle(teamName);
+
+ // 更新患者信息
+ if (result.data.patient) {
+ patientInfo.value = {
+ name: result.data.patient.name || "",
+ sex: result.data.patient.sex || "",
+ age: result.data.patient.age || "",
+ mobile: result.data.patient.mobile || "",
+ };
+ }
+ // 更新患者ID
+ if (result.data.patientId) {
+ patientId.value = result.data.patientId.toString();
+ }
+
console.log("获取群组订单状态:", {
orderStatus: orderStatus.value,
+ teamName: teamName,
+ patientInfo: patientInfo.value,
groupId: groupId.value,
});
} else {
@@ -282,12 +339,6 @@ const fetchGroupOrderStatus = async () => {
}
};
-// 检查是否有待接诊的系统消息
-function checkConsultPendingStatus() {
- // 直接获取最新的订单状态
- fetchGroupOrderStatus();
-}
-
// 获取消息气泡样式类
function getBubbleClass(message) {
// 图片消息不需要气泡背景
@@ -390,18 +441,17 @@ const initTIMCallbacks = async () => {
});
// 立即标记会话为已读,确保未读数为0
- if (timChatManager.tim && timChatManager.isLoggedIn && chatInfo.value.conversationID) {
+ if (
+ timChatManager.tim &&
+ timChatManager.isLoggedIn &&
+ chatInfo.value.conversationID
+ ) {
timChatManager.tim
.setMessageRead({
conversationID: chatInfo.value.conversationID,
})
.then(() => {
console.log("✓ 收到新消息后已标记为已读");
- // 触发会话列表更新,确保未读数为0
- timChatManager.triggerCallback('onConversationListUpdated', {
- conversationID: chatInfo.value.conversationID,
- unreadCount: 0
- });
})
.catch((error) => {
console.error("✗ 标记已读失败:", error);
@@ -461,9 +511,6 @@ const initTIMCallbacks = async () => {
isCompleted.value = data.isCompleted || false;
isLoadingMore.value = false;
- // 检查是否有待接诊的系统消息
- checkConsultPendingStatus();
-
nextTick(() => {
if (data.isRefresh) {
console.log("后台刷新完成,保持当前滚动位置");
@@ -558,11 +605,6 @@ const loadMessageList = async () => {
})
.then(() => {
console.log("✓ 会话已标记为已读:", chatInfo.value.conversationID);
- // 触发会话列表更新回调,通知消息列表页面清空未读数
- timChatManager.triggerCallback('onConversationListUpdated', {
- conversationID: chatInfo.value.conversationID,
- unreadCount: 0
- });
})
.catch((error) => {
console.error("✗ 标记会话已读失败:", error);
@@ -719,13 +761,32 @@ onShow(() => {
// 页面隐藏
onHide(() => {
stopIMMonitoring();
+ // 清空当前会话ID,避免离开页面后收到的消息被错误标记为已读
+ timChatManager.currentConversationID = null;
+ console.log("✓ 页面隐藏,已清空当前会话ID");
});
const sendCommonPhrase = (content) => {
if (chatInputRef.value) {
- chatInputRef.value.sendTextMessageFromPhrase(content);
+ // 覆盖输入框内容,而不是直接发送
+ chatInputRef.value.setInputText(content);
}
};
+
+// 处理流式文本输入
+const handleStreamText = (char) => {
+ if (chatInputRef.value) {
+ chatInputRef.value.appendStreamText(char);
+ }
+};
+
+// 处理清空输入框
+const handleClearInput = () => {
+ if (chatInputRef.value) {
+ chatInputRef.value.clearInputText();
+ }
+};
+
// 暴露方法给常用语页面调用
defineExpose({
sendCommonPhrase,
@@ -824,6 +885,21 @@ const handleRejectReasonConfirm = async (reason) => {
const handleRejectReasonCancel = () => {
showRejectReasonModal.value = false;
};
+
+// 处理查看患者详情
+const handleViewPatientDetail = () => {
+ if (!patientId.value) {
+ uni.showToast({
+ title: "患者信息不完整",
+ icon: "none",
+ });
+ return;
+ }
+
+ uni.navigateTo({
+ url: `/pages/case/archive-detail?id=${patientId.value}`,
+ });
+};
// 处理结束问诊
const handleEndConsult = async () => {
try {
@@ -966,4 +1042,4 @@ uni.$on("sendSurvey", async (data) => {
+
\ No newline at end of file
diff --git a/pages/message/message.vue b/pages/message/message.vue
index 4ebf662..654726d 100644
--- a/pages/message/message.vue
+++ b/pages/message/message.vue
@@ -1,707 +1,696 @@
-
-
-
-
-
- 处理中
-
-
-
- 已结束
-
-
-
-
-
-
-
-
-
-
-
- {{
- conversation.unreadCount > 99 ? "99+" : conversation.unreadCount
- }}
-
-
-
-
-
-
- {{
- conversation.lastMessage || "暂无消息"
- }}
-
-
-
-
-
-
-
- {{
- activeTab === "processing" ? "暂无处理中的会话" : "暂无已结束的会话"
- }}
-
-
-
-
- {{
- loadingMore ? "加载中..." : "上拉加载更多"
- }}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ conversation.unreadCount > 99 ? "99+" : conversation.unreadCount
+ }}
+
+
+
+
+
+
+ {{
+ conversation.lastMessage || "暂无消息"
+ }}
+
+
+
+
+
+
+
+ {{
+ activeTab === "processing" ? "暂无处理中的会话" : "暂无已结束的会话"
+ }}
+
+
+
+
+ {{
+ loadingMore ? "加载中..." : "上拉加载更多"
+ }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/work/components/filter-popup.vue b/pages/work/components/filter-popup.vue
new file mode 100644
index 0000000..c3cdd83
--- /dev/null
+++ b/pages/work/components/filter-popup.vue
@@ -0,0 +1,211 @@
+
+
+
+
+ 全部筛选
+
+
+
+
+ 任务状态
+
+
+ {{ i.label }}
+
+
+ 任务类型
+
+
+ {{ i.label }}
+
+
+ 所属团队
+
+
+ {{ teamName || '全部' }}
+
+
+
+
+
+
+ 计划日期
+
+
+
+
+
+ {{ form.dates[0] }} - {{ form.dates[1] }}
+
+ 请选择计划日期
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+ 重置
+ 确定
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/work/profile.vue b/pages/work/profile.vue
index 06bd33a..3124fa1 100644
--- a/pages/work/profile.vue
+++ b/pages/work/profile.vue
@@ -2,23 +2,59 @@
-
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
{{ jobStr }}
@@ -39,12 +75,24 @@
-
+
-
+
@@ -58,46 +106,56 @@ import api from "@/utils/api.js";
import { upload } from "@/utils/http.js";
import { toast } from "@/utils/widget";
-import buttonFooter from '@/components/button-footer.vue';
+import buttonFooter from "@/components/button-footer.vue";
import commonCell from "@/components/form-template/common-cell.vue";
import FormInput from "@/components/form-template/form-cell/form-input.vue";
import FormSelect from "@/components/form-template/form-cell/form-select.vue";
import FormTextarea from "@/components/form-template/form-cell/form-textarea.vue";
-import fullPage from '@/components/full-page.vue';
+import fullPage from "@/components/full-page.vue";
const { account, doctorInfo } = storeToRefs(useAccountStore());
const { useLoad, useShow } = useGuard();
const { getDoctorInfo } = useAccountStore();
-const job = { assistant: '医生助理', doctor: '医生' };
+const job = { assistant: "医生助理", doctor: "医生" };
const form = ref({});
-const inviteTeamId = ref('')
-const type = ref('');
+const inviteTeamId = ref("");
+const type = ref("");
-const formData = computed(() => ({ ...(doctorInfo.value || {}), ...form.value, mobile: account.value?.mobile }));
-const cancelText = computed(() => doctorInfo.value ? '取消' : '暂不填写');
-const confirmText = computed(() => type.value === 'cert' ? '下一步' : '保存');
+const formData = computed(() => ({
+ ...(doctorInfo.value || {}),
+ ...form.value,
+ mobile: account.value?.mobile,
+}));
+const cancelText = computed(() => (doctorInfo.value ? "取消" : "暂不填写"));
+const confirmText = computed(() => (type.value === "cert" ? "下一步" : "保存"));
const jobStr = computed(() => {
- const jobs = formData.value && Array.isArray(formData.value.job) ? formData.value.job.filter(i => i === 'assistant' || i === 'doctor') : [];
- return jobs[0] && job[jobs[0]] ? job[jobs[0]] : '';
-})
+ const jobs =
+ formData.value && Array.isArray(formData.value.job)
+ ? formData.value.job.filter((i) => i === "assistant" || i === "doctor")
+ : [];
+ return jobs[0] && job[jobs[0]] ? job[jobs[0]] : "";
+});
const rule = computed(() => {
- if (doctorInfo.value && ['verified', 'verifying'].includes(doctorInfo.value.verifyStatus)) {
+ if (
+ doctorInfo.value &&
+ ["verified", "verifying"].includes(doctorInfo.value.verifyStatus)
+ ) {
return {
- anotherName: { name: '姓名 (不可修改)', required: false, disabled: true },
- job: { name: '岗位 (不可修改)', disabled: true },
- title: { name: '职称 (不可修改)', disabled: true },
- dept: { name: '科室 (不可修改)', disabled: true },
- }
+ anotherName: { name: "姓名 (不可修改)", required: false, disabled: true },
+ job: { name: "岗位 (不可修改)", disabled: true },
+ title: { name: "职称 (不可修改)", disabled: true },
+ dept: { name: "科室 (不可修改)", disabled: true },
+ };
}
return {
- anotherName: { name: '姓名', required: true, disabled: false },
- job: { name: '岗位', disabled: false },
- title: { name: '职称', disabled: false },
- dept: { name: '科室', disabled: false },
- }
-})
+ anotherName: { name: "姓名", required: true, disabled: false },
+ job: { name: "岗位", disabled: false },
+ title: { name: "职称", disabled: false },
+ dept: { name: "科室", disabled: false },
+ };
+});
// 选项数据
const genderOptions = [
@@ -139,55 +197,60 @@ function chooseAvatar() {
if (url) {
form.value.avatar = url;
} else {
- toast('上传失败')
+ toast("上传失败");
}
- }
- })
+ },
+ });
}
function onChange({ title, value }) {
- form.value[title] = value
+ form.value[title] = value;
}
function selectJob() {
if (rule.value.job.disabled) return;
uni.showActionSheet({
- itemList: ['医生', '医生助理', '无'],
+ itemList: ["医生", "医生助理", "无"],
success: ({ tapIndex }) => {
- const job = ['doctor', 'assistant',][tapIndex];
+ const job = ["doctor", "assistant"][tapIndex];
form.value.job = job ? [job] : [];
- }
- })
+ },
+ });
}
function toCert() {
- if (jobStr.value === '医生') {
+ if (jobStr.value === "医生") {
uni.navigateTo({
- url: '/pages/work/verify/doctor'
- })
- } else if (jobStr.value === '医生助理') {
+ url: "/pages/work/verify/doctor",
+ });
+ } else if (jobStr.value === "医生助理") {
uni.navigateTo({
- url: '/pages/work/verify/assistant'
- })
+ url: "/pages/work/verify/assistant",
+ });
} else {
- toast('请选择岗位信息')
+ toast("请选择岗位信息");
}
}
async function save() {
- if (typeof formData.value.anotherName !== 'string' || !formData.value.anotherName.trim()) {
- return toast('请输入姓名')
+ if (
+ typeof formData.value.anotherName !== "string" ||
+ !formData.value.anotherName.trim()
+ ) {
+ return toast("请输入姓名");
}
- if (type.value === 'cert' && !jobStr.value) {
- return toast('请选择岗位信息')
+ if (type.value === "cert" && !jobStr.value) {
+ return toast("请选择岗位信息");
}
- const apiName = doctorInfo.value ? 'updateCorpMemberFromWxapp' : 'addCorpMemberFromWxapp';
+ const apiName = doctorInfo.value
+ ? "updateCorpMemberFromWxapp"
+ : "addCorpMemberFromWxapp";
const data = {
...form.value,
weChatOpenId: account.value.openid,
mobile: account.value.mobile,
corpId: account.value.corpId,
- }
+ };
if (doctorInfo.value) {
data.id = doctorInfo.value._id;
}
@@ -196,30 +259,29 @@ async function save() {
}
const res = await api(apiName, data);
if (res && res.success) {
- await getDoctorInfo()
+ await getDoctorInfo();
form.value = {};
- if (type.value === 'cert') {
- toCert()
+ if (type.value === "cert") {
+ toCert();
} else {
- await toast('保存成功');
- back()
+ await toast("保存成功");
+ back();
}
} else {
- await toast(res?.message || '保存失败');
+ await toast(res?.message || "保存失败");
}
}
-useLoad(opts => {
+useLoad((opts) => {
type.value = opts?.type;
- if (type.value === 'joinTeam' && opts.teamId) {
- inviteTeamId.value = opts.teamId
+ if (type.value === "joinTeam" && opts.teamId) {
+ inviteTeamId.value = opts.teamId;
}
-})
-
-useShow(() => {
- getDoctorInfo()
});
+useShow(() => {
+ getDoctorInfo();
+});
\ No newline at end of file
diff --git a/pages/work/work.vue b/pages/work/work.vue
index ac125ef..2c8d760 100644
--- a/pages/work/work.vue
+++ b/pages/work/work.vue
@@ -7,7 +7,7 @@
-
@@ -46,23 +46,29 @@
待办列表
- 个人
- 团队
+
+ 个人
+
+
+ 团队
+
共
- 23
+ {{ total }}
条
+ :class="filterData.eventStatus == i.value ? 'text-primary' : 'text-dark'" @click="changeStatus(i.value)">
{{ i.label }}
-
+
@@ -70,28 +76,35 @@
-
+
- 计划执行: 2025-10-22
+ 计划执行: {{ i.planDate }}
- 患者: 李珊珊
+ 患者: {{ i.customerName }}
- 患者满意度调查
-
- 待处理
+ {{ i.eventTypeLabel }}
+
+ {{ i.eventStatusLabel }}
- 对于门诊就诊患者的满意度做统计,以便优化…
-
-
- 发送内容:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX…
+ {{ i.sendContent }}
+
+
+
+ 发送内容:{{ file.file.name }}
+
发送
- 张敏西(张敏希服务团队)
- 创建:2026-01-08 张敏西
+
+ {{ i.executorUserName }}({{ i.executeTeamName }})
+
+
+ 创建:{{ i.createTime }} {{ i.creatorUserName }}
+
@@ -103,19 +116,28 @@
+
+
diff --git a/static/default-patient-avatar.png b/static/default-patient-avatar.png
new file mode 100644
index 0000000..6cfacfe
Binary files /dev/null and b/static/default-patient-avatar.png differ
diff --git a/static/icon/buchong.png b/static/icon/buchong.png
new file mode 100644
index 0000000..7dd053b
Binary files /dev/null and b/static/icon/buchong.png differ
diff --git a/static/icon/kaiqiAI.png b/static/icon/kaiqiAI.png
new file mode 100644
index 0000000..bd77156
Binary files /dev/null and b/static/icon/kaiqiAI.png differ
diff --git a/static/icon/zhuiwen.png b/static/icon/zhuiwen.png
new file mode 100644
index 0000000..d95bbd8
Binary files /dev/null and b/static/icon/zhuiwen.png differ
diff --git a/static/zhuanhua.svg b/static/zhuanhua.svg
new file mode 100644
index 0000000..061cbf5
--- /dev/null
+++ b/static/zhuanhua.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/store/account.js b/store/account.js
index ce571c3..11c2621 100644
--- a/store/account.js
+++ b/store/account.js
@@ -19,7 +19,7 @@ export default defineStore("accountStore", () => {
// 从缓存中恢复数据
const account = ref(cache.get(CACHE_KEYS.ACCOUNT, null));
- const loading = ref(false);
+
const loginPromise = ref(null);
// IM 相关
const openid = ref(cache.get(CACHE_KEYS.OPENID, ""));
@@ -48,6 +48,7 @@ export default defineStore("accountStore", () => {
});
if (code) {
const res = await api('wxAppLogin', {
+ appId: appid,
phoneCode,
code,
corpId,
diff --git a/store/team.js b/store/team.js
index c1d1a82..df47e1a 100644
--- a/store/team.js
+++ b/store/team.js
@@ -1,4 +1,4 @@
-import { ref } from "vue";
+import { computed, ref } from "vue";
import { defineStore, storeToRefs } from "pinia";
import api from '@/utils/api';
import { toast } from '@/utils/widget';
@@ -9,6 +9,13 @@ export default defineStore("teamStore", () => {
const { account, doctorInfo } = storeToRefs(useAccountStore());
const teams = ref([]);
+ const chargeTeams = computed(() => {
+ const userid = doctorInfo.value?.userid;
+ return teams.value.filter(team => {
+ const memberLeaderList = Array.isArray(team.memberLeaderList) ? team.memberLeaderList : [];
+ return memberLeaderList.includes(userid);
+ });
+ })
async function getTeam(teamId) {
if (!teamId || !account.value?.corpId) return;
const res = await api('getTeamData', { teamId, corpId: account.value.corpId });
@@ -21,11 +28,24 @@ export default defineStore("teamStore", () => {
async function getTeams() {
const corpId = account.value?.corpId;
- const mateId = doctorInfo.value?.corpId;
+ const mateId = doctorInfo.value?.userid;
if (!corpId || !mateId) return;
const res = await api('getJoinedTeams', { corpId, mateId });
teams.value = res && Array.isArray(res.data) ? res.data : [];
}
- return { teams, getTeam, getTeams }
+ // 获取团队成员头像映射
+ async function getTeamMemberAvatars(teamId) {
+ if (!teamId || !account.value?.corpId) return {};
+ const res = await api('getTeamMemberAvatars', {
+ teamId,
+ corpId: account.value.corpId
+ });
+ if (res && res.success && res.data) {
+ return res.data; // 返回 { userId: avatar } 的映射对象
+ }
+ return {};
+ }
+
+ return { teams, chargeTeams, getTeam, getTeams, getTeamMemberAvatars }
})
\ No newline at end of file
diff --git a/utils/api.js b/utils/api.js
index 74766be..3ac05f2 100644
--- a/utils/api.js
+++ b/utils/api.js
@@ -25,7 +25,8 @@ const urlsConfig = {
createOwnTeam: 'createOwnTeam',
removeTeammate: "removeTeammate",
toggleTeamLeaderRole: "toggleTeamLeaderRole",
- joinTheInvitedTeam: 'joinTheInvitedTeam'
+ joinTheInvitedTeam: 'joinTheInvitedTeam',
+ getTeamMemberAvatars: 'getTeamMemberAvatars'
},
knowledgeBase: {
@@ -97,7 +98,9 @@ const urlsConfig = {
acceptConsultation: "acceptConsultation",
sendArticleMessage: "sendArticleMessage",
getChatRecordsByGroupId: "getChatRecordsByGroupId",
- getGroupList: "getGroupList"
+ getGroupList: "getGroupList",
+ followUpInquiry: "followUpInquiry",
+ supplementMedicalCase: "supplementMedicalCase"
},
todo: {
getCustomerTodos: 'getCustomerTodos',
@@ -119,6 +122,7 @@ const urlsConfig = {
// 客户流转记录
customerTransferRecord: 'customerTransferRecord',
// sendConsultRejectedMessage: "sendConsultRejectedMessage"
+ getTeamTodos: 'getTeamTodos'
}
}
@@ -137,7 +141,7 @@ const urls = Object.keys(urlsConfig).reduce((acc, path) => {
}, {})
console.log('urls: ', urls)
-export default async function api(urlId, data) {
+export default async function api(urlId, data, loading) {
const config = urls[urlId];
if (!config) {
throw new Error(`Unknown URL ID: ${urlId}`);
@@ -148,7 +152,7 @@ export default async function api(urlId, data) {
data: {
...data,
type,
- }
- })
+ },
+ }, loading)
}
diff --git a/utils/conversation-merger.js b/utils/conversation-merger.js
index bb3e54e..429e232 100644
--- a/utils/conversation-merger.js
+++ b/utils/conversation-merger.js
@@ -42,41 +42,37 @@ export async function mergeConversationWithGroupDetails(conversationList, option
groupIds,
...options // 支持传入额外的查询参数(corpId, teamId, keyword等)
}
- const response = await api('getGroupList', requestData)
+ const response = await api('getGroupList', requestData, false)
// 4. 检查响应
if (!response || !response.success) {
console.error('获取群组详细信息失败:', response?.message || '未知错误')
return []
}
-
const groupDetailsMap = createGroupDetailsMap(response.data?.list || [])
console.log('获取到的群组详细信息数量:', Object.keys(groupDetailsMap).size)
-
// 5. 合并数据并过滤
const mergedList = conversationList
.map(conversation => mergeConversationData(conversation, groupDetailsMap))
- .filter(item => item !== null) // 过滤掉后端不存在的会话
-
+ .filter(item => item !== null);
console.log('合并后的会话列表数量:', mergedList.length)
console.log('过滤掉的会话数量:', conversationList.length - mergedList.length)
-
// 6. 格式化并排序会话列表
const formattedList = mergedList
.map((group) => ({
conversationID: group.conversationID || `GROUP${group.groupID}`,
- groupID: group.groupID,
- name: group.patientName
- ? `${group.patientName}的问诊`
- : group.name || "问诊群聊",
- avatar: group.avatar || "/static/default-avatar.png",
+ avatar: group.avatar || "/static/default-patient-avatar.png",
lastMessage: group.lastMessage || "暂无消息",
lastMessageTime: group.lastMessageTime || Date.now(),
+ groupID: group.groupID,
unreadCount: group.unreadCount || 0,
doctorId: group.doctorId,
patientName: group.patientName,
patientSex: group.patientSex,
patientAge: group.patientAge,
orderStatus: group.orderStatus,
+ teamId: group.teamId,
+ teamName: group.teamName,
+ teamMemberList: group.teamMemberList,
}))
.sort((a, b) => b.lastMessageTime - a.lastMessageTime)
@@ -127,9 +123,6 @@ function mergeConversationData(conversation, groupDetailsMap) {
return {
// 保留原有的会话信息
...conversation,
-
- // 合并后端的群组信息
- _id: groupDetail._id,
corpId: groupDetail.corpId,
teamId: groupDetail.teamId,
customerId: groupDetail.customerId,
@@ -148,7 +141,7 @@ function mergeConversationData(conversation, groupDetailsMap) {
teamName: groupDetail.team?.name,
teamMemberList: groupDetail.team?.memberList,
teamDescription: groupDetail.team?.description,
-
+ teamId: groupDetail.teamId,
// 时间信息
createdAt: groupDetail.createdAt,
updatedAt: groupDetail.updatedAt,
@@ -156,8 +149,8 @@ function mergeConversationData(conversation, groupDetailsMap) {
// 更新显示名称(使用后端的患者信息)
name: formatConversationName(groupDetail),
- // 更新头像
- avatar: groupDetail.patient?.avatar || conversation.avatar || '/static/default-avatar.png'
+ // 更新头像(优先使用已有头像,避免闪动)
+ avatar: conversation.avatar || groupDetail.patient?.avatar
}
}
diff --git a/utils/share-usage-example.md b/utils/share-usage-example.md
new file mode 100644
index 0000000..b4b8606
--- /dev/null
+++ b/utils/share-usage-example.md
@@ -0,0 +1,234 @@
+# 微信小程序分享功能使用指南
+
+## 功能说明
+
+提供了完整的微信小程序分享功能,包括:
+- 分享给好友
+- 分享到朋友圈
+- 保存图片到相册
+
+## 使用方法
+
+### 1. 基础分享(在页面中)
+
+```vue
+
+
+ 分享给好友
+
+
+
+
+```
+
+### 2. 使用分享组件
+
+```vue
+
+
+
+
+
+
+
+```
+
+### 3. 保存二维码图片
+
+```vue
+
+
+
+ 保存二维码
+
+
+
+
+```
+
+### 4. 动态分享内容
+
+```vue
+
+```
+
+## 配置说明
+
+### 1. 启用分享到朋友圈
+
+在 `pages.json` 中配置页面:
+
+```json
+{
+ "path": "pages/index/index",
+ "style": {
+ "navigationBarTitleText": "首页",
+ "enableShareTimeline": true
+ }
+}
+```
+
+### 2. 全局分享配置
+
+在 `App.vue` 中配置全局分享:
+
+```vue
+
+```
+
+## API 说明
+
+### createShareMessage(options)
+
+创建分享给好友的配置
+
+**参数:**
+- `title` (string): 分享标题
+- `path` (string): 分享路径
+- `imageUrl` (string): 分享图片URL
+
+**返回:** 分享配置对象
+
+### createShareTimeline(options)
+
+创建分享到朋友圈的配置
+
+**参数:**
+- `title` (string): 分享标题
+- `query` (string): 分享路径参数
+- `imageUrl` (string): 分享图片URL
+
+**返回:** 分享配置对象
+
+### saveImageToAlbum(filePath)
+
+保存图片到相册
+
+**参数:**
+- `filePath` (string): 图片路径(本地临时路径或网络路径)
+
+**返回:** Promise
+
+## 注意事项
+
+1. 分享图片建议尺寸:5:4,推荐 500x400px
+2. 分享路径必须是已注册的页面路径
+3. 保存图片需要用户授权相册权限
+4. 分享到朋友圈需要在页面配置中启用
+5. 网络图片会自动下载后保存到相册
diff --git a/utils/share.js b/utils/share.js
new file mode 100644
index 0000000..2119bc0
--- /dev/null
+++ b/utils/share.js
@@ -0,0 +1,169 @@
+/**
+ * 微信小程序分享工具
+ */
+
+import { toast } from './widget'
+
+/**
+ * 创建分享到好友的配置
+ * @param {Object} options 分享配置
+ * @param {string} options.title 分享标题
+ * @param {string} options.path 分享路径
+ * @param {string} options.imageUrl 分享图片URL
+ * @returns {Object} 分享配置对象
+ */
+export function createShareMessage(options = {}) {
+ const { title = '', path = '', imageUrl = '' } = options
+
+ return {
+ title,
+ path,
+ imageUrl,
+ success: () => {
+ toast('分享成功')
+ },
+ fail: (err) => {
+ console.error('分享失败:', err)
+ toast('分享失败')
+ }
+ }
+}
+
+/**
+ * 创建分享到朋友圈的配置
+ * @param {Object} options 分享配置
+ * @param {string} options.title 分享标题
+ * @param {string} options.query 分享路径参数
+ * @param {string} options.imageUrl 分享图片URL
+ * @returns {Object} 分享配置对象
+ */
+export function createShareTimeline(options = {}) {
+ const { title = '', query = '', imageUrl = '' } = options
+
+ return {
+ title,
+ query,
+ imageUrl
+ }
+}
+
+/**
+ * 在页面中启用分享功能
+ * 使用方法:在页面的 setup 中调用
+ *
+ * @example
+ * import { enableShare } from '@/utils/share'
+ *
+ * // 在 setup 中
+ * enableShare({
+ * message: {
+ * title: '分享标题',
+ * path: '/pages/index/index',
+ * imageUrl: 'https://example.com/image.jpg'
+ * },
+ * timeline: {
+ * title: '朋友圈标题',
+ * query: 'id=123',
+ * imageUrl: 'https://example.com/image.jpg'
+ * }
+ * })
+ */
+export function enableShare(config = {}) {
+ const { message, timeline } = config
+
+ // 分享给好友
+ if (message) {
+ uni.$on('onShareAppMessage', () => {
+ return createShareMessage(message)
+ })
+ }
+
+ // 分享到朋友圈
+ if (timeline) {
+ uni.$on('onShareTimeline', () => {
+ return createShareTimeline(timeline)
+ })
+ }
+}
+
+/**
+ * 保存图片到相册
+ * @param {string} filePath 图片路径(本地临时路径或网络路径)
+ */
+export async function saveImageToAlbum(filePath) {
+ try {
+ // 如果是网络图片,先下载
+ let localPath = filePath
+ if (filePath.startsWith('http')) {
+ const res = await uni.downloadFile({ url: filePath })
+ if (res[0]) {
+ throw new Error('下载图片失败')
+ }
+ localPath = res[1].tempFilePath
+ }
+
+ // 检查授权
+ const authRes = await uni.getSetting()
+ if (!authRes[1].authSetting['scope.writePhotosAlbum']) {
+ // 请求授权
+ try {
+ await uni.authorize({ scope: 'scope.writePhotosAlbum' })
+ } catch (err) {
+ // 用户拒绝授权,引导去设置
+ const [modalErr, modalRes] = await uni.showModal({
+ title: '提示',
+ content: '需要您授权保存相册',
+ confirmText: '去设置',
+ cancelText: '取消'
+ })
+
+ if (modalRes && modalRes.confirm) {
+ await uni.openSetting()
+ }
+ return false
+ }
+ }
+
+ // 保存图片
+ const [saveErr] = await uni.saveImageToPhotosAlbum({ filePath: localPath })
+ if (saveErr) {
+ throw saveErr
+ }
+
+ await toast('保存成功')
+ return true
+ } catch (err) {
+ console.error('保存图片失败:', err)
+ await toast('保存失败')
+ return false
+ }
+}
+
+/**
+ * 生成带参数的小程序码
+ * 需要后端接口支持
+ * @param {Object} options
+ * @param {string} options.scene 场景值
+ * @param {string} options.page 页面路径
+ * @returns {Promise} 返回小程序码图片URL
+ */
+export async function generateMiniCode(options = {}) {
+ // 这里需要调用后端接口生成小程序码
+ // 示例代码,需要根据实际后端接口调整
+ try {
+ const res = await uni.request({
+ url: '/api/wechat/generateMiniCode',
+ method: 'POST',
+ data: options
+ })
+
+ if (res[0] || !res[1].data.success) {
+ throw new Error('生成小程序码失败')
+ }
+
+ return res[1].data.data.url
+ } catch (err) {
+ console.error('生成小程序码失败:', err)
+ throw err
+ }
+}
diff --git a/utils/tim-chat.js b/utils/tim-chat.js
index d13f231..b6f2529 100644
--- a/utils/tim-chat.js
+++ b/utils/tim-chat.js
@@ -1086,7 +1086,7 @@ class TimChatManager {
const groupInfo = {
groupID: groupID,
name: group?.name || '问诊群聊',
- avatar: '/static/home/avatar.svg',
+ // avatar: '/static/home/avatar.svg',
memberCount: group?.memberCount || 0
}
@@ -1172,7 +1172,7 @@ class TimChatManager {
name: conversation.groupProfile?.name || '问诊群聊',
doctorId: '',
patientName: '',
- avatar: '/static/home/avatar.svg',
+ // avatar: '/static/home/avatar.svg',
lastMessage: '获取失败',
lastMessageTime: Date.now(),
unreadCount: conversation.unreadCount || 0,
@@ -1573,7 +1573,6 @@ class TimChatManager {
} else if (dbMsg.createdAt) {
lastTime = new Date(dbMsg.createdAt).getTime()
}
-
// 构建基础消息对象
const message = {
ID: dbMsg.MsgSeq || dbMsg._id || `db_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
@@ -2520,8 +2519,8 @@ class TimChatManager {
return '[自定义消息]'
}
- const customData = typeof payload.data === 'string'
- ? JSON.parse(payload.data)
+ const customData = typeof payload.data === 'string'
+ ? JSON.parse(payload.data)
: payload.data
const messageType = customData.messageType || customData.type
@@ -2569,7 +2568,6 @@ class TimChatManager {
conversationID,
groupID,
name: patientName ? `${patientName}的问诊` : groupName || '问诊群聊',
- avatar: '/static/default-avatar.png',
lastMessage,
lastMessageTime,
unreadCount: conversation.unreadCount || 0,
@@ -2582,7 +2580,6 @@ class TimChatManager {
conversationID: conversation.conversationID,
groupID: conversation.conversationID?.replace('GROUP', '') || '',
name: '问诊群聊',
- avatar: '/static/default-avatar.png',
lastMessage: '暂无消息',
lastMessageTime: Date.now(),
unreadCount: 0,
diff --git a/utils/widget.js b/utils/widget.js
index 495028e..8c88f8a 100644
--- a/utils/widget.js
+++ b/utils/widget.js
@@ -50,4 +50,47 @@ export async function confirm(content, opt = {}) {
}
})
})
+}
+
+// 保存图片到相册
+export async function saveImageToPhotosAlbum(filePath) {
+ try {
+ // 检查授权
+ const authRes = await uni.getSetting()
+ if (!authRes[1].authSetting['scope.writePhotosAlbum']) {
+ // 请求授权
+ try {
+ await uni.authorize({ scope: 'scope.writePhotosAlbum' })
+ } catch (err) {
+ await confirm('需要您授权保存相册', { title: '提示', showCancel: false })
+ await uni.openSetting()
+ return
+ }
+ }
+
+ // 保存图片
+ await uni.saveImageToPhotosAlbum({ filePath })
+ await toast('保存成功')
+ } catch (err) {
+ console.error('保存图片失败:', err)
+ await toast('保存失败')
+ }
+}
+
+// 分享到微信
+export function shareToWeChat(options = {}) {
+ const { title = '', path = '', imageUrl = '' } = options
+
+ return {
+ title,
+ path,
+ imageUrl,
+ success: () => {
+ toast('分享成功')
+ },
+ fail: (err) => {
+ console.error('分享失败:', err)
+ toast('分享失败')
+ }
+ }
}
\ No newline at end of file