Browse Source

fix:svg修改为v-html加载

dev-xjf
肖晋飞 1 month ago
parent
commit
f5b438bb5f
  1. 58
      public/example-meter.svg
  2. 2
      public/illustration.svg
  3. 1
      public/illustration1.svg
  4. 1
      public/test.svg
  5. 1
      public/送风机.svg
  6. 58
      src/assets/example-meter.svg
  7. 1
      src/assets/test.svg
  8. 1
      src/assets/送风机.svg
  9. 9
      src/views/dashboard/demo/components/staticImg.vue
  10. 135
      src/views/dashboard/demo/components/staticSvg.vue
  11. 225
      src/views/dashboard/demo/components/svg.vue
  12. 4
      src/views/dashboard/demo/index.vue

58
public/example-meter.svg

@ -0,0 +1,58 @@
<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
<rect width="400" height="300" fill="#f5f5f5" rx="10" ry="10"/>
<!-- 仪表盘背景 -->
<circle cx="200" cy="150" r="100" fill="white" stroke="#e8e8e8" stroke-width="2"/>
<circle cx="200" cy="150" r="90" fill="#fafafa"/>
<!-- 刻度 -->
<g id="scale">
<line x1="200" y1="60" x2="200" y2="70" stroke="#333" stroke-width="2"/>
<line x1="265" y1="85" x2="275" y2="95" stroke="#333" stroke-width="2"/>
<line x1="285" y1="150" x2="275" y2="150" stroke="#333" stroke-width="2"/>
<line x1="265" y1="215" x2="275" y2="205" stroke="#333" stroke-width="2"/>
<line x1="200" y1="240" x2="200" y2="230" stroke="#333" stroke-width="2"/>
<line x1="135" y1="215" x2="125" y2="205" stroke="#333" stroke-width="2"/>
<line x1="115" y1="150" x2="125" y2="150" stroke="#333" stroke-width="2"/>
<line x1="135" y1="85" x2="125" y2="95" stroke="#333" stroke-width="2"/>
</g>
<!-- 刻度标签 -->
<text x="200" y="50" text-anchor="middle" font-size="14" fill="#666">0</text>
<text x="290" y="90" text-anchor="middle" font-size="14" fill="#666">45</text>
<text x="290" y="155" text-anchor="middle" font-size="14" fill="#666">90</text>
<text x="290" y="215" text-anchor="middle" font-size="14" fill="#666">135</text>
<text x="200" y="260" text-anchor="middle" font-size="14" fill="#666">180</text>
<text x="110" y="215" text-anchor="middle" font-size="14" fill="#666">225</text>
<text x="110" y="155" text-anchor="middle" font-size="14" fill="#666">270</text>
<text x="110" y="90" text-anchor="middle" font-size="14" fill="#666">315</text>
<!-- 指针 -->
<g id="pointer-group">
<circle cx="200" cy="150" r="8" fill="#1890ff"/>
<line id="pointer" x1="200" y1="150" x2="200" y2="70" stroke="#1890ff" stroke-width="3" stroke-linecap="round" transform="rotate(0 200 150)"/>
<circle cx="200" cy="150" r="4" fill="#fff"/>
</g>
<!-- 实时数据显示区域 -->
<rect x="100" y="220" width="200" height="40" rx="5" ry="5" fill="#e6f7ff" stroke="#91d5ff" stroke-width="1"/>
<text x="120" y="245" font-size="16" fill="#333">当前值:</text>
<text id="current-value" x="200" y="245" text-anchor="middle" font-size="18" font-weight="bold" fill="#1890ff">0</text>
<text x="260" y="245" font-size="16" fill="#333">%</text>
<!-- 阈值指示器 -->
<g id="thresholds">
<rect x="50" y="270" width="30" height="20" rx="3" ry="3" fill="#52c41a"/>
<text x="65" y="284" text-anchor="middle" font-size="12" fill="white">正常</text>
<rect x="110" y="270" width="30" height="20" rx="3" ry="3" fill="#faad14"/>
<text x="125" y="284" text-anchor="middle" font-size="12" fill="white">警告</text>
<rect x="170" y="270" width="30" height="20" rx="3" ry="3" fill="#ff4d4f"/>
<text x="185" y="284" text-anchor="middle" font-size="12" fill="white">危险</text>
<!-- 当前状态指示器 -->
<rect id="status-indicator" x="250" y="270" width="100" height="20" rx="3" ry="3" fill="#52c41a"/>
<text id="status-text" x="300" y="284" text-anchor="middle" font-size="12" fill="white">状态: 正常</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

2
public/illustration.svg

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 70 KiB

1
public/illustration1.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

1
public/test.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 432 KiB

1
public/送风机.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 467 KiB

