| @ -0,0 +1,88 @@ | |||
| // 告警确认 | |||
| import request from '@/utils/request' | |||
| // 巡视结果列表 | |||
| export function getPatrolData(query) { | |||
| return request({ | |||
| url: '/patrol/statistics/listMulti', | |||
| method: 'get', | |||
| params: query | |||
| }) | |||
| } | |||
| /** | |||
| * 获取设备列表 | |||
| * @returns | |||
| */ | |||
| export function getDeviceTree(){ | |||
| return request({ | |||
| url:'/basedata/prodevmnt/device/treeAreaDeviceSelect', | |||
| method:'get' | |||
| }) | |||
| } | |||
| // 获取数据趋势左侧的设备树 | |||
| export function treeAreaDeviceSelectFilterByMeter() { | |||
| return request({ | |||
| url: '/basedata/prodevmnt/device/treeAreaDeviceSelectFilterByMeter', | |||
| method: 'get' | |||
| }) | |||
| } | |||
| // ?deviceId=1&recognitionType=1&startTime=2020-12-1&endTime=2022-12-1 | |||
| /** | |||
| * 获取图表数据 | |||
| * @param {*} query | |||
| * @returns | |||
| */ | |||
| export function getChartData(query){ | |||
| return request({ | |||
| url: '/patrol/statistics/chartsMulti', | |||
| method: 'get', | |||
| params: query | |||
| }) | |||
| } | |||
| /** | |||
| * 同源/非同源数据对比 阈值 | |||
| * @param {*} query | |||
| * @returns | |||
| */ | |||
| export function analysisData(query){ | |||
| return request({ | |||
| url: '/patrol/statistics/analyse', | |||
| method: 'post', | |||
| data: query | |||
| }) | |||
| } | |||
| /** | |||
| * 5次表计任务对比分析 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function recordAnalysisData(data){ | |||
| return request({ | |||
| url: '/patrol/statistics/verifyNumber', | |||
| method: 'post', | |||
| data | |||
| }) | |||
| } | |||
| /** | |||
| * 获取自定义属性字典 | |||
| * @returns | |||
| */ | |||
| export function getExportProps(){ | |||
| return request({ | |||
| url:'/patrol/statistics/colMap', | |||
| methods:'get' | |||
| }) | |||
| } | |||
| export function getPatrolPointName (params) { | |||
| return request({ | |||
| url: '/patrol/task/getPatrolPointName', | |||
| methods: 'get', | |||
| params | |||
| }) | |||
| } | |||
| @ -0,0 +1,116 @@ | |||
| // 首页 | |||
| import request from "@/utils/request"; | |||
| // 任务占比 | |||
| export function teskRationList(query) { | |||
| return request({ | |||
| url: "/patrol/task/teskRationList", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 告警统计 | |||
| export function alarmList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/alarmList", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 设备状态详情 | |||
| export function alarmDeviceList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/alarmList/status", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 巡视告警详情 | |||
| export function alarmPatrolList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/alarmList/alarm", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 巡视异常详情 | |||
| export function abnormalPatrolList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/alarmList/abnormal", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 巡视异常 | |||
| export function getAbnormalPatrolList(query) { | |||
| return request({ | |||
| url: "/patrol/analysis/list_exception", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 场站点位个数 | |||
| export function pointStatistics(query = {}) { | |||
| return request({ | |||
| url: "/patrol/task/pointStatistics", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 缺陷统计 | |||
| export function defectList(query) { | |||
| return request({ | |||
| url: "/patrol/task/defectList", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 设备数量 | |||
| export function deviceNumList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/deviceNumList", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 环境参数 | |||
| export function evnList(query) { | |||
| return request({ | |||
| url: "/basedata/montdata/evndata/evnList", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 阈值列表 | |||
| export function thresholdList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/subtypeData", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 阈值修改 | |||
| export function thresholdUpdate(params) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/subtypeUpdate", | |||
| method: "post", | |||
| data: params, | |||
| }); | |||
| } | |||
| // 阈值历史查看 | |||
| export function thresholdHistory(params) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/subtypeUpdateRecord", | |||
| method: "get", | |||
| params: params, | |||
| }); | |||
| } | |||
| @ -0,0 +1,9 @@ | |||
| import request from "@/utils/request"; | |||
| // 大模型对话 | |||
| export function semanticsModel (query) { | |||
| return request({ | |||
| url: '/simulator/ir/semanticsModel', | |||
| method: 'post', | |||
| data: query | |||
| }) | |||
| } | |||
| @ -0,0 +1,75 @@ | |||
| import request from "@/utils/request"; | |||
| //获取检修区域tree | |||
| export function treeselect(query) { | |||
| return request({ | |||
| url: "/basedata/area/treeselect", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| //获取监测设备列表 | |||
| export function getViewers(query) { | |||
| return request({ | |||
| url: "/api/device/getViewers", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| //获取生产设备列表 | |||
| export function getDevices(query) { | |||
| return request({ | |||
| url: "/basedata//prodevmnt/device/treeAreaDeviceSelect", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| //添加巡检任务 | |||
| export function taskAdd(data) { | |||
| return request({ | |||
| url: "patrol/task/add", | |||
| method: "post", | |||
| data: data, | |||
| }); | |||
| } | |||
| //任务详情中算法纠偏 | |||
| export function taskCorrection(data) { | |||
| return request({ | |||
| url: "patrol/task/correction", | |||
| method: "post", | |||
| data: data, | |||
| }); | |||
| } | |||
| //获取未处置的告警列表 | |||
| export function unexecuted(query) { | |||
| return request({ | |||
| url: "patrol/analysis/unexecuted", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| //查询检测设备列表 | |||
| export function getTeskMessageByEqpbookId(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/getTeskMessageByEqpbookId", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| export function treeProMainDevicePatrolEquSelect(query) { | |||
| return request({ | |||
| url: "/basedata/prodevmnt/device/treeProMainDevicePatrolEquSelect", | |||
| method: "post", | |||
| data: query, | |||
| }); | |||
| } | |||
| //获取无人机 | |||
| export function getUavList(query) { | |||
| return request({ | |||
| url: "/basedata/eqpbook/getUavList", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| @ -0,0 +1,322 @@ | |||
| import request from "@/utils/request"; | |||
| import { defectList } from "../home/index"; | |||
| /** | |||
| * 巡视设备树 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function getAreaEqubookTreeSelect(params) { | |||
| return request({ | |||
| // url: '/basedata/eqpbook/getAreaEqubookTreeSelect', | |||
| url: "/basedata/eqpbook/getAreaEqubookTreeSelectForFilter", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 巡视任务--任务展示--日 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function list(params) { | |||
| return request({ | |||
| url: "/patrol/taskstatus/list", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 巡视任务--任务展示--月、年 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function taskcount(params) { | |||
| return request({ | |||
| url: "/patrol/taskstatus/taskcount", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 任务列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function taskList(params) { | |||
| return request({ | |||
| // url: '/patrol/task/list', | |||
| url: "/patrol/taskstatus/list", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 任务列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function taskInfo(params) { | |||
| return request({ | |||
| url: "/patrol/task/taskInfo", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| // 查询巡视类型列表 | |||
| export function listPatroltype(query) { | |||
| return request({ | |||
| url: "/basedata/patrolpointmnt/patroltype/list", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 巡检区域 | |||
| export function listArea(query) { | |||
| return request({ | |||
| url: "/basedata/area/list", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| // 生产设备树 | |||
| export function equipmentree(query) { | |||
| return request({ | |||
| url: "/basedata/prodevmnt/device/getProDeviceTreeSelect", | |||
| method: "get", | |||
| params: query, | |||
| }); | |||
| } | |||
| /** | |||
| * 任务基本信息 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function editTask(taskId) { | |||
| return request({ | |||
| url: "/patrol/task/" + taskId, | |||
| method: "get", | |||
| }); | |||
| } | |||
| /** | |||
| * 任务下发 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function taskStatusChange(params) { | |||
| return request({ | |||
| url: "/patrol/taskstatus/taskStatusChange", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 录像列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function recordList(params) { | |||
| return request({ | |||
| url: "/patrol/videotape/record/list", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 获取视频地址 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function reviewStart(params) { | |||
| return request({ | |||
| url: "/patrol/videotape/review/start", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 获取预置点列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function getDeviceList(deviceCode, channelCode) { | |||
| return request({ | |||
| url: `/patrol/preset/listByEqIdAndChannelId`, | |||
| params: { | |||
| patroldeviceCode: deviceCode, | |||
| channelCode: channelCode, | |||
| }, | |||
| method: "get", | |||
| }); | |||
| } | |||
| /** | |||
| * 下发预置点任务 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function presetGoto(params) { | |||
| return request({ | |||
| url: `/patrol/preset/goto`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 下发预置点任务 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function videotapeRecord(params) { | |||
| return request({ | |||
| url: `/patrol/videotape/record`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 获取告警列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function patdevstadataList(params) { | |||
| return request({ | |||
| url: `basedata/montdata/patdevstadata/list`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 任务点位分析列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function taskInfoList(params) { | |||
| return request({ | |||
| url: `/patrol/task/taskInfo_v2`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 缺陷列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function analysisDefectList(params) { | |||
| return request({ | |||
| url: `/patrol/analysis/list_v2`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 绍兴站合并缺陷列表(ai和缺陷) | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function analysisDefectListShaoxing(params) { | |||
| return request({ | |||
| url: `/patrol/analysis/list_v2_ex`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 点位列表列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function getDefectPointList(params = {}) { | |||
| return request({ | |||
| url: `/patrol/analysis/summary/points`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 点位缺陷列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function getDefectAlarmList(params = {}) { | |||
| return request({ | |||
| url: `/patrol/analysis/summary/alarm`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 获取区域列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function areaList(params = {}) { | |||
| return request({ | |||
| url: `/basedata/area/list`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 巡视任务--任务日历展示年 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function calenderYear(params) { | |||
| return request({ | |||
| url: "/patrol/calender/year", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 巡视任务--任务日历展示月 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function calenderMonth(params) { | |||
| return request({ | |||
| url: "/patrol/calender/month", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 巡视任务--任务日历展示日 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function calenderDay(params) { | |||
| return request({ | |||
| url: "/patrol/calender/day", | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| /** | |||
| * 缺陷记录卡片的列表 | |||
| * @param {*} data | |||
| * @returns | |||
| */ | |||
| export function defectRecordList(params) { | |||
| return request({ | |||
| url: `/patrol/analysis/defect/list`, | |||
| method: "get", | |||
| params, | |||
| }); | |||
| } | |||
| @ -0,0 +1,673 @@ | |||
| <template> | |||
| <div> | |||
| <!-- <div class="tool-actions"> | |||
| <el-tooltip content="删除" placement="top"> | |||
| <el-button | |||
| style="position: absolute; top: 22%; right: 79%; z-index: 999" | |||
| type="danger" | |||
| circle | |||
| @click="clearAll" | |||
| :disabled="regions.length === 0" | |||
| > | |||
| <i class="el-icon-delete"></i> | |||
| </el-button> | |||
| </el-tooltip> | |||
| </div> | |||
| <div class="tool-actions"> | |||
| <el-tooltip content="保存" placement="top"> | |||
| <el-button | |||
| style="position: absolute; top: 22%; right: 83%" | |||
| type="danger" | |||
| icon="el-icon-document-checked" | |||
| circle | |||
| @click="saveRegions" | |||
| :disabled="regions.length === 0" | |||
| /> | |||
| </el-tooltip> | |||
| </div> --> | |||
| <div class="image-wrapper" ref="imageWrapper"> | |||
| <el-image | |||
| ref="temperatureImage" | |||
| :fit="fit" | |||
| :preview-src-list="imageList" | |||
| :src="imgUrl" | |||
| @load="handleImageLoad" | |||
| @error="handleImageLoadError" | |||
| @dblclick="onClicked" | |||
| @mousemove="handleMouseMove" | |||
| @mouseleave="stopDrawingIfNeeded" | |||
| class="temperature-image" | |||
| > | |||
| <div slot="placeholder" class="image-slot"> | |||
| 加载中<span class="dot">...</span> | |||
| </div> | |||
| <div slot="error" class="image-slot"> | |||
| {{ errorTips }} | |||
| </div> | |||
| </el-image> | |||
| <!-- 温度悬浮显示 --> | |||
| <div | |||
| v-if="hoverData" | |||
| class="temperature-display" | |||
| :style="{ | |||
| left: hoverData.x + 'px', | |||
| top: hoverData.y + 'px', | |||
| }" | |||
| > | |||
| {{ hoverData.temperature }}°C | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| // import { ftpImageViewUrl } from "@/utils/index"; | |||
| import { mapGetters } from "vuex"; | |||
| import { getConfigByKeys } from "@/api/system/config"; | |||
| import Papa from "papaparse"; | |||
| // 接口 | |||
| import { | |||
| selectBoxData, | |||
| clearBoxData, | |||
| addBoxData, | |||
| downloadCsv, | |||
| } from "@/api/basedata/patrolpointmnt/patrolpointpreset.js"; | |||
| // "@/api/analysis/index"; | |||
| // 临时接口 | |||
| import axios from "axios"; | |||
| export default { | |||
| name: "AsyncImage", | |||
| components: {}, | |||
| props: { | |||
| fit: { | |||
| type: String, | |||
| default: "scale-down", | |||
| }, | |||
| srcList: { | |||
| type: Array, | |||
| default: () => [], | |||
| }, | |||
| src: { | |||
| type: String, | |||
| default: "", | |||
| }, | |||
| errorTips: { | |||
| type: String, | |||
| default: "加载失败", | |||
| }, | |||
| supportPreview: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| }, | |||
| async created() {}, | |||
| data() { | |||
| return { | |||
| imgUrl: "", | |||
| imgName: "", | |||
| loading: false, | |||
| imageList: [], | |||
| config: null, | |||
| uploadDialogVisible: false, | |||
| originalImage: null, | |||
| // 温度数据相关 | |||
| temperatureData: [], // 存储从CSV读取的二维温度数据 | |||
| tempRows: 0, // CSV行数 | |||
| tempCols: 0, // CSV列数 | |||
| // 图片尺寸相关 | |||
| imageNaturalWidth: 0, // 图片原始宽度 | |||
| imageNaturalHeight: 0, // 图片原始高度 | |||
| displayWidth: 0, // 图片显示宽度 | |||
| displayHeight: 0, // 图片显示高度 | |||
| imageScale: 1, // 图片缩放比例 | |||
| // 鼠标悬浮相关 | |||
| hoverData: null, // 当前悬停位置的数据 | |||
| hoverTemperatureData: [], //存续从CSV读取的温度数据用于鼠标悬浮 | |||
| // 鼠标位置温度 | |||
| currentTemp: null, | |||
| currentPos: { x: 0, y: 0 }, | |||
| showTooltip: false, | |||
| tooltipPosition: { x: 0, y: 0 }, | |||
| // 矩形绘制 - 已注释(但保留变量) | |||
| isDrawing: false, | |||
| startPos: { x: 0, y: 0 }, | |||
| currentRect: { x: 0, y: 0, width: 0, height: 0 }, | |||
| // 温度区域 - 已注释(但保留变量) | |||
| regions: [], | |||
| selectedRegion: null, | |||
| // 温度范围 | |||
| maxGlobalTemp: 0, | |||
| minGlobalTemp: 0, | |||
| // 框的终点 | |||
| secondX: 0, | |||
| secondY: 0, | |||
| showFrame: false, //返回不同坐标计算 | |||
| maxTempPoints: [], // 存储每个区域的最高温点坐标 | |||
| }; | |||
| }, | |||
| computed: { | |||
| ...mapGetters(["systemParams"]), | |||
| tooltipStyle() { | |||
| return { | |||
| left: `${this.tooltipPosition.x}px`, | |||
| top: `${this.tooltipPosition.y}px`, | |||
| position: "absolute", | |||
| backgroundColor: "rgba(0, 0, 0, 0.8)", | |||
| color: "white", | |||
| padding: "5px 10px", | |||
| borderRadius: "4px", | |||
| fontSize: "14px", | |||
| pointerEvents: "none", | |||
| zIndex: 100, | |||
| whiteSpace: "nowrap", | |||
| }; | |||
| }, | |||
| legendGradient() { | |||
| return { | |||
| background: `linear-gradient(to top, | |||
| ${this.getColorForTemp(this.minGlobalTemp)} 0%, | |||
| ${this.getColorForTemp( | |||
| (this.minGlobalTemp + this.maxGlobalTemp) / 2 | |||
| )} 50%, | |||
| ${this.getColorForTemp(this.maxGlobalTemp)} 100%)`, | |||
| height: "200px", | |||
| borderRadius: "4px", | |||
| }; | |||
| }, | |||
| }, | |||
| // 悬浮 | |||
| mounted() { | |||
| setTimeout(() => { | |||
| this.updateImageSize(); | |||
| }, 500); | |||
| window.addEventListener("resize", this.updateImageSize); | |||
| this.showUploadDialog(); | |||
| }, | |||
| beforeDestroy() { | |||
| window.removeEventListener("resize", this.updateImageSize); | |||
| }, | |||
| methods: { | |||
| // ==================== 图片事件处理 ==================== | |||
| handleImageLoad(event) { | |||
| const img = event.target; | |||
| this.imageNaturalWidth = img.naturalWidth; | |||
| this.imageNaturalHeight = img.naturalHeight; | |||
| this.$nextTick(() => { | |||
| this.updateImageSize(); | |||
| }); | |||
| this.$emit('imgload', { | |||
| imgOriginalHeight: this.imageNaturalHeight, | |||
| imgOriginalWidth: this.imageNaturalWidth | |||
| }); | |||
| // 去除拖动鼠标图片被拖走问题 | |||
| img.draggable = false; | |||
| img.ondragstart = () => false; | |||
| }, | |||
| handleImageLoadError() { | |||
| this.$emit('imgloaderror'); | |||
| }, | |||
| onClicked() { | |||
| this.$emit('click'); | |||
| }, | |||
| showUploadDialog() { | |||
| this.uploadDialogVisible = true; | |||
| }, | |||
| // ==================== 鼠标交互 ==================== | |||
| handleMouseMove(event) { | |||
| // 处理悬浮温度显示(使用最近邻插值) | |||
| this.handleHoverShow(event); | |||
| }, | |||
| stopDrawingIfNeeded() { | |||
| this.showTooltip = false; | |||
| }, | |||
| // ==================== 图片尺寸更新 ==================== | |||
| updateImageSize() { | |||
| const imageEl = this.$refs.temperatureImage?.$el?.querySelector("img"); | |||
| if (!imageEl) { | |||
| return; | |||
| } | |||
| this.originalImage = imageEl; | |||
| const naturalWidth = imageEl.naturalWidth; | |||
| const naturalHeight = imageEl.naturalHeight; | |||
| const displayWidth = imageEl.clientWidth; | |||
| const displayHeight = imageEl.clientHeight; | |||
| // 计算正确的缩放比例 | |||
| this.imageScale = naturalWidth / displayWidth; | |||
| // console.log("图片尺寸更新:", { | |||
| // 实际尺寸: `${naturalWidth}×${naturalHeight}`, | |||
| // 显示尺寸: `${displayWidth}×${displayHeight}`, | |||
| // 缩放比例: this.imageScale, | |||
| // }); | |||
| const container = this.$refs.imageWrapper; | |||
| this.containerRect = container.getBoundingClientRect(); | |||
| if (this.$refs.temperatureImage) { | |||
| this.imageSize = { | |||
| width: naturalWidth, | |||
| height: naturalHeight, | |||
| }; | |||
| } | |||
| }, | |||
| // ==================== CSV加载和解析(分块处理,避免栈溢出) ==================== | |||
| downloadAndParseCSV() { | |||
| const data = { | |||
| filePath: this.imgName, | |||
| }; | |||
| downloadCsv(data) | |||
| .then((response) => { | |||
| const blob = response instanceof Blob ? response : new Blob([response]); | |||
| const reader = new FileReader(); | |||
| reader.onload = (e) => { | |||
| try { | |||
| const csvText = e.target.result; | |||
| this.parseCSVInChunks(csvText); | |||
| } catch (error) { | |||
| // this.$message.error('读取CSV出错: ' + error.message); | |||
| } | |||
| }; | |||
| reader.onerror = () => { | |||
| // this.$message.error('文件读取失败'); | |||
| }; | |||
| reader.readAsText(blob); | |||
| }) | |||
| .catch((error) => { | |||
| // this.$message.error('下载CSV失败'); | |||
| }); | |||
| }, | |||
| // 分块解析CSV,避免栈溢出 | |||
| parseCSVInChunks(csvText) { | |||
| try { | |||
| const lines = csvText.split('\n'); | |||
| const tempData = []; | |||
| let maxCols = 0; | |||
| const BATCH_SIZE = 1000; | |||
| let index = 0; | |||
| const processBatch = () => { | |||
| const endIndex = Math.min(index + BATCH_SIZE, lines.length); | |||
| for (let i = index; i < endIndex; i++) { | |||
| const line = lines[i].trim(); | |||
| if (!line) continue; | |||
| const values = line.split(',').map(v => { | |||
| const num = parseFloat(v.trim()); | |||
| return isNaN(num) ? null : num; | |||
| }); | |||
| const validValues = values.filter(v => v !== null && isFinite(v)); | |||
| if (validValues.length > 0) { | |||
| tempData.push(validValues); | |||
| if (validValues.length > maxCols) { | |||
| maxCols = validValues.length; | |||
| } | |||
| } | |||
| } | |||
| index = endIndex; | |||
| if (index < lines.length) { | |||
| setTimeout(processBatch, 0); | |||
| } else { | |||
| this.finalizeCSVData(tempData, maxCols); | |||
| } | |||
| }; | |||
| processBatch(); | |||
| } catch (error) { | |||
| // this.$message.error('解析CSV出错: ' + error.message); | |||
| } | |||
| }, | |||
| // 最终处理CSV数据(修复栈溢出) | |||
| finalizeCSVData(tempData, maxCols) { | |||
| try { | |||
| if (tempData.length === 0) { | |||
| this.$message.warning('CSV温度数据为空'); | |||
| return; | |||
| } | |||
| const normalizedData = []; | |||
| for (let i = 0; i < tempData.length; i++) { | |||
| const row = tempData[i]; | |||
| if (row.length < maxCols) { | |||
| const lastVal = row[row.length - 1] || 0; | |||
| while (row.length < maxCols) { | |||
| row.push(lastVal); | |||
| } | |||
| } | |||
| normalizedData.push(row); | |||
| } | |||
| this.temperatureData = normalizedData; | |||
| this.tempRows = normalizedData.length; | |||
| this.tempCols = normalizedData[0]?.length || 0; | |||
| // 计算温度范围(使用循环避免栈溢出) | |||
| let minTemp = Infinity; | |||
| let maxTemp = -Infinity; | |||
| let count = 0; | |||
| const sampleLimit = Math.min(normalizedData.length, 500); | |||
| for (let i = 0; i < sampleLimit; i++) { | |||
| const row = normalizedData[i]; | |||
| if (Array.isArray(row)) { | |||
| const rowLimit = Math.min(row.length, 500); | |||
| for (let j = 0; j < rowLimit; j++) { | |||
| const val = row[j]; | |||
| if (typeof val === 'number' && !isNaN(val) && isFinite(val)) { | |||
| if (val < minTemp) minTemp = val; | |||
| if (val > maxTemp) maxTemp = val; | |||
| count++; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // console.log('温度范围:', { | |||
| // min: count > 0 ? minTemp : 0, | |||
| // max: count > 0 ? maxTemp : 0, | |||
| // sampleCount: count | |||
| // }); | |||
| // 为悬停显示准备数据(使用循环避免栈溢出) | |||
| const hoverData = []; | |||
| const rowLimit = Math.min(normalizedData.length, 100); | |||
| for (let i = 0; i < rowLimit; i++) { | |||
| const row = normalizedData[i]; | |||
| if (Array.isArray(row)) { | |||
| const colLimit = Math.min(row.length, 100); | |||
| for (let j = 0; j < colLimit; j++) { | |||
| const val = row[j]; | |||
| if (typeof val === 'number' && !isNaN(val) && isFinite(val)) { | |||
| hoverData.push(val); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| this.hoverTemperatureData = hoverData; | |||
| // this.$message.success(`成功加载 ${this.tempRows}x${this.tempCols} 温度数据`); | |||
| } catch (error) { | |||
| // this.$message.error('处理CSV数据出错: ' + error.message); | |||
| } | |||
| }, | |||
| // ==================== 最近邻插值(精确匹配温度矩阵原始值) ==================== | |||
| nearestNeighborInterpolation(data, row, col, rows, cols) { | |||
| if (row < 0 || row > rows - 1 || col < 0 || col > cols - 1) { | |||
| return null; | |||
| } | |||
| const nearestRow = Math.round(row); | |||
| const nearestCol = Math.round(col); | |||
| const safeRow = Math.max(0, Math.min(nearestRow, rows - 1)); | |||
| const safeCol = Math.max(0, Math.min(nearestCol, cols - 1)); | |||
| const val = parseFloat(data[safeRow]?.[safeCol]); | |||
| return isNaN(val) ? null : val; | |||
| }, | |||
| // ==================== 处理鼠标悬浮显示温度(使用最近邻插值) ==================== | |||
| handleHoverShow(event) { | |||
| const imageEl = this.$refs.temperatureImage?.$el?.querySelector('img'); | |||
| if (!imageEl) return; | |||
| if (!this.temperatureData || this.temperatureData.length === 0) { | |||
| this.hoverData = null; | |||
| return; | |||
| } | |||
| const rect = imageEl.getBoundingClientRect(); | |||
| const mouseX = event.clientX - rect.left; | |||
| const mouseY = event.clientY - rect.top; | |||
| const normalizedX = mouseX / rect.width; | |||
| const normalizedY = mouseY / rect.height; | |||
| if (normalizedX < 0 || normalizedX > 1 || normalizedY < 0 || normalizedY > 1) { | |||
| this.hoverData = null; | |||
| return; | |||
| } | |||
| const rows = this.tempRows; | |||
| const cols = this.tempCols; | |||
| if (rows === 0 || cols === 0) { | |||
| this.hoverData = null; | |||
| return; | |||
| } | |||
| const floatRow = normalizedY * (rows - 1); | |||
| const floatCol = normalizedX * (cols - 1); | |||
| const temperature = this.nearestNeighborInterpolation( | |||
| this.temperatureData, | |||
| floatRow, | |||
| floatCol, | |||
| rows, | |||
| cols | |||
| ); | |||
| if (temperature !== null && !isNaN(temperature) && isFinite(temperature)) { | |||
| let tooltipX = event.clientX - rect.left + 15; | |||
| let tooltipY = event.clientY - rect.top - 10; | |||
| if (tooltipX + 100 > rect.width) { | |||
| tooltipX = event.clientX - rect.left - 100; | |||
| } | |||
| if (tooltipY < 20) { | |||
| tooltipY = event.clientY - rect.top + 20; | |||
| } | |||
| if (tooltipY + 30 > rect.height) { | |||
| tooltipY = rect.height - 30; | |||
| } | |||
| this.hoverData = { | |||
| x: tooltipX, | |||
| y: tooltipY, | |||
| temperature: temperature.toFixed(1), | |||
| }; | |||
| } else { | |||
| this.hoverData = null; | |||
| } | |||
| }, | |||
| // ==================== 辅助函数 ==================== | |||
| getRandomRegionColor() { | |||
| const colors = [ | |||
| "#FF5722", | |||
| "#2196F3", | |||
| "#4CAF50", | |||
| "#FFC107", | |||
| "#9C27B0", | |||
| "#00BCD4", | |||
| "#8BC34A", | |||
| "#FF9800", | |||
| "#673AB7", | |||
| "#3F51B5", | |||
| "#009688", | |||
| "#CDDC39", | |||
| ]; | |||
| return colors[Math.floor(Math.random() * colors.length)]; | |||
| }, | |||
| hexToRgba(hex, alpha) { | |||
| const r = parseInt(hex.slice(1, 3), 16); | |||
| const g = parseInt(hex.slice(3, 5), 16); | |||
| const b = parseInt(hex.slice(5, 7), 16); | |||
| return `rgba(${r}, ${g}, ${b}, ${alpha})`; | |||
| }, | |||
| // ==================== FTP和URL处理 ==================== | |||
| async ftpImageViewUrl(url) { | |||
| if (url.startsWith('http://') || url.startsWith('https://')) { | |||
| return url; | |||
| } | |||
| if (!this.systemParams.ftpViewAddress) { | |||
| const systemParams = {}; | |||
| await getConfigByKeys([ | |||
| 'EXTRANET_ADDRESS', | |||
| 'EXTRANET_FTP_VIEW_ADDRESS', | |||
| 'INTRANET_FTP_VIEW_ADDRESS', | |||
| 'VIRTUAL_RTC_MEDIAURL', | |||
| 'EXTRANET_MEDIA_URL', | |||
| 'INTRANET_MEDIA_URL', | |||
| 'VIRTUAL_FLV_MEDIAURL', | |||
| 'INTRANET_FLV_MEDIA_URL', | |||
| 'EXTRANET_FLV_MEDIA_URL', | |||
| ]).then((res) => { | |||
| res.data.forEach((x) => { | |||
| systemParams[x.configKey] = x.configValue; | |||
| }); | |||
| systemParams.PREFIX = location.hostname === systemParams.EXTRANET_ADDRESS | |||
| ? 'EXTRANET' | |||
| : 'INTRANET'; | |||
| systemParams.ftpViewAddress = systemParams[systemParams.PREFIX + '_FTP_VIEW_ADDRESS']; | |||
| systemParams.MEDIA_URL = systemParams[systemParams.PREFIX + '_MEDIA_URL']; | |||
| systemParams.FLV_MEDIA_URL = systemParams[systemParams.PREFIX + '_FLV_MEDIA_URL']; | |||
| }); | |||
| return systemParams.ftpViewAddress + url; | |||
| } else { | |||
| return this.systemParams.ftpViewAddress + url; | |||
| } | |||
| }, | |||
| async getUrls(val) { | |||
| this.imageList = []; | |||
| const urls = []; | |||
| for (let index = 0; index < val.length; index++) { | |||
| const element = val[index]; | |||
| urls.push(await this.ftpImageViewUrl(element)); | |||
| } | |||
| if (this.supportPreview) { | |||
| this.imageList = urls; | |||
| } | |||
| }, | |||
| }, | |||
| watch: { | |||
| srcList: { | |||
| handler(val) { | |||
| this.getUrls(val); | |||
| }, | |||
| immediate: true, | |||
| }, | |||
| src: { | |||
| async handler(val) { | |||
| if (val && val.length > 0) { | |||
| this.imgUrl = await this.ftpImageViewUrl(val); | |||
| // this.imgUrl = "http://192.168.1.116:18891/shaoxing/videoMonitor/app/htjc6/htjcImage//test/39357.jpg"; | |||
| this.imgName = val; | |||
| if (this.srcList.length === 0) { | |||
| this.getUrls([val]); | |||
| } | |||
| this.$nextTick(() => { | |||
| this.downloadAndParseCSV(); | |||
| }); | |||
| } else { | |||
| this.imgUrl = ""; | |||
| } | |||
| }, | |||
| immediate: true, | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .image-slot { | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| width: 100%; | |||
| height: 100%; | |||
| color: #ddd; | |||
| } | |||
| .image-wrapper { | |||
| position: relative; | |||
| border-radius: 4px; | |||
| overflow: hidden; | |||
| height: calc(66vh - 350px); | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| .temperature-image { | |||
| max-width: 100%; | |||
| max-height: 100%; | |||
| display: block; | |||
| cursor: default; | |||
| } | |||
| .image-slot { | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: center; | |||
| align-items: center; | |||
| height: 100%; | |||
| color: #909399; | |||
| font-size: 14px; | |||
| } | |||
| .image-slot i { | |||
| font-size: 36px; | |||
| margin-bottom: 10px; | |||
| } | |||
| /* 温度悬浮显示 */ | |||
| .temperature-display { | |||
| position: absolute; | |||
| padding: 6px 12px; | |||
| border-radius: 4px; | |||
| background-color: rgba(0, 0, 0, 0.85); | |||
| color: #fff; | |||
| font-weight: bold; | |||
| font-size: 14px; | |||
| pointer-events: none; | |||
| box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.3); | |||
| transform: translateY(-100%); | |||
| z-index: 10; | |||
| white-space: nowrap; | |||
| border: 1px solid rgba(255, 255, 255, 0.1); | |||
| } | |||
| </style> | |||
| @ -0,0 +1,658 @@ | |||
| // 卡片 | |||
| .card-page { | |||
| background-color: #222F3D; | |||
| .el-checkbox__input.is-checked .el-checkbox__inner { | |||
| background-color: #40486a; | |||
| border-color: #40486a; | |||
| } | |||
| .el-checkbox__input.is-checked+.el-checkbox__label { | |||
| color: #ffffff; | |||
| } | |||
| .el-descriptions { | |||
| .is-bordered .el-descriptions-item__cell { | |||
| border: 1px solid #40486a; | |||
| } | |||
| } | |||
| .el-table::before, | |||
| .el-table--group::after, | |||
| .el-table--border::after { | |||
| background-color: #40486a; | |||
| } | |||
| .el-table { | |||
| // 新页面中el-table的样式 | |||
| border-color: #40486a; | |||
| .el-table__expanded-cell { | |||
| background-color: transparent; | |||
| } | |||
| th { | |||
| border-right-color: #40486a; | |||
| border-bottom-color: #40486a !important; | |||
| .cell { | |||
| font-size: 14px; | |||
| color: #c6d3ec; | |||
| font-weight: bold; | |||
| // font-size: 18px; | |||
| } | |||
| } | |||
| td { | |||
| border-right-color: #40486a; | |||
| border-bottom-color: #40486a; | |||
| .cell, | |||
| .cell div, | |||
| .cell span { | |||
| // font-size: 17px; | |||
| // color: #c6d3ec; | |||
| // color:#000 | |||
| } | |||
| } | |||
| .el-table__header tr { | |||
| background-color: #101d29 !important; // 添加这一行来修改header的背景颜色 | |||
| } | |||
| .el-table__row { | |||
| background-color: #172533 !important; | |||
| } | |||
| .el-table__row--striped .el-table__cell { | |||
| background-color: #202f3e !important; | |||
| } | |||
| } | |||
| .el-select, | |||
| .el-input { | |||
| .el-input__inner { | |||
| border-color: #40486a; // 修改el-select边框颜色 | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| .el-input.is-disabled .el-input__inner { | |||
| background-color: transparent; | |||
| color: #fff; | |||
| } | |||
| .el-textarea { | |||
| .el-textarea__inner { | |||
| border-color: #40486a; // 修改el-select边框颜色 | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| .el-date-editor { | |||
| border: 1px solid #40486a; | |||
| .el-range-input { | |||
| font-size: 14px; // 修改字体大小 | |||
| background: none; | |||
| } | |||
| } | |||
| .el-form { | |||
| .el-form-item__label { | |||
| // font-size: 18px; | |||
| color: #e7f5f6; | |||
| } | |||
| // .el-button--mini { | |||
| // font-size: 18px; | |||
| // } | |||
| // .el-select, | |||
| // .el-input { | |||
| // .el-input__inner { | |||
| // font-size: 18px; | |||
| // } | |||
| // } | |||
| // .el-textarea { | |||
| // .el-textarea__inner { | |||
| // font-size: 18px; | |||
| // } | |||
| // } | |||
| // .el-date-editor { | |||
| // .el-range-input { | |||
| // font-size: 18px; // 修改字体大小 | |||
| // } | |||
| // } | |||
| } | |||
| .el-button:hover { | |||
| background: linear-gradient(0deg, | |||
| rgba(255, 255, 255, 0.1), | |||
| rgba(255, 255, 255, 0.1)), | |||
| #247382; | |||
| } | |||
| .el-button:focus { | |||
| background: linear-gradient(0deg, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), | |||
| #247382; | |||
| } | |||
| .el-button:disabled { | |||
| background: #247382; | |||
| opacity: 0.4; | |||
| } | |||
| .el-button { | |||
| background-color: #247382; // 修改按钮的背景颜色 | |||
| border: none; | |||
| } | |||
| .el-pagination { | |||
| .btn-prev:disabled, | |||
| .btn-next:disabled { | |||
| color: #c6d3ec; | |||
| opacity: 0.3; | |||
| } | |||
| .btn-prev, | |||
| .btn-next, | |||
| .el-pager li { | |||
| background-color: transparent; | |||
| color: #40e5f0; | |||
| } | |||
| .el-pager li:not(.disabled).active { | |||
| background-color: transparent; | |||
| color: #40e5f0; | |||
| } | |||
| .el-pager li:not(.disabled):hover { | |||
| color: #40e5f0; | |||
| } | |||
| } | |||
| .el-tree { | |||
| // background: #202F3E; | |||
| // /* 分割线 */ | |||
| // border: 1px solid #40486A; | |||
| .el-tree-node__content { | |||
| height: 36px; | |||
| .el-tree-node__label { | |||
| font-weight: 400; | |||
| font-size: 14px; | |||
| line-height: 22px; | |||
| color: #C6D3EC; | |||
| } | |||
| } | |||
| .el-tree-node__content:hover { | |||
| background: rgba(64, 229, 240, 0.15); | |||
| border-radius: 2px; | |||
| } | |||
| .el-tree-node:focus>.el-tree-node__content { | |||
| background: rgba(64, 229, 240, 0.15); | |||
| border-radius: 2px; | |||
| } | |||
| .el-tree-node.is-current>.el-tree-node__content { | |||
| background: rgba(64, 229, 240, 0.15); | |||
| border-radius: 2px; | |||
| border-right: #40E5F0 3px solid; | |||
| } | |||
| } | |||
| /* 设置滚动条的样式 */ | |||
| ::-webkit-scrollbar { | |||
| width: 8px; | |||
| height: 8px; | |||
| } | |||
| /* 滚动槽 */ | |||
| ::-webkit-scrollbar-track { | |||
| box-shadow: inset006pxrgba(0, 0, 0, 0.3); | |||
| border-radius: 10px; | |||
| } | |||
| /* 滚动条滑块 */ | |||
| ::-webkit-scrollbar-thumb { | |||
| border-radius: 10px; | |||
| background: rgba(64, 229, 240, 0.15); | |||
| box-shadow: inset006pxrgba(0, 0, 0, 0.5); | |||
| } | |||
| /* 6,边角,两个滚动条交汇处 */ | |||
| ::-webkit-scrollbar-corner { | |||
| background: transparent; | |||
| } | |||
| } | |||
| .card-select-dropdown { | |||
| background-color: #202f3e !important; // 修改弹窗的背景颜色 | |||
| border-color: #40486a !important; // 修改弹窗的边框颜色 | |||
| .el-select-dropdown__item.hover, | |||
| .el-select-dropdown__item:hover { | |||
| background: #172533; | |||
| } | |||
| } | |||
| /*日期选择器弹窗*/ | |||
| .card-date-editor { | |||
| border: 1px solid #40486a; | |||
| background-color: #202f3e; | |||
| .el-picker-panel__sidebar { | |||
| background-color: #202f3e; | |||
| border-right: 1px solid #40486a; | |||
| .el-picker-panel__shortcut { | |||
| color: #fff; | |||
| } | |||
| .el-picker-panel__shortcut:hover { | |||
| color: #62d0ff; | |||
| } | |||
| } | |||
| .el-picker-panel, | |||
| .el-select-dropdown, | |||
| .el-select .el-input__inner:focus, | |||
| .el-select .el-input.is-focus .el-input__inner { | |||
| border: 1px solid #40486a; | |||
| } | |||
| /* 年选择器样式 */ | |||
| .el-year-table td .cell:hover { | |||
| color: #62d0ff; | |||
| } | |||
| .el-year-table td.today .cell { | |||
| color: #62d0ff; | |||
| } | |||
| .el-date-picker__header--bordered { | |||
| border-bottom: solid 1px #40486a; | |||
| } | |||
| .el-year-table td.current:not(.disabled) .cell { | |||
| color: #62d0ff; | |||
| } | |||
| .el-date-table td.disabled div, | |||
| .el-date-table td.disabled div span { | |||
| color: rgba(255, 255, 255, 0.3); // 修改禁止点击字体的颜色 | |||
| background-color: transparent; | |||
| } | |||
| .normal.disabled div { | |||
| background-color: transparent !important; | |||
| } | |||
| .available.in-range.start-date div span, | |||
| .available.in-range.end-date div span { | |||
| background: #247382; | |||
| border-radius: 50%; | |||
| color: #fff; | |||
| } | |||
| .available.in-range div, | |||
| .available.in-range div:hover { | |||
| background: rgba(64, 229, 240, 0.8); | |||
| } | |||
| .el-date-table td.today span { | |||
| color: #62d0ff; | |||
| font-weight: bold; | |||
| } | |||
| .el-picker-panel__icon-btn:hover { | |||
| color: #40e5f0; | |||
| } | |||
| .el-date-range-picker__content.is-left { | |||
| border-right: 1px solid #40486a; | |||
| } | |||
| .el-date-table th { | |||
| border-bottom: solid 1px #40486a; | |||
| } | |||
| /* 年选择器样式 */ | |||
| .el-year-table td .cell:hover { | |||
| color: #62d0ff; | |||
| } | |||
| .el-year-table td.today .cell { | |||
| color: #62d0ff; | |||
| } | |||
| .el-date-picker__header--bordered { | |||
| border-bottom: solid 1px #40486a; | |||
| } | |||
| .el-year-table td.current:not(.disabled) .cell { | |||
| color: #62d0ff; | |||
| } | |||
| .el-date-table td.disabled div, | |||
| .el-date-table td.disabled div span { | |||
| color: rgba(255, 255, 255, 0.3); // 修改禁止点击字体的颜色 | |||
| background-color: transparent; | |||
| } | |||
| .normal.disabled div { | |||
| background-color: transparent !important; | |||
| } | |||
| .available.in-range.start-date div span, | |||
| .available.in-range.end-date div span { | |||
| background: #247382; | |||
| border-radius: 50%; | |||
| color: #fff; | |||
| } | |||
| .available.in-range div, | |||
| .available.in-range div:hover { | |||
| background: rgba(64, 229, 240, 0.8); | |||
| } | |||
| .el-year-table { | |||
| .available { | |||
| padding: 13px 3px; | |||
| } | |||
| } | |||
| /*月选择器样式*/ | |||
| .el-month-table { | |||
| .current { | |||
| .cell { | |||
| color: #62D0FF; | |||
| } | |||
| } | |||
| td.current:not(.disabled) .cell { | |||
| color: #62D0FF; | |||
| } | |||
| td.today .cell { | |||
| color: #62D0FF; | |||
| } | |||
| td .cell:hover { | |||
| color: #62D0FF; | |||
| } | |||
| td { | |||
| padding: 6px 0px; | |||
| } | |||
| } | |||
| /*日期选择器*/ | |||
| &.el-date-picker { | |||
| .el-date-picker__header { | |||
| margin: 0 10px; | |||
| margin-top: 10px; | |||
| } | |||
| .el-picker-panel__content { | |||
| margin-top: 0px; | |||
| margin-bottom: 10px; | |||
| } | |||
| } | |||
| .el-date-table { | |||
| td { | |||
| width: 25px; | |||
| height: 20px; | |||
| padding: 2px 0; | |||
| div { | |||
| height: 20px; | |||
| padding: 1px 0; | |||
| } | |||
| span { | |||
| height: 18px; | |||
| width: 18px; | |||
| line-height: 18px; | |||
| } | |||
| } | |||
| td.start-date div { | |||
| margin-left: 9px; | |||
| } | |||
| td.end-date div { | |||
| margin-right: 9px; | |||
| } | |||
| } | |||
| } | |||
| // 卡片内容文字颜色--主要 | |||
| .card-content-text-color { | |||
| color: #c6d3ec; | |||
| } | |||
| .card-dialog-height-auto { | |||
| height: auto; | |||
| } | |||
| .card-dialog { | |||
| .el-dialog__header { | |||
| background-color: #222F3D; | |||
| color: #fff; | |||
| border-bottom: #40486a 1px solid; | |||
| height: 63px; | |||
| padding: 17px 24px; | |||
| .el-dialog__headerbtn .el-dialog__close { | |||
| color: #fff; | |||
| } | |||
| .el-dialog__title { | |||
| color: #fff; | |||
| font-size: 20px; | |||
| } | |||
| } | |||
| .el-dialog__body { | |||
| background-color: #222F3D; | |||
| padding: 24px; | |||
| flex: 1; | |||
| // 添加后,垂直滚动才不至于超出 | |||
| height: 0; | |||
| } | |||
| .el-dialog__footer { | |||
| border-top: #40486a 1px solid; | |||
| background-color: #222F3D; | |||
| height: 72px; | |||
| padding: 17px 24px; | |||
| color: #fff; | |||
| .card-dialog-cancel-btn { | |||
| background-color: rgba(64, 72, 106, 0.1) !important; | |||
| background-image: url('../card_btn_border.png'); | |||
| background-repeat: no-repeat; | |||
| /* 防止背景图像重复 */ | |||
| background-position: center center; | |||
| /* 将背景图像居中 */ | |||
| height: 41px; | |||
| width: 77px; | |||
| &:hover { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(197, 201, 219, 0.2) !important; | |||
| } | |||
| &:focus { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(64, 72, 106, 0.05) !important; | |||
| } | |||
| &:disabled { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(68, 68, 68, 0.1) !important; | |||
| } | |||
| } | |||
| .card-dialog-sure-btn { | |||
| background-color: rgba(64, 229, 240, 0.1) !important; | |||
| background-image: url('../card_btn_border.png'); | |||
| background-repeat: no-repeat; | |||
| /* 防止背景图像重复 */ | |||
| background-position: center center; | |||
| /* 将背景图像居中 */ | |||
| height: 41px; | |||
| width: 77px; | |||
| &:hover { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(64, 229, 240, 0.2) !important; | |||
| } | |||
| &:focus { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(64, 124, 240, 0.1) !important; | |||
| } | |||
| &:disabled { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(68, 68, 68, 0.1) !important; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .card-message-box { | |||
| border: none; | |||
| padding-bottom: 0; | |||
| .el-message-box__header { | |||
| background-color: #222F3D; | |||
| color: #fff; | |||
| border-bottom: #40486a 1px solid; | |||
| height: 63px; | |||
| padding: 17px 24px; | |||
| .el-message-box__headerbtn .el-message-box__close { | |||
| color: #fff; | |||
| } | |||
| .el-message-box__title { | |||
| color: #fff; | |||
| font-size: 20px; | |||
| } | |||
| } | |||
| .el-message-box__content { | |||
| background-color: #222F3D; | |||
| padding: 24px; | |||
| } | |||
| .el-message-box__btns { | |||
| border-top: #40486a 1px solid; | |||
| background-color: #222F3D; | |||
| height: 72px; | |||
| padding: 17px 24px; | |||
| color: #fff; | |||
| .el-button { | |||
| background-color: rgba(64, 72, 106, 0.1) !important; | |||
| background-image: url('../card_btn_border.png'); | |||
| background-repeat: no-repeat; | |||
| /* 防止背景图像重复 */ | |||
| background-position: center center; | |||
| /* 将背景图像居中 */ | |||
| height: 41px; | |||
| width: 77px; | |||
| &:hover { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(197, 201, 219, 0.2) !important; | |||
| } | |||
| &:focus { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(64, 72, 106, 0.05) !important; | |||
| } | |||
| &:disabled { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(68, 68, 68, 0.1) !important; | |||
| } | |||
| } | |||
| .el-button--primary { | |||
| background-color: rgba(64, 229, 240, 0.1) !important; | |||
| background-image: url('../card_btn_border.png'); | |||
| background-repeat: no-repeat; | |||
| /* 防止背景图像重复 */ | |||
| background-position: center center; | |||
| /* 将背景图像居中 */ | |||
| height: 41px; | |||
| width: 77px; | |||
| &:hover { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(64, 229, 240, 0.2) !important; | |||
| } | |||
| &:focus { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(64, 124, 240, 0.1) !important; | |||
| } | |||
| &:disabled { | |||
| background-image: url('../card_btn_border.png'); | |||
| background-color: rgba(68, 68, 68, 0.1) !important; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .el-notification__title { | |||
| color: #fff; | |||
| } | |||
| .el-notification { | |||
| background-color: #40486a; | |||
| border-color: #005d6b; | |||
| } | |||
| .vue-treeselect { | |||
| .vue-treeselect__menu { | |||
| background-color: #202f3e; | |||
| border-color: #40486a; | |||
| border-top-color: transparent; | |||
| } | |||
| .vue-treeselect__option--highlight { | |||
| background: #172533; | |||
| } | |||
| } | |||
| @ -0,0 +1,48 @@ | |||
| let messageId = 0; | |||
| /** | |||
| * 调用卡片系统弹窗 | |||
| * @param {*} zhan_name 站点名称 | |||
| * @param {*} sourceCardId 源卡片id | |||
| * @param {*} targetCardId 目标卡片id | |||
| * @param {*} dialogTitle 弹窗标题 | |||
| * @param {*} dialogIsDrag 弹窗是否可拖拽 默认 true | |||
| * @param {*} positionTop 弹窗上边距 | |||
| * @param {*} positionLeft 弹窗左边距 | |||
| * @param {*} contentWidth 弹窗内容宽度 | |||
| * @param {*} contentHeight 弹窗内容高度 | |||
| * @param {*} params 要传递给卡片的参数 | |||
| */ | |||
| export function openDialog({ | |||
| zhan_name = "", | |||
| sourceCardId, | |||
| targetCardId, | |||
| dialogTitle, | |||
| dialogIsDrag = true, | |||
| positionTop = "30", | |||
| positionLeft = "30", | |||
| contentWidth = "1856", | |||
| contentHeight = "896", | |||
| params = {}, | |||
| }) { | |||
| const id = "message_" + ++messageId; | |||
| var data = { | |||
| id, | |||
| sourceId: zhan_name + sourceCardId, | |||
| targetId: "main_frame_global_modal", // 基座系统弹窗ID | |||
| event: { | |||
| type: "SWITCH_GLOBAL_MODAL", | |||
| data: { | |||
| title: dialogTitle, // 弹窗标题 | |||
| cardId: zhan_name + targetCardId, | |||
| isDrag: dialogIsDrag, // 弹窗是否可拖拽,默认为true可拖拽 | |||
| positionTop, // 数字,弹窗上边距 positionTop | |||
| positionLeft, // 数字,弹窗左边距 positionLeft | |||
| contentWidth, // 数字,弹窗内容宽度 | |||
| contentHeight, // 数字,弹窗内容高度 | |||
| params, // 需要传递的参数 | |||
| }, | |||
| }, | |||
| tm: new Date().getTime(), // 时间戳 | |||
| }; | |||
| window.parent.postMessage(JSON.stringify(data), "*"); | |||
| } | |||
| @ -0,0 +1,216 @@ | |||
| <template> | |||
| <div :class="{ hidden: hidden }" class="pagination-container"> | |||
| <div class="pagination-total">共 {{ total }} 条</div> | |||
| <el-pagination | |||
| :background="background" | |||
| :current-page.sync="currentPage" | |||
| :page-size.sync="pageSize" | |||
| :layout="layout" | |||
| :page-sizes="pageSizes" | |||
| :pager-count="pagerCount" | |||
| :total="total" | |||
| v-bind="$attrs" | |||
| popper-class="card-select-dropdown" | |||
| @size-change="handleSizeChange" | |||
| @current-change="handleCurrentChange" | |||
| /> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { scrollTo } from '@/utils/scroll-to' | |||
| export default { | |||
| name: 'CardPagination', | |||
| props: { | |||
| total: { | |||
| required: true, | |||
| type: Number | |||
| }, | |||
| page: { | |||
| type: Number, | |||
| default: 1 | |||
| }, | |||
| limit: { | |||
| type: Number, | |||
| default: 20 | |||
| }, | |||
| pageSizes: { | |||
| type: Array, | |||
| default() { | |||
| return [10, 20, 30, 50] | |||
| } | |||
| }, | |||
| // Page button count on small screens | |||
| pagerCount: { | |||
| type: Number, | |||
| default: document.body.clientWidth < 992 ? 5 : 7 | |||
| }, | |||
| layout: { | |||
| type: String, | |||
| default: 'sizes, prev, pager, next, jumper' | |||
| }, | |||
| background: { | |||
| type: Boolean, | |||
| default: true | |||
| }, | |||
| autoScroll: { | |||
| type: Boolean, | |||
| default: true | |||
| }, | |||
| hidden: { | |||
| type: Boolean, | |||
| default: false | |||
| } | |||
| }, | |||
| data() { | |||
| return {} | |||
| }, | |||
| computed: { | |||
| currentPage: { | |||
| get() { | |||
| return this.page | |||
| }, | |||
| set(val) { | |||
| this.$emit('update:page', val) | |||
| } | |||
| }, | |||
| pageSize: { | |||
| get() { | |||
| return this.limit | |||
| }, | |||
| set(val) { | |||
| this.$emit('update:limit', val) | |||
| } | |||
| } | |||
| }, | |||
| methods: { | |||
| handleSizeChange(val) { | |||
| if (this.currentPage * val > this.total) { | |||
| this.currentPage = 1 | |||
| } | |||
| this.$emit('pagination', { page: this.currentPage, limit: val }) | |||
| if (this.autoScroll) { | |||
| scrollTo(0, 800) | |||
| } | |||
| }, | |||
| handleCurrentChange(val) { | |||
| this.$emit('pagination', { page: val, limit: this.pageSize }) | |||
| if (this.autoScroll) { | |||
| scrollTo(0, 800) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .pagination-container { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| gap: 12px; | |||
| width: 100%; | |||
| min-height: 48px; | |||
| margin-top: 0; | |||
| padding: 10px 16px; | |||
| box-sizing: border-box; | |||
| color: #dcecff; | |||
| background: #0b1140 !important; | |||
| border-top: 1px solid rgba(79, 166, 255, 0.35); | |||
| } | |||
| .pagination-total { | |||
| flex: 0 0 auto; | |||
| font-size: 14px; | |||
| line-height: 24px; | |||
| white-space: nowrap; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination { | |||
| display: flex; | |||
| flex: 1; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| flex-wrap: wrap; | |||
| gap: 8px; | |||
| padding: 0; | |||
| color: #dcecff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination span:not([class*="suffix"]), | |||
| .pagination-container ::v-deep .el-pagination button, | |||
| .pagination-container ::v-deep .el-pager li { | |||
| font-size: 14px; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .btn-prev, | |||
| .pagination-container ::v-deep .el-pagination .btn-next, | |||
| .pagination-container ::v-deep .el-pagination .el-pager li { | |||
| min-width: 32px; | |||
| height: 28px; | |||
| line-height: 26px; | |||
| margin: 0 3px; | |||
| border: 1px solid rgba(90, 170, 255, 0.45); | |||
| border-radius: 2px; | |||
| background: #10306d; | |||
| color: #ffffff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .el-pager li.active { | |||
| background: #2f7efb; | |||
| border-color: #2f7efb; | |||
| color: #ffffff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .btn-prev:hover, | |||
| .pagination-container ::v-deep .el-pagination .btn-next:hover, | |||
| .pagination-container ::v-deep .el-pagination .el-pager li:hover { | |||
| border-color: #64e7ff; | |||
| color: #64e7ff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .btn-prev.is-disabled, | |||
| .pagination-container ::v-deep .el-pagination .btn-next.is-disabled, | |||
| .pagination-container ::v-deep .el-pagination .el-pager li.disabled { | |||
| background: #0b1f54; | |||
| border-color: rgba(90, 170, 255, 0.2); | |||
| color: rgba(255, 255, 255, 0.35); | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .el-select .el-input { | |||
| width: 92px; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .el-input__inner { | |||
| height: 28px; | |||
| line-height: 28px; | |||
| background: #0f2b6f; | |||
| border: 1px solid rgba(80, 170, 255, 0.55); | |||
| color: #ffffff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination .el-input__icon { | |||
| line-height: 28px; | |||
| color: #7dd6ff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination__sizes, | |||
| .pagination-container ::v-deep .el-pagination__jump, | |||
| .pagination-container ::v-deep .el-pagination__total { | |||
| color: #dcecff; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination__editor.el-input { | |||
| width: 48px; | |||
| } | |||
| .pagination-container ::v-deep .el-pagination__editor.el-input .el-input__inner { | |||
| width: 48px; | |||
| padding: 0 4px; | |||
| } | |||
| .pagination-container.hidden { | |||
| display: none; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,112 @@ | |||
| <template> | |||
| <div class="card-progress-container"> | |||
| <div class="card-progress" :style="{ 'background': bgColor }"> | |||
| <div class="card-progress-inner" :style="{ | |||
| width: progress + '%', | |||
| background: color, | |||
| }"></div> | |||
| <div class="point-circle big-circle" :style="{ | |||
| left: progress + '%', | |||
| }"></div> | |||
| <div class="point-circle small-circle" :style="{ | |||
| left: progress + '%', | |||
| }"></div> | |||
| </div> | |||
| <div v-if="!hiddenText" class="progress-text" :style="{ | |||
| fontSize: textSize + 'px', | |||
| }"> | |||
| {{ progressText }} | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| name: "CardProgressBar", | |||
| props: { | |||
| progress: { | |||
| type: Number, | |||
| required: true, | |||
| }, | |||
| color: { | |||
| type: String, | |||
| default: "#0042A6", | |||
| }, | |||
| bgColor: { | |||
| type: String, | |||
| default: "#B0C5E9", | |||
| }, | |||
| textSize: { | |||
| type: Number, | |||
| default: 14, | |||
| }, | |||
| getText: { | |||
| type: Function, | |||
| default: (progress) => `${progress}%`, | |||
| }, | |||
| hiddenText: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| }, | |||
| computed: { | |||
| progressText () { | |||
| return this.getText(this.progress); | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .card-progress-container { | |||
| display: flex; | |||
| align-items: center; | |||
| width: 100%; | |||
| min-height: 18px; | |||
| } | |||
| .card-progress { | |||
| position: relative; | |||
| width: 100%; | |||
| /* overflow: hidden; */ | |||
| flex: 1; | |||
| background-color: #B0C5E9; | |||
| height: 8px; | |||
| border-radius: 6px; | |||
| } | |||
| .card-progress-inner { | |||
| position: relative; | |||
| display: flex; | |||
| align-items: center; | |||
| height: 8px; | |||
| border-radius: 6px; | |||
| } | |||
| .point-circle { | |||
| position: absolute; | |||
| border-radius: 50%; | |||
| transform: translate(-50%, -50%); | |||
| /* margin-left: -5px; */ | |||
| } | |||
| .small-circle { | |||
| background: #4677C3; | |||
| width: 12px; | |||
| height: 12px; | |||
| top: 50%; | |||
| } | |||
| .big-circle { | |||
| background: rgba(70, 119, 195, 0.25); | |||
| width: 18px; | |||
| height: 18px; | |||
| top: 50%; | |||
| } | |||
| .progress-text { | |||
| white-space: nowrap; | |||
| padding-left: 10px; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,389 @@ | |||
| <template> | |||
| <div | |||
| class="flex-row card-page" | |||
| style="background: #090918" | |||
| v-loading="loading" | |||
| > | |||
| <div style="flex: 1; height: 100%; display: flex; flex-direction: column"> | |||
| <div class="img-wrap"> | |||
| <AsyncImage | |||
| style="width: 100%; height: 100%" | |||
| fit="contain" | |||
| :src="selectImgUrl" | |||
| ></AsyncImage> | |||
| <DrawShape | |||
| ref="drawShapeRef" | |||
| :brokenLineCount="1" | |||
| @drawComplete=" | |||
| (shape) => { | |||
| handleDrawComplete(shape); | |||
| } | |||
| " | |||
| /> | |||
| <!-- 上一个图片 --> | |||
| <el-button | |||
| v-if="bShowBackBtn" | |||
| @click="() => selectIndex--" | |||
| class="img-handle-btn img-handle-btn-left left-btn" | |||
| ></el-button> | |||
| <!-- 下一个图片 --> | |||
| <el-button | |||
| v-if="bShowNextBtn" | |||
| @click="() => selectIndex++" | |||
| class="img-handle-btn img-handle-btn-right right-btn" | |||
| ></el-button> | |||
| </div> | |||
| <!-- 开始测温 --> | |||
| <div | |||
| v-if="dataShowTemperatureTool && dataImageUrls.length > 0" | |||
| class="measure-temperature-btn" | |||
| > | |||
| <div> | |||
| <el-button @click="measureTemperature(ShapeEnum.point)" type="primary" | |||
| >点测温</el-button | |||
| > | |||
| <el-button | |||
| @click="measureTemperature(ShapeEnum.brokenline)" | |||
| type="primary" | |||
| >线测温</el-button | |||
| > | |||
| <el-button @click="measureTemperature(ShapeEnum.rect)" type="primary" | |||
| >区域测温</el-button | |||
| > | |||
| <el-button | |||
| v-if="measuring" | |||
| @click="cancelMeasureTemperature" | |||
| type="primary" | |||
| >取消测温</el-button | |||
| > | |||
| </div> | |||
| <div style="flex: 1; text-align: right"> | |||
| <span style="padding-left: 10px" | |||
| >平均温度:{{ | |||
| temperatureInfo.averageT ? temperatureInfo.averageT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| <span style="padding-left: 10px" | |||
| >最大温度:{{ | |||
| temperatureInfo.maxT ? temperatureInfo.maxT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| <span style="padding-left: 10px" | |||
| >最小温度:{{ | |||
| temperatureInfo.maxT ? temperatureInfo.minT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| <span style="padding-left: 10px" | |||
| >环境温度:{{ | |||
| temperatureInfo.weatherT ? temperatureInfo.weatherT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="img-list-wrap" v-if="dataImageUrls.length > 1"> | |||
| <ul ref="imgUlRef"> | |||
| <li | |||
| v-for="(item, index) in dataImageUrls" | |||
| :key="index" | |||
| @click="onSelectImage(item, index)" | |||
| > | |||
| <div | |||
| :style="imgListItemStyle" | |||
| :class="{ 'img-list-item-selected': index === selectIndex }" | |||
| > | |||
| <AsyncImage | |||
| style="width: 100%; height: 100%" | |||
| fit="contain" | |||
| :src="item" | |||
| ></AsyncImage> | |||
| </div> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getTemperatureValue } from "@/api/device"; | |||
| import DrawShape, { ShapeEnum } from "@/components/Canvas/DrawShape.vue"; | |||
| // 图片列表item高度 | |||
| const IMG_LIST_ITEM_HEIGHT = 170; | |||
| export default { | |||
| name: "ImagePreviewDialog", | |||
| components: { DrawShape }, | |||
| props: { | |||
| // 是否显示弹窗 | |||
| visible: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| // 是否显示测温工具 | |||
| showTemperatureTool: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| // 图片地址列表 | |||
| imageUrls: { | |||
| type: Array, | |||
| default: [], | |||
| require: false, | |||
| }, | |||
| // 选择图片的位置 | |||
| selectImgPosition: { | |||
| type: Number, | |||
| default: 0, | |||
| }, | |||
| }, | |||
| data() { | |||
| return { | |||
| // 当前选择图片url | |||
| selectIndex: 0, | |||
| // 图片列表item样式,因为有自动滚动,为了使其高度便于统一修改,所以放在了js里 | |||
| imgListItemStyle: { | |||
| width: "300px", | |||
| height: `${IMG_LIST_ITEM_HEIGHT}px`, | |||
| margin: "5px 0", | |||
| }, | |||
| ShapeEnum: ShapeEnum, | |||
| // 是否正在测量 | |||
| measuring: false, | |||
| temperatureInfo: {}, | |||
| loading: false, | |||
| dataShowTemperatureTool: false, | |||
| dataImageUrls: [], | |||
| }; | |||
| }, | |||
| computed: { | |||
| selectImgUrl() { | |||
| // console.log('33',this.dataImageUrls.length > this.selectIndex | |||
| // ? 'http://192.168.1.116:33333/'+this.dataImageUrls[this.selectIndex] | |||
| // : "") | |||
| // return this.dataImageUrls.length > this.selectIndex | |||
| // ? 'http://192.168.1.116:33333/'+this.dataImageUrls[this.selectIndex] | |||
| // : ""; | |||
| // 武汉巡视任务图片列表查看 | |||
| return this.dataImageUrls.length > this.selectIndex | |||
| ? this.dataImageUrls[this.selectIndex] | |||
| : ""; | |||
| }, | |||
| // 是否显示下一页 | |||
| bShowNextBtn() { | |||
| return this.selectIndex + 1 < this.dataImageUrls.length; | |||
| }, | |||
| // 是否显示上一页 | |||
| bShowBackBtn() { | |||
| return this.selectIndex > 0; | |||
| }, | |||
| }, | |||
| watch: { | |||
| visible(value) { | |||
| // 显示之前先复位状态 | |||
| if (value) { | |||
| this.reset(); | |||
| } | |||
| }, | |||
| selectImgPosition() { | |||
| if (this.selectImgPosition < this.dataImageUrls.length) { | |||
| this.selectIndex = this.selectImgPosition; | |||
| } | |||
| }, | |||
| selectIndex(newValue) { | |||
| // 滚动列表 | |||
| if (this.$refs.imgUlRef) { | |||
| this.$refs.imgUlRef.scrollTo({ | |||
| top: IMG_LIST_ITEM_HEIGHT * newValue, | |||
| behavior: "smooth", | |||
| }); | |||
| } | |||
| }, | |||
| }, | |||
| created() { | |||
| this.dataImageUrls = this.imageUrls; | |||
| this.selectIndex = this.selectImgPosition; | |||
| this.dataShowTemperatureTool = this.showTemperatureTool; | |||
| // 没传值,则从缓存中取 | |||
| if (!this.dataImageUrls || !this.dataImageUrls.length) { | |||
| const info = JSON.parse(sessionStorage.getItem("imageInfo")); | |||
| if (info.imageUrls) { | |||
| this.dataImageUrls = info.imageUrls; | |||
| } | |||
| if (info.selectImgPosition) { | |||
| this.selectIndex = info.selectImgPosition; | |||
| } | |||
| if (info.showTemperatureTool) { | |||
| this.dataShowTemperatureTool = info.showTemperatureTool; | |||
| } | |||
| } | |||
| }, | |||
| methods: { | |||
| reset() { | |||
| this.selectIndex = 0; | |||
| this.hideMeasureBox(); | |||
| }, | |||
| // 选择图片 | |||
| onSelectImage(item, index) { | |||
| this.selectIndex = index; | |||
| }, | |||
| // 显示测温框 | |||
| showMeasureBox() { | |||
| if (this.$refs.drawShapeRef) { | |||
| this.temperatureInfo = {}; | |||
| this.measuring = true; | |||
| this.$refs.drawShapeRef.show(); | |||
| } | |||
| }, | |||
| // 显示测温框 | |||
| hideMeasureBox() { | |||
| this.measuring = false; | |||
| this.temperatureInfo = {}; | |||
| if (this.$refs.drawShapeRef) { | |||
| this.$refs.drawShapeRef.hide(); | |||
| } | |||
| }, | |||
| // 测温 | |||
| measureTemperature(type) { | |||
| this.showMeasureBox(); | |||
| this.$nextTick(() => { | |||
| this.$refs.drawShapeRef.init(); | |||
| this.$refs.drawShapeRef.startDraw(type); | |||
| }); | |||
| }, | |||
| cancelMeasureTemperature() { | |||
| this.hideMeasureBox(); | |||
| }, | |||
| handleDrawComplete(shape) { | |||
| const info = shape.getData(); | |||
| this.loading = true; | |||
| getTemperatureValue(info.data, info.type, this.selectImgUrl) | |||
| .then((res) => { | |||
| this.temperatureInfo = res.data; | |||
| }) | |||
| .finally(() => { | |||
| this.loading = false; | |||
| }); | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .flex-row { | |||
| display: flex; | |||
| height: 100%; | |||
| } | |||
| .img-wrap { | |||
| flex: 1; | |||
| // height: 100%; | |||
| height: 0; | |||
| position: relative; | |||
| .img-handle-btn { | |||
| position: absolute; | |||
| top: 45%; | |||
| color: rgba(255, 255, 255, 0.5); | |||
| font-size: 25px; | |||
| width: 96px; | |||
| height: 96px; | |||
| // border: rgba(255, 255, 255, 0.5) solid 2px; | |||
| // border-radius: 25px; | |||
| border-radius: 8px; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| cursor: pointer; | |||
| } | |||
| .img-handle-btn-left { | |||
| left: 15px; | |||
| } | |||
| .img-handle-btn-right { | |||
| right: 15px; | |||
| } | |||
| } | |||
| .measure-temperature-btn { | |||
| padding: 10px 0; | |||
| display: flex; | |||
| } | |||
| .img-list-wrap { | |||
| width: 300px; | |||
| height: 100%; | |||
| padding: 0 0 0 10px; | |||
| ul { | |||
| width: 100%; | |||
| height: 100%; | |||
| padding: 0; | |||
| overflow-x: hidden; | |||
| overflow-y: auto; | |||
| margin: 0; | |||
| /* 解决火狐浏览器不隐藏滚动条的问题 */ | |||
| scrollbar-width: none; | |||
| .img-list-item { | |||
| width: 300px; | |||
| height: 170px; | |||
| margin: 5px 0; | |||
| } | |||
| .img-list-item-selected { | |||
| border: 3px solid #40486a; | |||
| } | |||
| } | |||
| } | |||
| ::v-deep .left-btn .is-disabled { | |||
| /* 禁用状态下移除 active 背景色 */ | |||
| background-image: none; | |||
| } | |||
| .left-btn { | |||
| /* 设置默认状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-normal.png"); | |||
| } | |||
| .left-btn:disabled { | |||
| /* 设置禁用状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-disable.png"); | |||
| } | |||
| .left-btn:not(.is-disabled):active { | |||
| /* 设置按下状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-press.png"); | |||
| } | |||
| .left-btn:not(.is-disabled):hover { | |||
| /* 设置悬停状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-hover.png"); | |||
| } | |||
| .right-btn { | |||
| /* 设置默认状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-normal.png"); | |||
| } | |||
| .right-btn:disabled { | |||
| /* 设置禁用状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-disable.png"); | |||
| } | |||
| .right-btn:not(.is-disabled):active { | |||
| /* 设置按下状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-press.png"); | |||
| } | |||
| .right-btn:not(.is-disabled):hover { | |||
| /* 设置悬停状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-hover.png"); | |||
| } | |||
| </style> | |||
| @ -0,0 +1,654 @@ | |||
| <template> | |||
| <div | |||
| class="flex-row card-page" | |||
| style="background: #090918" | |||
| v-loading="loading" | |||
| > | |||
| <div style="flex: 1; height: 100%; display: flex; flex-direction: column"> | |||
| <div class="img-wrap"> | |||
| <!-- 修改这部分:添加缩放容器和包装器 --> | |||
| <div class="zoom-container" ref="zoomContainer"> | |||
| <div | |||
| class="zoom-wrapper" | |||
| ref="zoomWrapper" | |||
| :style="{ | |||
| transform: `scale(${imageScale})`, | |||
| transformOrigin: '0 0', | |||
| position: 'absolute', | |||
| left: `${imagePosition.x}px`, | |||
| top: `${imagePosition.y}px`, | |||
| width: '100%', | |||
| height: '100%' | |||
| }" | |||
| @mousedown="startDrag($event)" | |||
| @touchstart="startTouchDrag($event)" | |||
| > | |||
| <AsyncImage | |||
| style="width: 100%; height: 100%" | |||
| fit="contain" | |||
| :src="selectImgUrl" | |||
| :supportPreview="false" | |||
| ></AsyncImage> | |||
| </div> | |||
| </div> | |||
| <DrawShape | |||
| ref="drawShapeRef" | |||
| :brokenLineCount="1" | |||
| @drawComplete=" | |||
| (shape) => { | |||
| handleDrawComplete(shape); | |||
| } | |||
| " | |||
| /> | |||
| <!-- 添加重置按钮 --> | |||
| <div class="zoom-controls"> | |||
| <el-button | |||
| size="mini" | |||
| circle | |||
| @click="resetImage" | |||
| title="重置" | |||
| > | |||
| ↺ | |||
| </el-button> | |||
| </div> | |||
| <!-- 上一个图片 --> | |||
| <el-button | |||
| v-if="bShowBackBtn" | |||
| @click="() => selectIndex--" | |||
| class="img-handle-btn img-handle-btn-left left-btn" | |||
| ></el-button> | |||
| <!-- 下一个图片 --> | |||
| <el-button | |||
| v-if="bShowNextBtn" | |||
| @click="() => selectIndex++" | |||
| class="img-handle-btn img-handle-btn-right right-btn" | |||
| ></el-button> | |||
| </div> | |||
| <!-- 开始测温 --> | |||
| <div | |||
| v-if="dataShowTemperatureTool && dataImageUrls.length > 0" | |||
| class="measure-temperature-btn" | |||
| > | |||
| <div> | |||
| <el-button @click="measureTemperature(ShapeEnum.point)" type="primary" | |||
| >点测温</el-button | |||
| > | |||
| <el-button | |||
| @click="measureTemperature(ShapeEnum.brokenline)" | |||
| type="primary" | |||
| >线测温</el-button | |||
| > | |||
| <el-button @click="measureTemperature(ShapeEnum.rect)" type="primary" | |||
| >区域测温</el-button | |||
| > | |||
| <el-button | |||
| v-if="measuring" | |||
| @click="cancelMeasureTemperature" | |||
| type="primary" | |||
| >取消测温</el-button | |||
| > | |||
| </div> | |||
| <div style="flex: 1; text-align: right"> | |||
| <span style="padding-left: 10px" | |||
| >平均温度:{{ | |||
| temperatureInfo.averageT ? temperatureInfo.averageT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| <span style="padding-left: 10px" | |||
| >最大温度:{{ | |||
| temperatureInfo.maxT ? temperatureInfo.maxT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| <span style="padding-left: 10px" | |||
| >最小温度:{{ | |||
| temperatureInfo.maxT ? temperatureInfo.minT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| <span style="padding-left: 10px" | |||
| >环境温度:{{ | |||
| temperatureInfo.weatherT ? temperatureInfo.weatherT + "℃" : "-" | |||
| }}</span | |||
| > | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="img-list-wrap" v-if="dataImageUrls.length > 1"> | |||
| <ul ref="imgUlRef"> | |||
| <li | |||
| v-for="(item, index) in dataImageUrls" | |||
| :key="index" | |||
| @click="onSelectImage(item, index)" | |||
| > | |||
| <div | |||
| :style="imgListItemStyle" | |||
| :class="{ 'img-list-item-selected': index === selectIndex }" | |||
| > | |||
| <AsyncImage | |||
| style="width: 100%; height: 100%" | |||
| fit="contain" | |||
| :src="item" | |||
| ></AsyncImage> | |||
| </div> | |||
| </li> | |||
| </ul> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getTemperatureValue } from "@/api/device"; | |||
| import DrawShape, { ShapeEnum } from "@/components/Canvas/DrawShape.vue"; | |||
| // 图片列表item高度 | |||
| const IMG_LIST_ITEM_HEIGHT = 170; | |||
| export default { | |||
| name: "ImagePreviewDialog", | |||
| components: { DrawShape }, | |||
| props: { | |||
| // 是否显示弹窗 | |||
| visible: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| // 是否显示测温工具 | |||
| showTemperatureTool: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| // 图片地址列表 | |||
| imageUrls: { | |||
| type: Array, | |||
| default: [], | |||
| require: false, | |||
| }, | |||
| // 选择图片的位置 | |||
| selectImgPosition: { | |||
| type: Number, | |||
| default: 0, | |||
| }, | |||
| }, | |||
| data() { | |||
| return { | |||
| // 当前选择图片url | |||
| selectIndex: 0, | |||
| // 图片列表item样式,因为有自动滚动,为了使其高度便于统一修改,所以放在了js里 | |||
| imgListItemStyle: { | |||
| width: "300px", | |||
| height: `${IMG_LIST_ITEM_HEIGHT}px`, | |||
| margin: "5px 0", | |||
| }, | |||
| ShapeEnum: ShapeEnum, | |||
| // 是否正在测量 | |||
| measuring: false, | |||
| temperatureInfo: {}, | |||
| loading: false, | |||
| dataShowTemperatureTool: false, | |||
| dataImageUrls: [], | |||
| // 新增:缩放相关数据 | |||
| imageScale: 1, | |||
| imagePosition: { x: 0, y: 0 }, | |||
| isDragging: false, | |||
| dragStartPos: { x: 0, y: 0 }, | |||
| dragStartImagePos: { x: 0, y: 0 }, | |||
| }; | |||
| }, | |||
| computed: { | |||
| selectImgUrl() { | |||
| return this.dataImageUrls.length > this.selectIndex | |||
| ? this.dataImageUrls[this.selectIndex] | |||
| : ""; | |||
| }, | |||
| // 是否显示下一页 | |||
| bShowNextBtn() { | |||
| return this.selectIndex + 1 < this.dataImageUrls.length; | |||
| }, | |||
| // 是否显示上一页 | |||
| bShowBackBtn() { | |||
| return this.selectIndex > 0; | |||
| }, | |||
| }, | |||
| watch: { | |||
| visible(value) { | |||
| // 显示之前先复位状态 | |||
| if (value) { | |||
| this.reset(); | |||
| } | |||
| }, | |||
| selectImgPosition() { | |||
| if (this.selectImgPosition < this.dataImageUrls.length) { | |||
| this.selectIndex = this.selectImgPosition; | |||
| this.resetImage(); // 切换到新图片时重置缩放 | |||
| } | |||
| }, | |||
| selectIndex() { | |||
| // 滚动列表 | |||
| if (this.$refs.imgUlRef) { | |||
| this.$refs.imgUlRef.scrollTo({ | |||
| top: IMG_LIST_ITEM_HEIGHT * this.selectIndex, | |||
| behavior: "smooth", | |||
| }); | |||
| } | |||
| // 切换图片时重置缩放 | |||
| this.resetImage(); | |||
| }, | |||
| }, | |||
| created() { | |||
| this.dataImageUrls = this.imageUrls; | |||
| this.selectIndex = this.selectImgPosition; | |||
| this.dataShowTemperatureTool = this.showTemperatureTool; | |||
| // 没传值,则从缓存中取 | |||
| if (!this.dataImageUrls || !this.dataImageUrls.length) { | |||
| const info = JSON.parse(sessionStorage.getItem("imageInfo")); | |||
| if (info.imageUrls) { | |||
| this.dataImageUrls = info.imageUrls; | |||
| } | |||
| if (info.selectImgPosition) { | |||
| this.selectIndex = info.selectImgPosition; | |||
| } | |||
| if (info.showTemperatureTool) { | |||
| this.dataShowTemperatureTool = info.showTemperatureTool; | |||
| } | |||
| } | |||
| }, | |||
| mounted() { | |||
| // 绑定事件 | |||
| this.bindGlobalEvents(); | |||
| // 添加滚轮事件监听 | |||
| this.$nextTick(() => { | |||
| if (this.$refs.zoomContainer) { | |||
| this.$refs.zoomContainer.addEventListener('wheel', this.handleWheel, { passive: false }); | |||
| } | |||
| }); | |||
| }, | |||
| beforeDestroy() { | |||
| // 移除事件 | |||
| this.unbindGlobalEvents(); | |||
| // 移除滚轮事件 | |||
| if (this.$refs.zoomContainer) { | |||
| this.$refs.zoomContainer.removeEventListener('wheel', this.handleWheel); | |||
| } | |||
| }, | |||
| methods: { | |||
| reset() { | |||
| this.selectIndex = 0; | |||
| this.hideMeasureBox(); | |||
| this.resetImage(); // 重置缩放 | |||
| }, | |||
| // 选择图片 | |||
| onSelectImage(item, index) { | |||
| this.selectIndex = index; | |||
| }, | |||
| // 显示测温框 | |||
| showMeasureBox() { | |||
| if (this.$refs.drawShapeRef) { | |||
| this.temperatureInfo = {}; | |||
| this.measuring = true; | |||
| this.$refs.drawShapeRef.show(); | |||
| } | |||
| }, | |||
| // 显示测温框 | |||
| hideMeasureBox() { | |||
| this.measuring = false; | |||
| this.temperatureInfo = {}; | |||
| if (this.$refs.drawShapeRef) { | |||
| this.$refs.drawShapeRef.hide(); | |||
| } | |||
| }, | |||
| // 测温 | |||
| measureTemperature(type) { | |||
| this.showMeasureBox(); | |||
| this.$nextTick(() => { | |||
| this.$refs.drawShapeRef.init(); | |||
| this.$refs.drawShapeRef.startDraw(type); | |||
| }); | |||
| }, | |||
| cancelMeasureTemperature() { | |||
| this.hideMeasureBox(); | |||
| }, | |||
| handleDrawComplete(shape) { | |||
| const info = shape.getData(); | |||
| this.loading = true; | |||
| getTemperatureValue(info.data, info.type, this.selectImgUrl) | |||
| .then((res) => { | |||
| this.temperatureInfo = res.data; | |||
| }) | |||
| .finally(() => { | |||
| this.loading = false; | |||
| }); | |||
| }, | |||
| // 新增:滚轮事件处理 | |||
| handleWheel(event) { | |||
| event.preventDefault(); | |||
| const delta = event.deltaY || event.detail || event.wheelDelta; | |||
| const direction = delta > 0 ? -1 : 1; | |||
| const scaleStep = 0.1; | |||
| const oldScale = this.imageScale; | |||
| let newScale = oldScale + direction * scaleStep; | |||
| newScale = Math.max(0.1, Math.min(5, newScale)); | |||
| // 计算鼠标在容器中的位置 | |||
| const container = event.currentTarget; | |||
| const containerRect = container.getBoundingClientRect(); | |||
| const mouseX = event.clientX - containerRect.left; | |||
| const mouseY = event.clientY - containerRect.top; | |||
| // 当前图片位置 | |||
| const currentPos = this.imagePosition; | |||
| // 计算缩放后的新位置 | |||
| // 缩放比例变化 | |||
| const scaleChange = newScale / oldScale; | |||
| // 调整位置使鼠标点保持在图片上的同一位置 | |||
| this.imagePosition = { | |||
| x: currentPos.x - (mouseX - currentPos.x) * (scaleChange - 1), | |||
| y: currentPos.y - (mouseY - currentPos.y) * (scaleChange - 1) | |||
| }; | |||
| this.imageScale = newScale; | |||
| }, | |||
| // 开始鼠标拖拽 | |||
| startDrag(event) { | |||
| if (event.button !== 0) return; | |||
| this.isDragging = true; | |||
| this.dragStartPos = { | |||
| x: event.clientX, | |||
| y: event.clientY | |||
| }; | |||
| this.dragStartImagePos = { | |||
| x: this.imagePosition.x, | |||
| y: this.imagePosition.y | |||
| }; | |||
| // 添加拖拽样式 | |||
| const wrapper = this.$refs.zoomWrapper; | |||
| if (wrapper) { | |||
| wrapper.classList.add('dragging'); | |||
| } | |||
| // 防止文本选择 | |||
| document.body.style.userSelect = 'none'; | |||
| document.body.style.webkitUserSelect = 'none'; | |||
| event.preventDefault(); | |||
| event.stopPropagation(); | |||
| }, | |||
| // 开始触摸拖拽 | |||
| startTouchDrag(event) { | |||
| if (event.touches.length !== 1) return; | |||
| this.isDragging = true; | |||
| this.dragStartPos = { | |||
| x: event.touches[0].clientX, | |||
| y: event.touches[0].clientY | |||
| }; | |||
| this.dragStartImagePos = { | |||
| x: this.imagePosition.x, | |||
| y: this.imagePosition.y | |||
| }; | |||
| const wrapper = this.$refs.zoomWrapper; | |||
| if (wrapper) { | |||
| wrapper.classList.add('dragging'); | |||
| } | |||
| event.preventDefault(); | |||
| event.stopPropagation(); | |||
| }, | |||
| // 处理拖拽移动 | |||
| handleDragMove(event) { | |||
| if (!this.isDragging) return; | |||
| let clientX, clientY; | |||
| if (event.type === 'mousemove') { | |||
| clientX = event.clientX; | |||
| clientY = event.clientY; | |||
| } else if (event.type === 'touchmove' && event.touches.length === 1) { | |||
| clientX = event.touches[0].clientX; | |||
| clientY = event.touches[0].clientY; | |||
| } else { | |||
| return; | |||
| } | |||
| const deltaX = clientX - this.dragStartPos.x; | |||
| const deltaY = clientY - this.dragStartPos.y; | |||
| // 更新图片位置 | |||
| this.imagePosition = { | |||
| x: this.dragStartImagePos.x + deltaX, | |||
| y: this.dragStartImagePos.y + deltaY | |||
| }; | |||
| event.preventDefault(); | |||
| event.stopPropagation(); | |||
| }, | |||
| // 结束拖拽 | |||
| handleDragEnd(event) { | |||
| if (!this.isDragging) return; | |||
| // 移除拖拽样式 | |||
| const wrapper = this.$refs.zoomWrapper; | |||
| if (wrapper) { | |||
| wrapper.classList.remove('dragging'); | |||
| } | |||
| // 恢复文本选择 | |||
| document.body.style.userSelect = ''; | |||
| document.body.style.webkitUserSelect = ''; | |||
| this.isDragging = false; | |||
| }, | |||
| // 重置图片缩放和位置 | |||
| resetImage() { | |||
| this.imageScale = 1; | |||
| this.imagePosition = { x: 0, y: 0 }; | |||
| }, | |||
| // 绑定全局事件 | |||
| bindGlobalEvents() { | |||
| document.addEventListener('mousemove', this.handleDragMove); | |||
| document.addEventListener('mouseup', this.handleDragEnd); | |||
| document.addEventListener('touchmove', this.handleDragMove, { passive: false }); | |||
| document.addEventListener('touchend', this.handleDragEnd); | |||
| }, | |||
| // 移除全局事件 | |||
| unbindGlobalEvents() { | |||
| document.removeEventListener('mousemove', this.handleDragMove); | |||
| document.removeEventListener('mouseup', this.handleDragEnd); | |||
| document.removeEventListener('touchmove', this.handleDragMove); | |||
| document.removeEventListener('touchend', this.handleDragEnd); | |||
| } | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .flex-row { | |||
| display: flex; | |||
| height: 100%; | |||
| } | |||
| .img-wrap { | |||
| flex: 1; | |||
| height: 0; | |||
| position: relative; | |||
| // 新增:缩放容器样式 | |||
| .zoom-container { | |||
| width: 100%; | |||
| height: 100%; | |||
| position: relative; | |||
| overflow: hidden; | |||
| cursor: grab; | |||
| &:active { | |||
| cursor: grabbing; | |||
| } | |||
| } | |||
| .zoom-wrapper { | |||
| width: 100%; | |||
| height: 100%; | |||
| position: absolute; | |||
| cursor: grab; | |||
| &.dragging { | |||
| cursor: grabbing; | |||
| } | |||
| ::v-deep .el-image { | |||
| width: 100%; | |||
| height: 100%; | |||
| user-select: none; | |||
| -webkit-user-drag: none; | |||
| pointer-events: none; /* 防止图片干扰拖拽事件 */ | |||
| } | |||
| } | |||
| // 新增:缩放控制按钮 | |||
| .zoom-controls { | |||
| position: absolute; | |||
| top: 10px; | |||
| right: 10px; | |||
| z-index: 10; | |||
| .el-button { | |||
| background: rgba(0, 0, 0, 0.6); | |||
| color: white; | |||
| border: none; | |||
| font-size: 16px; | |||
| &:hover { | |||
| background: rgba(0, 0, 0, 0.8); | |||
| } | |||
| } | |||
| } | |||
| .img-handle-btn { | |||
| position: absolute; | |||
| top: 45%; | |||
| color: rgba(255, 255, 255, 0.5); | |||
| font-size: 25px; | |||
| width: 96px; | |||
| height: 96px; | |||
| border-radius: 8px; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| cursor: pointer; | |||
| z-index: 11; // 确保在缩放控制之上 | |||
| } | |||
| .img-handle-btn-left { | |||
| left: 15px; | |||
| } | |||
| .img-handle-btn-right { | |||
| right: 15px; | |||
| } | |||
| } | |||
| .measure-temperature-btn { | |||
| padding: 10px 0; | |||
| display: flex; | |||
| } | |||
| .img-list-wrap { | |||
| width: 300px; | |||
| height: 100%; | |||
| padding: 0 0 0 10px; | |||
| ul { | |||
| width: 100%; | |||
| height: 100%; | |||
| padding: 0; | |||
| overflow-x: hidden; | |||
| overflow-y: auto; | |||
| margin: 0; | |||
| /* 解决火狐浏览器不隐藏滚动条的问题 */ | |||
| scrollbar-width: none; | |||
| .img-list-item { | |||
| width: 300px; | |||
| height: 170px; | |||
| margin: 5px 0; | |||
| } | |||
| .img-list-item-selected { | |||
| border: 3px solid #40486a; | |||
| } | |||
| } | |||
| } | |||
| ::v-deep .left-btn .is-disabled { | |||
| /* 禁用状态下移除 active 背景色 */ | |||
| background-image: none; | |||
| } | |||
| .left-btn { | |||
| /* 设置默认状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-normal.png"); | |||
| } | |||
| .left-btn:disabled { | |||
| /* 设置禁用状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-disable.png"); | |||
| } | |||
| .left-btn:not(.is-disabled):active { | |||
| /* 设置按下状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-press.png"); | |||
| } | |||
| .left-btn:not(.is-disabled):hover { | |||
| /* 设置悬停状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-left-hover.png"); | |||
| } | |||
| .right-btn { | |||
| /* 设置默认状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-normal.png"); | |||
| } | |||
| .right-btn:disabled { | |||
| /* 设置禁用状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-disable.png"); | |||
| } | |||
| .right-btn:not(.is-disabled):active { | |||
| /* 设置按下状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-press.png"); | |||
| } | |||
| .right-btn:not(.is-disabled):hover { | |||
| /* 设置悬停状态的背景图片 */ | |||
| background-image: url("../../assets/image-btn-right-hover.png"); | |||
| } | |||
| </style> | |||
| @ -0,0 +1,138 @@ | |||
| <template> | |||
| <div class="card-box-bg" :style="cardBoxStyle"> | |||
| <div v-if="showTitle" class="card-box-title-box" :style="titleStyle"> | |||
| <div class="card-box-title"> | |||
| <div | |||
| v-if="showBackBtn" | |||
| style="display: inline-block; cursor: pointer" | |||
| @click="handleBack" | |||
| > | |||
| <i class="el-icon-back"></i> | |||
| </div> | |||
| {{ title }} | |||
| </div> | |||
| <div class="title-btns-slot"> | |||
| <slot name="title-btns"></slot> | |||
| </div> | |||
| </div> | |||
| <div class="content" :class="{ 'content-border': !bgImgURL }"> | |||
| <slot /> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| name: "CardBox", | |||
| props: { | |||
| // 背景图 | |||
| bgImgURL: { | |||
| type: String, | |||
| default: "", | |||
| }, | |||
| // 标题 | |||
| title: { | |||
| type: String, | |||
| default: "", | |||
| }, | |||
| // 不设置bgImgURL时使用 | |||
| titleImgURL: { | |||
| type: String, | |||
| default: "", | |||
| }, | |||
| showTitle: { | |||
| type: Boolean, | |||
| default: true, | |||
| }, | |||
| showBackBtn: { | |||
| type: Boolean, | |||
| default: false, | |||
| }, | |||
| }, | |||
| data() { | |||
| return {}; | |||
| }, | |||
| computed: { | |||
| cardBoxStyle() { | |||
| if (this.bgImgURL) { | |||
| return { | |||
| backgroundImage: "url(" + this.bgImgURL + ")", | |||
| backgroundSize: "cover", | |||
| }; | |||
| } else { | |||
| return {}; | |||
| } | |||
| }, | |||
| titleStyle() { | |||
| if (this.titleImgURL) { | |||
| return { | |||
| backgroundImage: "url(" + this.titleImgURL + ")", | |||
| backgroundSize: "cover", | |||
| }; | |||
| } else { | |||
| return {}; | |||
| } | |||
| }, | |||
| }, | |||
| created() {}, | |||
| beforeDestroy() {}, | |||
| methods: { | |||
| handleBack() { | |||
| this.$emit("onBack"); | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped> | |||
| .card-box-bg { | |||
| background-size: cover; | |||
| background-repeat: no-repeat; | |||
| background-position: center; | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| .card-box-title-box { | |||
| display: flex; | |||
| flex-direction: row; | |||
| } | |||
| .card-box-title { | |||
| color: #fff; | |||
| font-size: 22px; | |||
| font-weight: bold; | |||
| height: 40px; | |||
| line-height: 40px; | |||
| margin-left: 60px; | |||
| flex: 2; | |||
| z-index: 1; | |||
| } | |||
| .title-btns-slot { | |||
| flex: 1; | |||
| display: flex; | |||
| align-items: flex-end; | |||
| justify-content: flex-end; | |||
| padding-bottom: 2px; | |||
| padding-right: 5px; | |||
| } | |||
| .content-border { | |||
| /* 分割线 */ | |||
| border: 1px solid #40486a; | |||
| } | |||
| .content { | |||
| flex: 1; | |||
| height: 0; | |||
| padding: 15px; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,221 @@ | |||
| <template> | |||
| <div class="model-dialog-container" ref="modelDialogContainer"> | |||
| <div class="model-dialog-title" @mousedown="startDrag"> | |||
| 模型信息查询 | |||
| <i class="el-icon-close cloesIcon" @click="showModelDialog"></i> | |||
| </div> | |||
| <div class="model-dialog-content"> | |||
| <div class="modelDialogContent" ref="modelDialogContent"> | |||
| <!-- <div class="item" v-for="item in 100">11111111</div> --> | |||
| <!-- <div class="modelSend"> | |||
| 撒打算来到喀山水水水水水水水水水水水水水水水水水水水 | |||
| </div> | |||
| <div class="modelReceive"> | |||
| 撒打算来到喀山水水水水水水水水水水水水水水水水水水水 | |||
| </div> --> | |||
| </div> | |||
| <div class="input-box"> | |||
| <el-input | |||
| class="input-box-input" | |||
| placeholder="请输入需要查询的内容" | |||
| v-model="modelName" | |||
| @keyup.enter="sendBtn" | |||
| ></el-input> | |||
| <el-button @click="sendBtn">发送</el-button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { semanticsModel } from "@/api/modelDialog/modelDialog.js"; | |||
| import { set } from "nprogress"; | |||
| export default { | |||
| data() { | |||
| return { | |||
| modelName: "", | |||
| isDragging: false, | |||
| offset: { x: 0, y: 0 }, | |||
| }; | |||
| }, | |||
| methods: { | |||
| sendBtn(event) { | |||
| if (event && !event.isTrusted) return; | |||
| if (!this.modelName.trim()) return; | |||
| // 创建新元素 | |||
| let newDiv = document.createElement("div"); | |||
| newDiv.className = "modelSend"; | |||
| newDiv.textContent = this.modelName; | |||
| // 添加到 .modelDialogContent 容器中 | |||
| let container = this.$refs.modelDialogContent; | |||
| container.appendChild(newDiv); | |||
| let inputStr = this.modelName; | |||
| this.modelName = ""; | |||
| // 滚动到底部 | |||
| container.scrollTop = container.scrollHeight; | |||
| // ajax..... | |||
| semanticsModel({ text: inputStr }).then((res) => { | |||
| console.log(res, "模型返回"); | |||
| if (res.code == 200) { | |||
| let str = res.data; | |||
| console.log(str, "模型数据处理"); | |||
| let receiveDiv = document.createElement("div"); | |||
| receiveDiv.classList.add("modelReceive", "modelReceiveTyping"); | |||
| receiveDiv.textContent = str; | |||
| // 添加到 .content 容器中 | |||
| // this.$refs.modelDialogContent.appendChild(receiveDiv); | |||
| container.appendChild(receiveDiv); | |||
| container.scrollTop = container.scrollHeight; | |||
| // this.typeWriterWithAutoScroll(str, receiveDiv, 50); | |||
| } | |||
| }); | |||
| }, | |||
| // typeWriterWithAutoScroll(text, element, speed) { | |||
| // let i = 0; | |||
| // // 清空容器 | |||
| // element.html(""); | |||
| // function typing() { | |||
| // if (i < text.length) { | |||
| // // 添加新字符 | |||
| // element.append(text.charAt(i)); | |||
| // // 自动滚动到底部 | |||
| // $(".modelDialogContent").scrollTop(element[0].scrollHeight); | |||
| // i++; | |||
| // setTimeout(typing, speed); | |||
| // } else { | |||
| // // 完成时移除光标 | |||
| // element.find(".typewriter-cursor").remove(); | |||
| // element.classList.remove("modelReceiveTyping"); | |||
| // } | |||
| // } | |||
| // // 开始打字效果 | |||
| // typing(); | |||
| // }, | |||
| showModelDialog() { | |||
| console.log(111); | |||
| this.$emit("showModelDialog", false); | |||
| }, | |||
| // 开始拖拽 | |||
| startDrag(e) { | |||
| if (e.target !== e.currentTarget) return; // 防止点击子元素触发拖拽 | |||
| this.isDragging = true; | |||
| const dialog = this.$refs.modelDialogContainer; | |||
| const rect = dialog.getBoundingClientRect(); | |||
| this.offset = { | |||
| x: e.clientX - rect.left, | |||
| y: e.clientY - rect.top, | |||
| }; | |||
| document.addEventListener("mousemove", this.onDrag); | |||
| document.addEventListener("mouseup", this.endDrag); | |||
| }, | |||
| // 拖拽中 | |||
| onDrag(e) { | |||
| if (!this.isDragging) return; | |||
| const dialog = this.$refs.modelDialogContainer; | |||
| const x = e.clientX - this.offset.x; | |||
| const y = e.clientY - this.offset.y; | |||
| dialog.style.left = `${x}px`; | |||
| dialog.style.top = `${y}px`; | |||
| }, | |||
| // 结束拖拽 | |||
| endDrag() { | |||
| this.isDragging = false; | |||
| document.removeEventListener("mousemove", this.onDrag); | |||
| document.removeEventListener("mouseup", this.endDrag); | |||
| }, | |||
| showModelDialog() { | |||
| this.$emit("showModelDialog", false); | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style> | |||
| .model-dialog-container { | |||
| width: 500px; | |||
| height: 600px; | |||
| background-color: #0a0919; | |||
| position: fixed; | |||
| top: 120px; | |||
| /* right: 20px; */ | |||
| left: calc(100% - 520px); /* 替代 right */ | |||
| z-index: 999999; | |||
| border-radius: 5px; | |||
| /* border: 1px solid #006686; */ | |||
| } | |||
| .model-dialog-title { | |||
| height: 46px; | |||
| line-height: 37px; | |||
| /* padding: 5px; */ | |||
| padding-left: 40px; | |||
| background-image: url("../assets/right-title.png"); | |||
| background-size: 100% 46px; | |||
| position: relative; | |||
| cursor: move; | |||
| } | |||
| .model-dialog-content { | |||
| padding: 10px; | |||
| width: 100%; | |||
| height: calc(100% - 46px); | |||
| } | |||
| .modelDialogContent { | |||
| width: 100%; | |||
| height: 90%; | |||
| border: 1px solid #006686; | |||
| border-radius: 5px; | |||
| overflow-y: scroll; | |||
| padding: 5px; | |||
| /* background: red; */ | |||
| } | |||
| .modelDialogContent div { | |||
| margin-bottom: 5px; | |||
| } | |||
| .input-box { | |||
| margin-top: 10px; | |||
| width: 100%; | |||
| /* height: 10%; */ | |||
| /* border: 1px solid #006686; */ | |||
| display: flex; | |||
| align-items: flex-start; | |||
| justify-content: space-between; | |||
| } | |||
| .input-box-input { | |||
| width: 85%; | |||
| } | |||
| .modelSend { | |||
| width: 80%; | |||
| line-height: 24px; | |||
| background: #172533; | |||
| /* color: black; */ | |||
| border-radius: 5px; | |||
| margin-left: 20%; | |||
| padding: 5px; | |||
| padding-right: 0px; | |||
| } | |||
| .modelReceive { | |||
| width: 80%; | |||
| line-height: 24px; | |||
| background: #1e4451; | |||
| border-radius: 5px; | |||
| padding: 5px; | |||
| } | |||
| .queryHint { | |||
| width: 80%; | |||
| line-height: 24px; | |||
| background: #1e4451; | |||
| border-radius: 5px; | |||
| padding: 5px; | |||
| } | |||
| .cloesIcon { | |||
| position: absolute; | |||
| right: 10px; | |||
| font-size: 30px; | |||
| top: 5px; | |||
| cursor: pointer; | |||
| z-index: 9999999; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,300 @@ | |||
| <template> | |||
| <div class="card-page page-content"> | |||
| <!-- <el-form | |||
| :model="queryParams" | |||
| ref="queryForm" | |||
| :inline="true" | |||
| label-width="90px" | |||
| > | |||
| <el-form-item label="设备名称" prop="deviceName"> | |||
| <el-input | |||
| v-model="queryParams.deviceName" | |||
| placeholder="设备名称" | |||
| clearable | |||
| ></el-input> | |||
| </el-form-item> | |||
| <el-form-item label="告警时间"> | |||
| <el-date-picker | |||
| v-model="dateRange" | |||
| size="small" | |||
| style="width: 240px" | |||
| value-format="yyyy-MM-dd" | |||
| type="daterange" | |||
| :unlink-panels="true" | |||
| :picker-options="dateRangePickerOptions" | |||
| range-separator="-" | |||
| start-placeholder="开始日期" | |||
| end-placeholder="结束日期" | |||
| :clearable="true" | |||
| popper-class="card-date-editor" | |||
| ></el-date-picker> | |||
| </el-form-item> | |||
| <el-form-item> | |||
| <el-button | |||
| type="primary" | |||
| icon="el-icon-search" | |||
| size="mini" | |||
| @click="handleQuery" | |||
| >搜索</el-button | |||
| > | |||
| <el-button icon="el-icon-refresh" size="mini" @click="resetQuery" | |||
| >重置</el-button | |||
| > | |||
| </el-form-item> | |||
| </el-form> --> | |||
| <div class="table-card" ref="tableCardRef"> | |||
| <el-table | |||
| style="height: 100%" | |||
| class="card-table" | |||
| v-loading="loading" | |||
| :data="dataList" | |||
| :max-height="tableHeight" | |||
| border | |||
| stripe | |||
| > | |||
| <el-table-column label="序号" align="center" width="55" type="index" /> | |||
| <el-table-column label="区域" align="center" prop="areaName" /> | |||
| <el-table-column label="设备名称" align="center" prop="deviceName" /> | |||
| <el-table-column label="相机名" align="center" prop="channelName" /> | |||
| <el-table-column label="系统" align="center" prop="deviceSource" /> | |||
| <!-- <el-table-column label="部件名称" align="center" prop="componentName" /> --> | |||
| <el-table-column label="点位名称" align="center" prop="pointName" /> | |||
| <!-- <el-table-column label="间隔名称" align="center" prop="bayName" />--> | |||
| <!-- <el-table-column label="实物ID" align="center" prop="materialId" />--> | |||
| <el-table-column label="缺陷类型" align="center" prop="algName"> | |||
| </el-table-column> | |||
| <!-- <el-table-column label="告警等级" align="center" prop="warnlevel" /> --> | |||
| <!-- 初筛异常 filter==1 --> | |||
| <el-table-column label="初筛基准图" align="center" v-if="filter == 1"> | |||
| <template slot-scope="scope"> | |||
| <AsyncImage | |||
| v-if="scope.row.imageNormalUrlPath" | |||
| class="table-img" | |||
| :src="scope.row.imageNormalUrlPath" | |||
| :supportPreview="false" | |||
| @click="onImagePreviewClicked(scope.row.imageNormalUrlPath)" | |||
| ></AsyncImage> | |||
| <span v-else>--</span> | |||
| </template> | |||
| </el-table-column> | |||
| <!-- 初筛异常 filter==1 --> | |||
| <el-table-column label="初筛分析图" align="center" v-if="filter == 1"> | |||
| <template slot-scope="scope"> | |||
| <AsyncImage | |||
| v-if="scope.row.imgAnalyse" | |||
| class="table-img" | |||
| :src="scope.row.imgAnalyse" | |||
| :supportPreview="false" | |||
| @click="onImagePreviewClicked(scope.row.imgAnalyse)" | |||
| ></AsyncImage> | |||
| <span v-else>--</span> | |||
| </template> | |||
| </el-table-column> | |||
| <!-- AI异常 filter==0 --> | |||
| <el-table-column label="初筛图片" align="center" v-if="filter == 0"> | |||
| <template slot-scope="scope"> | |||
| <AsyncImage | |||
| v-if="scope.row.img" | |||
| class="table-img" | |||
| :src="scope.row.img" | |||
| :supportPreview="false" | |||
| @click="onImagePreviewClicked(scope.row.img)" | |||
| ></AsyncImage> | |||
| <span v-else>--</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="视觉模型图片" align="center" v-if="filter == 0"> | |||
| <template slot-scope="scope"> | |||
| <AsyncImage | |||
| v-if="scope.row.imgAnalyse" | |||
| class="table-img" | |||
| :src="scope.row.imgAnalyse" | |||
| :supportPreview="false" | |||
| @click="onImagePreviewClicked(scope.row.imgAnalyse)" | |||
| ></AsyncImage> | |||
| <span v-else>--</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column label="时间" align="center" prop="patrolTime" /> | |||
| <el-table-column label="描述" align="center" prop="desc" /> | |||
| </el-table> | |||
| <CardPagination | |||
| v-show="total > 0" | |||
| :total="total" | |||
| :page.sync="queryParams.pageNum" | |||
| :limit.sync="queryParams.pageSize" | |||
| @pagination="getList" | |||
| /> | |||
| </div> | |||
| <el-dialog | |||
| title="图片预览" | |||
| :visible.sync="showPreviewImages" | |||
| :close-on-click-modal="false" | |||
| :close-on-press-escape="false" | |||
| :destroy-on-close="true" | |||
| custom-class="card-dialog card-page" | |||
| width="70%" | |||
| append-to-body | |||
| > | |||
| <ImagePreview | |||
| v-if="showPreviewImages" | |||
| :imageUrls="showImgesUrls" | |||
| :selectImgPosition="previewImagePos" | |||
| :showTemperatureTool="false" | |||
| /> | |||
| </el-dialog> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import CardPagination from "../components/CardPagination/index.vue"; | |||
| import { getAbnormalPatrolList } from "@/api/home"; | |||
| import { getAllAlgorithmList } from "@/api/inspectionDataManage/alarmConfirm.js"; | |||
| import debounce from "lodash.debounce"; | |||
| import ImagePreview from "@/views/cards/components/ImagePreview/index.vue"; | |||
| // 巡视归档对比对话框 | |||
| export default { | |||
| name: "PatrolAbnormalStatistics", | |||
| components: { CardPagination, ImagePreview }, | |||
| props: { | |||
| info: { | |||
| type: Object, | |||
| default: null, | |||
| }, | |||
| }, | |||
| dicts: [ | |||
| "patrol_result_value_type", | |||
| "patrol_result_recognition_type", | |||
| "patrol_result_valid_type", | |||
| ], | |||
| data() { | |||
| return { | |||
| taskInfo: null, | |||
| // table 高度 | |||
| tableHeight: null, | |||
| // 遮罩层 | |||
| loading: true, | |||
| // 总条数 | |||
| total: 0, | |||
| // 数据 | |||
| dataList: [], | |||
| algorithmList: [], | |||
| //时间范围 | |||
| dateRange: [], | |||
| dateRangePickerOptions: { | |||
| disabledDate(selectDate) { | |||
| return new Date().getTime() < selectDate.getTime(); | |||
| }, | |||
| }, | |||
| // 查询参数 | |||
| queryParams: { | |||
| pageNum: 1, | |||
| pageSize: 20, | |||
| deviceName: null, | |||
| }, | |||
| showPreviewImages: false, | |||
| filter: "", //判断是初筛还是ai类型 | |||
| }; | |||
| }, | |||
| computed: {}, | |||
| created() { | |||
| if (!this.info) { | |||
| this.taskInfo = JSON.parse(sessionStorage.getItem("selectInfo")); | |||
| this.filter = this.taskInfo.defectType; | |||
| } else { | |||
| this.taskInfo = this.info; | |||
| this.filter = this.taskInfo.defectType; | |||
| } | |||
| // this.getAlgorithmList(); | |||
| // 防抖 | |||
| this.setTableHeight = debounce(this.setTableHeight, 300); | |||
| window.addEventListener("resize", this.setTableHeight); | |||
| this.getList(); | |||
| }, | |||
| mounted() { | |||
| this.setTableHeight(); | |||
| }, | |||
| destroyed() { | |||
| this.setTableHeight.cancel(); | |||
| window.removeEventListener("resize", this.setTableHeight); | |||
| }, | |||
| methods: { | |||
| getAlgorithmList() { | |||
| getAllAlgorithmList().then((res) => { | |||
| this.algorithmList = res.rows; | |||
| }); | |||
| }, | |||
| setTableHeight() { | |||
| this.tableHeight = this.$refs.tableCardRef.clientHeight - 40; | |||
| }, | |||
| /** 搜索按钮操作 */ | |||
| handleQuery() { | |||
| this.queryParams.pageNum = 1; | |||
| this.getList(); | |||
| }, | |||
| /** 重置按钮操作 */ | |||
| resetQuery() { | |||
| this.dateRange = []; | |||
| this.resetForm("queryForm"); | |||
| this.handleQuery(); | |||
| }, | |||
| getList() { | |||
| this.loading = true; | |||
| const params = { ...this.queryParams }; | |||
| params.filter = this.taskInfo.defectType; | |||
| params.taskPatrolledId = this.taskInfo.taskPatrolledId; | |||
| params.posType = this.taskInfo.posType; | |||
| // 拍摄时间筛选范围 | |||
| if (this.dateRange && this.dateRange.length === 2) { | |||
| params.beginTime = this.dateRange[0]; | |||
| params.endTime = this.dateRange[1]; | |||
| } | |||
| getAbnormalPatrolList(params).then((response) => { | |||
| this.dataList = response.rows; | |||
| this.total = response.total; | |||
| this.loading = false; | |||
| }); | |||
| }, | |||
| // 获取缺陷类型字符串 | |||
| getDefectTypeListStr(options, value) { | |||
| if (!value) return ""; | |||
| const valueList = value.split(","); | |||
| const infoList = []; | |||
| for (let index = 0; index < valueList.length; index++) { | |||
| const element = valueList[index]; | |||
| const tmp = options.find((item) => item.algSubtypeCode == element); | |||
| if (tmp) { | |||
| infoList.push(tmp.algSubtypeName); | |||
| } | |||
| } | |||
| return infoList.join(","); | |||
| }, | |||
| // 图片预览 | |||
| onImagePreviewClicked(imgUrl) { | |||
| if (!imgUrl) { | |||
| return; | |||
| } | |||
| this.previewImagePos = 0; | |||
| this.showImgesUrls = [imgUrl]; | |||
| this.showPreviewImages = true; | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .page-content { | |||
| width: 100%; | |||
| height: 100%; | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| .table-card { | |||
| flex: 1; | |||
| height: 0; | |||
| ::v-deep .el-card__body { | |||
| height: 100%; | |||
| } | |||
| } | |||
| </style> | |||
| @ -0,0 +1,222 @@ | |||
| <template> | |||
| <!-- <CardBox | |||
| class="card-page page-content" | |||
| title="缺陷详情" | |||
| :titleImgURL="require('../assets/card_full_bg.png')" | |||
| > --> | |||
| <CardBox | |||
| class="card-page page-content"> | |||
| <div class="page-container"> | |||
| <div class="title-wrap">{{ taskInfo.taskName }}</div> | |||
| <div class="decfect-box"> | |||
| <el-row> | |||
| <el-col | |||
| style="padding: 8px" | |||
| :span="4" | |||
| v-for="(item, index) in dataList" | |||
| :key="index" | |||
| > | |||
| <div | |||
| class="decfect-item" | |||
| :class="{ | |||
| 'decfect-item-alarm': item.resStatus == 1, | |||
| 'decfect-item-following': item.resStatus == 3, | |||
| 'decfect-item-cancel': item.resStatus == 2, | |||
| }" | |||
| @click="onItemClicked(item, index)" | |||
| > | |||
| <div class="img-title">{{ item.pointName }}</div> | |||
| <AsyncImage | |||
| class="img-box" | |||
| :src="item.imgAnalyse" | |||
| fit="contain" | |||
| :supportPreview="false" | |||
| ></AsyncImage> | |||
| <!-- 撤销的不显示 --> | |||
| <!-- <div v-if="item.resStatus != 2 && item.algType == 'switch'" class="desc-info"> | |||
| {{ item.resValue }} | |||
| </div> --> | |||
| <div v-if="item.resStatus != 2" class="desc-info"> | |||
| {{ item.desc }} | |||
| </div> | |||
| </div> | |||
| </el-col> | |||
| </el-row> | |||
| </div> | |||
| <CardPagination | |||
| v-show="total > 0" | |||
| :total="total" | |||
| :page.sync="queryParams.pageNum" | |||
| :limit.sync="queryParams.pageSize" | |||
| @pagination="getList" | |||
| /> | |||
| </div> | |||
| <el-dialog | |||
| title="缺陷详情" | |||
| :visible.sync="showInfo" | |||
| :close-on-click-modal="false" | |||
| :close-on-press-escape="false" | |||
| :destroy-on-close="true" | |||
| custom-class="card-dialog card-page card-dialog-height-auto defect-detail-image-dialog" | |||
| width="70%" | |||
| append-to-body | |||
| @close="getList" | |||
| > | |||
| <div class="defect-detail-image-dialog-wrap"> | |||
| <InspectionTaskDefectDetailImage | |||
| :dataList="dataList" | |||
| :selectIndex="selectIndex" | |||
| :showHandleBtn="taskInfo != null && taskInfo.fileStatus != 1" | |||
| /> | |||
| </div> | |||
| </el-dialog> | |||
| </CardBox> | |||
| </template> | |||
| <script> | |||
| import CardBox from "../components/cardBox.vue"; | |||
| import CardPagination from "../components/CardPagination/index.vue"; | |||
| import debounce from "lodash.debounce"; | |||
| import InspectionTaskDefectDetailImage from "./inspectionTaskDefectDetailImage.vue"; | |||
| import { analysisDefectList, analysisDefectListShaoxing} from "@/api/videosMonitor/index"; | |||
| export default { | |||
| name: "InspectionTaskDefectDetail", | |||
| components: { CardBox, CardPagination, InspectionTaskDefectDetailImage }, | |||
| props: { | |||
| info: { | |||
| type: Object, | |||
| default: null, | |||
| }, | |||
| }, | |||
| data() { | |||
| return { | |||
| taskInfo: null, | |||
| // 查询参数 | |||
| queryParams: { | |||
| pageNum: 1, | |||
| pageSize: 20, | |||
| }, | |||
| // 总条数 | |||
| total: 0, | |||
| // 数据 | |||
| dataList: [], | |||
| showInfo: false, | |||
| selectIndex: 0, | |||
| }; | |||
| }, | |||
| computed: {}, | |||
| created() { | |||
| if (!this.info) { | |||
| this.taskInfo = JSON.parse(sessionStorage.getItem("selectInfo")); | |||
| } else { | |||
| this.taskInfo = this.info; | |||
| } | |||
| this.queryParams.taskPatrolledId = this.taskInfo.taskPatrolledId; | |||
| this.queryParams.filter = this.taskInfo.defectType; | |||
| this.getList(); | |||
| }, | |||
| beforeDestroy() {}, | |||
| methods: { | |||
| // 获取列表 | |||
| getList() { | |||
| analysisDefectListShaoxing(this.queryParams).then((res) => { | |||
| this.dataList = res.rows; | |||
| this.total = res.total; | |||
| }); | |||
| }, | |||
| onItemClicked(row, index) { | |||
| this.selectIndex = index; | |||
| this.showInfo = true; | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .page-content { | |||
| width: 100%; | |||
| height: 100%; | |||
| } | |||
| .page-container { | |||
| height: 100%; | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| ::v-deep .card-box-title-box { | |||
| display: none !important; | |||
| } | |||
| .title-wrap { | |||
| text-align: center; | |||
| font-size: 18px; | |||
| color: #40e5f0; | |||
| padding: 0 0 10px; | |||
| } | |||
| .decfect-box { | |||
| height: 0; | |||
| flex: 1; | |||
| overflow-y: auto; | |||
| .decfect-item { | |||
| height: 213px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| position: relative; | |||
| background-color: #091318; | |||
| .img-title { | |||
| color: #b0bdd4; | |||
| padding: 8px 0 8px 8px; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .img-title:before { | |||
| content: ""; | |||
| width: 16px; /* 图标的宽度 */ | |||
| height: 16px; /* 图标的高度 */ | |||
| margin-right: 10px; | |||
| background-image: url("../assets/icon-title-1.png"); | |||
| background-size: cover; | |||
| } | |||
| .img-box { | |||
| width: 100%; | |||
| height: 0; | |||
| flex: 1; | |||
| } | |||
| .desc-info { | |||
| background-color: #ff4a41; | |||
| color: #ffffff; | |||
| padding: 5px; | |||
| border-top-left-radius: 5px; | |||
| position: absolute; | |||
| right: 0; | |||
| bottom: 0; | |||
| font-size: 11px; | |||
| letter-spacing: 1px; | |||
| } | |||
| } | |||
| //告警 | |||
| .decfect-item-alarm { | |||
| border: 2px solid #ff4a41; | |||
| } | |||
| //跟踪 | |||
| .decfect-item-following { | |||
| border: 2px solid #ffa800; | |||
| } | |||
| //撤销 | |||
| .decfect-item-cancel { | |||
| border: 2px solid #40e5f0; | |||
| } | |||
| } | |||
| .defect-detail-image-dialog-wrap { | |||
| height: calc(100vh - 180px); | |||
| min-height: 560px; | |||
| overflow: hidden; | |||
| } | |||
| ::v-deep .defect-detail-image-dialog .el-dialog__body { | |||
| height: auto; | |||
| padding: 0; | |||
| overflow: hidden; | |||
| } | |||
| </style> | |||
| @ -0,0 +1,701 @@ | |||
| <template> | |||
| <div class="page-box"> | |||
| <div | |||
| v-if="selectInfo" | |||
| class="decfect-box" | |||
| :class="{ | |||
| 'decfect-box-alarm': selectInfo.pointStatus == 5, | |||
| 'decfect-box-following': selectInfo.pointStatus == 4, | |||
| 'decfect-box-cancel': selectInfo.pointStatus == 3, | |||
| }" | |||
| > | |||
| <!-- 3-已撤销(忽略) 4-跟踪(告警) 5-确认(g告警 --> | |||
| <div class="img-title">{{ selectInfo.pointName }}</div> | |||
| <div class="img-box"> | |||
| <div class="image-container"> | |||
| <div class="zoom-container" ref="image1Container"> | |||
| <div | |||
| class="zoom-wrapper" | |||
| ref="image1Wrapper" | |||
| :style="{ | |||
| transform: `scale(${image1Scale})`, | |||
| transformOrigin: '0 0', | |||
| position: 'absolute', | |||
| left: `${image1Position.x}px`, | |||
| top: `${image1Position.y}px`, | |||
| width: '100%', | |||
| height: '100%', | |||
| }" | |||
| @mousedown="startDrag($event, 'image1')" | |||
| @touchstart="startTouchDrag($event, 'image1')" | |||
| > | |||
| <AsyncImage | |||
| class="img-wrap zoomable-image" | |||
| :src="selectInfo.imageNormalUrlPath" | |||
| fit="contain" | |||
| :supportPreview="false" | |||
| ></AsyncImage> | |||
| </div> | |||
| </div> | |||
| <div class="zoom-controls"> | |||
| <el-button size="mini" circle @click="resetImage1" title="重置"> | |||
| ↺ | |||
| </el-button> | |||
| </div> | |||
| </div> | |||
| <div class="image-container"> | |||
| <div class="zoom-container" ref="image2Container"> | |||
| <div | |||
| class="zoom-wrapper" | |||
| ref="image2Wrapper" | |||
| :style="{ | |||
| transform: `scale(${image2Scale})`, | |||
| transformOrigin: '0 0', | |||
| position: 'absolute', | |||
| left: `${image2Position.x}px`, | |||
| top: `${image2Position.y}px`, | |||
| width: '100%', | |||
| height: '100%', | |||
| }" | |||
| @mousedown="startDrag($event, 'image2')" | |||
| @touchstart="startTouchDrag($event, 'image2')" | |||
| > | |||
| <AsyncImage | |||
| class="img-wrap zoomable-image" | |||
| :src="selectInfo.imgAnalyse" | |||
| fit="contain" | |||
| :supportPreview="false" | |||
| ></AsyncImage> | |||
| </div> | |||
| </div> | |||
| <div class="zoom-controls"> | |||
| <el-button size="mini" circle @click="resetImage2" title="重置"> | |||
| ↺ | |||
| </el-button> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <!-- 撤销的不显示 --> | |||
| <div v-if="selectInfo.pointStatus != 3" class="desc-info"> | |||
| {{ selectInfo.desc }} | |||
| </div> | |||
| </div> | |||
| <div class="tool-box"> | |||
| <!-- 原有的其他代码保持不变 --> | |||
| <div class="alg-box" v-if="selectInfo.pointStatus == 0 && showHandleBtn"> | |||
| <div class="alg-wrap"> | |||
| <el-radio-group v-model="selectAlgName" size="medium"> | |||
| <el-radio-button | |||
| v-for="(item, index) in algList" | |||
| :key="index" | |||
| :label="item.algSubtypeName" | |||
| ></el-radio-button> | |||
| </el-radio-group> | |||
| </div> | |||
| <div class="suggest-wrap"> | |||
| <span>建议:</span> | |||
| <el-input | |||
| style="flex: 1" | |||
| v-model="suggestion" | |||
| placeholder="请输入建议" | |||
| ></el-input> | |||
| </div> | |||
| </div> | |||
| <div class="tool-wrap"> | |||
| <div class="pre-next-box"> | |||
| <div class="pre-next-btn"> | |||
| <el-image | |||
| v-if="showPreBtn" | |||
| class="img-wrap" | |||
| :src="require('../assets/btn-pre.png')" | |||
| fit="contain" | |||
| @click="preBtnClicked" | |||
| ></el-image> | |||
| </div> | |||
| <div class="pre-next-btn"> | |||
| <el-image | |||
| v-if="showNextBtn" | |||
| class="img-wrap" | |||
| :src="require('../assets/btn-next.png')" | |||
| fit="contain" | |||
| @click="nextBtnClicked" | |||
| ></el-image> | |||
| </div> | |||
| </div> | |||
| <div | |||
| class="handle-box" | |||
| v-if="(selectInfo.pointStatus == 0 || selectInfo.pointStatus == 6) && showHandleBtn" | |||
| > | |||
| <el-button | |||
| class="handle-btn handle-btn-second" | |||
| type="primary" | |||
| @click="cancelAlarm" | |||
| >忽略</el-button | |||
| > | |||
| <el-button class="handle-btn" type="primary" @click="sureAlarm" | |||
| >确认</el-button | |||
| > | |||
| <el-button | |||
| class="handle-btn handle-btn-second" | |||
| type="primary" | |||
| @click="following" | |||
| >跟踪</el-button | |||
| > | |||
| </div> | |||
| <!-- <div v-else class="handle-box"> | |||
| 备注:{{ selectInfo.suggestion ? selectInfo.suggestion : "--" }} | |||
| </div> --> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { | |||
| handleBatchAlarmConfirm, | |||
| getAlgTypeList, | |||
| } from "@/api/inspectionDataManage/alarmConfirm"; | |||
| export default { | |||
| name: "InspectionTaskDefectDetailImage", | |||
| props: { | |||
| dataList: { | |||
| type: Array, | |||
| default: [], | |||
| }, | |||
| selectIndex: { | |||
| type: Number, | |||
| default: 0, | |||
| }, | |||
| showHandleBtn: { | |||
| type: Boolean, | |||
| default: true, | |||
| }, | |||
| }, | |||
| data() { | |||
| return { | |||
| pos: this.selectIndex, | |||
| selectAlgName: "", | |||
| suggestion: "", | |||
| algList: [], | |||
| image1Scale: 1, | |||
| image1Position: { x: 0, y: 0 }, | |||
| image2Scale: 1, | |||
| image2Position: { x: 0, y: 0 }, | |||
| isDragging: false, | |||
| currentDraggingImage: null, | |||
| dragStartPos: { x: 0, y: 0 }, | |||
| dragStartImagePos: { x: 0, y: 0 }, | |||
| }; | |||
| }, | |||
| watch: { | |||
| selectIndex() { | |||
| this.pos = this.selectIndex; | |||
| this.resetImageScalesAndPositions(); | |||
| }, | |||
| }, | |||
| computed: { | |||
| selectInfo() { | |||
| if (this.pos < this.dataList.length) { | |||
| return this.dataList[this.pos]; | |||
| } | |||
| return null; | |||
| }, | |||
| showPreBtn() { | |||
| return this.pos > 0; | |||
| }, | |||
| showNextBtn() { | |||
| return this.pos < this.dataList.length - 1; | |||
| }, | |||
| }, | |||
| created() { | |||
| getAlgTypeList().then((res) => { | |||
| res.rows.push({ | |||
| algSubtypeName: "其他", | |||
| }); | |||
| this.algList = res.rows.filter((item, index, self) => { | |||
| return ( | |||
| self.findIndex((t) => t.algSubtypeName === item.algSubtypeName) === | |||
| index | |||
| ); | |||
| }); | |||
| }); | |||
| }, | |||
| mounted() { | |||
| // 在mounted中绑定事件,确保DOM已加载 | |||
| this.bindGlobalEvents(); | |||
| // 为容器添加滚轮事件 | |||
| this.$nextTick(() => { | |||
| if (this.$refs.image1Container) { | |||
| this.$refs.image1Container.addEventListener( | |||
| "wheel", | |||
| this.handleImage1Wheel, | |||
| { passive: false } | |||
| ); | |||
| } | |||
| if (this.$refs.image2Container) { | |||
| this.$refs.image2Container.addEventListener( | |||
| "wheel", | |||
| this.handleImage2Wheel, | |||
| { passive: false } | |||
| ); | |||
| } | |||
| }); | |||
| }, | |||
| beforeDestroy() { | |||
| this.unbindGlobalEvents(); | |||
| // 移除滚轮事件 | |||
| if (this.$refs.image1Container) { | |||
| this.$refs.image1Container.removeEventListener( | |||
| "wheel", | |||
| this.handleImage1Wheel | |||
| ); | |||
| } | |||
| if (this.$refs.image2Container) { | |||
| this.$refs.image2Container.removeEventListener( | |||
| "wheel", | |||
| this.handleImage2Wheel | |||
| ); | |||
| } | |||
| }, | |||
| methods: { | |||
| preBtnClicked() { | |||
| this.pos = this.pos - 1 >= 0 ? this.pos - 1 : 0; | |||
| // 切换图片后重置缩放和位置 | |||
| this.resetImageScalesAndPositions(); | |||
| }, | |||
| nextBtnClicked() { | |||
| this.pos = | |||
| this.pos + 1 < this.dataList.length - 1 | |||
| ? this.pos + 1 | |||
| : this.dataList.length - 1; | |||
| // 切换图片后重置缩放和位置 | |||
| this.resetImageScalesAndPositions(); | |||
| }, | |||
| // 确认 | |||
| sureAlarm() { | |||
| this.handleAlarm(5); | |||
| }, | |||
| // 跟踪 | |||
| following() { | |||
| this.handleAlarm(4); | |||
| }, | |||
| // 忽略 | |||
| cancelAlarm() { | |||
| this.handleAlarm(3); | |||
| }, | |||
| handleAlarm(status) { | |||
| const suggestion = this.selectAlgName + "," + this.suggestion; | |||
| const params = { | |||
| lineIds: this.selectInfo.lineId, | |||
| status, | |||
| suggestion, | |||
| }; | |||
| handleBatchAlarmConfirm(params).then((res) => { | |||
| this.$modal.msgSuccess("操作成功"); | |||
| this.$set(this.dataList, this.pos, { | |||
| ...this.dataList[this.pos], | |||
| pointStatus: status, | |||
| suggestion, | |||
| }); | |||
| this.nextBtnClicked(); | |||
| this.selectAlgName = ""; | |||
| this.suggestion = ""; | |||
| }); | |||
| }, | |||
| // 处理第一张图片的滚轮事件 | |||
| handleImage1Wheel(event) { | |||
| event.preventDefault(); | |||
| this.handleImageWheel(event, "image1Scale", "image1Position"); | |||
| }, | |||
| // 处理第二张图片的滚轮事件 | |||
| handleImage2Wheel(event) { | |||
| event.preventDefault(); | |||
| this.handleImageWheel(event, "image2Scale", "image2Position"); | |||
| }, | |||
| // 通用的图片滚轮处理函数 | |||
| handleImageWheel(event, scaleProperty, positionProperty) { | |||
| const delta = event.deltaY || event.detail || event.wheelDelta; | |||
| const direction = delta > 0 ? -1 : 1; | |||
| const scaleStep = 0.1; | |||
| const oldScale = this[scaleProperty]; | |||
| let newScale = oldScale + direction * scaleStep; | |||
| newScale = Math.max(0.1, Math.min(5, newScale)); | |||
| // 计算鼠标在容器中的位置 | |||
| const container = event.currentTarget; | |||
| const containerRect = container.getBoundingClientRect(); | |||
| const mouseX = event.clientX - containerRect.left; | |||
| const mouseY = event.clientY - containerRect.top; | |||
| // 当前图片位置 | |||
| const currentPos = this[positionProperty]; | |||
| // 计算缩放后的新位置 | |||
| // 缩放比例变化 | |||
| const scaleChange = newScale / oldScale; | |||
| // 调整位置使鼠标点保持在图片上的同一位置 | |||
| this[positionProperty] = { | |||
| x: currentPos.x - (mouseX - currentPos.x) * (scaleChange - 1), | |||
| y: currentPos.y - (mouseY - currentPos.y) * (scaleChange - 1), | |||
| }; | |||
| this[scaleProperty] = newScale; | |||
| }, | |||
| // 开始鼠标拖拽 | |||
| startDrag(event, imageName) { | |||
| if (event.button !== 0) return; | |||
| this.isDragging = true; | |||
| this.currentDraggingImage = imageName; | |||
| this.dragStartPos = { | |||
| x: event.clientX, | |||
| y: event.clientY, | |||
| }; | |||
| this.dragStartImagePos = { | |||
| x: this[`${imageName}Position`].x, | |||
| y: this[`${imageName}Position`].y, | |||
| }; | |||
| // 添加拖拽样式 | |||
| const wrapper = this.$refs[`${imageName}Wrapper`]; | |||
| if (wrapper) { | |||
| wrapper.classList.add("dragging"); | |||
| } | |||
| // 防止文本选择 | |||
| document.body.style.userSelect = "none"; | |||
| document.body.style.webkitUserSelect = "none"; | |||
| event.preventDefault(); | |||
| event.stopPropagation(); | |||
| }, | |||
| // 开始触摸拖拽 | |||
| startTouchDrag(event, imageName) { | |||
| if (event.touches.length !== 1) return; | |||
| this.isDragging = true; | |||
| this.currentDraggingImage = imageName; | |||
| this.dragStartPos = { | |||
| x: event.touches[0].clientX, | |||
| y: event.touches[0].clientY, | |||
| }; | |||
| this.dragStartImagePos = { | |||
| x: this[`${imageName}Position`].x, | |||
| y: this[`${imageName}Position`].y, | |||
| }; | |||
| const wrapper = this.$refs[`${imageName}Wrapper`]; | |||
| if (wrapper) { | |||
| wrapper.classList.add("dragging"); | |||
| } | |||
| event.preventDefault(); | |||
| event.stopPropagation(); | |||
| }, | |||
| // 处理拖拽移动 | |||
| handleDragMove(event) { | |||
| if (!this.isDragging || !this.currentDraggingImage) return; | |||
| let clientX, clientY; | |||
| if (event.type === "mousemove") { | |||
| clientX = event.clientX; | |||
| clientY = event.clientY; | |||
| } else if (event.type === "touchmove" && event.touches.length === 1) { | |||
| clientX = event.touches[0].clientX; | |||
| clientY = event.touches[0].clientY; | |||
| } else { | |||
| return; | |||
| } | |||
| const deltaX = clientX - this.dragStartPos.x; | |||
| const deltaY = clientY - this.dragStartPos.y; | |||
| // 更新图片位置 | |||
| this[`${this.currentDraggingImage}Position`] = { | |||
| x: this.dragStartImagePos.x + deltaX, | |||
| y: this.dragStartImagePos.y + deltaY, | |||
| }; | |||
| event.preventDefault(); | |||
| event.stopPropagation(); | |||
| }, | |||
| // 结束拖拽 | |||
| handleDragEnd(event) { | |||
| if (!this.isDragging) return; | |||
| // 移除拖拽样式 | |||
| if (this.currentDraggingImage) { | |||
| const wrapper = this.$refs[`${this.currentDraggingImage}Wrapper`]; | |||
| if (wrapper) { | |||
| wrapper.classList.remove("dragging"); | |||
| } | |||
| } | |||
| // 恢复文本选择 | |||
| document.body.style.userSelect = ""; | |||
| document.body.style.webkitUserSelect = ""; | |||
| this.isDragging = false; | |||
| this.currentDraggingImage = null; | |||
| }, | |||
| // 重置第一张图片的缩放和位置 | |||
| resetImage1() { | |||
| this.image1Scale = 1; | |||
| this.image1Position = { x: 0, y: 0 }; | |||
| }, | |||
| // 重置第二张图片的缩放和位置 | |||
| resetImage2() { | |||
| this.image2Scale = 1; | |||
| this.image2Position = { x: 0, y: 0 }; | |||
| }, | |||
| // 重置所有图片的缩放和位置 | |||
| resetImageScalesAndPositions() { | |||
| this.resetImage1(); | |||
| this.resetImage2(); | |||
| }, | |||
| // 绑定全局事件 | |||
| bindGlobalEvents() { | |||
| document.addEventListener("mousemove", this.handleDragMove); | |||
| document.addEventListener("mouseup", this.handleDragEnd); | |||
| document.addEventListener("touchmove", this.handleDragMove, { | |||
| passive: false, | |||
| }); | |||
| document.addEventListener("touchend", this.handleDragEnd); | |||
| }, | |||
| // 移除全局事件 | |||
| unbindGlobalEvents() { | |||
| document.removeEventListener("mousemove", this.handleDragMove); | |||
| document.removeEventListener("mouseup", this.handleDragEnd); | |||
| document.removeEventListener("touchmove", this.handleDragMove); | |||
| document.removeEventListener("touchend", this.handleDragEnd); | |||
| }, | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .page-box { | |||
| height: 100%; | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| .decfect-box { | |||
| flex: 1; | |||
| height: 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| position: relative; | |||
| background-color: #091318; | |||
| .img-title { | |||
| color: #ffffff; | |||
| padding: 8px 0 8px 8px; | |||
| display: flex; | |||
| font-weight: 500; | |||
| font-size: 13px; | |||
| align-items: center; | |||
| } | |||
| .img-title:before { | |||
| content: ""; | |||
| width: 16px; /* 图标的宽度 */ | |||
| height: 16px; /* 图标的高度 */ | |||
| margin-right: 10px; | |||
| background-image: url("../assets/icon-title-1.png"); | |||
| background-size: cover; | |||
| } | |||
| .img-box { | |||
| display: flex; | |||
| gap: 10px; | |||
| flex: 1; | |||
| height: 0; | |||
| .image-container { | |||
| flex: 1; | |||
| width: 0; | |||
| height: 100%; | |||
| position: relative; | |||
| overflow: hidden; | |||
| .zoom-container { | |||
| width: 100%; | |||
| height: 100%; | |||
| position: relative; | |||
| overflow: hidden; | |||
| cursor: grab; | |||
| &:active { | |||
| cursor: grabbing; | |||
| } | |||
| } | |||
| .zoom-wrapper { | |||
| width: 100%; | |||
| height: 100%; | |||
| position: absolute; | |||
| cursor: grab; | |||
| &.dragging { | |||
| cursor: grabbing; | |||
| } | |||
| .img-wrap { | |||
| width: 100%; | |||
| height: 100%; | |||
| user-select: none; | |||
| -webkit-user-drag: none; | |||
| pointer-events: none; /* 防止图片干扰拖拽事件 */ | |||
| } | |||
| } | |||
| .zoom-controls { | |||
| position: absolute; | |||
| top: 10px; | |||
| right: 10px; | |||
| z-index: 10; | |||
| .el-button { | |||
| background: rgba(0, 0, 0, 0.6); | |||
| color: white; | |||
| border: none; | |||
| font-size: 16px; | |||
| &:hover { | |||
| background: rgba(0, 0, 0, 0.8); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .desc-info { | |||
| background-color: #ff4a41; | |||
| color: #ffffff; | |||
| padding: 5px; | |||
| border-top-left-radius: 5px; | |||
| position: absolute; | |||
| right: 0; | |||
| bottom: 0; | |||
| font-size: 11px; | |||
| letter-spacing: 1px; | |||
| z-index: 10; | |||
| pointer-events: none; | |||
| } | |||
| } | |||
| /*告警*/ | |||
| .decfect-box-alarm { | |||
| border: 2px solid #ff4a41; | |||
| } | |||
| /*跟踪*/ | |||
| .decfect-box-following { | |||
| border: 2px solid #ffa800; | |||
| } | |||
| /*撤销*/ | |||
| .decfect-box-cancel { | |||
| border: 2px solid #40e5f0; | |||
| } | |||
| .tool-box { | |||
| height: 183px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| } | |||
| .alg-box { | |||
| display: flex; | |||
| align-items: center; | |||
| align-items: center; | |||
| height: 80px; | |||
| gap: 15px; | |||
| .alg-wrap { | |||
| flex: 1; | |||
| width: 0; | |||
| overflow-x: auto; | |||
| white-space: nowrap; | |||
| } | |||
| .suggest-wrap { | |||
| width: 280px; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| } | |||
| .tool-wrap { | |||
| height: 0; | |||
| flex: 1; | |||
| display: flex; | |||
| align-items: center; | |||
| .pre-next-box { | |||
| flex: 1; | |||
| display: flex; | |||
| gap: 83px; | |||
| .pre-next-btn { | |||
| width: 122px; | |||
| height: 90px; | |||
| .img-wrap { | |||
| width: 100%; | |||
| height: 100%; | |||
| user-select: none; | |||
| } | |||
| } | |||
| } | |||
| .handle-box { | |||
| display: flex; | |||
| justify-content: flex-end; | |||
| gap: 10px; | |||
| .handle-btn { | |||
| width: 143px; | |||
| height: 55px; | |||
| font-size: 27px; | |||
| } | |||
| .handle-btn-second { | |||
| background-color: #575a68; | |||
| } | |||
| } | |||
| } | |||
| </style> | |||
| @ -0,0 +1,108 @@ | |||
| <template> | |||
| <div :class="['status_container', type]"> | |||
| <div class="badge"></div> | |||
| <div class="text"> | |||
| <slot> </slot> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| // "1": "success",//已执行 | |||
| // "2": "",//正在执行 默认是正在执行 | |||
| // "3": "warning",//暂停 | |||
| // "4": "info",//终止 | |||
| // "5": "notrun",//未执行 | |||
| // "6": "danger"//已超期 | |||
| props: { | |||
| type: [String], | |||
| }, | |||
| }; | |||
| </script> | |||
| <style lang="scss" scoped> | |||
| .status_container { | |||
| justify-content: center; | |||
| justify-items: center; | |||
| align-content: center; | |||
| align-items: center; | |||
| display: flex; | |||
| .badge { | |||
| margin-top: 1px; | |||
| margin-right: 5px; | |||
| width: 2px; | |||
| height: 2px; | |||
| background: #007df1; | |||
| border: 3px solid rgba(2, 139, 250, 0.2); | |||
| border-radius: 4px; | |||
| } | |||
| .text { | |||
| font-family: "PingFang SC"; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| font-size: 14px; | |||
| color: #007df1; | |||
| } | |||
| &.success { | |||
| .badge { | |||
| /* 绿色 */ | |||
| background: #3ca600; | |||
| border-color: rgba(60, 166, 0, 0.2); | |||
| } | |||
| .text { | |||
| color: #3ca600; | |||
| } | |||
| } | |||
| &.warning { | |||
| .badge { | |||
| /* 警告色 */ | |||
| background: #eba714; | |||
| border-color: rgba(235, 202, 53, 0.2); | |||
| } | |||
| .text { | |||
| color: #eba714; | |||
| } | |||
| } | |||
| &.info { | |||
| .badge { | |||
| /* 终止色 */ | |||
| background: #999; | |||
| border-color: rgba(0, 0, 0, 0.2); | |||
| } | |||
| .text { | |||
| color: #999; | |||
| } | |||
| } | |||
| &.notrun { | |||
| .badge { | |||
| /* 正在执行 */ | |||
| background: #929292; | |||
| border-color: rgba(138, 138, 138, 0.2); | |||
| } | |||
| .text { | |||
| color: #929292; | |||
| } | |||
| } | |||
| &.danger { | |||
| .badge { | |||
| /* 已超期 */ | |||
| background: #ff5e5e; | |||
| border-color: rgba(255, 91, 91, 0.2); | |||
| } | |||
| .text { | |||
| color: #ff5e5e; | |||
| } | |||
| } | |||
| } | |||
| </style> | |||