Browse Source

feat:对接聊天图片发送功能

wangsisi 5 ngày trước cách đây
mục cha
commit
8fffee5d40

+ 53 - 17
src/components/chatWindow.vue

@@ -8,13 +8,13 @@
                     <!-- <div class="avatar">{{ msg.receiverName.charAt(0) }}</div> -->
                     <el-avatar class="avatar" :size="40" :src="msg.receiverIcon || 'https://birdseye-img.sysuimars.com/birdseye-look-mini/Group%201321316260.png'" />
                     <img src="" alt="">
-                    <div class="bubble">
+                    <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image' }">
                         <!-- 文本消息 -->
                         <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)" :onload="handleImageLoad()" alt="图片" />
+                            <img :src="msg.content + resize" @click="showImagePreview(msg.content)" @load="handleImageLoad" alt="图片" />
                         </div>
 
                         <!-- 语音消息 -->
@@ -68,13 +68,13 @@
 
                 <!-- 我方消息 -->
                 <template v-else>
-                    <div class="bubble">
+                    <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image' }">
                         <!-- 文本消息 -->
                         <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)" :onload="handleImageLoad()" alt="图片" />
+                            <img :src="msg.content + resize" @click="showImagePreview(msg.content)" @load="handleImageLoad" alt="图片" />
                         </div>
 
                         <!-- 语音消息 -->
