Commit 18f5f06e authored by youjie's avatar youjie
parents f9b600a1 af3e1ed6
...@@ -124,12 +124,44 @@ const cardStyle = computed(() => { ...@@ -124,12 +124,44 @@ const cardStyle = computed(() => {
// 封面样式 // 封面样式
const coverStyle = computed(() => { const coverStyle = computed(() => {
const style: Record<string, string> = {} 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 { } else {
style.width = `${props.coverHeight}px` // 兼容旧数据:使用 coverHeight
style.flexShrink = '0' if (props.layout === 'vertical') {
style.height = `${props.coverHeight}px`
} else {
style.width = `${props.coverHeight}px`
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
}) })
......
...@@ -259,7 +259,7 @@ const buttonStyleComputed = computed(() => { ...@@ -259,7 +259,7 @@ const buttonStyleComputed = computed(() => {
// 下拉面板样式 // 下拉面板样式
const dropdownStyleComputed = computed(() => ({ const dropdownStyleComputed = computed(() => ({
maxHeight: `${props.dropdownStyle.maxHeight}px`, maxHeight: props.dropdownStyle.maxHeight==0?"unset":`${props.dropdownStyle.maxHeight}px`,
borderRadius: `${props.dropdownStyle.borderRadius}px`, borderRadius: `${props.dropdownStyle.borderRadius}px`,
backgroundColor: props.dropdownStyle.backgroundColor, backgroundColor: props.dropdownStyle.backgroundColor,
boxShadow: props.dropdownStyle.boxShadow, boxShadow: props.dropdownStyle.boxShadow,
......
...@@ -15,7 +15,13 @@ ...@@ -15,7 +15,13 @@
class="social-icon hover:opacity-80 transition-opacity" class="social-icon hover:opacity-80 transition-opacity"
:title="t(`footer.social.${key}`)" :title="t(`footer.social.${key}`)"
> >
<span class="text-lg">{{ getSocialIcon(key) }}</span> <VIcon
v-if="getSocialIconName(key)"
:name="getSocialIconName(key)!"
fill="outline"
class="text-[24px]"
/>
<span v-else class="text-sm">{{ key }}</span>
</a> </a>
</div> </div>
...@@ -51,47 +57,14 @@ ...@@ -51,47 +57,14 @@
<!-- 中间区域:链接列 --> <!-- 中间区域:链接列 -->
<div class="footer-middle border-b border-gray-700 py-8"> <div class="footer-middle border-b border-gray-700 py-8">
<div class="footer-container"> <div class="footer-container">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8"> <div class="grid grid-cols-1 gap-8" :class="[`md:grid-cols-${bottomNavs.length+1}`]">
<!-- 关于我们 -->
<div>
<h3 class="font-semibold mb-4">{{ t('footer.about.title') }}</h3>
<ul class="space-y-2">
<li v-for="link in aboutLinks" :key="link.key">
<a
:href="link.url"
class="text-sm text-gray-300 hover:text-white transition-colors"
>
{{ t(`footer.about.${link.key}`) }}
</a>
</li>
</ul>
</div>
<!-- 给旅人 -->
<div>
<h3 class="font-semibold mb-4">{{ t('footer.travelers.title') }}</h3>
<ul class="space-y-2">
<li v-for="link in travelerLinks" :key="link.key">
<a
:href="link.url"
class="text-sm text-gray-300 hover:text-white transition-colors"
>
{{ t(`footer.travelers.${link.key}`) }}
</a>
</li>
</ul>
</div>
<!-- 合作伙伴 --> <div v-for="item in bottomNavs" :key="item.id">
<div> <h3 class="font-semibold mb-4">{{ t(item.navTitle) }}</h3>
<h3 class="font-semibold mb-4">{{ t('footer.partners.title') }}</h3>
<ul class="space-y-2"> <ul class="space-y-2">
<li v-for="link in partnerLinks" :key="link.key"> <li v-for="child in item.childList" :key="child.id">
<a <a :href="child.navUrl??'javascript:void(0);'" class="text-sm text-gray-300 hover:text-white transition-colors">
:href="link.url" {{ t(child.navTitle) }}
class="text-sm text-gray-300 hover:text-white transition-colors"
>
{{ t(`footer.partners.${link.key}`) }}
</a> </a>
</li> </li>
</ul> </ul>
...@@ -110,10 +83,9 @@ ...@@ -110,10 +83,9 @@
:name="method.icon" :name="method.icon"
fill="outline" fill="outline"
class="text-[20px]" class="text-[20px]"
v-if="method.icon && method.icon!=''"
/> />
<span class="text-xs text-gray-200"> <img :src="method.url" :alt="method.label" class="w-[42px] h-[auto]" v-if="method.url && method.url!=''" />
{{ method.label }}
</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -155,84 +127,97 @@ import { useI18n } from 'vue-i18n' ...@@ -155,84 +127,97 @@ import { useI18n } from 'vue-i18n'
import { useSystemConfigStore } from '@/stores/systemConfig' import { useSystemConfigStore } from '@/stores/systemConfig'
import { IconUp } from '@arco-design/web-vue/es/icon' import { IconUp } from '@arco-design/web-vue/es/icon'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import VIcon from '@/components/global/VIcon.vue'
const { t } = useI18n() const { t } = useI18n()
const systemConfigStore = useSystemConfigStore() const systemConfigStore = useSystemConfigStore()
const email = ref('') const email = ref('')
const bottomNavs = computed(() => systemConfigStore.getBottomNavs)
// 社交媒体字段到 VIcon 图标名称的映射
const socialIconMap: Record<string, string> = {
facebook: 'facebook',
instagram: 'instagram',
snapchat: 'snapchat',
whatsapp: 'whatsapp',
youtube: 'youtube',
twitter: 'twitter',
tiktok: 'tiktok',
}
// 社交媒体链接 // 所有支持的社交媒体字段
const allSocialFields = [
'facebook', 'instagram', 'kakaotalk', 'line', 'linkedin',
'messenger', 'naver', 'signal', 'snapchat', 'telegram',
'tiktok', 'twitter', 'viber', 'wechat', 'weibo',
'whatsapp', 'xiaohongshu', 'youtube', 'zalo'
]
// 社交媒体链接(自动过滤空值)
const socialMediaLinks = computed(() => { const socialMediaLinks = computed(() => {
const config = systemConfigStore.config const config = systemConfigStore.config
if (!config) return {} if (!config) return {}
const links: Record<string, string> = {} const links: Record<string, string> = {}
const socialFields = [
'facebook', 'youtube', 'instagram', 'weibo', 'twitter', 'pinterest'
]
socialFields.forEach(field => { allSocialFields.forEach(field => {
const value = (config as any)[field] const value = (config as any)[field]
if (value && typeof value === 'string') { // 只添加非空字符串链接
links[field] = value if (value && typeof value === 'string' && value.trim() !== '') {
links[field] = value.trim()
} }
}) })
return links return links
}) })
// 获取社交媒体图标组件 // 获取社交媒体图标名称(如果支持)
const getSocialIcon = (key: string) => { const getSocialIconName = (key: string): string | null => {
// 使用简单的文本图标,实际项目中可以使用图标库 return socialIconMap[key] || null
const iconMap: Record<string, string> = {
facebook: 'f',
youtube: '▶',
instagram: '📷',
weibo: '微博',
twitter: 'X',
pinterest: 'P'
}
return iconMap[key] || key
} }
// 关于我们链接 // 汇总没有图标的媒体(用于调试,可在控制台查看)
const aboutLinks = [ // 使用 console.log 输出,避免未使用变量的警告
{ key: 'about', url: '/about' }, if (import.meta.env.DEV) {
{ key: 'terms', url: '/terms' }, const checkSocialMediaWithoutIcons = () => {
{ key: 'privacy', url: '/privacy' }, const config = systemConfigStore.config
{ key: 'help', url: '/help' }, if (!config) return
{ key: 'media', url: '/media' }
] const withoutIcons: string[] = []
// 给旅人链接 allSocialFields.forEach(field => {
const travelerLinks = [ const value = (config as any)[field]
{ key: 'guarantee', url: '/guarantee' }, // 如果有值但没有对应的图标
{ key: 'blog', url: '/blog' }, if (value && typeof value === 'string' && value.trim() !== '' && !socialIconMap[field]) {
{ key: 'points', url: '/points' }, withoutIcons.push(field)
{ key: 'redeem', url: '/redeem' }, }
{ key: 'pointsCard', url: '/points-card' } })
]
if (withoutIcons.length > 0) {
console.log('[Footer] 没有图标的社交媒体:', withoutIcons)
}
}
// 延迟检查,确保 config 已加载
setTimeout(checkSocialMediaWithoutIcons, 1000)
}
// 合作伙伴链接
const partnerLinks = [
{ key: 'becomeSupplier', url: '/become-supplier' },
{ key: 'supplierLogin', url: '/supplier-login' },
{ key: 'rezio', url: '/rezio' },
{ key: 'cooperation', url: '/cooperation' },
{ key: 'affiliate', url: '/affiliate' }
]
// 支付方式图标(使用 Keenicons,通过全局组件 VIcon 渲染) // 支付方式图标(使用 Keenicons,通过全局组件 VIcon 渲染)
const paymentIcons = [ const paymentIcons = [
{ key: 'card', icon: 'two-credit-cart', label: 'Credit Card' }, {key:'visa',icon:'',label:'Visa',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/visa.svg'},
{ key: 'paypal', icon: 'paypal', label: 'PayPal' }, {key:'mastercard',icon:'',label:'MasterCard',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/mastercard.svg'},
{ key: 'apple', icon: 'apple', label: 'Apple Pay' }, {key:'jcb',icon:'',label:'JCB',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/jcb.svg'},
{ key: 'google', icon: 'google-play', label: 'Google Pay' }, {key:'jko_pay',icon:'',label:'JKO Pay',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/jko_pay.svg'},
{key:'apple_pay',icon:'',label:'Apple Pay',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/apple_pay.svg'},
{key:'google_pay',icon:'',label:'Google Pay',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/google_pay.svg'},
{key:'line_pay',icon:'',label:'Line Pay',url:'https://cdn.kkday.com/pc-web/assets/img/footer/payment/line_pay.svg'},
] ]
// 版权信息 // 版权信息
const copyright = computed(() => { const copyright = computed(() => {
return systemConfigStore.copyright || `COPYRIGHT © ${new Date().getFullYear()} All rights reserved.` return systemConfigStore.platformConfig?.copyright || `COPYRIGHT © ${new Date().getFullYear()} All rights reserved.`
}) })
// 订阅 Newsletter // 订阅 Newsletter
......
<template> <template>
<div class="w-[1200px] py-2 flex items-center justify-between h-[60px] text-xs text-gray-700"> <div class="w-[1200px] mx-2 py-2 flex items-center justify-between h-[60px] text-xs text-gray-700">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<img <img
v-if="logo" v-if="logo"
......
<template> <template>
<header class="app-header shadow-sm customPrimary-bg-7 flex flex-col items-center"> <header class="app-header flex flex-col items-center pt-[60px]">
<HeaderTopBar /> <div class="fixed top-0 left-0 right-[14px] z-10 shadow-sm customPrimary-bg-7 flex justify-center">
<HeaderTopBar />
</div>
<div class="h-[1px] w-full bg-gray-700/5"></div> <div class="h-[1px] w-full bg-gray-700/5"></div>
<HeaderNavBar /> <HeaderNavBar />
</header> </header>
......
...@@ -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
......
...@@ -152,7 +152,9 @@ export const useSystemConfigStore = defineStore('systemConfig', { ...@@ -152,7 +152,9 @@ export const useSystemConfigStore = defineStore('systemConfig', {
getTopNavs: (state): NavItemDto[] => { getTopNavs: (state): NavItemDto[] => {
return state.navs?.filter(item => item.type === 1) || [] return state.navs?.filter(item => item.type === 1) || []
}, },
getBottomNavs: (state): NavItemDto[] => {
return state.navs?.filter(item => item.type === 2) || []
},
}, },
actions: { actions: {
/** /**
......
...@@ -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 // 标题字号
......
...@@ -13,9 +13,10 @@ export interface CarouselImage { ...@@ -13,9 +13,10 @@ export interface CarouselImage {
content?: { content?: {
title?: string // 标题 title?: string // 标题
subtitle?: string // 副标题 subtitle?: string // 副标题
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