mirror of
https://github.com/dataease/dataease.git
synced 2026-06-16 20:42:07 +08:00
feat(数据大屏): 支持装饰组件 (#16788)
This commit is contained in:
@@ -74,6 +74,97 @@ export const CANVAS_MATERIAL = [
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'DeDecoration',
|
||||
title: t('visualization.decoration'),
|
||||
span: 8,
|
||||
details: [
|
||||
{
|
||||
value: 'DeDecoration1',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [1]),
|
||||
icon: 'DeDecoration1'
|
||||
},
|
||||
{
|
||||
value: 'DeDecoration2',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [2]),
|
||||
icon: 'DeDecoration2'
|
||||
},
|
||||
{
|
||||
value: 'DeDecoration3',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [3]),
|
||||
icon: 'DeDecoration3'
|
||||
},
|
||||
{
|
||||
value: 'DeDecoration4',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [4]),
|
||||
icon: 'DeDecoration4'
|
||||
},
|
||||
{
|
||||
value: 'DeDecoration5',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [5]),
|
||||
icon: 'DeDecoration5'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard1',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [6]),
|
||||
icon: 'DeBoard1'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard2',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [7]),
|
||||
icon: 'DeBoard2'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard3',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [8]),
|
||||
icon: 'DeBoard3'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard4',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [9]),
|
||||
icon: 'DeBoard4'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard5',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [10]),
|
||||
icon: 'DeBoard5'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard6',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [11]),
|
||||
icon: 'DeBoard6'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard7',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [12]),
|
||||
icon: 'DeBoard7'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard8',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [13]),
|
||||
icon: 'DeBoard8'
|
||||
},
|
||||
{
|
||||
value: 'DeBoard10',
|
||||
type: 'de_decoration',
|
||||
title: t('visualization.decoration_name', [14]),
|
||||
icon: 'DeBoard10'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'DeGraphical',
|
||||
title: t('visualization.graphic'),
|
||||
|
||||
@@ -4,6 +4,7 @@ import eventBus from '@/utils/eventBus'
|
||||
import Icon from '@/components/icon-custom/src/Icon.vue'
|
||||
import { CANVAS_MATERIAL } from '@/custom-component/common/ComponentConfig'
|
||||
import { ElScrollbar } from 'element-plus-secondary'
|
||||
import DeDecoration from '@/custom-component/de-decoration/Component.vue'
|
||||
|
||||
defineProps({
|
||||
propValue: {
|
||||
@@ -48,6 +49,10 @@ const groupActiveChange = category => {
|
||||
state.curCategory = category
|
||||
anchorPosition('#' + category)
|
||||
}
|
||||
|
||||
const findUrl = name => {
|
||||
return new URL(`/src/assets/dynamic-background/${name}`, import.meta.url).href
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -91,6 +96,12 @@ const groupActiveChange = category => {
|
||||
class-name="item-top-icon"
|
||||
><component class="svg-icon item-top-icon" :is="chartInfo.icon"></component
|
||||
></Icon>
|
||||
<DeDecoration
|
||||
:curStyle="{ width: 530, height: 373 }"
|
||||
:element="{ innerType: chartInfo.value }"
|
||||
:scale="0.15"
|
||||
v-else-if="['de_decoration'].includes(chartInfo.type)"
|
||||
></DeDecoration>
|
||||
<component v-else style="color: #a6a6a6" :is="chartInfo.icon"></component>
|
||||
</div>
|
||||
<div v-if="chartInfo.title" class="item-bottom">
|
||||
|
||||
@@ -245,7 +245,8 @@ export const commonAttr = {
|
||||
'videoLinks',
|
||||
'streamLinks',
|
||||
'carouselInfo',
|
||||
'events'
|
||||
'events',
|
||||
'decoration_style'
|
||||
], // 编辑组件时记录当前使用的是哪个折叠面板,再次回来时恢复上次打开的折叠面板,优化用户体验
|
||||
linkage: {
|
||||
duration: 0, // 过渡持续时间
|
||||
@@ -492,6 +493,38 @@ const list = [
|
||||
backdropFilter: 'blur(0px)'
|
||||
}
|
||||
},
|
||||
{
|
||||
component: 'DeDecoration',
|
||||
name: t('visualization.decoration'),
|
||||
label: t('visualization.decoration'),
|
||||
propValue: ' ',
|
||||
icon: 'dv_decoration',
|
||||
style: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
color0: '#298e73',
|
||||
color1: '#2862b7',
|
||||
color2: '#2862b7',
|
||||
dur: 6,
|
||||
reverse: false,
|
||||
borderActive: false,
|
||||
backdropFilter: 'blur(0px)'
|
||||
}
|
||||
},
|
||||
{
|
||||
component: 'DynamicBackground',
|
||||
name: t('visualization.dynamic_background'),
|
||||
label: t('visualization.dynamic_background'),
|
||||
propValue: ' ',
|
||||
icon: 'dv_dynamic_background',
|
||||
style: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
backgroundColor: 'rgba(236,231,231,0.1)',
|
||||
borderActive: false,
|
||||
backdropFilter: 'blur(0px)'
|
||||
}
|
||||
},
|
||||
{
|
||||
component: 'RectShape',
|
||||
name: t('visualization.rect_shape'),
|
||||
|
||||
113
core/core-frontend/src/custom-component/de-decoration/Attr.vue
Normal file
113
core/core-frontend/src/custom-component/de-decoration/Attr.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="attr-list de-collapse-style">
|
||||
<CommonAttr :element="curComponent">
|
||||
<el-collapse-item
|
||||
effect="dark"
|
||||
:title="t('visualization.style')"
|
||||
name="decoration_style"
|
||||
v-if="curComponent && !mobileInPc"
|
||||
>
|
||||
<div style="display: flex">
|
||||
<el-tooltip effect="dark" placement="bottom">
|
||||
<template #content> {{ t('visualization.color_setting', [1]) }} </template>
|
||||
<el-form-item
|
||||
effect="dark"
|
||||
style="margin-left: 12px"
|
||||
class="form-item no-margin-bottom"
|
||||
:class="'form-item-dark'"
|
||||
>
|
||||
<el-color-picker
|
||||
:title="t('visualization.color_setting', [1])"
|
||||
v-model="state.style.color0"
|
||||
class="color-picker-style"
|
||||
:prefix-icon="dvStyleColor"
|
||||
:triggerWidth="80"
|
||||
is-custom
|
||||
show-alpha
|
||||
:predefine="state.predefineColors"
|
||||
@change="onStyleChange('color0')"
|
||||
>
|
||||
</el-color-picker>
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip effect="dark" placement="bottom">
|
||||
<template #content> {{ t('visualization.color_setting', [2]) }} </template>
|
||||
<el-form-item
|
||||
effect="dark"
|
||||
style="margin-left: 12px"
|
||||
class="form-item no-margin-bottom"
|
||||
:class="'form-item-dark'"
|
||||
>
|
||||
<el-color-picker
|
||||
:title="t('visualization.color_setting', [2])"
|
||||
v-model="curComponent.style.color1"
|
||||
class="color-picker-style"
|
||||
:prefix-icon="dvStyleColor"
|
||||
:triggerWidth="80"
|
||||
is-custom
|
||||
show-alpha
|
||||
:predefine="state.predefineColors"
|
||||
@change="onStyleChange('color0')"
|
||||
>
|
||||
</el-color-picker>
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</CommonAttr>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import CommonAttr from '@/custom-component/common/CommonAttr.vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { onMounted, reactive, watch } from 'vue'
|
||||
import { COLOR_PANEL } from '@/views/chart/components/editor/util/chart'
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
const { t } = useI18n()
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { curComponent, mobileInPc } = storeToRefs(dvMainStore)
|
||||
import dvStyleColor from '@/assets/svg/dv-style-color.svg'
|
||||
const state = reactive({
|
||||
style: {
|
||||
color0: null,
|
||||
color1: null,
|
||||
color2: null,
|
||||
dur: 6,
|
||||
reverse: false
|
||||
},
|
||||
predefineColors: COLOR_PANEL
|
||||
})
|
||||
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
|
||||
const onStyleChange = key => {
|
||||
curComponent.value.style[key] = state.style[key]
|
||||
snapshotStore.recordSnapshotCache('decoration')
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => curComponent.value],
|
||||
() => {
|
||||
init()
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
const init = () => {
|
||||
setTimeout(() => {
|
||||
state.style.color0 = curComponent.value.style.color0
|
||||
state.style.color1 = curComponent.value.style.color1
|
||||
state.style.color2 = curComponent.value.style.color2
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="dynamic-shape">
|
||||
<component
|
||||
:curStyle="curStyleAdaptor"
|
||||
:scale="calScale"
|
||||
:is="findDecoration(element.innerType)"
|
||||
:color="curColor"
|
||||
></component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { findDecoration } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { computed } from 'vue'
|
||||
const calScale = computed(() => {
|
||||
return props.scale
|
||||
})
|
||||
|
||||
const curStyleAdaptor = computed(() => {
|
||||
if (props.showPosition.includes('edit')) {
|
||||
return {
|
||||
width: parseInt(props.curStyle.width) / props.scale,
|
||||
height: parseInt(props.curStyle.height) / props.scale
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
width: parseInt(props.curStyle.width),
|
||||
height: parseInt(props.curStyle.height)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const curColor = computed(() => {
|
||||
return [props.element.style?.color0 || null, props.element.style?.color1 || null]
|
||||
})
|
||||
const props = defineProps({
|
||||
curStyle: {
|
||||
type: Object
|
||||
},
|
||||
scale: {
|
||||
type: Number
|
||||
},
|
||||
showPosition: {
|
||||
required: false,
|
||||
type: String,
|
||||
default: 'preview'
|
||||
},
|
||||
element: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
innerType: null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dynamic-shape {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div class="dv-border-box-1" :style="border_style" :ref="refName">
|
||||
<svg class="border" :width="width" :height="height">
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`10, 27 10, ${height - 27} 13, ${height - 24} 13, ${height - 21} 24, ${height - 11}
|
||||
38, ${height - 11} 41, ${height - 8} 73, ${height - 8} 75, ${height - 10} 81, ${height - 10}
|
||||
85, ${height - 6} ${width - 85}, ${height - 6} ${width - 81}, ${height - 10} ${width - 75}, ${
|
||||
height - 10
|
||||
}
|
||||
${width - 73}, ${height - 8} ${width - 41}, ${height - 8} ${width - 38}, ${height - 11}
|
||||
${width - 24}, ${height - 11} ${width - 13}, ${height - 21} ${width - 13}, ${height - 24}
|
||||
${width - 10}, ${height - 27} ${width - 10}, 27 ${width - 13}, 25 ${width - 13}, 21
|
||||
${width - 24}, 11 ${width - 38}, 11 ${width - 41}, 8 ${width - 73}, 8 ${width - 75}, 10
|
||||
${width - 81}, 10 ${
|
||||
width - 85
|
||||
}, 6 85, 6 81, 10 75, 10 73, 8 41, 8 38, 11 24, 11 13, 21 13, 24`"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<svg width="150px" height="150px" :key="item" v-for="item in border" :class="`${item} border`">
|
||||
<polygon
|
||||
:fill="mergedColor[0]"
|
||||
points="6,66 6,18 12,12 18,12 24,6 27,6 30,9 36,9 39,6 84,6 81,9 75,9 73.2,7 40.8,7 37.8,10.2 24,10.2 12,21 12,24 9,27 9,51 7.8,54 7.8,63"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
:values="`${mergedColor[0]};${mergedColor[1]};${mergedColor[0]}`"
|
||||
dur="0.5s"
|
||||
begin="0s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</polygon>
|
||||
<polygon
|
||||
:fill="mergedColor[1]"
|
||||
points="27.599999999999998,4.8 38.4,4.8 35.4,7.8 30.599999999999998,7.8"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
:values="`${mergedColor[1]};${mergedColor[0]};${mergedColor[1]}`"
|
||||
dur="0.5s"
|
||||
begin="0s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</polygon>
|
||||
<polygon
|
||||
:fill="mergedColor[0]"
|
||||
points="9,54 9,63 7.199999999999999,66 7.199999999999999,75 7.8,78 7.8,110 8.4,110 8.4,66 9.6,66 9.6,54"
|
||||
>
|
||||
<animate
|
||||
attributeName="fill"
|
||||
:values="`${mergedColor[0]};${mergedColor[1]};transparent`"
|
||||
dur="1s"
|
||||
begin="0s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</polygon>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: object
|
||||
scale: number
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => {
|
||||
return {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
const refName = ref('border-box-1')
|
||||
const border = ref(['left-top', 'right-top', 'left-bottom', 'right-bottom'])
|
||||
const defaultColor = ref(['#4fd2dd', '#235fa7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0'
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.color,
|
||||
() => {
|
||||
mergeColor()
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
mergeColor()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-1 {
|
||||
position: relative;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
will-change: transform; // 提示浏览器准备变换
|
||||
|
||||
.border {
|
||||
position: absolute;
|
||||
display: block;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: optimizeSpeed;
|
||||
/* 禁用鼠标事件提高性能 */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 保持原有角标定位不变 */
|
||||
.right-top {
|
||||
right: 0px;
|
||||
transform: rotateY(180deg) translateZ(0); // 添加硬件加速
|
||||
}
|
||||
|
||||
.left-bottom {
|
||||
bottom: 0px;
|
||||
transform: rotateX(180deg) translateZ(0); // 添加硬件加速
|
||||
}
|
||||
|
||||
.right-bottom {
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
transform: rotateX(180deg) rotateY(180deg) translateZ(0); // 添加硬件加速
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 创建新的层叠上下文 */
|
||||
isolation: isolate;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,126 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
const border = ['left-top', 'right-top', 'left-bottom', 'right-bottom']
|
||||
|
||||
const defaultColor = ref(['#2862b7', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform', // 提示浏览器优化
|
||||
'box-shadow': `inset 0 0 25px 3px ${mergedColor.value[0]}`,
|
||||
'--border-color': mergedColor.value[1] // CSS变量传递颜色
|
||||
}
|
||||
})
|
||||
|
||||
// 使用立即执行的watch
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-10" :style="border_style" ref="dv-border-box-10">
|
||||
<!-- 主边框SVG -->
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`
|
||||
4, 0 ${width - 4}, 0 ${width}, 4 ${width}, ${height - 4} ${width - 4}, ${height}
|
||||
4, ${height} 0, ${height - 4} 0, 4
|
||||
`"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- 四个角标 -->
|
||||
<template v-for="item in border" :key="item">
|
||||
<svg width="150px" height="150px" :class="`corner-${item} dv-border-svg-container`">
|
||||
<polygon fill="var(--border-color)" points="40, 0 5, 0 0, 5 0, 16 3, 19 3, 7 7, 3 35, 3" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-10 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
display: block;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
}
|
||||
|
||||
/* 角标位置和变换 */
|
||||
.corner-left-top {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.corner-right-top {
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: rotateY(180deg) translateZ(0); /* 添加硬件加速 */
|
||||
}
|
||||
|
||||
.corner-left-bottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
transform: rotateX(180deg) translateZ(0); /* 添加硬件加速 */
|
||||
}
|
||||
|
||||
.corner-right-bottom {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transform: rotateX(180deg) rotateY(180deg) translateZ(0); /* 添加硬件加速 */
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: object
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
const defaultColor = ref(['#4fd2dd', '#235fa7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器准备变换
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.color, mergeColor)
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 保持原有模板结构不变 -->
|
||||
<div class="dv-border-box-2" :style="border_style" :ref="ref">
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`
|
||||
7, 7 ${width - 7}, 7 ${width - 7}, ${height - 7} 7, ${height - 7}
|
||||
`"
|
||||
/>
|
||||
<polyline
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`2, 2 ${width - 2} ,2 ${width - 2}, ${height - 2} 2, ${height - 2} 2, 2`"
|
||||
/>
|
||||
<polyline
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`6, 6 ${width - 6}, 6 ${width - 6}, ${height - 6} 6, ${height - 6} 6, 6`"
|
||||
/>
|
||||
<circle :fill="mergedColor[0]" cx="11" cy="11" r="1" />
|
||||
<circle :fill="mergedColor[0]" :cx="width - 11" cy="11" r="1" />
|
||||
<circle :fill="mergedColor[0]" :cx="width - 11" :cy="height - 11" r="1" />
|
||||
<circle :fill="mergedColor[0]" cx="11" :cy="height - 11" r="1" />
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-2 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: optimizeSpeed;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > polyline {
|
||||
fill: none;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke; /* 保持线条宽度不受缩放影响 */
|
||||
}
|
||||
|
||||
& > circle {
|
||||
will-change: transform; /* 优化小圆点渲染 */
|
||||
}
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
|
||||
/* 如果内容不需要交互也可以添加 */
|
||||
/* pointer-events: none; */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
const defaultColor = ref(['#2862b7', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器准备变换
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-3" :style="border_style" :ref="ref">
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`
|
||||
23, 23 ${width - 24}, 23 ${width - 24}, ${height - 24} 23, ${height - 24}
|
||||
`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb3-line1"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`4, 4 ${width - 22} ,4 ${width - 22}, ${height - 22} 4, ${height - 22} 4, 4`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb3-line2"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`10, 10 ${width - 16}, 10 ${width - 16}, ${height - 16} 10, ${height - 16} 10, 10`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb3-line2"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`16, 16 ${width - 10}, 16 ${width - 10}, ${height - 10} 16, ${height - 10} 16, 16`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb3-line2"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`22, 22 ${width - 4}, 22 ${width - 4}, ${height - 4} 22, ${height - 4} 22, 22`"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-3 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > polyline {
|
||||
fill: none;
|
||||
vector-effect: non-scaling-stroke; /* 保持线条宽度不受缩放影响 */
|
||||
}
|
||||
|
||||
.dv-bb3-line1 {
|
||||
stroke-width: 3;
|
||||
stroke-linecap: round; /* 线条端点圆角 */
|
||||
}
|
||||
|
||||
.dv-bb3-line2 {
|
||||
stroke-width: 1;
|
||||
stroke-dasharray: 100; /* 添加虚线效果提升视觉层次 */
|
||||
stroke-dashoffset: 100;
|
||||
animation: dash 5s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
to {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,213 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
const reverse = computed(() => false)
|
||||
|
||||
const defaultColor = ref(['#805151', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器准备变换
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-4" :style="border_style" :ref="ref">
|
||||
<svg
|
||||
:class="`dv-border-svg-container ${reverse && 'dv-reverse'}`"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`
|
||||
${width - 15}, 22 170, 22 150, 7 40, 7 28, 21 32, 24
|
||||
16, 42 16, ${height - 32} 41, ${height - 7} ${width - 15}, ${height - 7}
|
||||
`"
|
||||
/>
|
||||
|
||||
<polyline
|
||||
class="dv-bb4-line-1"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`145, ${height - 5} 40, ${height - 5} 10, ${height - 35}
|
||||
10, 40 40, 5 150, 5 170, 20 ${width - 15}, 20`"
|
||||
/>
|
||||
<polyline
|
||||
:stroke="mergedColor[1]"
|
||||
class="dv-bb4-line-2"
|
||||
:points="`245, ${height - 1} 36, ${height - 1} 14, ${height - 23}
|
||||
14, ${height - 100}`"
|
||||
/>
|
||||
|
||||
<polyline
|
||||
class="dv-bb4-line-3"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`7, ${height - 40} 7, ${height - 75}`"
|
||||
/>
|
||||
<polyline class="dv-bb4-line-4" :stroke="mergedColor[0]" :points="`28, 24 13, 41 13, 64`" />
|
||||
<polyline class="dv-bb4-line-5" :stroke="mergedColor[0]" :points="`5, 45 5, 140`" />
|
||||
<polyline class="dv-bb4-line-6" :stroke="mergedColor[1]" :points="`14, 75 14, 180`" />
|
||||
<polyline
|
||||
class="dv-bb4-line-7"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`55, 11 147, 11 167, 26 250, 26`"
|
||||
/>
|
||||
<polyline class="dv-bb4-line-8" :stroke="mergedColor[1]" :points="`158, 5 173, 16`" />
|
||||
<polyline
|
||||
class="dv-bb4-line-9"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`200, 17 ${width - 10}, 17`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb4-line-10"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`385, 17 ${width - 10}, 17`"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-4 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.dv-reverse {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > polyline {
|
||||
fill: none;
|
||||
vector-effect: non-scaling-stroke; /* 保持线条宽度不受缩放影响 */
|
||||
}
|
||||
}
|
||||
|
||||
.sw1 {
|
||||
stroke-width: 1;
|
||||
stroke-linecap: butt;
|
||||
}
|
||||
|
||||
.sw3 {
|
||||
stroke-width: 3px;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.dv-bb4-line-1 {
|
||||
.sw1;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb4-line-2 {
|
||||
.sw1;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb4-line-3 {
|
||||
.sw3;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb4-line-4 {
|
||||
.sw3;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb4-line-5 {
|
||||
.sw1;
|
||||
stroke-dasharray: 2 2; /* 添加虚线效果 */
|
||||
}
|
||||
|
||||
.dv-bb4-line-6 {
|
||||
.sw1;
|
||||
stroke-dasharray: 2 2; /* 添加虚线效果 */
|
||||
}
|
||||
|
||||
.dv-bb4-line-7 {
|
||||
.sw1;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb4-line-8 {
|
||||
.sw3;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb4-line-9 {
|
||||
.sw3;
|
||||
stroke-dasharray: 10 5; /* 优化虚线样式 */
|
||||
animation: dash-animation 3s linear infinite;
|
||||
}
|
||||
|
||||
.dv-bb4-line-10 {
|
||||
.sw1;
|
||||
stroke-dasharray: 8 6; /* 优化虚线样式 */
|
||||
animation: dash-animation 3s linear infinite reverse;
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash-animation {
|
||||
to {
|
||||
stroke-dashoffset: 15;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,190 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
const reverse = computed(() => false)
|
||||
|
||||
const defaultColor = ref(['#2862b7', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化变换
|
||||
}
|
||||
})
|
||||
|
||||
// 使用立即执行的watch
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-5" :style="border_style" ref="dv-border-box-5">
|
||||
<svg
|
||||
:class="`dv-border-svg-container ${reverse && 'dv-reverse'}`"
|
||||
:width="width"
|
||||
:height="height"
|
||||
>
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`
|
||||
10, 22 ${width - 22}, 22 ${width - 22}, ${height - 86} ${width - 84}, ${height - 24} 10, ${
|
||||
height - 24
|
||||
}
|
||||
`"
|
||||
/>
|
||||
|
||||
<polyline
|
||||
class="dv-bb5-line-1"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`8, 5 ${width - 5}, 5 ${width - 5}, ${height - 100}
|
||||
${width - 100}, ${height - 5} 8, ${height - 5} 8, 5`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb5-line-2"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`3, 5 ${width - 20}, 5 ${width - 20}, ${height - 60}
|
||||
${width - 74}, ${height - 5} 3, ${height - 5} 3, 5`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb5-line-3"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`50, 13 ${width - 35}, 13`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb5-line-4"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`15, 20 ${width - 35}, 20`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb5-line-5"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`15, ${height - 20} ${width - 110}, ${height - 20}`"
|
||||
/>
|
||||
<polyline
|
||||
class="dv-bb5-line-6"
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`15, ${height - 13} ${width - 110}, ${height - 13}`"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-5 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.dv-reverse {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > polyline {
|
||||
fill: none;
|
||||
vector-effect: non-scaling-stroke; /* 保持线条宽度不受缩放影响 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 线条样式优化 */
|
||||
.dv-bb5-line-1 {
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: none;
|
||||
}
|
||||
|
||||
.dv-bb5-line-2 {
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 5 2; /* 添加虚线效果 */
|
||||
}
|
||||
|
||||
.dv-bb5-line-3 {
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
animation: line-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.dv-bb5-line-4 {
|
||||
stroke-width: 2;
|
||||
stroke-linecap: square;
|
||||
}
|
||||
|
||||
.dv-bb5-line-5 {
|
||||
stroke-width: 2;
|
||||
stroke-linecap: square;
|
||||
stroke-dasharray: 10 5; /* 添加虚线效果 */
|
||||
}
|
||||
|
||||
.dv-bb5-line-6 {
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
animation: line-pulse 2s ease-in-out infinite reverse;
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes line-pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
stroke-width: 5;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
stroke-width: 6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,194 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
const defaultColor = ref(['#2862b7', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化变换
|
||||
}
|
||||
})
|
||||
|
||||
// 使用立即执行的watch
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-6" :style="border_style" ref="dv-border-box-6">
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`
|
||||
9, 7 ${width - 9}, 7 ${width - 9}, ${height - 7} 9, ${height - 7}
|
||||
`"
|
||||
/>
|
||||
|
||||
<!-- 优化圆点渲染 -->
|
||||
<g class="corner-dots">
|
||||
<circle :fill="mergedColor[1]" cx="5" cy="5" r="2" />
|
||||
<circle :fill="mergedColor[1]" :cx="width - 5" cy="5" r="2" />
|
||||
<circle :fill="mergedColor[1]" :cx="width - 5" :cy="height - 5" r="2" />
|
||||
<circle :fill="mergedColor[1]" cx="5" :cy="height - 5" r="2" />
|
||||
</g>
|
||||
|
||||
<!-- 水平线 -->
|
||||
<polyline
|
||||
class="horizontal-line"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`10, 4 ${width - 10}, 4`"
|
||||
/>
|
||||
<polyline
|
||||
class="horizontal-line"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`10, ${height - 4} ${width - 10}, ${height - 4}`"
|
||||
/>
|
||||
|
||||
<!-- 垂直线 -->
|
||||
<polyline
|
||||
class="vertical-line"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`5, 70 5, ${height - 70}`"
|
||||
/>
|
||||
<polyline
|
||||
class="vertical-line"
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`${width - 5}, 70 ${width - 5}, ${height - 70}`"
|
||||
/>
|
||||
|
||||
<!-- 装饰短线 -->
|
||||
<g class="decoration-lines">
|
||||
<polyline :stroke="mergedColor[0]" :points="`3, 10, 3, 50`" />
|
||||
<polyline :stroke="mergedColor[0]" :points="`7, 30 7, 80`" />
|
||||
<polyline :stroke="mergedColor[0]" :points="`${width - 3}, 10 ${width - 3}, 50`" />
|
||||
<polyline :stroke="mergedColor[0]" :points="`${width - 7}, 30 ${width - 7}, 80`" />
|
||||
<polyline :stroke="mergedColor[0]" :points="`3, ${height - 10} 3, ${height - 50}`" />
|
||||
<polyline :stroke="mergedColor[0]" :points="`7, ${height - 30} 7, ${height - 80}`" />
|
||||
<polyline
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`${width - 3}, ${height - 10} ${width - 3}, ${height - 50}`"
|
||||
/>
|
||||
<polyline
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`${width - 7}, ${height - 30} ${width - 7}, ${height - 80}`"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-6 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
/* 公共线条样式 */
|
||||
& > polyline {
|
||||
fill: none;
|
||||
stroke-width: 1;
|
||||
vector-effect: non-scaling-stroke; /* 保持线条宽度不受缩放影响 */
|
||||
}
|
||||
|
||||
/* 角点样式 */
|
||||
.corner-dots > circle {
|
||||
animation: dot-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* 水平线样式 */
|
||||
.horizontal-line {
|
||||
stroke-dasharray: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
/* 垂直线样式 */
|
||||
.vertical-line {
|
||||
stroke-dasharray: 5 2;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
/* 装饰短线样式 */
|
||||
.decoration-lines > polyline {
|
||||
stroke-dasharray: 3 3;
|
||||
animation: line-flicker 1.5s ease-in-out infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画定义 */
|
||||
@keyframes dot-pulse {
|
||||
0%,
|
||||
100% {
|
||||
r: 2;
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
r: 3;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes line-flicker {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,165 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
})
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
const defaultColor = ref(['#2862b7', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
const [primaryColor, secondaryColor] = mergedColor.value
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform', // 提示浏览器优化
|
||||
'box-shadow': `inset 0 0 25px ${primaryColor}33`, // 添加透明度
|
||||
border: `1px solid ${primaryColor}`,
|
||||
'background-color': props.backgroundColor
|
||||
}
|
||||
})
|
||||
|
||||
// 使用立即执行的watch并添加防抖
|
||||
let debounceTimer: number
|
||||
watch(
|
||||
() => props.color,
|
||||
() => {
|
||||
clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(mergeColor, 50)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-7" :style="border_style" ref="dv-border-box-7">
|
||||
<!-- 使用分组减少DOM数量 -->
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<!-- 外层边框 -->
|
||||
<g class="outer-border">
|
||||
<polyline :stroke="mergedColor[0]" :points="`0, 25 0, 0 25, 0`" />
|
||||
<polyline :stroke="mergedColor[0]" :points="`${width - 25}, 0 ${width}, 0 ${width}, 25`" />
|
||||
<polyline
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`${width - 25}, ${height} ${width}, ${height} ${width}, ${height - 25}`"
|
||||
/>
|
||||
<polyline
|
||||
:stroke="mergedColor[0]"
|
||||
:points="`0, ${height - 25} 0, ${height} 25, ${height}`"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<!-- 内层边框 -->
|
||||
<g class="inner-border">
|
||||
<polyline :stroke="mergedColor[1]" :points="`0, 10 0, 0 10, 0`" />
|
||||
<polyline :stroke="mergedColor[1]" :points="`${width - 10}, 0 ${width}, 0 ${width}, 10`" />
|
||||
<polyline
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`${width - 10}, ${height} ${width}, ${height} ${width}, ${height - 10}`"
|
||||
/>
|
||||
<polyline
|
||||
:stroke="mergedColor[1]"
|
||||
:points="`0, ${height - 10} 0, ${height} 10, ${height}`"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-7 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
overflow: hidden; /* 防止内容溢出 */
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > g > polyline {
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
vector-effect: non-scaling-stroke; /* 保持线条宽度不受缩放影响 */
|
||||
transition: stroke 0.3s ease; /* 颜色变化动画 */
|
||||
}
|
||||
|
||||
.outer-border > polyline {
|
||||
stroke-width: 2;
|
||||
animation: outer-glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.inner-border > polyline {
|
||||
stroke-width: 5;
|
||||
animation: inner-glow 1.5s ease-in-out infinite alternate;
|
||||
}
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
z-index: 1; /* 确保内容在边框上方 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画定义 */
|
||||
@keyframes outer-glow {
|
||||
0% {
|
||||
stroke-opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes inner-glow {
|
||||
0% {
|
||||
stroke-width: 4;
|
||||
}
|
||||
100% {
|
||||
stroke-width: 6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
}),
|
||||
duration: 3
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
const dur = computed(() => props.duration)
|
||||
|
||||
const defaultColor = ref(['#2862b7', '#2862b7'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
// 生成唯一ID
|
||||
const generateId = () => Math.random().toString(36).substring(2, 11)
|
||||
const pathId = `path-${generateId()}`
|
||||
const gradientId = `gradient-${generateId()}`
|
||||
const maskId = `mask-${generateId()}`
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const pathD = computed(() => {
|
||||
return `M2.5,2.5 L${width.value - 2.5},2.5 L${width.value - 2.5},${height.value - 2.5} L2.5,${
|
||||
height.value - 2.5
|
||||
} L2.5,2.5`
|
||||
})
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化变换
|
||||
}
|
||||
})
|
||||
|
||||
// 使用立即执行的watch
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-8" :style="border_style" ref="dv-border-box-8">
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<defs>
|
||||
<!-- 优化路径定义 -->
|
||||
<path :id="pathId" :d="pathD" fill="none" />
|
||||
|
||||
<!-- 优化渐变定义 -->
|
||||
<radialGradient :id="gradientId" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" :stop-color="mergedColor[1]" stop-opacity="0.8" />
|
||||
<stop offset="100%" :stop-color="mergedColor[1]" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
|
||||
<!-- 优化遮罩定义 -->
|
||||
<mask :id="maskId">
|
||||
<circle cx="0" cy="0" r="120" :fill="`url(#${gradientId})`">
|
||||
<animateMotion
|
||||
:dur="`${dur}s`"
|
||||
:path="pathD"
|
||||
rotate="auto"
|
||||
repeatCount="indefinite"
|
||||
calcMode="linear"
|
||||
/>
|
||||
</circle>
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<!-- 背景多边形 -->
|
||||
<polygon
|
||||
:fill="backgroundColor"
|
||||
:points="`5,5 ${width - 5},5 ${width - 5} ${height - 5} 5,${height - 5}`"
|
||||
/>
|
||||
|
||||
<!-- 静态边框 -->
|
||||
<use :stroke="mergedColor[0]" stroke-width="1" :href="`#${pathId}`" stroke-linecap="round" />
|
||||
|
||||
<!-- 动态发光边框 -->
|
||||
<use
|
||||
:stroke="mergedColor[1]"
|
||||
stroke-width="3"
|
||||
:href="`#${pathId}`"
|
||||
:mask="`url(#${maskId})`"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dasharray"
|
||||
:values="`0,${length};${length},0`"
|
||||
:dur="`${dur}s`"
|
||||
repeatCount="indefinite"
|
||||
calcMode="linear"
|
||||
/>
|
||||
</use>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-8 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
overflow: hidden;
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: optimizeSpeed;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > polygon {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,230 @@
|
||||
<script lang="tsx" setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
backgroundColor?: string
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
duration?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
backgroundColor: 'transparent',
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
}),
|
||||
duration: 10
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
// 生成更简洁的ID
|
||||
const generateId = () => Math.random().toString(36).substring(2, 8)
|
||||
const gradientId = `grad-${generateId()}`
|
||||
const maskId = `mask-${generateId()}`
|
||||
|
||||
const defaultColor = ref(['#11eefd', '#0078d2'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
const border_style = computed(() => {
|
||||
return {
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化
|
||||
}
|
||||
})
|
||||
|
||||
// 计算关键点坐标
|
||||
const points = computed(() => ({
|
||||
// 背景多边形点
|
||||
bgPoints: `
|
||||
15,9 ${width.value * 0.1 + 1},9 ${width.value * 0.1 + 4},6 ${width.value * 0.52 + 2},6
|
||||
${width.value * 0.52 + 6},10 ${width.value * 0.58 - 7},10 ${width.value * 0.58 - 2},6
|
||||
${width.value * 0.9 + 2},6 ${width.value * 0.9 + 6},10 ${width.value - 10},10 ${
|
||||
width.value - 10
|
||||
},${height.value * 0.1 - 6}
|
||||
${width.value - 6},${height.value * 0.1 - 1} ${width.value - 6},${height.value * 0.8 + 1} ${
|
||||
width.value - 10
|
||||
},${height.value * 0.8 + 6}
|
||||
${width.value - 10},${height.value - 10} ${width.value * 0.92 + 7},${height.value - 10} ${
|
||||
width.value * 0.92 + 2
|
||||
},${height.value - 6}
|
||||
11,${height.value - 6} 11,${height.value * 0.15 - 2} 15,${height.value * 0.15 - 7}
|
||||
`,
|
||||
// 遮罩多边形点
|
||||
maskPoints1: `8,${height.value * 0.4} 8,3 ${width.value * 0.4 + 7},3`,
|
||||
maskPoints2: `8,${height.value * 0.15} 8,3 ${width.value * 0.1 + 7},3 ${
|
||||
width.value * 0.1
|
||||
},8 14,8 14,${height.value * 0.15 - 7}`,
|
||||
maskPoints3: `${width.value * 0.5},3 ${width.value - 3},3 ${width.value - 3},${
|
||||
height.value * 0.25
|
||||
}`,
|
||||
maskPoints4: `${width.value * 0.52},3 ${width.value * 0.58},3 ${width.value * 0.58 - 7},9 ${
|
||||
width.value * 0.52 + 7
|
||||
},9`,
|
||||
maskPoints5: `${width.value * 0.9},3 ${width.value - 3},3 ${width.value - 3},${
|
||||
height.value * 0.1
|
||||
} ${width.value - 9},${height.value * 0.1 - 7} ${width.value - 9},9 ${width.value * 0.9 + 7},9`,
|
||||
maskPoints6: `8,${height.value * 0.5} 8,${height.value - 3} ${width.value * 0.3 + 7},${
|
||||
height.value - 3
|
||||
}`,
|
||||
maskPoints7: `8,${height.value * 0.55} 8,${height.value * 0.7} 2,${height.value * 0.7 - 7} 2,${
|
||||
height.value * 0.55 + 7
|
||||
}`,
|
||||
maskPoints8: `${width.value * 0.35},${height.value - 3} ${width.value - 3},${height.value - 3} ${
|
||||
width.value - 3
|
||||
},${height.value * 0.35}`,
|
||||
maskPoints9: `${width.value * 0.92},${height.value - 3} ${width.value - 3},${height.value - 3} ${
|
||||
width.value - 3
|
||||
},${height.value * 0.8} ${width.value - 9},${height.value * 0.8 + 7} ${width.value - 9},${
|
||||
height.value - 9
|
||||
} ${width.value * 0.92 + 7},${height.value - 9}`
|
||||
}))
|
||||
|
||||
// 使用立即执行的watch
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-border-box-9" :style="border_style" ref="dv-border-box-9">
|
||||
<svg class="dv-border-svg-container" :width="width" :height="height">
|
||||
<defs>
|
||||
<!-- 优化渐变定义 -->
|
||||
<linearGradient :id="gradientId" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<animate
|
||||
attributeName="x1"
|
||||
values="0%;100%;0%"
|
||||
:dur="`${props.duration}s`"
|
||||
calcMode="linear"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="x2"
|
||||
values="100%;0%;100%"
|
||||
:dur="`${props.duration}s`"
|
||||
calcMode="linear"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<stop offset="0%" :stop-color="mergedColor[0]">
|
||||
<animate
|
||||
attributeName="stop-color"
|
||||
:values="`${mergedColor[0]};${mergedColor[1]};${mergedColor[0]}`"
|
||||
:dur="`${props.duration}s`"
|
||||
calcMode="linear"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</stop>
|
||||
<stop offset="100%" :stop-color="mergedColor[1]">
|
||||
<animate
|
||||
attributeName="stop-color"
|
||||
:values="`${mergedColor[1]};${mergedColor[0]};${mergedColor[1]}`"
|
||||
:dur="`${props.duration}s`"
|
||||
calcMode="linear"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</stop>
|
||||
</linearGradient>
|
||||
|
||||
<!-- 优化遮罩定义 -->
|
||||
<mask :id="maskId">
|
||||
<polyline
|
||||
stroke="#fff"
|
||||
stroke-width="3"
|
||||
fill="transparent"
|
||||
:points="points.maskPoints1"
|
||||
/>
|
||||
<polyline fill="#fff" :points="points.maskPoints2" />
|
||||
<polyline
|
||||
stroke="#fff"
|
||||
stroke-width="3"
|
||||
fill="transparent"
|
||||
:points="points.maskPoints3"
|
||||
/>
|
||||
<polyline fill="#fff" :points="points.maskPoints4" />
|
||||
<polyline fill="#fff" :points="points.maskPoints5" />
|
||||
<polyline
|
||||
stroke="#fff"
|
||||
stroke-width="3"
|
||||
fill="transparent"
|
||||
:points="points.maskPoints6"
|
||||
/>
|
||||
<polyline fill="#fff" :points="points.maskPoints7" />
|
||||
<polyline
|
||||
stroke="#fff"
|
||||
stroke-width="3"
|
||||
fill="transparent"
|
||||
:points="points.maskPoints8"
|
||||
/>
|
||||
<polyline fill="#fff" :points="points.maskPoints9" />
|
||||
</mask>
|
||||
</defs>
|
||||
|
||||
<!-- 背景多边形 -->
|
||||
<polygon :fill="backgroundColor" :points="points.bgPoints" />
|
||||
|
||||
<!-- 渐变矩形 -->
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:fill="`url(#${gradientId})`"
|
||||
:mask="`url(#${maskId})`"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<div class="border-box-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-border-box-9 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
overflow: hidden;
|
||||
|
||||
.dv-border-svg-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: optimizeSpeed;
|
||||
pointer-events: none; /* 禁用鼠标事件 */
|
||||
|
||||
& > polygon,
|
||||
& > rect {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
}
|
||||
|
||||
.border-box-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
isolation: isolate; /* 创建新的层叠上下文 */
|
||||
z-index: 1; /* 确保内容在边框上方 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,202 @@
|
||||
<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 } from 'lodash-es'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
curStyle: object
|
||||
scale: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
curStyle: () => {
|
||||
return {
|
||||
width: 320,
|
||||
height: 240
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
const border_style = computed(() => ({
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化
|
||||
}))
|
||||
|
||||
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 = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
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 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
transform-origin: left top;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: optimizeSpeed;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
reverse?: boolean
|
||||
dur?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
}),
|
||||
reverse: false,
|
||||
dur: 6
|
||||
})
|
||||
|
||||
// 尺寸计算
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
// 样式计算
|
||||
const border_style = computed(() => ({
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化
|
||||
}))
|
||||
|
||||
// 颜色配置
|
||||
const defaultColor = ref(['#3faacb', '#fff'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
// SVG元素位置和尺寸
|
||||
const svgElements = computed(() => ({
|
||||
x: props.reverse ? width.value / 2 : 0,
|
||||
y: props.reverse ? 0 : height.value / 2,
|
||||
w: props.reverse ? 1 : width.value,
|
||||
h: props.reverse ? height.value : 1
|
||||
}))
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
// 监听器
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
watch(() => props.reverse, mergeColor)
|
||||
|
||||
// 生命周期
|
||||
onMounted(mergeColor)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-decoration-2" :style="border_style">
|
||||
<svg :width="`${width}px`" :height="`${height}px`" class="decoration-svg">
|
||||
<!-- 主矩形动画 -->
|
||||
<rect
|
||||
:x="svgElements.x"
|
||||
:y="svgElements.y"
|
||||
:width="svgElements.w"
|
||||
:height="svgElements.h"
|
||||
:fill="mergedColor[0]"
|
||||
class="main-rect"
|
||||
>
|
||||
<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="svgElements.x"
|
||||
:y="svgElements.y"
|
||||
width="1"
|
||||
height="1"
|
||||
:fill="mergedColor[1]"
|
||||
class="dot-rect"
|
||||
>
|
||||
<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>
|
||||
|
||||
<style lang="less">
|
||||
.dv-decoration-2 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.decoration-svg {
|
||||
position: absolute;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none;
|
||||
|
||||
.main-rect {
|
||||
opacity: 0.8;
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
|
||||
.dot-rect {
|
||||
rx: 50%; /* 圆形点 */
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,146 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
animationRatio?: number // 新增:控制动画元素比例
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
}),
|
||||
animationRatio: 0.6 // 默认60%的元素有动画
|
||||
})
|
||||
|
||||
// 尺寸计算
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
// 样式计算
|
||||
const border_style = computed(() => ({
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化
|
||||
}))
|
||||
|
||||
// 点阵配置
|
||||
const pointSideLength = 7
|
||||
const svgWH = ref([300, 35])
|
||||
const rowNum = 2
|
||||
const rowPoints = 25
|
||||
|
||||
// 颜色配置
|
||||
const defaultColor = ref(['#7acaec', 'transparent'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
// 计算属性
|
||||
const halfPointSideLength = computed(() => pointSideLength / 2)
|
||||
const svgScale = computed(() => [width.value / svgWH.value[0], height.value / svgWH.value[1]])
|
||||
|
||||
// 点阵位置
|
||||
const points = ref<number[][]>([])
|
||||
|
||||
// 预生成动画配置(优化随机性能)
|
||||
const animationConfigs = computed(() => {
|
||||
return points.value.map((_, i) => ({
|
||||
shouldAnimate: Math.random() <= props.animationRatio,
|
||||
duration: 1 + Math.random(), // 1-2秒
|
||||
delay: Math.random() * 2 // 0-2秒
|
||||
}))
|
||||
})
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
// 计算点阵位置
|
||||
const calcPointsPosition = () => {
|
||||
const [w, h] = svgWH.value
|
||||
const horizontalGap = w / (rowPoints + 1)
|
||||
const verticalGap = h / (rowNum + 1)
|
||||
|
||||
points.value = Array.from({ length: rowNum }, (_, i) =>
|
||||
Array.from({ length: rowPoints }, (_, j) => [horizontalGap * (j + 1), verticalGap * (i + 1)])
|
||||
).flat()
|
||||
}
|
||||
|
||||
// 响应式更新
|
||||
const updateLayout = () => {
|
||||
calcPointsPosition()
|
||||
}
|
||||
|
||||
// 生命周期和监听
|
||||
onMounted(() => {
|
||||
mergeColor()
|
||||
updateLayout()
|
||||
})
|
||||
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
watch([width, height], updateLayout)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-decoration-3" :style="border_style">
|
||||
<svg
|
||||
:width="`${svgWH[0]}px`"
|
||||
:height="`${svgWH[1]}px`"
|
||||
:style="`transform:scale(${svgScale[0]},${svgScale[1]});`"
|
||||
class="decoration-svg"
|
||||
>
|
||||
<rect
|
||||
v-for="(point, i) in points"
|
||||
:key="i"
|
||||
:fill="mergedColor[0]"
|
||||
:x="point[0] - halfPointSideLength"
|
||||
:y="point[1] - halfPointSideLength"
|
||||
:width="pointSideLength"
|
||||
:height="pointSideLength"
|
||||
rx="1"
|
||||
opacity="0.8"
|
||||
>
|
||||
<animate
|
||||
v-if="animationConfigs[i].shouldAnimate"
|
||||
attributeName="fill"
|
||||
:values="mergedColor.join(';')"
|
||||
:dur="`${animationConfigs[i].duration}s`"
|
||||
:begin="`${animationConfigs[i].delay}s`"
|
||||
repeatCount="indefinite"
|
||||
calcMode="linear"
|
||||
/>
|
||||
</rect>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-decoration-3 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.decoration-svg {
|
||||
position: absolute;
|
||||
transform-origin: left top;
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: optimizeSpeed;
|
||||
pointer-events: none;
|
||||
|
||||
rect {
|
||||
transition: fill 0.3s ease; /* 颜色变化过渡 */
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,155 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
reverse?: boolean
|
||||
dur?: number
|
||||
strokeWidth?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
}),
|
||||
reverse: false,
|
||||
dur: 6,
|
||||
strokeWidth: 3
|
||||
})
|
||||
|
||||
// 尺寸计算
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
// 样式计算
|
||||
const border_style = computed(() => ({
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化
|
||||
}))
|
||||
|
||||
// 颜色配置
|
||||
const defaultColor = ref(['rgba(255, 255, 255, 0.3)', 'rgba(255, 255, 255, 0.3)'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
// 计算属性
|
||||
const containerStyle = computed(() => ({
|
||||
width: props.reverse ? `${width.value}px` : `${props.strokeWidth}px`,
|
||||
height: props.reverse ? `${props.strokeWidth}px` : `${height.value}px`,
|
||||
'--animation-duration': `${props.dur}s`,
|
||||
'--stroke-width': `${props.strokeWidth}px`,
|
||||
'--half-stroke': `${props.strokeWidth / 2}px`
|
||||
}))
|
||||
|
||||
const svgPoints = computed(() =>
|
||||
props.reverse
|
||||
? `0, ${props.strokeWidth / 2} ${width.value}, ${props.strokeWidth / 2}`
|
||||
: `${props.strokeWidth / 2}, 0 ${props.strokeWidth / 2}, ${height.value}`
|
||||
)
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
// 生命周期和监听
|
||||
onMounted(mergeColor)
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-decoration-4" :style="border_style">
|
||||
<div :class="['container', reverse ? 'reverse' : 'normal']" :style="containerStyle">
|
||||
<svg
|
||||
:width="reverse ? width : strokeWidth"
|
||||
:height="reverse ? strokeWidth : height"
|
||||
class="decoration-svg"
|
||||
>
|
||||
<!-- 细线 -->
|
||||
<polyline :stroke="mergedColor[0]" :points="svgPoints" stroke-linecap="round" />
|
||||
|
||||
<!-- 粗虚线 -->
|
||||
<polyline
|
||||
class="bold-line"
|
||||
:stroke="mergedColor[1]"
|
||||
:stroke-width="strokeWidth"
|
||||
stroke-dasharray="20, 80"
|
||||
stroke-dashoffset="-30"
|
||||
:points="svgPoints"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-decoration-4 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
will-change: transform;
|
||||
|
||||
&.normal {
|
||||
left: 50%;
|
||||
margin-left: calc(-1 * var(--half-stroke));
|
||||
animation: ani-height var(--animation-duration) ease-in-out infinite;
|
||||
}
|
||||
|
||||
&.reverse {
|
||||
top: 50%;
|
||||
margin-top: calc(-1 * var(--half-stroke));
|
||||
animation: ani-width var(--animation-duration) ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.decoration-svg {
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none;
|
||||
|
||||
polyline {
|
||||
transition: stroke 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ani-height {
|
||||
0% {
|
||||
height: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
70%,
|
||||
100% {
|
||||
height: 100%;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ani-width {
|
||||
0% {
|
||||
width: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
70%,
|
||||
100% {
|
||||
width: 100%;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { customMergeColor } from '@/custom-component/de-decoration/component_details/config'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
interface Props {
|
||||
color?: string[]
|
||||
curStyle: { width: number; height: number }
|
||||
scale: number
|
||||
dur?: number
|
||||
line1Width?: number
|
||||
line2Width?: number
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
color: () => [],
|
||||
curStyle: () => ({
|
||||
width: 320,
|
||||
height: 240
|
||||
}),
|
||||
dur: 3,
|
||||
line1Width: 3,
|
||||
line2Width: 2
|
||||
})
|
||||
|
||||
// 尺寸计算
|
||||
const width = computed(() => props.curStyle.width)
|
||||
const height = computed(() => props.curStyle.height)
|
||||
|
||||
// 样式计算
|
||||
const border_style = computed(() => ({
|
||||
width: `${width.value}px`,
|
||||
height: `${height.value}px`,
|
||||
transform: `scale(${props.scale})`,
|
||||
'transform-origin': '0 0',
|
||||
'will-change': 'transform' // 提示浏览器优化
|
||||
}))
|
||||
|
||||
// 颜色配置
|
||||
const defaultColor = ref(['#3f96a5', '#3f96a5'])
|
||||
const mergedColor = ref<string[]>([])
|
||||
|
||||
// 路径数据
|
||||
const svgPaths = computed(() => {
|
||||
const line1Points = [
|
||||
[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 line2Points = [
|
||||
[width.value * 0.3, height.value * 0.8],
|
||||
[width.value * 0.7, height.value * 0.8]
|
||||
]
|
||||
|
||||
// 计算路径长度(简化版,实际项目中可以使用更精确的计算)
|
||||
const calculateLength = (points: number[][]) => {
|
||||
let length = 0
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
const [x1, y1] = points[i - 1]
|
||||
const [x2, y2] = points[i]
|
||||
length += Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
|
||||
}
|
||||
return length
|
||||
}
|
||||
|
||||
return {
|
||||
line1: {
|
||||
points: line1Points.map(p => p.join(',')).join(' '),
|
||||
length: calculateLength(line1Points),
|
||||
halfLength: calculateLength(line1Points) / 2
|
||||
},
|
||||
line2: {
|
||||
points: line2Points.map(p => p.join(',')).join(' '),
|
||||
length: calculateLength(line2Points),
|
||||
halfLength: calculateLength(line2Points) / 2
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const mergeColor = () => {
|
||||
mergedColor.value = customMergeColor(cloneDeep(defaultColor.value), props.color)
|
||||
}
|
||||
|
||||
// 生命周期和监听
|
||||
onMounted(mergeColor)
|
||||
watch(() => props.color, mergeColor, { immediate: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dv-decoration-5" :style="border_style">
|
||||
<svg :width="width" :height="height" class="decoration-svg">
|
||||
<!-- 主线条动画 -->
|
||||
<polyline
|
||||
fill="transparent"
|
||||
:stroke="mergedColor[0]"
|
||||
:stroke-width="line1Width"
|
||||
stroke-linecap="round"
|
||||
:points="svgPaths.line1.points"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dasharray"
|
||||
:values="`0,${svgPaths.line1.halfLength},0,${svgPaths.line1.halfLength};0,0,${svgPaths.line1.length},0`"
|
||||
:dur="`${dur}s`"
|
||||
calcMode="spline"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.4,1,0.49,0.98"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</polyline>
|
||||
|
||||
<!-- 副线条动画 -->
|
||||
<polyline
|
||||
fill="transparent"
|
||||
:stroke="mergedColor[1]"
|
||||
:stroke-width="line2Width"
|
||||
stroke-linecap="round"
|
||||
:points="svgPaths.line2.points"
|
||||
>
|
||||
<animate
|
||||
attributeName="stroke-dasharray"
|
||||
:values="`0,${svgPaths.line2.halfLength},0,${svgPaths.line2.halfLength};0,0,${svgPaths.line2.length},0`"
|
||||
:dur="`${dur}s`"
|
||||
calcMode="spline"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.4,1,0.49,0.98"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</polyline>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.dv-decoration-5 {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* 启用硬件加速 */
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
contain: content; /* 限制重绘范围 */
|
||||
|
||||
.decoration-svg {
|
||||
/* 优化SVG渲染 */
|
||||
shape-rendering: crispEdges;
|
||||
pointer-events: none;
|
||||
|
||||
polyline {
|
||||
transition: stroke 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
import DeBoard1 from '@/custom-component/de-decoration/component_details/DeBoard1.vue'
|
||||
import DeBoard2 from '@/custom-component/de-decoration/component_details/DeBoard2.vue'
|
||||
import DeBoard3 from '@/custom-component/de-decoration/component_details/DeBoard3.vue'
|
||||
import DeBoard4 from '@/custom-component/de-decoration/component_details/DeBoard4.vue'
|
||||
import DeBoard5 from '@/custom-component/de-decoration/component_details/DeBoard5.vue'
|
||||
import DeBoard6 from '@/custom-component/de-decoration/component_details/DeBoard6.vue'
|
||||
import DeBoard7 from '@/custom-component/de-decoration/component_details/DeBoard7.vue'
|
||||
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,
|
||||
DeBoard2: DeBoard2,
|
||||
DeBoard3: DeBoard3,
|
||||
DeBoard4: DeBoard4,
|
||||
DeBoard5: DeBoard5,
|
||||
DeBoard6: DeBoard6,
|
||||
DeBoard7: DeBoard7,
|
||||
DeBoard8: DeBoard8,
|
||||
DeBoard9: DeBoard9,
|
||||
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]))
|
||||
}
|
||||
|
||||
export function customMergeColor(defaultColor: string[], newColor: []) {
|
||||
return defaultColor.map((defaultVal, index) => {
|
||||
return newColor && newColor[index] !== null ? newColor[index] : defaultVal
|
||||
})
|
||||
}
|
||||
@@ -504,9 +504,15 @@ export function adaptCurThemeCommonStyle(component) {
|
||||
// 背景融合-Begin 如果是大屏['CanvasBoard', 'CanvasIcon', 'Picture']组件不需要设置背景
|
||||
if (
|
||||
dvMainStore.dvInfo.type === 'dataV' &&
|
||||
['CanvasBoard', 'CanvasIcon', 'Picture', 'Group', 'SvgTriangle', 'SvgStar'].includes(
|
||||
component.component
|
||||
)
|
||||
[
|
||||
'CanvasBoard',
|
||||
'CanvasIcon',
|
||||
'Picture',
|
||||
'Group',
|
||||
'SvgTriangle',
|
||||
'SvgStar',
|
||||
'DeDecoration'
|
||||
].includes(component.component)
|
||||
) {
|
||||
component.commonBackground['backgroundColorSelect'] = false
|
||||
component.commonBackground['innerPadding'] = 0
|
||||
|
||||
@@ -96,6 +96,9 @@ export function findNewComponent(componentName, innerType, staticMap?) {
|
||||
if (newComponent.isPlugin) {
|
||||
newComponent.staticMap = staticMap
|
||||
}
|
||||
} else if (['DeDecoration', 'DynamicBackground'].includes(componentName)) {
|
||||
newComponent.style.borderWidth = 0
|
||||
newComponent.style.innerPadding = 0
|
||||
}
|
||||
return newComponent
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import ScrollText from '@/custom-component/scroll-text/Component.vue'
|
||||
import PopArea from '@/custom-component/pop-area/Component.vue'
|
||||
import PictureGroup from '@/custom-component/picture-group/Component.vue'
|
||||
import DeScreen from '@/custom-component/de-screen/Component.vue'
|
||||
import DeDecoration from '@/custom-component/de-decoration/Component.vue'
|
||||
export const componentsMap = {
|
||||
VText: VText,
|
||||
VQuery,
|
||||
@@ -31,6 +32,7 @@ export const componentsMap = {
|
||||
DeGraphical: DeGraphical,
|
||||
CircleShape: CircleShape,
|
||||
RectShape: RectShape,
|
||||
DeDecoration: DeDecoration,
|
||||
SvgTriangle: SvgTriangle,
|
||||
DeTimeClock: DeTimeClock,
|
||||
GroupArea: GroupArea,
|
||||
|
||||
@@ -9,6 +9,7 @@ import DeScreenAttr from '@/custom-component/de-screen/Attr.vue'
|
||||
import DeGraphicalAttr from '@/custom-component/de-graphical/Attr.vue'
|
||||
import CircleShapeAttr from '@/custom-component/circle-shape/Attr.vue'
|
||||
import RectShapeAttr from '@/custom-component/rect-shape/Attr.vue'
|
||||
import DeDecorationAttr from '@/custom-component/de-decoration/Attr.vue'
|
||||
import SvgTriangleAttr from '@/custom-component/svgs/svg-triangle/Attr.vue'
|
||||
import DeTimeClockAttr from '@/custom-component/de-time-clock/Attr.vue'
|
||||
import GroupAreaAttr from '@/custom-component/group-area/Attr.vue'
|
||||
@@ -29,6 +30,7 @@ export const componentsMap = {
|
||||
DeGraphicalAttr: DeGraphicalAttr,
|
||||
CircleShapeAttr: CircleShapeAttr,
|
||||
RectShapeAttr: RectShapeAttr,
|
||||
DeDecorationAttr: DeDecorationAttr,
|
||||
SvgTriangleAttr: SvgTriangleAttr,
|
||||
DeTimeClockAttr: DeTimeClockAttr,
|
||||
GroupAreaAttr: GroupAreaAttr,
|
||||
|
||||
Reference in New Issue
Block a user