@@ -205,6 +205,19 @@ 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 createSession = (targetUserId,callback) =>{
     // 先保存当前的对话样式消息  要注释
@@ -212,19 +225,29 @@ const createSession = (targetUserId,callback) =>{
     
     VE_API.bbs.createSession({farmId:766,targetUserId}).then(({data,code}) => {
         if(code === 0){
-            senderIcon.value = data.session.aaa
+            senderIcon.value = localUserInfoIcon
             receiverIcon.value = data.session.targetUserAvatar
             receiverIdVal.value = data.session.targetUserId
             messages.value = data.messages.map(item =>{
                 let content = item.content
                 if(item.messageType==='image'){
-                    content = JSON.parse(item.content).originUrl
+                    // 优先读取后端的 image 字段,其次兼容旧的 content(JSON)
+                    if (item.image && (item.image.url || item.image.originUrl)) {
+                        content = item.image.url || item.image.originUrl
+                    } else if (item.content) {
+                        try {
+                            const imgObj = JSON.parse(item.content)
+                            content = imgObj.url || imgObj.originUrl
+                        } catch (e) {
+                            console.error(e,'e')
+                        }
+                    }
                 }
                 return {
                     ...item,
                     content,
                     sender:item.senderId===curUserId?'sent':'received',
-                    senderIcon:data.session.aaa,
+                    senderIcon:item.senderId===curUserId ? localUserInfoIcon : data.session.targetUserAvatar,
                     receiverIcon:data.session.targetUserAvatar
                 }
             })
@@ -274,9 +297,7 @@ watch(()=>props.userId,(newValue) =>{
                     const imgArr = JSON.parse(props.img)
                     if(imgArr.length){
                         imgArr.forEach(item =>{
-                            sendMsg('image','',{
-                                originUrl:item
-                            })
+                            sendMsg('image', '', { url: item, thumbnailUrl: item + resize })
                             messages.value.push({
                                 sender: "sent",
                                 senderIcon: senderIcon.value,
@@ -334,8 +355,16 @@ const initMqtt = () => {
             console.log('message有值',obj)
             if(obj.senderId === receiverIdVal.value){
                 if(obj.messageType==="image"){
-                    const img = JSON.parse(obj.content)
-                    obj.content = img.originUrl
+                    if (obj.image && (obj.image.url || obj.image.originUrl)) {
+                        obj.content = obj.image.url || obj.image.originUrl
+                    } else if (obj.content) {
+                        try {
+                            const img = JSON.parse(obj.content)
+                            obj.content = img.url || img.originUrl
+                        } catch (e) {
+                            console.error(e,'e')
+                        }
+                    }
                 }
                 obj.receiverId = curUserId
                 obj.sender = obj.senderId === curUserId ?'sent':'received',
@@ -356,10 +385,8 @@ const sendMessage = (message) => {
     if(message.messageType === 'text'){
         sendMsg('text',message.content)
     }else if(message.messageType === 'image'){
-        // sendMsg('image','',{
-        //     originUrl:message.content
-        // })
-        sendMsg('image',message.content)
+        // 按新协议:不传 content,传 image 对象
+        sendMsg('image', '', { url: message.content, thumbnailUrl: message.content + resize })
     }else if(message.messageType === 'dialog'){
         // 对话样式消息不发送到服务器,只显示在本地
         console.log('发送对话样式消息:', message.content);
@@ -661,7 +688,7 @@ const sendFarmReportDialog = () => {
         .sent {
             justify-content: flex-end;
             .bubble {
-                background-color: #2199f8;
+                background-color: #07c160;
                 border-radius: 10px 0 10px 10px;
                 padding: 10px 15px;
                 color: #fff;
@@ -742,6 +769,15 @@ const sendFarmReportDialog = () => {
     }
 }
 
+/* 图片消息不使用对话气泡样式 */
+.no-bubble {
+    background: transparent !important;
+    border-radius: 0 !important;
+    padding: 0 !important;
+    box-shadow: none !important;
+    color: inherit !important;
+}
+
 .audio-message {
     display: flex;
     align-items: center;

+ 1 - 0
src/views/old_mini/dev_login.vue

@@ -22,6 +22,7 @@ onMounted(async () => {
         store.dispatch(`app/${SET_TOKEN}`, data.token);
         store.dispatch(`app/${SET_USER_ROLES}`, data.roles);
         store.dispatch(`app/${SET_USER_CUR_ROLE}`, data.roles[0] || 0);
+        localStorage.setItem("localUserInfo", JSON.stringify(data));
     }
     // 存userId
     let pointXy = route.query.point.split(",")

+ 103 - 71
src/views/old_mini/monitor/subPages/plan.vue

@@ -2,13 +2,21 @@
     <div class="plan-page">
         <custom-header name="农事规划"></custom-header>
         <div class="plan-content">
+            <div class="season-tabs">
+                <div
+                    v-for="s in seasons"
+                    :key="s.value"
+                    class="season-tab"
+                    :class="{ active: s.value === activeSeason }"
+                    @click="activeSeason = s.value"
+                >
+                    {{ s.label }}
+                </div>
+            </div>
             <div class="status-filter">
-                <div class="status-label">农事状态</div>
-                <div class="status-items">
-                    <div v-for="status in statusList" :key="status.value" class="status-item" :class="status.color">
-                        <div class="status-dot"></div>
-                        <span class="status-text">{{ status.label }}</span>
-                    </div>
+                <div v-for="status in statusList" :key="status.value" class="status-item" :class="status.color">
+                    <div class="status-dot"></div>
+                    <span class="status-text">{{ status.label }}</span>
                 </div>
             </div>
 
@@ -33,8 +41,12 @@
                                 <div class="cycle-task-text">梢期</div>
                                 <div class="cycle-task-text">杀虫</div>
                                 <div v-if="item.icon" class="status-icon" :class="item.icon.type">
-                                    <el-icon v-if="item.icon.type === 'complete'" size="16" color="#1CA900"><SuccessFilled /></el-icon>
-                                    <el-icon v-if="item.icon.type === 'warning'" size="18" color="#FF953D"><WarnTriangleFilled /></el-icon>
+                                    <el-icon v-if="item.icon.type === 'complete'" size="16" color="#1CA900"
+                                        ><SuccessFilled
+                                    /></el-icon>
+                                    <el-icon v-if="item.icon.type === 'warning'" size="18" color="#FF953D"
+                                        ><WarnTriangleFilled
+                                    /></el-icon>
                                 </div>
                             </div>
                             <!-- 任务连接器 -->
@@ -60,7 +72,7 @@
             <div class="control-section">
                 <div class="toggle-group">
                     <el-switch v-model="isDefaultEnabled" />
-                    <span class="toggle-label">{{ isDefaultEnabled ? '默认' : '' }}发起农情需求</span>
+                    <span class="toggle-label">{{ isDefaultEnabled ? "默认" : "" }}发起农情需求</span>
                 </div>
                 <div class="add-button-group">
                     <div class="add-button button" @click="addNewTask">新增农事</div>
@@ -84,6 +96,14 @@ import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
 const router = useRouter();
 
 // 状态列表数据
+const seasons = reactive([
+    { value: "spring", label: "春季" },
+    { value: "summer", label: "夏季" },
+    { value: "autumn", label: "秋季" },
+    { value: "winter", label: "冬季" },
+]);
+const activeSeason = ref("spring");
+
 const statusList = reactive([
     { value: "pending", label: "待触发", color: "gray" },
     { value: "executing", label: "待完成", color: "blue" },
@@ -187,7 +207,7 @@ const toggleDefault = () => {
 const addNewTask = () => {
     router.push({
         path: "/modify_work",
-        query: { data: JSON.stringify(['生长异常']), gardenId: 766, isAdd: true },
+        query: { data: JSON.stringify(["生长异常"]), gardenId: 766, isAdd: true },
     });
 };
 
@@ -203,16 +223,16 @@ const manageTask = () => {
 const detailDialogRef = ref(null);
 
 const handleRowClick = (item) => {
-    if(item.status === "complete"){
+    if (item.status === "complete") {
         router.push({
             path: "/review_work",
             query: {
                 id: item.id,
             },
         });
-    }else if(item.type !== "term" && item.status === "default"){
+    } else if (item.type !== "term" && item.status === "default") {
         detailDialogRef.value.showDialog();
-    }else if (item.status === "warning" || item.status === "normal"){
+    } else if (item.status === "warning" || item.status === "normal") {
         router.push({
             path: "/completed_work",
             query: {
@@ -249,53 +269,65 @@ const handleRowClick = (item) => {
             gap: 16px;
             font-size: 12px;
 
-            .status-label {
-                font-size: 12px;
-                white-space: nowrap;
-            }
-
-            .status-items {
+            .status-item {
                 display: flex;
                 align-items: center;
-                gap: 20px;
-
-                .status-item {
-                    display: flex;
-                    align-items: center;
-                    gap: 6px;
-                    &.gray {
-                        color: #c4c6c9;
-                        .status-dot {
-                            background-color: #c4c6c9;
-                        }
+                justify-content: center;
+                gap: 6px;
+                flex: 1;
+                &.gray {
+                    color: #c4c6c9;
+                    .status-dot {
+                        background-color: #c4c6c9;
                     }
+                }
 
-                    &.blue {
-                        color: #2199f8;
-                        .status-dot {
-                            background-color: #2199f8;
-                        }
+                &.blue {
+                    color: #2199f8;
+                    .status-dot {
+                        background-color: #2199f8;
                     }
+                }
 
-                    &.green {
-                        color: #1ca900;
-                        .status-dot {
-                            background-color: #1ca900;
-                        }
+                &.green {
+                    color: #1ca900;
+                    .status-dot {
+                        background-color: #1ca900;
                     }
+                }
 
-                    &.orange {
-                        color: #ff953d;
-                        .status-dot {
-                            background-color: #ff953d;
-                        }
-                    }
+                &.orange {
+                    color: #ff953d;
                     .status-dot {
-                        width: 6px;
-                        height: 6px;
-                        border-radius: 50%;
+                        background-color: #ff953d;
                     }
                 }
+                .status-dot {
+                    width: 6px;
+                    height: 6px;
+                    border-radius: 50%;
+                }
+            }
+        }
+
+        .season-tabs {
+            display: flex;
+            gap: 8px;
+            margin-bottom: 10px;
+            .season-tab {
+                flex: 1;
+                padding: 7px;
+                text-align: center;
+                background: #F3F3F3;
+                color: #898A8A;
+                border-radius: 3px;
+                border: 1px solid transparent;
+                font-size: 12px;
+            }
+            .season-tab.active {
+                background: #ffffff;
+                color: #2199f8;
+                border-color: #2199f8;
             }
         }
 
@@ -376,37 +408,37 @@ const handleRowClick = (item) => {
                                 border-bottom: 4px solid #dde1e7;
                             }
 
-                            &.warning{
-                                .cycle-task-box{
-                                    border-color: #FF953D;
+                            &.warning {
+                                .cycle-task-box {
+                                    border-color: #ff953d;
                                 }
                                 .cycle-task-text {
-                                    color: #FF953D;
+                                    color: #ff953d;
                                 }
-                                .cycle-task-connector{
-                                    border-bottom-color: #FF953D;
+                                .cycle-task-connector {
+                                    border-bottom-color: #ff953d;
                                 }
                             }
-                            &.complete{
-                                .cycle-task-box{
-                                    border-color: #1CA900;
+                            &.complete {
+                                .cycle-task-box {
+                                    border-color: #1ca900;
                                 }
                                 .cycle-task-text {
-                                    color: #1CA900;
+                                    color: #1ca900;
                                 }
-                                .cycle-task-connector{
-                                    border-bottom-color: #1CA900;
+                                .cycle-task-connector {
+                                    border-bottom-color: #1ca900;
                                 }
                             }
-                            &.normal{
-                                .cycle-task-box{
-                                    border-color: #2199F8;
+                            &.normal {
+                                .cycle-task-box {
+                                    border-color: #2199f8;
                                 }
                                 .cycle-task-text {
-                                    color: #2199F8;
+                                    color: #2199f8;
                                 }
-                                .cycle-task-connector{
-                                    border-bottom-color: #2199F8;
+                                .cycle-task-connector {
+                                    border-bottom-color: #2199f8;
                                 }
                             }
                         }
@@ -430,7 +462,7 @@ const handleRowClick = (item) => {
                                 color: #c7c7c7;
                             }
 
-                            &.active{
+                            &.active {
                                 .cycle-term-dot {
                                     background: #858383;
                                 }
@@ -494,15 +526,15 @@ const handleRowClick = (item) => {
                 }
             }
 
-            .add-button-group{
+            .add-button-group {
                 display: flex;
                 align-items: center;
                 gap: 8px;
                 .button {
-                    color: #2199F8;
+                    color: #2199f8;
                     border-radius: 25px;
                     padding: 9px 15px;
-                    border: 1px solid #2199F8;
+                    border: 1px solid #2199f8;
                 }
                 .add-button {
                     background: linear-gradient(120deg, #76c3ff 0%, #2199f8 100%);