diff --git a/pages/home/index/index.vue b/pages/home/index/index.vue index 14b6ea5..39fc0e9 100644 --- a/pages/home/index/index.vue +++ b/pages/home/index/index.vue @@ -159,6 +159,11 @@ {{ message.displayTime }} + + + {{ message.message }} + + @@ -258,6 +265,8 @@ class="message-content" :class="{ 'message-content-width': !message.isLoading, + 'message-content-structured': + getStructuredMessageBlocks(message).length, }" > @@ -266,7 +275,53 @@ - + + + + {{ block.value }} + + + + + {{ + block.name + }} + + + + @@ -322,6 +377,8 @@ class="message-content" :class="{ 'message-content-width': !message.isLoading, + 'message-content-structured': + getStructuredMessageBlocks(message).length, }" > @@ -329,6 +386,53 @@ + + + + {{ block.value }} + + + + + {{ + block.name + }} + + + + @@ -1080,6 +1184,7 @@ export default { if (res.succeed) { console.log("hotQuestionsDetail.....", res.data); this.currentDMid = res.data.dmId; + const messageId = res.data.messageId; const data = res.data.entityInfo; // 从消息列表中移除加载状态消息 @@ -1087,12 +1192,19 @@ export default { (msg) => !msg.isLoading, ); + const answerTypeLabel = data.detailedExplanation; // 文字 + const imageUrl = data.imageUrl; // 图片 + const nearbyPaths = data.nearbyPaths; // 文件 + const message = `{\r\n "answerTypeLabel": ${JSON.stringify( + answerTypeLabel || "", + )},\r\n "imageUrl": ${JSON.stringify( + imageUrl || "", + )},\r\n "nearbyPaths": ${JSON.stringify(nearbyPaths || "")}\r\n}`; + // 创建AI回复消息对象 const aiMessage = { - id: - data.conversationId || - Math.random().toString(36).substring(2, 15), - message: data.detailedExplanation, + id: messageId || Math.random().toString(36).substring(2, 15), + message: message, sendDate: "", isSend: true, isRead: false, @@ -1160,6 +1272,107 @@ export default { this.handleGetConversationDetail(); }, + // 解析结构化消息块 + getStructuredMessageBlocks(message) { + const rawMessage = + (message && (message.rawMessage || message.message)) || ""; + if (!rawMessage || typeof rawMessage !== "string") return []; + + let parsed = null; + try { + parsed = JSON.parse(rawMessage); + } catch (error) { + return []; + } + if (!parsed || typeof parsed !== "object") return []; + + const answerTypeLabel = (parsed.answerTypeLabel || "").trim(); + const imageUrl = parsed.imageUrl || ""; + const nearbyPaths = parsed.nearbyPaths || ""; + + if (!answerTypeLabel && !imageUrl && !nearbyPaths) return []; + + const blocks = []; + if (answerTypeLabel) { + blocks.push({ type: "text", value: answerTypeLabel }); + } + + this.normalizeResourceList(imageUrl).forEach((item) => { + const url = this.formatMessageResourceUrl(item); + if (url) blocks.push({ type: "image", url }); + }); + + this.normalizeResourceList(nearbyPaths).forEach((item) => { + const url = this.formatMessageResourceUrl(item); + if (!url) return; + blocks.push({ + type: "file", + url, + name: this.getFileNameFromPath(item), + }); + }); + + return blocks; + }, + normalizeResourceList(value) { + if (!value) return []; + if (Array.isArray(value)) { + return value.map((item) => String(item || "").trim()).filter(Boolean); + } + return String(value) + .split(/[\n,|]/) + .map((item) => item.trim()) + .filter(Boolean); + }, + formatMessageResourceUrl(path) { + if (!path) return ""; + const currentPath = String(path).trim(); + if (!currentPath) return ""; + if ( + currentPath.startsWith("http://") || + currentPath.startsWith("https://") || + currentPath.startsWith("blob:") + ) { + return currentPath; + } + const cleanPath = currentPath.replace(/\\/g, "/"); + const cleanBaseUrl = (this.baseUrl || "") + .replace(/\\/g, "/") + .replace(/\/$/, ""); + if (!cleanBaseUrl) return cleanPath; + if (cleanPath.startsWith("/")) return `${cleanBaseUrl}${cleanPath}`; + return `${cleanBaseUrl}/${cleanPath}`; + }, + getFileNameFromPath(path) { + const cleanPath = String(path || "") + .replace(/\\/g, "/") + .split("?")[0]; + const segments = cleanPath.split("/"); + const fileName = segments[segments.length - 1] || "下载文件"; + try { + return decodeURIComponent(fileName); + } catch (error) { + return fileName; + } + }, + previewMessageImage(url) { + if (!url) return; + uni.previewImage({ + current: url, + urls: [url], + }); + }, + downloadMessageFile(url) { + if (!url) return; + if (typeof window !== "undefined" && window.open) { + window.open(url, "_blank"); + return; + } + uni.downloadFile({ + url, + }); + }, + // 处理消息内容,如果是JSON格式则解析并格式化 processMessageContent(message) { return processChatMessageContent(message); @@ -1259,7 +1472,7 @@ export default { onScrollToUpper() { console.log("触发上拉刷新"); - if (!this.currentDMid) return; + // if (!this.currentDMid) return; if (this.isLiveAgentChat) return; // 如果已经没有更多数据或正在切换对话或当前对话为空(新建对话),不再触发上拉刷新 @@ -1307,8 +1520,15 @@ export default { // 回答反馈:点赞/点踩(统一调用刷新&上一页回退逻辑) handleFeedback(message, isHelp) { this.$u.api.ModifyStatus({ id: message.id, isHelp }).then((res) => { - if (!res.succeed) return; - this.$u.toast("操作成功"); + if (!res.succeed) { + uni.showToast({ + title: res.error || "操作失败", + icon: "none", + }); + + return; + } + // this.$u.toast("操作成功"); // 去除操作成功提示 // 刷新当前页;若空则自动回退上一页并刷新 this.refreshPageWithFallback(); }); @@ -1685,6 +1905,12 @@ export default { color: #999999; padding: 20rpx; } + .message-tip { + text-align: center; + font-size: 24rpx; + color: #999999; + padding-bottom: 40rpx; + } .message-left, .message-right { @@ -1720,6 +1946,12 @@ export default { font-size: 28rpx; line-height: 1.5; + &.message-content-structured { + background-color: transparent; + padding: 0; + border-radius: 0; + } + /* 加载动画样式 */ .loading-dots { display: flex; @@ -1792,6 +2024,89 @@ export default { font-size: 24rpx; } } + + .structured-message { + .structured-item + .structured-item { + margin-top: 16rpx; + } + + .structured-item { + max-width: 100%; + } + + .structured-item-text { + .structured-text { + display: inline-block; + background-color: #ffffff; + color: #333333; + font-size: 28rpx; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + padding: 20rpx 24rpx; + border-radius: 0 16rpx 16rpx 16rpx; + } + } + + .structured-text { + display: inline-block; + color: #333333; + font-size: 28rpx; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + } + + .structured-item-image { + .structured-image { + display: block; + max-width: 240rpx; + border-radius: 12rpx; + } + } + + .structured-image { + display: block; + max-width: 240rpx; + border-radius: 12rpx; + } + + .structured-item-file { + .structured-file { + display: flex; + align-items: center; + width: 500rpx; + max-width: 100%; + padding: 24rpx; + background-color: #ffffff; + border-radius: 16rpx; + box-sizing: border-box; + + .structured-file-icon { + width: 48rpx; + height: 62rpx; + flex-shrink: 0; + margin-right: 20rpx; + } + + .structured-file-name { + flex: 1; + font-size: 28rpx; + color: #333333; + word-break: break-all; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; + } + + .structured-file-download { + flex-shrink: 0; + margin-left: 20rpx; + } + } + } + } } .message-content-width { diff --git a/pages/login/login/index.vue b/pages/login/login/index.vue index 0dd48cd..9c9a016 100644 --- a/pages/login/login/index.vue +++ b/pages/login/login/index.vue @@ -106,7 +106,7 @@ 图形验证码 - ({ ...item, + rawMessage: item && item.message, message: processChatMessageContent(item && item.message), }));