Browse Source

fix:svg读实时数据测试

dev-xjf
肖晋飞 4 weeks ago
parent
commit
d56f913e53
  1. 2
      src/views/dashboard/demo/components/ParameterInfo.vue
  2. 3
      src/views/dashboard/demo/components/ProjectCard.vue
  3. 1
      src/views/dashboard/demo/components/staticImg.vue
  4. 106
      src/views/dashboard/demo/components/staticSvg.vue
  5. 157
      src/views/dashboard/demo/components/svg copy.vue
  6. 226
      src/views/dashboard/demo/components/svg.vue
  7. 5
      src/views/dashboard/demo/index.vue

2
src/views/dashboard/demo/components/ParameterInfo.vue

@ -21,13 +21,11 @@ watch(
}, },
) )
function getInterval(record: any) { function getInterval(record: any) {
console.log(record)
if (record.predictValue === '-') if (record.predictValue === '-')
return '-' return '-'
record.predictValue = Number(record.predictValue).toFixed(2) record.predictValue = Number(record.predictValue).toFixed(2)
const threshold = record.ResidualThreshold const threshold = record.ResidualThreshold
console.log(threshold)
const predictValue = record.predictValue || 0 const predictValue = record.predictValue || 0
return `[${Number(predictValue) - threshold},${Number(predictValue) + threshold}]` return `[${Number(predictValue) - threshold},${Number(predictValue) + threshold}]`
} }

3
src/views/dashboard/demo/components/ProjectCard.vue

@ -32,15 +32,12 @@ async function getNewData() {
ifColor = res === '1' ifColor = res === '1'
gridData.value.push(Object.assign({}, item, { ifColor })) gridData.value.push(Object.assign({}, item, { ifColor }))
console.log(gridData.value)
} }
catch (e) { catch (e) {
} }
} }
} }
// //
// onMounted(async () => { // onMounted(async () => {
// const rows = props.data.Content || [] // const rows = props.data.Content || []

1
src/views/dashboard/demo/components/staticImg.vue

@ -25,7 +25,6 @@ watch(
) )
function getAssetsFile() { function getAssetsFile() {
const url = props.data.path const url = props.data.path
console.log(url)
return new URL(`../../../../assets/${url}`, import.meta.url).href return new URL(`../../../../assets/${url}`, import.meta.url).href
} }
</script> </script>

106
src/views/dashboard/demo/components/staticSvg.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from 'vue' import { watch } from 'vue'
import { Card } from 'ant-design-vue' import { Card } from 'ant-design-vue'
import Svg from './svg.vue' import Svg from './svg.vue'
@ -10,121 +10,19 @@ const props = defineProps({
}, },
}) })
//
const realtimeData = ref({
path: '/送风机.svg',
// Header: 'SVG',
realtimeValues: {
'current-value': 0,
'status-text': '状态: 正常',
'status-indicator': 'normal',
},
valueMapping: {
'current-value': 'current-value',
'status-text': 'status-text',
},
})
//
let updateTimer: number | null = null
//
function updateData() {
// 0-100
const newValue = Math.floor(Math.random() * 101)
// //
// let status = ''
// let statusColor = '#52c41a'
// if (newValue > 90) {
// status = ''
// statusColor = '#ff4d4f'
// }
// else if (newValue > 70) {
// status = ''
// statusColor = '#faad14'
// }
//
realtimeData.value.realtimeValues = {
'current-value': newValue,
// 'status-text': `: ${status}`,
// 'status-indicator': status.toLowerCase(),
}
// 0-100-135135
const angle = -135 + (newValue / 100) * 270
const svgElement = document.querySelector('.svg-container > svg')
// svgElement?.removeAttribute('width')
// svgElement?.removeAttribute('height')
if (svgElement) {
const pointer = svgElement.getElementById('pointer')
if (pointer)
pointer.setAttribute('transform', `rotate(${angle} 200 150)`)
//
const statusIndicator = svgElement.getElementById('status-indicator')
if (statusIndicator)
statusIndicator.setAttribute('fill', statusColor)
}
}
//
onMounted(() => {
//
updateData()
// 2
updateTimer = window.setInterval(updateData, 2000)
})
watch( watch(
() => props.data, () => props.data,
(newValue, oldValue) => { (newValue, oldValue) => {
// //
console.log('a has changed', newValue, oldValue) console.log('a has changed', newValue, oldValue)
realtimeData.value.path = props.data.path
}, },
) )
onUnmounted(() => {
//
if (updateTimer)
clearInterval(updateTimer)
})
</script> </script>
<template> <template>
<!-- <div class="svg-demo-container"> -->
<!-- <div class="demo-header">
<h2>SVG实时数据可视化演示</h2>
<p>这个演示展示了如何嵌入SVG并实时更新其中的数据数据每2秒自动更新一次也可以点击下方按钮手动更新</p>
<button
class="update-btn"
@click="manualUpdate"
>
手动更新数据
</button>
</div>
<div class="demo-content">
<Svg :data="realtimeData" />
</div>
<div class="demo-info">
<h3>使用说明</h3>
<ul>
<li>1. 将SVG文件放在项目的assets/svg目录下</li>
<li>2. 在父组件中准备数据对象包含SVG路径实时值和映射关系</li>
<li>3. SVG中的元素需要有唯一的ID以便与数据进行绑定</li>
<li>4. 组件支持自动监听数据变化并更新SVG内容</li>
<li>5. 可以通过点击SVG区域手动触发数据更新</li>
</ul>
</div>
</div> -->
<!-- </div> -->
<Card class="enter-y !my-1" :title="props.data.Header"> <Card class="enter-y !my-1" :title="props.data.Header">
<!-- <img class="mx-auto h-full w-full" :src="props.data.path"> --> <!-- <img class="mx-auto h-full w-full" :src="props.data.path"> -->
<Svg class="h-full w-full" :data="realtimeData" /> <Svg :data="props.data.path" />
</Card> </Card>
</template> </template>

