feat(数据大屏): 内置动态装饰

This commit is contained in:
wangjiahao
2025-07-08 18:37:10 +08:00
committed by 王嘉豪
parent ea7501b405
commit fef1a4b564
8 changed files with 791 additions and 1 deletions

View File

@@ -79,6 +79,36 @@ export const CANVAS_MATERIAL = [
title: t('visualization.decoration'),
span: 8,
details: [
{
value: 'DeDecoration1',
type: 'de_decoration',
title: t('visualization.decoration_name', [11]),
icon: 'DeDecoration1'
},
{
value: 'DeDecoration2',
type: 'de_decoration',
title: t('visualization.decoration_name', [12]),
icon: 'DeDecoration2'
},
{
value: 'DeDecoration3',
type: 'de_decoration',
title: t('visualization.decoration_name', [12]),
icon: 'DeDecoration3'
},
{
value: 'DeDecoration4',
type: 'de_decoration',
title: t('visualization.decoration_name', [12]),
icon: 'DeDecoration4'
},
{
value: 'DeDecoration5',
type: 'de_decoration',
title: t('visualization.decoration_name', [12]),
icon: 'DeDecoration5'
},
{
value: 'DeBoard1',
type: 'de_decoration',

View File

@@ -501,6 +501,10 @@ const list = [
style: {
width: 400,
height: 300,
color0: null,
color1: null,
color2: null,
dur: 6,
borderActive: false,
backdropFilter: 'blur(0px)'
}

View File

@@ -0,0 +1,197 @@
<template>
<div class="dv-decoration-1" :style="border_style" :ref="refName">
<svg
:width="`${svgWH[0]}px`"
:height="`${svgWH[1]}px`"
:style="`transform:scale(${svgScale[0]},${svgScale[1]});`"
>
<template v-for="(point, i) in points" :key="i">
<rect
v-if="Math.random() > 0.6"
:fill="mergedColor[0]"
:x="point[0] - halfPointSideLength"
:y="point[1] - halfPointSideLength"
:width="pointSideLength"
:height="pointSideLength"
>
<animate
v-if="Math.random() > 0.6"
attributeName="fill"
:values="`${mergedColor[0]};transparent`"
dur="1s"
:begin="Math.random() * 2"
repeatCount="indefinite"
/>
</rect>
</template>
<rect
v-if="rects[0]"
:fill="mergedColor[1]"
:x="rects[0][0] - pointSideLength"
:y="rects[0][1] - pointSideLength"
:width="pointSideLength * 2"
:height="pointSideLength * 2"
>
<animate
attributeName="width"
:values="`0;${pointSideLength * 2}`"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="height"
:values="`0;${pointSideLength * 2}`"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="x"
:values="`${rects[0][0]};${rects[0][0] - pointSideLength}`"
dur="2s"
repeatCount="indefinite"
/>
<animate
attributeName="y"
:values="`${rects[0][1]};${rects[0][1] - pointSideLength}`"
dur="2s"
repeatCount="indefinite"
/>
</rect>
<rect
v-if="rects[1]"
:fill="mergedColor[1]"
:x="rects[1][0] - 40"
:y="rects[1][1] - pointSideLength"
:width="40"
:height="pointSideLength * 2"
>
<animate attributeName="width" values="0;40;0" dur="2s" repeatCount="indefinite" />
<animate
attributeName="x"
:values="`${rects[1][0]};${rects[1][0] - 40};${rects[1][0]}`"
dur="2s"
repeatCount="indefinite"
/>
</rect>
</svg>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { cloneDeep, merge } from 'lodash-es'
interface Props {
color?: string[]
curStyle: object
scale: number
}
const props = withDefaults(defineProps<Props>(), {
color: () => [],
curStyle: () => {
return {
width: '320px',
height: '240px'
}
}
})
const width = computed(() => {
return parseInt(props.curStyle.width) / props.scale
})
const height = computed(() => {
return parseInt(props.curStyle.height) / props.scale
})
const border_style = computed(() => {
return {
width: `${width.value}px`,
height: `${height.value}px`,
zoom: props.scale
}
})
const pointSideLength = 2.5
const refName = ref('decoration-1')
const svgWH = ref([200, 50])
const svgScale = ref([1, 1])
const rowNum = 4
const rowPoints = 20
const halfPointSideLength = computed(() => pointSideLength / 2)
const points = ref<number[][]>([])
const rects = ref<number[][]>([])
const defaultColor = ref(['#fff', '#0de7c2'])
const mergedColor = ref<string[]>([])
const calcPointsPosition = () => {
const [w, h] = svgWH.value
const horizontalGap = w / (rowPoints + 1)
const verticalGap = h / (rowNum + 1)
let pointsArray = new Array(rowNum)
.fill(0)
.map((_, i) =>
new Array(rowPoints).fill(0).map((_, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)])
)
points.value = pointsArray.reduce((all, item) => [...all, ...item], [])
}
const calcRectsPosition = () => {
const rect1 = points.value[rowPoints * 2 - 1]
const rect2 = points.value[rowPoints * 2 - 3]
rects.value = [rect1, rect2]
}
const calcScale = () => {
const [w, h] = svgWH.value
svgScale.value = [width.value / w, height.value / h]
}
const calcSVGData = () => {
calcPointsPosition()
calcRectsPosition()
calcScale()
}
const mergeColor = () => {
mergedColor.value = merge(cloneDeep(defaultColor.value), props.color || []) as string[]
}
const onResize = () => {
calcSVGData()
}
watch(
() => props.color,
() => {
mergeColor()
}
)
onMounted(() => {
mergeColor()
calcSVGData()
})
// Handle autoResize mixin
// Note: In Vue 3, consider converting autoResize to a composable
watch([width, height], () => {
onResize()
})
</script>
<style lang="less">
.dv-decoration-1 {
width: 100%;
height: 100%;
svg {
transform-origin: left top;
}
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div class="dv-decoration-2" :style="border_style" :ref="refName">
<svg :width="`${width}px`" :height="`${height}px`">
<rect :x="x" :y="y" :width="w" :height="h" :fill="mergedColor[0]">
<animate
:attributeName="reverse ? 'height' : 'width'"
from="0"
:to="reverse ? height : width"
:dur="`${dur}s`"
calcMode="spline"
keyTimes="0;1"
keySplines=".42,0,.58,1"
repeatCount="indefinite"
/>
</rect>
<rect :x="x" :y="y" width="1" height="1" :fill="mergedColor[1]">
<animate
:attributeName="reverse ? 'y' : 'x'"
from="0"
:to="reverse ? height : width"
:dur="`${dur}s`"
calcMode="spline"
keyTimes="0;1"
keySplines="0.42,0,0.58,1"
repeatCount="indefinite"
/>
</rect>
</svg>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { cloneDeep, merge } from 'lodash-es'
interface Props {
color?: string[]
curStyle: object
scale: number
reverse?: boolean
dur?: number
}
const props = withDefaults(defineProps<Props>(), {
color: () => [],
curStyle: () => {
return {
width: '320px',
height: '240px'
}
},
reverse: false,
dur: 6
})
const refName = ref('decoration-2')
const x = ref(0)
const y = ref(0)
const w = ref(0)
const h = ref(0)
const defaultColor = ref(['#3faacb', '#fff'])
const mergedColor = ref<string[]>([])
const width = computed(() => {
return parseInt(props.curStyle.width) / props.scale
})
const height = computed(() => {
return parseInt(props.curStyle.height) / props.scale
})
const border_style = computed(() => {
return {
width: `${width.value}px`,
height: `${height.value}px`,
zoom: props.scale
}
})
const calcSVGData = () => {
if (props.reverse) {
w.value = 1
h.value = height.value
x.value = width.value / 2
y.value = 0
} else {
w.value = width.value
h.value = 1
x.value = 0
y.value = height.value / 2
}
}
const mergeColor = () => {
mergedColor.value = merge(cloneDeep(defaultColor.value), props.color || []) as string[]
}
// Watchers
watch(
() => props.color,
() => {
mergeColor()
}
)
watch(
() => props.reverse,
() => {
calcSVGData()
}
)
watch([width, height], () => {
calcSVGData()
})
// Lifecycle hooks
onMounted(() => {
mergeColor()
calcSVGData()
})
</script>
<style lang="less">
.dv-decoration-2 {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div class="dv-decoration-3" :style="border_style" :ref="refName">
<svg
:width="`${svgWH[0]}px`"
:height="`${svgWH[1]}px`"
:style="`transform:scale(${svgScale[0]},${svgScale[1]});`"
>
<rect
v-for="(point, i) in points"
:key="i"
:fill="mergedColor[0]"
:x="point[0] - halfPointSideLength"
:y="point[1] - halfPointSideLength"
:width="pointSideLength"
:height="pointSideLength"
>
<animate
v-if="Math.random() > 0.6"
attributeName="fill"
:values="mergedColor.join(';')"
:dur="`${Math.random() + 1}s`"
:begin="`${Math.random() * 2}s`"
repeatCount="indefinite"
/>
</rect>
</svg>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { cloneDeep, merge } from 'lodash-es'
interface Props {
color?: string[]
curStyle: object
scale: number
}
const props = withDefaults(defineProps<Props>(), {
color: () => [],
curStyle: () => {
return {
width: '320px',
height: '240px'
}
}
})
const width = computed(() => {
return parseInt(props.curStyle.width) / props.scale
})
const height = computed(() => {
return parseInt(props.curStyle.height) / props.scale
})
const border_style = computed(() => {
return {
width: `${width.value}px`,
height: `${height.value}px`,
zoom: props.scale
}
})
const mergeColor = () => {
mergedColor.value = merge(cloneDeep(defaultColor.value), props.color || []) as string[]
}
// Constants
const pointSideLength = 7
const rowNum = 2
const rowPoints = 25
// Refs
const refName = ref('decoration-3')
const svgWH = ref([300, 35])
const svgScale = ref([1, 1])
const points = ref<number[][]>([])
const defaultColor = ref(['#7acaec', 'transparent'])
const mergedColor = ref<string[]>([])
// Computed
const halfPointSideLength = computed(() => pointSideLength / 2)
// Methods
const calcPointsPosition = () => {
const [w, h] = svgWH.value
const horizontalGap = w / (rowPoints + 1)
const verticalGap = h / (rowNum + 1)
const pointsArray = Array.from({ length: rowNum }, (_, i) =>
Array.from({ length: rowPoints }, (_, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)])
)
points.value = pointsArray.flat()
}
const calcScale = () => {
const [w, h] = svgWH.value
svgScale.value = [width.value / w, height.value / h]
}
const calcSVGData = () => {
calcPointsPosition()
calcScale()
}
const onResize = () => {
calcSVGData()
}
// Watchers
watch(() => props.color, mergeColor)
watch([width, height], onResize)
// Lifecycle
onMounted(() => {
mergeColor()
calcSVGData()
})
</script>
<style lang="less">
.dv-decoration-3 {
width: 100%;
height: 100%;
svg {
transform-origin: left top;
}
}
</style>

View File

@@ -0,0 +1,136 @@
<template>
<div class="dv-decoration-4" :style="border_style" :ref="refName">
<div :class="['container', reverse ? 'reverse' : 'normal']" :style="containerStyle">
<svg :width="reverse ? width : 5" :height="reverse ? 5 : height">
<polyline
:stroke="mergedColor[0]"
:points="reverse ? `0, 2.5 ${width}, 2.5` : `2.5, 0 2.5, ${height}`"
/>
<polyline
class="bold-line"
:stroke="mergedColor[1]"
stroke-width="3"
stroke-dasharray="20, 80"
stroke-dashoffset="-30"
:points="reverse ? `0, 2.5 ${width}, 2.5` : `2.5, 0 2.5, ${height}`"
/>
</svg>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { cloneDeep, merge } from 'lodash-es'
interface Props {
color?: string[]
curStyle: object
scale: number
reverse?: boolean
dur?: number
}
const props = withDefaults(defineProps<Props>(), {
color: () => [],
curStyle: () => {
return {
width: '320px',
height: '240px'
}
},
reverse: false,
dur: 6
})
const refName = ref('decoration-4')
const defaultColor = ref(['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.3)'])
const mergedColor = ref<string[]>([])
const width = computed(() => {
return parseInt(props.curStyle.width) / props.scale
})
const height = computed(() => {
return parseInt(props.curStyle.height) / props.scale
})
const border_style = computed(() => {
return {
width: `${width.value}px`,
height: `${height.value}px`,
zoom: props.scale
}
})
const mergeColor = () => {
mergedColor.value = merge(cloneDeep(defaultColor.value), props.color || []) as string[]
}
// Computed properties
const containerStyle = computed(() => ({
width: props.reverse ? `${width.value}px` : '5px',
height: props.reverse ? '5px' : `${height.value}px`,
animationDuration: `${props.dur}s`
}))
// Watchers
watch(() => props.color, mergeColor)
// Lifecycle
onMounted(mergeColor)
</script>
<style lang="less">
.dv-decoration-4 {
position: relative;
width: 100%;
height: 100%;
.container {
display: flex;
overflow: hidden;
position: absolute;
flex: 1;
&.normal {
animation: ani-height ease-in-out infinite;
left: 50%;
margin-left: -2px;
}
&.reverse {
animation: ani-width ease-in-out infinite;
top: 50%;
margin-top: -2px;
}
}
@keyframes ani-height {
0% {
height: 0%;
}
70% {
height: 100%;
}
100% {
height: 100%;
}
}
@keyframes ani-width {
0% {
width: 0%;
}
70% {
width: 100%;
}
100% {
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<div class="dv-decoration-5" :style="border_style" :ref="refName">
<svg :width="width" :height="height">
<polyline fill="transparent" :stroke="mergedColor[0]" stroke-width="3" :points="line1Points">
<animate
attributeName="stroke-dasharray"
attributeType="XML"
:from="`0, ${line1Length / 2}, 0, ${line1Length / 2}`"
:to="`0, 0, ${line1Length}, 0`"
:dur="`${dur}s`"
begin="0s"
calcMode="spline"
keyTimes="0;1"
keySplines="0.4,1,0.49,0.98"
repeatCount="indefinite"
/>
</polyline>
<polyline fill="transparent" :stroke="mergedColor[1]" stroke-width="2" :points="line2Points">
<animate
attributeName="stroke-dasharray"
attributeType="XML"
:from="`0, ${line2Length / 2}, 0, ${line2Length / 2}`"
:to="`0, 0, ${line2Length}, 0`"
:dur="`${dur}s`"
begin="0s"
calcMode="spline"
keyTimes="0;1"
keySplines=".4,1,.49,.98"
repeatCount="indefinite"
/>
</polyline>
</svg>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from 'vue'
import { getPointDistances } from '@/custom-component/de-decoration/component_details/config'
import { cloneDeep, merge, sum } from 'lodash-es'
interface Props {
color?: string[]
curStyle: object
scale: number
dur?: number
}
const props = withDefaults(defineProps<Props>(), {
color: () => [],
curStyle: () => {
return {
width: '320px',
height: '240px'
}
},
dur: 3
})
const refName = ref('decoration-5')
const line1Points = ref('')
const line2Points = ref('')
const line1Length = ref(0)
const line2Length = ref(0)
const defaultColor = ref(['#3f96a5', '#3f96a5'])
const mergedColor = ref<string[]>([])
const width = computed(() => {
return parseInt(props.curStyle.width) / props.scale
})
const height = computed(() => {
return parseInt(props.curStyle.height) / props.scale
})
const border_style = computed(() => {
return {
width: `${width.value}px`,
height: `${height.value}px`,
zoom: props.scale
}
})
const mergeColor = () => {
mergedColor.value = merge(cloneDeep(defaultColor.value), props.color || []) as string[]
}
const calcSVGData = () => {
const line1PointsArray = [
[0, height.value * 0.2],
[width.value * 0.18, height.value * 0.2],
[width.value * 0.2, height.value * 0.4],
[width.value * 0.25, height.value * 0.4],
[width.value * 0.27, height.value * 0.6],
[width.value * 0.72, height.value * 0.6],
[width.value * 0.75, height.value * 0.4],
[width.value * 0.8, height.value * 0.4],
[width.value * 0.82, height.value * 0.2],
[width.value, height.value * 0.2]
]
const line2PointsArray = [
[width.value * 0.3, height.value * 0.8],
[width.value * 0.7, height.value * 0.8]
]
line1Length.value = sum(getPointDistances(line1PointsArray))
line2Length.value = sum(getPointDistances(line2PointsArray))
line1Points.value = line1PointsArray.map(point => point.join(',')).join(' ')
line2Points.value = line2PointsArray.map(point => point.join(',')).join(' ')
}
// Watchers
watch(() => props.color, mergeColor)
watch([width, height], calcSVGData)
// Lifecycle
onMounted(() => {
mergeColor()
calcSVGData()
})
</script>
<style lang="less">
.dv-decoration-5 {
width: 100%;
height: 100%;
}
</style>

View File

@@ -8,6 +8,11 @@ import DeBoard7 from '@/custom-component/de-decoration/component_details/DeBoard
import DeBoard8 from '@/custom-component/de-decoration/component_details/DeBoard8.vue'
import DeBoard9 from '@/custom-component/de-decoration/component_details/DeBoard9.vue'
import DeBoard10 from '@/custom-component/de-decoration/component_details/DeBoard10.vue'
import DeDecoration1 from '@/custom-component/de-decoration/component_details/DeDecoration1.vue'
import DeDecoration2 from '@/custom-component/de-decoration/component_details/DeDecoration2.vue'
import DeDecoration3 from '@/custom-component/de-decoration/component_details/DeDecoration3.vue'
import DeDecoration4 from '@/custom-component/de-decoration/component_details/DeDecoration4.vue'
import DeDecoration5 from '@/custom-component/de-decoration/component_details/DeDecoration5.vue'
const boardInfoMap = {
DeBoard1: DeBoard1,
@@ -19,9 +24,32 @@ const boardInfoMap = {
DeBoard7: DeBoard7,
DeBoard8: DeBoard8,
DeBoard9: DeBoard9,
DeBoard10: DeBoard10
DeBoard10: DeBoard10,
DeDecoration1: DeDecoration1,
DeDecoration2: DeDecoration2,
DeDecoration3: DeDecoration3,
DeDecoration4: DeDecoration4,
DeDecoration5: DeDecoration5
}
export const findDecoration = name => {
return boardInfoMap[name]
}
export const calcTwoPointDistance = (pointA, pointB) => {
const minusX = Math.abs(pointA[0] - pointB[0])
const minusY = Math.abs(pointA[1] - pointB[1])
return Math.sqrt(Math.pow(minusX, 2) + Math.pow(minusY, 2))
}
/**
* @description 获取多个点,每个点之间的距离
* @param {Point[]} points
* @return {number[]}
*/
export function getPointDistances(points) {
return new Array(points.length - 1)
.fill(0)
.map((_, i) => calcTwoPointDistance(points[i], points[i + 1]))
}