jvs-ppt-ui 0.0.15 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/jvs-ppt-ui.es.js +20301 -19823
- package/dist/lib/jvs-ppt-ui.umd.js +99 -99
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CanvasArea.vue +14 -115
- package/src/components/ChartTypeSelector.vue +34 -30
- package/src/components/Header.vue +40 -5
- package/src/components/PresentationGroupChild.vue +44 -13
- package/src/components/PresentationMode.vue +84 -33
- package/src/components/SidebarLeft.vue +96 -18
- package/src/components/SidebarRight.vue +6 -3
- package/src/components/Toolbar.vue +1 -1
- package/src/components/canvas/elements/CanvasShapeElement.vue +91 -34
- package/src/components/sidebar/common/SidebarBorderAccordion.vue +2 -2
- package/src/components/sidebar/common/SidebarReflectionAccordion.vue +2 -2
- package/src/components/sidebar/common/SidebarShadowAccordion.vue +2 -2
- package/src/components/sidebar/panels/SidebarChartFormatPanel.vue +3 -3
- package/src/components/sidebar/panels/SidebarDesignPanel.vue +170 -9
- package/src/components/sidebar/panels/SidebarShapeFormatPanel.vue +176 -16
- package/src/components/sidebar/panels/SidebarTableFormatPanel.vue +2 -2
- package/src/components/sidebar/panels/SidebarTextFormatPanel.vue +2 -2
- package/src/components/sidebar/panels/SidebarTransitionPanel.vue +30 -19
- package/src/composables/canvas/useCanvasTransform.ts +24 -0
- package/src/composables/sidebar/useSidebarShapeFormat.ts +248 -17
- package/src/composables/useSlides.ts +12 -0
- package/src/style.css +4 -2
- package/src/utils/canvas/elementStyles.ts +22 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { computed, ref, watch, onBeforeUnmount, nextTick } from 'vue'
|
|
3
3
|
import {
|
|
4
4
|
getElementPattern,
|
|
5
|
+
isElementPatternTile,
|
|
6
|
+
getElementPatternTileSize,
|
|
5
7
|
getElementGradientConfig,
|
|
6
8
|
getElementGradientCss,
|
|
7
9
|
getSvgGradientCoords,
|
|
@@ -24,7 +26,8 @@ import { createTextSelectionOverlay, type TextSelectionOverlayHandle } from '../
|
|
|
24
26
|
|
|
25
27
|
const { shapeFormatPainter, isReadonly } = useSlides()
|
|
26
28
|
const isTextPaintActive = computed(() => !!(shapeFormatPainter.value && shapeFormatPainter.value.mode === 'text-range'))
|
|
27
|
-
const
|
|
29
|
+
const svgIdKey = computed(() => props.isReflection ? `${props.element.id}-refl` : `${props.element.id}`)
|
|
30
|
+
const isTextSelectable = computed(() => !props.isReflection && ((props.isEditingText && props.selectedElementId === props.element.id) || isTextPaintActive.value))
|
|
28
31
|
|
|
29
32
|
function getEffectiveBorderWidth(el: any): number {
|
|
30
33
|
if (props.isImagePlaceholder && props.isImagePlaceholder(el) && el.style?.enableBorder !== true) return 0
|
|
@@ -35,6 +38,23 @@ function getEffectiveBorderWidth(el: any): number {
|
|
|
35
38
|
return 0
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
function getShapePatternTransform(el: any, isTriangle = false): string | undefined {
|
|
42
|
+
if (!el || !isElementPatternTile(el)) return undefined
|
|
43
|
+
const { width, height } = getElementWidthHeight(el)
|
|
44
|
+
if (isTriangle) {
|
|
45
|
+
const sx = width > 0 ? 100 / width : 1
|
|
46
|
+
const sy = height > 0 ? 100 / height : 1
|
|
47
|
+
return `scale(${sx}, ${sy})`
|
|
48
|
+
}
|
|
49
|
+
const vbW = el.viewBox ? el.viewBox[0] : (width || 200)
|
|
50
|
+
const vbH = el.viewBox ? el.viewBox[1] : (height || 200)
|
|
51
|
+
const hasBorder = hasElementBorder(el)
|
|
52
|
+
const bw = hasBorder ? getEffectiveBorderWidth(el) : 0
|
|
53
|
+
const effW = bw > 0 ? Math.max(1, width - bw) : Math.max(1, width)
|
|
54
|
+
const effH = bw > 0 ? Math.max(1, height - bw) : Math.max(1, height)
|
|
55
|
+
return `scale(${vbW / effW}, ${vbH / effH})`
|
|
56
|
+
}
|
|
57
|
+
|
|
38
58
|
function getShapeSvgTransform(el: any): string | undefined {
|
|
39
59
|
if (!el) return undefined
|
|
40
60
|
const { width, height } = getElementWidthHeight(el)
|
|
@@ -65,7 +85,7 @@ function getShapeSvgTransform(el: any): string | undefined {
|
|
|
65
85
|
function getEffectiveBorderColor(el: any, fallback = '#10b981'): string {
|
|
66
86
|
if (props.isImagePlaceholder && props.isImagePlaceholder(el) && el.style?.enableBorder !== true) return 'transparent'
|
|
67
87
|
if (el.style?.enableBorder === false) return 'transparent'
|
|
68
|
-
if (hasElementBorderGradient(el)) return `url(#shape-border-gradient-${
|
|
88
|
+
if (hasElementBorderGradient(el)) return `url(#shape-border-gradient-${svgIdKey.value})`
|
|
69
89
|
return el.style?.borderColor || getElementBorderColor(el) || fallback
|
|
70
90
|
}
|
|
71
91
|
|
|
@@ -102,22 +122,32 @@ function isRectShape(el: any): boolean {
|
|
|
102
122
|
|
|
103
123
|
function getRectRadiusInVb(el: any): { rx: number; ry: number } {
|
|
104
124
|
if (!el) return { rx: 0, ry: 0 }
|
|
125
|
+
const { width, height } = getElementWidthHeight(el)
|
|
105
126
|
let r = 0
|
|
127
|
+
const isRoundRect = el.pathFormula === 'roundRect' || el.shapType === 'roundRect' || el.title === '圆角矩形'
|
|
106
128
|
const rVal = el.style?.borderRadius !== undefined && el.style?.borderRadius !== null && el.style?.borderRadius !== ''
|
|
107
129
|
? el.style.borderRadius
|
|
108
130
|
: el.radius
|
|
109
131
|
|
|
110
|
-
if (
|
|
132
|
+
if (isRoundRect && Array.isArray(el.keypoints) && el.keypoints[0] !== undefined && el.keypoints[0] !== null) {
|
|
133
|
+
const kp = Number(el.keypoints[0])
|
|
134
|
+
if (!isNaN(kp)) {
|
|
135
|
+
r = Math.min(width, height) * Math.max(0, Math.min(0.5, kp))
|
|
136
|
+
}
|
|
137
|
+
} else if (isRoundRect && el.keypoints && typeof el.keypoints === 'object' && !Array.isArray(el.keypoints) && (el.keypoints as any).adj !== undefined) {
|
|
138
|
+
const adj = Number((el.keypoints as any).adj)
|
|
139
|
+
if (!isNaN(adj)) {
|
|
140
|
+
r = Math.min(width, height) * Math.max(0, Math.min(0.5, adj * 0.5))
|
|
141
|
+
}
|
|
142
|
+
} else if (rVal !== undefined && rVal !== null && rVal !== '') {
|
|
111
143
|
const parsed = parseFloat(String(rVal))
|
|
112
144
|
if (!isNaN(parsed)) r = parsed
|
|
113
|
-
} else if (
|
|
114
|
-
|
|
115
|
-
r = Math.min(width, height) * 0.15
|
|
145
|
+
} else if (isRoundRect) {
|
|
146
|
+
r = Math.min(width, height) * 0.125
|
|
116
147
|
}
|
|
117
148
|
|
|
118
149
|
if (r <= 0) return { rx: 0, ry: 0 }
|
|
119
150
|
|
|
120
|
-
const { width, height } = getElementWidthHeight(el)
|
|
121
151
|
const { w: vbW, h: vbH } = getShapeVb(el)
|
|
122
152
|
|
|
123
153
|
const maxR = Math.min(width, height) / 2
|
|
@@ -146,6 +176,7 @@ const props = defineProps<{
|
|
|
146
176
|
getElementTextShadow?: (el: any) => string
|
|
147
177
|
getReflectionLeadingCompensation?: (el: any) => number
|
|
148
178
|
isImagePlaceholder?: (el: any) => boolean
|
|
179
|
+
isReflection?: boolean
|
|
149
180
|
// 直接父盒子(组合)的像素尺寸,用于把 % 样式的宽高解析成 px。
|
|
150
181
|
// 由 CanvasGroupElement 传入;顶层元素不传,走 px 样式分支。
|
|
151
182
|
parentWidthPx?: number
|
|
@@ -345,9 +376,9 @@ function formatShapeHtml(el: any): string {
|
|
|
345
376
|
height: '100%',
|
|
346
377
|
backgroundColor: getElementPattern(element) ? 'transparent' : ((getElementGradientCss(element) || (getElementFill(element) !== undefined && getElementFill(element) !== 'none')) ? (getElementGradientCss(element) ? 'transparent' : getElementFill(element)) : (getElementFill(element) === 'none' ? 'transparent' : '#3b82f6')),
|
|
347
378
|
backgroundImage: getElementPattern(element) ? `url('${getElementPattern(element)}')` : (getElementGradientCss(element) || 'none'),
|
|
348
|
-
backgroundSize: getElementPattern(element) ? 'cover' : undefined,
|
|
349
|
-
backgroundPosition: getElementPattern(element) ? 'center center' : undefined,
|
|
350
|
-
backgroundRepeat: getElementPattern(element) ? 'no-repeat' : undefined,
|
|
379
|
+
backgroundSize: getElementPattern(element) ? (isElementPatternTile(element) ? `${getElementPatternTileSize(element).w}px ${getElementPatternTileSize(element).h}px` : 'cover') : undefined,
|
|
380
|
+
backgroundPosition: getElementPattern(element) ? (isElementPatternTile(element) ? '0 0' : 'center center') : undefined,
|
|
381
|
+
backgroundRepeat: getElementPattern(element) ? (isElementPatternTile(element) ? 'repeat' : 'no-repeat') : undefined,
|
|
351
382
|
borderRadius: '50%',
|
|
352
383
|
borderWidth: (isImagePlaceholder && isImagePlaceholder(element) && element.style?.enableBorder !== true)
|
|
353
384
|
? '0px'
|
|
@@ -375,7 +406,7 @@ function formatShapeHtml(el: any): string {
|
|
|
375
406
|
<defs v-if="getElementBorderGradientConfig(element)">
|
|
376
407
|
<radialGradient
|
|
377
408
|
v-if="getElementBorderGradientConfig(element)!.path && getElementBorderGradientConfig(element)!.path !== 'line'"
|
|
378
|
-
:id="`shape-border-gradient-${
|
|
409
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
379
410
|
:cx="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.x * 100) : 50}%`"
|
|
380
411
|
:cy="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.y * 100) : 50}%`"
|
|
381
412
|
:r="getElementBorderGradientConfig(element)!.path === 'rect' ? '60%' : (getElementBorderGradientConfig(element)!.path === 'path' ? '70%' : '50%')"
|
|
@@ -392,7 +423,7 @@ function formatShapeHtml(el: any): string {
|
|
|
392
423
|
</radialGradient>
|
|
393
424
|
<linearGradient
|
|
394
425
|
v-else
|
|
395
|
-
:id="`shape-border-gradient-${
|
|
426
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
396
427
|
:x1="getSvgGradientCoords(getElementBorderGradientConfig(element)).x1"
|
|
397
428
|
:y1="getSvgGradientCoords(getElementBorderGradientConfig(element)).y1"
|
|
398
429
|
:x2="getSvgGradientCoords(getElementBorderGradientConfig(element)).x2"
|
|
@@ -430,13 +461,26 @@ function formatShapeHtml(el: any): string {
|
|
|
430
461
|
:style="{ filter: (element.shadow && element.shadow.type === 'inner') ? `url(#shape-inner-shadow-${element.id})` : '' }"
|
|
431
462
|
>
|
|
432
463
|
<defs>
|
|
433
|
-
<pattern
|
|
434
|
-
|
|
464
|
+
<pattern
|
|
465
|
+
v-if="getElementPattern(element)"
|
|
466
|
+
:id="`shape-pattern-${svgIdKey}`"
|
|
467
|
+
:width="isElementPatternTile(element) ? getElementPatternTileSize(element).w : '100%'"
|
|
468
|
+
:height="isElementPatternTile(element) ? getElementPatternTileSize(element).h : '100%'"
|
|
469
|
+
:patternUnits="isElementPatternTile(element) ? 'userSpaceOnUse' : 'objectBoundingBox'"
|
|
470
|
+
:patternContentUnits="isElementPatternTile(element) ? 'userSpaceOnUse' : 'objectBoundingBox'"
|
|
471
|
+
:patternTransform="getShapePatternTransform(element, true)"
|
|
472
|
+
>
|
|
473
|
+
<image
|
|
474
|
+
:href="getElementPattern(element)"
|
|
475
|
+
:width="isElementPatternTile(element) ? getElementPatternTileSize(element).w : 1"
|
|
476
|
+
:height="isElementPatternTile(element) ? getElementPatternTileSize(element).h : 1"
|
|
477
|
+
preserveAspectRatio="none"
|
|
478
|
+
/>
|
|
435
479
|
</pattern>
|
|
436
480
|
<template v-if="getElementGradientConfig(element)">
|
|
437
481
|
<radialGradient
|
|
438
482
|
v-if="getElementGradientConfig(element)!.path && getElementGradientConfig(element)!.path !== 'line'"
|
|
439
|
-
:id="`shape-gradient-${
|
|
483
|
+
:id="`shape-gradient-${svgIdKey}`"
|
|
440
484
|
:cx="`${getElementGradientConfig(element)!.center ? Math.round(getElementGradientConfig(element)!.center!.x * 100) : 50}%`"
|
|
441
485
|
:cy="`${getElementGradientConfig(element)!.center ? Math.round(getElementGradientConfig(element)!.center!.y * 100) : 50}%`"
|
|
442
486
|
:r="getElementGradientConfig(element)!.path === 'rect' ? '60%' : (getElementGradientConfig(element)!.path === 'path' ? '70%' : '50%')"
|
|
@@ -453,7 +497,7 @@ function formatShapeHtml(el: any): string {
|
|
|
453
497
|
</radialGradient>
|
|
454
498
|
<linearGradient
|
|
455
499
|
v-else
|
|
456
|
-
:id="`shape-gradient-${
|
|
500
|
+
:id="`shape-gradient-${svgIdKey}`"
|
|
457
501
|
:x1="getSvgGradientCoords(element).x1"
|
|
458
502
|
:y1="getSvgGradientCoords(element).y1"
|
|
459
503
|
:x2="getSvgGradientCoords(element).x2"
|
|
@@ -471,7 +515,7 @@ function formatShapeHtml(el: any): string {
|
|
|
471
515
|
<template v-if="getElementBorderGradientConfig(element)">
|
|
472
516
|
<radialGradient
|
|
473
517
|
v-if="getElementBorderGradientConfig(element)!.path && getElementBorderGradientConfig(element)!.path !== 'line'"
|
|
474
|
-
:id="`shape-border-gradient-${
|
|
518
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
475
519
|
:cx="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.x * 100) : 50}%`"
|
|
476
520
|
:cy="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.y * 100) : 50}%`"
|
|
477
521
|
:r="getElementBorderGradientConfig(element)!.path === 'rect' ? '60%' : (getElementBorderGradientConfig(element)!.path === 'path' ? '70%' : '50%')"
|
|
@@ -488,7 +532,7 @@ function formatShapeHtml(el: any): string {
|
|
|
488
532
|
</radialGradient>
|
|
489
533
|
<linearGradient
|
|
490
534
|
v-else
|
|
491
|
-
:id="`shape-border-gradient-${
|
|
535
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
492
536
|
:x1="getSvgGradientCoords(getElementBorderGradientConfig(element)).x1"
|
|
493
537
|
:y1="getSvgGradientCoords(getElementBorderGradientConfig(element)).y1"
|
|
494
538
|
:x2="getSvgGradientCoords(getElementBorderGradientConfig(element)).x2"
|
|
@@ -506,7 +550,7 @@ function formatShapeHtml(el: any): string {
|
|
|
506
550
|
</defs>
|
|
507
551
|
<polygon
|
|
508
552
|
points="50,0 0,100 100,100"
|
|
509
|
-
:fill="hasElementFill(element) ? (getElementPattern(element) ? `url(#shape-pattern-${
|
|
553
|
+
:fill="hasElementFill(element) ? (getElementPattern(element) ? `url(#shape-pattern-${svgIdKey})` : (getElementGradientConfig(element) ? `url(#shape-gradient-${svgIdKey})` : (element.style?.fill !== undefined ? element.style.fill : '#f59e0b'))) : 'none'"
|
|
510
554
|
:stroke="getEffectiveBorderColor(element, '#d97706')"
|
|
511
555
|
:stroke-width="getEffectiveBorderWidth(element)"
|
|
512
556
|
:stroke-dasharray="element.style?.borderStyle === 'dashed' ? '5 5' : ''"
|
|
@@ -525,13 +569,26 @@ function formatShapeHtml(el: any): string {
|
|
|
525
569
|
:style="{ filter: (element.shadow && element.shadow.type === 'inner') ? `url(#shape-inner-shadow-${element.id})` : '' }"
|
|
526
570
|
>
|
|
527
571
|
<defs>
|
|
528
|
-
<pattern
|
|
529
|
-
|
|
572
|
+
<pattern
|
|
573
|
+
v-if="getElementPattern(element)"
|
|
574
|
+
:id="`shape-pattern-${svgIdKey}`"
|
|
575
|
+
:width="isElementPatternTile(element) ? getElementPatternTileSize(element).w : '100%'"
|
|
576
|
+
:height="isElementPatternTile(element) ? getElementPatternTileSize(element).h : '100%'"
|
|
577
|
+
:patternUnits="isElementPatternTile(element) ? 'userSpaceOnUse' : 'objectBoundingBox'"
|
|
578
|
+
:patternContentUnits="isElementPatternTile(element) ? 'userSpaceOnUse' : 'objectBoundingBox'"
|
|
579
|
+
:patternTransform="getShapePatternTransform(element, false)"
|
|
580
|
+
>
|
|
581
|
+
<image
|
|
582
|
+
:href="getElementPattern(element)"
|
|
583
|
+
:width="isElementPatternTile(element) ? getElementPatternTileSize(element).w : 1"
|
|
584
|
+
:height="isElementPatternTile(element) ? getElementPatternTileSize(element).h : 1"
|
|
585
|
+
preserveAspectRatio="none"
|
|
586
|
+
/>
|
|
530
587
|
</pattern>
|
|
531
588
|
<template v-if="getElementGradientConfig(element)">
|
|
532
589
|
<radialGradient
|
|
533
590
|
v-if="getElementGradientConfig(element)!.path && getElementGradientConfig(element)!.path !== 'line'"
|
|
534
|
-
:id="`shape-gradient-${
|
|
591
|
+
:id="`shape-gradient-${svgIdKey}`"
|
|
535
592
|
:cx="`${getElementGradientConfig(element)!.center ? Math.round(getElementGradientConfig(element)!.center!.x * 100) : 50}%`"
|
|
536
593
|
:cy="`${getElementGradientConfig(element)!.center ? Math.round(getElementGradientConfig(element)!.center!.y * 100) : 50}%`"
|
|
537
594
|
:r="getElementGradientConfig(element)!.path === 'rect' ? '60%' : (getElementGradientConfig(element)!.path === 'path' ? '70%' : '50%')"
|
|
@@ -548,7 +605,7 @@ function formatShapeHtml(el: any): string {
|
|
|
548
605
|
</radialGradient>
|
|
549
606
|
<linearGradient
|
|
550
607
|
v-else
|
|
551
|
-
:id="`shape-gradient-${
|
|
608
|
+
:id="`shape-gradient-${svgIdKey}`"
|
|
552
609
|
:x1="getSvgGradientCoords(element).x1"
|
|
553
610
|
:y1="getSvgGradientCoords(element).y1"
|
|
554
611
|
:x2="getSvgGradientCoords(element).x2"
|
|
@@ -566,7 +623,7 @@ function formatShapeHtml(el: any): string {
|
|
|
566
623
|
<template v-if="getElementBorderGradientConfig(element)">
|
|
567
624
|
<radialGradient
|
|
568
625
|
v-if="getElementBorderGradientConfig(element)!.path && getElementBorderGradientConfig(element)!.path !== 'line'"
|
|
569
|
-
:id="`shape-border-gradient-${
|
|
626
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
570
627
|
:cx="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.x * 100) : 50}%`"
|
|
571
628
|
:cy="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.y * 100) : 50}%`"
|
|
572
629
|
:r="getElementBorderGradientConfig(element)!.path === 'rect' ? '60%' : (getElementBorderGradientConfig(element)!.path === 'path' ? '70%' : '50%')"
|
|
@@ -583,7 +640,7 @@ function formatShapeHtml(el: any): string {
|
|
|
583
640
|
</radialGradient>
|
|
584
641
|
<linearGradient
|
|
585
642
|
v-else
|
|
586
|
-
:id="`shape-border-gradient-${
|
|
643
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
587
644
|
:x1="getSvgGradientCoords(getElementBorderGradientConfig(element)).x1"
|
|
588
645
|
:y1="getSvgGradientCoords(getElementBorderGradientConfig(element)).y1"
|
|
589
646
|
:x2="getSvgGradientCoords(getElementBorderGradientConfig(element)).x2"
|
|
@@ -611,7 +668,7 @@ function formatShapeHtml(el: any): string {
|
|
|
611
668
|
:rx="getRectRadiusInVb(element).rx"
|
|
612
669
|
:ry="getRectRadiusInVb(element).ry"
|
|
613
670
|
vector-effect="non-scaling-stroke"
|
|
614
|
-
:fill="hasElementFill(element) ? (getElementPattern(element) ? `url(#shape-pattern-${
|
|
671
|
+
:fill="hasElementFill(element) ? (getElementPattern(element) ? `url(#shape-pattern-${svgIdKey})` : (getElementGradientConfig(element) ? `url(#shape-gradient-${svgIdKey})` : (getElementFill(element) !== undefined ? getElementFill(element) : '#3b82f6'))) : 'none'"
|
|
615
672
|
:stroke="(isImagePlaceholder && isImagePlaceholder(element) && element.style?.enableBorder !== true) ? 'none' : (element.style?.enableBorder === false ? 'none' : getEffectiveBorderColor(element, 'none'))"
|
|
616
673
|
:stroke-width="(isImagePlaceholder && isImagePlaceholder(element) && element.style?.enableBorder !== true) ? 0 : (element.style?.enableBorder === false ? 0 : getElementBorderWidth(element))"
|
|
617
674
|
:stroke-linecap="element.style?.strokeLinecap || element.borderLineCap || element.lineCap || (element.shapType === 'arc' ? 'round' : (isOpenLineShape(element) ? 'butt' : undefined))"
|
|
@@ -621,7 +678,7 @@ function formatShapeHtml(el: any): string {
|
|
|
621
678
|
v-else
|
|
622
679
|
vector-effect="non-scaling-stroke"
|
|
623
680
|
:d="element.path || element.content"
|
|
624
|
-
:fill="hasElementFill(element) ? (getElementPattern(element) ? `url(#shape-pattern-${
|
|
681
|
+
:fill="hasElementFill(element) ? (getElementPattern(element) ? `url(#shape-pattern-${svgIdKey})` : (getElementGradientConfig(element) ? `url(#shape-gradient-${svgIdKey})` : (getElementFill(element) !== undefined ? getElementFill(element) : '#3b82f6'))) : 'none'"
|
|
625
682
|
:stroke="(isImagePlaceholder && isImagePlaceholder(element) && element.style?.enableBorder !== true) ? 'none' : (element.style?.enableBorder === false ? 'none' : getEffectiveBorderColor(element, 'none'))"
|
|
626
683
|
:stroke-width="(isImagePlaceholder && isImagePlaceholder(element) && element.style?.enableBorder !== true) ? 0 : (element.style?.enableBorder === false ? 0 : getElementBorderWidth(element))"
|
|
627
684
|
:stroke-linecap="element.style?.strokeLinecap || element.borderLineCap || element.lineCap || (element.shapType === 'arc' ? 'round' : (isOpenLineShape(element) ? 'butt' : undefined))"
|
|
@@ -649,9 +706,9 @@ function formatShapeHtml(el: any): string {
|
|
|
649
706
|
height: '100%',
|
|
650
707
|
backgroundColor: getElementPattern(element) ? 'transparent' : ((isImagePlaceholder && isImagePlaceholder(element) && !getElementGradientCss(element)) ? getImagePlaceholderFill(element) : ((getElementGradientCss(element) || (getElementFill(element) !== undefined && getElementFill(element) !== 'none')) ? (getElementGradientCss(element) ? 'transparent' : getElementFill(element)) : (getElementFill(element) === 'none' ? 'transparent' : '#10b981'))),
|
|
651
708
|
backgroundImage: getElementPattern(element) ? `url('${getElementPattern(element)}')` : (getElementGradientCss(element) || 'none'),
|
|
652
|
-
backgroundSize: getElementPattern(element) ? 'cover' : undefined,
|
|
653
|
-
backgroundPosition: getElementPattern(element) ? 'center center' : undefined,
|
|
654
|
-
backgroundRepeat: getElementPattern(element) ? 'no-repeat' : undefined,
|
|
709
|
+
backgroundSize: getElementPattern(element) ? (isElementPatternTile(element) ? `${getElementPatternTileSize(element).w}px ${getElementPatternTileSize(element).h}px` : 'cover') : undefined,
|
|
710
|
+
backgroundPosition: getElementPattern(element) ? (isElementPatternTile(element) ? '0 0' : 'center center') : undefined,
|
|
711
|
+
backgroundRepeat: getElementPattern(element) ? (isElementPatternTile(element) ? 'repeat' : 'no-repeat') : undefined,
|
|
655
712
|
borderRadius: (isImagePlaceholder && isImagePlaceholder(element)) ? getImagePlaceholderRadius(element) : (element.style?.borderRadius !== undefined ? (typeof element.style.borderRadius === 'number' ? `${element.style.borderRadius}px` : element.style.borderRadius) : (element.radius ? `${element.radius}px` : '0px')),
|
|
656
713
|
borderWidth: (isImagePlaceholder && isImagePlaceholder(element) && element.style?.enableBorder !== true)
|
|
657
714
|
? '0px'
|
|
@@ -677,7 +734,7 @@ function formatShapeHtml(el: any): string {
|
|
|
677
734
|
<defs v-if="getElementBorderGradientConfig(element)">
|
|
678
735
|
<radialGradient
|
|
679
736
|
v-if="getElementBorderGradientConfig(element)!.path && getElementBorderGradientConfig(element)!.path !== 'line'"
|
|
680
|
-
:id="`shape-border-gradient-${
|
|
737
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
681
738
|
:cx="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.x * 100) : 50}%`"
|
|
682
739
|
:cy="`${getElementBorderGradientConfig(element)!.center ? Math.round(getElementBorderGradientConfig(element)!.center!.y * 100) : 50}%`"
|
|
683
740
|
:r="getElementBorderGradientConfig(element)!.path === 'rect' ? '60%' : (getElementBorderGradientConfig(element)!.path === 'path' ? '70%' : '50%')"
|
|
@@ -694,7 +751,7 @@ function formatShapeHtml(el: any): string {
|
|
|
694
751
|
</radialGradient>
|
|
695
752
|
<linearGradient
|
|
696
753
|
v-else
|
|
697
|
-
:id="`shape-border-gradient-${
|
|
754
|
+
:id="`shape-border-gradient-${svgIdKey}`"
|
|
698
755
|
:x1="getSvgGradientCoords(getElementBorderGradientConfig(element)).x1"
|
|
699
756
|
:y1="getSvgGradientCoords(getElementBorderGradientConfig(element)).y1"
|
|
700
757
|
:x2="getSvgGradientCoords(getElementBorderGradientConfig(element)).x2"
|
|
@@ -759,7 +816,7 @@ function formatShapeHtml(el: any): string {
|
|
|
759
816
|
}">
|
|
760
817
|
<div
|
|
761
818
|
ref="shapeEditableRef"
|
|
762
|
-
:id="`editable-${element.id}`"
|
|
819
|
+
:id="isReflection ? undefined : `editable-${element.id}`"
|
|
763
820
|
:contenteditable="isTextSelectable ? 'true' : 'false'"
|
|
764
821
|
@mousedown="isTextSelectable ? $event.stopPropagation() : null"
|
|
765
822
|
@click="isTextSelectable ? $event.stopPropagation() : null"
|
|
@@ -784,7 +841,7 @@ function formatShapeHtml(el: any): string {
|
|
|
784
841
|
boxSizing: 'content-box',
|
|
785
842
|
overflow: 'wrap',
|
|
786
843
|
wordBreak: 'break-word',
|
|
787
|
-
pointerEvents: isTextSelectable ? 'auto' : (getRawText(element).trim() ? 'auto' : 'none'),
|
|
844
|
+
pointerEvents: isReflection ? 'none' : (isTextSelectable ? 'auto' : (getRawText(element).trim() ? 'auto' : 'none')),
|
|
788
845
|
cursor: isTextSelectable ? 'text' : undefined,
|
|
789
846
|
userSelect: isTextSelectable ? 'text' : 'none',
|
|
790
847
|
color: element.style?.textColor || element.style?.color || element.textColor || ((isImagePlaceholder && isImagePlaceholder(element)) ? '#6366f1' : (element.style?.fill === '#ffffff' || element.style?.fill === '#FFFFFF' ? '#475569' : (themeRecipe?.shape?.textColor || '#ffffff'))),
|
|
@@ -229,7 +229,7 @@ function toggleFillSwitch(val: any) {
|
|
|
229
229
|
<div class="shape-property-row"
|
|
230
230
|
style="display: flex; align-items: center; justify-content: space-between; gap: 8px; height: auto;">
|
|
231
231
|
<span class="shape-property-label" style="flex-shrink: 0; width: 64px; text-align: left;">图表类型</span>
|
|
232
|
-
<el-popover placement="bottom" :width="
|
|
232
|
+
<el-popover placement="bottom" :width="568" trigger="click" popper-class="chart-popover-popper"
|
|
233
233
|
v-model:visible="chartTypePopoverVisible" :show-arrow="false">
|
|
234
234
|
<template #reference>
|
|
235
235
|
<div
|
|
@@ -477,8 +477,8 @@ function toggleFillSwitch(val: any) {
|
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
.shape-accordion-header-label {
|
|
480
|
-
font-size:
|
|
481
|
-
font-weight:
|
|
480
|
+
font-size: 14px;
|
|
481
|
+
font-weight: 400;
|
|
482
482
|
color: var(--text-primary);
|
|
483
483
|
display: flex;
|
|
484
484
|
align-items: center;
|
|
@@ -7,7 +7,7 @@ import QuickColorPopover from '../../QuickColorPopover.vue'
|
|
|
7
7
|
import GradientEditor from './GradientEditor.vue'
|
|
8
8
|
import { useSlides, gradientPresets, normalizeSlideBackground } from '../../../composables/useSlides'
|
|
9
9
|
import { SYS_FONTS } from '../../../configs/fonts'
|
|
10
|
-
import { PATTERN_PRESETS, parseColor, generateGradientEndColor } from '../../../composables/sidebar/useSidebarShapeFormat'
|
|
10
|
+
import { PATTERN_PRESETS, parseColor, hexToRgba, generateGradientEndColor, buildPatternSvgDataUrl, parsePatternSvgMeta } from '../../../composables/sidebar/useSidebarShapeFormat'
|
|
11
11
|
|
|
12
12
|
const props = defineProps<{
|
|
13
13
|
designActiveTab: string
|
|
@@ -81,6 +81,8 @@ const quickRecentColors = ref<string[]>([])
|
|
|
81
81
|
const globalFontColorPopoverVisible = ref(false)
|
|
82
82
|
const globalBorderColorPopoverVisible = ref(false)
|
|
83
83
|
const bgColorPopoverVisible = ref(false)
|
|
84
|
+
const bgPatternFgPopoverVisible = ref(false)
|
|
85
|
+
const bgPatternBgPopoverVisible = ref(false)
|
|
84
86
|
const isCustomColorPickerOpen = ref(false)
|
|
85
87
|
|
|
86
88
|
// 画布尺寸下拉 Popover 状态
|
|
@@ -399,8 +401,11 @@ const bgFillType = computed({
|
|
|
399
401
|
if (isExistingSvgPattern && existingUrl) {
|
|
400
402
|
activeSlide.value.background = `url("${existingUrl}") left top / auto repeat`
|
|
401
403
|
} else {
|
|
402
|
-
const
|
|
403
|
-
activeSlide.value
|
|
404
|
+
const pType = (activeSlide.value as any).bgPatternType || 'dots-sparse'
|
|
405
|
+
const pFg = (activeSlide.value as any).bgPatternFg || '#3B82F6'
|
|
406
|
+
const pBg = (activeSlide.value as any).bgPatternBg || '#FFFFFF'
|
|
407
|
+
const patUrl = buildPatternSvgDataUrl(pType, pFg, pBg)
|
|
408
|
+
activeSlide.value.background = `url("${patUrl}") left top / auto repeat`
|
|
404
409
|
}
|
|
405
410
|
}
|
|
406
411
|
}
|
|
@@ -555,6 +560,80 @@ const bgImagePreviewStyle = computed(() => {
|
|
|
555
560
|
}
|
|
556
561
|
})
|
|
557
562
|
|
|
563
|
+
const activeBgPatternMeta = computed(() => {
|
|
564
|
+
const rawUrl = extractUrlFromBgCss(activeSlide.value?.background)
|
|
565
|
+
return parsePatternSvgMeta(rawUrl)
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
const activeBgPatternType = computed(() => {
|
|
569
|
+
return (activeSlide.value as any)?.bgPatternType || activeBgPatternMeta.value.type || 'dots-sparse'
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
const activeBgPatternFg = computed(() => {
|
|
573
|
+
return (activeSlide.value as any)?.bgPatternFg || activeBgPatternMeta.value.fg || '#3B82F6'
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
const activeBgPatternBg = computed(() => {
|
|
577
|
+
return (activeSlide.value as any)?.bgPatternBg || activeBgPatternMeta.value.bg || '#FFFFFF'
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
const parsedBgPatternFg = computed(() => parseColor(activeBgPatternFg.value))
|
|
581
|
+
const parsedBgPatternBg = computed(() => parseColor(activeBgPatternBg.value))
|
|
582
|
+
|
|
583
|
+
function getBgPatternPresetPreviewUrl(p: { key: string; url: string }) {
|
|
584
|
+
return buildPatternSvgDataUrl(p.key, activeBgPatternFg.value, activeBgPatternBg.value)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function isBgPatternPresetActive(p: { key: string; url: string }) {
|
|
588
|
+
return activeBgPatternType.value === p.key
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function applyBgPattern(type: string, fg: string, bg: string) {
|
|
592
|
+
if (!activeSlide.value) return
|
|
593
|
+
;(activeSlide.value as any).bgPatternType = type
|
|
594
|
+
;(activeSlide.value as any).bgPatternFg = fg
|
|
595
|
+
;(activeSlide.value as any).bgPatternBg = bg
|
|
596
|
+
;(activeSlide.value as any).isPattern = true
|
|
597
|
+
const patUrl = buildPatternSvgDataUrl(type, fg, bg)
|
|
598
|
+
activeSlide.value.background = `url("${patUrl}") left top / auto repeat`
|
|
599
|
+
activeSlide.value.bgType = 'image'
|
|
600
|
+
addHistorySnapshot()
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function selectBgPatternPreset(p: { key: string; url: string }) {
|
|
604
|
+
applyBgPattern(p.key, activeBgPatternFg.value, activeBgPatternBg.value)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function onSelectBgPatternFgColor(color: string) {
|
|
608
|
+
const fg = (!color || color === 'transparent' || color === 'none') ? 'transparent' : color
|
|
609
|
+
applyBgPattern(activeBgPatternType.value, fg, activeBgPatternBg.value)
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function handleBgPatternFgOpacityInput(e: Event) {
|
|
613
|
+
const target = e.target as HTMLInputElement
|
|
614
|
+
let val = parseInt(target.value)
|
|
615
|
+
if (isNaN(val)) val = 100
|
|
616
|
+
val = Math.max(0, Math.min(100, val))
|
|
617
|
+
const hex = parsedBgPatternFg.value.hex === 'transparent' ? '#3B82F6' : parsedBgPatternFg.value.hex
|
|
618
|
+
const fg = val === 0 ? 'transparent' : (val === 100 ? hex : hexToRgba(hex, val))
|
|
619
|
+
applyBgPattern(activeBgPatternType.value, fg, activeBgPatternBg.value)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function onSelectBgPatternBgColor(color: string) {
|
|
623
|
+
const bg = (!color || color === 'transparent' || color === 'none') ? 'transparent' : color
|
|
624
|
+
applyBgPattern(activeBgPatternType.value, activeBgPatternFg.value, bg)
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function handleBgPatternBgOpacityInput(e: Event) {
|
|
628
|
+
const target = e.target as HTMLInputElement
|
|
629
|
+
let val = parseInt(target.value)
|
|
630
|
+
if (isNaN(val)) val = 100
|
|
631
|
+
val = Math.max(0, Math.min(100, val))
|
|
632
|
+
const hex = parsedBgPatternBg.value.hex === 'transparent' ? '#FFFFFF' : parsedBgPatternBg.value.hex
|
|
633
|
+
const bg = val === 0 ? 'transparent' : (val === 100 ? hex : hexToRgba(hex, val))
|
|
634
|
+
applyBgPattern(activeBgPatternType.value, activeBgPatternFg.value, bg)
|
|
635
|
+
}
|
|
636
|
+
|
|
558
637
|
const bgFileInputRef = ref<HTMLInputElement | null>(null)
|
|
559
638
|
const uploadBgImage = () => {
|
|
560
639
|
bgFileInputRef.value?.click()
|
|
@@ -1042,7 +1121,7 @@ watch(extractTab, () => {
|
|
|
1042
1121
|
</svg>
|
|
1043
1122
|
</button>
|
|
1044
1123
|
<button type="button" class="fill-type-icon-btn" :class="{ active: activeBgTab === 'pattern' }"
|
|
1045
|
-
@click="selectBgTab('pattern')" title="
|
|
1124
|
+
@click="selectBgTab('pattern')" title="图案填充">
|
|
1046
1125
|
<svg viewBox="0 0 18 18" width="18" height="18">
|
|
1047
1126
|
<defs>
|
|
1048
1127
|
<pattern id="bgFillBarPattern" width="4" height="4" patternUnits="userSpaceOnUse"
|
|
@@ -1141,12 +1220,68 @@ watch(extractTab, () => {
|
|
|
1141
1220
|
</div>
|
|
1142
1221
|
</div>
|
|
1143
1222
|
|
|
1144
|
-
<!--
|
|
1223
|
+
<!-- 图案填充卡片 -->
|
|
1145
1224
|
<template v-else-if="isPatternBg">
|
|
1146
|
-
<div class="pattern-
|
|
1147
|
-
<div
|
|
1148
|
-
|
|
1149
|
-
|
|
1225
|
+
<div class="pattern-fill-container">
|
|
1226
|
+
<div class="pattern-presets-grid">
|
|
1227
|
+
<div v-for="p in PATTERN_PRESETS" :key="p.key" class="pattern-preset-card"
|
|
1228
|
+
:class="{ active: isBgPatternPresetActive(p) }" :title="p.name"
|
|
1229
|
+
:style="{ backgroundImage: `url('${getBgPatternPresetPreviewUrl(p)}')` }"
|
|
1230
|
+
@click="selectBgPatternPreset(p)"></div>
|
|
1231
|
+
</div>
|
|
1232
|
+
|
|
1233
|
+
<!-- 前景 / 背景 颜色选择 (对齐 PPT / WPS 图案填充) -->
|
|
1234
|
+
<div class="pattern-colors-section">
|
|
1235
|
+
<div class="pattern-color-row">
|
|
1236
|
+
<span class="pattern-color-label">前景</span>
|
|
1237
|
+
<div class="full-color-pill" style="flex: 1;">
|
|
1238
|
+
<QuickColorPopover v-model:visible="bgPatternFgPopoverVisible"
|
|
1239
|
+
:color="parsedBgPatternFg.hex === 'transparent' ? '#3B82F6' : parsedBgPatternFg.hex"
|
|
1240
|
+
:alpha="parsedBgPatternFg.alpha" v-model:recentColors="quickRecentColors"
|
|
1241
|
+
@select-color="onSelectBgPatternFgColor">
|
|
1242
|
+
<template #reference>
|
|
1243
|
+
<div class="color-left-info">
|
|
1244
|
+
<div class="color-square-preview"
|
|
1245
|
+
:style="{ backgroundColor: parsedBgPatternFg.hex === 'transparent' ? 'transparent' : parsedBgPatternFg.hex }">
|
|
1246
|
+
</div>
|
|
1247
|
+
<span class="color-hex-text">{{ formatHexDisplay(parsedBgPatternFg.hex) }}</span>
|
|
1248
|
+
</div>
|
|
1249
|
+
</template>
|
|
1250
|
+
</QuickColorPopover>
|
|
1251
|
+
<div class="color-opacity-input-wrapper" @click.stop title="前景不透明度">
|
|
1252
|
+
<input type="number" class="color-opacity-num-input" :value="parsedBgPatternFg.alpha" min="0"
|
|
1253
|
+
max="100" @input="handleBgPatternFgOpacityInput"
|
|
1254
|
+
@keydown.enter="($event.target as HTMLElement).blur()" />
|
|
1255
|
+
<span class="percent-unit">%</span>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
|
|
1260
|
+
<div class="pattern-color-row">
|
|
1261
|
+
<span class="pattern-color-label">背景</span>
|
|
1262
|
+
<div class="full-color-pill" style="flex: 1;">
|
|
1263
|
+
<QuickColorPopover v-model:visible="bgPatternBgPopoverVisible"
|
|
1264
|
+
:color="parsedBgPatternBg.hex === 'transparent' ? '#FFFFFF' : parsedBgPatternBg.hex"
|
|
1265
|
+
:alpha="parsedBgPatternBg.alpha" v-model:recentColors="quickRecentColors"
|
|
1266
|
+
@select-color="onSelectBgPatternBgColor">
|
|
1267
|
+
<template #reference>
|
|
1268
|
+
<div class="color-left-info">
|
|
1269
|
+
<div class="color-square-preview"
|
|
1270
|
+
:style="{ backgroundColor: parsedBgPatternBg.hex === 'transparent' ? 'transparent' : parsedBgPatternBg.hex }">
|
|
1271
|
+
</div>
|
|
1272
|
+
<span class="color-hex-text">{{ formatHexDisplay(parsedBgPatternBg.hex) }}</span>
|
|
1273
|
+
</div>
|
|
1274
|
+
</template>
|
|
1275
|
+
</QuickColorPopover>
|
|
1276
|
+
<div class="color-opacity-input-wrapper" @click.stop title="背景不透明度">
|
|
1277
|
+
<input type="number" class="color-opacity-num-input" :value="parsedBgPatternBg.alpha" min="0"
|
|
1278
|
+
max="100" @input="handleBgPatternBgOpacityInput"
|
|
1279
|
+
@keydown.enter="($event.target as HTMLElement).blur()" />
|
|
1280
|
+
<span class="percent-unit">%</span>
|
|
1281
|
+
</div>
|
|
1282
|
+
</div>
|
|
1283
|
+
</div>
|
|
1284
|
+
</div>
|
|
1150
1285
|
</div>
|
|
1151
1286
|
</template>
|
|
1152
1287
|
|
|
@@ -2072,6 +2207,32 @@ watch(extractTab, () => {
|
|
|
2072
2207
|
user-select: none;
|
|
2073
2208
|
}
|
|
2074
2209
|
|
|
2210
|
+
.pattern-fill-container {
|
|
2211
|
+
display: flex;
|
|
2212
|
+
flex-direction: column;
|
|
2213
|
+
gap: 10px;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
.pattern-colors-section {
|
|
2217
|
+
display: flex;
|
|
2218
|
+
flex-direction: column;
|
|
2219
|
+
gap: 8px;
|
|
2220
|
+
padding-top: 2px;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
.pattern-color-row {
|
|
2224
|
+
display: flex;
|
|
2225
|
+
align-items: center;
|
|
2226
|
+
gap: 8px;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.pattern-color-label {
|
|
2230
|
+
width: 36px;
|
|
2231
|
+
font-size: 13px;
|
|
2232
|
+
color: #475569;
|
|
2233
|
+
flex-shrink: 0;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2075
2236
|
.pattern-presets-grid {
|
|
2076
2237
|
display: grid;
|
|
2077
2238
|
grid-template-columns: repeat(4, 1fr);
|