This commit is contained in:
JiXinHui 2026-03-19 15:36:14 +08:00
commit e14818a399
4 changed files with 326 additions and 10 deletions

View File

@ -159,6 +159,11 @@
{{ message.displayTime }}
</view>
<!-- 提示 -->
<view class="message-tip" v-if="message.sendUserType === 3">
{{ message.message }}
</view>
<!-- 转人工选择卡片独立消息不属于AI回复不展示头像/反馈 -->
<view
class="message-transfer"
@ -226,7 +231,9 @@
<!-- 0 用户消息 -->
<view
class="message-right"
v-else-if="message.interactMode === 0"
v-else-if="
message.interactMode === 0 && message.sendUserType !== 3
"
:id="'msg-' + message.id"
>
<view class="message-content">
@ -258,6 +265,8 @@
class="message-content"
:class="{
'message-content-width': !message.isLoading,
'message-content-structured':
getStructuredMessageBlocks(message).length,
}"
>
<!-- 加载动画 -->
@ -266,7 +275,53 @@
<view class="dot"></view>
<view class="dot"></view>
</view>
<!-- 正常消息内容 -->
<view
v-else-if="getStructuredMessageBlocks(message).length"
class="structured-message"
>
<view
v-for="(block, blockIndex) in getStructuredMessageBlocks(
message,
)"
:key="`${message.id || index}-block-${blockIndex}`"
class="structured-item"
:class="`structured-item-${block.type}`"
>
<text
v-if="block.type === 'text'"
class="structured-text"
>
{{ block.value }}
</text>
<image
v-else-if="block.type === 'image'"
class="structured-image"
:src="block.url"
mode="widthFix"
@click="previewMessageImage(block.url)"
></image>
<view
v-else-if="block.type === 'file'"
class="structured-file"
@click="downloadMessageFile(block.url)"
>
<image
class="structured-file-icon"
src="/static/common/images/icon-file.png"
mode="scaleToFill"
></image>
<text class="structured-file-name">{{
block.name
}}</text>
<u-icon
name="download"
size="36"
color="#666666"
class="structured-file-download"
></u-icon>
</view>
</view>
</view>
<markdown-viewer v-else :content="message.message" />
</view>
</view>
@ -322,6 +377,8 @@
class="message-content"
:class="{
'message-content-width': !message.isLoading,
'message-content-structured':
getStructuredMessageBlocks(message).length,
}"
>
<view v-if="message.isLoading" class="loading-dots">
@ -329,6 +386,53 @@
<view class="dot"></view>
<view class="dot"></view>
</view>
<view
v-else-if="getStructuredMessageBlocks(message).length"
class="structured-message"
>
<view
v-for="(block, blockIndex) in getStructuredMessageBlocks(
message,
)"
:key="`${message.id || index}-teacher-block-${blockIndex}`"
class="structured-item"
:class="`structured-item-${block.type}`"
>
<text
v-if="block.type === 'text'"
class="structured-text"
>
{{ block.value }}
</text>
<image
v-else-if="block.type === 'image'"
class="structured-image"
:src="block.url"
mode="widthFix"
@click="previewMessageImage(block.url)"
></image>
<view
v-else-if="block.type === 'file'"
class="structured-file"
@click="downloadMessageFile(block.url)"
>
<image
class="structured-file-icon"
src="/static/common/images/icon-file.png"
mode="scaleToFill"
></image>
<text class="structured-file-name">{{
block.name
}}</text>
<u-icon
name="download"
size="36"
color="#666666"
class="structured-file-download"
></u-icon>
</view>
</view>
</view>
<markdown-viewer v-else :content="message.message" />
</view>
</view>
@ -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 {

View File

@ -106,7 +106,7 @@
<text>图形验证码</text>
</view>
<view class="input-wrapper">
<inputx
<input
type="text"
class="form-input"
placeholder="请输入验证码"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -164,6 +164,7 @@ export function processChatMessageContent(message) {
export function sortChatMessages(list = []) {
const processedList = (list || []).map((item) => ({
...item,
rawMessage: item && item.message,
message: processChatMessageContent(item && item.message),
}));