|
|
@@ -1,48 +1,99 @@
|
|
|
<template>
|
|
|
- <div :style="{
|
|
|
- ...styleMap?.mainStyle
|
|
|
- }" class="relative w-full h-full box-border overflow-hidden relative">
|
|
|
+ <div
|
|
|
+ :style="{
|
|
|
+ ...styleMap?.mainStyle
|
|
|
+ }"
|
|
|
+ class="relative w-full h-full box-border overflow-hidden relative"
|
|
|
+ >
|
|
|
+ <ImageBg
|
|
|
+ v-if="styleMap?.mainStyle?.imageSrc"
|
|
|
+ :src="styleMap?.mainStyle?.imageSrc"
|
|
|
+ :imageStyle="styleMap?.mainStyle?.imageStyle"
|
|
|
+ />
|
|
|
|
|
|
- <ImageBg v-if="styleMap?.mainStyle?.imageSrc" :src="styleMap?.mainStyle?.imageSrc"
|
|
|
- :imageStyle="styleMap?.mainStyle?.imageStyle" />
|
|
|
-
|
|
|
- <div class="absolute inset-0 w-full h-full" :style="{ transform: `rotate(${props.rotate}deg)` }">
|
|
|
- <svg :viewBox="`0 0 ${width} ${height}`" preserveAspectRatio="xMidYMid meet" class="w-full h-full block"
|
|
|
- xmlns="http://www.w3.org/2000/svg">
|
|
|
+ <div
|
|
|
+ class="absolute inset-0 w-full h-full"
|
|
|
+ :style="{ transform: `rotate(${props.rotate}deg)` }"
|
|
|
+ >
|
|
|
+ <svg
|
|
|
+ :viewBox="`0 0 ${width} ${height}`"
|
|
|
+ preserveAspectRatio="xMidYMid meet"
|
|
|
+ class="w-full h-full block"
|
|
|
+ xmlns="http://www.w3.org/2000/svg"
|
|
|
+ >
|
|
|
<defs>
|
|
|
<!-- 背景条图片 pattern:保持图像原始尺寸,居中显示 -->
|
|
|
- <pattern v-if="styleMap?.mainStyle?.curve?.imageSrc" :id="`${arcId}-bg-pattern`" patternUnits="userSpaceOnUse"
|
|
|
- :width="width" :height="height" x="0" y="0">
|
|
|
+ <pattern
|
|
|
+ v-if="styleMap?.mainStyle?.curve?.imageSrc"
|
|
|
+ :id="`${arcId}-bg-pattern`"
|
|
|
+ patternUnits="userSpaceOnUse"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ x="0"
|
|
|
+ y="0"
|
|
|
+ >
|
|
|
<image :href="styleMap.mainStyle.curve.imageSrc" transform="translate(-50%, -50%)" />
|
|
|
</pattern>
|
|
|
<!-- 进度值条图片 pattern:保持图像原始尺寸,居中显示 -->
|
|
|
- <pattern v-if="styleMap?.indicatorStyle?.curve?.imageSrc" :id="`${arcId}-indicator-pattern`"
|
|
|
- patternUnits="userSpaceOnUse" :width="width" :height="height" x="0" y="0">
|
|
|
- <image :href="styleMap.indicatorStyle.curve.imageSrc" transform="translate(-50%, -50%)" />
|
|
|
+ <pattern
|
|
|
+ v-if="styleMap?.indicatorStyle?.curve?.imageSrc"
|
|
|
+ :id="`${arcId}-indicator-pattern`"
|
|
|
+ patternUnits="userSpaceOnUse"
|
|
|
+ :width="width"
|
|
|
+ :height="height"
|
|
|
+ x="0"
|
|
|
+ y="0"
|
|
|
+ >
|
|
|
+ <image
|
|
|
+ :href="styleMap.indicatorStyle.curve.imageSrc"
|
|
|
+ transform="translate(-50%, -50%)"
|
|
|
+ />
|
|
|
</pattern>
|
|
|
</defs>
|
|
|
<!-- 背景条:绘制完整的起始到结束角度 -->
|
|
|
- <path :d="bgPath" fill="none"
|
|
|
- :stroke="styleMap?.mainStyle?.curve?.imageSrc ? `url(#${arcId}-bg-pattern)` : (styleMap?.mainStyle?.curve?.color || '#eeeeee')"
|
|
|
+ <path
|
|
|
+ :d="bgPath"
|
|
|
+ fill="none"
|
|
|
+ :stroke="
|
|
|
+ styleMap?.mainStyle?.curve?.imageSrc
|
|
|
+ ? `url(#${arcId}-bg-pattern)`
|
|
|
+ : styleMap?.mainStyle?.curve?.color || '#eeeeee'
|
|
|
+ "
|
|
|
:stroke-width="styleMap?.mainStyle?.curve?.width ?? 1"
|
|
|
:stroke-linecap="styleMap?.mainStyle?.curve?.radius ? 'round' : 'butt'"
|
|
|
- :opacity="styleMap?.mainStyle?.curve?.opacity" />
|
|
|
+ :opacity="styleMap?.mainStyle?.curve?.opacity"
|
|
|
+ />
|
|
|
|
|
|
<!-- 进度值条 -->
|
|
|
- <path :d="valuePath" fill="none"
|
|
|
- :stroke="styleMap?.indicatorStyle?.curve?.imageSrc ? `url(#${arcId}-indicator-pattern)` : (styleMap?.indicatorStyle?.curve?.color || '#2092f5')"
|
|
|
+ <path
|
|
|
+ :d="valuePath"
|
|
|
+ fill="none"
|
|
|
+ :stroke="
|
|
|
+ styleMap?.indicatorStyle?.curve?.imageSrc
|
|
|
+ ? `url(#${arcId}-indicator-pattern)`
|
|
|
+ : styleMap?.indicatorStyle?.curve?.color || '#2092f5'
|
|
|
+ "
|
|
|
:stroke-width="styleMap?.indicatorStyle?.curve?.width ?? 1"
|
|
|
:stroke-linecap="styleMap?.indicatorStyle?.curve?.radius ? 'round' : 'butt'"
|
|
|
- :opacity="styleMap?.indicatorStyle?.curve?.opacity" />
|
|
|
+ :opacity="styleMap?.indicatorStyle?.curve?.opacity"
|
|
|
+ />
|
|
|
|
|
|
<!-- 进度圆点:无背景图时用实心圆 -->
|
|
|
- <circle v-if="dotPos && !styleMap?.knobStyle?.imageSrc" :cx="dotPos.x" :cy="dotPos.y" :r="knobRadius.radius"
|
|
|
- :fill="styleMap?.knobStyle?.backgroundColor || '#2092f5'" />
|
|
|
+ <circle
|
|
|
+ v-if="dotPos && !styleMap?.knobStyle?.imageSrc"
|
|
|
+ :cx="dotPos.x"
|
|
|
+ :cy="dotPos.y"
|
|
|
+ :r="knobRadius.radius"
|
|
|
+ :fill="styleMap?.knobStyle?.backgroundColor || '#2092f5'"
|
|
|
+ />
|
|
|
</svg>
|
|
|
|
|
|
<!-- 进度圆点背景图:与 SVG 同坐标系,用 ImageBg 统一实现 -->
|
|
|
- <div v-if="dotPos && styleMap?.knobStyle?.imageSrc" class="absolute overflow-hidden pointer-events-none"
|
|
|
- :style="knobOverlayStyle">
|
|
|
+ <div
|
|
|
+ v-if="dotPos && styleMap?.knobStyle?.imageSrc"
|
|
|
+ class="absolute overflow-hidden pointer-events-none"
|
|
|
+ :style="knobOverlayStyle"
|
|
|
+ >
|
|
|
<ImageBg :src="styleMap.knobStyle.imageSrc" :imageStyle="styleMap.knobStyle.imageStyle" />
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -52,9 +103,9 @@
|
|
|
<script setup lang="ts">
|
|
|
import { computed, ref } from 'vue'
|
|
|
import { useWidgetStyle } from '../hooks/useWidgetStyle'
|
|
|
-import { useProjectStore } from '@/store/modules/project';
|
|
|
+import { useProjectStore } from '@/store/modules/project'
|
|
|
|
|
|
-import ImageBg from '../ImageBg.vue';
|
|
|
+import ImageBg from '../ImageBg.vue'
|
|
|
|
|
|
const arcId = ref('arc-' + Math.random().toString(36).slice(2))
|
|
|
|
|
|
@@ -108,8 +159,12 @@ const knobRadius = computed(() => {
|
|
|
?.find((item) => item.widget === 'lv_arc')
|
|
|
?.part?.find((item) => item.partName === 'knob')?.defaultStyle
|
|
|
|
|
|
- const indicatorStyle = props.styles?.find((s: any) => s.part?.name === 'indicator' && s.part.state === props.state)
|
|
|
- const knobStyle = props.styles?.find((s: any) => s.part?.name === 'knob' && s.part.state === props.state)
|
|
|
+ const indicatorStyle = props.styles?.find(
|
|
|
+ (s: any) => s.part?.name === 'indicator' && s.part.state === props.state
|
|
|
+ )
|
|
|
+ const knobStyle = props.styles?.find(
|
|
|
+ (s: any) => s.part?.name === 'knob' && s.part.state === props.state
|
|
|
+ )
|
|
|
const padding = knobStyle?.padding?.left ?? defaultKnobStyle?.padding?.left
|
|
|
const r = indicatorStyle?.curve?.width ?? defaultIndicatorStyle?.curve?.width ?? 12
|
|
|
|
|
|
@@ -132,6 +187,10 @@ function polarToCartesian(
|
|
|
) {
|
|
|
// LVGL 习惯通常 0 度在右侧,如果需要 0 度在上方,这里减去 90
|
|
|
// 这里我们遵循标准:angleStart 为输入值
|
|
|
+ // 当angleInDegress为360时,需要偏移一点
|
|
|
+ if (angleInDegrees === 360) {
|
|
|
+ angleInDegrees = 359.999
|
|
|
+ }
|
|
|
const radians = ((angleInDegrees - 0) * Math.PI) / 180.0
|
|
|
return {
|
|
|
x: centerX + radius * Math.cos(radians),
|
|
|
@@ -188,9 +247,7 @@ const progressData = computed(() => {
|
|
|
|
|
|
const rangeDiff = rangeEnd - rangeStart
|
|
|
const ratio =
|
|
|
- Math.abs(rangeDiff) < 1e-9
|
|
|
- ? 0
|
|
|
- : Math.max(0, Math.min(1, (value - rangeStart) / rangeDiff))
|
|
|
+ Math.abs(rangeDiff) < 1e-9 ? 0 : Math.max(0, Math.min(1, (value - rangeStart) / rangeDiff))
|
|
|
|
|
|
const span = clockwiseSpan.value
|
|
|
let startA = angleStart
|
|
|
@@ -235,10 +292,9 @@ const valuePath = computed(() => {
|
|
|
// 4. 圆点位置(与弧同半径)
|
|
|
const dotPos = computed(() => {
|
|
|
// 原始结束角:normal / symmetrical 用 endA,reverse 用 startA
|
|
|
- const baseAngle =
|
|
|
- props.mode === 'reverse' ? progressData.value.startA : progressData.value.endA
|
|
|
+ const baseAngle = props.mode === 'reverse' ? progressData.value.startA : progressData.value.endA
|
|
|
// 仅对“结束值原点”做偏移,不整体偏移整条进度弧
|
|
|
- const offset = props.rotateOffset ? props.rotateOffsetValue ?? 0 : 0
|
|
|
+ const offset = props.rotateOffset ? (props.rotateOffsetValue ?? 0) : 0
|
|
|
const angle = baseAngle + offset
|
|
|
return polarToCartesian(cx.value, cx.value, trackRadius.value, angle)
|
|
|
})
|
|
|
@@ -251,8 +307,8 @@ const knobOverlayStyle = computed(() => {
|
|
|
return {
|
|
|
left: `${((x - r) / props.width) * 100}%`,
|
|
|
top: `${((y - r) / props.height) * 100}%`,
|
|
|
- width: `${(r * 2 / props.width) * 100}%`,
|
|
|
- height: `${(r * 2 / props.height) * 100}%`,
|
|
|
+ width: `${((r * 2) / props.width) * 100}%`,
|
|
|
+ height: `${((r * 2) / props.height) * 100}%`,
|
|
|
borderRadius: '50%'
|
|
|
}
|
|
|
})
|