|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 325 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 467 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 432 KiB |
|
After Width: | Height: | Size: 467 KiB |
@ -0,0 +1,49 @@ |
|||
<script lang="ts" setup> |
|||
import { ref, watch } from 'vue' |
|||
import { Card } from 'ant-design-vue' |
|||
import ExpandAltOutlined from '@ant-design/icons-vue/ExpandAltOutlined' |
|||
import Svg from './svg.vue' |
|||
|
|||
const props = defineProps({ |
|||
data: { |
|||
type: Object, |
|||
default: () => {}, |
|||
}, |
|||
}) |
|||
const path = ref<string>('') |
|||
|
|||
watch( |
|||
() => props.data, |
|||
(newValue, oldValue) => { |
|||
// 下方信息不会打印在控制台中 |
|||
console.log('a has changed', newValue, oldValue) |
|||
path.value = newValue.path |
|||
}, |
|||
) |
|||
|
|||
const svgRef: any = ref(null) |
|||
|
|||
function fullScreen() { |
|||
// 检查子组件实例是否已经挂载 |
|||
if (svgRef.value) { |
|||
// 通过 .value 访问子组件实例,并调用其暴露的方法 |
|||
svgRef.value.toggleFullscreen() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Card class="enter-y !my-1" :title="props.data.Header"> |
|||
<template #extra> |
|||
<ExpandAltOutlined style="cursor:pointer" @click="fullScreen" /> |
|||
</template> |
|||
<Svg ref="svgRef" :path="path" /> |
|||
</Card> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
::v-deep .ant-card-body { |
|||
padding: 5px; |
|||
height:calc(46vh - 60px); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,205 @@ |
|||
<script lang="ts" setup> |
|||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue' |
|||
import { getEXANowListReal } from '@/api/alert/exa' |
|||
|
|||
const props = defineProps<{ |
|||
path: string |
|||
default: '' |
|||
|
|||
}>() |
|||
|
|||
// const { data } = toRefs(props) |
|||
const svgContent = ref<string>('') |
|||
const svgError = ref<string>('') |
|||
const point = ref<{ itemNameArray: string[] }>({ itemNameArray: [] }) |
|||
// const pointElements = ref<NodeListOf<SVGElement> | undefined>() |
|||
// 监听数据变化 |
|||
watch( |
|||
() => props.path, |
|||
(newValue, oldValue) => { |
|||
// 下方信息不会打印在控制台中 |
|||
console.log('a has changed', newValue) |
|||
if (newValue !== oldValue && newValue !== '' && newValue != null) |
|||
loadSvg() |
|||
}, |
|||
{ immediate: true }, |
|||
|
|||
) |
|||
// 加载SVG文件 |
|||
async function loadSvg() { |
|||
console.log('加载SVG文件...') |
|||
if (!props.path) { |
|||
svgError.value = '未提供SVG路径' |
|||
return |
|||
} |
|||
|
|||
svgError.value = '' |
|||
try { |
|||
// 判断路径是否为完整URL |
|||
let url = props.path |
|||
if (!url.startsWith('http') && !url.startsWith('/')) { |
|||
// 如果是相对路径,尝试从assets加载 |
|||
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) |
|||
|
|||
if (!response.ok) |
|||
throw new Error(`无法加载SVG: ${response.statusText}`) |
|||
|
|||
svgContent.value = await response.text() |
|||
|
|||
// 等待DOM更新后应用实时数据 |
|||
await nextTick() |
|||
document.querySelector('.svg-container > svg')?.removeAttribute('width') |
|||
document.querySelector('.svg-container > svg')?.removeAttribute('height') |
|||
|
|||
// updateRealtimeValues() |
|||
} |
|||
catch (error) { |
|||
console.error('加载SVG失败:', error) |
|||
svgError.value = `加载失败: ${error instanceof Error ? error.message : String(error)}` |
|||
} |
|||
} |
|||
|
|||
// 更新SVG中的实时数据 |
|||
async function updateRealtimeValues() { |
|||
if (!svgContent.value) |
|||
return |
|||
try { |
|||
const svgElement = document.querySelector('.svg-container > svg') |
|||
svgElement?.removeAttribute('width') |
|||
svgElement?.removeAttribute('height') |
|||
// 获取SVG元素 |
|||
if (!svgElement) |
|||
return |
|||
|
|||
// 处理拥有point-name属性的元素 |
|||
const pointElements = svgElement.querySelectorAll('[point-name]') |
|||
const pointList: string[] = [] |
|||
|
|||
// 使用同步方式收集point-name |
|||
pointElements.forEach((element) => { |
|||
const pointName = element.getAttribute('point-name') |
|||
if (pointName) |
|||
pointList.push(pointName) |
|||
}) |
|||
|
|||
point.value = { itemNameArray: pointList } |
|||
const res = await getEXANowListReal(point.value) |
|||
|
|||
if (res && res.ReturnValue === 1 && res.ValueArray) { |
|||
const dataValues = res.ValueArray |
|||
|
|||
if (pointElements) { |
|||
pointElements.forEach((element, index) => { |
|||
// 不区分大小写比较标签名 |
|||
const value = dataValues[index] || '' |
|||
|
|||
// 设置文本内容,不再受标签名条件限制 |
|||
element.textContent = String(value) |
|||
|
|||
// 同时设置data-value属性便于调试和样式控制 |
|||
element.setAttribute('data-value', String(value)) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
catch (error) { |
|||
console.error('更新实时数据失败:', error) |
|||
} |
|||
} |
|||
|
|||
const updateInterval = ref<NodeJS.Timeout | null>(null) |
|||
|
|||
onMounted(() => { |
|||
// 设置定时器并保存引用,以便在组件卸载时清除 |
|||
updateInterval.value = setInterval(() => { |
|||
console.log('定时更新数据...') |
|||
updateRealtimeValues() |
|||
}, 1000) |
|||
}) |
|||
|
|||
onUnmounted(() => { |
|||
// 清理定时器等资源 |
|||
clearInterval(updateInterval.value!) |
|||
}) |
|||
|
|||
// 全屏方案 |
|||
function toggleFullscreen() { |
|||
const container = $refs.svgContainer |
|||
if (isFullscreen()) |
|||
exitFullscreen() |
|||
else |
|||
this.requestFullscreen(container) |
|||
} |
|||
// 检查当前是否处于全屏状态 |
|||
function isFullscreen() { |
|||
return document.fullscreenElement |
|||
|| document.mozFullScreenElement |
|||
|| document.webkitFullscreenElement |
|||
|| document.msFullscreenElement |
|||
} |
|||
// 打开全屏 |
|||
function requestFullscreen(element) { |
|||
if (element.requestFullscreen) { |
|||
element.requestFullscreen() |
|||
} |
|||
else if (element.mozRequestFullScreen) { // Firefox |
|||
element.mozRequestFullScreen() |
|||
} |
|||
else if (element.webkitRequestFullscreen) { // Chrome, Safari and Opera |
|||
element.webkitRequestFullscreen() |
|||
} |
|||
else if (element.msRequestFullscreen) { // IE/Edge |
|||
element.msRequestFullscreen() |
|||
} |
|||
} |
|||
// 关闭全屏 |
|||
function exitFullscreen() { |
|||
if (document.exitFullscreen) { |
|||
document.exitFullscreen() |
|||
} |
|||
else if (document.mozCancelFullScreen) { // Firefox |
|||
document.mozCancelFullScreen() |
|||
} |
|||
else if (document.webkitExitFullscreen) { // Chrome, Safari and Opera |
|||
document.webkitExitFullscreen() |
|||
} |
|||
else if (document.msExitFullscreen) { // IE/Edge |
|||
document.msExitFullscreen() |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<!-- <Card class="enter-y !my-1" :title="data.Header || 'SVG可视化'"> --> |
|||
<div v-if="svgError" class="h-40 flex items-center justify-center text-red-500"> |
|||
{{ svgError }} |
|||
</div> |
|||
<div v-else class="svg-container" 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%; |
|||
cursor: pointer; /* 点击时可以手动触发数据更新 */ |
|||
} |
|||
</style> |
|||
@ -0,0 +1,287 @@ |
|||
<script lang="ts" setup> |
|||
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue' |
|||
import { getEXANowListReal } from '@/api/alert/exa' |
|||
|
|||
const props = defineProps<{ |
|||
path: string |
|||
}>() |
|||
|
|||
// 定义SVG容器的引用 |
|||
const svgContainer = ref<HTMLElement>() |
|||
|
|||
// const { data } = toRefs(props) |
|||
const svgContent = ref<string>('') |
|||
const svgError = ref<string>('') |
|||
const point = ref<{ itemNameArray: string[] }>({ itemNameArray: [] }) |
|||
// const pointElements = ref<NodeListOf<SVGElement> | undefined>() |
|||
// 监听数据变化 |
|||
watch( |
|||
() => props.path, |
|||
(newValue, oldValue) => { |
|||
// 下方信息不会打印在控制台中 |
|||
console.log('a has changed', newValue) |
|||
if (newValue !== oldValue && newValue !== '' && newValue != null) |
|||
loadSvg() |
|||
}, |
|||
{ immediate: true }, |
|||
|
|||
) |
|||
// 加载SVG文件 |
|||
async function loadSvg() { |
|||
console.log('加载SVG文件...') |
|||
if (!props.path) { |
|||
svgError.value = '未提供SVG路径' |
|||
return |
|||
} |
|||
|
|||
svgError.value = '' |
|||
try { |
|||
// 判断路径是否为完整URL |
|||
let url = props.path |
|||
if (!url.startsWith('http') && !url.startsWith('/')) { |
|||
// 如果是相对路径,尝试从assets加载 |
|||
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) |
|||
|
|||
if (!response.ok) |
|||
throw new Error(`无法加载SVG: ${response.statusText}`) |
|||
|
|||
svgContent.value = await response.text() |
|||
|
|||
// 等待DOM更新后应用实时数据 |
|||
await nextTick() |
|||
document.querySelector('.svg-container > svg')?.setAttribute('width', '100%') |
|||
document.querySelector('.svg-container > svg')?.setAttribute('height', '100%') |
|||
document.querySelector('.svg-container > svg')?.setAttribute('preserveAspectRatio', 'none') |
|||
// updateRealtimeValues() |
|||
} |
|||
catch (error) { |
|||
console.error('加载SVG失败:', error) |
|||
svgError.value = `加载失败: ${error instanceof Error ? error.message : String(error)}` |
|||
} |
|||
} |
|||
|
|||
// 更新SVG中的实时数据 |
|||
async function updateRealtimeValues() { |
|||
if (!svgContent.value) |
|||
return |
|||
try { |
|||
const svgElement = document.querySelector('.svg-container > svg') |
|||
svgElement?.setAttribute('width', '100%') |
|||
svgElement?.setAttribute('height', '100%') |
|||
svgElement?.setAttribute('preserveAspectRatio', 'none') |
|||
|
|||
// 获取SVG元素 |
|||
if (!svgElement) |
|||
return |
|||
|
|||
// 处理拥有point-name属性的元素 |
|||
const pointElements = svgElement.querySelectorAll('[point-name]') |
|||
const pointList: string[] = [] |
|||
|
|||
// 使用同步方式收集point-name |
|||
pointElements.forEach((element) => { |
|||
const pointName = element.getAttribute('point-name') |
|||
if (pointName) |
|||
pointList.push(pointName) |
|||
}) |
|||
|
|||
point.value = { itemNameArray: pointList } |
|||
const res = await getEXANowListReal(point.value) |
|||
|
|||
if (res && res.ReturnValue === 1 && res.ValueArray) { |
|||
const dataValues = res.ValueArray |
|||
|
|||
if (pointElements) { |
|||
pointElements.forEach((element, index) => { |
|||
// 不区分大小写比较标签名 |
|||
const value = dataValues[index] || '' |
|||
|
|||
// 设置文本内容,不再受标签名条件限制 |
|||
element.textContent = String(value) |
|||
|
|||
// 同时设置data-value属性便于调试和样式控制 |
|||
element.setAttribute('data-value', String(value)) |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
catch (error) { |
|||
console.error('更新实时数据失败:', error) |
|||
} |
|||
} |
|||
|
|||
const updateInterval = ref<NodeJS.Timeout | null>(null) |
|||
|
|||
onMounted(() => { |
|||
// 设置定时器并保存引用,以便在组件卸载时清除 |
|||
updateInterval.value = setInterval(() => { |
|||
console.log('定时更新数据...') |
|||
updateRealtimeValues() |
|||
}, 1000) |
|||
}) |
|||
|
|||
onUnmounted(() => { |
|||
// 清理定时器等资源 |
|||
clearInterval(updateInterval.value!) |
|||
}) |
|||
|
|||
// 全屏方案 |
|||
function toggleFullscreen() { |
|||
const container = svgContainer.value |
|||
if (!container) |
|||
return |
|||
|
|||
if (isFullscreen()) |
|||
exitFullscreen() |
|||
else |
|||
requestFullscreen(container) |
|||
} |
|||
// 检查当前是否处于全屏状态 |
|||
function isFullscreen() { |
|||
return document.fullscreenElement |
|||
|| document.mozFullScreenElement |
|||
|| document.webkitFullscreenElement |
|||
|| document.msFullscreenElement |
|||
} |
|||
// 打开全屏 |
|||
function requestFullscreen(element) { |
|||
if (element.requestFullscreen) { |
|||
element.requestFullscreen() |
|||
} |
|||
else if (element.mozRequestFullScreen) { // Firefox |
|||
element.mozRequestFullScreen() |
|||
} |
|||
else if (element.webkitRequestFullscreen) { // Chrome, Safari and Opera |
|||
element.webkitRequestFullscreen() |
|||
} |
|||
else if (element.msRequestFullscreen) { // IE/Edge |
|||
element.msRequestFullscreen() |
|||
} |
|||
} |
|||
// 关闭全屏 |
|||
function exitFullscreen() { |
|||
if (document.exitFullscreen) { |
|||
document.exitFullscreen() |
|||
} |
|||
else if (document.mozCancelFullScreen) { // Firefox |
|||
document.mozCancelFullScreen() |
|||
} |
|||
else if (document.webkitExitFullscreen) { // Chrome, Safari and Opera |
|||
document.webkitExitFullscreen() |
|||
} |
|||
else if (document.msExitFullscreen) { // IE/Edge |
|||
document.msExitFullscreen() |
|||
} |
|||
} |
|||
|
|||
// 2. 使用 defineExpose 将 greet 方法暴露给父组件 |
|||
defineExpose({ |
|||
toggleFullscreen, |
|||
}) |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="svg-wrapper"> |
|||
<!-- 控制栏 |
|||
<div class="svg-controls"> |
|||
<h3 class="svg-title"> |
|||
SVG可视化 |
|||
</h3> |
|||
<button class="fullscreen-btn" title="切换全屏" @click="toggleFullscreen"> |
|||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> |
|||
<path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" /> |
|||
</svg> |
|||
</button> |
|||
</div> --> |
|||
|
|||
<!-- SVG内容 --> |
|||
<div v-if="svgError" class="h-40 flex items-center justify-center text-red-500"> |
|||
{{ svgError }} |
|||
</div> |
|||
<div v-else ref="svgContainer" class="svg-container" v-html="svgContent" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped> |
|||
.svg-wrapper { |
|||
width:100%; |
|||
height:100%; |
|||
border: 1px solid #e8e8e8; |
|||
border-radius: 6px; |
|||
/* overflow: auto; */ |
|||
|
|||
} |
|||
|
|||
/* .svg-controls { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
padding: 10px 16px; |
|||
background-color: #f5f5f5; |
|||
border-bottom: 1px solid #e8e8e8; |
|||
} |
|||
|
|||
.svg-title { |
|||
margin: 0; |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
color: #333; |
|||
} */ |
|||
|
|||
.svg-container { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 100%; |
|||
height:100%; |
|||
min-height: 300px; |
|||
overflow: auto; |
|||
} |
|||
|
|||
/* 全屏状态样式 */ |
|||
:global(.svg-wrapper:fullscreen), |
|||
:global(.svg-wrapper:-webkit-full-screen), |
|||
:global(.svg-wrapper:-moz-full-screen), |
|||
:global(.svg-wrapper:-ms-fullscreen) { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
border: none; |
|||
border-radius: 0; |
|||
} |
|||
|
|||
:global(.svg-wrapper:fullscreen) .svg-container, |
|||
:global(.svg-wrapper:-webkit-full-screen) .svg-container, |
|||
:global(.svg-wrapper:-moz-full-screen) .svg-container, |
|||
:global(.svg-wrapper:-ms-fullscreen) .svg-container { |
|||
flex: 1; |
|||
padding: 20px; |
|||
width:100%; |
|||
height:100%; |
|||
} |
|||
|
|||
/* SVG内容自适应 */ |
|||
.svg-container { |
|||
/* max-width: 100%; |
|||
max-height: 100%; */ |
|||
height:100%; |
|||
width:100% |
|||
|
|||
} |
|||
|
|||
svg { |
|||
height:98%; |
|||
width:100%; |
|||
} |
|||
</style> |
|||
@ -1,68 +0,0 @@ |
|||
<script lang="ts" setup> |
|||
import { onMounted, ref } from 'vue' |
|||
import WorkbenchHeader from './components/WorkbenchHeader.vue' |
|||
import ProjectCard from './components/ProjectCard.vue' |
|||
import DynamicInfo from './components/DynamicInfo.vue' |
|||
import StaticImg from './components/staticImg.vue' |
|||
import ParameterInfo from './components/ParameterInfo.vue' |
|||
import Operation from './components/Operation.vue' |
|||
import data from './components/demo.json' |
|||
import { PageWrapper } from '@/components/Page' |
|||
|
|||
const loading = ref(true) |
|||
|
|||
onMounted(() => { |
|||
const htmlRoot = document.getElementsByClassName('workbench') |
|||
// let theme = window.localStorage.getItem('__APP__DARK__MODE__') |
|||
const theme = 'dark' |
|||
|
|||
if (htmlRoot && theme) { |
|||
for (let i = 0; i < htmlRoot.length; i++) |
|||
htmlRoot[i].setAttribute('data-theme', theme) |
|||
} |
|||
// theme = htmlRoot = null |
|||
}) |
|||
|
|||
setTimeout(() => { |
|||
loading.value = false |
|||
}, 500) |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="workbench m-1" data-theme="light"> |
|||
<WorkbenchHeader class="workbench m-1" data-theme="light" :data="data" /> |
|||
<div class="enter-y workbench h-92 md:flex" data-theme="light"> |
|||
<StaticImg data-theme="light" class="workbench m-1 w-full md:w-1/4" :loading="loading" :data="data.LeftUp" /> |
|||
<ProjectCard data-theme="light" class="enter-y workbench m-1 w-full md:w-1/2" :loading="loading" :data="data.LeftMidUp" /> |
|||
<div class="enter-y w-full !mr-4 md:w-1/4"> |
|||
<DynamicInfo :loading="loading" data-theme="light" class="workbench enter-y m-1 h-35 w-full" :data="data.RightMidUp" /> |
|||
<Operation data-theme="light" class="workbench enter-y m-1 h-54 w-full" :loading="loading" :data="data.RightUp" /> |
|||
</div> |
|||
</div> |
|||
<div class="enter-y workbench h-95 md:flex" data-theme="light"> |
|||
<StaticImg data-theme="light" class="workbench m-1 w-full md:w-1/2" :loading="loading" :data="data.LeftDown" /> |
|||
<ParameterInfo data-theme="light" class="workbench m-1 w-full md:w-1/2" :loading="loading" :data="data.RightDown" /> |
|||
</div> |
|||
<!-- <div class="enter-y w-full !mr-4 lg:w-7/10"> |
|||
<StaticImg :loading="loading" :data="data.LeftUp" class="enter-y" /> |
|||
</div> --> |
|||
|
|||
<!-- <div class="enter-y w-full !mr-4 lg:w-7/10"> |
|||
<ProjectCard :loading="loading" class="enter-y" /> |
|||
<DynamicInfo :loading="loading" class="enter-y !my-4" /> |
|||
</div> |
|||
<div class="enter-y w-full lg:w-3/10"> |
|||
<QuickNav :loading="loading" class="enter-y" /> |
|||
|
|||
<Card class="enter-y !my-4" :loading="loading"> |
|||
<img class="mx-auto h-30 xl:h-50" src="@/assets/svg/illustration.svg"> |
|||
</Card> |
|||
|
|||
<SaleRadar :loading="loading" class="enter-y" /> |
|||
</div> --> |
|||
</div> |
|||
</template> |
|||
|
|||
<style lang="less"> |
|||
|
|||
</style> |
|||