ソースを参照

feat: 许愿树详情页加入 AI 智能聊天客服

- 后端集成 Spring AI + DeepSeek,创建 ChatController/ChatService
- 聊天记录持久化到 chat_message 表,按用户和许愿树关联
- 匿名用户不加载历史记录
- 前端 ChatWidget 组件:悬浮按钮 + 底部弹出聊天面板
- 支持加载历史、发送消息、AI 回复气泡展示

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
tanlie 2 週間 前
コミット
3186b8fd93
24 ファイル変更556 行追加0 行削除
  1. 28 0
      wishing-platform/platform-entity/platform-entity-wishing/src/main/java/cn/qinys/platform/entity/wishing/ChatMessage.java
  2. BIN
      wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/ChatMessage.class
  3. BIN
      wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/UserWish.class
  4. BIN
      wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/WishingTree.class
  5. BIN
      wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/WishingUser.class
  6. 4 0
      wishing-platform/platform-entity/platform-entity-wishing/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  7. 6 0
      wishing-platform/platform-entity/platform-entity-wishing/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  8. BIN
      wishing-platform/platform-entity/platform-entity-wishing/target/platform-entity-wishing-1.0.0-SNAPSHOT.jar
  9. 4 0
      wishing-platform/platform-service/platform-service-mobile/pom.xml
  10. 28 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/ChatClientConfig.java
  11. 34 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/controller/ChatController.java
  12. 9 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/mapper/ChatMessageMapper.java
  13. 16 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/req/ChatReq.java
  14. 20 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/resp/ChatHistoryResp.java
  15. 11 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/resp/ChatResp.java
  16. 19 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/ChatService.java
  17. 95 0
      wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/impl/ChatServiceImpl.java
  18. 8 0
      wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-dev.yml
  19. 7 0
      wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-local.yml
  20. 1 0
      wishing-tree-h5/components.d.ts
  21. 21 0
      wishing-tree-h5/src/api/chat.ts
  22. 242 0
      wishing-tree-h5/src/components/ChatWidget.vue
  23. 3 0
      wishing-tree-h5/src/views/TreeDetailView.vue
  24. BIN
      wishing-tree-h5/wish.zip

+ 28 - 0
wishing-platform/platform-entity/platform-entity-wishing/src/main/java/cn/qinys/platform/entity/wishing/ChatMessage.java

@@ -0,0 +1,28 @@
+package cn.qinys.platform.entity.wishing;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("chat_message")
+public class ChatMessage extends BaseEntity {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 用户标识 */
+    private String userId;
+
+    /** 关联许愿树 ID */
+    private Integer treeId;
+
+    /** 角色:user / ai */
+    private String role;
+
+    /** 消息内容 */
+    private String content;
+}

BIN
wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/ChatMessage.class


BIN
wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/UserWish.class


BIN
wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/WishingTree.class


BIN
wishing-platform/platform-entity/platform-entity-wishing/target/classes/cn/qinys/platform/entity/wishing/WishingUser.class


+ 4 - 0
wishing-platform/platform-entity/platform-entity-wishing/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,4 @@
+cn\qinys\platform\entity\wishing\WishingTree.class
+cn\qinys\platform\entity\wishing\ChatMessage.class
+cn\qinys\platform\entity\wishing\UserWish.class
+cn\qinys\platform\entity\wishing\WishingUser.class

+ 6 - 0
wishing-platform/platform-entity/platform-entity-wishing/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,6 @@
+G:\许愿树\wishing-platform\platform-entity\platform-entity-wishing\src\main\java\cn\qinys\platform\entity\wishing\BaseEntity.java
+G:\许愿树\wishing-platform\platform-entity\platform-entity-wishing\src\main\java\cn\qinys\platform\entity\wishing\ChatMessage.java
+G:\许愿树\wishing-platform\platform-entity\platform-entity-wishing\src\main\java\cn\qinys\platform\entity\wishing\UserWish.java
+G:\许愿树\wishing-platform\platform-entity\platform-entity-wishing\src\main\java\cn\qinys\platform\entity\wishing\WishingTree.java
+G:\许愿树\wishing-platform\platform-entity\platform-entity-wishing\src\main\java\cn\qinys\platform\entity\wishing\WishingTreeExtension.java
+G:\许愿树\wishing-platform\platform-entity\platform-entity-wishing\src\main\java\cn\qinys\platform\entity\wishing\WishingUser.java

BIN
wishing-platform/platform-entity/platform-entity-wishing/target/platform-entity-wishing-1.0.0-SNAPSHOT.jar


+ 4 - 0
wishing-platform/platform-service/platform-service-mobile/pom.xml

@@ -58,6 +58,10 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-loadbalancer</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-starter-model-deepseek</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <finalName>wishing-mobile</finalName>

+ 28 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/config/ChatClientConfig.java

@@ -0,0 +1,28 @@
+package cn.qinys.platform.config;
+
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ChatClientConfig {
+
+    @Bean
+    ChatClient chatClient(ChatModel chatModel) {
+        return ChatClient.builder(chatModel)
+                .defaultSystem("""
+                        你是许愿树的智能客服小助手,名叫"小愿"。
+                        你的职责是:
+                        1. 为用户介绍许愿树的历史故事、文化传统、许愿方式
+                        2. 解答用户关于许愿的疑问,如如何许愿、许愿礼仪等
+                        3. 为用户推荐附近适合许愿的地点
+                        4. 用温暖、友好的语气与用户交流
+                        5. 适当使用 emoji 让对话更生动
+
+                        回复控制在 100-200 字以内,简洁明了。
+                        如果用户问的问题与许愿无关,礼貌地引导回许愿相关话题。
+                        """)
+                .build();
+    }
+}

+ 34 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/controller/ChatController.java

@@ -0,0 +1,34 @@
+package cn.qinys.platform.mobile.controller;
+
+import cn.qinys.platform.base.response.Result;
+import cn.qinys.platform.mobile.req.ChatReq;
+import cn.qinys.platform.mobile.resp.ChatResp;
+import cn.qinys.platform.mobile.service.ChatService;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import cn.qinys.platform.mobile.resp.ChatHistoryResp;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/dgapi/mobile/chat")
+public class ChatController {
+
+    @Resource
+    private ChatService chatService;
+
+    /** 发送消息 */
+    @PostMapping
+    public Result<ChatResp> chat(@RequestBody @Valid ChatReq req) {
+        String reply = chatService.chat(req.getMessage(), req.getTreeId());
+        ChatResp resp = new ChatResp();
+        resp.setReply(reply);
+        return new Result<>(resp);
+    }
+
+    /** 获取聊天历史(按许愿树过滤) */
+    @GetMapping("/history")
+    public Result<ChatHistoryResp> history(@RequestParam(required = false) Integer treeId) {
+        ChatHistoryResp resp = chatService.getHistory(treeId);
+        return new Result<>(resp);
+    }
+}

+ 9 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/mapper/ChatMessageMapper.java

@@ -0,0 +1,9 @@
+package cn.qinys.platform.mobile.mapper;
+
+import cn.qinys.platform.entity.wishing.ChatMessage;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ChatMessageMapper extends BaseMapper<ChatMessage> {
+}

+ 16 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/req/ChatReq.java

@@ -0,0 +1,16 @@
+package cn.qinys.platform.mobile.req;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class ChatReq implements Serializable {
+
+    @NotBlank(message = "消息不能为空")
+    private String message;
+
+    /** 许愿树 ID(可选,用于关联上下文) */
+    private Integer treeId;
+}

+ 20 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/resp/ChatHistoryResp.java

@@ -0,0 +1,20 @@
+package cn.qinys.platform.mobile.resp;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class ChatHistoryResp implements Serializable {
+
+    private List<ChatMsg> list;
+
+    @Data
+    public static class ChatMsg implements Serializable {
+        private String role;
+        private String content;
+        private LocalDateTime createdAt;
+    }
+}

+ 11 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/resp/ChatResp.java

@@ -0,0 +1,11 @@
+package cn.qinys.platform.mobile.resp;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class ChatResp implements Serializable {
+
+    private String reply;
+}

+ 19 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/ChatService.java

@@ -0,0 +1,19 @@
+package cn.qinys.platform.mobile.service;
+
+import cn.qinys.platform.mobile.resp.ChatHistoryResp;
+
+/**
+ * 智能聊天客服服务
+ */
+public interface ChatService {
+
+    /**
+     * 发送消息并获取 AI 回复(自动保存聊天记录)
+     */
+    String chat(String message, Integer treeId);
+
+    /**
+     * 获取当前用户在某棵树下的聊天历史
+     */
+    ChatHistoryResp getHistory(Integer treeId);
+}

+ 95 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/java/cn/qinys/platform/mobile/service/impl/ChatServiceImpl.java

@@ -0,0 +1,95 @@
+package cn.qinys.platform.mobile.service.impl;
+
+import cn.qinys.platform.base.security.utils.CurrentUtils;
+import cn.qinys.platform.entity.wishing.ChatMessage;
+import cn.qinys.platform.mobile.mapper.ChatMessageMapper;
+import cn.qinys.platform.mobile.resp.ChatHistoryResp;
+import cn.qinys.platform.mobile.service.ChatService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.client.ChatClient;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Slf4j
+@Service
+public class ChatServiceImpl implements ChatService {
+
+    @Resource
+    private ChatClient chatClient;
+
+    @Resource
+    private ChatMessageMapper chatMessageMapper;
+
+    @Override
+    public String chat(String message, Integer treeId) {
+        String userId = getUserId();
+
+        // 保存用户消息
+        saveMessage(userId, treeId, "user", message);
+
+        String reply;
+        try {
+            reply = chatClient.prompt()
+                    .user(message)
+                    .call()
+                    .content();
+        } catch (Exception e) {
+            log.error("AI chat error", e);
+            reply = "抱歉,我暂时无法回复,请稍后再试~";
+        }
+
+        // 保存 AI 回复
+        saveMessage(userId, treeId, "ai", reply);
+
+        return reply;
+    }
+
+    @Override
+    public ChatHistoryResp getHistory(Integer treeId) {
+        String userId = getUserId();
+        ChatHistoryResp resp = new ChatHistoryResp();
+        if ("0".equals(userId) || "anonymous".equals(userId)) {
+            resp.setList(List.of());
+            return resp;
+        }
+        LambdaQueryWrapper<ChatMessage> wrapper = new LambdaQueryWrapper<ChatMessage>()
+                .eq(ChatMessage::getUserId, userId)
+                .orderByAsc(ChatMessage::getCreatedAt);
+        if (treeId != null) {
+            wrapper.eq(ChatMessage::getTreeId, treeId);
+        }
+        List<ChatMessage> messages = chatMessageMapper.selectList(wrapper);
+
+        List<ChatHistoryResp.ChatMsg> list = messages.stream().map(m -> {
+            ChatHistoryResp.ChatMsg msg = new ChatHistoryResp.ChatMsg();
+            msg.setRole(m.getRole());
+            msg.setContent(m.getContent());
+            msg.setCreatedAt(m.getCreatedAt());
+            return msg;
+        }).toList();
+
+        ChatHistoryResp resp = new ChatHistoryResp();
+        resp.setList(list);
+        return resp;
+    }
+
+    private void saveMessage(String userId, Integer treeId, String role, String content) {
+        ChatMessage msg = new ChatMessage();
+        msg.setUserId(userId);
+        msg.setTreeId(treeId);
+        msg.setRole(role);
+        msg.setContent(content);
+        chatMessageMapper.insert(msg);
+    }
+
+    private String getUserId() {
+        try {
+            return CurrentUtils.getCurrentUserId();
+        } catch (Exception e) {
+            return "anonymous";
+        }
+    }
+}

+ 8 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-dev.yml

@@ -36,6 +36,14 @@ mybatis-plus:
       logic-not-delete-value: 0
 
 
+  ai:
+    deepseek:
+      api-key: sk-7de46bc6a5c845fcb5ec060e5e3f1441
+      chat:
+        options:
+          model: deepseek-chat
+          temperature: 0.7
+
 system:
   base-url: https://wish.qinys.cn/dgapi/mobile/ext/file/
   file-directory: /file/

