feat(home): 替换首页轮播图实现为广告系统驱动
- 移除原有的硬编码轮播图组件和相关样式 - 新增 getAdByCode 方法用于获取广告数据 - 实现解析广告数据的工具函数 parseSlides 和 parsePx - 集成 useAsyncData 获取 flash 广告数据 - 添加备用图片以确保加载失败时的显示 - 更新页面样式适配新的轮播组件结构
This commit is contained in:
@@ -104,3 +104,13 @@ export async function getCmsAd(id: number) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.data.message));
|
return Promise.reject(new Error(res.data.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAdByCode(code: string){
|
||||||
|
const res = await request.get<ApiResult<CmsAd>>(
|
||||||
|
MODULES_API_URL + '/cms/cms-ad/getByCode/' + code
|
||||||
|
);
|
||||||
|
if (res.data.code === 0 && res.data.data) {
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|||||||
112
app/components/Carousel.vue
Normal file
112
app/components/Carousel.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<section class="carousel-wrap">
|
||||||
|
<div class="carousel-panel" :style="{ height: heightCss }">
|
||||||
|
<ClientOnly>
|
||||||
|
<a-carousel
|
||||||
|
class="carousel"
|
||||||
|
:autoplay="autoplayEnabled"
|
||||||
|
:autoplay-speed="autoplaySpeed"
|
||||||
|
:dots="dotsEnabled"
|
||||||
|
:effect="effect"
|
||||||
|
>
|
||||||
|
<div v-for="it in normalizedItems" :key="it.src" class="carousel-item">
|
||||||
|
<NuxtLink
|
||||||
|
v-if="it.href && !isExternal(it.href)"
|
||||||
|
:to="it.href"
|
||||||
|
class="carousel-link"
|
||||||
|
aria-label="carousel-slide"
|
||||||
|
>
|
||||||
|
<div class="carousel-bg" :style="{ backgroundImage: `url(${it.src})` }"></div>
|
||||||
|
</NuxtLink>
|
||||||
|
<a
|
||||||
|
v-else-if="it.href"
|
||||||
|
class="carousel-link"
|
||||||
|
:href="it.href"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="carousel-slide"
|
||||||
|
>
|
||||||
|
<div class="carousel-bg" :style="{ backgroundImage: `url(${it.src})` }"></div>
|
||||||
|
</a>
|
||||||
|
<div v-else class="carousel-bg" :style="{ backgroundImage: `url(${it.src})` }"></div>
|
||||||
|
</div>
|
||||||
|
</a-carousel>
|
||||||
|
|
||||||
|
<template #fallback>
|
||||||
|
<div class="carousel-bg" :style="{ height: heightCss, backgroundImage: `url(${fallbackSrc})` }"></div>
|
||||||
|
</template>
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
type CarouselItem = {
|
||||||
|
src: string
|
||||||
|
href?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
items: CarouselItem[]
|
||||||
|
height?: number | string
|
||||||
|
autoplaySpeed?: number
|
||||||
|
effect?: 'scrollx' | 'fade'
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
height: 360,
|
||||||
|
autoplaySpeed: 4500,
|
||||||
|
effect: 'fade'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const heightCss = computed(() => (typeof props.height === 'number' ? `${props.height}px` : props.height))
|
||||||
|
|
||||||
|
const normalizedItems = computed(() => (props.items || []).filter((it) => it && typeof it.src === 'string' && it.src))
|
||||||
|
|
||||||
|
const autoplayEnabled = computed(() => normalizedItems.value.length > 1)
|
||||||
|
const dotsEnabled = computed(() => normalizedItems.value.length > 1)
|
||||||
|
|
||||||
|
const fallbackSrc = computed(() => normalizedItems.value[0]?.src || '')
|
||||||
|
|
||||||
|
function isExternal(href: string) {
|
||||||
|
return /^https?:\/\//i.test(href)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.carousel-panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-link {
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-bg {
|
||||||
|
height: 100%;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel :deep(.slick-list),
|
||||||
|
.carousel :deep(.slick-track),
|
||||||
|
.carousel :deep(.slick-slide),
|
||||||
|
.carousel :deep(.slick-slide > div) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,85 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<main class="home">
|
<main class="home">
|
||||||
<section class="mx-auto max-w-screen-xl px-4 py-8">
|
<Carousel class="px-4 py-8" :items="flashSlides" :height="flashHeight" />
|
||||||
<div class="grid grid-cols-12 gap-6">
|
|
||||||
<div class="col-span-12">
|
|
||||||
<div class="panel hero">
|
|
||||||
<div class="hero-bg" aria-hidden="true">
|
|
||||||
<ClientOnly>
|
|
||||||
<a-carousel class="hero-carousel" autoplay effect="fade" :autoplaySpeed="4500" :dots="false">
|
|
||||||
<div
|
|
||||||
v-for="src in heroSlides"
|
|
||||||
:key="src"
|
|
||||||
class="hero-slide"
|
|
||||||
:style="{ backgroundImage: `url(${src})` }"
|
|
||||||
></div>
|
|
||||||
</a-carousel>
|
|
||||||
<template #fallback>
|
|
||||||
<div class="hero-carousel">
|
|
||||||
<div class="hero-slide" :style="{ backgroundImage: `url(${heroSlides[0]})` }"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</ClientOnly>
|
|
||||||
</div>
|
|
||||||
<div class="hero-inner">
|
|
||||||
<!-- <div class="hero-badge">OFFICIAL</div>-->
|
|
||||||
<!-- <div class="hero-title">{{ COMPANY.projectName }}</div>-->
|
|
||||||
<!-- <div class="hero-sub">生物基材料技术研发 · 技术服务 · 食品与农产品流通</div>-->
|
|
||||||
<!-- <div class="mt-6 flex flex-wrap gap-3">-->
|
|
||||||
<!-- <a-button type="primary" size="large" @click="navigateTo('/contact')">-->
|
|
||||||
<!-- <template #icon><PhoneOutlined /></template>-->
|
|
||||||
<!-- 合作咨询-->
|
|
||||||
<!-- </a-button>-->
|
|
||||||
<!-- <a-button size="large" @click="scrollToCompany">-->
|
|
||||||
<!-- <template #icon><IdcardOutlined /></template>-->
|
|
||||||
<!-- 工商信息-->
|
|
||||||
<!-- </a-button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- <div class="mt-6 flex flex-wrap gap-2">-->
|
|
||||||
<!-- <a-tag v-for="t in COMPANY.tags" :key="t" color="green">{{ t }}</a-tag>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <div class="mt-6 grid grid-cols-12 gap-6">-->
|
|
||||||
<!-- <div class="col-span-12 lg:col-span-7">-->
|
|
||||||
<!-- <div class="panel">-->
|
|
||||||
<!-- <div class="section-pill">-->
|
|
||||||
<!-- <span class="pill-left">-->
|
|
||||||
<!-- <AppstoreOutlined />-->
|
|
||||||
<!-- 业务板块-->
|
|
||||||
<!-- </span>-->
|
|
||||||
<!-- <span class="pill-right">SERVICES</span>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
<!-- <div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2">-->
|
|
||||||
<!-- <a-card-->
|
|
||||||
<!-- v-for="s in services"-->
|
|
||||||
<!-- :key="s.title"-->
|
|
||||||
<!-- class="guide-card service-card"-->
|
|
||||||
<!-- :bordered="true"-->
|
|
||||||
<!-- hoverable-->
|
|
||||||
<!-- @click="navigateTo('/products')"-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- <a-space>-->
|
|
||||||
<!-- <a-avatar :size="44" class="guide-icon service-icon">-->
|
|
||||||
<!-- <component :is="s.icon" />-->
|
|
||||||
<!-- </a-avatar>-->
|
|
||||||
<!-- <div>-->
|
|
||||||
<!-- <div class="guide-title">{{ s.title }}</div>-->
|
|
||||||
<!-- <div class="guide-desc">{{ s.desc }}</div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </a-space>-->
|
|
||||||
<!-- </a-card>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- </div>-->
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="banner">
|
<section class="banner">
|
||||||
<div class="mx-auto max-w-screen-xl px-4 py-10">
|
<div class="mx-auto max-w-screen-xl px-4 py-10">
|
||||||
@@ -148,6 +69,8 @@ import {
|
|||||||
ShopOutlined
|
ShopOutlined
|
||||||
} from '@ant-design/icons-vue'
|
} from '@ant-design/icons-vue'
|
||||||
import { usePageSeo } from '@/composables/usePageSeo'
|
import { usePageSeo } from '@/composables/usePageSeo'
|
||||||
|
import { getAdByCode } from '@/api/cms/cmsAd'
|
||||||
|
import type { CmsAd } from '@/api/cms/cmsAd/model'
|
||||||
import { COMPANY } from '@/config/company'
|
import { COMPANY } from '@/config/company'
|
||||||
|
|
||||||
usePageSeo({
|
usePageSeo({
|
||||||
@@ -157,10 +80,78 @@ usePageSeo({
|
|||||||
path: '/'
|
path: '/'
|
||||||
})
|
})
|
||||||
|
|
||||||
const heroSlides = [
|
function parsePx(value?: string) {
|
||||||
'https://file-cloud.yst.com.cn/photo/website/2024/11/28/5a5cc07336224e54a84561c80899bcac.jpg',
|
if (!value) return undefined
|
||||||
'https://oss.wsdns.cn/20260115/75690dea8f064ceda03246b198a7d710.jpg'
|
const m = String(value).match(/(\d+)/)
|
||||||
]
|
if (!m) return undefined
|
||||||
|
const n = Number(m[1])
|
||||||
|
return Number.isFinite(n) ? n : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSlides(ad?: CmsAd | null) {
|
||||||
|
const raw = ad?.imageList
|
||||||
|
const fallbackHref = ad?.path
|
||||||
|
|
||||||
|
function pickString(obj: Record<string, unknown>, keys: string[]) {
|
||||||
|
for (const k of keys) {
|
||||||
|
const v = obj[k]
|
||||||
|
if (typeof v === 'string' && v.trim()) return v
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function asArray(val: unknown): unknown[] {
|
||||||
|
if (!val) return []
|
||||||
|
if (Array.isArray(val)) return val
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
const text = val.trim()
|
||||||
|
if (!text) return []
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(text)
|
||||||
|
return Array.isArray(parsed) ? parsed : [parsed]
|
||||||
|
} catch {
|
||||||
|
return text.split(/[,;\n]+/g).map((s) => s.trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof val === 'object') {
|
||||||
|
const obj = val as Record<string, unknown>
|
||||||
|
if (Array.isArray(obj.list)) return obj.list
|
||||||
|
if (Array.isArray(obj.imageList)) return obj.imageList
|
||||||
|
if (Array.isArray(obj.images)) return obj.images
|
||||||
|
return [obj]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return asArray(raw)
|
||||||
|
.map((it) => {
|
||||||
|
if (typeof it === 'string') return { src: it, href: fallbackHref }
|
||||||
|
if (it && typeof it === 'object') {
|
||||||
|
const obj = it as Record<string, unknown>
|
||||||
|
const src = pickString(obj, ['src', 'url', 'image', 'imageUrl', 'img', 'imgUrl', 'pic', 'picUrl'])
|
||||||
|
const href = pickString(obj, ['href', 'link', 'path', 'to']) ?? fallbackHref
|
||||||
|
if (!src) return null
|
||||||
|
return { src, href }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
.filter(Boolean) as Array<{ src: string; href?: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: flashAd } = await useAsyncData('cms-ad-flash', () =>
|
||||||
|
getAdByCode('flash').catch(() => null)
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(flashAd.value?.imageList,'flashAdflashAdflashAd');
|
||||||
|
|
||||||
|
const flashSlides = computed(() => {
|
||||||
|
const slides = parseSlides(flashAd.value)
|
||||||
|
return slides.length
|
||||||
|
? slides
|
||||||
|
: [{ src: 'https://file-cloud.yst.com.cn/photo/website/2024/11/28/5a5cc07336224e54a84561c80899bcac.jpg' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const flashHeight = computed(() => parsePx(flashAd.value?.height) ?? 360)
|
||||||
|
|
||||||
function splitScope(text: string) {
|
function splitScope(text: string) {
|
||||||
return text
|
return text
|
||||||
@@ -274,75 +265,6 @@ function scrollToCompany() {
|
|||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
|
||||||
position: relative;
|
|
||||||
min-height: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-bg {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-carousel {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-slide {
|
|
||||||
height: 100%;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-carousel :deep(.slick-list),
|
|
||||||
.hero-carousel :deep(.slick-track),
|
|
||||||
.hero-carousel :deep(.slick-slide),
|
|
||||||
.hero-carousel :deep(.slick-slide > div) {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-inner {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
height: 100%;
|
|
||||||
padding: 28px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-badge {
|
|
||||||
width: fit-content;
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 9999px;
|
|
||||||
background: rgba(22, 163, 74, 0.12);
|
|
||||||
color: rgba(21, 128, 61, 0.95);
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
margin-top: 14px;
|
|
||||||
font-size: 42px;
|
|
||||||
line-height: 1.1;
|
|
||||||
font-weight: 900;
|
|
||||||
color: rgba(0, 0, 0, 0.9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-sub {
|
|
||||||
margin-top: 12px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: rgba(0, 0, 0, 0.65);
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-head {
|
.panel-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -522,14 +444,4 @@ function scrollToCompany() {
|
|||||||
color: rgba(0, 0, 0, 0.55);
|
color: rgba(0, 0, 0, 0.55);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.hero-inner {
|
|
||||||
padding: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-title {
|
|
||||||
font-size: 34px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user