58
src/assets/example-meter.svg

@ -0,0 +1,58 @@
<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
<rect width="400" height="300" fill="#f5f5f5" rx="10" ry="10"/>
<!-- 仪表盘背景 -->
<circle cx="200" cy="150" r="100" fill="white" stroke="#e8e8e8" stroke-width="2"/>
<circle cx="200" cy="150" r="90" fill="#fafafa"/>
<!-- 刻度 -->
<g id="scale">
<line x1="200" y1="60" x2="200" y2="70" stroke="#333" stroke-width="2"/>
<line x1="265" y1="85" x2="275" y2="95" stroke="#333" stroke-width="2"/>
<line x1="285" y1="150" x2="275" y2="150" stroke="#333" stroke-width="2"/>
<line x1="265" y1="215" x2="275" y2="205" stroke="#333" stroke-width="2"/>
<line x1="200" y1="240" x2="200" y2="230" stroke="#333" stroke-width="2"/>
<line x1="135" y1="215" x2="125" y2="205" stroke="#333" stroke-width="2"/>
<line x1="115" y1="150" x2="125" y2="150" stroke="#333" stroke-width="2"/>
<line x1="135" y1="85" x2="125" y2="95" stroke="#333" stroke-width="2"/>
</g>
<!-- 刻度标签 -->
<text x="200" y="50" text-anchor="middle" font-size="14" fill="#666">0</text>
<text x="290" y="90" text-anchor="middle" font-size="14" fill="#666">45</text>
<text x="290" y="155" text-anchor="middle" font-size="14" fill="#666">90</text>
<text x="290" y="215" text-anchor="middle" font-size="14" fill="#666">135</text>
<text x="200" y="260" text-anchor="middle" font-size="14" fill="#666">180</text>
<text x="110" y="215" text-anchor="middle" font-size="14" fill="#666">225</text>
<text x="110" y="155" text-anchor="middle" font-size="14" fill="#666">270</text>
<text x="110" y="90" text-anchor="middle" font-size="14" fill="#666">315</text>
<!-- 指针 -->
<g id="pointer-group">
<circle cx="200" cy="150" r="8" fill="#1890ff"/>
<line id="pointer" x1="200" y1="150" x2="200" y2="70" stroke="#1890ff" stroke-width="3" stroke-linecap="round" transform="rotate(0 200 150)"/>
<circle cx="200" cy="150" r="4" fill="#fff"/>
</g>
<!-- 实时数据显示区域 -->
<rect x="100" y="220" width="200" height="40" rx="5" ry="5" fill="#e6f7ff" stroke="#91d5ff" stroke-width="1"/>
<text x="120" y="245" font-size="16" fill="#333">当前值:</text>
<text id="current-value" x="200" y="245" text-anchor="middle" font-size="18" font-weight="bold" fill="#1890ff">0</text>
<text x="260" y="245" font-size="16" fill="#333">%</text>
<!-- 阈值指示器 -->
<g id="thresholds">
<rect x="50" y="270" width="30" height="20" rx="3" ry="3" fill="#52c41a"/>
<text x="65" y="284" text-anchor="middle" font-size="12" fill="white">正常</text>
<rect x="110" y="270" width="30" height="20" rx="3" ry="3" fill="#faad14"/>
<text x="125" y="284" text-anchor="middle" font-size="12" fill="white">警告</text>
<rect x="170" y="270" width="30" height="20" rx="3" ry="3" fill="#ff4d4f"/>
<text x="185" y="284" text-anchor="middle" font-size="12" fill="white">危险</text>
<!-- 当前状态指示器 -->
<rect id="status-indicator" x="250" y="270" width="100" height="20" rx="3" ry="3" fill="#52c41a"/>
<text id="status-text" x="300" y="284" text-anchor="middle" font-size="12" fill="white">状态: 正常</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

1
src/assets/test.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 432 KiB

1
src/assets/送风机.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 467 KiB

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

