Browse Source

增加巡视任务、详情、缺陷详情页面

master
douyage 2 days ago
parent
commit
87cd6c664a
45 changed files with 8653 additions and 45 deletions
  1. +2
    -0
      package.json
  2. +88
    -0
      src/api/analysis/index.js
  3. +116
    -0
      src/api/home/index.js
  4. +4
    -0
      src/api/inspectionDataManage/alarmConfirm.js
  5. +9
    -0
      src/api/inspectionDataManage/inspectionArchive.js
  6. +9
    -0
      src/api/modelDialog/modelDialog.js
  7. +75
    -0
      src/api/screen/screen.js
  8. +322
    -0
      src/api/videosMonitor/index.js
  9. +673
    -0
      src/components/AsyncImageFrame/index.vue
  10. +133
    -42
      src/components/CardPagination/index.vue
  11. +3
    -0
      src/layout/components/Sidebar/Item.vue
  12. +8
    -2
      src/main.js
  13. +15
    -1
      src/router/index.js
  14. BIN
      src/views/cards/assets/btn-next.png
  15. BIN
      src/views/cards/assets/btn-pre.png
  16. BIN
      src/views/cards/assets/card_btn_border.png
  17. BIN
      src/views/cards/assets/icon-document.png
  18. BIN
      src/views/cards/assets/icon-title-1.png
  19. BIN
      src/views/cards/assets/icon-title-2.png
  20. BIN
      src/views/cards/assets/image-btn-left-disable.png
  21. BIN
      src/views/cards/assets/image-btn-left-hover.png
  22. BIN
      src/views/cards/assets/image-btn-left-normal.png
  23. BIN
      src/views/cards/assets/image-btn-left-press.png
  24. BIN
      src/views/cards/assets/image-btn-right-disable.png
  25. BIN
      src/views/cards/assets/image-btn-right-hover.png
  26. BIN
      src/views/cards/assets/image-btn-right-normal.png
  27. BIN
      src/views/cards/assets/image-btn-right-press.png
  28. BIN
      src/views/cards/assets/model-img.png
  29. BIN
      src/views/cards/assets/right-title.png
  30. +658
    -0
      src/views/cards/assets/styles/index.scss
  31. BIN
      src/views/cards/assets/taskDetail.png
  32. +48
    -0
      src/views/cards/common.js
  33. +216
    -0
      src/views/cards/components/CardPagination/index.vue
  34. +112
    -0
      src/views/cards/components/CardProgressBar.vue
  35. +389
    -0
      src/views/cards/components/ImagePreview/index.vue
  36. +654
    -0
      src/views/cards/components/ImagePreviewZoom/index.vue
  37. +1016
    -0
      src/views/cards/components/ImagePreviewZoomTemperature/index.vue
  38. +138
    -0
      src/views/cards/components/cardBox.vue
  39. +221
    -0
      src/views/cards/components/model-dialog.vue
  40. +300
    -0
      src/views/cards/error_statistics/patrolAbnormalStatistics.vue
  41. +1279
    -0
      src/views/cards/taskSummary/inspectionTask.vue
  42. +222
    -0
      src/views/cards/taskSummary/inspectionTaskDefectDetail.vue
  43. +701
    -0
      src/views/cards/taskSummary/inspectionTaskDefectDetailImage.vue
  44. +1134
    -0
      src/views/cards/taskSummary/newInspectionTaskDetail.vue
  45. +108
    -0
      src/views/cards/task_statistics/task_status.vue

+ 2
- 0
package.json View File

@ -53,7 +53,9 @@
"linq": "^4.0.3",
"lodash": "^4.17.21",
"md5.js": "^1.3.5",
"moment": "^2.29.2",
"nprogress": "0.2.0",
"papaparse": "^5.5.3",
"quill": "2.0.2",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",


+ 88
- 0
src/api/analysis/index.js View File

@ -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
})
}

+ 116
- 0
src/api/home/index.js View File

@ -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,
});
}

+ 4
- 0
src/api/inspectionDataManage/alarmConfirm.js View File

@ -58,3 +58,7 @@ export function getAllAlgorithmList() {
},
});
}
export function getAlgTypeList() {
return getAllAlgorithmList();
}

+ 9
- 0
src/api/inspectionDataManage/inspectionArchive.js View File

@ -47,3 +47,12 @@ export function partrolResultArchive (params) {
params
})
}
// 绍兴增加在任务列表中归档
export function partrolResultArchiveAdd (params) {
return request({
url: '/patrol/resultmain/updateInfo_shaoxing',
method: 'post',
data: params
})
}