157
src/views/dashboard/demo/components/svg copy.vue

@ -0,0 +1,157 @@
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, ref, toRefs, watch } from 'vue'
import { getEXANowListReal } from '@/api/alert/exa'
const props = defineProps<{
data: string
}>()
const { data } = toRefs(props)
const svgContent = ref<string>('')
const isLoading = ref<boolean>(true)
const svgError = ref<string>('')
const point = ref<{ itemNameArray: string[] }>({ itemNameArray: [] })
const pointElements = ref<NodeListOf<SVGElement> | undefined>()
let updateInterval: number | undefined
// SVG
async function loadSvg() {
if (!data.value) {
svgError.value = '未提供SVG路径'
isLoading.value = false
return
}
isLoading.value = true
svgError.value = ''
// URL
let url = data.value
if (!url.startsWith('http') && !url.startsWith('/')) {
// assets
url = new URL(`../../../../assets/${url}`, import.meta.url).href
}
const response = await fetch(url)
if (!response.ok)
throw new Error(`无法加载SVG: ${response.statusText}`)
svgContent.value = await response.text()
const svgElement = document.querySelector('.svg-container > svg')
svgElement?.removeAttribute('width')
svgElement?.removeAttribute('height')
try {
// SVG
const svgElement = document.querySelector('.svg-container > svg')
if (!svgElement)
return
// point-name
pointElements.value = svgElement.querySelectorAll('[point-name]')
const pointList: string[] = []
pointElements.value.forEach(async (element) => {
const pointName = element.getAttribute('point-name')
pointName && pointList.push(pointName)
})
point.value = { itemNameArray: pointList }
// DOM
await nextTick()
// updateRealtimeValues()
}
catch (error) {
console.error('加载SVG失败:', error)
svgError.value = `加载失败: ${error instanceof Error ? error.message : String(error)}`
}
finally {
isLoading.value = false
}
}
// SVG
async function updateRealtimeValues() {
if (!svgContent.value || isLoading.value)
return
try {
const res = await getEXANowListReal(point.value)
if (res.ReturnValue && res.ReturnValue === 1) {
const data = res.ValueArray
console.log(data)
if (pointElements.value) {
pointElements.value.forEach((element, index) => {
console.log(element)
console.log(element.tagName)
//
if (element.tagName === 'text' || element.tagName === 'tspan' || element.tagName === 'DIV')
console.log(element.tagName)
element.textContent = String(data[index] || '')
})
}
}
}
catch (error) {
console.error('更新实时数据失败:', error)
}
}
//
watch(
() => data.value,
() => {
loadSvg()
},
{ immediate: true },
)
onMounted(() => {
loadSvg()
// updateRealtimeValues()
setInterval(() => {
updateRealtimeValues()
}, 3000) // 2
})
onUnmounted(() => {
//
clearInterval(updateInterval)
})
</script>
<template>
<!-- <Card class="enter-y !my-1" :title="data.Header || 'SVG可视化'"> -->
<div v-if="isLoading" class="h-40 flex items-center justify-center">
加载中...
</div>
<div v-else-if="svgError" class="h-40 flex items-center justify-center text-red-500">
{{ svgError }}
</div>
<div v-else class="svg-container" @click="updateRealtimeValues" v-html="svgContent" />
<!-- </Card> -->
</template>
<style scoped>
::v-deep .ant-card-body {
padding: 10px;
overflow: auto;
}
.svg-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
min-height: 300px;
cursor: pointer; /* 点击时可以手动触发数据更新 */
}
.svg-container > svg {
max-width: 100%;
max-height: 100%;
}
</style>

226
src/views/dashboard/demo/components/svg.vue