+ 7 - 0
wishing-platform/platform-service/platform-service-mobile/src/main/resources/application-local.yml

@@ -2,6 +2,13 @@ logging:
   level:
     cn.qinys.platform: debug
 spring:
+  ai:
+    deepseek:
+      api-key: sk-7de46bc6a5c845fcb5ec060e5e3f1441
+      chat:
+        options:
+          model: deepseek-chat
+          temperature: 0.7
   cloud:
     nacos:
       discovery:

+ 1 - 0
wishing-tree-h5/components.d.ts

@@ -12,6 +12,7 @@ export {}
 declare module 'vue' {
   export interface GlobalComponents {
     BottomNav: typeof import('./src/components/BottomNav.vue')['default']
+    ChatWidget: typeof import('./src/components/ChatWidget.vue')['default']
     ImageUploader: typeof import('./src/components/ImageUploader.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 21 - 0
wishing-tree-h5/src/api/chat.ts

@@ -0,0 +1,21 @@
+import { request } from './request'
+
+export interface ChatMsg {
+  role: 'user' | 'ai'
+  content: string
+  createdAt: string
+}
+
+export async function sendMessage(message: string, treeId?: number): Promise<string> {
+  const res = await request<{ reply: string }>('/mobile/chat', {
+    method: 'POST',
+    body: JSON.stringify({ message, treeId }),
+  })
+  return res.data.reply
+}
+
+export async function fetchHistory(treeId?: number): Promise<ChatMsg[]> {
+  const query = treeId != null ? `?treeId=${treeId}` : ''
+  const res = await request<{ list: ChatMsg[] }>(`/mobile/chat/history${query}`)
+  return res.data.list || []
+}

+ 242 - 0
wishing-tree-h5/src/components/ChatWidget.vue

@@ -0,0 +1,242 @@
+<template>
+  <!-- 悬浮按钮 -->
+  <div class="chat-fab" @click="toggleChat">
+    <van-icon :name="showChat ? 'cross' : 'chat-o'" size="22" color="#fff" />
+  </div>
+
+  <!-- 聊天面板 -->
+  <van-popup
+    v-model:show="showChat"
+    position="bottom"
+    round
+    :style="{ height: '65vh' }"
+    teleport="body"
+  >
+    <div class="chat-panel">
+      <div class="chat-header">
+        <span class="chat-title">🤖 小愿 · 智能客服</span>
+        <van-icon name="cross" size="18" @click="showChat = false" />
+      </div>
+
+      <div class="chat-body" ref="msgBody">
+        <div v-if="messages.length === 0" class="chat-empty">
+          <p>👋 你好,我是许愿树小助手"小愿"</p>
+          <p>有什么关于许愿的问题都可以问我~</p>
+        </div>
+        <div
+          v-for="(msg, i) in messages"
+          :key="i"
+          :class="['chat-msg', msg.role]"
+        >
+          <div class="chat-bubble">{{ msg.content }}</div>
+        </div>
+        <div v-if="sending" class="chat-msg ai">
+          <div class="chat-bubble typing">...</div>
+        </div>
+      </div>
+
+      <div class="chat-footer">
+        <van-field
+          v-model="input"
+          placeholder="输入你的问题..."
+          :border="false"
+          @keypress.enter="send"
+        />
+        <van-button
+          type="primary"
+          size="small"
+          round
+          :disabled="!input.trim() || sending"
+          @click="send"
+        >
+          发送
+        </van-button>
+      </div>
+    </div>
+  </van-popup>
+</template>
+
+<script setup lang="ts">
+import { ref, nextTick, watch } from 'vue'
+import { sendMessage, fetchHistory } from '@/api/chat'
+
+const props = defineProps<{
+  treeId?: number
+}>()
+
+interface ChatMsg {
+  role: 'user' | 'ai'
+  content: string
+}
+
+const showChat = ref(false)
+const input = ref('')
+const sending = ref(false)
+const messages = ref<ChatMsg[]>([])
+const msgBody = ref<HTMLElement | null>(null)
+const historyLoaded = ref(false)
+
+async function loadHistory() {
+  try {
+    const list = await fetchHistory(props.treeId)
+    if (list.length > 0) {
+      messages.value = list.map((m) => ({ role: m.role, content: m.content }))
+    }
+  } catch {
+    // 加载失败不影响使用
+  }
+  historyLoaded.value = true
+}
+
+function toggleChat() {
+  showChat.value = !showChat.value
+}
+
+function scrollToBottom() {
+  nextTick(() => {
+    const el = msgBody.value
+    if (el) {
+      el.scrollTop = el.scrollHeight
+    }
+  })
+}
+
+async function send() {
+  const text = input.value.trim()
+  if (!text || sending.value) return
+
+  messages.value.push({ role: 'user', content: text })
+  input.value = ''
+  scrollToBottom()
+
+  sending.value = true
+  try {
+    const reply = await sendMessage(text, props.treeId)
+    messages.value.push({ role: 'ai', content: reply })
+  } catch {
+    messages.value.push({ role: 'ai', content: '抱歉,网络出了问题,请稍后再试~' })
+  } finally {
+    sending.value = false
+    scrollToBottom()
+  }
+}
+
+watch(showChat, (val) => {
+  if (val) {
+    if (!historyLoaded.value) {
+      loadHistory()
+    }
+    scrollToBottom()
+  }
+})
+</script>
+
+<style scoped>
+.chat-fab {
+  position: fixed;
+  right: 16px;
+  bottom: 80px;
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #07c160, #05a650);
+  box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 999;
+  cursor: pointer;
+  transition: transform 0.2s;
+}
+.chat-fab:active {
+  transform: scale(0.9);
+}
+
+.chat-panel {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background: #f7f8fa;
+}
+
+.chat-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 14px 16px;
+  background: #fff;
+  border-bottom: 1px solid #ebedf0;
+  flex-shrink: 0;
+}
+.chat-title {
+  font-size: 16px;
+  font-weight: 600;
+}
+
+.chat-body {
+  flex: 1;
+  overflow-y: auto;
+  padding: 12px 14px;
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.chat-empty {
+  text-align: center;
+  color: #999;
+  margin-top: 40px;
+  font-size: 14px;
+  line-height: 2;
+}
+
+.chat-msg {
+  display: flex;
+  max-width: 80%;
+}
+.chat-msg.user {
+  align-self: flex-end;
+}
+.chat-msg.ai {
+  align-self: flex-start;
+}
+
+.chat-bubble {
+  padding: 10px 14px;
+  border-radius: 16px;
+  font-size: 14px;
+  line-height: 1.6;
+  word-break: break-word;
+}
+.chat-msg.user .chat-bubble {
+  background: #07c160;
+  color: #fff;
+  border-bottom-right-radius: 4px;
+}
+.chat-msg.ai .chat-bubble {
+  background: #fff;
+  color: #333;
+  border-bottom-left-radius: 4px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
+}
+.chat-bubble.typing {
+  color: #999;
+  padding: 10px 18px;
+}
+
+.chat-footer {
+  display: flex;
+  align-items: center;
+  padding: 8px 12px;
+  background: #fff;
+  border-top: 1px solid #ebedf0;
+  gap: 8px;
+  flex-shrink: 0;
+}
+.chat-footer :deep(.van-cell) {
+  flex: 1;
+  background: #f5f5f5;
+  border-radius: 20px;
+  padding: 6px 14px;
+}
+</style>

+ 3 - 0
wishing-tree-h5/src/views/TreeDetailView.vue

@@ -47,6 +47,8 @@
     </div>
 
     <van-loading v-else class="loading" />
+
+    <ChatWidget :tree-id="tree?.id" />
   </div>
 </template>
 
@@ -57,6 +59,7 @@ import { useLocationStore } from '@/stores/location'
 import { fetchTreeDetail } from '@/api/tree'
 import { fetchTreeWishes } from '@/api/wish'
 import WishCard from '@/components/WishCard.vue'
+import ChatWidget from '@/components/ChatWidget.vue'
 import { getTreeGradient, getTreeEmoji } from '@/utils/theme'
 import type { WishingTree } from '@/mock/data'
 import type { Wish } from '@/mock/data'

BIN
wishing-tree-h5/wish.zip