+ 9
- 0
src/api/modelDialog/modelDialog.js View File

@ -0,0 +1,9 @@
import request from "@/utils/request";
// 大模型对话
export function semanticsModel (query) {
return request({
url: '/simulator/ir/semanticsModel',
method: 'post',
data: query
})
}

+ 75
- 0
src/api/screen/screen.js View File

@ -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,
});
}

+ 322
- 0
src/api/videosMonitor/index.js View File

@ -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,
});
}

+ 673
- 0
src/components/AsyncImageFrame/index.vue View File

@ -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})`;
},
// ==================== FTPURL ====================
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>

+ 133
- 42
src/components/CardPagination/index.vue View File

@ -1,5 +1,6 @@
<template>
<div :class="{ hidden: hidden }" class="pagination-container">
<div class="pagination-total"> {{ total }} </div>
<el-pagination
:background="background"
:current-page.sync="currentPage"
@ -17,108 +18,198 @@
</template>
<script>
import { scrollTo } from "@/utils/scroll-to";
import { scrollTo } from '@/utils/scroll-to'
export default {
name: "CardPagination",
name: 'CardPagination',
props: {
total: {
required: true,
type: Number,
type: Number
},
page: {
type: Number,
default: 1,
default: 1
},
limit: {
type: Number,
default: 20,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50];
},
return [10, 20, 30, 50]
}
},
// 5
// Page button count on small screens
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7,
default: document.body.clientWidth < 992 ? 5 : 7
},
layout: {
type: String,
default: "total, sizes, prev, pager, next, jumper",
default: 'sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true,
default: true
},
autoScroll: {
type: Boolean,
default: true,
default: true
},
hidden: {
type: Boolean,
default: false,
},
default: false
}
},
data() {
return {};
return {}
},
computed: {
currentPage: {
get() {
return this.page;
return this.page
},
set(val) {
this.$emit("update:page", val);
},
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit;
return this.limit
},
set(val) {
this.$emit("update:limit", val);
},
},
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
if (this.currentPage * val > this.total) {
this.currentPage = 1;
this.currentPage = 1
}
this.$emit("pagination", { page: this.currentPage, limit: val });
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800);
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit("pagination", { page: val, limit: this.pageSize });
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800);
scrollTo(0, 800)
}
},
},
};
}
}
}
</script>
<style lang="scss" scoped>
.pagination-container {
background: #fff;
padding: 0px 20px !important;
margin-top: 5px;
margin-bottom: 0px;
::v-deep .el-pagination span:not([class*="suffix"]),
::v-deep .el-pagination button {
font-size: 14px;
}
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);
}
::v-deep .el-pager li {
font-size: 14px;
}
.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;
}


+ 3
- 0
src/layout/components/Sidebar/Item.vue View File

@ -6,6 +6,8 @@ const navIconMap = {
'Home': require('@/assets/images/nav/home.png'),
'遥视任务管理': require('@/assets/images/nav/task.png'),
'巡检任务管理': require('@/assets/images/nav/task.png'),
'巡检任务': require('@/assets/images/nav/task.png'),
'巡视任务': require('@/assets/images/nav/task.png'),
'设备管理': require('@/assets/images/nav/device.png'),
'点位管理': require('@/assets/images/nav/point.png'),
'系统管理': require('@/assets/images/nav/system.png'),
@ -63,3 +65,4 @@ export default {
}
}
</script>

+ 8
- 2
src/main.js View File

@ -1,4 +1,4 @@
import Vue from 'vue'
import Vue from 'vue'
import Cookies from 'js-cookie'
@ -7,6 +7,7 @@ import './assets/styles/element-variables.scss'
import '@/assets/styles/index.scss' // global css
import '@/assets/styles/ruoyi.scss' // ruoyi css
import './views/cards/assets/styles/index.scss'
import App from './App'
import store from './store'
import router from './router'
@ -30,7 +31,9 @@ import FileUpload from "@/components/FileUpload"
// 图片上传组件
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件
import ImagePreview from "@/components/ImagePreview"
import ImagePreview from '@/components/ImagePreview'
// 寮傛鍥剧墖鐢绘缁勪欢
import AsyncImageFrame from '@/components/AsyncImageFrame'
// 字典标签组件
import DictTag from '@/components/DictTag'
// 头部标签组件
@ -64,6 +67,7 @@ Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)
Vue.component('AsyncImageFrame', AsyncImageFrame)
Vue.use(VideoPlayer)
@ -95,3 +99,5 @@ new Vue({
i18n,
render: h => h(App)
})

+ 15
- 1
src/router/index.js View File

@ -87,6 +87,20 @@ export const constantRoutes = [
meta: { title: '个人中心', icon: 'user' }
}
]
},
{
path: '/task-summary',
component: Layout,
redirect: '/task-summary/inspectionTask',
meta: { title: '巡视任务', icon: 'task' },
children: [
{
path: 'inspectionTask',
component: () => import('@/views/cards/taskSummary/inspectionTask'),
name: 'InspectionTaskSummary',
meta: { title: '巡视任务', icon: 'task', activeMenu: '/task-summary' }
}
]
}
]
@ -161,7 +175,7 @@ export const dynamicRoutes = [
meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
}
]
}
},
]
// 防止连续点击多次路由报错


BIN
src/views/cards/assets/btn-next.png View File

Before After
Width: 122  |  Height: 90  |  Size: 2.2 KiB

BIN
src/views/cards/assets/btn-pre.png View File

Before After
Width: 122  |  Height: 90  |  Size: 2.3 KiB

BIN
src/views/cards/assets/card_btn_border.png View File

Before After
Width: 77  |  Height: 41  |  Size: 527 B

BIN
src/views/cards/assets/icon-document.png View File

Before After
Width: 22  |  Height: 22  |  Size: 2.1 KiB

BIN
src/views/cards/assets/icon-title-1.png View File

Before After
Width: 19  |  Height: 23  |  Size: 636 B

BIN
src/views/cards/assets/icon-title-2.png View File

Before After
Width: 22  |  Height: 22  |  Size: 734 B

BIN
src/views/cards/assets/image-btn-left-disable.png View File

Before After
Width: 96  |  Height: 96  |  Size: 781 B

BIN
src/views/cards/assets/image-btn-left-hover.png View File

Before After
Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/views/cards/assets/image-btn-left-normal.png View File

Before After
Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/views/cards/assets/image-btn-left-press.png View File

Before After
Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/views/cards/assets/image-btn-right-disable.png View File

Before After
Width: 96  |  Height: 96  |  Size: 779 B

BIN
src/views/cards/assets/image-btn-right-hover.png View File

Before After
Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/views/cards/assets/image-btn-right-normal.png View File

Before After
Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/views/cards/assets/image-btn-right-press.png View File

Before After
Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/views/cards/assets/model-img.png View File

Before After
Width: 200  |  Height: 200  |  Size: 33 KiB

BIN
src/views/cards/assets/right-title.png View File

Before After
Width: 611  |  Height: 47  |  Size: 15 KiB

+ 658
- 0
src/views/cards/assets/styles/index.scss View File

@ -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;
}
}

BIN
src/views/cards/assets/taskDetail.png View File

Before After
Width: 36  |  Height: 36  |  Size: 2.7 KiB

+ 48
- 0
src/views/cards/common.js View File

@ -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), "*");
}

+ 216
- 0
src/views/cards/components/CardPagination/index.vue View File

@ -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>

+ 112
- 0
src/views/cards/components/CardProgressBar.vue View File

@ -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>

+ 389
- 0
src/views/cards/components/ImagePreview/index.vue View File

@ -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>

+ 654
- 0
src/views/cards/components/ImagePreviewZoom/index.vue View File

@ -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>

+ 1016
- 0
src/views/cards/components/ImagePreviewZoomTemperature/index.vue
File diff suppressed because it is too large
View File


+ 138
- 0
src/views/cards/components/cardBox.vue View File

@ -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>

+ 221
- 0
src/views/cards/components/model-dialog.vue View File

@ -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>

+ 300
- 0
src/views/cards/error_statistics/patrolAbnormalStatistics.vue View File

@ -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>

+ 1279
- 0
src/views/cards/taskSummary/inspectionTask.vue
File diff suppressed because it is too large
View File


+ 222
- 0
src/views/cards/taskSummary/inspectionTaskDefectDetail.vue View File

@ -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>

+ 701
- 0
src/views/cards/taskSummary/inspectionTaskDefectDetailImage.vue View File

@ -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() {
// mountedDOM
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>

+ 1134
- 0
src/views/cards/taskSummary/newInspectionTaskDetail.vue
File diff suppressed because it is too large
View File


+ 108
- 0
src/views/cards/task_statistics/task_status.vue View File

@ -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>

Loading…
Cancel
Save