FlowVue/src/views/ECG/ECGhtml.vue
2024-11-27 16:57:46 +08:00

775 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<!-- <button @click="moveCanvas('left')">左边-左移</button>
<button @click="moveCanvas('right')">左边-右移</button>-->
<sb :-isfd="FD"/>
<div class="container">
<div id="canvas-container">
<canvas ref="leftCanvas" id="leftCanvas" width="21" height="1140"></canvas>
<canvas ref="bottomCanvas" id="bottomCanvas" width="1140" height="1140"></canvas>
<canvas
ref="topCanvas"
id="topCanvas"
width="1140"
height="1140"
@mousedown="handleMouseDown($event, 'L')"
></canvas>
</div>
<div id="canvas-container1">
<canvas ref="rightCanvas" id="rightCanvas" width="21" height="1140"></canvas>
<canvas ref="bottomCanvas1" id="bottomCanvas1" width="1140" height="1140"></canvas>
<canvas
ref="topCanvas1"
id="topCanvas1"
width="1140"
height="1140"
@mousedown="handleMouseDown($event, 'R')"
></canvas>
</div>
</div>
<span class="myrtip"
>{{ '&nbsp;走速:' + convert(suduratio, 0) + 'mm/s ' }}
{{ '&nbsp;振幅:' + convert(lineratio, 1) + 'mm/mv ' }} {{ '&nbsp;报告模式6X2' }}</span
>
<div @click="preventClick">
<el-slider
v-model="sliderValue"
:min="0"
:max="50"
:step="1"
@input="handleSliderChange"
:show-tooltip="false"
/>
</div>
</div>
<!--纠错功能-->
<el-dialog v-model="isdialog" title="导联纠错" width="600" @close="close">
<div>
<el-checkbox-group v-model="transfer" :min="0" :max="2" class="mycheckbox-group">
<el-checkbox
v-for="city in transferleads"
:key="city"
:label="city"
:value="city"
class="mycheckbox-item"
@change="checkboxchage"
>
{{ city }}
</el-checkbox>
</el-checkbox-group>
</div>
<div style="margin-top: 10px">
<el-button type="primary" @click="clickdl" :disabled="checkes">交换导联</el-button>
<el-button type="primary" @click="recovery()">恢复正常</el-button>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { number } from 'vue-types'
import Magnifier from './ECGFD.vue'
import sb from './ECGSB.vue'
import BB from './ECGBB.vue'
//读取出来的json
const text = ref()
const sliderValue = ref(0) // 初始化滑块值
//给父组件回传值
const emits = defineEmits(['update:value'])
const lineratio1 = ref(0.05) //画线的系数 振幅
const previousValue = ref(0) // 记录上一个滑块值
const swipeDirection = ref('') // 用于存储滑动方向
// 响应式引用
const leftCanvas = ref(null)
const rightCanvas = ref(null)
const bottomCanvas = ref(null)
const bottomCanvas1 = ref(null)
const topCanvas = ref(null)
const topCanvas1 = ref(null)
const url = ref()
/*鼠标绘制相关*/
const ctx = ref(null)
const isVertical = ref(false) // 控制线条类型true 为竖直false 为水平
const lastPoint = ref(null) // 用于存储上一个点的坐标
const isdialog = ref(false) //用于开启纠错
const transfer = ref() //纠错导联 值
const checkes = ref(true) //控制导联交换按钮是否可用
// 响应式数据
const beatArray1 = ref([])
const beatArray2 = ref([]) //备份数据
let offset = 0
const spacing = 20
const heightoff=ref(0)//心电图的偏移量 高度变化的时候
const FD=ref(false)
function handleMouseDown(event, type) {
if (type === 'L') {
ctx.value = topCanvas.value.getContext('2d')
} else if (type === 'R') {
ctx.value = topCanvas1.value.getContext('2d')
}
if (infoParams.Ismeasure !== 1) {
return
}
const rect =
type === 'L'
? topCanvas.value.getBoundingClientRect()
: topCanvas1.value.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
if (lastPoint.value) {
// 如果已经有一个点被记录,那么使用这个新点和上一个点绘制线条
drawLineAndDistance(lastPoint.value, { x, y })
lastPoint.value = null // 重置上一个点
} else {
// 记录这个点作为上一个点
lastPoint.value = { x, y }
}
}
function calculateDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
}
function drawLineAndDistance(point1, point2) {
//ctx.value.clearRect(0, 0, topCanvas.value.width, topCanvas.value.height); // 清除画布
//竖线
if (Math.abs(point2.x - point1.x) <= 20) {
point2.x = point1.x
lineTo(point1.x, point1.y, point2.x, point2.y, 1)
} else if (Math.abs(point2.y - point1.y) <= 20) {
//横
point2.y = point1.y
lineTo(point1.x, point1.y, point2.x, point2.y, 0)
} else {
lastPoint.value = null
}
}
function lineTo(x, y, x1, y1, type) {
// 绘制两个点
ctx.value.fillStyle = 'black'
ctx.value.beginPath()
ctx.value.arc(x, y, 1, 0, 2 * Math.PI)
ctx.value.fill()
ctx.value.beginPath()
ctx.value.arc(x1, y1, 1, 0, 2 * Math.PI)
ctx.value.fill()
// 绘制直线
ctx.value.beginPath()
ctx.value.moveTo(x, y)
ctx.value.lineTo(x1, y1)
ctx.value.strokeStyle = 'black'
ctx.value.lineWidth = 1
ctx.value.stroke()
// 计算并绘制距离文本
const distance = calculateDistance(x, y, x1, y1)
const ms = (25.4 / 96) * (25 / 1000) * distance
ctx.value.font = '11px Arial'
ctx.value.fillStyle = 'black'
if (type === 1) {
ctx.value.fillText(`距离: ${ms.toFixed(2)}ms`, x + 5, y + 30)
} else {
ctx.value.fillText(`距离: ${ms.toFixed(2)}ms`, x + 10, y - 10)
}
}
/*鼠标绘制相关*/
const handleSliderChange = (value) => {
// 计算当前值与上一次值之间的差值
const difference = value - previousValue.value
const swipeDifference = Math.abs(difference)
console.log('滑动差值' + swipeDifference)
if (value > previousValue.value) {
swipeDirection.value = '右滑' // 向右滑动
moveCanvas('left', swipeDifference)
} else if (value < previousValue.value) {
swipeDirection.value = '左滑' // 向左滑动
moveCanvas('right', swipeDifference)
} else {
swipeDirection.value = '未滑动' // 没有滑动
}
previousValue.value = value // 更新上一个值
console.log('滑块值改变为:', value)
}
//渲染组件后执行
onMounted(async () => {
console.log('挂载完成')
// topCanvas.value = document.getElementById('topCanvas');
})
nextTick(() => {
/* 计算宽度和高度*/
var canvasContainer = document.getElementById('canvas-container')
var canvasContainer1 = document.getElementById('canvas-container1')
var leftCanvas = document.getElementById('leftCanvas')
// 获取元素的计算后的样式
var style = window.getComputedStyle(canvasContainer)
// 获取元素的计算后的样式
var leftCanvasstyle = window.getComputedStyle(leftCanvas)
// 获取宽度
var widthPercentage = style.width
//获取高度
var heightPercentage = style.height
var widthInPixels = canvasContainer.offsetWidth
var heightInPixels = canvasContainer.offsetHeight
// 宽度除以 20 并取整
var adjustedWidth = widthInPixels / 20
var adjustedheight = heightInPixels / 20
// 判断 adjustedWidth 是否为整数
if (Number.isInteger(adjustedWidth)) {
canvasContainer.style.width = widthInPixels + 'px'
} else {
const integer = Math.round(adjustedWidth)
const awidth = integer * 20
canvasContainer.style.width = awidth - 20 + 'px'
canvasContainer1.style.width = awidth - 20 + 'px'
console.log('adjustedWidth 不是整数')
}
if (Number.isInteger(adjustedheight)) {
canvasContainer.style.height = heightInPixels + 'px'
leftCanvas.height = heightInPixels + 'px'
} else {
const integer = Math.round(adjustedheight)
const ahe = integer * 20
canvasContainer.style.height = ahe - 118 + 'px'
canvasContainer1.style.height = ahe - 118 + 'px'
leftCanvas.height = ahe - 118
heightoff.value=(ahe -118)/6
}
fetchData()
})
// 定义异步方法
const fetchData = async () => {
try {
text.value = await fetchFileContent(infoParams.jsonurl)
handleFileChange()
Bottom()
// console.log('获取到的数据:', text.value);
} catch (error) {
console.error('获取数据时出错:', error)
}
}
const inputSlider = (value) => {
setScrollTop(value)
}
const formatTooltip = (value) => {
return `${value} px`
}
async function fetchFileContent(url) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error('网络响应不是 ok')
}
return await response.text() // 返回文本内容
} catch (error) {
console.error('获取文件内容时出错:', error)
throw error // 抛出错误以便调用者可以处理
}
}
///接受参数
const infoParams = defineProps({
jsonurl: String,
examId: String,
Isgrid: Number,
Ismeasure: Number,
Isfd: Boolean,
lineratio: Number,
suduratio: Number,
isrefresh: Boolean,
iscorrect: Boolean
})
// 监听多个值的变化
watch(
[
() => infoParams.Isgrid,
() => infoParams.Ismeasure,
() => infoParams.lineratio,
() => infoParams.suduratio,
() => infoParams.isrefresh,
() => infoParams.iscorrect,
()=>infoParams.Isfd
],
(
[newIsGridValue, newAnotherValue, newlineratio, newsuduratio, newisrefresh, newiniscorrect,newfd],
[oldIsGridView, oldAnotherValue, oldlineratio, oldsuduratio, oldisrefresh, oldiscorrect,oldds]
) => {
// 检查 isGrid 是否发生变化
if (newIsGridValue !== oldIsGridView) {
// 值发送变化的时候 重新绘制 先清除画布
Repaint()
}
// 检查 anotherValue 是否发生变化
if (newAnotherValue !== oldAnotherValue) {
// 是否开启测量
}
//振幅
if (newlineratio !== oldlineratio) {
Repaint()
}
//走速
if (newsuduratio !== oldsuduratio) {
Repaint()
}
//刷新
if (newisrefresh !== oldisrefresh) {
console.log(newisrefresh)
Repaint()
}
//
if (newiniscorrect !== oldiscorrect) {
isdialog.value = newiniscorrect
}
if(newfd!==oldds)
{
FD.value=newfd
console.log(FD.value)
}
}
)
//恢复导联
function recovery() {
transfer.value = []
beatArray1.value = beatArray2.value.slice()
Repaint()
}
//交换导联按钮
function checkboxchage() {
checkes.value = transfer.value.length === 2 ? false : true
}
//交换导联
function clickdl() {
const one = transfer.value[0]
const two = transfer.value[1]
const oneindex = transferleads.indexOf(one)
const twoindex = transferleads.indexOf(two)
swapElements(oneindex, twoindex)
Repaint()
}
// 定义一个方法来交换数组中的两个元素
const swapElements = (index1, index2) => {
// 检查索引是否有效
if (
index1 < 0 ||
index2 < 0 ||
index1 >= beatArray1.value.length ||
index2 >= beatArray1.value.length
) {
console.log('索引超出数组范围')
return
}
// 交换元素
const temp = beatArray1.value[index1]
beatArray1.value[index1] = beatArray1.value[index2]
beatArray1.value[index2] = temp
}
//纠错
function close() {
isdialog.value = false
transfer.value = []
emits('update:value', isdialog.value)
}
// 定义一个对象来存储命令值和对应的比例文本
const lineratioMap = {
0.012: '2.5',
0.025: '5',
0.05: '10',
0.1: '20',
0.2: '40'
}
const suduratioMap = {
0.2: '5',
0.4: '10',
0.5: '12.5',
1: '25',
2: '50'
}
function convert(command, type) {
if (type === 0) {
return suduratioMap[command] || null
} else {
return lineratioMap[command] || null
}
}
// 刷新 重绘
function Repaint() {
eliminate(leftCanvas.value)
eliminate(rightCanvas.value)
eliminate(bottomCanvas.value)
eliminate(bottomCanvas1.value)
eliminate(topCanvas.value)
eliminate(topCanvas1.value)
Bottom()
}
// 定义要处理的键
const leads = [
'LEAD_I',
'LEAD_II',
'LEAD_III',
'LEAD_AVR',
'LEAD_AVL',
'LEAD_AVF',
'LEAD_V1',
'LEAD_V2',
'LEAD_V3',
'LEAD_V4',
'LEAD_V5',
'LEAD_V6'
]
const transferleads = ['I', 'II', 'III', 'avR', 'avL', 'avF', 'V1', 'V2', 'V3', 'V4', 'V5', 'V6']
// 每个心电波形名称数组
const lead_name = ['I', 'II', 'III', 'aVR', 'aVL', 'aVF']
const rlead_name = ['V1', 'V2', 'V3', 'V4', 'V5', 'V6']
//将读取到的文件转换成数组
function handleFileChange() {
const json = JSON.parse(text.value)
leads.forEach((lead, index) => {
const value = json[lead].trim()
const commaSeparatedValue = value.replace(/\s+/g, ',')
const numberArray = commaSeparatedValue.split(',').map(Number)
beatArray1.value.push(numberArray)
})
beatArray2.value = beatArray1.value.slice()
}
// 定义移动画布的函数
function moveCanvas(direction, count) {
const xoffset = count === 1 ? 1 : count
if (direction === 'left') {
if (offset !== -20 * 60) offset -= spacing * xoffset
} else if (direction === 'right') {
if (offset !== 0) offset += spacing * xoffset
}
// if (direction === 'left') {
// offset -= spacing
// } else if (direction === 'right') {
// offset += spacing
// }
topCanvas.value.style.left = offset + 'px'
topCanvas1.value.style.left = offset + 'px'
}
//清除画布内容
function eliminate(c_canvas) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
}
function Bottom() {
drawBottomCanvas()
drawTopCanvas()
}
// 绘制底层画布的内容
function drawBottomCanvas() {
if (infoParams.Isgrid === 1) {
lbackcanvas(leftCanvas.value)
rbackcanvas(rightCanvas.value)
drawMultipleLines(bottomCanvas.value, beatArray1.value)
drawMultipleLines(bottomCanvas1.value, beatArray1.value)
}
begin(leftCanvas.value, beatArray1.value)
beginr(rightCanvas.value, beatArray1.value)
}
// 绘制顶层画布的内容
function drawTopCanvas() {
drawMultipleLinesl(topCanvas.value, beatArray1.value)
drawMultipleLinesr(topCanvas1.value, beatArray1.value)
}
//定标电压的画布
function lbackcanvas(c_canvas) {
drawBigBG(c_canvas)
drawSmallGrid(c_canvas)
drawMediumGrid(c_canvas)
}
function rbackcanvas(c_canvas) {
drawBigBG(c_canvas)
drawSmallGrid(c_canvas)
drawMediumGrid(c_canvas)
}
// 绘制函数
function drawBigBG(c_canvas) {
const context = c_canvas.getContext('2d')
context.globalAlpha = 1
context.fillStyle = 'rgba(0,0,0,0)'
context.fillRect(0, 0, c_canvas.width + 21, c_canvas.height + 21)
}
//画小格子
function drawSmallGrid(c_canvas) {
const context = c_canvas.getContext('2d')
context.globalAlpha = 1
context.fillStyle = '#ff6b64'
const dotMarginX = 20 / 5,
dotMarginY = 20 / 5
for (let i = dotMarginX; i < c_canvas.width; i += dotMarginX) {
if (i % 20 !== 0) {
for (let j = dotMarginY; j < c_canvas.height; j += dotMarginY) {
if (j % 20 !== 0) {
context.rect(i, j, 1, 0.5)
}
}
}
}
context.fill()
}
//中格子
function drawMediumGrid(c_canvas) {
const context = c_canvas.getContext('2d')
context.globalAlpha = 1
context.strokeStyle = '#ff6b64'
context.strokeWidth = 1.5
context.beginPath()
for (let x = 0.5; x < c_canvas.width; x += 20) {
context.moveTo(x, 0)
context.lineTo(x, c_canvas.height)
}
for (let y = 0.5; y < c_canvas.height; y += 20) {
context.moveTo(0, y)
context.lineTo(c_canvas.width, y)
}
context.stroke()
context.closePath()
}
function begin(c_canvas, beatArray) {
const ctx = c_canvas.getContext('2d')
ctx.globalAlpha = 1
ctx.strokeStyle = '#000000'
let offset = -50
const spacing = heightoff.value
beatArray.forEach((dataArray, index) => {
if (index <= 6) {
const x = 0
const y = offset - dataArray[index - 1] * 0.025
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 15, y - 40)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 15, y - 40)
ctx.lineTo(x + 15, y)
ctx.moveTo(x + 15, y)
ctx.lineTo(x + 20, y)
ctx.stroke()
ctx.closePath()
if (index > 0 && index < 4) {
ctx.font = '15px Arial'
ctx.fillStyle = 'black'
ctx.fillText(lead_name[index - 1], x + 4, y + 14)
} else {
ctx.font = '10px Arial'
ctx.fillStyle = 'black'
ctx.fillText(lead_name[index - 1], x + 1, y + 12)
}
offset += spacing
}
})
ctx.strokeStyle = '#000000'
}
function beginr(c_canvas, beatArray) {
const ctx = c_canvas.getContext('2d')
ctx.globalAlpha = 1
ctx.strokeStyle = '#000000'
let offset = -50
const spacing = heightoff.value
beatArray.forEach((dataArray, index) => {
if (index <= 6) {
const x = 0
const y = offset - dataArray[index - 1] * 0.025
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 15, y - 40)
ctx.moveTo(x + 5, y - 40)
ctx.lineTo(x + 5, y)
ctx.moveTo(x + 15, y - 40)
ctx.lineTo(x + 15, y)
ctx.moveTo(x + 15, y)
ctx.lineTo(x + 20, y)
ctx.stroke()
ctx.closePath()
ctx.font = '11px Arial'
ctx.fillStyle = 'black'
ctx.fillText(rlead_name[index - 1], x + 2, y + 12)
offset += spacing
}
})
ctx.strokeStyle = '#000000'
}
//背景
function drawMultipleLines(c_canvas, beatArrays) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
drawBigBG(c_canvas)
drawSmallGrid(c_canvas)
drawMediumGrid(c_canvas)
}
//左边心电图
function drawMultipleLinesl(c_canvas, beatArrays) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
let offset = 0
const spacing = heightoff.value
beatArrays.forEach((dataArray, index) => {
if (index <= 5) {
drawLine1(c_canvas, dataArray, offset, index)
offset += spacing
}
})
}
//右边心电图
function drawMultipleLinesr(c_canvas, beatArrays) {
const ctx = c_canvas.getContext('2d')
ctx.clearRect(0, 0, c_canvas.width, c_canvas.height)
let offset = 0
const spacing = heightoff.value
beatArrays.forEach((dataArray, index) => {
if (index > 5) {
drawLine1(c_canvas, dataArray, offset, index)
offset += spacing
}
})
}
//画心电图线
function drawLine1(c_canvas, beatArray, offset, index) {
const ctx = c_canvas.getContext('2d')
ctx.strokeStyle = '#030101'
ctx.lineWidth = 1
ctx.beginPath()
if (index <= 5) {
if (beatArray.length > 0) {
const firstX = 0
const firstY = (heightoff.value-50) - beatArray[0] * infoParams.lineratio + offset
ctx.moveTo(firstX, firstY)
for (let i = 0; i < beatArray.length - 1; i++) {
const x2 = (0 + (i + 1) / 10) * infoParams.suduratio
const y2 = (heightoff.value-50) - beatArray[i + 1] * infoParams.lineratio + offset
ctx.lineTo(x2, y2)
}
ctx.stroke()
ctx.closePath()
}
}
if (index > 5) {
if (beatArray.length > 0) {
const firstX = 0
const firstY = (heightoff.value-50) - beatArray[0] * infoParams.lineratio + offset
ctx.moveTo(firstX, firstY)
for (let i = 0; i < beatArray.length - 1; i++) {
const x2 = (0 + (i + 1) / 10) * infoParams.suduratio
const y2 = (heightoff.value-50) - beatArray[i + 1] * infoParams.lineratio + offset
ctx.lineTo(x2, y2)
}
ctx.stroke()
ctx.closePath()
}
}
}
</script>
<style scoped>
.container {
display: flex;
justify-content: center;
align-items: flex-start;
width: 100%;
overflow-x: auto;
overflow-y: hidden;
height: 100%;
max-width: 3920px;
max-height: 3920px;
}
#canvas-container,
#canvas-container1 {
overflow-x: hidden;
overflow-y: hidden;
position: relative;
width: 50%;
height: 77vh;
margin-right: 0;
}
#bottomCanvas,
#bottomCanvas1 {
position: absolute;
/* width: 1140; */
/* height: 75vh; */
z-index: 0;
margin-left: 20px;
}
#leftCanvas,
#rightCanvas {
position: absolute;
width: 21;
/* height: 600px; */
z-index: 99;
background-color: rgb(255, 255, 255);
}
#topCanvas,
#topCanvas1 {
position: absolute;
width: 1140px;
/* height: 600px; */
z-index: 1;
background: transparent;
margin-left: 20px;
}
canvas {
position: absolute;
}
.myrtip {
display: flex;
flex-direction: row-reverse;
margin-right: 30px;
}
.mycheckbox-group {
display: flex;
flex-wrap: wrap;
}
.mycheckbox-item {
margin-left: 5px;
margin-right: 30px; /* 根据需要调整间距 */
margin-bottom: 10px; /* 根据需要调整间距 */
width: 30px;
}
/* 添加其他样式 */
</style>