@ -1,43 +1,47 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Card } from 'ant-design-vue' import { nextTick, onMounted, onUnmounted, ref, toRefs, watch } from 'vue'
import { nextTick, onMounted, ref, toRefs, watch } from 'vue' import { getEXANowListReal } from '@/api/alert/exa'
interface SvgData {
path: string // SVG
Header?: string //
realtimeValues?: Record<string, any> //
valueMapping?: Record<string, string> // SVGID
}
const props = defineProps<{ const props = defineProps<{
data: SvgData data: string
}>() }>()
const { data } = toRefs(props) const { data } = toRefs(props)
const svgContent = ref<string>('') const svgContent = ref<string>('')
const isLoading = ref<boolean>(true) const isLoading = ref<boolean>(true)
const svgError = ref<string>('') const svgError = ref<string>('')
const point = ref<{ itemNameArray: string[] }>({ itemNameArray: [] })
// const pointElements = ref<NodeListOf<SVGElement> | undefined>()
let updateInterval: NodeJS.Timeout | undefined
// SVG // SVG
async function loadSvg() { async function loadSvg() {
if (!data.value.path) { if (!data.value) {
svgError.value = '未提供SVG路径' svgError.value = '未提供SVG路径'
isLoading.value = false isLoading.value = false
return return
} }
isLoading.value = true
svgError.value = ''
try { try {
isLoading.value = true // URL
svgError.value = '' let url = data.value
// URL
let url = data.value.path
if (!url.startsWith('http') && !url.startsWith('/')) { if (!url.startsWith('http') && !url.startsWith('/')) {
// assets // assets
url = new URL(`../../../../assets/${url}`, import.meta.url).href url = new URL(`../../../../assets/${url}`, import.meta.url).href
} }
//
const timestamp = new Date().getTime()
if (url.includes('?'))
url += `&t=${timestamp}`
else
url += `?t=${timestamp}`
const response = await fetch(url) const response = await fetch(url)
console.log(response)
if (!response.ok) if (!response.ok)
throw new Error(`无法加载SVG: ${response.statusText}`) throw new Error(`无法加载SVG: ${response.statusText}`)
@ -45,27 +49,11 @@ async function loadSvg() {
// DOM // DOM
await nextTick() await nextTick()
console.log(document.querySelector('.svg-container > svg'))
document.querySelector('.svg-container > svg')?.removeAttribute('width')
document.querySelector('.svg-container > svg')?.removeAttribute('height')
// SVGviewBox // updateRealtimeValues()
const svgElement = document.querySelector('.svg-container > svg')
if (svgElement) {
// widthheightSVGCSS
svgElement.removeAttribute('width')
svgElement.removeAttribute('height')
// SVGviewBox
if (!svgElement.hasAttribute('viewBox')) {
// viewBoxSVG
const bbox = svgElement.getBBox()
if (bbox.width && bbox.height)
svgElement.setAttribute('viewBox', `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`)
}
// preserveAspectRatio
svgElement.setAttribute('preserveAspectRatio', 'xMidYMid meet')
}
updateRealtimeValues()
} }
catch (error) { catch (error) {
console.error('加载SVG失败:', error) console.error('加载SVG失败:', error)
@ -77,57 +65,54 @@ async function loadSvg() {
} }
// SVG // SVG
function updateRealtimeValues() { async function updateRealtimeValues() {
if (!svgContent.value || !data.value.realtimeValues) if (!svgContent.value || isLoading.value)
return return
try { try {
// SVG
const svgElement = document.querySelector('.svg-container > svg') const svgElement = document.querySelector('.svg-container > svg')
svgElement?.removeAttribute('width')
svgElement?.removeAttribute('height')
console.log(svgElement) console.log(svgElement)
// SVG
if (!svgElement) if (!svgElement)
return return
const { realtimeValues, valueMapping = {} } = data.value // point-name
const pointElements = svgElement.querySelectorAll('[point-name]')
// SVG const pointList: string[] = []
Object.entries(valueMapping).forEach(([svgElementId, dataField]) => {
console.log([svgElementId, dataField]) // 使point-name
const element = svgElement.getElementById(svgElementId) pointElements.forEach((element) => {
console.log(element) const pointName = element.getAttribute('point-name')
if (pointName) {
if (element && realtimeValues[dataField] !== undefined) { pointList.push(pointName)
// console.log(`找到元素: ${element.tagName}, point-name=${pointName}`)
if (element.tagName === 'text' || element.tagName === 'tspan') {
element.textContent = String(realtimeValues[dataField])
}
else if (element.tagName === 'circle' || element.tagName === 'rect' || element.tagName === 'path') {
//
element.setAttribute('data-value', String(realtimeValues[dataField]))
//
if (typeof realtimeValues[dataField] === 'number') {
const value = realtimeValues[dataField] as number
//
if (value > 90)
element.setAttribute('fill', '#ff4d4f')
else if (value > 70)
element.setAttribute('fill', '#faad14')
else
element.setAttribute('fill', '#52c41a')
}
}
} }
}) })
// ID point.value = { itemNameArray: pointList }
if (Object.keys(valueMapping).length === 0) { console.log('收集的point-name列表:', pointList)
Object.entries(realtimeValues).forEach(([field, value]) => { const res = await getEXANowListReal(point.value)
const element = svgElement.getElementById(field) console.log('API响应结果:', res)
if (element) {
if (element.tagName === 'text' || element.tagName === 'tspan') if (res && res.ReturnValue === 1 && res.ValueArray) {
element.textContent = String(value) const dataValues = res.ValueArray
} console.log('数据值:', dataValues)
})
if (pointElements) {
pointElements.forEach((element, index) => {
//
const tagName = element.tagName.toLowerCase()
const value = dataValues[index] || ''
console.log(`更新元素[${index}]: 标签=${element.tagName}, 值=${value}`)
//
element.textContent = String(value)
// data-value便
element.setAttribute('data-value', String(value))
})
}
} }
} }
catch (error) { catch (error) {
@ -137,24 +122,26 @@ function updateRealtimeValues() {
// //
watch( watch(
() => data.value.path, () => data.value,
() => { () => {
loadSvg() loadSvg()
}, },
{ immediate: true }, { immediate: true },
) )
//
watch(
() => data.value.realtimeValues,
() => {
updateRealtimeValues()
},
{ deep: true },
)
onMounted(() => { onMounted(() => {
loadSvg() loadSvg()
// 便
updateInterval = setTimeout(() => {
console.log('定时更新数据...')
updateRealtimeValues()
}, 3000)
})
onUnmounted(() => {
//
clearTimeout(updateInterval)
}) })
</script> </script>
@ -166,60 +153,33 @@ onMounted(() => {
<div v-else-if="svgError" class="h-40 flex items-center justify-center text-red-500"> <div v-else-if="svgError" class="h-40 flex items-center justify-center text-red-500">
{{ svgError }} {{ svgError }}
</div> </div>
<div v-else class="svg-container" @click="updateRealtimeValues" v-html="svgContent" /> <div v-else class="svg-container" v-html="svgContent" />
<!-- </Card> --> <!-- </Card> -->
</template> </template>
<style scoped> <style scoped>
/* 响应式调整 */
@media (max-width: 768px) {
.svg-container {
min-height: 300px;
}
}
::v-deep .ant-card-body { ::v-deep .ant-card-body {
padding: 10px; padding: 10px;
overflow: auto; overflow: auto;
} }
vg-container { .svg-container {
/* 响应式容器设置 */ display: flex;
position: relative; align-items: center;
width: 100%; justify-content: center;
height: 100%; width: 100%;
min-height: 400px; height: 100%;
overflow: visible; min-height: 300px;
cursor: pointer; cursor: pointer; /* 点击时可以手动触发数据更新 */
} }
.svg-container > svg {
box-sizing: border-box !important;
display: block !important;
/* 核心自适应设置 - 配合viewBox使用 */
width: 100% !important;
/* 确保SVG完全响应容器变化 */
max-width: none !important;
height: auto !important;
max-height: none !important;
padding: 0 !important;
/* 消除默认样式 */
margin: 0 !important;
/* 确保SVG内容完全可见 */ .svg-container > svg {
overflow: visible !important; max-width: 100%;
border: none !important; max-height: 100%;
} }
/* 确保所有父容器也支持响应式 */ svg{
:deep(.ant-card), width:initial;
:deep(.ant-card-body) { height:initial;
width: 100%; }
height: auto;
padding: 0;
margin: 0;
}
</style> </style>

5
src/views/dashboard/demo/index.vue

@ -1,8 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onBeforeMount, onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import WorkbenchHeader from './components/WorkbenchHeader.vue'
import ProjectCard from './components/ProjectCard.vue' import ProjectCard from './components/ProjectCard.vue'
import DynamicInfo from './components/DynamicInfo.vue' import DynamicInfo from './components/DynamicInfo.vue'
import StaticImg from './components/staticImg.vue' import StaticImg from './components/staticImg.vue'
@ -26,9 +25,7 @@ const id = route.path.split('/').pop() || ''
const data = ref<any>(data1) const data = ref<any>(data1)
onMounted(async () => { onMounted(async () => {
console.log(route.path)
const res = await getDeviceInfo(id) const res = await getDeviceInfo(id)
console.log(res)
if (res) if (res)
data.value = JSON.parse(res.Page_Content) data.value = JSON.parse(res.Page_Content)
// getColor(data.value) // getColor(data.value)

Loading…
Cancel
Save