Commit d57d3d47 authored by 罗超's avatar 罗超

修正个别组件

parent d6fc4e78
...@@ -125,12 +125,44 @@ const cardStyle = computed(() => { ...@@ -125,12 +125,44 @@ const cardStyle = computed(() => {
const coverStyle = computed(() => { const coverStyle = computed(() => {
const style: Record<string, string> = {} const style: Record<string, string> = {}
// 获取尺寸模式(兼容旧数据)
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 {
// 兼容旧数据:使用 coverHeight
if (props.layout === 'vertical') { if (props.layout === 'vertical') {
style.height = `${props.coverHeight}px` style.height = `${props.coverHeight}px`
} else { } else {
style.width = `${props.coverHeight}px` style.width = `${props.coverHeight}px`
style.flexShrink = '0' style.flexShrink = '0'
} }
}
return style return style
}) })
......
<template> <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"> <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'"> <template v-if="props.navigation.arrowType === 'default'">
<svg viewBox="0 0 24 24" class="nav-svg"> <svg viewBox="0 0 24 24" class="nav-svg">
...@@ -53,7 +65,7 @@ ...@@ -53,7 +65,7 @@
> >
<!-- 图片轮播 --> <!-- 图片轮播 -->
<SwiperSlide v-for="(image, index) in props.images" :key="image.id || index"> <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"> <template v-if="image.url">
<div class="slide-content"> <div class="slide-content">
...@@ -63,18 +75,27 @@ ...@@ -63,18 +75,27 @@
:style="imageStyle" :style="imageStyle"
loading="lazy" loading="lazy"
class="slide-image" 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>
<!-- 内容层(文案或子组件) --> <!-- 内容层(文案或子组件) -->
<div <div
class="slide-content-layer" class="slide-content-layer"
:class="{'hover-layer': image.content?.showOnHover}"
:style="contentLayerStyle" :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)"> <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"> <h2 v-if="image.content.title" class="slide-title" :style="titleStyle">
{{ image.content.title }} {{ image.content.title }}
</h2> </h2>
...@@ -172,7 +193,12 @@ ...@@ -172,7 +193,12 @@
</div> </div>
<!-- 外部右箭头 --> <!-- 外部右箭头 -->
<template v-if="isOutsideNav && props.navigation.enabled"> <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'"> <template v-if="props.navigation.arrowType === 'default'">
<svg viewBox="0 0 24 24" class="nav-svg"> <svg viewBox="0 0 24 24" class="nav-svg">
...@@ -356,6 +382,40 @@ const isOutsideNav = computed(() => { ...@@ -356,6 +382,40 @@ const isOutsideNav = computed(() => {
return props.navigation.enabled && props.navigation.position === 'outside' 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(() => { const navigationConfig = computed(() => {
if (!props.navigation.enabled) return false if (!props.navigation.enabled) return false
...@@ -521,7 +581,7 @@ const wrapperStyle = computed(() => { ...@@ -521,7 +581,7 @@ const wrapperStyle = computed(() => {
style.width = '100%' style.width = '100%'
style.marginLeft = 'auto' style.marginLeft = 'auto'
style.marginRight = 'auto' style.marginRight = 'auto'
style.padding = '0 20px' // 两侧留白 style.padding = '0 0px' // 两侧留白
style.boxSizing = 'border-box' style.boxSizing = 'border-box'
} }
} else { } else {
...@@ -598,6 +658,7 @@ const imageStyle = computed<CSSProperties>(() => ({ ...@@ -598,6 +658,7 @@ const imageStyle = computed<CSSProperties>(() => ({
height: '100%', height: '100%',
objectFit: 'cover', objectFit: 'cover',
display: 'block', display: 'block',
borderRadius:props.borderRadius + 'px',
})) }))
// 处理图片点击 // 处理图片点击
...@@ -952,6 +1013,7 @@ const contentLayerStyle = computed(() => { ...@@ -952,6 +1013,7 @@ const contentLayerStyle = computed(() => {
const { horizontal, vertical } = props.contentAlign || { horizontal: 'center', vertical: 'middle' } const { horizontal, vertical } = props.contentAlign || { horizontal: 'center', vertical: 'middle' }
return { return {
borderRadius:props.borderRadius + 'px',
// flex-direction: column 时,justifyContent 控制垂直对齐,alignItems 控制水平对齐 // flex-direction: column 时,justifyContent 控制垂直对齐,alignItems 控制水平对齐
justifyContent: vertical === 'top' ? 'flex-start' : vertical === 'bottom' ? 'flex-end' : 'center', justifyContent: vertical === 'top' ? 'flex-start' : vertical === 'bottom' ? 'flex-end' : 'center',
alignItems: horizontal === 'left' ? 'flex-start' : horizontal === 'right' ? 'flex-end' : 'center', alignItems: horizontal === 'left' ? 'flex-start' : horizontal === 'right' ? 'flex-end' : 'center',
...@@ -980,11 +1042,11 @@ const descriptionStyle = computed(() => ({ ...@@ -980,11 +1042,11 @@ const descriptionStyle = computed(() => ({
})) }))
const buttonStyle = computed(() => ({ const buttonStyle = computed(() => ({
backgroundColor: props.contentStyle?.button.backgroundColor || '#ffffff', backgroundColor: props.contentStyle?.button.backgroundColor || 'transparent',
color: props.contentStyle?.button.color || '#1d2129', color: props.contentStyle?.button.color || '#1d2129',
borderRadius: `${(props.contentStyle?.button.borderRadius || 4) / 16}rem`, borderRadius: `${(props.contentStyle?.button.borderRadius || 4) / 16}rem`,
padding: `${(props.contentStyle?.button.paddingVertical || 8) / 16}rem ${(props.contentStyle?.button.paddingHorizontal || 24) / 16}rem`, padding: `${(props.contentStyle?.button.paddingVertical) / 16}rem ${(props.contentStyle?.button.paddingHorizontal) / 16}rem`,
fontSize: `${(props.contentStyle?.button.fontSize || 14) / 16}rem`, fontSize: `${(props.contentStyle?.button.fontSize || 12) / 16}rem`,
fontWeight: props.contentStyle?.button.fontWeight || 500, fontWeight: props.contentStyle?.button.fontWeight || 500,
border: 'none', border: 'none',
cursor: 'pointer', cursor: 'pointer',
...@@ -1009,7 +1071,26 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1009,7 +1071,26 @@ const handleButtonClick = (buttonLink?: string) => {
position: relative; position: relative;
z-index: 9; 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 { .carousel-swiper {
width: 100%; width: 100%;
...@@ -1084,6 +1165,15 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1084,6 +1165,15 @@ const handleButtonClick = (buttonLink?: string) => {
} }
} }
// === 外部模式:绝对定位(不占用宽度) ===
.carousel-wrapper.outside-nav-absolute {
position: relative;
.carousel-swiper {
width: 100%;
}
}
// === 外部箭头按钮 === // === 外部箭头按钮 ===
.outside-nav-btn { .outside-nav-btn {
flex-shrink: 0; flex-shrink: 0;
...@@ -1100,6 +1190,14 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1100,6 +1190,14 @@ const handleButtonClick = (buttonLink?: string) => {
user-select: none; user-select: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); 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%) // SVG 样式(70%)
.nav-svg { .nav-svg {
width: v-bind('`${props.navigation.size * 0.7}px`'); width: v-bind('`${props.navigation.size * 0.7}px`');
...@@ -1237,6 +1335,7 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1237,6 +1335,7 @@ const handleButtonClick = (buttonLink?: string) => {
height: 100%; height: 100%;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
overflow: hidden;
img { img {
user-select: none; user-select: none;
...@@ -1285,6 +1384,14 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1285,6 +1384,14 @@ const handleButtonClick = (buttonLink?: string) => {
object-fit: cover; object-fit: cover;
display: block; display: block;
} }
.slide-hover-image {
width: 100%;
height: 100%;
display: block;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
// Ken Burns 缩放效果 // Ken Burns 缩放效果
.carousel-swiper { .carousel-swiper {
...@@ -1332,6 +1439,7 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1332,6 +1439,7 @@ const handleButtonClick = (buttonLink?: string) => {
> * { > * {
pointer-events: auto; pointer-events: auto;
} }
} }
// 文案内容容器 // 文案内容容器
...@@ -1341,6 +1449,14 @@ const handleButtonClick = (buttonLink?: string) => { ...@@ -1341,6 +1449,14 @@ const handleButtonClick = (buttonLink?: string) => {
align-items: inherit; // 继承父容器的对齐 align-items: inherit; // 继承父容器的对齐
text-align: center; text-align: center;
max-width: 800px; // 限制最大宽度,提高可读性 max-width: 800px; // 限制最大宽度,提高可读性
transition: opacity 0.3s ease, visibility 0.3s ease;
// 悬浮显示模式
&.show-on-hover {
opacity: 0;
visibility: hidden;
}
.slide-title, .slide-title,
.slide-subtitle, .slide-subtitle,
......
...@@ -92,8 +92,18 @@ const gridCells = computed(() => { ...@@ -92,8 +92,18 @@ const gridCells = computed(() => {
return [] return []
}) })
// 计算当前应该显示的列数(考虑响应式) // 计算当前应该显示的列数(考虑响应式)
const currentColumns = computed(() => { 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 return props.columns
}) })
......
...@@ -16,6 +16,21 @@ ...@@ -16,6 +16,21 @@
/> />
</div> </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 样式 --> <!-- 其他组件:应用 maxWidth 样式 -->
<div v-else :style="getComponentWrapperStyle(comp)"> <div v-else :style="getComponentWrapperStyle(comp)">
<component <component
......
...@@ -8,9 +8,15 @@ export interface CardProps { ...@@ -8,9 +8,15 @@ export interface CardProps {
// 封面图片 // 封面图片
coverImage?: string // 封面图片 URL coverImage?: string // 封面图片 URL
coverHeight: number // 封面高度(px,纵向布局)或宽度(横向布局) coverHeight: number // 封面高度(px,纵向布局)或宽度(横向布局)- 兼容旧数据
coverFit: 'cover' | 'contain' | 'fill' // 图片适配方式 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 // 主标题 title: string // 主标题
titleSize: number // 标题字号 titleSize: number // 标题字号
......
...@@ -16,6 +16,7 @@ export interface CarouselImage { ...@@ -16,6 +16,7 @@ export interface CarouselImage {
description?: string // 描述 description?: string // 描述
buttonText?: string // 按钮文字 buttonText?: string // 按钮文字
buttonLink?: string // 按钮链接 buttonLink?: string // 按钮链接
showOnHover?: boolean // 是否仅在悬浮时显示(默认 false,始终显示)
} }
} }
...@@ -106,6 +107,7 @@ export interface CarouselProps { ...@@ -106,6 +107,7 @@ export interface CarouselProps {
// 位置 // 位置
position: 'inside' | 'outside' // 内部/外部 position: 'inside' | 'outside' // 内部/外部
offset: number // 距离边缘的偏移 0-50px offset: number // 距离边缘的偏移 0-50px
absolutePosition?: boolean // 外部箭头是否使用绝对定位(不占用组件宽度),默认 false
} }
// 分页指示器(简化,删除透明度选项) // 分页指示器(简化,删除透明度选项)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment