Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
boyueCEnd
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
游洁
boyueCEnd
Commits
d57d3d47
Commit
d57d3d47
authored
Dec 04, 2025
by
罗超
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
修正个别组件
parent
d6fc4e78
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
199 additions
and
18 deletions
+199
-18
Card.vue
src/components/page-builder/Card.vue
+37
-5
Carousel.vue
src/components/page-builder/Carousel.vue
+127
-11
GridContainer.vue
src/components/page-builder/GridContainer.vue
+10
-0
OTAPageRenderer.vue
src/renderer/OTAPageRenderer.vue
+15
-0
card.ts
src/types/card.ts
+7
-1
carousel.ts
src/types/carousel.ts
+3
-1
No files found.
src/components/page-builder/Card.vue
View file @
d57d3d47
...
...
@@ -124,12 +124,44 @@ const cardStyle = computed(() => {
// 封面样式
const
coverStyle
=
computed
(()
=>
{
const
style
:
Record
<
string
,
string
>
=
{}
if
(
props
.
layout
===
'vertical'
)
{
style
.
height
=
`
${
props
.
coverHeight
}
px`
// 获取尺寸模式(兼容旧数据)
const
sizeMode
=
props
.
coverSizeMode
||
(
props
.
coverRatio
?
'ratio'
:
'custom'
)
if
(
sizeMode
===
'ratio'
&&
props
.
coverRatio
)
{
// 比例模式:使用 aspect-ratio CSS 属性
const
[
width
,
height
]
=
props
.
coverRatio
.
split
(
':'
).
map
(
Number
)
if
(
props
.
layout
===
'vertical'
)
{
// 纵向布局:宽度 100%,高度按比例计算
style
.
width
=
'100%'
style
.
aspectRatio
=
`
${
width
}
/
${
height
}
`
}
else
{
// 横向布局:高度 100%,宽度按比例计算
style
.
height
=
'100%'
style
.
aspectRatio
=
`
${
width
}
/
${
height
}
`
style
.
flexShrink
=
'0'
// 横向布局时,宽度由 aspect-ratio 自动计算,但需要设置一个基础宽度
style
.
minWidth
=
'0'
}
}
else
if
(
sizeMode
===
'custom'
)
{
// 自定义模式:使用自定义宽高
if
(
props
.
layout
===
'vertical'
)
{
style
.
width
=
props
.
coverCustomWidth
?
`
${
props
.
coverCustomWidth
}
px`
:
'100%'
style
.
height
=
props
.
coverCustomHeight
?
`
${
props
.
coverCustomHeight
}
px`
:
`
${
props
.
coverHeight
}
px`
}
else
{
style
.
width
=
props
.
coverCustomWidth
?
`
${
props
.
coverCustomWidth
}
px`
:
`
${
props
.
coverHeight
}
px`
style
.
height
=
props
.
coverCustomHeight
?
`
${
props
.
coverCustomHeight
}
px`
:
'100%'
style
.
flexShrink
=
'0'
}
}
else
{
style
.
width
=
`
${
props
.
coverHeight
}
px`
style
.
flexShrink
=
'0'
// 兼容旧数据:使用 coverHeight
if
(
props
.
layout
===
'vertical'
)
{
style
.
height
=
`
${
props
.
coverHeight
}
px`
}
else
{
style
.
width
=
`
${
props
.
coverHeight
}
px`
style
.
flexShrink
=
'0'
}
}
return
style
...
...
src/components/page-builder/Carousel.vue
View file @
d57d3d47
<
template
>
<div
class=
"carousel-wrapper"
:class=
"
{ 'outside-nav': isOutsideNav }" :style="wrapperStyle">
<div
class=
"carousel-wrapper"
:class=
"
{
'outside-nav': isOutsideNav
&&
!isAbsolutePosition,
'outside-nav-absolute': isOutsideNav
&&
isAbsolutePosition
}"
:style="wrapperStyle"
>
<!-- 外部左箭头 -->
<template
v-if=
"isOutsideNav && props.navigation.enabled"
>
<div
class=
"outside-nav-btn outside-nav-prev"
@
click=
"handlePrevClick"
>
<div
class=
"outside-nav-btn outside-nav-prev"
:class=
"
{ 'absolute-position': isAbsolutePosition }"
:style="isAbsolutePosition ? outsideNavAbsoluteStyle('prev') : {}"
@click="handlePrevClick"
>
<!-- 默认模式 -->
<template
v-if=
"props.navigation.arrowType === 'default'"
>
<svg
viewBox=
"0 0 24 24"
class=
"nav-svg"
>
...
...
@@ -53,7 +65,7 @@
>
<!-- 图片轮播 -->
<SwiperSlide
v-for=
"(image, index) in props.images"
:key=
"image.id || index"
>
<div
class=
"carousel-slide"
@
click=
"handleImageClick(image)"
>
<div
class=
"carousel-slide"
:style=
"{borderRadius:props.borderRadius+'px'}"
@
click=
"handleImageClick(image)"
>
<!-- 有图片:显示图片 -->
<
template
v-if=
"image.url"
>
<div
class=
"slide-content"
>
...
...
@@ -63,18 +75,27 @@
:style="imageStyle"
loading="lazy"
class="slide-image"
v-if="!image.content?.showOnHover"
/>
<div
:style=
"
{...imageStyle,backgroundImage: `url(${image.url})`}" v-else class="slide-hover-image">
</div>
<!-- 遮罩层 -->
<div
v-if=
"overlayStyle"
class=
"slide-overlay"
:style=
"overlayStyle"
></div>
<div
v-if=
"overlayStyle
&& !image.content?.showOnHover
"
class=
"slide-overlay"
:style=
"overlayStyle"
></div>
</div>
<!-- 内容层(文案或子组件) -->
<div
class=
"slide-content-layer"
:class=
"
{'hover-layer': image.content?.showOnHover}"
:style="contentLayerStyle"
@mouseenter="handleSlideMouseEnter(index)"
@mouseleave="handleSlideMouseLeave(index)"
>
<!-- 优先显示图片自己的文案 -->
<template
v-if=
"image.content && (image.content.title || image.content.subtitle || image.content.description || image.content.buttonText)"
>
<div
class=
"slide-text-content"
>
<div
class=
"slide-text-content"
:class=
"
{ 'show-on-hover': image.content.showOnHover }"
>
<h2
v-if=
"image.content.title"
class=
"slide-title"
:style=
"titleStyle"
>
{{
image
.
content
.
title
}}
</h2>
...
...
@@ -172,7 +193,12 @@
</div>
<!-- 外部右箭头 -->
<
template
v-if=
"isOutsideNav && props.navigation.enabled"
>
<div
class=
"outside-nav-btn outside-nav-next"
@
click=
"handleNextClick"
>
<div
class=
"outside-nav-btn outside-nav-next"
:class=
"
{ 'absolute-position': isAbsolutePosition }"
:style="isAbsolutePosition ? outsideNavAbsoluteStyle('next') : {}"
@click="handleNextClick"
>
<!-- 默认模式 -->
<template
v-if=
"props.navigation.arrowType === 'default'"
>
<svg
viewBox=
"0 0 24 24"
class=
"nav-svg"
>
...
...
@@ -356,6 +382,40 @@ const isOutsideNav = computed(() => {
return
props
.
navigation
.
enabled
&&
props
.
navigation
.
position
===
'outside'
})
// 判断外部箭头是否使用绝对定位(不占用组件宽度)
const
isAbsolutePosition
=
computed
(()
=>
{
return
isOutsideNav
.
value
&&
(
props
.
navigation
.
absolutePosition
===
true
)
})
// 处理幻灯片鼠标进入(用于其他可能的交互)
const
handleSlideMouseEnter
=
(
index
:
number
)
=>
{
// 可以在这里添加其他悬浮交互逻辑
}
// 处理幻灯片鼠标离开(用于其他可能的交互)
const
handleSlideMouseLeave
=
(
index
:
number
)
=>
{
// 可以在这里添加其他悬浮交互逻辑
}
// 外部箭头绝对定位样式
const
outsideNavAbsoluteStyle
=
(
direction
:
'prev'
|
'next'
)
=>
{
const
{
size
,
offset
}
=
props
.
navigation
const
style
:
Record
<
string
,
string
>
=
{
position
:
'absolute'
,
top
:
'50%'
,
transform
:
'translateY(-50%)'
,
zIndex
:
'10'
,
}
if
(
direction
===
'prev'
)
{
style
.
left
=
`-
${
size
+
offset
}
px`
}
else
{
style
.
right
=
`-
${
size
+
offset
}
px`
}
return
style
}
// 导航配置
const
navigationConfig
=
computed
(()
=>
{
if
(
!
props
.
navigation
.
enabled
)
return
false
...
...
@@ -521,7 +581,7 @@ const wrapperStyle = computed(() => {
style
.
width
=
'100%'
style
.
marginLeft
=
'auto'
style
.
marginRight
=
'auto'
style
.
padding
=
'0
2
0px'
// 两侧留白
style
.
padding
=
'0 0px'
// 两侧留白
style
.
boxSizing
=
'border-box'
}
}
else
{
...
...
@@ -598,6 +658,7 @@ const imageStyle = computed<CSSProperties>(() => ({
height
:
'100%'
,
objectFit
:
'cover'
,
display
:
'block'
,
borderRadius
:
props
.
borderRadius
+
'px'
,
}))
// 处理图片点击
...
...
@@ -952,6 +1013,7 @@ const contentLayerStyle = computed(() => {
const
{
horizontal
,
vertical
}
=
props
.
contentAlign
||
{
horizontal
:
'center'
,
vertical
:
'middle'
}
return
{
borderRadius
:
props
.
borderRadius
+
'px'
,
// flex-direction: column 时,justifyContent 控制垂直对齐,alignItems 控制水平对齐
justifyContent
:
vertical
===
'top'
?
'flex-start'
:
vertical
===
'bottom'
?
'flex-end'
:
'center'
,
alignItems
:
horizontal
===
'left'
?
'flex-start'
:
horizontal
===
'right'
?
'flex-end'
:
'center'
,
...
...
@@ -980,11 +1042,11 @@ const descriptionStyle = computed(() => ({
}))
const buttonStyle = computed(() => ({
backgroundColor: props.contentStyle?.button.backgroundColor || '
#ffffff
',
backgroundColor: props.contentStyle?.button.backgroundColor || '
transparent
',
color: props.contentStyle?.button.color || '#1d2129',
borderRadius: `
$
{(
props
.
contentStyle
?.
button
.
borderRadius
||
4
)
/
16
}
rem
`,
padding: `
$
{(
props
.
contentStyle
?.
button
.
paddingVertical
||
8
)
/
16
}
rem
$
{(
props
.
contentStyle
?.
button
.
paddingHorizontal
||
24
)
/
16
}
rem
`,
fontSize: `
$
{(
props
.
contentStyle
?.
button
.
fontSize
||
1
4
)
/
16
}
rem
`,
padding: `
$
{(
props
.
contentStyle
?.
button
.
paddingVertical
)
/
16
}
rem
$
{(
props
.
contentStyle
?.
button
.
paddingHorizontal
)
/
16
}
rem
`,
fontSize: `
$
{(
props
.
contentStyle
?.
button
.
fontSize
||
1
2
)
/
16
}
rem
`,
fontWeight: props.contentStyle?.button.fontWeight || 500,
border: 'none',
cursor: 'pointer',
...
...
@@ -1009,7 +1071,26 @@ const handleButtonClick = (buttonLink?: string) => {
position
:
relative
;
z-index
:
9
;
}
// 父容器悬浮时显示
.swiper-slide
{
&
:hover
{
.show-on-hover
{
opacity
:
1
;
visibility
:
visible
;
transition
:
.3s
;
}
.slide-hover-image
{
filter
:
blur
(
6px
);
transform
:
scale
(
1
.05
);
transition
:
.3s
;
}
.hover-layer
{
background
:
rgba
(
0
,
0
,
0
,
.3
);
transition
:
.3s
;
overflow
:
hidden
;
}
}
}
.carousel-swiper
{
width
:
100%
;
...
...
@@ -1084,6 +1165,15 @@ const handleButtonClick = (buttonLink?: string) => {
}
}
// === 外部模式:绝对定位(不占用宽度) ===
.carousel-wrapper.outside-nav-absolute
{
position
:
relative
;
.carousel-swiper
{
width
:
100%
;
}
}
// === 外部箭头按钮 ===
.outside-nav-btn
{
flex-shrink
:
0
;
...
...
@@ -1100,6 +1190,14 @@ const handleButtonClick = (buttonLink?: string) => {
user-select
:
none
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0
.15
);
// 绝对定位模式
&
.absolute-position
{
position
:
absolute
;
top
:
50%
;
transform
:
translateY
(
-50%
);
z-index
:
10
;
}
// SVG 样式(70%)
.nav-svg
{
width
:
v-bind
(
'`${props.navigation.size * 0.7}px`'
);
...
...
@@ -1237,6 +1335,7 @@ const handleButtonClick = (buttonLink?: string) => {
height
:
100%
;
position
:
relative
;
cursor
:
pointer
;
overflow
:
hidden
;
img
{
user-select
:
none
;
...
...
@@ -1285,6 +1384,14 @@ const handleButtonClick = (buttonLink?: string) => {
object-fit
:
cover
;
display
:
block
;
}
.slide-hover-image
{
width
:
100%
;
height
:
100%
;
display
:
block
;
background-size
:
cover
;
background-position
:
center
;
background-repeat
:
no-repeat
;
}
// Ken Burns 缩放效果
.carousel-swiper
{
...
...
@@ -1332,6 +1439,7 @@ const handleButtonClick = (buttonLink?: string) => {
>
*
{
pointer-events
:
auto
;
}
}
// 文案内容容器
...
...
@@ -1341,6 +1449,14 @@ const handleButtonClick = (buttonLink?: string) => {
align-items
:
inherit
;
// 继承父容器的对齐
text-align
:
center
;
max-width
:
800px
;
// 限制最大宽度,提高可读性
transition
:
opacity
0
.3s
ease
,
visibility
0
.3s
ease
;
// 悬浮显示模式
&
.show-on-hover
{
opacity
:
0
;
visibility
:
hidden
;
}
.slide-title
,
.slide-subtitle
,
...
...
src/components/page-builder/GridContainer.vue
View file @
d57d3d47
...
...
@@ -92,8 +92,18 @@ const gridCells = computed(() => {
return
[]
})
// 计算当前应该显示的列数(考虑响应式)
const
currentColumns
=
computed
(()
=>
{
// 如果响应式配置启用
if
(
props
.
responsive
?.
enabled
&&
props
.
responsive
?.
config
)
{
const
responsiveCols
=
props
.
responsive
.
config
[
runtimeDevice
.
value
]
// 如果响应式列数不为0,优先使用响应式列数
if
(
responsiveCols
&&
responsiveCols
>
0
)
{
return
responsiveCols
}
}
// 否则使用默认的 columns
return
props
.
columns
})
...
...
src/renderer/OTAPageRenderer.vue
View file @
d57d3d47
...
...
@@ -16,6 +16,21 @@
/>
</div>
<!-- GridContainer 特殊处理:需要传递 children 到 slot -->
<div
v-else-if=
"comp.type === 'grid-container'"
:style=
"getComponentWrapperStyle(comp)"
>
<component
:is=
"getComponent(comp.type)"
v-bind=
"comp.props"
>
<component
v-for=
"child in comp.children"
:key=
"child.id"
:is=
"getComponent(child.type)"
v-bind=
"child.props"
/>
</component>
</div>
<!-- 其他组件:应用 maxWidth 样式 -->
<div
v-else
:style=
"getComponentWrapperStyle(comp)"
>
<component
...
...
src/types/card.ts
View file @
d57d3d47
...
...
@@ -8,9 +8,15 @@ export interface CardProps {
// 封面图片
coverImage
?:
string
// 封面图片 URL
coverHeight
:
number
// 封面高度(px,纵向布局)或宽度(横向布局)
coverHeight
:
number
// 封面高度(px,纵向布局)或宽度(横向布局)
- 兼容旧数据
coverFit
:
'cover'
|
'contain'
|
'fill'
// 图片适配方式
// 封面尺寸模式(新增)
coverSizeMode
?:
'ratio'
|
'custom'
// 尺寸模式:比例/自定义(默认 'ratio' 兼容旧数据)
coverRatio
?:
string
// 预设比例,如 '16:9', '4:3', '1:1' 等
coverCustomWidth
?:
number
// 自定义宽度(px,仅在 custom 模式下使用)
coverCustomHeight
?:
number
// 自定义高度(px,仅在 custom 模式下使用)
// 标题区
title
:
string
// 主标题
titleSize
:
number
// 标题字号
...
...
src/types/carousel.ts
View file @
d57d3d47
...
...
@@ -13,9 +13,10 @@ export interface CarouselImage {
content
?:
{
title
?:
string
// 标题
subtitle
?:
string
// 副标题
description
?:
string
// 描述
description
?:
string
// 描述
buttonText
?:
string
// 按钮文字
buttonLink
?:
string
// 按钮链接
showOnHover
?:
boolean
// 是否仅在悬浮时显示(默认 false,始终显示)
}
}
...
...
@@ -106,6 +107,7 @@ export interface CarouselProps {
// 位置
position
:
'inside'
|
'outside'
// 内部/外部
offset
:
number
// 距离边缘的偏移 0-50px
absolutePosition
?:
boolean
// 外部箭头是否使用绝对定位(不占用组件宽度),默认 false
}
// 分页指示器(简化,删除透明度选项)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment