Просмотр исходного кода

feat:添加专家咨询页面

wangsisi 2 недель назад
Родитель
Сommit
83b3e57349
1 измененных файлов с 588 добавлено и 1 удалено
  1. 588 1
      src/views/old_mini/chat_frame/consult.vue

+ 588 - 1
src/views/old_mini/chat_frame/consult.vue

@@ -2,13 +2,369 @@
     <div class="consult">
         <custom-header name="咨询专家"></custom-header>
         <div class="consult-content">
-            111
+            <!-- 聊天消息区域 -->
+            <div class="chat-messages" ref="messagesContainer">
+                <div v-for="(msg, index) in messages" :key="index" class="message" :class="msg.sender">
+                    <!-- 对方消息 -->
+                    <template v-if="msg.sender === 'received'">
+                        <!-- <div class="avatar">{{ msg.receiverName.charAt(0) }}</div> -->
+                        <el-avatar
+                            class="avatar"
+                            :size="40"
+                            :src="
+                                msg.receiverIcon ||
+                                'https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png'
+                            "
+                        />
+                        <img src="" alt="" />
+                        <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image' ,'card-bubble': msg.messageType === 'card'}">
+                            <!-- 文本消息 -->
+                            <div v-if="msg.messageType === 'text'" class="content">{{ msg.content }}</div>
+
+                            <!-- 图片消息 -->
+                            <div v-if="msg.messageType === 'image'" class="image-message">
+                                <img
+                                    :src="msg.content + resize"
+                                    @click="showImagePreview(msg.content)"
+                                    @load="handleImageLoad"
+                                    alt="图片"
+                                />
+                            </div>
+
+                            <!-- 对话样式消息 -->
+                            <div v-if="msg.messageType === 'report'" class="dialog-message" @click="handleReportClick(msg)">
+                                <template v-if="(msg.reportType || msg.content.reportType) === 'farm_report'">
+                                    <div class="report-title">{{ msg.title ||msg.content.title }}</div>
+                                    <div class="dialog-title">这是{{curRole == 2 ? '该农场' : '我'}}的果园情况,请查看~</div>
+                                    <img src="https://birdseye-img.sysuimars.com/birdseye-look-mini/share-report-bg.png" alt="" class="monitor-image" />
+                                </template>
+                                <template v-else>
+                                    <div class="dialog-title">{{ msg.title || msg.content.title}}</div>
+                                    <img src="@/assets/img/monitor/image.png" alt="" class="monitor-image" />
+                                </template>
+                            </div>
+
+                             <!-- 对话样式消息 -->
+                            <div v-if="msg.messageType === 'card'" class="card-message" @click="handleCardClick(msg)">
+                                <template v-if="(msg.cardType || msg.content.cardType) === 'quotation'">
+                                    <div class="card-title">向您发送了一张 服务报价单</div>
+                                    <img src="https://birdseye-img.sysuimars.com/temp/price.png" alt="" />
+                                </template>
+                                <template v-else>
+                                    <div class="card-title">{{ msg.title || msg.content.title }}</div>
+                                    <img :src="handleImgUrl(msg.coverUrl || msg.content.coverUrl)" alt="" />
+                                </template>
+                            </div>
+
+                            <!-- <div class="time">{{ msg.time }}</div> -->
+                        </div>
+                    </template>
+
+                    <!-- 我方消息 -->
+                    <template v-else>
+                        <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image','card-bubble': msg.messageType === 'card' || msg.messageType === 'report' }">
+                            <!-- 文本消息 -->
+                            <div v-if="msg.messageType === 'text'" class="content">{{ msg.content }}</div>
+
+                            <!-- 图片消息 -->
+                            <div v-if="msg.messageType === 'image'" class="image-message">
+                                <img
+                                    :src="msg.content + resize"
+                                    @click="showImagePreview(msg.content)"
+                                    @load="handleImageLoad"
+                                    alt="图片"
+                                />
+                            </div>
+
+                            <!-- 对话样式消息 -->
+                            <div v-if="msg.messageType === 'report'" class="dialog-message" @click="handleReportClick(msg)">
+                                <template v-if="(msg.reportType || msg.content.reportType) === 'farm_report'">
+                                    <div class="report-title">{{ msg.title ||msg.content.title }}</div>
+                                    <div class="dialog-title">这是{{curRole == 2 ? '该农场' : '我'}}果园情况,请查看~</div>
+                                    <img src="https://birdseye-img.sysuimars.com/birdseye-look-mini/share-report-bg.png" alt="" class="monitor-image" />
+                                </template>
+                                <template v-else>
+                                    <div class="dialog-title">{{ msg.title || msg.content.title}}</div>
+                                    <img src="@/assets/img/monitor/image.png" alt="" class="monitor-image" />
+                                </template>
+                            </div>
+
+                            <!-- 对话样式消息 -->
+                            <div v-if="msg.messageType === 'card'" class="card-message" @click="handleCardClick(msg)">
+                                <template v-if="(msg.cardType || msg.content.cardType) === 'quotation'">
+                                    <div class="card-title">向您发送了一张 服务报价单</div>
+                                    <img src="https://birdseye-img.sysuimars.com/temp/price.png" alt="" />
+                                </template>
+                                <template v-else>
+                                    <div class="card-title">{{ msg.title || msg.content.title }}</div>
+                                    <img :src="handleImgUrl(msg.coverUrl || msg.content.coverUrl)" alt="" />
+                                </template>
+                            </div>
+
+                            <!-- <div class="time">{{ msg.time }}</div> -->
+                        </div>
+                        <!-- <div class="avatar avatar-r">{{ msg.senderName.charAt(0) }}</div> -->
+                        <el-avatar
+                            class="avatar avatar-r"
+                            :size="40"
+                            :src="
+                                msg.senderIcon ||
+                                'https://birdseye-img.sysuimars.com/dinggou-mini/defalut-icon.png'
+                            "
+                        />
+                    </template>
+                </div>
+            </div>
+
+            <!-- 输入框区域 -->
+            <div class="input-area">
+                <div class="toolbar">
+                    <el-icon class="link" @click="startImageUpload"><Link /></el-icon>
+                    <input type="file" ref="fileInput" accept="image/*" style="display: none" @change="handleImageUpload" />
+                </div>
+
+                <input type="text" v-model="inputMessage" placeholder="请输入你想说的话~" @keyup.enter="sendTextMessage" />
+                <div class="send" @click="sendTextMessage">发送</div>
+            </div>
+
+            <!-- 图片预览模态框 -->
+            <div v-if="previewImage" class="image-preview" @click="previewImage = null">
+                <img :src="previewImage" alt="预览" />
+            </div>
         </div>
     </div>
 </template>
 
 <script setup>
+import { ref, onUnmounted, nextTick, watch, onActivated, onDeactivated } from "vue";
+import { useRouter ,useRoute} from "vue-router";
+import { base_img_url2 } from "@/api/config";
+import { getFileExt } from "@/utils/util";
+import UploadFile from "@/utils/upliadFile";
+import MqttClient from "@/plugins/MqttClient";
 import customHeader from "@/components/customHeader.vue";
+
+const resize = "?x-oss-process=image/resize,p_120/format,webp/quality,q_100";
+const router = useRouter();
+const route = useRoute();
+
+const props = defineProps({
+    text: {
+        type: String,
+        defalut: "",
+    },
+    img: {
+        type: String,
+        defalut: "",
+    },
+    userId: {
+        type: [String, Number],
+        defalut: "",
+    },
+});
+
+const emit = defineEmits(['update:name']);
+
+const curUserId = Number(localStorage.getItem("MINI_USER_ID"));
+const senderIcon = ref("");
+const receiverIcon = ref("");
+const receiverIdVal = ref(null);
+
+// 本地用户头像
+const localUserInfoIcon = (() => {
+    try {
+        const info = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
+        return info?.icon || "";
+    } catch (e) {
+        return "";
+    }
+})();
+
+// 初始化本地头像为默认发送者头像
+senderIcon.value = localUserInfoIcon;
+const nameVal = ref('');
+
+// 监听 nameVal 变化,传递给父组件
+watch(nameVal, (newVal) => {
+    emit('update:name', newVal);
+});
+
+// mqtt 连接
+const mqttClient = ref(null);
+const messagesContainer = ref(null);
+
+// 消息数据
+const messages = ref([]);
+
+// 输入相关
+const inputMessage = ref("");
+const fileInput = ref(null);
+const showEmojiPicker = ref(false);
+const emojis = ["😀", "😂", "😍", "👍", "👋", "🎉", "❤️", "🙏"];
+
+function handleImageLoad() {
+    scrollToBottom();
+}
+
+// 图片预览
+const previewImage = ref(null);
+
+const userId = ref(null);
+
+const handleCardClick = (msg) => {
+    router.push(msg.linkUrl || msg.content.linkUrl);
+}
+
+const handleImgUrl = (url) => {
+    if (url && url.includes('https://')) {
+        return url;
+    } else {
+        return base_img_url2 + url + resize;
+    }
+}
+
+// 图片处理
+const startImageUpload = () => {
+    fileInput.value.click();
+};
+
+const uploadFileObj = new UploadFile();
+
+const handleImageUpload = (event) => {
+    const file = event.target.files[0];
+    if (file) {
+        // 实际项目中应该上传到服务器,这里使用本地URL模拟
+        const miniUserId = localStorage.getItem("MINI_USER_ID");
+        let ext = getFileExt(file.name);
+        let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
+        let imageUrl = "";
+        uploadFileObj.put(key, file).then((resFilename) => {
+            imageUrl = base_img_url2 + resFilename;
+            sendImageMessage(imageUrl);
+        });
+    }
+};
+
+const showImagePreview = (imageUrl) => {
+    previewImage.value = imageUrl;
+};
+
+// 发送图片消息
+const sendImageMessage = (thumbnailUrl) => {
+    const message = {
+        sender: "sent",
+        messageType: "image",
+        senderIcon: senderIcon.value,
+        content: thumbnailUrl,
+        time: getCurrentTime(),
+    };
+    sendMessage(message);
+};
+
+//发送消息接口
+//类型 text ,file,image
+const sendMsg = (messageType = "text", content = "", obj = {}) => {
+    const params = {
+        farmId: farmVal.value,
+        senderId: curUserId,
+        receiverId: userId.value,
+        content,
+        [messageType]:obj,
+        messageType,
+    };
+    VE_API.bbs.sendMsg(params);
+};
+
+// 发送消息
+const sendMessage = (message) => {
+    if (message.messageType === "text") {
+        sendMsg("text", message.content);
+    } else if (message.messageType === "image") {
+        // 按新协议:不传 content,传 image 对象
+        sendMsg("image", "", { url: message.content, thumbnailUrl: message.content + resize });
+    } else if (message.messageType === "report") {
+        // 对话样式消息不发送到服务器,只显示在本地
+        console.log("发送对话样式消息:", message);
+        if(message.reportType === 'farm_report'){
+            sendMsg('report','',{
+                title: message.title,
+                reportId: message.reportId,
+                reportType: message.reportType,
+            });
+        }else{
+            sendMsg('report','',{
+                title: message.title,
+                reportId: message.reportId,
+                reportType: message.reportType,
+            });
+            console.log('其他文件1');
+        }
+    }else{
+        sendMsg('card','',{
+            title: message.title,
+            coverUrl: message.coverUrl,
+            cardType: message.cardType,
+            linkUrl: message.linkUrl
+        });
+    }
+    messages.value.push(message);
+    scrollToBottom();
+};
+
+// 发送文本消息
+const sendTextMessage = () => {
+    if (inputMessage.value.trim()) {
+        const message = {
+            sender: "sent",
+            messageType: "text",
+            senderIcon: senderIcon.value,
+            content: inputMessage.value,
+            time: getCurrentTime(),
+        };
+        sendMessage(message);
+        inputMessage.value = "";
+    }
+};
+
+// 辅助函数
+const getCurrentTime = () => {
+    return new Date().toLocaleTimeString("zh-CN", {
+        hour: "2-digit",
+        minute: "2-digit",
+    });
+};
+
+const scrollToBottom = () => {
+    nextTick(() => {
+        setTimeout(() => {
+            if (messagesContainer.value) {
+                messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
+            }
+        }, 300);
+    });
+};
+
+const farmVal = ref("");
+const options = ref([]);
+const curRole = ref(null);
+
+// 点击农场报告对话框
+const handleReportClick = (msg) => {
+    console.log(msg);
+    if(msg.reportType === 'farm_report' || msg.messageType === 'report'){
+        const params = {
+            farmId: msg.reportId || msg.content?.reportId,
+            showFilter: true,
+        }
+        router.push(`/farm_report?miniJson=${JSON.stringify(params)}`);
+    }else{
+        console.log('其他文件');
+    }
+}
+
+onDeactivated(() => {
+    mqttClient.value && mqttClient.value.client.end(true);
+});
 </script>
 
 <style scoped lang="scss">
@@ -19,6 +375,237 @@ import customHeader from "@/components/customHeader.vue";
     .consult-content {
         width: 100%;
         height: 100%;
+        display: flex;
+        flex-direction: column;
+        position: relative;
+    }
+}
+
+/* 聊天消息区域样式 */
+.chat-messages {
+    flex: 1;
+    padding: 12px;
+    overflow-y: auto;
+    background-color: #f5f5f5;
+    box-sizing: border-box;
+    .message {
+        display: flex;
+        margin-bottom: 15px;
+    }
+    .received {
+        justify-content: flex-start;
+        .bubble {
+            background-color: white;
+            border-radius: 0 10px 10px 10px;
+            padding: 10px 12px;
+            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+        }
+    }
+    .sent {
+        justify-content: flex-end;
+        .bubble {
+            background-color: #07c160;
+            border-radius: 10px 0 10px 10px;
+            padding: 10px 15px;
+            color: #fff;
+            box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+        }
+    }
+    .avatar {
+        width: 40px;
+        height: 40px;
+        border-radius: 50%;
+        background-color: #07c160;
+        color: white;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-right: 10px;
+        font-weight: bold;
+    }
+
+    .avatar-r {
+        margin: 0 0 0 10px;
+    }
+    .bubble {
+        max-width: 70%;
+    }
+}
+
+.content {
+    font-size: 16px;
+    line-height: 1.4;
+}
+
+.time {
+    font-size: 12px;
+    color: #fff;
+    margin-top: 5px;
+    text-align: right;
+}
+
+.input-area {
+    display: flex;
+    align-items: center;
+    padding: 15px 10px;
+    border-top: 1px solid #e6e6e6;
+    background-color: white;
+    position: relative;
+    width: 100%;
+    box-sizing: border-box;
+    input {
+        flex: 1;
+        padding: 10px;
+        border: 1px solid #e6e6e6;
+        border-radius: 20px;
+        outline: none;
+    }
+    .send {
+        margin-left: 10px;
+        padding: 8px 20px;
+        background-color: #07c160;
+        color: white;
+        border: none;
+        border-radius: 20px;
+        cursor: pointer;
+    }
+}
+
+/* 新增的多媒体消息样式 */
+.image-message {
+    img {
+        max-width: 200px;
+        max-height: 200px;
+        border-radius: 8px;
+        cursor: pointer;
+    }
+}
+
+/* 图片消息不使用对话气泡样式 */
+.no-bubble {
+    background: transparent !important;
+    border-radius: 0 !important;
+    padding: 0 !important;
+    box-shadow: none !important;
+    color: inherit !important;
+}
+.card-bubble{
+    background: #fff !important;
+}
+
+/* 工具栏样式 */
+.toolbar {
+    display: flex;
+    align-items: center;
+    button {
+        background: none;
+        border: none;
+        font-size: 20px;
+        margin-right: 10px;
+        cursor: pointer;
+        padding: 5px;
+    }
+    .link {
+        font-size: 24px;
+        margin-right: 10px;
+    }
+}
+
+/* 图片预览 */
+.image-preview {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.8);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1000;
+}
+
+.image-preview img {
+    max-width: 90%;
+    max-height: 90%;
+    object-fit: contain;
+}
+
+/* 对话样式消息 */
+.dialog-message {
+    max-width: 100%;
+    background: #fff !important;
+    border-radius: 10px;
+    .report-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: #000;
+        margin-bottom: 5px;
+    }
+
+    .dialog-title {
+        font-size: 12px;
+        color: rgba(0, 0, 0, 0.6);
+        margin-bottom: 10px;
+    }
+    .monitor-image {
+        width: 222px;
+        height: 180px;
+        object-fit: cover;
+    }
+
+    .farm-report-content,
+    .farm-work-content {
+        .report-details,
+        .work-details {
+            background: #f8f9fa;
+            border-radius: 8px;
+            padding: 12px;
+            margin-top: 10px;
+
+            .detail-item {
+                display: flex;
+                margin-bottom: 6px;
+                font-size: 13px;
+
+                &:last-child {
+                    margin-bottom: 0;
+                }
+
+                .detail-label {
+                    color: #666;
+                    min-width: 80px;
+                }
+
+                .detail-value {
+                    color: #333;
+                    flex: 1;
+                }
+            }
+        }
+    }
+}
+
+.card-message{
+    .card-title{
+        font-size: 15px;
+        font-weight: 600;
+        color: #000;
+        margin-bottom: 5px;
+    }
+    img{
+        width: 222px;
+        height: 180px;
+        object-fit: cover;
+    }
+}
+
+/* 我方消息中的对话样式 */
+.message.sent .dialog-message {
+    background: #e3f2fd;
+
+    .work-details {
+        background: #f0f8ff;
     }
 }
 </style>