@ -1,3 +1,4 @@
<!-- 这个文件是直接展示svg,不处理svg中数据 -->
<script lang="ts" setup> <script lang="ts" setup>
import { Card, CardGrid } from 'ant-design-vue' import { Card, CardGrid } from 'ant-design-vue'
import { ref, toRefs, watch } from 'vue' import { ref, toRefs, watch } from 'vue'
@ -31,6 +32,12 @@ function getAssetsFile() {
<template> <template>
<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-70 w-full" :src="props.data.path"> <img class="mx-auto h-full w-full" :src="props.data.path">
</Card> </Card>
</template> </template>
<style scoped>
::v-deep .ant-card-body {
padding: 10px;
}
</style>

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

@ -0,0 +1,135 @@
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { Card } from 'ant-design-vue'
import Svg from './svg.vue'
const props = defineProps({
data: {
type: Object,
default: () => {},
},
})
//
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(
() => props.data,
(newValue, oldValue) => {
//
console.log('a has changed', newValue, oldValue)
realtimeData.value.path = props.data.path
},
)
onUnmounted(() => {
//
if (updateTimer)
clearInterval(updateTimer)
})
</script>
<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">
<!-- <img class="mx-auto h-full w-full" :src="props.data.path"> -->
<Svg class="h-full w-full" :data="realtimeData" />
</Card>
</template>
<style scoped>
::v-deep .ant-card-body {
padding: 10px;
}
</style>

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

@ -0,0 +1,225 @@
<script lang="ts" setup>
import { Card } from 'ant-design-vue'
import { nextTick, onMounted, ref, toRefs, watch } from 'vue'
interface SvgData {
path: string // SVG
Header?: string //
realtimeValues?: Record<string, any> //
valueMapping?: Record<string, string> // SVGID
}
const props = defineProps<{
data: SvgData
}>()
const { data } = toRefs(props)
const svgContent = ref<string>('')
const isLoading = ref<boolean>(true)
const svgError = ref<string>('')
// SVG
async function loadSvg() {
if (!data.value.path) {
svgError.value = '未提供SVG路径'
isLoading.value = false
return
}
try {
isLoading.value = true
svgError.value = ''
// URL
let url = data.value.path
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()
// DOM
await nextTick()
// SVGviewBox
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) {
console.error('加载SVG失败:', error)
svgError.value = `加载失败: ${error instanceof Error ? error.message : String(error)}`
}
finally {
isLoading.value = false
}
}
// SVG
function updateRealtimeValues() {
if (!svgContent.value || !data.value.realtimeValues)
return
try {
// SVG
const svgElement = document.querySelector('.svg-container > svg')
console.log(svgElement)
if (!svgElement)
return
const { realtimeValues, valueMapping = {} } = data.value
// SVG
Object.entries(valueMapping).forEach(([svgElementId, dataField]) => {
console.log([svgElementId, dataField])
const element = svgElement.getElementById(svgElementId)
console.log(element)
if (element && realtimeValues[dataField] !== undefined) {
//
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
if (Object.keys(valueMapping).length === 0) {
Object.entries(realtimeValues).forEach(([field, value]) => {
const element = svgElement.getElementById(field)
if (element) {
if (element.tagName === 'text' || element.tagName === 'tspan')
element.textContent = String(value)
}
})
}
}
catch (error) {
console.error('更新实时数据失败:', error)
}
}
//
watch(
() => data.value.path,
() => {
loadSvg()
},
{ immediate: true },
)
//
watch(
() => data.value.realtimeValues,
() => {
updateRealtimeValues()
},
{ deep: true },
)
onMounted(() => {
loadSvg()
})
</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>
/* 响应式调整 */
@media (max-width: 768px) {
.svg-container {
min-height: 300px;
}
}
::v-deep .ant-card-body {
padding: 10px;
overflow: auto;
}
vg-container {
/* 响应式容器设置 */
position: relative;
width: 100%;
height: 100%;
min-height: 400px;
overflow: visible;
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内容完全可见 */
overflow: visible !important;
border: none !important;
}
/* 确保所有父容器也支持响应式 */
:deep(.ant-card),
:deep(.ant-card-body) {
width: 100%;
height: auto;
padding: 0;
margin: 0;
}
</style>

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

@ -6,6 +6,8 @@ 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'
import StaticSvg from './components/staticSvg.vue'
import ParameterInfo from './components/ParameterInfo.vue' import ParameterInfo from './components/ParameterInfo.vue'
import Operation from './components/Operation.vue' import Operation from './components/Operation.vue'
import data1 from './components/demo.json' import data1 from './components/demo.json'
@ -70,7 +72,7 @@ onMounted(async () => {
</div> </div>
</div> </div>
<div class="enter-y h-[calc(91vh/2)] md:flex"> <div class="enter-y h-[calc(91vh/2)] md:flex">
<StaticImg class="m-1 w-full md:w-1/2" :loading="loading" :data="data.LeftDown || {}" /> <StaticSvg class="m-1 w-full md:w-1/2" :loading="loading" :data="data.LeftDown || {}" />
<ParameterInfo class="m-1 w-full md:w-1/2" :loading="loading" :data="data.RightDown || {}" /> <ParameterInfo class="m-1 w-full md:w-1/2" :loading="loading" :data="data.RightDown || {}" />
</div> </div>
<!-- <div class="enter-y w-full !mr-4 lg:w-7/10"> <!-- <div class="enter-y w-full !mr-4 lg:w-7/10">

Loading…
Cancel
Save