frontend/src/views/home/index.vue
2026-01-27 10:08:03 +08:00

1073 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="main-container">
<Header />
<div class="banner">
<Swiper
:modules="[SwiperAutoplay, SwiperPagination]"
:slides-per-view="1"
:loop="true"
:autoplay="{
delay: 5000,
disableOnInteraction: false,
pauseOnMouseEnter: true,
}"
:pagination="{
clickable: true,
}"
:speed="1000"
effect="fade"
class="swiper-banner"
>
<SwiperSlide
v-for="(slide, index) in bannerSlides"
:key="index"
class="slide"
>
<div class="slide-bg">
<video
v-if="slide.image.endsWith('.mp4')"
:src="slide.image"
autoplay
loop
muted
playsinline
class="slide-media"
></video>
<img
v-else
:src="slide.image"
class="slide-media"
alt="Banner Image"
/>
<div class="banner-content">
<h2 v-if="slide.title" class="slide-title">{{ slide.title }}</h2>
<p
v-if="slide.description"
class="slide-description"
v-html="slide.description"
></p>
<button v-if="slide.buttonText" class="slide-button">
{{ slide.buttonText }}
</button>
</div>
</div>
</SwiperSlide>
</Swiper>
</div>
<!-- 头条 -->
<div class="topnews">
<div class="content">
<div class="left">
<span><i class="fas fa-volume-down"></i></span>
<span>美天头条</span>
<span>|</span>
<span>
国家认证 + 技术硬核!江苏美天智能科技凭什么跻身国家级高新技术企业
</span>
</div>
<div class="right">
<span class="more" @click="handleMoreNewsClick">more news</span>
</div>
</div>
</div>
<main class="content-container">
<!-- 新闻中心 -->
<div class="newscenter">
<div class="title">
<span class="text-4xl">新闻中心</span>
<span class="text-xl">&nbsp;&nbsp;/&nbsp;&nbsp;NEWS CENTER</span>
</div>
<div class="content">
<div class="left">
<div
v-if="newsData[activeNewsIndex]?.image"
class="news-banner"
:style="{
backgroundImage: `url(${newsData[activeNewsIndex]?.image})`,
}"
></div>
<div v-else class="news-banner"></div>
</div>
<div
class="news-list"
@mouseenter="stopAutoScroll"
@mouseleave="startAutoScroll"
>
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<i class="fas fa-spinner fa-spin"></i>
<span>加载中...</span>
</div>
<!-- 错误信息 -->
<div v-else-if="error" class="error-state">
<i class="fas fa-exclamation-circle"></i>
<span>{{ error }}</span>
</div>
<!-- 新闻列表 -->
<div
v-else-if="newsData.length > 0"
v-for="(news, index) in newsData"
:key="news.id"
class="news-item"
:class="{ active: index === activeNewsIndex }"
@click="goToDetail(news.id)"
@mouseenter="setActiveNews(index)"
>
<div class="news-date">
<div class="date">{{ news.date }}</div>
<div class="month">{{ news.month }}</div>
</div>
<div class="news-content">
<div class="news-title-container">
<span v-if="news.recommend === 1" class="recommend-tag">
推荐
</span>
<span class="news-title">
{{ news.title }}
</span>
</div>
<span class="news-desc">
{{ news.description || news.desc }}
</span>
</div>
</div>
<!-- 无数据状态 -->
<div v-else class="empty-state">
<i class="fas fa-newspaper"></i>
<span>暂无新闻数据</span>
</div>
</div>
</div>
</div>
<div class="hr"></div>
<!-- 解决方案 -->
<div class="solutions">
<div class="title">
<span class="text-4xl">行业解决方案</span>
<span class="text-xl">
&nbsp;&nbsp;/&nbsp;&nbsp;INDUSTRY SOLUTIONS
</span>
</div>
<div class="solutions-container" v-if="solutions.length >= 4">
<!-- 第一行:两个大卡片 -->
<div class="top-row">
<div
v-if="solutions[0]"
class="solution-card large"
:style="{ backgroundImage: `url(${solutions[0].image})` }"
>
<div class="solution-overlay">
<div class="solution-icon">
<i :class="solutions[0].icon"></i>
</div>
<h3>{{ solutions[0].title }}</h3>
<p>{{ solutions[0].desc }}</p>
</div>
</div>
<div
v-if="solutions[1]"
class="solution-card large"
:style="{ backgroundImage: `url(${solutions[1].image})` }"
>
<div class="solution-overlay">
<div class="solution-icon">
<i :class="solutions[1].icon"></i>
</div>
<h3>{{ solutions[1].title }}</h3>
<p>{{ solutions[1].desc }}</p>
</div>
</div>
</div>
<!-- 第二行:两个小卡片 -->
<div class="bottom-row">
<div
v-if="solutions[2]"
class="solution-card small"
:style="{ backgroundImage: `url(${solutions[2].image})` }"
>
<div class="solution-overlay">
<div class="solution-icon">
<i :class="solutions[2].icon"></i>
</div>
<h3>{{ solutions[2].title }}</h3>
<p>{{ solutions[2].desc }}</p>
</div>
</div>
<div
v-if="solutions[3]"
class="solution-card small"
:style="{ backgroundImage: `url(${solutions[3].image})` }"
>
<div class="solution-overlay">
<div class="solution-icon">
<i :class="solutions[3].icon"></i>
</div>
<h3>{{ solutions[3].title }}</h3>
<p>{{ solutions[3].desc }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- 平台数据 -->
<div
class="mt20 newsfoot"
ref="platformRef"
style="
background: url('/src/assets/images/indexImg/about_b1.png') no-repeat
center bottom;
background-size: cover;
display: flex;
align-items: center;
"
>
<div style="width: 1200px; margin: 0 auto">
<div style="display: flex; justify-content: space-between">
<div
style="text-align: center"
v-for="item in platformStats"
:key="item.id"
>
<div>
<span>
<span style="font-size: 48px; font-weight: bold">
{{ item.current }}
</span>
<span style="font-size: 48px; font-weight: bold">+</span>
{{ item.unit }}
</span>
<br />
<span style="font-size: 20px">{{ item.label }}</span>
</div>
</div>
</div>
</div>
</div>
<!--合作伙伴-->
<div class="partners">
<div class="title">
<span class="text-4xl">合作伙伴</span>
<span class="text-xl">&nbsp;&nbsp;/&nbsp;&nbsp;PARTNERS</span>
</div>
<div class="partners-content">
<div class="partner-inner" v-for="url in partnerImages">
<el-image :key="url" :src="url" lazy />
</div>
</div>
</div>
</main>
<Footer />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { Swiper, SwiperSlide } from 'swiper/vue'
import {
Autoplay as SwiperAutoplay,
Pagination as SwiperPagination,
} from 'swiper/modules'
import 'swiper/css'
import 'swiper/css/autoplay'
import 'swiper/css/pagination'
import 'swiper/css/effect-fade'
import Header from '../components/header.vue'
import Footer from '../components/footer.vue'
// 引入API调用
import { getNewsCenterTop4 } from '@/api/article'
const router = useRouter()
// 跳转到详情页
const goToDetail = (id: number) => {
router.push(`/newscenter/companyNews/detail/${id}`)
}
// 在文件顶部添加视频导入
import bannerVideo1 from '@/assets/images/banner/banner_video1.mp4'
import bannerVideo2 from '@/assets/images/banner/banner_video2.mp4'
// 轮播图数据
const bannerSlides = [
{
image: bannerVideo1,
title: '制造业数字化转型解决方案',
description:
'制造业数字化转型解决方案源于金蝶百万企业最佳实践。<br>为制造业提供新的经济增长点,帮助制造业做大做强。',
buttonText: '了解更多',
},
{
image: bannerVideo2,
title: '美天云科技平台',
description:
'助力企业数字化转型,打造财务管理新世界。<br>共需求驱动,敏捷运营,助力企业优化供应链。<br>数字技术与精益生产深度融合,帮助制造企业打造高效运营的数字化工厂。',
buttonText: '查看详情',
},
{
image: 'https://picsum.photos/1920/600?random=3',
title: '限时优惠',
description: '限时特惠,不容错过',
buttonText: '立即购买',
},
]
// 新闻数据
const newsData = ref<any[]>([])
// 加载状态
const loading = ref(false)
// 错误信息
const error = ref('')
// 获取新闻数据
const fetchNewsData = async () => {
loading.value = true
error.value = ''
try {
const response = await getNewsCenterTop4()
// console.log('数据是:', response)
if (response.code === 200) {
// 处理图片URL确保正确显示
const processedNews = response.list.map((item: any) => ({
...item,
// 确保图片URL是完整的使用环境变量中的API基础URL
image: item.image
? item.image.startsWith('http')
? item.image
: `${import.meta.env.VITE_APP_API_URL}${item.image}`
: '',
}))
newsData.value = processedNews
} else {
error.value = '获取新闻数据失败'
console.error('获取新闻数据失败:', response)
}
} catch (err) {
error.value = '获取新闻数据时发生错误'
console.error('获取新闻数据错误:', err)
} finally {
loading.value = false
}
}
//解决方案数据
const solutions = ref([
{
title: '内勤保障解决方案',
desc: '内勤保障一站式解决方案',
icon: 'fas fa-briefcase',
image: '/src/assets/images/indexImg/view1.jpg',
},
{
title: '港航供应链一体化解决方案',
desc: '软硬件一体化综合解决方案,让场内运输管理、堆场管理、仓库盘查清查更简单。',
icon: 'fas fa-ship',
image: '/src/assets/images/indexImg/view2.jpg',
},
{
title: '帆船训练解决方案',
desc: '优化训练数据,提升训练安全性',
icon: 'fas fa-sailboat',
image: '/src/assets/images/indexImg/view3.jpg',
},
{
title: '进销存解决方案',
desc: '企业门店一体化综合解决方案',
icon: 'fas fa-store',
image: '/src/assets/images/indexImg/view4.jpg',
},
])
// 当前选中的新闻索引
const activeNewsIndex = ref<number>(0)
// 设置当前选中的新闻
const setActiveNews = (index: number): void => {
if (index >= 0 && index < newsData.value.length) {
activeNewsIndex.value = index
}
}
// 自动滚动每5秒切换一条悬停暂停/离开恢复
let newsAutoTimer: number | null = null
const scrollToNextNews = (): void => {
const next = (activeNewsIndex.value + 1) % newsData.value.length
activeNewsIndex.value = next
}
const startAutoScroll = (): void => {
stopAutoScroll()
newsAutoTimer = window.setInterval(scrollToNextNews, 5000)
}
const stopAutoScroll = (): void => {
if (newsAutoTimer) {
clearInterval(newsAutoTimer)
newsAutoTimer = null
}
}
onMounted(() => {
startAutoScroll()
fetchNewsData()
loadImagesWithOptions()
})
onUnmounted(() => stopAutoScroll())
const handleMoreNewsClick = () => {
// console.log('more news clicked')
}
const partnerImages = ref<any[]>([])
// TODO 改为接口读取图片文件
const loadImagesWithOptions = async () => {
try {
// 如果需要过滤特定格式或处理
const modules = import.meta.glob('/src/assets/images/indexImg/partner/*', {
eager: true,
query: '?url',
import: 'default',
})
const images = []
for (const path in modules) {
// 可选:根据文件名过滤或排序
if (path.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) {
images.push(modules[path])
}
}
// 按文件名排序(可选)
images.sort()
partnerImages.value = images
} catch (error) {
console.error('加载图片失败:', error)
}
}
const platformStats = ref([
{ id: 1, label: '国家软件著作权', target: 20, current: 0, unit: '项' },
{ id: 2, label: '知识产权', target: 10, current: 0, unit: '项' },
{ id: 3, label: '合作商', target: 20, current: 0, unit: '家' },
{ id: 4, label: '客户', target: 110, current: 0, unit: '家' },
])
const platformRef = ref<HTMLElement | null>(null)
let observer: IntersectionObserver | null = null
let hasAnimated = false
const animateNumber = (item: any) => {
const step = Math.ceil(item.target / 40)
const timer = setInterval(() => {
item.current += step
if (item.current >= item.target) {
item.current = item.target
clearInterval(timer)
}
}, 50)
}
onMounted(() => {
observer = new IntersectionObserver(
([entry]) => {
if (entry?.isIntersecting && !hasAnimated) {
hasAnimated = true
platformStats.value.forEach(animateNumber)
}
},
{ threshold: 0.3 },
)
if (platformRef.value) {
observer.observe(platformRef.value)
}
})
onUnmounted(() => {
observer?.disconnect()
})
</script>
<style lang="scss" scoped>
.main-container {
display: flex;
flex-direction: column;
min-height: 100vh;
width: 100%;
overflow-x: hidden;
.banner {
position: relative;
width: 100%;
height: 740px;
overflow: hidden;
.swiper-banner {
width: 100%;
height: 100%;
}
.slide {
width: 100%;
height: 100%;
}
.slide-bg {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
}
}
.banner-content {
position: relative;
z-index: 10;
color: white;
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 0 20px;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
.slide-title {
font-size: 48px;
font-weight: bold;
margin-bottom: 20px;
opacity: 0;
animation: slideUp 0.6s ease forwards 0.3s;
}
.slide-description {
font-size: 18px;
margin-bottom: 30px;
max-width: 600px;
opacity: 0;
animation: slideUp 0.6s ease forwards 0.5s;
}
.slide-button {
padding: 12px 30px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
opacity: 0;
animation: slideUp 0.6s ease forwards 0.7s;
transition: all 0.3s ease;
&:hover {
background-color: #40a9ff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
}
/* 分页器样式 */
:deep(.swiper-pagination-bullet) {
width: 10px;
height: 10px;
background: rgba(255, 255, 255, 0.5);
opacity: 1;
margin: 0 5px;
transition: all 0.3s ease;
}
:deep(.swiper-pagination-bullet-active) {
width: 30px;
border-radius: 5px;
background: #1890ff;
}
/* 动画 */
@keyframes slideUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式调整 */
@media (max-width: 992px) {
height: 500px;
.banner-content {
.slide-title {
font-size: 36px;
}
.slide-description {
font-size: 16px;
}
}
}
@media (max-width: 768px) {
height: 400px;
margin-bottom: 20px;
.banner-content {
text-align: center;
padding: 0 30px;
.slide-title {
font-size: 28px;
margin-bottom: 15px;
}
.slide-description {
font-size: 15px;
margin: 0 auto 25px;
max-width: 100%;
}
.slide-button {
padding: 10px 25px;
font-size: 15px;
}
}
}
}
.content-container {
width: 100%;
//max-width: 1200px;
margin: 0 auto;
padding: 20px 0;
flex: 1;
box-sizing: border-box;
overflow: hidden;
.content {
margin: 0 auto;
}
.newscenter {
.title {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 0;
}
.content {
max-width: 1200px;
display: flex;
align-items: center;
gap: 30px;
.left {
flex: 1;
.news-banner {
width: 600px;
height: 480px;
background-color: #f0f2f5;
border-radius: 8px;
background-size: cover;
background-position: center;
transition: all 0.5s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
.news-list {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
/* 加载、错误、无数据状态样式 */
.loading-state,
.error-state,
.empty-state {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
text-align: center;
border-radius: 8px;
background-color: #f9f9f9;
color: #666;
font-size: 16px;
i {
margin-right: 10px;
font-size: 24px;
}
.error-state {
color: #f56c6c;
i {
color: #f56c6c;
}
}
}
.news-item {
display: flex;
padding: 15px;
border-radius: 8px;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
&:hover {
box-shadow: 0 2px 12px rgba(16, 140, 255, 0.1);
background-color: #1890ff;
.news-content {
.news-title,
.news-desc {
color: white !important;
}
}
}
&.active {
box-shadow: 0 2px 12px rgba(16, 140, 255, 0.1);
background-color: #1890ff;
.news-content {
.news-title,
.news-desc {
color: white !important;
}
}
}
.news-date {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80px;
flex-shrink: 0;
margin-right: 20px;
padding: 12px 0;
background: #f9f9f9;
border-radius: 6px;
.date {
font-size: 24px;
font-weight: bold;
color: #1890ff;
}
.month {
font-size: 14px;
color: #666;
margin-top: 4px;
}
}
.news-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
/* 标题容器使用flex布局 */
.news-title-container {
display: flex;
align-items: center;
}
.news-title {
font-size: 16px;
font-weight: 500;
color: #333;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
/* 推荐标签样式 */
.recommend-tag {
display: inline-block;
padding: 2px 6px;
background-color: #f56c6c;
color: white;
font-size: 12px;
font-weight: normal;
margin-right: 8px;
border-radius: 4px;
line-height: 1;
white-space: nowrap;
}
.news-desc {
font-size: 14px;
color: #666;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
}
.solutions {
padding: 60px 0;
.title {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 0;
}
.solutions-container {
display: flex;
flex-direction: column;
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
gap: 20px;
}
.top-row {
display: flex;
gap: 20px;
}
.bottom-row {
display: flex;
gap: 20px;
}
.solution-card {
position: relative;
border-radius: 8px;
overflow: hidden;
background-size: cover;
background-position: center;
transition: all 0.3s ease;
cursor: pointer;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.1),
rgba(0, 0, 0, 0.7)
);
transition: all 0.3s ease;
}
&:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
//&::before {
//background: rgba(24, 144, 255, 0.8);
//}
.solution-overlay {
opacity: 1;
transform: translateY(0);
}
}
}
.solution-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 30px 20px;
color: white;
text-align: left;
opacity: 1;
transform: none;
transition: all 0.3s ease;
.solution-icon {
font-size: 40px;
margin-bottom: 15px;
color: white;
}
h3 {
font-size: 18px;
margin-bottom: 10px;
color: white;
}
p {
font-size: 14px;
line-height: 1.6;
margin: 0;
opacity: 0.9;
}
}
/* 尺寸定义2x2网格布局 */
.top-row .solution-card.large {
flex: 1;
height: 350px;
}
.bottom-row .solution-card.small {
flex: 1;
height: 250px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.top-row,
.bottom-row {
flex-direction: column;
}
.top-row .solution-card.large,
.bottom-row .solution-card.small {
height: 250px;
}
}
}
}
}
.topnews {
color: #646767;
font-size: var(--topnews-size);
background-color: #f6f7f7;
.content {
width: 1200px;
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 auto;
padding: 40px 0;
span {
margin-right: 10px;
}
.right {
.more {
cursor: pointer;
color: #646767;
&:hover {
color: #333;
font-weight: 500;
transition: all 0.1s ease;
}
}
}
}
}
.slide-media {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 1;
}
.slide-bg {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.hr {
width: 100%;
height: 60px;
}
.newsfoot {
min-height: 320px;
color: #fff;
}
.partners {
.title {
display: flex;
align-items: center;
justify-content: center;
padding: 40px 0;
}
.partners-content {
max-width: 1200px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 20px 20px;
.partner-inner {
padding: 1.5rem 2.375rem;
margin: -1px;
background-color: #fff;
border: 1px solid #fff;
font-size: 0;
height: 100px;
display: flex;
align-items: center;
}
.partner-inner:hover {
border: 1px solid #3650f9;
box-shadow: 1px 3px 20px rgba(0, 0, 0, 0.2);
}
}
}
</style>