delivery-message.service.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import { Injectable } from '@angular/core';
  2. import { FmodeParse, FmodeObject } from 'fmode-ng/parse';
  3. const Parse = FmodeParse.with('nova');
  4. /**
  5. * 消息类型
  6. */
  7. export type MessageType = 'text' | 'image' | 'text_with_image';
  8. /**
  9. * 交付消息接口
  10. */
  11. export interface DeliveryMessage {
  12. id?: string;
  13. project: string; // 项目ID
  14. stage: string; // 阶段 (white_model/soft_decor/rendering/post_process)
  15. messageType: MessageType; // 消息类型
  16. content: string; // 文本内容
  17. images?: string[]; // 图片URL数组
  18. sentBy: string; // 发送人ID
  19. sentByName?: string; // 发送人姓名
  20. sentAt: Date; // 发送时间
  21. }
  22. /**
  23. * 预设话术模板
  24. */
  25. export const MESSAGE_TEMPLATES = {
  26. white_model: [
  27. '老师我这里硬装模型做好了,看下是否有问题,如果没有,我去做渲染',
  28. '老师,白模阶段完成,请查看确认',
  29. '硬装结构已完成,请审阅'
  30. ],
  31. soft_decor: [
  32. '软装好了,准备渲染,有问题可以留言',
  33. '老师,软装设计已完成,请查看',
  34. '家具配置完成,准备进入渲染阶段'
  35. ],
  36. rendering: [
  37. '老师,渲染图已完成,请查看效果',
  38. '效果图已出,请审阅',
  39. '渲染完成,请查看是否需要调整'
  40. ],
  41. post_process: [
  42. '老师,后期处理完成,请验收',
  43. '最终成品已完成,请查看',
  44. '所有修图完成,请确认'
  45. ]
  46. };
  47. /**
  48. * 交付消息服务
  49. */
  50. @Injectable({
  51. providedIn: 'root'
  52. })
  53. export class DeliveryMessageService {
  54. /**
  55. * 获取阶段预设话术
  56. */
  57. getStageTemplates(stage: string): string[] {
  58. return MESSAGE_TEMPLATES[stage as keyof typeof MESSAGE_TEMPLATES] || [];
  59. }
  60. /**
  61. * 创建文本消息
  62. */
  63. async createTextMessage(
  64. projectId: string,
  65. stage: string,
  66. content: string,
  67. currentUser: FmodeObject
  68. ): Promise<DeliveryMessage> {
  69. const message: DeliveryMessage = {
  70. project: projectId,
  71. stage,
  72. messageType: 'text',
  73. content,
  74. sentBy: currentUser.id!,
  75. sentByName: currentUser.get('name') || '未知用户',
  76. sentAt: new Date()
  77. };
  78. // 🔥 保存到数据库
  79. await this.saveMessageToProject(projectId, message);
  80. // 🔥 发送到企业微信
  81. await this.sendToWxwork(content, []);
  82. return message;
  83. }
  84. /**
  85. * 创建图片消息
  86. */
  87. async createImageMessage(
  88. projectId: string,
  89. stage: string,
  90. imageUrls: string[],
  91. content: string = '',
  92. currentUser: FmodeObject
  93. ): Promise<DeliveryMessage> {
  94. const message: DeliveryMessage = {
  95. project: projectId,
  96. stage,
  97. messageType: content ? 'text_with_image' : 'image',
  98. content,
  99. images: imageUrls,
  100. sentBy: currentUser.id!,
  101. sentByName: currentUser.get('name') || '未知用户',
  102. sentAt: new Date()
  103. };
  104. // 🔥 保存到数据库
  105. await this.saveMessageToProject(projectId, message);
  106. // 🔥 发送到企业微信
  107. await this.sendToWxwork(content, imageUrls);
  108. return message;
  109. }
  110. /**
  111. * 保存消息到项目data字段
  112. */
  113. private async saveMessageToProject(projectId: string, message: DeliveryMessage): Promise<void> {
  114. try {
  115. const query = new Parse.Query('Project');
  116. const project = await query.get(projectId);
  117. const data = project.get('data') || {};
  118. const messages = data.deliveryMessages || [];
  119. // 生成消息ID
  120. message.id = `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  121. messages.push(message);
  122. data.deliveryMessages = messages;
  123. project.set('data', data);
  124. await project.save();
  125. console.log('✅ 消息已保存到项目:', message.id);
  126. } catch (error) {
  127. console.error('❌ 保存消息失败:', error);
  128. throw error;
  129. }
  130. }
  131. /**
  132. * 获取项目的所有消息
  133. */
  134. async getProjectMessages(projectId: string): Promise<DeliveryMessage[]> {
  135. try {
  136. const query = new Parse.Query('Project');
  137. const project = await query.get(projectId);
  138. const data = project.get('data') || {};
  139. return data.deliveryMessages || [];
  140. } catch (error) {
  141. console.error('❌ 获取消息失败:', error);
  142. return [];
  143. }
  144. }
  145. /**
  146. * 获取特定阶段的消息
  147. */
  148. async getStageMessages(projectId: string, stage: string): Promise<DeliveryMessage[]> {
  149. const allMessages = await this.getProjectMessages(projectId);
  150. return allMessages.filter(msg => msg.stage === stage);
  151. }
  152. /**
  153. * 🔥 发送消息到企业微信当前窗口
  154. */
  155. private async sendToWxwork(text: string, imageUrls: string[] = []): Promise<void> {
  156. try {
  157. // 检查是否在企业微信环境中
  158. const isWxwork = window.location.href.includes('/wxwork/');
  159. if (!isWxwork) {
  160. console.log('⚠️ 非企业微信环境,跳过发送');
  161. return;
  162. }
  163. // 动态导入企业微信 JSSDK
  164. const ww = await import('@wecom/jssdk');
  165. console.log('📧 准备发送消息到企业微信...');
  166. console.log(' 文本:', text);
  167. console.log(' 图片数量:', imageUrls.length);
  168. // 🔥 发送文本消息
  169. if (text) {
  170. await ww.sendChatMessage({
  171. msgtype: 'text',
  172. text: {
  173. content: text
  174. }
  175. });
  176. console.log('✅ 文本消息已发送');
  177. }
  178. // 🔥 发送图片消息(使用news图文消息类型)
  179. for (let i = 0; i < imageUrls.length; i++) {
  180. const imageUrl = imageUrls[i];
  181. try {
  182. // 使用news类型发送图文消息,可以显示图片预览
  183. await ww.sendChatMessage({
  184. msgtype: 'news',
  185. news: {
  186. link: imageUrl,
  187. title: `图片 ${i + 1}/${imageUrls.length}`,
  188. desc: '点击查看大图',
  189. imgUrl: imageUrl
  190. }
  191. });
  192. console.log(`✅ 图文消息 ${i + 1}/${imageUrls.length} 已发送: ${imageUrl}`);
  193. // 避免发送过快,加入小延迟
  194. if (i < imageUrls.length - 1) {
  195. await new Promise(resolve => setTimeout(resolve, 300));
  196. }
  197. } catch (imgError) {
  198. console.error(`❌ 图片 ${i + 1} 发送失败:`, imgError);
  199. // 如果news类型失败,降级为纯文本链接
  200. try {
  201. await ww.sendChatMessage({
  202. msgtype: 'text',
  203. text: {
  204. content: `📷 图片 ${i + 1}/${imageUrls.length}\n${imageUrl}`
  205. }
  206. });
  207. console.log(`✅ 已改用文本方式发送图片链接`);
  208. } catch (textError) {
  209. console.error(`❌ 文本方式也失败:`, textError);
  210. }
  211. }
  212. }
  213. console.log('✅ 所有消息已发送到企业微信');
  214. } catch (error) {
  215. console.error('❌ 发送消息到企业微信失败:', error);
  216. // 发送失败不影响主流程,只记录错误
  217. }
  218. }
  219. }