|
|
@@ -11,7 +11,6 @@ import { Post } from "../../../../lib/request/method";
|
|
|
import { upload } from "../../../../lib/request/upload";
|
|
|
import dayjs from "dayjs";
|
|
|
|
|
|
-// sendType映射: 1-患者 2-医生 3-系统 4-AI
|
|
|
const sendTypeMap: Record<string, "user" | "agent" | "human" | "system"> = {
|
|
|
"1": "user", // 患者
|
|
|
"2": "human", // 医生
|
|
|
@@ -31,166 +30,21 @@ function calculateSafeBottomRpx(): number {
|
|
|
return (750 / systemInfo.windowWidth) * safeBottom;
|
|
|
}
|
|
|
|
|
|
-// 判断消息是否为真正的咨询结束消息(排除30分钟提醒消息)
|
|
|
function isConsultEndMessage(msg: ConsultMessage): boolean {
|
|
|
if (msg.sendType !== "3" || !msg.messageContent) {
|
|
|
return false;
|
|
|
}
|
|
|
const content = msg.messageContent;
|
|
|
- // 排除30分钟提醒消息(包含"30分钟"和"5分钟后")
|
|
|
const isReminder =
|
|
|
content.includes("30分钟") &&
|
|
|
(content.includes("5分钟后") || content.includes("将在"));
|
|
|
- // 判断是否为真正的咨询结束消息
|
|
|
const isRealEnd = content.includes("咨询结束") && !isReminder;
|
|
|
return isRealEnd;
|
|
|
}
|
|
|
|
|
|
-// 转义 HTML 特殊字符
|
|
|
-function escapeHtml(text: string): string {
|
|
|
- return text
|
|
|
- .replace(/&/g, "&")
|
|
|
- .replace(/</g, "<")
|
|
|
- .replace(/>/g, ">")
|
|
|
- .replace(/"/g, """)
|
|
|
- .replace(/'/g, "'");
|
|
|
-}
|
|
|
-
|
|
|
-// 将 Markdown 格式文本转换为 HTML(支持基本的 Markdown 语法)
|
|
|
-function parseMarkdown(text: string): string {
|
|
|
- if (!text) return "";
|
|
|
-
|
|
|
- // 先处理代码块(三个反引号),提取出来避免被其他规则匹配
|
|
|
- const codeBlocks: string[] = [];
|
|
|
- let codeBlockIndex = 0;
|
|
|
- let html = text.replace(/```([\s\S]*?)```/g, (_, code) => {
|
|
|
- const placeholder = `__CODE_BLOCK_${codeBlockIndex}__`;
|
|
|
- codeBlocks[codeBlockIndex] = escapeHtml(code);
|
|
|
- codeBlockIndex++;
|
|
|
- return placeholder;
|
|
|
- });
|
|
|
-
|
|
|
- // 处理行内代码(一个反引号),也要先提取出来
|
|
|
- const inlineCodes: string[] = [];
|
|
|
- let inlineCodeIndex = 0;
|
|
|
- html = html.replace(/`([^`\n]+)`/g, (_, code) => {
|
|
|
- const placeholder = `__INLINE_CODE_${inlineCodeIndex}__`;
|
|
|
- inlineCodes[inlineCodeIndex] = escapeHtml(code);
|
|
|
- inlineCodeIndex++;
|
|
|
- return placeholder;
|
|
|
- });
|
|
|
-
|
|
|
- // 先处理列表(在处理加粗之前,避免 split 时分割 strong 标签)
|
|
|
- // 处理列表:支持无序列表(以 - 或 * 开头的行)
|
|
|
- const lines = html.split("\n");
|
|
|
- let inList = false;
|
|
|
- let result: string[] = [];
|
|
|
-
|
|
|
- for (let i = 0; i < lines.length; i++) {
|
|
|
- const line = lines[i];
|
|
|
- // 匹配以空格+减号或星号开头的行(列表项)
|
|
|
- const listMatch = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
|
-
|
|
|
- if (listMatch) {
|
|
|
- if (!inList) {
|
|
|
- result.push("<ul>");
|
|
|
- inList = true;
|
|
|
- }
|
|
|
- result.push(`<li>${listMatch[2]}</li>`);
|
|
|
- } else {
|
|
|
- if (inList) {
|
|
|
- result.push("</ul>");
|
|
|
- inList = false;
|
|
|
- }
|
|
|
- if (line.trim()) {
|
|
|
- result.push(line);
|
|
|
- } else {
|
|
|
- result.push("<br>");
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (inList) {
|
|
|
- result.push("</ul>");
|
|
|
- }
|
|
|
-
|
|
|
- html = result.join("\n");
|
|
|
-
|
|
|
- // 处理加粗:两个※※(优先处理,避免与单个※冲突)
|
|
|
- // 使用 span 标签配合 style,确保是行内元素
|
|
|
- html = html.replace(/※※([^\n]+?)※※/g, (_, content) => {
|
|
|
- return `<span style="font-weight:bold;display:inline;">${escapeHtml(content.trim())}</span>`;
|
|
|
- });
|
|
|
- // 处理加粗:两个星号 **
|
|
|
- html = html.replace(/\*\*([^\n]+?)\*\*/g, (_, content) => {
|
|
|
- return `<span style="font-weight:bold;display:inline;">${escapeHtml(content.trim())}</span>`;
|
|
|
- });
|
|
|
-
|
|
|
- // 处理斜体:一个※(但要排除已经是加粗标记的)
|
|
|
- html = html.replace(/(?<!※)※(?!※)([^※\n]+?)※(?!※)/g, (_, content) => {
|
|
|
- return `<span style="font-style:italic;display:inline;">${escapeHtml(content.trim())}</span>`;
|
|
|
- });
|
|
|
- // 处理斜体:一个星号 *(但要排除已经是加粗标记的)
|
|
|
- html = html.replace(/(?<!\*)\*(?!\*)([^\*\n]+?)\*(?!\*)/g, (_, content) => {
|
|
|
- return `<span style="font-style:italic;display:inline;">${escapeHtml(content.trim())}</span>`;
|
|
|
- });
|
|
|
-
|
|
|
- // 恢复行内代码
|
|
|
- for (let i = 0; i < inlineCodes.length; i++) {
|
|
|
- html = html.replace(`__INLINE_CODE_${i}__`, `<code>${inlineCodes[i]}</code>`);
|
|
|
- }
|
|
|
-
|
|
|
- // 恢复代码块
|
|
|
- for (let i = 0; i < codeBlocks.length; i++) {
|
|
|
- html = html.replace(`__CODE_BLOCK_${i}__`, `<pre><code>${codeBlocks[i]}</code></pre>`);
|
|
|
- }
|
|
|
-
|
|
|
- // 转义剩余文本中的 HTML 特殊字符(但保留已经生成的 HTML 标签)
|
|
|
- const tagPlaceholders: string[] = [];
|
|
|
- let tagIndex = 0;
|
|
|
- // 现在使用 span 标签,所以需要匹配 span 和 code、pre、ul、li、br
|
|
|
- html = html.replace(/<(\/?)(span|code|pre|ul|li|br)[^>]*>/gi, (match) => {
|
|
|
- const placeholder = `__TAG_${tagIndex}__`;
|
|
|
- tagPlaceholders[tagIndex] = match;
|
|
|
- tagIndex++;
|
|
|
- return placeholder;
|
|
|
- });
|
|
|
-
|
|
|
- // 转义剩余的 HTML 特殊字符
|
|
|
- html = escapeHtml(html);
|
|
|
-
|
|
|
- // 恢复 HTML 标签
|
|
|
- for (let i = 0; i < tagPlaceholders.length; i++) {
|
|
|
- html = html.replace(`__TAG_${i}__`, tagPlaceholders[i]);
|
|
|
- }
|
|
|
-
|
|
|
- // 统一清理所有 HTML 标签之间的空白字符
|
|
|
- // 关键:加粗标签后绝对不能有任何空白字符,包括换行符、空格、制表符等
|
|
|
-
|
|
|
- // 第一步:清理所有标签之间的空白
|
|
|
- html = html.replace(/>[\s\n\r\t]+</g, "><");
|
|
|
-
|
|
|
- // 第二步:特别处理加粗标签 - 移除后面所有空白字符(包括换行符)
|
|
|
- html = html.replace(/(<\/span[^>]*style="[^"]*font-weight:bold[^"]*"[^>]*>)[\s\n\r\t\u00A0\u2000-\u200B\u2028\u2029\u3000]+/g, "$1");
|
|
|
-
|
|
|
- // 第三步:清理其他 span 标签前后的空白
|
|
|
- html = html.replace(/(<\/span[^>]*>)[\s\n\r\t]+/g, "$1");
|
|
|
- html = html.replace(/[\s\n\r\t]+(<span[^>]*>)/g, "$1");
|
|
|
-
|
|
|
- // 第四步:清理其他标签前后的空白
|
|
|
- html = html.replace(/>[\s\n\r\t]+/g, ">");
|
|
|
- html = html.replace(/[\s\n\r\t]+</g, "<");
|
|
|
-
|
|
|
- // 第五步:将换行符转换为 <br>(加粗标签后的换行已经被移除了)
|
|
|
- html = html.replace(/\n/g, "<br>");
|
|
|
-
|
|
|
- // 第六步:最后再次确保加粗标签后没有任何空白字符
|
|
|
- html = html.replace(/(<\/span[^>]*style="[^"]*font-weight:bold[^"]*"[^>]*>)[\s\u00A0\u2000-\u200B\u2028\u2029\u3000]+/g, "$1");
|
|
|
-
|
|
|
- return html;
|
|
|
-}
|
|
|
+// Markdown 转换功能已移除,直接显示原始文本
|
|
|
|
|
|
-// 获取的聊天消息为ConsultMessage格式 提取为一个公共的方法
|
|
|
+// 获取的聊天消息为ConsultMessage格式
|
|
|
function transformMessage(item: AnyObject): ConsultMessage {
|
|
|
const sender = sendTypeMap[item.sendType];
|
|
|
return {
|
|
|
@@ -237,14 +91,7 @@ Component({
|
|
|
if (res.data && res.data.length > 0) {
|
|
|
messages = res.data.map((item: AnyObject) => {
|
|
|
const msg = transformMessage(item);
|
|
|
- // 对 AI 和医生的文本消息进行 Markdown 解析
|
|
|
- if (
|
|
|
- (msg.sender === "agent" || msg.sender === "human") &&
|
|
|
- msg.messageType === "1" &&
|
|
|
- msg.messageContent
|
|
|
- ) {
|
|
|
- msg.messageContent = parseMarkdown(msg.messageContent);
|
|
|
- }
|
|
|
+ // 不再进行 Markdown 转换,直接使用原始文本
|
|
|
return msg;
|
|
|
});
|
|
|
}
|
|
|
@@ -256,7 +103,7 @@ Component({
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 检查历史消息中是否有真正的咨询结束消息(排除30分钟提醒消息)
|
|
|
+ // 检查历史消息中是否有真正的咨询结束消息
|
|
|
const hasEndMessage = messages.some((msg: ConsultMessage) =>
|
|
|
isConsultEndMessage(msg)
|
|
|
);
|
|
|
@@ -276,23 +123,16 @@ Component({
|
|
|
});
|
|
|
console.log("baseBottom==输入框的位置", baseBottom);
|
|
|
this.triggerEvent("boxBottom", { inputBoxBottom: baseBottom });
|
|
|
- // 监听键盘高度变化事件
|
|
|
- // 每次键盘高度变化时直接更新位置
|
|
|
const kbHandler = (res: any) => {
|
|
|
const height = res?.height ?? 0;
|
|
|
-
|
|
|
+
|
|
|
// 键盘收起时,直接更新位置
|
|
|
if (height === 0) {
|
|
|
this._updateInputPosition(0);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- // 键盘弹起时的处理:
|
|
|
- // 1. 如果输入框已聚焦,直接更新位置
|
|
|
- // 2. 如果输入框未聚焦但 keepFocus 为 true,说明正在等待聚焦(第一次打开的情况),也应该更新位置
|
|
|
- // 这样可以解决鸿蒙系统第一次打开时键盘先弹起但输入框未聚焦的问题
|
|
|
+
|
|
|
if (this.data.inputFocus || this.data.keepFocus) {
|
|
|
- // 直接更新位置,每次键盘高度变化时都更新
|
|
|
this._updateInputPosition(height);
|
|
|
}
|
|
|
};
|
|
|
@@ -300,20 +140,17 @@ Component({
|
|
|
wx.onKeyboardHeightChange?.(kbHandler);
|
|
|
(this as any)._kbHandler = kbHandler;
|
|
|
|
|
|
- // 渲染完成后再触发一次聚焦,确保键盘弹起
|
|
|
this._ensureFocus();
|
|
|
-
|
|
|
- // 鸿蒙系统兼容:延迟检查键盘高度,确保第一次打开时能正确更新位置
|
|
|
- // 在 _ensureFocus 延迟聚焦之后,再延迟一点时间检查键盘高度
|
|
|
+
|
|
|
const systemInfo = wx.getSystemInfoSync();
|
|
|
- // 判断是否是鸿蒙系统:通过 system 字段判断,鸿蒙系统通常包含 "HarmonyOS"
|
|
|
- const isHarmonyOS = systemInfo.system && systemInfo.system.toLowerCase().includes('harmony');
|
|
|
+ const isHarmonyOS =
|
|
|
+ systemInfo.system &&
|
|
|
+ systemInfo.system.toLowerCase().includes("harmony");
|
|
|
console.log("isHarmonyOS==是否是鸿蒙系统", isHarmonyOS);
|
|
|
const delayTime = isHarmonyOS ? 300 : 0;
|
|
|
-
|
|
|
+
|
|
|
if (delayTime > 0) {
|
|
|
setTimeout(() => {
|
|
|
- // 如果此时键盘高度已记录但位置未更新,强制更新一次
|
|
|
if (this.data._keyboardHeight > 0 && this.data.keepFocus) {
|
|
|
this._updateInputPosition(this.data._keyboardHeight);
|
|
|
}
|
|
|
@@ -330,16 +167,13 @@ Component({
|
|
|
if ((this as any)._kbHandler) {
|
|
|
wx.offKeyboardHeightChange?.((this as any)._kbHandler);
|
|
|
}
|
|
|
- // 清理轮询定时器
|
|
|
this._stopPolling();
|
|
|
},
|
|
|
},
|
|
|
methods: {
|
|
|
- // 滚动到底部-
|
|
|
_scrollToBottom() {
|
|
|
this.triggerEvent("scroll", { id: "bottom" });
|
|
|
},
|
|
|
- // 收起键盘并更新位置
|
|
|
_hideKeyboardAndUpdatePosition() {
|
|
|
wx.hideKeyboard?.();
|
|
|
this.setData({
|
|
|
@@ -348,40 +182,31 @@ Component({
|
|
|
});
|
|
|
this._updateInputPosition(0);
|
|
|
},
|
|
|
- // 统一的位置更新方法
|
|
|
_updateInputPosition(keyboardHeight: number) {
|
|
|
const systemInfo = wx.getSystemInfoSync();
|
|
|
const rpx2px = systemInfo.windowWidth / 750;
|
|
|
|
|
|
- // 键盘高度是 px,转换为 rpx
|
|
|
const keyboardHeightRpx =
|
|
|
keyboardHeight > 0 ? keyboardHeight / rpx2px : 0;
|
|
|
|
|
|
- // 计算输入框底部位置
|
|
|
- // 键盘展开时:面板紧贴键盘(bottom = 键盘高度),确保没有空隙
|
|
|
- // 键盘收起时:保留 tabbar 距离(baseInputBottom 已包含安全区 + 100rpx tabbar)
|
|
|
const nextBottom =
|
|
|
keyboardHeight > 0 ? keyboardHeightRpx : this.data.baseInputBottom;
|
|
|
|
|
|
// 避免重复更新相同位置(容差1rpx)
|
|
|
if (Math.abs(nextBottom - this.data.inputBoxBottom) < 1) {
|
|
|
- // 位置已经正确,只更新键盘高度记录
|
|
|
if (keyboardHeight !== this.data._keyboardHeight) {
|
|
|
this.setData({ _keyboardHeight: keyboardHeight });
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 更新位置和键盘高度
|
|
|
this.setData({
|
|
|
inputBoxBottom: nextBottom,
|
|
|
_keyboardHeight: keyboardHeight,
|
|
|
});
|
|
|
|
|
|
- // 通知父组件更新底部 padding
|
|
|
this.triggerEvent("boxBottom", { inputBoxBottom: nextBottom });
|
|
|
|
|
|
- // 键盘弹出时平滑滚动到底部
|
|
|
if (keyboardHeight > 0) {
|
|
|
this.triggerEvent("scroll", { id: "bottom" });
|
|
|
}
|
|
|
@@ -393,8 +218,6 @@ Component({
|
|
|
setTimeout(() => {
|
|
|
if (this.data.keepFocus) {
|
|
|
this.setData({ inputFocus: true });
|
|
|
- // 鸿蒙系统兼容:在聚焦后延迟检查键盘高度并更新位置
|
|
|
- // 这样可以确保即使键盘在聚焦前弹起,也能在聚焦后正确更新位置
|
|
|
setTimeout(() => {
|
|
|
if (this.data._keyboardHeight > 0 && this.data.inputFocus) {
|
|
|
this._updateInputPosition(this.data._keyboardHeight);
|
|
|
@@ -429,10 +252,8 @@ Component({
|
|
|
});
|
|
|
},
|
|
|
async _endConsult() {
|
|
|
- // 格式化日期时间,格式:MM-DD HH:mm:ss(与系统消息格式一致)
|
|
|
const endDate = dayjs().format("MM-DD HH:mm:ss");
|
|
|
|
|
|
- // 手动添加系统消息样式的结束时间
|
|
|
const consultId = wx.getStorageSync("consultId");
|
|
|
this._appendMessage({
|
|
|
id: `end-time-${Date.now()}`,
|
|
|
@@ -459,11 +280,8 @@ Component({
|
|
|
// 设置结束状态
|
|
|
this.setData({ consultEnded: true });
|
|
|
|
|
|
- // 更新本地存储:标记咨询已结束
|
|
|
wx.setStorageSync("consultEnded", true);
|
|
|
- //咨询结束之后 需要清除咨询id
|
|
|
wx.removeStorageSync("consultId");
|
|
|
- // 停止轮询最新消息
|
|
|
this._stopPolling();
|
|
|
|
|
|
// 收起键盘
|
|
|
@@ -474,7 +292,6 @@ Component({
|
|
|
const tabBarHeight = 100; // rpx
|
|
|
const normalBottom = tabBarHeight + safeBottomRpx;
|
|
|
|
|
|
- // 通知父组件重置 paddingBottom,避免菜单下方有大的距离
|
|
|
this.triggerEvent("boxBottom", { inputBoxBottom: normalBottom });
|
|
|
|
|
|
// 通知父组件显示guide菜单组件
|
|
|
@@ -507,29 +324,22 @@ Component({
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 单行时保持最小高度,不更新
|
|
|
if (lineCount === 1) {
|
|
|
- // 只有当当前高度不是最小高度时才更新
|
|
|
if (this.data.textareaHeight !== minHeight) {
|
|
|
this.setData({ textareaHeight: minHeight });
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- // 多行时才动态计算高度
|
|
|
- // 字体大小 28rpx,行高 1.9,所以每行实际高度约为 28 * 1.9 ≈ 53rpx
|
|
|
const lineHeight = 53; // 每行高度(rpx)
|
|
|
- const padding = 24; // 上下 padding 总和(12rpx * 2 = 24rpx)
|
|
|
+ const padding = 24;
|
|
|
|
|
|
- // 计算高度:行数 * 行高 + 上下 padding
|
|
|
const calculatedHeight = lineCount * lineHeight + padding;
|
|
|
- // 限制在 minHeight 和 maxHeight 之间
|
|
|
const finalHeight = Math.max(
|
|
|
minHeight,
|
|
|
Math.min(maxHeight, calculatedHeight)
|
|
|
);
|
|
|
|
|
|
- // 只在高度确实需要变化时更新(阈值设为 3rpx,减少频繁更新和跳动)
|
|
|
if (Math.abs(this.data.textareaHeight - finalHeight) > 3) {
|
|
|
this.setData({ textareaHeight: finalHeight });
|
|
|
}
|
|
|
@@ -543,12 +353,9 @@ Component({
|
|
|
keepFocus: true,
|
|
|
});
|
|
|
|
|
|
- // 如果从 focus 事件获取到有效的键盘高度,立即更新位置
|
|
|
if (keyboardHeight > 0) {
|
|
|
this._updateInputPosition(keyboardHeight);
|
|
|
} else {
|
|
|
- // 如果键盘高度为0,但之前已经记录了键盘高度(可能是鸿蒙系统的问题)
|
|
|
- // 尝试使用已记录的键盘高度来更新位置
|
|
|
if (this.data._keyboardHeight > 0) {
|
|
|
// 延迟一点时间,等待键盘完全弹起后再更新
|
|
|
setTimeout(() => {
|
|
|
@@ -566,10 +373,8 @@ Component({
|
|
|
this.setData({
|
|
|
inputFocus: false,
|
|
|
});
|
|
|
-
|
|
|
- // 延迟恢复位置,确保键盘完全收起
|
|
|
+
|
|
|
setTimeout(() => {
|
|
|
- // 只有当前没有聚焦时,才恢复位置(避免在快速切换时出现问题)
|
|
|
if (!this.data.inputFocus) {
|
|
|
this._updateInputPosition(0);
|
|
|
}
|
|
|
@@ -578,13 +383,9 @@ Component({
|
|
|
|
|
|
// 启动轮询最新消息
|
|
|
_startPolling() {
|
|
|
- // 如果咨询已结束,不启动轮询
|
|
|
if (this.data.consultEnded) return;
|
|
|
- // 清除之前的轮询定时器
|
|
|
this._stopPolling();
|
|
|
- // 每5秒轮询一次
|
|
|
const timer = setInterval(() => {
|
|
|
- // 如果咨询已结束,停止轮询
|
|
|
if (this.data.consultEnded) {
|
|
|
this._stopPolling();
|
|
|
return;
|
|
|
@@ -593,7 +394,6 @@ Component({
|
|
|
}, 5000);
|
|
|
this.setData({ _pollTimer: timer });
|
|
|
},
|
|
|
- // 停止轮询最新消息
|
|
|
_stopPolling() {
|
|
|
if (this.data._pollTimer) {
|
|
|
clearInterval(this.data._pollTimer);
|
|
|
@@ -607,30 +407,19 @@ Component({
|
|
|
try {
|
|
|
// 获取最新消息
|
|
|
const res = await Post(`/consultManage/getLatestMsgs/${consultId}`);
|
|
|
- // 如果有最新消息要追加到消息列表
|
|
|
if (res.data && Array.isArray(res.data) && res.data.length > 0) {
|
|
|
const newMessages = res.data.map((item: AnyObject) => {
|
|
|
const msg = transformMessage(item);
|
|
|
- // 对 AI 和医生的文本消息进行 Markdown 解析
|
|
|
- if (
|
|
|
- (msg.sender === "agent" || msg.sender === "human") &&
|
|
|
- msg.messageType === "1" &&
|
|
|
- msg.messageContent
|
|
|
- ) {
|
|
|
- msg.messageContent = parseMarkdown(msg.messageContent);
|
|
|
- }
|
|
|
+ // 不再进行 Markdown 转换,直接使用原始文本
|
|
|
return msg;
|
|
|
});
|
|
|
const allMessages = [...this.data.messages, ...newMessages];
|
|
|
this.setData({ messages: allMessages });
|
|
|
|
|
|
- // 检查是否有真正的咨询结束消息(排除30分钟提醒消息)
|
|
|
const hasEndMessage = newMessages.some((msg: ConsultMessage) =>
|
|
|
isConsultEndMessage(msg)
|
|
|
);
|
|
|
|
|
|
- // 如果收到真正的咨询结束消息,更新状态并停止轮询
|
|
|
- // 注意:30分钟提醒消息不会触发此逻辑,会继续轮询等待真正的结束消息
|
|
|
if (hasEndMessage && !this.data.consultEnded) {
|
|
|
this.setData({ consultEnded: true });
|
|
|
wx.setStorageSync("consultEnded", true);
|
|
|
@@ -639,19 +428,16 @@ Component({
|
|
|
// 收起键盘
|
|
|
wx.hideKeyboard?.();
|
|
|
|
|
|
- // 重置底部位置为正常值(tabbar 高度 + 安全区高度)
|
|
|
const safeBottomRpx = calculateSafeBottomRpx();
|
|
|
const tabBarHeight = 100; // rpx
|
|
|
const normalBottom = tabBarHeight + safeBottomRpx;
|
|
|
|
|
|
- // 通知父组件重置 paddingBottom
|
|
|
this.triggerEvent("boxBottom", { inputBoxBottom: normalBottom });
|
|
|
|
|
|
// 通知父组件显示guide菜单组件
|
|
|
this.triggerEvent("consultEvent", { type: "end" });
|
|
|
}
|
|
|
|
|
|
- //如果有最新消息要滚动到底部
|
|
|
this._scrollToBottom();
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
@@ -674,11 +460,8 @@ Component({
|
|
|
messageType,
|
|
|
messageContent,
|
|
|
}).then(() => {
|
|
|
- // 发送成功后获取最新消息
|
|
|
- // 重置轮询定时器,避免短时间内重复调用
|
|
|
this._stopPolling();
|
|
|
this._getLatestMessages();
|
|
|
- // 重新启动轮询,避免重复调用
|
|
|
setTimeout(() => {
|
|
|
if (!this.data.consultEnded) {
|
|
|
this._startPolling();
|
|
|
@@ -694,11 +477,8 @@ Component({
|
|
|
},
|
|
|
async sendText() {
|
|
|
const text = this.data.inputText.trim();
|
|
|
- // 至少要有一个内容才能发送
|
|
|
if (!text) {
|
|
|
wx.showToast({ title: "发送内容不能为空", icon: "none" });
|
|
|
- // 如果键盘未展开(inputFocus 为 false),不应该聚焦输入框和展开键盘
|
|
|
- // 只显示提示即可
|
|
|
return;
|
|
|
}
|
|
|
const consultId = wx.getStorageSync("consultId");
|
|
|
@@ -712,7 +492,6 @@ Component({
|
|
|
messageType: "1",
|
|
|
messageContent: text,
|
|
|
});
|
|
|
- // 平滑滚动到底部,确保新消息可见
|
|
|
this._scrollToBottom();
|
|
|
// 发送信息
|
|
|
this._sendMessage("1", text);
|
|
|
@@ -721,35 +500,26 @@ Component({
|
|
|
const wasFocused = this.data.inputFocus;
|
|
|
const currentKeyboardHeight = this.data._keyboardHeight;
|
|
|
|
|
|
- // 清空输入框,保持键盘展开状态(只有在键盘已展开时才保持)
|
|
|
- // 同时重置高度为最小高度,确保立即恢复
|
|
|
this.setData({
|
|
|
inputText: "",
|
|
|
- textareaHeight: 80, // 重置为最小高度
|
|
|
+ textareaHeight: 80,
|
|
|
});
|
|
|
-
|
|
|
- // 如果之前键盘是展开的,保持键盘展开状态;如果键盘是收起的,不要聚焦
|
|
|
+
|
|
|
if (!wasFocused) {
|
|
|
- // 键盘未展开,不需要聚焦,保持当前状态即可
|
|
|
this.setData({
|
|
|
inputFocus: false,
|
|
|
keepFocus: false,
|
|
|
});
|
|
|
} else {
|
|
|
- // 键盘已展开,保持聚焦状态
|
|
|
- // 但不需要手动触发聚焦,因为已经聚焦了
|
|
|
- // 只需要确保键盘位置正确
|
|
|
if (currentKeyboardHeight > 0) {
|
|
|
this._updateInputPosition(currentKeyboardHeight);
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
async chooseImage() {
|
|
|
- // 保存当前焦点状态,选择图片后恢复
|
|
|
const wasFocused = this.data.inputFocus;
|
|
|
const currentKeyboardHeight = this.data._keyboardHeight;
|
|
|
|
|
|
- // 临时收起键盘,选择图片需要系统界面
|
|
|
this._hideKeyboardAndUpdatePosition();
|
|
|
|
|
|
try {
|
|
|
@@ -765,7 +535,6 @@ Component({
|
|
|
// 直接发送图片
|
|
|
await this._sendImageMessage(imagePath);
|
|
|
|
|
|
- // 如果之前键盘是展开的,选择图片后恢复键盘
|
|
|
if (wasFocused) {
|
|
|
wx.nextTick?.(() => {
|
|
|
setTimeout(() => {
|
|
|
@@ -781,12 +550,10 @@ Component({
|
|
|
});
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
- // 用户取消选择图片时不提示错误
|
|
|
if (error.errMsg && !error.errMsg.includes("cancel")) {
|
|
|
console.error("选择图片失败", error);
|
|
|
}
|
|
|
|
|
|
- // 取消时也恢复键盘(如果之前是展开的)
|
|
|
if (wasFocused) {
|
|
|
wx.nextTick?.(() => {
|
|
|
setTimeout(() => {
|
|
|
@@ -807,7 +574,6 @@ Component({
|
|
|
async _sendImageMessage(imagePath: string) {
|
|
|
const consultId = wx.getStorageSync("consultId");
|
|
|
try {
|
|
|
- // 先添加图片消息到界面(显示本地路径)
|
|
|
const messageId = `user-image-${Date.now()}`;
|
|
|
this._appendMessage({
|
|
|
id: messageId,
|
|
|
@@ -815,7 +581,7 @@ Component({
|
|
|
sender: "user",
|
|
|
sendType: "1",
|
|
|
messageType: "2",
|
|
|
- messageContent: imagePath, // 先用本地路径,上传后更新
|
|
|
+ messageContent: imagePath,
|
|
|
});
|
|
|
|
|
|
// 上传图片
|
|
|
@@ -838,7 +604,6 @@ Component({
|
|
|
messages[messageIndex].messageContent = imageUrl;
|
|
|
this.setData({ messages });
|
|
|
}
|
|
|
- // 平滑滚动到底部,确保新消息可见
|
|
|
this._scrollToBottom();
|
|
|
} catch (error: any) {
|
|
|
wx.showToast({
|
|
|
@@ -850,15 +615,14 @@ Component({
|
|
|
// 预览图片
|
|
|
previewImage(e: any) {
|
|
|
const currentUrl = e.currentTarget.dataset.url;
|
|
|
- // 获取所有图片消息的 URL 列表
|
|
|
const urls = this.data.messages
|
|
|
.filter(
|
|
|
(msg: ConsultMessage) => msg.messageType === "2" && msg.messageContent
|
|
|
)
|
|
|
.map((msg: ConsultMessage) => msg.messageContent!);
|
|
|
wx.previewImage({
|
|
|
- current: currentUrl, // 当前显示图片的链接
|
|
|
- urls: urls.length > 0 ? urls : [currentUrl], // 需要预览的图片链接列表
|
|
|
+ current: currentUrl,
|
|
|
+ urls: urls.length > 0 ? urls : [currentUrl],
|
|
|
fail: (err) => {
|
|
|
wx.showToast({
|
|
|
title: err?.errMsg || "预览图片失败",
|