chat.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. interface Message {
  2. id: string;
  3. type: 'ai' | 'user' | 'reply';
  4. contentType: 'text' | 'image' | 'faq';
  5. content: string;
  6. questions?: string[];
  7. timestamp: number;
  8. }
  9. interface FAQItem {
  10. question: string;
  11. answer: string;
  12. }
  13. import { api_cozeAgent } from '../../api/login';
  14. const FAQ_LIST: FAQItem[] = [
  15. { question: '产品价格是多少?', answer: '我们的产品价格根据不同的套餐有所不同。基础版¥99/月,专业版¥199/月,企业版¥499/月。您可以根据自己的需求选择合适的套餐。' },
  16. { question: '如何购买课程?', answer: '您可以通过以下步骤购买课程:1. 在首页浏览课程列表;2. 点击心仪的课程进入详情页;3. 点击"立即购买"按钮;4. 选择支付方式完成支付。' },
  17. { question: '支持哪些设备?', answer: '我们的产品支持多种设备:iOS设备(iPhone、iPad)、Android设备(手机、平板)、Windows电脑、Mac电脑,以及主流的智能电视。' },
  18. { question: '有没有试用版本?', answer: '有的!我们为所有新用户提供7天免费试用期。试用期间您可以体验全部专业版功能,无需绑定支付方式。试用期结束后,您可以选择是否订阅。' },
  19. { question: '售后服务政策', answer: '我们提供完善的售后服务:1. 7天无理由退款;2. 产品问题30天内免费换新;3. 终身技术支持;4. 专属客服一对一服务。如有任何问题,请随时联系我们。' },
  20. { question: '开具发票', answer: '我们可以为您开具增值税普通发票或专用发票。请在购买后30天内,在"我的订单"页面点击"申请开票",填写相关信息后,我们会在5个工作日内将发票发送至您的邮箱。' },
  21. ];
  22. Page({
  23. data: {
  24. messages: [] as Message[],
  25. inputValue: '',
  26. scrollToMessage: '',
  27. userAvatar: '',
  28. isLoading: false,
  29. avatarError: false,
  30. keyboardHeight: 0,
  31. },
  32. onLoad() {
  33. this.loadUserInfo();
  34. this.initWelcomeMessages();
  35. },
  36. // 加载用户信息
  37. loadUserInfo() {
  38. const userInfo = wx.getStorageSync('userInfo');
  39. if (userInfo) {
  40. this.setData({
  41. userAvatar: userInfo.avatarUrl || ''
  42. });
  43. }
  44. },
  45. // 初始化欢迎消息
  46. initWelcomeMessages() {
  47. const messages: Message[] = [];
  48. // 第一条欢迎消息
  49. messages.push({
  50. id: this.generateId(),
  51. type: 'ai',
  52. contentType: 'text',
  53. content: '您好!欢迎来到音乐数字AI客服中心。我是您的专属客服小助手,很高兴为您服务!请问有什么可以帮助您的吗?',
  54. timestamp: Date.now()
  55. });
  56. // 第二条常见问题卡片
  57. messages.push({
  58. id: this.generateId(),
  59. type: 'ai',
  60. contentType: 'faq',
  61. content: '常见问题',
  62. questions: FAQ_LIST.map(item => item.question),
  63. timestamp: Date.now() + 1
  64. });
  65. this.setData({ messages }, () => {
  66. this.scrollToBottom();
  67. });
  68. },
  69. // 生成唯一ID
  70. generateId(): string {
  71. return Date.now().toString(36) + Math.random().toString(36).substr(2);
  72. },
  73. // 滚动到底部
  74. scrollToBottom() {
  75. this.setData({
  76. scrollToMessage: 'last-message'
  77. });
  78. },
  79. // 头像加载失败
  80. onAvatarError() {
  81. this.setData({ avatarError: true });
  82. },
  83. // 输入框变化
  84. onInput(e: WechatMiniprogram.Input) {
  85. this.setData({
  86. inputValue: e.detail.value
  87. });
  88. },
  89. onKeyboardHeightChange(e: WechatMiniprogram.InputKeyboardHeightChange) {
  90. const keyboardHeight = e.detail.height || 0;
  91. if (keyboardHeight === this.data.keyboardHeight) return;
  92. this.setData({ keyboardHeight }, () => {
  93. this.scrollToBottom();
  94. });
  95. },
  96. // 发送文字消息
  97. sendTextMessage() {
  98. const { inputValue, isLoading } = this.data;
  99. if (!inputValue.trim() || isLoading) return;
  100. const content = inputValue.trim();
  101. // 添加用户消息
  102. this.addUserMessage('text', content);
  103. // 清空输入框
  104. this.setData({ inputValue: '' });
  105. // 发送到服务器
  106. this.sendMessageToServer(content, 'text');
  107. },
  108. // 添加用户消息
  109. addUserMessage(contentType: 'text' | 'image', content: string) {
  110. const { messages } = this.data;
  111. const newMessage: Message = {
  112. id: this.generateId(),
  113. type: 'user',
  114. contentType,
  115. content,
  116. timestamp: Date.now()
  117. };
  118. this.setData({
  119. messages: [...messages, newMessage]
  120. }, () => {
  121. this.scrollToBottom();
  122. });
  123. },
  124. // 选择图片
  125. chooseImage() {
  126. wx.chooseMedia({
  127. count: 1,
  128. mediaType: ['image'],
  129. sourceType: ['album', 'camera'],
  130. success: (res) => {
  131. const tempFilePath = res.tempFiles[0].tempFilePath;
  132. this.uploadImage(tempFilePath);
  133. },
  134. fail: (err) => {
  135. console.error('选择图片失败:', err);
  136. wx.showToast({
  137. title: '选择图片失败',
  138. icon: 'none'
  139. });
  140. }
  141. });
  142. },
  143. // 上传图片
  144. uploadImage(filePath: string) {
  145. // 先显示本地图片
  146. this.addUserMessage('image', filePath);
  147. // 获取全局配置
  148. const app = getApp<IAppOption>();
  149. const baseUrl = app.globalData?.baseUrl || 'https://test.kt.colexiu.com';
  150. // 上传图片到服务器
  151. wx.uploadFile({
  152. url: `${baseUrl}/edu-app/ai/chat/upload`,
  153. filePath: filePath,
  154. name: 'file',
  155. header: {
  156. 'Authorization': wx.getStorageSync('token') || ''
  157. },
  158. success: (res) => {
  159. try {
  160. const data = JSON.parse(res.data);
  161. if (data.code === 200) {
  162. // 上传成功,发送图片URL到聊天
  163. this.sendMessageToServer(data.data.url, 'image');
  164. } else {
  165. throw new Error(data.msg || '上传失败');
  166. }
  167. } catch (err) {
  168. console.error('解析上传响应失败:', err);
  169. this.addReplyMessage('图片上传失败,请重试');
  170. }
  171. },
  172. fail: (err) => {
  173. console.error('上传图片失败:', err);
  174. // 模拟成功,直接回复
  175. setTimeout(() => {
  176. this.addReplyMessage('我已收到您的图片,请问有什么可以帮助您的吗?');
  177. }, 1000);
  178. }
  179. });
  180. },
  181. // 发送消息到服务器
  182. async sendMessageToServer(content: string, contentType: 'text' | 'image') {
  183. this.setData({ isLoading: true });
  184. // 获取用户ID(优先使用openid,其次使用token中的用户ID)
  185. const userInfo = wx.getStorageSync('userInfo') || {};
  186. const userId = userInfo.openId || userInfo.userId || wx.getStorageSync('openId');
  187. // 构建消息内容(图片发送固定提示语)
  188. const messageContent = contentType === 'image' ? '我发送了一张图片' : content;
  189. try {
  190. // 调用客服消息接口
  191. const {data}: any = await api_cozeAgent({
  192. message: messageContent,
  193. userId: userId
  194. });
  195. if (data.code === 200 && data.data) {
  196. // 使用接口返回的回复内容
  197. const reply =data.data || '您好,我已经收到您的问题。';
  198. this.addReplyMessage(reply);
  199. } else {
  200. // 接口返回错误,使用默认回复
  201. this.handleDefaultReply(contentType);
  202. }
  203. } catch (err) {
  204. console.error('客服接口调用失败:', err);
  205. // 接口调用失败,使用默认回复
  206. this.handleDefaultReply(contentType);
  207. } finally {
  208. this.setData({ isLoading: false });
  209. }
  210. },
  211. // 处理默认回复(当接口调用失败时使用)
  212. handleDefaultReply(contentType: 'text' | 'image') {
  213. let reply = '';
  214. if (contentType === 'image') {
  215. reply = '我已收到您的图片,请问有什么可以帮助您的吗?';
  216. } else {
  217. reply = '您好,我已经收到您的问题。我们的客服人员会尽快为您解答。如果您有其他问题,也可以继续提问。';
  218. }
  219. this.addReplyMessage(reply);
  220. },
  221. // 添加AI回复消息
  222. addReplyMessage(content: string) {
  223. const { messages } = this.data;
  224. const replyMessage: Message = {
  225. id: this.generateId(),
  226. type: 'reply',
  227. contentType: 'text',
  228. content,
  229. timestamp: Date.now()
  230. };
  231. this.setData({
  232. messages: [...messages, replyMessage]
  233. }, () => {
  234. this.scrollToBottom();
  235. });
  236. },
  237. // 点击常见问题
  238. onFaqTap(e: WechatMiniprogram.Tap) {
  239. const index = e.currentTarget.dataset.index;
  240. const faq = FAQ_LIST[index];
  241. if (faq) {
  242. // 添加用户选择的问题
  243. this.addUserMessage('text', faq.question);
  244. // 直接回复固定内容
  245. setTimeout(() => {
  246. this.addReplyMessage(faq.answer);
  247. }, 500);
  248. }
  249. },
  250. // 点击图片预览
  251. onImageTap(e: any) {
  252. const url = e.currentTarget.dataset.url;
  253. if (url) {
  254. wx.previewImage({
  255. urls: [url],
  256. current: url
  257. });
  258. }
  259. }
  260. });