/** * 消息发送助手 - 统一处理不同类型消息的发送 */ import { globalTimChatManager } from './tim-chat.js'; import api from './api.js'; import { toast } from './widget.js'; const env = __VITE_ENV__; function nowTs() { return Date.now(); } async function tryAddServiceRecord(payload) { try { const res = await api('addServiceRecord', payload); if (!res?.success) { console.warn('写入服务记录失败:', res?.message || res); } } catch (e) { console.error('写入服务记录异常:', e); } } function canWriteServiceRecord(options = {}) { return Boolean(options?.corpId && options?.userId && options?.customerId); } function normalizeServiceRecordBase(options = {}) { return { corpId: options.corpId, executorUserId: String(options.userId || ''), creatorUserId: String(options.userId || ''), customerId: String(options.customerId || ''), customerName: String(options.customerName || ''), executeTeamId: String(options.teamId || options.executeTeamId || ''), executionTime: nowTs(), externalUserId: String(options.externalUserId || options.customerUserId || ''), }; } /** * 发送文字消息 * @param {string} content - 文字内容 * @returns {Promise} 发送是否成功 */ export async function sendTextMessage(content) { if (!content || !content.trim()) { toast('文字内容不能为空'); return false; } try { if (!globalTimChatManager?.isLoggedIn) { toast('IM系统未就绪,请稍后重试'); return false; } // 直接调用 tim-chat 的 sendTextMessage 方法 const result = await globalTimChatManager.sendTextMessage(content.trim()); if (result?.success) { return true; } else { toast(result?.error || '发送文字消息失败'); return false; } } catch (error) { console.error('发送文字消息异常:', error); toast('发送文字消息失败'); return false; } } /** * 发送图片消息 * @param {string|File} imageSource - 图片URL(字符串)或文件对象 * @param {string} imageName - 图片名称(可选) * @returns {Promise} 发送是否成功 */ export async function sendImageMessage(imageSource, imageName = '图片') { if (!imageSource) { toast('图片不能为空'); return false; } try { if (!globalTimChatManager?.isLoggedIn) { toast('IM系统未就绪,请稍后重试'); return false; } // 如果是URL字符串,需要先下载转换为文件对象 let imageFile = imageSource; if (typeof imageSource === 'string') { imageFile = await downloadImageAsFile(imageSource, imageName); if (!imageFile) { toast('图片下载失败'); return false; } } const result = await globalTimChatManager.sendImageMessage(imageFile); if (result?.success) { return true; } else { toast(result?.error || '发送图片消息失败'); return false; } } catch (error) { console.error('发送图片消息异常:', error); toast('发送图片消息失败'); return false; } } /** * 将图片URL下载转换为文件对象(小程序环境) * @param {string} imageUrl - 图片URL * @param {string} fileName - 文件名 * @returns {Promise} 文件对象或null */ async function downloadImageAsFile(imageUrl, fileName = '图片') { return new Promise((resolve) => { uni.downloadFile({ url: imageUrl, success: (res) => { if (res.statusCode === 200) { // 返回包含 tempFiles 数组的对象,模拟 uni.chooseMedia 的返回格式 const fileObj = { tempFiles: [{ tempFilePath: res.tempFilePath, path: res.tempFilePath, size: 0, type: 'image' }], // 也保留直接的 tempFilePath 以兼容其他代码 tempFilePath: res.tempFilePath }; console.log('图片下载成功,文件对象:', fileObj); resolve(fileObj); } else { console.error('下载图片失败,状态码:', res.statusCode); resolve(null); } }, fail: (error) => { console.error('下载图片失败:', error); resolve(null); } }); }); } /** * 发送宣教文章消息 * @param {Object} article - 文章对象 { _id, title, cover, url } * @param {Object} options - 额外选项 { groupId, patientId, corpId } * @returns {Promise} 发送是否成功 */ export async function sendArticleMessage(article, options = {}) { if (!article || !article._id) { toast('文章信息不完整'); return false; } try { if (!globalTimChatManager?.isLoggedIn) { toast('IM系统未就绪,请稍后重试'); return false; } // 构建自定义消息 - 直接传递对象,sendCustomMessage会处理JSON序列化 const customMessageData = { type: 'article', title: article.title || '宣教文章', articleId: article._id, cover: article.cover || '', desc: article.subtitle || '点击查看详情', url: article.url || '', messageType: 'article', }; // 发送自定义消息 const result = await globalTimChatManager.sendCustomMessage(customMessageData); if (result?.success) { // 记录文章发送记录(异步,不阻塞) if (options.articleId && options.userId && options.customerId && options.corpId) { const params = { articleId: options.articleId, userId: options.userId, customerId: options.customerId, corpId: options.corpId, }; if (options.teamId) { params.teamId = options.teamId; } api('addArticleSendRecord', params).catch((err) => { console.error('记录文章发送失败:', err); }); } // 写入服务记录留痕(异步,不阻塞) if (canWriteServiceRecord(options)) { const base = normalizeServiceRecordBase(options); tryAddServiceRecord({ ...base, eventType: 'ContentReminder', taskContent: `推送文章:${article.title || '宣教文章'}`, pannedEventSendFile: { type: 'article', articleId: article._id || options.articleId || '', title: article.title || '', url: article.url || '', }, pannedEventName: '宣教发送', }); } return true; } else { toast(result?.error || '发送文章消息失败'); return false; } } catch (error) { console.error('发送文章消息异常:', error); toast('发送文章消息失败'); return false; } } /** * 发送问卷消息 * @param {Object} survey - 问卷对象 { _id, name, surveryId, url, createBy } * @param {Object} options - 额外选项 { userId, customerId, customerName, corpId } * @returns {Promise} 发送是否成功 */ export async function sendSurveyMessage(survey, options = {}) { if (!survey || !survey._id) { toast('问卷信息不完整'); return false; } try { if (!globalTimChatManager?.isLoggedIn) { toast('IM系统未就绪,请稍后重试'); return false; } // 生成发送ID const sendSurveyId = generateRandomString(10); const recordParams = { corpId: options.corpId, userId: options.userId, surveryId: survey._id, memberId: options.customerId, customer: options.customerName, sendSurveyId: sendSurveyId, }; if (options.teamId) { recordParams.teamId = options.teamId; } // 创建问卷记录 const createRecordRes = await api('createSurveyRecord', recordParams); if (!createRecordRes?.success) { toast(createRecordRes?.message || '创建问卷记录失败'); return false; } const answerId = createRecordRes?.id || ''; // 生成问卷链接 const surveyLink = generateSendLink( survey, answerId, options.customerId, options.customerName, sendSurveyId, { corpId: options.corpId, userId: options.userId } ); // 构建自定义消息 const customMessageData = buildSurveyMessage(survey, surveyLink); // 发送自定义消息 - 直接传递消息对象,不再进行额外序列化 const result = await globalTimChatManager.sendCustomMessage(customMessageData); if (result?.success) { // 写入服务记录留痕(异步,不阻塞) if (canWriteServiceRecord(options)) { const base = normalizeServiceRecordBase(options); tryAddServiceRecord({ ...base, eventType: 'questionnaire', taskContent: `推送问卷:${survey.name || '问卷'}`, pannedEventSendFile: { type: 'questionnaire', surveryId: survey._id || survey.surveryId || '', name: survey.name || '', url: surveyLink || '', }, pannedEventName: '问卷调查', }); } return true; } else { toast(result?.error || '发送问卷消息失败'); return false; } } catch (error) { console.error('发送问卷消息异常:', error); toast('发送问卷消息失败'); return false; } } /** * 生成随机字符串 * @param {number} length - 字符串长度 * @returns {string} 随机字符串 */ function generateRandomString(length) { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } /** * 生成问卷发送链接 * @param {Object} survey - 问卷对象 * @param {string} answerId - 答卷ID * @param {string} customerId - 客户ID * @param {string} customerName - 客户名称 * @param {string} sendSurveyId - 发送问卷ID * @param {Object} context - 上下文信息 { corpId, userId, env } * @returns {string} 问卷链接 */ function generateSendLink(survey, answerId, customerId, customerName, sendSurveyId, context = {}) { const { corpId, userId } = context; const isSystem = survey.createBy === 'system'; let url = ''; if (isSystem) { // 系统问卷:使用 VITE_SURVEY_URL url = `${env?.MP_SURVEY_URL}?corpId=${corpId}&surveryId=${survey.surveryId}&memberId=${customerId}&sendSurveyId=${sendSurveyId}&userId=${userId}`; } else { // 自定义问卷:使用 VITE_PATIENT_PAGE_BASE_URL url = `${env?.MP_PATIENT_PAGE_BASE_URL}pages/survery/fill?corpId=${corpId}&surveryId=${survey._id}&memberId=${customerId}&unionid=unionid&answerId=${answerId}&name=${customerName || ''}`; } // 如果已有链接,直接使用并拼接额外参数 if (survey.url && survey.url.trim()) { const separator = survey.url.includes('?') ? '&' : '?'; const params = []; if (answerId) params.push(`answerId=${answerId}`); if (sendSurveyId) params.push(`sendSurveyId=${sendSurveyId}`); if (userId) params.push(`userId=${userId}`); if (customerId) params.push(`memberId=${customerId}`); if (params.length > 0) { url = `${survey.url}${separator}${params.join('&')}`; } else { url = survey.url; } } return url; } /** * 构建问卷自定义消息 * @param {Object} survey - 问卷对象 * @param {string} surveyLink - 问卷链接 * @returns {Object} 自定义消息对象 */ function buildSurveyMessage(survey, surveyLink) { return { type: 'survey', title: survey.name || '填写问卷', desc: '请填写问卷', url: surveyLink, imgUrl: 'https://796f-youcan-clouddev-1-8ewcqf31dbb2b5-1317294507.tcb.qcloud.la/other/19-%E9%97%AE%E5%8D%B7.png?sign=55a4cd77c418b2c548b65792a2cf6bce&t=1701328694', messageType: 'survey', }; } /** * 处理回访任务消息发送 * @param {Object} messages - 消息数组 * @param {Object} context - 上下文信息 { userId, customerId, customerName, corpId } * @returns {Promise} 是否全部发送成功 */ export async function handleFollowUpMessages(messages, context = {}) { if (!Array.isArray(messages) || messages.length === 0) { toast('没有要发送的消息'); return false; } try { let allSuccess = true; for (const msg of messages) { let success = false; if (msg.type === 'text') { success = await sendTextMessage(msg.content); } else if (msg.type === 'image') { success = await sendImageMessage(msg.content, msg.name); } else if (msg.type === 'article') { success = await sendArticleMessage(msg.content, { articleId: msg.content.articleId, userId: context.userId, customerId: context.customerId, corpId: context.corpId, }); } else if (msg.type === 'questionnaire') { success = await sendSurveyMessage(msg.content, { userId: context.userId, customerId: context.customerId, customerName: context.customerName, corpId: context.corpId }); } if (!success) { allSuccess = false; // 继续发送其他消息,不中断 } } return allSuccess; } catch (error) { console.error('处理回访任务消息异常:', error); toast('消息发送过程中出现错误'); return false; } }