From 54572b3c9db78d4374281087a8c66e5c3b3b6200 Mon Sep 17 00:00:00 2001 From: sd <shidong@jhsoft.cc> Date: 星期五, 08 八月 2025 17:28:08 +0800 Subject: [PATCH] 整合文搜万物模块 --- src/api/SurveyView.ts | 101 + src/pages/datapush/index/RightEvent.vue | 74 src/assets/img/conversation.png | 0 src/assets/img/AI-avatar.png | 0 src/api/AiRetrievalView.ts | 25 src/assets/img/historical copy.png | 0 src/assets/img/historical.png | 0 src/pages/searchNew/components/cameraVideo.vue | 232 ++++ src/pages/modelTuning/components/rightCardList.vue | 3 src/pages/searchNew/index/mixins.ts | 25 src/assets/img/UDP配置.png | 0 src/assets/img/article-fill@1x copy.png | 0 src/pages/searchNew/components/AiRetrievalView.vue | 938 ++++++++++++++++ src/pages/searchNew/components/SurveyView.vue | 1570 +++++++++++++++++++++++++++ src/pages/searchNew/index/App.vue | 131 ++ src/assets/img/time-fill@1x.png | 0 src/assets/img/live-fill@1x.png | 0 src/pages/searchNew/index/main.ts | 30 src/assets/img/fold_up.png | 0 src/api/ChatHistoryView.ts | 20 src/pages/searchNew/components/ChatHistoryView.vue | 248 ++++ 21 files changed, 3,375 insertions(+), 22 deletions(-) diff --git a/src/api/AiRetrievalView.ts b/src/api/AiRetrievalView.ts new file mode 100644 index 0000000..368a7d0 --- /dev/null +++ b/src/api/AiRetrievalView.ts @@ -0,0 +1,25 @@ +import request from "@/scripts/httpRequest" + +export default {//娣诲姞瀵硅瘽璁板綍 + createChat(data) { + return request({ + url: '/api-v1/v1/chat/addDetail', + method: 'post', + data + }) + }, + getDetail(params) {//鑾峰彇瀵硅瘽璇︽儏 + return request({ + url: '/api-v1/v1/chat/detail', + method: 'get', + params + }) + }, + getChatDetail(params) {//瀵硅瘽妫�绱� + return request({ + url: '/api-v1/v1/record/chat', + method: 'post', + params + }) + }, +} \ No newline at end of file diff --git a/src/api/ChatHistoryView.ts b/src/api/ChatHistoryView.ts new file mode 100644 index 0000000..8116dcb --- /dev/null +++ b/src/api/ChatHistoryView.ts @@ -0,0 +1,20 @@ +import request from "@/scripts/httpRequest" + +export default { + getChats(data) { + // return service.get('/chat/all', data) + return request({ + url: '/api-v1/v1/chat/all', + method: 'get', + data + }) + }, + deleteChat(params) { + // return service.delete('/chat/delete', { params }) + return request({ + url: '/api-v1/v1/chat/delete', + method: 'delete', + params + }) + }, +} \ No newline at end of file diff --git a/src/api/SurveyView.ts b/src/api/SurveyView.ts new file mode 100644 index 0000000..13e686f --- /dev/null +++ b/src/api/SurveyView.ts @@ -0,0 +1,101 @@ +import request from '@/scripts/httpRequest' + + +export default { + // getSurveys(params) { + // return service.get('/record/all', { params }) + // }, + createFile(data) { + // return service.post('/task/add', data) + return request({ + url: '/api-v1/v1/task/add', + method: 'post', + data + }) + }, + updateFile(data) { + // return service.put(`/task/update`, data) + return request({ + url: '/api-v1/v1/task/update', + method: 'put', + data + }) + }, + deleteFile(params) { + // return service.delete(`/task/delete`, { params }) + return request({ + url: '/api-v1/v1/task/delete', + method: 'delete', + params + }) + }, + getTasks(params) { + // return service.get('/task/all', { params }) + return request({ + url: '/api-v1/v1/task/all', + method: 'get', + params + }) + }, + getCheckContents(params) { + // return service.get('/checkContent/all', { params }) + return request({ + url: '/api-v1/v1/checkContent/all', + method: 'get', + params + }) + }, + getWarnings(params) { + // return service.get('/warningRule/all', { params }) + return request({ + url: '/api-v1/v1/warningRule/all', + method: 'get', + params + }) + }, + getCameras(params) { + // return service.get('/task/getVideoOption') + return request({ + url: '/api-v1/v1/task/getVideoOption', + method: 'get', + params + }) + }, + getTimes(params) { + // return service.get('/workTime/all') + return request({ + url: '/api-v1/v1/workTime/all', + method: 'get', + params + }) + }, + getEvents(params) { + // return service.get('/task/getDictType') + return request({ + url: '/api-v1/v1/task/getDictType', + method: 'get', + params + }) + }, + getSurveys(data) { + return request({ + url: '/api-v1/v1/record/all', + method: 'post', + data + }) + }, + insertModelTraining (params){ + return request({ + url: "/api-v1/v1/train/add ", + method: "post", + data: params + }) + }, + downloadFiles(params){ + return request({ + url: '/api-v1/v1/knowledge/download', + method: 'get', + params + }) + }, +} \ No newline at end of file diff --git a/src/assets/img/AI-avatar.png b/src/assets/img/AI-avatar.png new file mode 100644 index 0000000..d732d6c --- /dev/null +++ b/src/assets/img/AI-avatar.png Binary files differ diff --git "a/src/assets/img/UDP\351\205\215\347\275\256.png" "b/src/assets/img/UDP\351\205\215\347\275\256.png" new file mode 100644 index 0000000..77e0ade --- /dev/null +++ "b/src/assets/img/UDP\351\205\215\347\275\256.png" Binary files differ diff --git a/src/assets/img/article-fill@1x copy.png b/src/assets/img/article-fill@1x copy.png new file mode 100644 index 0000000..7d1825c --- /dev/null +++ b/src/assets/img/article-fill@1x copy.png Binary files differ diff --git a/src/assets/img/conversation.png b/src/assets/img/conversation.png new file mode 100644 index 0000000..69bcfce --- /dev/null +++ b/src/assets/img/conversation.png Binary files differ diff --git a/src/assets/img/fold_up.png b/src/assets/img/fold_up.png new file mode 100644 index 0000000..61816da --- /dev/null +++ b/src/assets/img/fold_up.png Binary files differ diff --git a/src/assets/img/historical copy.png b/src/assets/img/historical copy.png new file mode 100644 index 0000000..09bb019 --- /dev/null +++ b/src/assets/img/historical copy.png Binary files differ diff --git a/src/assets/img/historical.png b/src/assets/img/historical.png new file mode 100644 index 0000000..09bb019 --- /dev/null +++ b/src/assets/img/historical.png Binary files differ diff --git a/src/assets/img/live-fill@1x.png b/src/assets/img/live-fill@1x.png new file mode 100644 index 0000000..84000bc --- /dev/null +++ b/src/assets/img/live-fill@1x.png Binary files differ diff --git a/src/assets/img/time-fill@1x.png b/src/assets/img/time-fill@1x.png new file mode 100644 index 0000000..a637f01 --- /dev/null +++ b/src/assets/img/time-fill@1x.png Binary files differ diff --git a/src/pages/datapush/index/RightEvent.vue b/src/pages/datapush/index/RightEvent.vue index e07f0c5..062a47a 100644 --- a/src/pages/datapush/index/RightEvent.vue +++ b/src/pages/datapush/index/RightEvent.vue @@ -15,9 +15,9 @@ <div> <span style="line-height: 38px;margin-right: 20px;">鎺ㄩ�佹柟寮�</span> - <el-radio :disabled="urls.length > 0" v-model="pushType" label="1">UDP</el-radio> - <el-radio :disabled="urls.length > 0" v-model="pushType" label="2">HTTP</el-radio> - <el-radio disabled v-model="pushType" label="3">MQTT</el-radio> + <el-radio :disabled="urls.length > 0" v-model="taskEditData.pushType" label="1">UDP</el-radio> + <el-radio :disabled="urls.length > 0" v-model="taskEditData.pushType" label="2">HTTP</el-radio> + <el-radio disabled v-model="taskEditData.pushType" label="3">MQTT</el-radio> </div> <span style="line-height: 38px">鎺ㄩ�佹湇鍔″櫒</span> <div class="icon-btn" v-if="urls.length < 1" @click="addUrl()"> @@ -28,13 +28,14 @@ <div> <el-checkbox v-model="item.enable"></el-checkbox> <span class="ml20">{{ "URL " }}</span> - <el-input v-if="pushType === '1'" v-model="item.ip" style="width: 180px; margin-left: 0px;margin-right: 30px" size="small" + <el-input v-if="taskEditData.pushType === '1'" v-model="item.server_ip" + style="width: 180px; margin-left: 0px;margin-right: 30px" size="small" placeholder="192.168.1.100"></el-input> - 绔彛 <el-input v-if="pushType === '1'" v-model="item.sort" style="width: 70px; margin-left: 10px" size="small" - placeholder="8030"></el-input> - <el-input v-if="pushType === '2'" v-model="item.url" style="width: 360px; margin-left: 0px" size="small" - placeholder="http://10.10.10.10:8000/dataApi"></el-input> - <!-- <el-input v-if="pushType === '3'" v-model="item.url" style="width: 360px; margin-left: 0px" size="small" + 绔彛 <el-input v-if="taskEditData.pushType === '1'" v-model="item.port" style="width: 70px; margin-left: 10px" + size="small" placeholder="8030"></el-input> + <el-input v-if="taskEditData.pushType === '2'" v-model="item.url" style="width: 360px; margin-left: 0px" + size="small" placeholder="http://10.10.10.10:8000/dataApi"></el-input> + <!-- <el-input v-if="taskEditData.pushType === '3'" v-model="item.url" style="width: 360px; margin-left: 0px" size="small" placeholder="MQTT"></el-input> --> </div> <div class="server-add"> @@ -110,10 +111,15 @@ <el-input v-model="rule.rule_value" placeholder="璇疯緭鍏ュ唴瀹�" size="small"></el-input> </div> <div v-else> - <el-select v-model="rule.rule_values" multiple collapse-tags placeholder="璇烽�夋嫨" size="small" - @change="selectValue(rule, $event)"> + <el-select v-if="!isWarningSelect" v-model="rule.rule_values" multiple collapse-tags placeholder="璇烽�夋嫨" + size="small" @change="selectValue(rule, $event)"> <el-option v-for="item in rule.ruleValueOptions" :key="item.id" :label="item.name" :disabled="item.disabled" :value="item.value"></el-option> + </el-select> + <el-select v-else v-model="rule.rule_values" collapse-tags placeholder="璇烽�夋嫨" size="small" + @change="selectValue(rule, $event)"> + <el-option v-for="item in rule.ruleValueOptions" :key="item.id" :label="item.name" + :value="item.value"></el-option> </el-select> </div> </el-col> @@ -135,7 +141,8 @@ </div> <div class="config-item"> <b>鎺ㄩ�佸瓧娈�</b> - <el-button v-if="pushType === '1'" type="primary" size="mini" @click="openPushImsDialog">鏌ョ湅</el-button> + <el-button v-if="taskEditData.pushType === '1'" type="primary" size="mini" + @click="openPushImsDialog">鏌ョ湅</el-button> <el-button v-else type="primary" size="mini" @click="openPushSetDialog">璁剧疆</el-button> </div> <div class="save-btn"> @@ -145,7 +152,8 @@ </div> <el-dialog :visible="pushImageDialog" :append-to-body="false" :close-on-click-modal="false" class="dialog-push-field" @close="pushImageDialog = false"> - <el-image fit="fill" src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"></el-image> + <!-- <el-image fit="fill" src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"></el-image> --> + <img src="@/assets/img/UDP閰嶇疆.png" style="width: 100%;"> </el-dialog> <el-dialog :visible="pushFieldDialog" :append-to-body="false" :close-on-click-modal="false" class="dialog-push-field" @close="pushFieldDialog = false"> @@ -203,7 +211,7 @@ }, data() { return { - pushType: '1', + isWarningSelect: true, taskEditData: {}, dataList: [], dictionary: [], @@ -226,7 +234,7 @@ ruleValueOptions: [], }, pushFieldDialog: false, - pushImageDialog:false, + pushImageDialog: false, tempPushSet: [], pushFields: [], allFieldChecked: false, @@ -252,7 +260,7 @@ this.taskEditData.lineWay = newVal.link_type; this.taskEditData.eventTxt = newVal.rule_text; this.taskEditData.radioValue = newVal.is_satisfy_all ? "1" : "2"; - this.pushType = '2' + this.taskEditData.pushType = newVal.push_type + "" if (!this.taskEditData.urls) { this.$set(this.taskEditData, "urls", []); @@ -426,6 +434,8 @@ checked: true, hash: Math.random().toString(36).substr(2), url: "", + server_ip: "", + port: "" }); }, delUrl(index) { @@ -434,6 +444,7 @@ }, // 淇濆瓨 async eventPushsSave() { + console.log(this.taskEditData.urls) if (this.taskEditData.name.length < 1) { this.$notify({ type: "warning", @@ -450,7 +461,7 @@ return; } for (let i = 0; i < this.taskEditData.urls.length; i++) { - if (this.taskEditData.urls[i].url.length < 1) { + if (this.taskEditData.urls[i].url.length < 1 && this.taskEditData.urls[i].server_ip.length < 1) { this.$notify({ type: "warning", message: "鎺ュ彛URL鍦板潃涓嶅厑璁镐负绌�", @@ -511,11 +522,16 @@ rules: ruleList, time_start: this.taskEditData.time[0], time_end: this.taskEditData.time[1], - urls: this.taskEditData.urls, + urls: this.taskEditData.urls.map(item => { + return { + ...item, + port: item.port ? Number(item.port) : 0 + } + }), is_satisfy_all: this.taskEditData.radioValue === "1", link_type: this.taskEditData.lineWay, push_set: this.taskEditData.push_set, - pushType:this.pushType + push_type: Number(this.taskEditData.pushType) }; let res = await eventPushsSave(json); @@ -536,6 +552,12 @@ this.baseRule.topicTypeOptions = this.dictionary.EVENTRULETOPIC; this.baseRule.operatorTypeOpionts = this.dictionary.EVENTTYPECOMPUTE; this.dictionary["alarmLevel"] = this.dictionary.ALARMLEVEL.map((el) => { + return { + name: el.name, + value: el.name, + }; + }); + this.dictionary["warning"] = this.dictionary.WARNING.map((el) => { return { name: el.name, value: el.name, @@ -578,6 +600,7 @@ // 鏂板缓閰嶇疆 createSet() { this.dataList.push(JSON.parse(JSON.stringify(this.baseRule))); + console.log(this.dataList, "dataList") }, cleanSet() { this.dataList.splice(0, this.dataList.length); @@ -602,6 +625,11 @@ } } }); + if (rule.topic_type && rule.topic_type === 'warning') { + this.isWarningSelect = true + } else { + this.isWarningSelect = false + } }, selectArg(rule, resetNext = false) { let argInfo = rule.topicArgOptions.filter((arg) => { @@ -668,8 +696,12 @@ return; } } + if (this.isWarningSelect) { + rule.rule_value = val + } else { + rule.rule_value = val.join(","); + } - rule.rule_value = val.join(","); }, setOptionsDisable(rule) { console.log(rule); @@ -729,9 +761,9 @@ radioValue: "1", eventTxt: "", push_set: this.pushFields, + pushType: '1' }; this.dataList = []; - this.pushType ='1' }, onCancle() { this.$emit("onCancle"); diff --git a/src/pages/modelTuning/components/rightCardList.vue b/src/pages/modelTuning/components/rightCardList.vue index 1eeb450..7b8ff6b 100644 --- a/src/pages/modelTuning/components/rightCardList.vue +++ b/src/pages/modelTuning/components/rightCardList.vue @@ -47,7 +47,7 @@ <!-- 閫夋嫨鏃舵 --> <el-form-item label="閫夋嫨鏃舵"> <el-date-picker style="width: 256px;" v-model="filter.timeRange" type="daterange" - value-format="yyyy-MM-dd hh:mm:ss" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" + value-format="yyyy-MM-dd" range-separator="鑷�" start-placeholder="寮�濮嬫棩鏈�" end-placeholder="缁撴潫鏃ユ湡" /> </el-form-item> @@ -584,6 +584,7 @@ } .image-gallery { + min-width: 1265px; background-color: #ffffff; padding: 20px; border-radius: 4px; diff --git a/src/pages/searchNew/components/AiRetrievalView.vue b/src/pages/searchNew/components/AiRetrievalView.vue new file mode 100644 index 0000000..e26d59b --- /dev/null +++ b/src/pages/searchNew/components/AiRetrievalView.vue @@ -0,0 +1,938 @@ +<template> + <div class="chat-container"> + <div class="messages"> + <div v-for="(msg, index) in messages" :key="index" + :class="['message-bubble', msg.role, { 'with-think': msg.think }]"> + <!-- 鎬濊�冨唴瀹瑰尯鍩� --> + <div v-if="msg.think" class="think-section"> + <div class="think-header" @click="toggleThink(index)"> + <span>鎬濊�冭繃绋�</span> + <i :class="[ + 'arrow', + msg.isThinkExpanded + ? 'el-icon-arrow-down' + : 'el-icon-arrow-right', + ]"></i> + </div> + <transition name="slide"> + <div v-show="msg.isThinkExpanded" class="think-content"> + {{ msg.think }} + </div> + </transition> + </div> + + <!-- 姝e父娑堟伅鍐呭 --> + <div class="content"> + <template v-if="msg.role === 'assistant'"> + <span v-if="isGenerating && index === messages.length - 1"> + <span v-if="!msg.think" class="generating-indicator">姝e湪鎬濊��...</span> + <!-- <span class="cursor"></span> --> + </span> + {{ msg.content }} + </template> + <template v-else> + {{ msg.content }} + </template> + </div> + </div> + </div> + + <div class="preview-area" v-if="uploadedImages.length > 0"> + <div v-for="(img, index) in uploadedImages" :key="index" class="image-preview"> + <img :src="'data:image/png;base64,' + img.base64" class="thumbnail" /> + <span class="image-name">{{ img.name }}</span> + <span class="image-size">{{ formatSize(img.size) }}</span> + <i class="el-icon-close remove-icon" @click="removeImage(index)"></i> + </div> + </div> + <div class="input-area"> + <div class="input-area2"> + <textarea v-model="inputText" @keydown.enter.prevent="handleEnter" :disabled="isGenerating" + placeholder="璇疯緭鍏ヤ綘鐨勯棶棰�" class="input-textarea" rows="1" @input="autoResize"></textarea> + </div> + + <div class="action-buttons"> + <el-upload action="" :auto-upload="false" :show-file-list="false" :on-change="handleImageUpload" + accept="image/*" class="action-img" :disabled="true"> + <i class="el-icon-picture upload-icon"></i> + </el-upload> + <div class="action-img"> + <i class="el-icon-microphone microphone-icon"></i> + </div> + <button @click="startStream" :disabled="isGenerating" class="send-button"> + <i class="el-icon-position send-icon"></i> + </button> + </div> + </div> + <div class="tip-text">鍐呭鐢盇I澶фā鍨嬬敓鎴愶紝璇蜂粩缁嗙攧鍒�</div> + </div> +</template> + +<script> +import AiRetrieval from "@/api/AiRetrievalView"; +export default { + props: ["currentChatId"], + watch: { + currentChatId: { + immediate: true, + handler(newVal) { + if (newVal) this.loadChat(); // 鐩戝惉chatId鍙樺寲 + }, + }, + }, + name: "AiRetrieval", + data() { + return { + chatUrl: "", + severUrl:"", + params: "", + chatId: null, + messages: [], + inputText: "", + isGenerating: false, + controller: null, + uploadedImages: [], + }; + }, + mounted() { + this.fetchCameraInfo(); + }, + methods: { + async fetchCameraInfo() { + const response = await fetch("/config.json"); + if (!response.ok) throw new Error(`璇锋眰澶辫触: ${response.status}`); + const responseData = await response.json(); + this.chatUrl = responseData.chatUrl; + this.severUrl = responseData.severUrl; + // console.info("chatUrl:"+this.chatUrl); + }, + toggleThink(index) { + this.$set( + this.messages[index], + "isThinkExpanded", + !this.messages[index].isThinkExpanded + ); + }, + handleItemSend(params) { + this.$emit("list-selected", params); // 瑙﹀彂鑷畾涔変簨浠� 鍒锋柊鍙充晶鍐呭鍖哄煙 + }, + async loadChat() { + if (!this.currentChatId) return; + + try { + // console.info("鏂板璇�"+this.currentChatId); + const res = await AiRetrieval.getDetail({ + chatId: this.currentChatId, + }); + // console.info(res.data); + + this.messages = []; + this.chatId = res.data[0].chatId; + // console.info(+this.chatId ) + for (let i = 0; i < res.data.length; i++) { + this.messages.push({ + role: "user", + content: res.data[i].question, + }); + this.messages.push({ + role: "assistant", + content: res.data[i].content, + think: res.data[i].think, + }); + } + // this.messages = res.data.data; // 鍋囪鎺ュ彛杩斿洖娑堟伅鍒楄〃 + this.$nextTick(() => { + const container = this.$el.querySelector(".messages"); + container.scrollTop = container.scrollHeight; + }); + } catch (error) { + console.error("鍔犺浇鑱婂ぉ澶辫触:", error); + } + }, + + // async saveToDatabase(chatData) { + // if (this.currentChatId) { // 鏇存柊閫昏緫 + // await AiRetrieval.updateChat(this.currentChatId, chatData); + // } else { // 鏂板缓閫昏緫 + // const res = await AiRetrieval.createChat(chatData); + // this.currentChatId = res.data.chatId; + // } + // }, + reset() { + // console.info("娓呯┖瀵硅瘽璁板綍"); + // 娓呯┖瀵硅瘽璁板綍 + this.messages = []; + // 閲嶇疆杈撳叆妗� + this.inputMessage = ""; + // 濡傛灉鏈夊叾浠栭渶瑕侀噸缃殑鐘舵�� + this.searchResults = []; + this.currentPage = 1; + // 鍙互鏍规嵁闇�瑕佹坊鍔犲叾浠栭噸缃�昏緫 + }, + // 鏂板鑷姩璋冩暣楂樺害鏂规硶 + autoResize(e) { + const textarea = e.target; + textarea.style.height = "auto"; // 閲嶇疆楂樺害 + const maxHeight = 6 * 24; // 6琛�*24px琛岄珮 + 8px*2鍐呰竟璺� + textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`; + }, + handleImageUpload(file) { + const reader = new FileReader(); + reader.onload = (e) => { + const base64Data = e.target.result.split(",")[1]; + this.uploadedImages.push({ + name: file.name, + size: file.size, + base64: base64Data, + }); + }; + reader.readAsDataURL(file.raw); + }, + removeImage(index) { + this.uploadedImages.splice(index, 1); + }, + formatSize(bytes) { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; + }, + handleEnter(e) { + if (!e.shiftKey && !this.isGenerating) { + this.startStream(); + } + }, + async startStream() { + if (!this.inputText.trim() || this.isGenerating) return; + + this.messages.push({ role: "user", content: this.inputText }); + const userMessage = this.inputText; + this.inputText = ""; + this.messages.push({ + role: "assistant", + content: "", + think: "", + isThinkExpanded: true, + }); + // this.$nextTick(() => { + // const container = this.$el.querySelector(".messages"); + // container.scrollTop = container.scrollHeight; + // }); + this.isGenerating = true; + + try { + this.controller = new AbortController(); + + let ids = []; + let idsStr = ""; + //绛涢�夋渶缁堢粨鏋� + // const response = await AiRetrieval.getChatDetail( + // { + // message:userMessage + // } + // ); + const response = await fetch(this.severUrl +"/v1/record/chat", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + message: userMessage + }), + }); + + if (!response.ok) throw new Error(`璇锋眰澶辫触: ${response.status}`); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let boo = true; + // 淇敼鍚庣殑fetch澶勭悊閫昏緫 + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split("\n").filter((line) => line.trim()); + + // 淇濆瓨鏈�鏂版秷鎭殑寮曠敤 + const lastMessage = this.messages[this.messages.length - 1]; + + for (const line of lines) { + try { + const json = JSON.parse(line.replace("data:", "").trim()); + if (json.data === true) break; + // 鐩存帴鏇挎崲鑰屼笉鏄拷鍔� + if (json.data) { + + // 浣跨敤姝e垯琛ㄨ揪寮忓尮閰嶆�濊�冨唴瀹� + const thoughtMatch = json.data.match( + /<think>(.*?)(?=\n<\/think>|$)/s + ); + if (thoughtMatch) { + // 鏇存柊娑堟伅鍐呭 甯hink + lastMessage.think = thoughtMatch[1].trim(); + let con = json.data //绗竴閮ㄥ垎锛� + .match(/<\/think>([\s\S]*)/)[1] + .trim(); + // console.info('con='+con) + const rescou = con + .match(/绗竴閮ㄥ垎锛�(.*?)(?=\n绗簩閮ㄥ垎锛殀$)/s)[1] + .trim(); + + // console.info('rescou='+rescou) + if (rescou) { + lastMessage.content = rescou; + idsStr = con.match(/绗簩閮ㄥ垎锛�([\s\S]*)/)[1].trim(); + // console.info("idsStr=" + idsStr); + boo = false; + } + } else { + // 鏇存柊娑堟伅鍐呭 + lastMessage.think = json.data + .match(/<think>([\s\S]*)/)[1] + .trim(); + } + } + // console.info("boo:" + boo) + } catch (e) { + console.info("") + } + } + } + + // console.info("boo:"+boo) + if (boo) { + this.messages[this.messages.length - 1].content = "鍖归厤鍒扮殑鏁版嵁涓�0鏉�"; + } else { + ids = idsStr.split(","); + } + // this.handleItemSend(lists);//鍙戦�佹暟鎹睍绀哄埌椤甸潰 + this.handleItemSend(ids); + // // 淇濆瓨鍒版暟鎹簱鐨勯�昏緫 + // console.info("淇濆瓨鏃ф暟鎹�"+this.chatId) + if (this.messages.length >= 2) { + // console.info(this.messages) + const userMsg = this.messages[this.messages.length - 2]; + const assistantMsg = this.messages[this.messages.length - 1]; + this.saveToDatabase({ + question: userMsg.content, + content: assistantMsg.content, + think: assistantMsg.think, + chatId: this.chatId, + // images: this.uploadedImages + }); + } + } catch (error) { + if (error.name !== "AbortError") { + console.error("鐢熸垚澶辫触:", error); + this.messages[this.messages.length - 1].content += "锛堢敓鎴愬け璐ワ級"; + } + } finally { + this.isGenerating = false; + this.controller = null; + } + }, + // async startStream() { + // if (!this.inputText.trim() || this.isGenerating) return; + + // this.messages.push({ role: "user", content: this.inputText }); + // const userMessage = this.inputText; + // this.inputText = ""; + // this.messages.push({ + // role: "assistant", + // content: "", + // think: "", + // isThinkExpanded: true, + // }); + // // this.$nextTick(() => { + // // const container = this.$el.querySelector(".messages"); + // // container.scrollTop = container.scrollHeight; + // // }); + // this.isGenerating = true; + + // try { + // this.controller = new AbortController(); + + // //浼樺寲鐢ㄦ埛闂 + // const resstar = + // "milvus鏁版嵁搴撲腑瀛樺偍浜唅d銆佹憚鍍忔満鎶撴媿鍥剧墖銆佸浘鐗囩殑鏂囨湰鎻忚堪锛屽浘鐗囨枃鏈弿杩版槸閫氳繃qwen妯″瀷鎶婂浘鐗囦腑鍖呭惈鐨勫唴瀹瑰舰鎴愪簡鏂囨湰鍐呭銆傜敤鎴蜂細杈撳叆浠巑ilvus鏁版嵁搴撲腑鏌ユ壘鍥剧墖鐨勯渶姹傦紝璇峰府鎴戞妸鐢ㄦ埛鐨勯棶棰樻敼鍐欎竴涓嬶紝浠ヤ究鍦╩ilvus鏁版嵁搴撲腑鏌ユ壘鍒版洿鍔犲噯纭殑涓旂鍚堢敤鎴锋湡鏈涚殑鏁版嵁銆備笉瑕佽緭鍑烘敼鍐欒鏄� /nothink"; + // const startRes = await fetch(this.chatUrl + "/chat", { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ + // prompt: resstar, + // llm_name: "qwen3:8b",//qwen2.5vl + // messages: [ + // ...this.messages.slice(0, -1), + // { role: "user", content: userMessage }, + // ], + // stream: false, + // gen_conf: { + // temperature: 0.1, + // max_tokens: 1000 + // } + // // image:this.uploadedImages[0]?.base64 || '' + // }), + // }); + // // console.info("chat鍦板潃锛�"+"/api-AI" + "/chat") + // if (!startRes.ok) throw new Error(`璇锋眰澶辫触: ${startRes.status}`); + // const responseData = await startRes.json(); + + + // let res1 = responseData.data.split('\n\n') + // let rescou = res1.length>1?res1[2]:"" + // //let rescou = responseData.data.match(/锛�(.*?)(?=\n锛亅$)/s)[1].trim(); + // console.info(rescou); + // if (!rescou) { + // rescou = userMessage; + // } + // //妫�绱㈠悜閲忔暟鎹簱 + // const res = await fetch(this.chatUrl + "/search", { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ + // collection_name: "smartrag", + // query_text: rescou, + // search_mode: "hybrid", + // limit: 20, + // weight_dense: 0.7, + // weight_sparse: 0.3, + // filter_expr: "", + // output_fields: [ + // "id", + // "image_path", + // "create_timestamp", + // "task_name", + // "event_level_name", + // "rtsp_address", + // "video_point_id", + // "zh_desc_class", + // "video_path", + // "detect_time", + // ], + // }), + // }); + + // // console.info(res.json()) + // const text = await res.text(); + // const data = JSONbig({ storeAsString: true }).parse(text); + // console.info(data); + // let lists = []; + // let ids = []; + // let idsStr = ""; + // for (let i = 0; i < data.results.length; i++) { + // // console.info(data.results[i].entity.id) + // this.params += + // "淇℃伅id锛�" + + // data.results[i].entity.id + + // ",鍐呭锛�" + + // data.results[i].entity.video_path + + // "鎽勫儚澶村湪" + + // data.results[i].entity.detect_time + + // "鏃堕棿鎷嶆憚鐨勫浘鐗�" + + // data.results[i].entity.zh_desc_class + + // "\n"; + // lists.push(data.results[i].entity); + // } + + // // const mes = + // // "鐢ㄦ埛璇㈤棶銆�" + + // // rescou + + // // "銆戙�俓n璇峰湪浠ヤ笅淇℃伅涓繘琛屽尮閰嶏紝淇℃伅涓篭n" + + // // this.params + + // // "銆俓n鍥炵瓟:銆怷XX淇℃伅id銆�"; + + + // const mes = + // "鐢ㄦ埛璇㈤棶銆�" + + // rescou + + // "銆戙�俓n璇峰湪浠ヤ笅淇℃伅涓繘琛屽尮閰嶏紝淇℃伅涓篭n" + + // this.params + + // "銆俓n涓嶈繘琛屾帹鐞嗗拰瑙h瘧锛屽繀椤昏繑鍥炰袱閮ㄥ垎缁撴灉锛屼笖缁撴灉涓繀椤诲甫鏈夌涓�閮ㄥ垎鎴栫浜岄儴鍒嗗洓涓瓧锛岀涓�閮ㄥ垎涓烘�荤粨鍖归厤鍒板灏戞潯锛岀浜岄儴鍒嗙殑鏍煎紡蹇呴』鏄� 绗簩閮ㄥ垎锛歑XX,XXX鍏朵腑XXX涓烘暟鎹簱id"; + // this.params = ""; + // if (!res.ok) throw new Error(`璇锋眰澶辫触: ${res.status}`); + + // let contentMes = mes.replace(/\s+/g, '') + + // //绛涢�夋渶缁堢粨鏋� + // const response = await fetch(this.chatUrl + "/chat", { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ + // prompt: "", + // llm_name: "qwen3:8b",//qwen2.5vl + // messages: [ + // ...this.messages.slice(0, -1), + // { role: "user", content: contentMes }, + // ], + // stream: true, + // gen_conf: { + // temperature: 0.7, + // max_tokens: 4000, + // }, + // // image:this.uploadedImages[0]?.base64 || '' + // }), + // }); + + // if (!response.ok) throw new Error(`璇锋眰澶辫触: ${response.status}`); + + // const reader = response.body.getReader(); + // const decoder = new TextDecoder(); + // let boo = true; + // // 淇敼鍚庣殑fetch澶勭悊閫昏緫 + // while (true) { + // const { done, value } = await reader.read(); + // if (done) break; + + // const chunk = decoder.decode(value, { stream: true }); + // const lines = chunk.split("\n").filter((line) => line.trim()); + + // // 淇濆瓨鏈�鏂版秷鎭殑寮曠敤 + // const lastMessage = this.messages[this.messages.length - 1]; + + // for (const line of lines) { + // try { + // const json = JSON.parse(line.replace("data:", "").trim()); + // if (json.data === true) break; + // // 鐩存帴鏇挎崲鑰屼笉鏄拷鍔� + // if (json.data) { + + // // 浣跨敤姝e垯琛ㄨ揪寮忓尮閰嶆�濊�冨唴瀹� + // const thoughtMatch = json.data.match( + // /<think>(.*?)(?=\n<\/think>|$)/s + // ); + // if (thoughtMatch) { + // // 鏇存柊娑堟伅鍐呭 甯hink + // lastMessage.think = thoughtMatch[1].trim(); + // let con = json.data //绗竴閮ㄥ垎锛� + // .match(/<\/think>([\s\S]*)/)[1] + // .trim(); + // // console.info('con='+con) + // const rescou = con + // .match(/绗竴閮ㄥ垎锛�(.*?)(?=\n绗簩閮ㄥ垎锛殀$)/s)[1] + // .trim(); + + // // console.info('rescou='+rescou) + // if (rescou) { + // lastMessage.content = rescou; + // idsStr = con.match(/绗簩閮ㄥ垎锛�([\s\S]*)/)[1].trim(); + // // console.info("idsStr=" + idsStr); + // boo = false; + // } + // } else { + // // 鏇存柊娑堟伅鍐呭 + // lastMessage.think = json.data + // .match(/<think>([\s\S]*)/)[1] + // .trim(); + // } + // } + // } catch (e) { + // // console.error("瑙f瀽閿欒:", e); + // } + // } + // // this.$nextTick(() => { + // // const container = this.$el.querySelector(".messages"); + // // container.scrollTop = container.scrollHeight; + // // }); + // } + + // if (boo) { + // this.messages[this.messages.length - 1].content = "鍖归厤鍒扮殑鏁版嵁涓�0鏉�"; + // lists = []; + // } else { + // ids = idsStr.split(","); + // } + // // this.handleItemSend(lists);//鍙戦�佹暟鎹睍绀哄埌椤甸潰 + // this.handleItemSend(ids); + // // // 淇濆瓨鍒版暟鎹簱鐨勯�昏緫 + // // console.info("淇濆瓨鏃ф暟鎹�"+this.chatId) + // if (this.messages.length >= 2) { + // // console.info(this.messages) + // const userMsg = this.messages[this.messages.length - 2]; + // const assistantMsg = this.messages[this.messages.length - 1]; + // this.saveToDatabase({ + // question: userMsg.content, + // content: assistantMsg.content, + // think: assistantMsg.think, + // chatId: this.chatId, + // // images: this.uploadedImages + // }); + // } + // } catch (error) { + // if (error.name !== "AbortError") { + // console.error("鐢熸垚澶辫触:", error); + // this.messages[this.messages.length - 1].content += "锛堢敓鎴愬け璐ワ級"; + // } + // } finally { + // this.isGenerating = false; + // this.controller = null; + // } + // }, + async saveToDatabase(chatData) { + // 瀹為檯璋冪敤API鎺ュ彛 + try { + const response = await AiRetrieval.createChat(chatData); + // console.info(response.data); + this.chatId = response.data || null; + } catch (error) { + console.error("API Error:", error); + } + }, + cancelGeneration() { + if (this.controller) this.controller.abort(); + }, + }, +}; +</script> + +<style scoped> +/* 鏂板鎬濊�冨唴瀹规牱寮� */ +.message-bubble.assistant.with-think { + border-radius: 12px; +} + +.think-section { + background: #f8f9fa; + padding: 12px; + border-bottom: 1px solid #e9ecef; + text-align: left; +} + +.think-header { + font-size: 12px; + color: #6c757d; + margin-bottom: 8px; + display: flex; + align-items: center; + font-weight: 500; +} + +.think-header::before { + content: "馃挕"; + margin-right: 6px; +} + +.think-content { + font-size: 10px; + color: #495057; + line-height: 1.6; + white-space: pre-wrap; +} + +.generating-indicator { + color: #6c757d; + font-size: 0.9em; + margin-right: 8px; +} + +/* 璋冩暣鍘熸湁鏍峰紡 */ +.message-bubble.assistant .content { + background: #f5f6f8; + padding: 12px 15px; + border-radius: 0 0 12px 12px; + text-align: left; +} + +/* 鏂板鍥剧墖鐩稿叧鏍峰紡 */ +.preview-area { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 8px; + background: #f5f5f5; + border-radius: 4px; + margin-bottom: 8px; +} + +.image-preview { + position: relative; + display: flex; + align-items: center; + gap: 4px; + padding: 4px; + background: white; + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.thumbnail { + width: 30px; + height: 30px; + object-fit: cover; + border-radius: 2px; +} + +.image-name { + font-size: 12px; + max-width: 100px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.image-size { + font-size: 10px; + color: #666; +} + +.remove-icon { + position: absolute; + top: -8px; + right: -8px; + font-size: 12px; + background: white; + border-radius: 50%; + cursor: pointer; +} + +.action-img { + width: 20px; + height: 20px; + border-radius: 13px; + padding: 6px; + background-color: #f2f8ff; +} + +.upload-icon { + font-size: 20px; + color: #7fade6; + cursor: pointer; +} + +.chat-container { + position: relative; + width: 100%; + max-width: 500px; + height: 600px; + margin: 0 auto; + /* background: #f1f1f1; */ + border-radius: 12px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); + display: flex; + flex-direction: column; +} + +.close-button { + position: absolute; + right: 15px; + top: 15px; + font-size: 20px; + color: #999; + cursor: pointer; + z-index: 1; + transition: color 0.2s; +} + +.close-button:hover { + color: #666; +} + +.messages { + flex: 1; + padding: 20px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 15px; +} + +.message-bubble { + max-width: 80%; + position: relative; + font-size: 14px; + line-height: 1.5; +} + +.message-bubble.user { + align-self: flex-end; +} + +.message-bubble.assistant { + align-self: flex-start; +} + +.content { + padding: 12px 15px; + border-radius: 5px; + word-break: break-word; +} + +.user .content { + background: #cadcff; + color: #2e2f31; + border-radius: 12px 12px 0 12px; +} + +.assistant .content { + background: white; + color: #2e2f31; + border-radius: 12px 12px 12px 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.input-area { + display: flex; + flex-direction: column; + /* 鏀逛负鍨傜洿甯冨眬 */ + background: #fff; + border: 2px solid #1890ff; + border-radius: 10px; + padding-top: 8px; + gap: 8px; + /* 娣诲姞闂磋窛 */ + min-height: 80px; + max-height: 200px; + align-items: center; + /* position: relative; */ +} + +.input-area2 { + width: 100%; + display: flex; + background: #fff; + min-height: 60px; + max-height: 180px; + /* border: 2px solid #1890ff; */ +} + +.input-textarea { + /* background: #0c0c0c; */ + width: 100%; + border: none; + resize: none; + line-height: 24px; + /* 绮剧‘琛岄珮 */ + font-size: 14px; + padding: 8px 15px; + min-height: 24px !important; + /* 寮哄埗鍗曡楂樺害 */ + max-height: 152px !important; + /* 寮哄埗6琛岄珮搴� */ + overflow-y: auto; + box-sizing: content-box; + /* 闃叉padding褰卞搷璁$畻 */ +} + +.input-textarea:focus { + outline: none; + box-shadow: none; +} + +.wechat-input { + flex: 1; + min-height: 40px; + max-height: 144px; + /* 6琛� x 24px琛岄珮 */ + padding: 8px 0; + line-height: 1.5; + border: 0; + background: transparent; + font-size: 16px; + color: #2c3e50; + resize: none; + overflow-y: auto; +} + +.wechat-input:focus { + border-color: #07c160; + background: white; + box-shadow: 0 4px 12px rgba(7, 193, 96, 0.1); + /* 娣诲姞鑱氱劍闃村奖 */ +} + +.wechat-input::placeholder { + color: #a8c5e6; + /* 娴呰摑鑹插崰浣嶇 */ +} + +.microphone-icon { + font-size: 20px; + color: #7fade6; + cursor: pointer; +} + +/* 鎿嶄綔鎸夐挳鍖哄煙 */ +.action-buttons { + width: 100%; + display: flex; + justify-content: flex-end; + /* 鍙冲榻愭寜閽� */ + gap: 8px; + padding: 0 0 5px 0; + background: transparent; + /* border: 2px solid #1890ff; */ +} + +/* 鍙戦�佹寜閽敼閫� */ +.send-button { + width: 45px; + height: 25px; + border-radius: 10px; + background: #1890ff; + border: none; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + margin: 2px 6px; +} + +.send-icon { + color: white; + font-size: 16px; + /* transform: rotate(45deg); */ +} + +.send-button:disabled { + background: #b6d7ff; + cursor: not-allowed; +} + +/* 鎻愮ず鏂囨湰 */ +.tip-text { + text-align: center; + font-size: 12px; + color: #95a5c6; + margin: 12px 0 10px 0; + letter-spacing: 0.5px; +} + +/* 杈撳叆妗嗙鐢ㄧ姸鎬� */ +.wechat-input:disabled { + background: #f0f0f0; + opacity: 0.8; +} + +.send-button:not(:disabled):hover { + background: #2482ff; +} + +.cursor { + display: inline-block; + width: 8px; + height: 16px; + background: #666; + margin-left: 2px; + animation: blink 1s infinite; +} + +@keyframes blink { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0; + } +} +</style> \ No newline at end of file diff --git a/src/pages/searchNew/components/ChatHistoryView.vue b/src/pages/searchNew/components/ChatHistoryView.vue new file mode 100644 index 0000000..9247149 --- /dev/null +++ b/src/pages/searchNew/components/ChatHistoryView.vue @@ -0,0 +1,248 @@ +<template> + <div class="schedule-container"> + <el-card class="schedule-card" shadow="never"> + <div v-for="(item, index) in chatList" :key="index" class="chat-item" @mouseenter="showDelete = index" + @mouseleave="showDelete = null"> + <!-- 鏂板鍒犻櫎鎸夐挳 --> + <i class="el-icon-delete delete-button" + v-show="showDelete === index" + @click.stop="handleDelete(item.chatId, $event)"></i> + <div class="chat-header"></div> + <div class="chat-header"> + <!-- <span class="chat-time">{{ formatDate(item.createTime) }}</span> + <span class="chat-id">#{{ item.chatId }}</span> --> + </div> + <div class="chat-content" @click="handleItemClick(item.chatId)"> + {{ extractContent(item.title) }} + </div> + + </div> + </el-card> + </div> +</template> + +<script> +import chatHistory from "@/api/ChatHistoryView" +export default { + name: 'ChatHistory', + data() { + return { + showDelete: null, // 褰撳墠鏄剧ず鍒犻櫎鎸夐挳鐨勭储寮� + chatData: { + list: [], + pagination: {} + } + } + }, + computed: { + chatList() { + return this.chatData.list || [] + } + }, + methods: { + // 鏂板鍒犻櫎澶勭悊鏂规硶 + async handleDelete(chatId, index) { + try { + await this.$confirm('纭畾瑕佸垹闄ゆ鑱婂ぉ璁板綍鍚楋紵', '鎻愮ず', { + confirmButtonText: '纭畾', + cancelButtonText: '鍙栨秷', + type: 'warning' + }) + + // 璋冪敤鍒犻櫎鎺ュ彛 + await chatHistory.deleteChat({ + chatId:chatId + }) + + // 鍓嶇绔嬪嵆鏇存柊鍒楄〃 + this.chatData.list.splice(index, 1) + this.$message.success('鍒犻櫎鎴愬姛') + } catch (error) { + if (error !== 'cancel') { + console.error('鍒犻櫎澶辫触:', error) + this.$message.error('鍒犻櫎澶辫触') + } + } + }, + handleItemClick(chatId) { + // console.info("鐐瑰嚮"+chatId) + this.$emit('item-selected', chatId); // 瑙﹀彂鑷畾涔変簨浠� + }, + async fetchChatHistory() { + try { + const tasks = await chatHistory.getChats({//妫�娴嬪璇濆巻鍙� + // page: 1, + // pageSize: 999 + }); + // console.info(tasks.data.list) + this.chatData.list = tasks.data.list + this.chatData.pagination = tasks.data.pagination + } catch (error) { + console.error('鑾峰彇绛涢�夐�夐」澶辫触:', error); + this.$message.error('绛涢�夐�夐」鍔犺浇澶辫触'); + } + }, + formatDate(dateString) { + return dateString.split(' ')[0] // 鎻愬彇鏃ユ湡閮ㄥ垎 + }, + extractContent(content) { + // 绉婚櫎<think>鏍囩鍐呭锛屾彁鍙栧疄闄呭唴瀹� + const cleanContent = content.replace(/<think>[\s\S]*?<\/think>\n*/g, '') + return cleanContent.trim() + } + }, + mounted() { + // 妯℃嫙鎺ユ敹鍚庣鏁版嵁 + this.fetchChatHistory() + // this.chatData = { + // list: [ + // { + // chatId: 19, + // title: `<think>鎬濊�冭繃绋�...</think>\n\n浣犲ソ锛佹湁浠�涔堟垜鍙互甯綘鐨勫悧锛燄煒奰, + // createTime: "2025-05-27 09:30:33.1284251+00:00" + // } + // // 鍏朵粬鏁版嵁椤�... + // ], + // pagination: { + // page: 1, + // total: 6 + // } + // } + } +} +</script> + +<style scoped> +/* 浼樺寲鍚庣殑鏍峰紡 */ +.schedule-container { + height: 600px; + background: white; + padding: 0; + overflow-y: auto; +} + +.schedule-card { + padding: 0; + border: none; + min-height: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; +} + +.chat-item { + padding: 10px 0; + /* border-bottom: 1px solid #f0f0f0; */ + cursor: pointer; + transition: background-color 0.3s; + position: relative; /* 涓虹粷瀵瑰畾浣嶆寜閽彁渚涘弬鑰� */ + padding-right: 40px; /* 涓烘寜閽暀鍑虹┖闂� */ +} +.delete-button { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + color: #ff4d4f; + padding: 5px; + font-size: 16px; + transition: all 0.3s; + z-index: 2; +} +.delete-button:hover { + background-color: rgba(255,77,79,0.1); + border-radius: 50%; +} + +/* 浼樺寲鎮仠鏁堟灉 */ +.chat-item:hover .delete-button { + display: block; +} + +.chat-item:hover { + background-color: #f8f9fa; +} + +.chat-header { + display: flex; + justify-content: space-between; + /* margin-bottom: 8px; */ + font-size: 12px; + color: #666; +} +.item-container { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; +} +.chat-content { + flex: 1; + cursor: pointer; + color: #1a1a1a; + font-size: 16px; + line-height: 1.6; + word-break: break-word; + text-align: left; /* 鏂板宸﹀榻� */ + + /* 鏂板鍗曡鐪佺暐鏍峰紡 */ + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + /* 纭繚瀹藉害闄愬埗 */ + max-width: 100%; + width: 100%; +} +/* 鎮仠鏃舵樉绀哄畬鏁村唴瀹� */ +/* .chat-item:hover .chat-content { + white-space: normal; + overflow: visible; + text-overflow: initial; +} */ + +.loading-container { + padding: 40px; + text-align: center; + color: #666; + font-size: 14px; +} + +.empty-state { + text-align: center; + padding: 40px; + color: #999; + + i { + font-size: 48px; + margin-bottom: 16px; + } +} + +.pagination { + margin-top: auto; + padding: 16px; + justify-content: center; +} + +/* 婊氬姩鏉′紭鍖� */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} +</style> \ No newline at end of file diff --git a/src/pages/searchNew/components/SurveyView.vue b/src/pages/searchNew/components/SurveyView.vue new file mode 100644 index 0000000..4a21fc3 --- /dev/null +++ b/src/pages/searchNew/components/SurveyView.vue @@ -0,0 +1,1570 @@ +<template> + <div> + <!-- 鏂板瀹炴椂鐩戞帶寮圭獥 --> + <el-dialog title="瀹炴椂鐩戞帶" :visible.sync="realTimeVisible" width="60%" custom-class="resizable-dialog" :modal="true" + :lock-scroll="false" :close-on-click-modal="false"> + <!-- <div class="dialog-content"> --> + <camera-video @close="realTimeVisible = false" /> + <!-- <p>灏濊瘯鎷栨嫿杈圭紭鎴栬钀借皟鏁村ぇ灏�</p> + <div v-for="handle in handles" :key="handle.position" class="resize-handle" :class="handle.position" + @mousedown.prevent="startResize(handle.position, $event)"> + </div> + </div> --> + </el-dialog> + <!-- 璇︽儏寮圭獥 --> + <!-- 淇敼鍚庣殑璇︽儏寮圭獥 --> + <el-dialog :visible.sync="detailVisible" title="" width="660px" custom-class="detail-dialog right-aligned"> + <!-- 椤堕儴鍒囨崲鎸夐挳 --> + <div class="media-switch"> + <el-radio-group v-model="currentMediaType"> + <el-radio-button label="image" class="media-tab">澶у浘</el-radio-button> + <el-radio-button label="video" class="media-tab">瑙嗛</el-radio-button> + </el-radio-group> + </div> + <el-divider></el-divider> + <!-- 涓婇儴濯掍綋鍖� --> + <div class="media-container"> + <div v-if="currentMediaType === 'image'" class="main-image"> + <el-image :src="detailItem.image_path" alt="浜嬩欢鎴浘" class="detail-img previewable-image" + :preview-src-list="[detailItem.image_path]" /> + </div> + <div v-else class="video-section"> + <div class="video-placeholder"> + <!-- 淇敼video鏍囩閮ㄥ垎 --> + <video :src="detailItem.video_path" controls style="width: 100%; height: 100%; object-fit: contain"></video> + <!-- <wasm-player style="width: 100%; height: 100%; object-fit: contain"></wasm-player> --> + </div> + </div> + </div> + + <!-- 涓嬮儴淇℃伅鍖� --> + <div class="info-container"> + <!-- <el-descriptions :column="2" border> --> + <el-descriptions :column="1"> + <el-descriptions-item label="鏃堕棿" class="info-item"> + <span style="width: 28px"></span> + <span class="info-value">{{ + formatStartDateTime(detailItem.detect_time) + }}</span> + </el-descriptions-item> + + <el-descriptions-item label="瑙嗛鐐逛綅" class="info-item"> + <span class="info-value">{{ detailItem.video_name }}</span> + </el-descriptions-item> + <el-descriptions-item label="浠诲姟鍚嶇О" v-if="backendData"> + <span class="multi-value"> + {{ detailItem.task_names }} + </span> + </el-descriptions-item> + + <el-descriptions-item label="浜嬩欢绛夌骇" v-if="backendData"> + <span style="background-color: #f70713; width: 25px; height: 15px"> + </span><span style="width: 5px"></span>{{ detailItem.event_levels }} + </el-descriptions-item> + + <el-descriptions-item label="闅愭偅鎻忚堪" v-if="backendData"> + <el-tooltip placement="top" :content="detailItem.risk_description" effect="light" + popper-class="my-tooltip"> + <span class="multi-value ellipsis">{{ detailItem.risk_description }}</span> + </el-tooltip> + + </el-descriptions-item> + + <el-descriptions-item label="澶勭悊寤鸿" v-if="backendData"> + <el-tooltip placement="top" :content="detailItem.suggestion" effect="light" popper-class="my-tooltip"> + <span class="multi-value ellipsis">{{ detailItem.suggestion }}</span> + </el-tooltip> + </el-descriptions-item> + <el-descriptions-item label="鐩稿叧鏂囨。" v-if="backendData"> + <div v-for="(doc, index) in detailItem.knowledge_documents || []" :key="index" class="multi-value"> + <!-- <a :href="getPreviewUrl(doc)" target="_blank" class="doc-link" style="text-decoration: none;"> + <span>銆妠{ doc.fileName }}銆�</span> + </a> --> + <span class="doc-link" @click="getPreviewUrl2(doc.id)">銆妠{ doc.fileName }}銆�</span> + <span v-if="index < detailItem.knowledge_documents.length - 1">, </span> + </div> + </el-descriptions-item> + <el-descriptions-item label="鍥剧墖鍐呭" style=""> + <div class="desc_class"> + <span class="multi-value2"> + {{ detailItem.zh_desc_class }} + </span> + </div> + </el-descriptions-item> + + </el-descriptions> + </div> + </el-dialog> + + <div class="statistics-container"> + <!-- 宸︿晶绛涢�夋爮 --> + <div class="left-sidebar" :class="{ collapsed: isSidebarCollapsed }"> + <div class="stats-header"> + <div class="ai-avatar"> + <img :src="require('@/assets/img/AI-avatar.png')" class="avatar-img" /> + <div class="header-text" v-show="!isSidebarCollapsed"> + <h3 class="content-title header-gradient2">浣犲ソ锛屾垜鏄皬璐�</h3> + <div class="stats-numbers"> + <span>寰堥珮鍏磋鍒颁綘锛�</span> + </div> + </div> + <div class="header-actions"> + <img :src="require('@/assets/img/fold_up.png')" class="fold-icon" :class="{ reverse: isSidebarCollapsed }" + @click="toggleSidebar" /> + <img v-if="isSidebarCollapsed" :src="require('@/assets/img/conversation.png')" class="fold-icon" + :class="{ reverse: isSidebarCollapsed }" @click="handleNewSession" /> + <img v-if="isSidebarCollapsed" :src="require('@/assets/img/historical.png')" class="fold-icon" + :class="{ reverse: isSidebarCollapsed }" @click="handleHistoryClick" /> + </div> + </div> + </div> + <div class="session-buttons" v-if="!isSidebarCollapsed"> + <el-button @click="handleHistoryClick" type="primary" round class="history-btn" + style="flex: 1; margin-right: 10px"> + 鍘嗗彶浼氳瘽 + </el-button> + <el-button @click="handleNewSession" type="primary" round class="new-session-btn" + style="flex: 1; position: relative"> + 寮�鍚柊浼氳瘽 + <i class="el-icon-arrow-right" style="margin-left: 8px; font-size: 14px"></i> + </el-button> + </div> + <el-divider v-if="!isSidebarCollapsed"></el-divider> + <!-- AI妫�绱㈢粍浠� --> + <ai-retrieval @list-selected="handleAiRetrievalSelected" :current-chat-id="selectedChatId" ref="aiRetrieval" + class="ai-retrieval-container" v-show="showAIRetrieval && !isSidebarCollapsed" /> + <chat-history @item-selected="handleHistorySelected" class="ai-retrieval-container" + v-show="!showAIRetrieval && !isSidebarCollapsed" /> + </div> + + <!-- 鍙充晶涓诲唴瀹� --> + <div class="right-content"> + <div class="right-header"> + <h2 class="content-title" > + <span class="header-gradient">AI</span><span style="margin-left: 15px;font-size: 30px">鏂囨悳涓囩墿</span> + </h2> + <div class="header-actions-right"> + <el-button type="primary" class="action-btn" @click="handleRealTimeMonitor"> + 瀹炴椂鐩戞帶 + </el-button> + <el-button type="primary" class="action-btn" @click="resetList()"> + 鍒锋柊鏁版嵁 + </el-button> + </div> + </div> + <!-- 妫�娴嬬粨鏋滃睍绀� --> + <div class="content-wrapper"> + <!-- 娣诲姞鏁版嵁鍔犺浇鐘舵�佹彁绀� --> + <div v-if="isLoading" class="loading-container"> + <el-icon class="is-loading" size="24"> + <Loading /> + </el-icon> + <span>鏁版嵁鍔犺浇涓�...</span> + </div> + <!-- 鏃犻檺婊氬姩瀹瑰櫒 --> + <div class="results-container" @scroll="handleScroll"> + <el-empty v-if="results.length === 0" description="鏆傛棤鏁版嵁" style="margin-top: 50px"></el-empty> + <div v-else class="gallery-section"> + <div class="image-grid-container"> + <div class="image-grid" ref="imageGrid"> + <div v-for="(item, index) in results" :key="index" class="image-card-wrapper" + :style="{ width: cardWidth }"> + <el-card class="result-card" :class="{ 'selected-card': selectedItemId === item.id }" + @click.native.stop="handleCardClick(item)"> + <div class="image-wrapper"> + <el-image slot="error" :src="item.image_path" class="result-image" alt="妫�娴嬬粨鏋�" /> + <!-- <img slot="error" src="@/assets/01.png" class="result-image" alt="妫�娴嬬粨鏋�" /> --> + <div class="image-overlay" v-if="item.is_warning == 1"> + <span class="check-item"> + {{ item.task_names }} + </span> + <el-tag size="mini" class="level-tag"> + {{ item.event_levels }} + </el-tag> + </div> + </div> + <div class="card-content"> + <div class="meta-info"> + <div class="time-info"> + <img src="@/assets/img/time-fill@1x.png" alt="" class="time-icon"> + <span class="detect-time">{{ formatStartDateTime(item.detect_time) }}</span> + <el-popover placement="bottom" width="300" trigger="click" v-model="popoverVisible[index]" + v-if="item.is_desc === 2" class="right-btn2"> + <i class="el-icon-close" @click="closePopover(index)"></i> + <span style="color: black; font-weight: 600">鍥剧墖鍐呭</span> + <div class="task-popover-content"> + {{ item.zh_desc_class }} + </div> + <img src="@/assets/img/article-fill@1x.png" alt="" class="time-icon2" slot="reference" + @click.stop="togglePopover(index)"> + </el-popover> + </div> + <div class="device-info"> + <div class="camera-info"> + <img src="@/assets/img/live-fill@1x.png" alt="" class="time-icon"> + <span>{{ item.video_name }}</span> + <el-dropdown size="small" @command="handleCommand" class="right-btn"> + <img src="@/assets/img/modelTraining.png" + style="width: 16px;height: 16px;margin-left: 10px; vertical-align: middle"> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item + :command="{ ruleName: item.rule_names ? item.rule_names[0].fileName : '', cameraId: item.video_point_id + '', cameraName: item.video_name, imagePath: item.image_path, status: 1 }">姝g‘</el-dropdown-item> + <el-dropdown-item + :command="{ ruleName: item.rule_names ? item.rule_names[0].fileName : '', cameraId: item.video_point_id + '', cameraName: item.video_name, imagePath: item.image_path, status: 2 }">閿欒</el-dropdown-item> + <el-dropdown-item + :command="{ ruleName: item.rule_names ? item.rule_names[0].fileName : '', cameraId: item.video_point_id + '', cameraName: item.video_name, imagePath: item.image_path, status: 0 }">涓嶇‘瀹�</el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> + </div> + </div> + </div> + </div> + </el-card> + </div> + </div> + </div> + </div> + <!-- 婊氬姩鍔犺浇鎻愮ず --> + <div v-if="isLoadingMore" class="loading-more"> + <el-icon class="is-loading" size="16"> + <Loading /> + </el-icon> + <span>鍔犺浇鏇村...</span> + </div> + <div v-if="!hasMore && results.length > 0" class="no-more-data"> + 娌℃湁鏇村鏁版嵁浜� + </div> + + </div> + + <!-- 鍒嗛〉 --> + <!-- <div class="pagination-wrapper" v-if="results.length > 0"> + <el-pagination :current-page="currentPage" :page-size="16" :total="totalResults" + layout="prev, pager, next" @current-change="handlePageChange"> + </el-pagination> + </div> --> + </div> + </div> + </div> + </div> +</template> + +<script> +import _ from 'lodash'; // 鐢ㄤ簬闃叉姈 +import surey from "@/api/SurveyView"; +import aiRetrieval from './AiRetrievalView'; +import chatHistory from './ChatHistoryView'; +import cameraVideo from './cameraVideo.vue'; +export default { + components: { + aiRetrieval, + chatHistory, + cameraVideo + }, + data() { + return { + // 鏂板鍗$墖瀹藉害璁$畻鐩稿叧鏁版嵁 + cardWidth: '300px', // 榛樿鍗$墖瀹藉害 + minCardWidth: 300, // 鍗$墖鏈�灏忓搴� + margin: 20, // 鍗$墖闂磋窛 + idsList: [], + pageSize: 30, // 姣忛〉鍔犺浇鏁版嵁閲� + hasMore: true, // 鏄惁鏈夋洿澶氭暟鎹� + isLoadingMore: false, // 鏄惁姝e湪鍔犺浇鏇村 + scrollTop: 0, // 婊氬姩浣嶇疆璁板綍 + docUrl: "", + isLoading: false, // 鏂板鍔犺浇鐘舵�佸彉閲� + backendData: true, + popoverVisible: {}, // 鐢ㄤ簬鎺у埗姣忎釜鍗$墖鐨勬皵娉℃樉绀虹姸鎬� + dialogVisible: true, + handles: [ + { position: "top" }, + { position: "bottom" }, + { position: "left" }, + { position: "right" }, + { position: "top-left" }, + { position: "top-right" }, + { position: "bottom-left" }, + { position: "bottom-right" }, + ], + isResizing: false, + startX: 0, + startY: 0, + startWidth: 0, + startHeight: 0, + startLeft: 0, + startTop: 0, + realTimeVisible: false, // 鏂板寮圭獥鎺у埗鍙橀噺 + selectedChatId: null, + showAIRetrieval: true, + isSidebarCollapsed: false, + selectedItemId: null, // 鏂板閫変腑椤笽D + filterOptions: { + tasks: [], + levels: [], + cameras: [], + rules: [], + }, + currentMediaType: "image", // 褰撳墠濯掍綋绫诲瀷 + detailVisible: false, + detailItem: {}, + currentPage: 1, + totalResults: 40, // 鎬绘暟鎹噺 + filter: { + keyword: "", + task: "", + timeRange: [], + level: "", + camera: "", + rules: "", + }, + showWarningOnly: true, + results: [ + { + // image_path: require("@/assets/01.png"), + // video_path: require("@/assets/video.mp4"), + risk_description: "闅愭偅鎻忚堪", + detect_time: "2021-12-09 20:23", + video_name: "鎽勫儚鏈篈", + event_level: "1", + is_warning: 1, + event_levels: "涓�绾�", + task_names: "鐢熶骇浠诲姟绠℃帶,鐢熶骇浠诲姟绠℃帶,鐢熶骇浠诲姟绠℃帶,鐢熶骇浠诲姟绠℃帶,鐢熶骇浠诲姟绠℃帶", // 鏀逛负鏁扮粍 + suggestion: "澶勭悊寤鸿", // 澶勭悊寤鸿 + video_point_id: 1, + rule_names: "", + knowledge_documents: [ + { ruleId: 1, fileName: "瀹夊叏鍑嗗垯", file_url: "https://image.baidu.com/search/detail?z=0&word=%E5%9B%BE%E7%89%87&hs=0&pn=1&spn=0&di=7498023338351001601&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&lm=&cs=2629589424%2C2655723787&os=2900564191%2C785059641&simid=3974997321%2C325136453&adpicid=0&lpn=0&fr=click-pic&fm=&ic=&hd=&latest=©right=&isImgSet=&commodity=&hot=&imgratio=&imgformat=&sme=&width=0&height=0&cg=&bdtype=0&oriquery=&objurl=https%3A%2F%2Fww2.sinaimg.cn%2Fmw690%2F61d7678dgy1hvt194v9kqj20p00uuape.jpg&fromurl=ippr_z2C%24qAzdH3FAzdH3Fojtk5_z%26e3Bv54AzdH3F8m98cam0a8AzdH3FP81pPuN3N&gsm=1e&islist=&querylist=&lid=9dec68e500008991" }, + { ruleId: 1, fileName: "瀹夊叏鍑嗗垯", file_url: "http://192.168.1.232:7010/home/debian/GroundingDINO/txt/zs/AI-avatar.png" }, + ], // 棰勮瑙勫垯鏁扮粍 + zh_desc_class: "鍥剧墖鏄剧ず鐨勬槸涓�涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨勫浘鐗囨樉绀虹殑鏄竴涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨�" + }, + { + image_path: "", + risk_description: "闅愭偅鎻忚堪", + detect_time: "2021-12-09 21:15", + video_name: "鎽勫儚鏈築", + is_desc: 2, + zh_desc_class: "鍥剧墖鏄剧ず鐨勬槸涓�涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨勫浘鐗囨樉绀虹殑鏄竴涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨勫浘鐗囨樉绀虹殑鏄竴涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨勫浘鐗囨樉绀虹殑鏄竴涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨勫浘鐗囨樉绀虹殑鏄竴涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨勫浘鐗囨樉绀虹殑鏄竴涓鍐呭満鏅紝鏃堕棿鎴虫樉绀轰负2025骞�6鏈�27鏃ユ槦鏈熶簲16:57:12銆傜敾闈腑鍙互鐪嬪埌涓�涓蛋寤婅繃閬撶殑鐜锛屽彸渚ф湁涓�鎵囩幓鐠冮棬锛岄棬涓婅鐩栫潃钃濊壊鐨勯槻灏樺竷銆傚乏渚у涓婃湁涓�涓粦鏉匡紝涓婇潰鍐欐湁涓�浜涗腑鏂囧拰鑻辨枃鐨勬枃瀛楋紝鍐呭鍖呮嫭鈥滆帿浼熻揪鈥濄�佲�渋nt4鈥濄�佲�淎WQ鈥濄�佲��4G鈥濈瓑銆傞粦鏉挎梺杈规湁涓�涓櫧鑹茬殑鏌滃瓙锛屾煖瀛愪笂鏀剧潃涓�浜涚墿鍝併�傚瑙掑杩樻湁涓�鏍皬鏍戙�傛暣涓埧闂寸殑澧欏鏄櫧鑹茬殑锛屽ぉ鑺辨澘涔熸槸鐧借壊鐨�" + }, + ], + }; + }, + computed: { + normalizedPath() { + return (backendPath) => { + // 鏇挎崲閫昏緫 + return require(backendPath + .replace(/^[a-zA-Z]:/, "") // 鍘婚櫎鐩樼 + .replace(/\/+/g, "/") // 鍚堝苟澶氫綑鏂滄潬 + .replace("/opt/smart", "@/assets")); // 鍏抽敭璺緞鏇挎崲 + }; + }, + visibleResults() { + return this.results.slice( + (this.currentPage - 1) * 8, + this.currentPage * 8 + ); + }, + boxHeight() { + // 鏍规嵁鍚庣鏁版嵁鍐冲畾楂樺害 + // if (!this.backendData) return "380px"; + return this.backendData ? "160px" : "320px"; + }, + }, + mounted() { + // this.fetchFilterOptions();//绛涢�夊垪琛� + this.handleSearch([]); + this.fetchCameraInfo(); + this.resetList(); + this.calculateCardWidth(); + // 娣诲姞闃叉姈鐨剅esize鐩戝惉 + window.addEventListener('resize', _.debounce(this.calculateCardWidth, 100)); + }, + beforeDestroy() { + window.removeEventListener('resize', this.calculateCardWidth); + }, + methods: { + // 鏂板鍗$墖瀹藉害璁$畻鏂规硶 + calculateCardWidth() { + if (this.$refs.imageGrid) { + const containerWidth = this.$refs.imageGrid.clientWidth; + // 璁$畻姣忚鍙互鏀剧疆鐨勫崱鐗囨暟閲忥紝鏈�灏�3鍒� + const cardsPerRow = Math.max(3, Math.floor(containerWidth / (this.minCardWidth + this.margin))); + + // 璁剧疆鍗$墖瀹藉害鍏紡 + this.cardWidth = `calc(${100 / cardsPerRow}% - ${this.margin}px)`; + } + }, + handleCommand(command) { + console.log(JSON.stringify(command)) + surey.insertModelTraining(JSON.stringify(command)).then((res) => { + if (res && res.status === 200) { + this.$notify({ + type: "success", + message: "娣诲姞鎴愬姛", + }); + } else { + this.$notify({ + type: "error", + message: "娣诲姞澶辫触锛�", + }); + } + }); + }, + // 閲嶇疆鍒楄〃 + resetList() { + this.idsList = []; + this.currentPage = 1; + this.hasMore = true; + this.results = []; + this.handleSearch([]); + }, + + // 婊氬姩浜嬩欢澶勭悊 + handleScroll(event) { + const container = event.target; + // 璁板綍婊氬姩浣嶇疆 + this.scrollTop = container.scrollTop; + + // 妫�鏌ユ槸鍚︽粴鍔ㄥ埌搴曢儴 + const isBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 50; + + // 褰撴粴鍔ㄥ埌搴曢儴涓旀湁鏇村鏁版嵁锛屼笖褰撳墠娌℃湁鍔犺浇涓紝鍒欏姞杞芥洿澶� + if (isBottom && this.hasMore && !this.isLoading && !this.isLoadingMore) { + this.loadMoreData(); + } + }, + // 鍔犺浇鏇村鏁版嵁 + async loadMoreData() { + if (this.isLoadingMore || !this.hasMore) return; + + try { + this.isLoadingMore = true; + this.currentPage++; + + const params = { + ids: [], + page: this.currentPage, + pageSize: this.pageSize + }; + + const response = await surey.getSurveys(params); + const lists = response.data.list || []; + if (lists) { + for (let i = 0; i < lists.length; i++) { + console.log("333:" + lists[i].video_point_id) + this.results.push({ + task_names: lists[i].task_name, + video_name: lists[i].video_name, + image_path: "/api-img" + lists[i].image_path, + video_path: "/api-img" + lists[i].video_path, + detect_time: lists[i].detect_time, + event_levels: lists[i].event_level_name, + zh_desc_class: lists[i].zh_desc_class, + is_warning: lists[i].is_warning, + is_desc: lists[i].is_desc, + video_point_id: lists[i].video_point_id, + rule_names: lists[i].rule_names, + knowledge_documents: lists[i].knowledge_documents.map(file => { + return { + ...file, + file_url: "/api-img" + file.file_url, + fileName: file.title + } + }) + }); + } + } + + // 鍚堝苟缁撴灉 + // this.results = [...this.results, ...lists]; + + // 鏇存柊鏄惁鏈夋洿澶氭暟鎹姸鎬� + this.hasMore = lists.length >= this.pageSize; + + } catch (error) { + console.error("鍔犺浇鏇村澶辫触:", error); + } finally { + this.isLoadingMore = false; + } + }, + async fetchCameraInfo() { + const response = await fetch("/config.json"); + if (!response.ok) throw new Error(`璇锋眰澶辫触: ${response.status}`); + const responseData = await response.json(); + this.docUrl = responseData.docUrl; + console.info("docUrl:" + this.docUrl) + }, + closePopover(index) { + this.$set(this.popoverVisible, index, false); + }, + startResize(position, e) { + this.isResizing = true; + const dialog = document.querySelector(".resizable-dialog"); + + this.startX = e.clientX; + this.startY = e.clientY; + this.startWidth = dialog.offsetWidth; + this.startHeight = dialog.offsetHeight; + this.startLeft = dialog.offsetLeft; + this.startTop = dialog.offsetTop; + + document.addEventListener("mousemove", this.handleResize(position)); + document.addEventListener("mouseup", this.stopResize); + }, + handleResize(position) { + return (e) => { + if (!this.isResizing) return; + + const dialog = document.querySelector(".resizable-dialog"); + const deltaX = e.clientX - this.startX; + const deltaY = e.clientY - this.startY; + + // 闄愬埗鏈�灏忓昂瀵� + const minWidth = 400; + const minHeight = 300; + + switch (position) { + case "top": + dialog.style.height = + Math.max(minHeight, this.startHeight - deltaY) + "px"; + dialog.style.top = `${this.startTop + deltaY}px`; + break; + case "bottom": + dialog.style.height = + Math.max(minHeight, this.startHeight + deltaY) + "px"; + break; + case "left": + dialog.style.width = + Math.max(minWidth, this.startWidth - deltaX) + "px"; + dialog.style.left = `${this.startLeft + deltaX}px`; + break; + case "right": + dialog.style.width = + Math.max(minWidth, this.startWidth + deltaX) + "px"; + break; + case "top-left": + dialog.style.width = + Math.max(minWidth, this.startWidth - deltaX) + "px"; + dialog.style.height = + Math.max(minHeight, this.startHeight - deltaY) + "px"; + dialog.style.left = `${this.startLeft + deltaX}px`; + dialog.style.top = `${this.startTop + deltaY}px`; + break; + case "top-right": + dialog.style.width = + Math.max(minWidth, this.startWidth + deltaX) + "px"; + dialog.style.height = + Math.max(minHeight, this.startHeight - deltaY) + "px"; + dialog.style.top = `${this.startTop + deltaY}px`; + break; + case "bottom-left": + dialog.style.width = + Math.max(minWidth, this.startWidth - deltaX) + "px"; + dialog.style.height = + Math.max(minHeight, this.startHeight + deltaY) + "px"; + dialog.style.left = `${this.startLeft + deltaX}px`; + break; + case "bottom-right": + dialog.style.width = + Math.max(minWidth, this.startWidth + deltaX) + "px"; + dialog.style.height = + Math.max(minHeight, this.startHeight + deltaY) + "px"; + break; + } + }; + }, + + stopResize() { + this.isResizing = false; + document.removeEventListener("mousemove", this.handleResize); + document.removeEventListener("mouseup", this.stopResize); + }, + // 瀹炴椂鐩戞帶鏂规硶 + handleRealTimeMonitor() { + this.realTimeVisible = true; + }, + handleHistorySelected(chatId) { + this.showAIRetrieval = true; // 鍒囨崲缁勪欢 + this.selectedChatId = chatId; // 淇濆瓨chatId + // this.$nextTick(() => { + // this.$refs.aiRetrieval.loadChat(); // 瑙﹀彂瀛愮粍浠跺姞杞� + // }); + }, + handleAiRetrievalSelected(params) { + console.info("params:" + params) + //鏍规嵁id鏌ヨ鏁版嵁 + this.idsList = params; + this.handleSearch() + }, + handleHistoryClick() { + this.showAIRetrieval = false; + this.isSidebarCollapsed = false; + }, + handleNewSession() { + // 璋冪敤AI妫�绱㈢粍浠剁殑閲嶇疆鏂规硶 + if ( + this.$refs.aiRetrieval && + typeof this.$refs.aiRetrieval.reset === "function" + ) { + this.$refs.aiRetrieval.reset(); + } + // 淇濇寔渚ц竟鏍忓睍寮�鐘舵�� + this.isSidebarCollapsed = false; + this.showAIRetrieval = true; + }, + toggleSidebar() { + this.isSidebarCollapsed = !this.isSidebarCollapsed; + }, + // 淇敼鍚庣殑鐐瑰嚮澶勭悊鏂规硶 + handleCardClick(item) { + this.selectedItemId = item.id; + this.showDetail(item); + }, + // getLevelType(level) { + // const typeMap = { + // '1': 'danger', + // '2': 'warning', + // '3': 'primary', + // '4': 'info', + // '5': 'success' + // } + // return typeMap[level] || 'info' + // }, + // 鑾峰彇绛涢�夋潯浠堕�夐」 + + async fetchFilterOptions() { + try { + const tasks = await surey.getTasks({ + //妫�娴嬪唴瀹� + page: 1, + pageSize: 999, + }); + this.filterOptions.tasks = tasks.data.list; + const cameras = await surey.getCameras(); //瑙嗛鐐逛綅 + this.filterOptions.cameras = cameras.data; + const warnings = await surey.getWarnings({ + //棰勮瑙勫垯 + page: 1, + pageSize: 999, + }); + this.filterOptions.rules = warnings.data.list; + + const events = await surey.getEvents(); //浜嬩欢绛夌骇 + this.filterOptions.levels = events.data; + // console.info(this.filterOptions.levels) + } catch (error) { + console.error("鑾峰彇绛涢�夐�夐」澶辫触:", error); + this.$message.error("绛涢�夐�夐」鍔犺浇澶辫触"); + } + }, + // 鏍煎紡鍖栧紑濮嬫椂闂达紙淇濇寔鍘熸椂闂达級 + formatStartDateTime(date) { + if (!date) return null; + const d = new Date(date); + const pad = (num) => num.toString().padStart(2, "0"); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad( + d.getDate() + )} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; + }, + + // 鏍煎紡鍖栫粨鏉熸椂闂达紙鍥哄畾涓� 23:59:59锛� + formatEndDateTime(date) { + if (!date) return null; + const d = new Date(date); + d.setHours(23, 59, 59); // 寮哄埗璁剧疆涓哄綋澶╃殑 23:59:59 + const pad = (num) => num.toString().padStart(2, "0"); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad( + d.getDate() + )} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; + }, + // 鏌ヨ鏂规硶 + async handleSearch() { + console.info("this.idsList:" + this.idsList) + this.isLoading = true; // 寮�濮嬪姞杞� + // console.info(ids) + try { + this.currentPage = 1; + const params = { + ids: this.idsList, + page: 1, + pageSize: this.pageSize, + }; + + const response = await surey.getSurveys(params); + const lists = response.data.list || []; + this.results = []; + if (lists) { + for (let i = 0; i < lists.length; i++) { + this.results.push({ + task_names: lists[i].task_name, + video_name: lists[i].video_name, + image_path: "/api-img" + lists[i].image_path, + video_path: "/api-img" + lists[i].video_path, + detect_time: lists[i].detect_time, + event_levels: lists[i].event_level_name, + zh_desc_class: lists[i].zh_desc_class, + is_warning: lists[i].is_warning, + is_desc: lists[i].is_desc, + video_point_id: lists[i].video_point_id, + rule_names: lists[i].rule_names, + knowledge_documents: lists[i].knowledge_documents.map(file => { + return { + ...file, + file_url: "/api-img" + file.file_url, + fileName: file.title + } + }), + risk_description: lists[i].risk_description, + suggestion: lists[i].suggestion + }); + } + } + this.totalResults = response.data.pagination.total; + // 鏇存柊鏄惁鏈夋洿澶氭暟鎹姸鎬� + this.hasMore = lists.length >= this.pageSize; + // console.info(response.data) + } catch (error) { + console.error("鏌ヨ澶辫触:", error); + this.$message.error("鏌ヨ澶辫触锛岃绋嶅悗閲嶈瘯"); + } finally { + this.isLoading = false; // 缁撴潫鍔犺浇 + } + }, + // 鏂板璇︽儏灞曠ず鏂规硶 + showDetail(item) { + // console.info(item) + this.backendData = item.is_warning == 1 ? true : false; + // console.info(item.is_warning) + this.currentMediaType = "image"; + this.detailItem = { + ...item, + task: "鐢熶骇浠诲姟绠℃帶", // 鏍规嵁鍥剧墖淇℃伅娣诲姞浠诲姟鍚嶇О + }; + // this.detailItem.image_path = 'http://192.168.1.232:7010/' + item.image_path + // this.detailItem.video_path = 'http://192.168.1.232:7010/' + item.video_path + this.detailItem.image_path = item.image_path; + this.detailItem.video_path = item.video_path; + this.detailItem.is_warning = item.is_warning; + this.detailItem.suggestion = item.suggestion; + this.detailItem.risk_description = item.risk_description; + this.detailVisible = true; + // console.info(item) + }, + // 鍒嗛〉澶勭悊 + handlePageChange(page) { + this.currentPage = page; + this.handleSearch([]); + }, + // 閲嶇疆绛涢�夋潯浠� + resetFilter() { + this.filter = { + keyword: "", + task: "", + timeRange: [], + level: "", + camera: "", + }; + this.currentPage = 1; + this.handleSearch([]); + }, + // 鑾峰彇鏂囨。棰勮URL + getPreviewUrl(doc) { + // 鑾峰彇鏂囦欢鎵╁睍鍚� + const extension = this.getFileExtension(doc.fileName); + + // PDF浣跨敤鐩存帴璁块棶 + if (extension === 'pdf') { + return doc.file_url; + } + + // Office鏂囨。浣跨敤寰蒋棰勮鏈嶅姟 + // if (['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'].includes(extension)) { + // return `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(doc.file_url)}`; + // } + + // 鍏朵粬绫诲瀷鐩存帴杩斿洖 + return doc.file_url; + }, + getPreviewUrl2(id) { + window.location.href = "/api/v1/knowledge/download?id=" + id + // window.location.href="http://192.168.1.176:8088/v1/knowledge/download?id="+id + }, + + // 鑾峰彇鏂囦欢鎵╁睍鍚� + getFileExtension(filename) { + const parts = filename.split('.'); + return parts.length > 1 ? parts.pop().toLowerCase() : ''; + }, + }, + watch: { + detailVisible(newVal) { + if (!newVal) { + this.selectedItemId = null; // 鍏抽棴寮圭獥鏃舵竻闄ら�変腑鐘舵�� + } + }, + }, +}; +</script> +<style> +/* 鍏ㄥ眬鐢熸晥锛屽彲瑕嗙洊 tooltip */ +.my-tooltip { + max-width: 540px !important; + color: #606266 !important; +} +</style> +<style lang="scss" scoped> + +/* 鏂板鍥剧墖缃戞牸甯冨眬鏍峰紡 */ +.image-grid-container { + width: 100%; + overflow: hidden; /* 瓒呭嚭閮ㄥ垎闅愯棌 */ +} + +.image-grid { + display: flex; + flex-wrap: wrap; + margin: -10px; /* 璐熻竟璺濇姷娑堝寘瑁瑰厓绱犵殑杈硅窛 */ + width: 100%; +} + +.image-card-wrapper { + margin: 10px; /* 璁剧疆鍗$墖闂磋窛 */ + // box-sizing: border-box; + // transition: width 0.3s ease; /* 娣诲姞骞虫粦杩囨浮鏁堟灉 */ + min-width: 300px; /* 鍗$墖鏈�灏忓搴� */ + flex-shrink: 0; /* 闃叉鍗$墖缂╁皬 */ +} + + +.right-btn { + position: absolute; + right: 8%; + top: 50%; + transform: translateY(-50%); + /* 鍨傜洿灞呬腑 */ +} +.right-btn2 { + position: absolute; + right: 10.5%; + top: 74.5%; + transform: translateY(-50%); + /* 鍨傜洿灞呬腑 */ +} + +.time-icon { + width: 16px; + height: 16px; + margin-right: 20px; + vertical-align: middle; +} + +.time-icon2 { + width: 16px; + height: 16px; + margin-right: 0px; + vertical-align: middle; +} + +/* 鍏抽棴鎸夐挳鏍峰紡 */ +.el-icon-close { + position: absolute; + top: 10px; + right: 5px; + padding: 0; + width: 24px; + height: 24px; + font-size: 14px; + color: #909399; + background: none; + border: none; + cursor: pointer; + transition: all 0.2s; +} + +/* 鏂板姘旀场鍐呭鏍峰紡 */ +.task-popover-content { + max-height: 100px; + overflow-y: auto; + padding: 20px 15px 15px 15px; + /* 澧炲姞椤堕儴绌洪棿缁欏叧闂寜閽� */ + line-height: 1.5; + word-break: break-word; + white-space: pre-line; + border-radius: 10px; + + /* 鑷畾涔夋粴鍔ㄦ潯鏍峰紡 */ + &::-webkit-scrollbar { + width: 4px; + /* 缂╁皬婊氬姩鏉″搴� */ + background-color: transparent; + } + + &::-webkit-scrollbar-track { + background: transparent; + /* 闅愯棌杞ㄩ亾 */ + } + + &::-webkit-scrollbar-thumb { + background-color: #c0c4cc; + /* 璁剧疆婊戝潡棰滆壊 */ + border-radius: 2px; + } + + /* 绉婚櫎婊氬姩鏉$澶� */ + &::-webkit-scrollbar-button { + display: none; + /* 闅愯棌涓婁笅绠ご */ + } +} + +.resizable-dialog { + position: fixed !important; + margin: 0 !important; + padding-top: 0px; + min-width: 200px; + min-height: 300px; + overflow: auto; + top: 50% !important; + left: 50% !important; + transform: translate(-50%, -50%) !important; +} + +.resize-handle { + position: absolute; + background: #409eff !important; + z-index: 9999; + opacity: 0; + transition: opacity 0.2s; +} + +.resize-handle:hover { + opacity: 1; +} + +/* 鍚勬柟鍚戞墜鏌勫畾浣� */ +.top { + top: 0; + left: 0; + right: 0; + height: 4px; + cursor: ns-resize; +} + +.bottom { + bottom: 0; + left: 0; + right: 0; + height: 4px; + cursor: ns-resize; +} + +.left { + top: 0; + left: 0; + width: 4px; + bottom: 0; + cursor: ew-resize; +} + +.right { + top: 0; + right: 0; + width: 4px; + bottom: 0; + cursor: ew-resize; +} + +.top-left { + top: 0; + left: 0; + width: 10px; + height: 10px; + cursor: nwse-resize; +} + +.top-right { + top: 0; + right: 0; + width: 10px; + height: 10px; + cursor: nesw-resize; +} + +.bottom-left { + bottom: 0; + left: 0; + width: 10px; + height: 10px; + cursor: nesw-resize; +} + +.bottom-right { + bottom: 0; + right: 0; + width: 10px; + height: 10px; + cursor: nwse-resize; +} + +.el-dialog__body { + height: calc(100% - 54px); + overflow: auto; +} + +.fold-icon { + width: 24px; + height: 24px; + cursor: pointer; + transition: transform 0.3s ease; + margin: 8px; + + &:hover { + opacity: 0.8; + filter: drop-shadow(0 0 2px rgba(11, 113, 216, 0.5)); + } + + &.reverse { + transform: rotate(180deg); + } +} + +/* ai-retrieval瀹瑰櫒鏍峰紡 */ +.ai-retrieval-container { + position: absolute; + // left: 5px; + width: 360px; + // height: 1000px; + z-index: 1000; + background: rgb(245, 244, 244); + // box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); +} + +/* 鏂板澶氬�兼樉绀烘牱寮� */ +.multi-value { + display: inline-block; + max-width: 540px; + // white-space: nowrap; + overflow: hidden; + // text-overflow: ellipsis; +} + +.desc_class { + // max-height: 380px; + overflow-y: auto; +} + +.multi-value2 { + display: inline-block; + // max-height: 380px; +} + +.stats-header { + margin-bottom: 20px; + + .ai-avatar { + display: flex; + align-items: flex-start; // 椤堕儴瀵归綈 + + .header-text { + margin-left: 10px; + + .content-title { + text-align: left; + margin: 5px 0 10px; + color: #0e3eaa; + } + + .stats-numbers { + font-size: 12px; + text-align: left; + } + } + + .header-actions { + flex: 1; + text-align: right; + margin-top: 10px; + } + } +} + +.results-container { + padding: 0 15px; + // min-height: 600px; + overflow-x: hidden; /* 闅愯棌妯悜婊氬姩鏉� */ + overflow-y: auto; /* 淇濈暀绾靛悜婊氬姩 */ + /* 鏂板婊氬姩鏉� */ + height: 900px; + + + + .result-card { + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + border: none; + position: relative; // 涓虹粷瀵瑰畾浣嶆彁渚涘熀鍑� + overflow: visible; // 闃叉鎸夐挳琚鍓� + + .image-wrapper { + position: relative; + // height: 200px; + overflow: visible; + border-radius: 6px 6px 0 0; + + .result-image { + width: 100%; + // height: 130px; + object-fit: cover; + transition: transform 0.3s; + } + + .image-overlay { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.6); + /* 鍗婇�忔槑榛戣壊鑳屾櫙 */ + padding: 5px 10px; + display: flex; + justify-content: space-between; + align-items: center; + z-index: 2; + /* 纭繚鍦ㄥ浘鐗囦笂鏂� */ + } + + .check-item { + color: white; + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 70%; + } + + .level-tag { + z-index: 3; + /* 纭繚鍦ㄨ鐩栧眰涓婃柟 */ + transform: scale(0.9); + margin: 2px; + color: #fa0505; + background-color: #f3e1e1; + } + } + + .card-content { + padding: 15px; + + .meta-info { + .time-info { + display: flex; + text-align: left; + align-items: center; + margin-bottom: 10px; + color: #909399; + width: 100%; + margin-bottom: 10px; + + .el-icon-time { + color: #409eff !important; + margin-right: 20px; + font-size: 16px; + } + + span { + font-size: 13px; + margin-right: 20px; + } + + .el-icon-document { + color: #034914; + } + + .detect-time { + flex-shrink: 0; + color: #909399; + font-size: 13px; + } + } + + .device-info { + // display: flex; + justify-content: space-between; + align-items: center; + + .camera-info { + // display: flex; + position: relative; + text-align: left; + align-items: center; + + i { + color: #409eff !important; + margin-right: 20px; + font-size: 16px; + } + + span { + font-size: 14px; + color: #303133; + } + } + } + } + } + } +} + +.pagination-wrapper { + // position: fixed; + right: 30px; + bottom: 30px; + background: #fff; + padding: 10px 20px; + border-radius: 4px; + // box-shadow: 0 2px 12px rgba(0,0,0,0.1); +} + +// 璋冩暣甯冨眬缁撴瀯 +.statistics-container { + display: flex; + height: 98vh; // 鏂板 + + .left-sidebar { + width: 320px; + transition: all 0.3s ease; + left: 0; + // height: 100vh; + z-index: 1000; + background: #fff; + box-shadow: 1px 0 10px rgba(0, 0, 0, 0.1); + + padding: 10px; + border-right: 1px solid #ebeef5; + position: relative; + padding-bottom: 40px; + /* 涓哄簳閮ㄦ寜閽暀鍑虹┖闂� */ + /* 涓虹粷瀵瑰畾浣嶅垱寤哄弬鐓� */ + + .session-buttons { + // position: absolute; + bottom: 20px; + left: 20px; + right: 20px; + display: flex; + gap: 0; + + .el-button { + height: 50px; + border-radius: 5px; + transition: all 0.3s ease; + background-color: #e1ebff !important; + color: #2482ff; + border: 1px solid #e1ebff !important; + font-size: 16px !important; + padding: 0 12px; + } + + ::v-deep .el-button { + background-color: #e1ebff !important; + } + } + + &.collapsed { + width: 40px; + + .ai-avatar { + flex-direction: column; + align-items: center; + padding: 10px 0; + + .avatar-img { + margin: 0; + width: 51px; + height: 35px; + } + } + + .header-actions i { + transform: rotate(180deg); + margin-top: 10px; + } + } + + .header-actions { + i { + transition: all 0.3s ease; + cursor: pointer; + font-size: 20px; + color: #666; + + &:hover { + color: #0b71d8; + } + + &.reverse { + transform: rotate(180deg); + } + } + } + + .right-content { + transition: margin 0.3s ease; + padding: 20px; + min-height: 100vh; + background: #f5f6fa; + } + + .ai-retrieval-container { + width: 100%; + height: calc(100% - 150px); + /* 鏍规嵁瀹為檯甯冨眬璋冩暣楂樺害 */ + position: relative; + box-shadow: none; + background: #fff; + } + + .el-checkbox { + margin-left: 15px; + display: block; + text-align: left; + } + + .el-form-item__content { + margin-left: 0 !important; + } + } + + .right-content { + flex: 1; + margin-left: 20px; + position: relative; + box-shadow: 1px 0 10px rgba(0, 0, 0, 0.1); + padding: 20px; + padding-top: 0px; + border-left: 1px solid #ebeef5; + + .right-header { + display: flex; + margin-bottom: 20px; + border-bottom: 1px solid #ebeef5; + padding-bottom: 10px; + justify-content: space-between; + height: 70px; + /* 鏂板锛氬乏鍙充袱绔榻� */ + + .content-title{ + text-align: left; + margin: 15px 0 0 0; + } + .header-actions-right { + margin-top: 20px; + + .action-btn { + height: 40px; + margin-bottom: 0px; + padding: 10px 20px; + border-radius: 6px; + letter-spacing: 0.5px; + } + } + } + } +} + +.form-actions { + margin-top: 50px; + display: flex; + gap: 10px; // 鎸夐挳闂磋窛 + + .full-width-btn { + flex: 1; // 绛夊垎鍓╀綑绌洪棿 + margin-left: 0 !important; + } + + // 娓呴櫎Element榛樿杈硅窛 + ::v-deep .el-button { + margin-left: 0; + margin-right: 0; + } +} + +// 璇︽儏寮圭獥鏍峰紡 +.detail-dialog { + .el-dialog__header { + border-bottom: 1px solid #ebeef5; + } + + .media-switch { + margin: -50px 0 15px; + text-align: left; + + .media-tab { + &.is-active { + background: #409eff !important; + border-color: #409eff !important; + color: white; + } + } + } + + .media-container { + height: 350px; + border: 1px solid #ebeef5; + border-radius: 4px; + margin-bottom: 20px; + position: relative; + overflow: hidden; // 娣诲姞婧㈠嚭闅愯棌 + + .main-image { + height: 100%; + padding: 0px; + + .detail-img { + width: 100%; + height: 100%; + object-fit: contain; + } + } + + .video-section { + height: 100%; + width: 100%; + + .video-placeholder { + width: 100%; + height: 100%; + position: relative; + + video { + max-width: 100%; + max-height: 100%; + display: block; + margin: 0 auto; + } + } + } + } + + .info-container { + height: 400px; + // overflow-y: auto; + + ::v-deep .el-descriptions__body { + background: white; + } + + .info-item { + padding: 12px 10px; + + &>.el-descriptions-item__label { + color: #606266; + width: 90px; + } + } + + .red-text { + color: #f56c6c !important; + } + + .blue-text { + color: #409eff !important; + } + + .doc-link { + padding-left: 10px; + text-decoration: none; + color: #1b50e4; + // display: inline-block; + // padding: 5px 8px; + // border-radius: 4px; + // transition: all 0.3s; + // color: #165DFF; + + &:hover { + cursor: pointer; + } + } + + ::v-deep .el-descriptions-item__label { + color: black !important; + font-weight: 600 !important; + } + } +} + +/* 鍙充晶瀵归綈鏍峰紡 */ +.detail-dialog { + position: fixed !important; + top: 50% !important; + left: auto !important; + right: 20px !important; + transform: translateY(-50%) !important; + width: 660px !important; + margin: 0 !important; + z-index: 2000 !important; +} + +::v-deep .el-dialog { + + // margin-right: 0px; + .el-divider--horizontal { + margin: 12px 0; + } +} + +/* 琛ㄦ牸琛ㄥご鏍峰紡 */ +.header-gradient { + background: radial-gradient(circle at 20% 30%, #0e5397, #2482FF); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-weight: bold; + font-size: 35px; +} + +.header-gradient2 { + font-size: 18px; + background: radial-gradient(circle at 20% 30%, #165DFF, #01040a); + -webkit-background-clip: text; + background-clip: text; +} + +/* 鏂板鍔犺浇鐘舵�佹牱寮� */ +.loading-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 300px; + color: #606266; + font-size: 16px; + + .el-icon { + margin-bottom: 15px; + animation: rotating 1.5s linear infinite; + + @keyframes rotating { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } + } +} + +.ellipsis { + display: inline-block; + /* 鎴� block */ + // max-width: 100%; /* 鏍规嵁瀹為檯瀹瑰櫒瀹藉害璋冩暣 */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + /* 鍏抽敭锛氱姝㈡崲琛� */ +} +</style> \ No newline at end of file diff --git a/src/pages/searchNew/components/cameraVideo.vue b/src/pages/searchNew/components/cameraVideo.vue new file mode 100644 index 0000000..eec1bb7 --- /dev/null +++ b/src/pages/searchNew/components/cameraVideo.vue @@ -0,0 +1,232 @@ +<template> + <div class="container"> + <!-- 甯冨眬鎺у埗鍙婃挱鏀惧湴鍧�閫夋嫨 --> + <div class="layout-controls"> + <el-select + v-model="selectedUrl" + placeholder="閫夋嫨鎾斁鍦板潃" + class="left-control" + style="width: 200px; margin-right: 20px" + @change="handleUrlChange" + :disabled="activeIndex === -1" + > + <el-option + v-for="item in playbackOptions" + :key="item.videoId" + :label="item.deviceName" + :value="item.videoId" + /> + </el-select> + <el-button-group class="right-control"> + <el-button + :type="gridLayout === 1 ? 'primary' : ''" + @click="changeLayout(1)" + > + 鍗曟牸 + </el-button> + <el-button + :type="gridLayout === 4 ? 'primary' : ''" + @click="changeLayout(4)" + > + 鍥涙牸 + </el-button> + <el-button + :type="gridLayout === 9 ? 'primary' : ''" + @click="changeLayout(9)" + > + 涔濇牸 + </el-button> + </el-button-group> + </div> + + <!-- 瑙嗛缃戞牸甯冨眬 --> + <el-row :gutter="5"> + <el-col + v-for="(item, index) in showVideos" + :key="index" + :span="colSpan" + class="video-col" + :class="{ active: activeIndex === index }" + > + <div class="video-container" + @click="handleGridClick(index)"> + <!-- 绾粦鑳屾櫙灏侀潰 --> + <div + class="video-cover" + v-show="!item.playing" + style="pointer-events: none" + /> + + <!-- 瑙嗛鎾斁鍣� --> + <iframe + v-show="item.playing" + class="video-iframe" + :src="item.url" + frameborder="0" + allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" + allowfullscreen + style="pointer-events: none" + ></iframe> + </div> + </el-col> + </el-row> + </div> +</template> + +<script> +import camera from "@/api/SurveyView" +export default { + name: 'cameraVideo', + data() { + return { + iframeUrl:'', + gridLayout: 1, + activeIndex: -1, + selectedUrl: '', + playbackOptions: [ + { + label: '鎽勫儚澶�1', + value: 'rtsp://Admin:1234@192.168.1.209/h264' + }, + { + label: '鎽勫儚澶�2', + value: 'http://example.com/another-video-source' + } + ], + allVideos: Array(9).fill(null).map((_, i) => ({ + title: `瑙嗛 ${i + 1}`, + url: '', + playing: false + })) + } + }, + computed: { + showVideos() { + return this.allVideos.slice(0, this.gridLayout) + }, + colSpan() { + return 24 / Math.sqrt(this.gridLayout) + } + }, + mounted() { + this.fetchSelect(); + this.fetchCameraInfo() +}, + methods: { + async fetchCameraInfo() { + const response = await fetch('/config.json'); + if (!response.ok) throw new Error(`璇锋眰澶辫触: ${response.status}`); + const responseData = await response.json(); + this.iframeUrl = responseData.iframeUrl; + console.info(this.iframeUrl) + }, + async fetchSelect(){ + const cameras = await camera.getCameras();//瑙嗛鐐逛綅 + this.playbackOptions = cameras.data + }, + changeLayout(num) { + this.gridLayout = num + this.activeIndex = -1 + this.resetAllVideos() + }, + handleGridClick(index) { + this.activeIndex = index + console.info('鐐瑰嚮浜嬩欢鐢熸晥') + }, + handleUrlChange(val) { + console.info(process.env.NODE_ENV) + if (this.activeIndex === -1) return + // console.info('鐐瑰嚮閫変腑鐨勬暟鎹細'+this.playbackOptions.find(item => item.videoId === val).rtspAddress) + const currentVideo = this.allVideos[this.activeIndex] + currentVideo.url = this.iframeUrl+`/view/cameraPlayer/index.html?rtspUrl=${encodeURIComponent(this.playbackOptions.find(item => item.videoId === val).rtspAddress)}` + currentVideo.playing = true + + console.info("鍦板潃锛�"+currentVideo.url) + }, + resetAllVideos() { + this.allVideos.forEach((video, index) => { + if (index >= this.gridLayout) { + video.playing = false + video.url = '' + } + }) + } + } +} +</script> + +<style scoped> +/* 绠�鍖栧悗鐨勬牱寮� */ +.container { + padding-top: 80px; + position: relative; +} + +.layout-controls { + position: absolute; + left: 60px; + right: 60px; /* 鏂板鍙充晶瀵归綈 */ + top: 20px; + z-index: 1000; + display: flex; + justify-content: space-between; /* 鍏抽敭灞炴�� */ + align-items: center; +} +/* 绉婚櫎鍘熸湁鐨� margin-right */ +.el-select { + width: 200px; +} +/* 鍙�夛細闃叉鎸夐挳缁勬崲琛� */ +.el-button-group { + flex-shrink: 0; +} + +.video-col { + position: relative; + margin-bottom: 5px; + transition: all 0.3s; + min-height: 100px; +} + +.video-col.active::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 3px solid #409EFF; + pointer-events: none; + z-index: 2; +} + +.video-container { + position: relative; + width: 100%; + height: 0; + padding-top: 56.25%; + cursor: pointer; + background-color: #000; + overflow: hidden; +} + +.video-cover { + pointer-events: none; /* 鍏佽浜嬩欢绌块�� */ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #000; /* 绾粦鑳屾櫙 */ +} + +.video-iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + pointer-events: none; +} +</style> \ No newline at end of file diff --git a/src/pages/searchNew/index/App.vue b/src/pages/searchNew/index/App.vue new file mode 100644 index 0000000..508ff07 --- /dev/null +++ b/src/pages/searchNew/index/App.vue @@ -0,0 +1,131 @@ +<template> + <div class="column"> + <div class="column-right"> + <survey-view ref="cardlist"/> + </div> + </div> +</template> + +<script> +import { getUrlKey } from "@/api/utils"; +import surveyView from "../components/SurveyView"; + +export default { + name: "VideoManage", + components: { + surveyView + }, + computed: { + app() { + return getUrlKey("dataStack") !== null ? "DataStack" : "Camera"; + }, + }, + data() { + return { + leftWith: 0, + screenHeight: 0, + }; + }, + mounted() { + this.screenHeight = document.documentElement.clientHeight; + window.onresize = () => { + return (() => { + this.screenHeight = document.documentElement.clientHeight; + })(); + }; + + this.leftWith = this.$refs["left"].offsetWidth; + this.TreeDataPool.readonly = false; + this.TreeDataPool.gbReadonly = false; + this.DataStackPool.readonly = false; + }, + methods: { + changeTrainId(trainId){ + if (this.$refs.cardlist) { + this.$refs.cardlist.changeTrainId(trainId); + } + } + }, +}; +</script> + +<style lang="scss" scoped> +.column { + overflow: hidden; + //min-width: 1399px; + //min-width: 1920px; + height: 100%; +} +.column-right { + padding: 5px; + height: 100vh; + // background-color: #eee; + box-sizing: border-box; + overflow: scroll; +} +.heigher-index { + position: absolute; + top: 0; + z-index: 10; + width: 100%; + height: 100%; +} +.resize-save { + position: absolute; + top: 0; + right: 5px; + bottom: 0; + left: 0; + padding: 16px; + padding-top: 8px; + overflow-x: hidden; + overflow-y: auto; +} +.resize-bar { + width: 338px; + height: inherit; + resize: horizontal; + cursor: ew-resize; + opacity: 0; + overflow: scroll; + max-width: 500px; //璁惧畾鏈�澶ф媺浼搁暱搴� + min-width: 33px; //璁惧畾鏈�灏忓搴� +} +/* 鎷栨嫿绾� */ +.resize-line { + position: absolute; + right: 0; + top: 0; + bottom: 0; + border-right: 2px solid #efefef; + border-left: 1px solid #e0e0e0; + pointer-events: none; +} +.resize-bar:hover ~ .resize-line, +.resize-bar:active ~ .resize-line { + border-left: 1px dashed skyblue; +} +.resize-bar::-webkit-scrollbar { + width: 200px; + height: inherit; +} + +/* Firefox鍙湁涓嬮潰涓�灏忓潡鍖哄煙鍙互鎷変几 */ +@supports (-moz-user-select: none) { + .resize-bar:hover ~ .resize-line, + .resize-bar:active ~ .resize-line { + border-left: 1px solid #bbb; + } + .resize-bar:hover ~ .resize-line::after, + .resize-bar:active ~ .resize-line::after { + content: ""; + position: absolute; + width: 16px; + height: 16px; + bottom: 0; + right: -8px; + // background: url(./resize.svg); + background-size: 100% 100%; + } +} +</style> diff --git a/src/pages/searchNew/index/main.ts b/src/pages/searchNew/index/main.ts new file mode 100644 index 0000000..aa3df71 --- /dev/null +++ b/src/pages/searchNew/index/main.ts @@ -0,0 +1,30 @@ +import Vue from "vue"; +import App from './App.vue'; + +import ElementUI from 'element-ui'; +import 'element-ui/lib/theme-chalk/index.css'; +// import "@/assets/css/element-variables.scss"; + +import ToggleButton from 'vue-js-toggle-button'; +import VueAwesomeSwiper from "vue-awesome-swiper"; +import "swiper/dist/css/swiper.css"; +import * as VueWindow from "@hscmap/vue-window"; +import moment from 'moment'; +import Mixin from "./mixins"; + +Vue.prototype.$moment = moment; +Vue.use(ElementUI); +Vue.use(ToggleButton); +Vue.use(VueAwesomeSwiper as any); +Vue.use(VueWindow); +Vue.filter('moment', function (value, formatString) { + formatString = formatString || 'YYYY-MM-DD HH:mm:ss'; + return moment(value).format(formatString); + +}); +Vue.mixin(Mixin); + +new Vue({ + el: '#app', + render: h => h(App) +}) diff --git a/src/pages/searchNew/index/mixins.ts b/src/pages/searchNew/index/mixins.ts new file mode 100644 index 0000000..52fb92b --- /dev/null +++ b/src/pages/searchNew/index/mixins.ts @@ -0,0 +1,25 @@ +import TreeDataPool from "@/Pool/TreeData"; +import DataStackPool from "@/Pool/dataStack" +import DataPool from "@/Pool/PollData" +import VideoManageData from "@/Pool/VideoManageData"; +import TaskMange from '@/Pool/TaskMange' + +/* eslint-disable */ +const onlyTreeDataPool = new TreeDataPool +const onlyDataStack = new DataStackPool +const onlyDataPool = new DataPool +const onlyVideoManageData = new VideoManageData +const onlyTaskMange = new TaskMange + +const mixin = { + data() { + return { + TreeDataPool: onlyTreeDataPool, + DataStackPool: onlyDataStack, + VideoManageData: onlyVideoManageData, + TaskMange: onlyTaskMange, + PollData: onlyDataPool + }; + }, +}; +export default mixin; \ No newline at end of file -- Gitblit v1.8.0