|
@@ -2,23 +2,26 @@
|
|
|
<DefaultLayout>
|
|
|
<!-- banner -->
|
|
|
<section class="banner-section">
|
|
|
- <img src="@assets/cases-banner.png" alt="" srcset="">
|
|
|
+ <div class="banner-image animate-fade-in">
|
|
|
+ <img src="@assets/cases-banner.png" alt="" srcset="">
|
|
|
+ </div>
|
|
|
</section>
|
|
|
|
|
|
<!-- 智慧解决方案 -->
|
|
|
- <section class="smart-solutions">
|
|
|
+ <section class="smart-solutions" ref="solutionsSection">
|
|
|
<div class="container">
|
|
|
<!-- 标题区域 -->
|
|
|
- <div class="title-section">
|
|
|
+ <div class="title-section animate-fade-up" :class="{ 'animate-visible': isSolutionsVisible }">
|
|
|
<h2 class="main-title">智慧解决方案</h2>
|
|
|
<p class="sub-title">SMART SOLUTION</p>
|
|
|
<p class="description">智慧管理系统采用先进的开发技术,为智慧产业提供一站式解决方案</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- 解决方案列表 -->
|
|
|
- <div class="solutions-grid">
|
|
|
- <div v-for="solution in visibleSolutions" :key="solution.id" @click="goToCaseDetail(solution)"
|
|
|
- class="solution-card">
|
|
|
+ <div class="solutions-grid animate-fade-up" :class="{ 'animate-visible': isSolutionsVisible }" style="animation-delay: 0.3s">
|
|
|
+ <div v-for="(solution, index) in visibleSolutions" :key="solution.id" @click="goToCaseDetail(solution)"
|
|
|
+ class="solution-card animate-slide-up" :class="{ 'animate-visible': isSolutionsVisible }"
|
|
|
+ :style="{ 'animation-delay': `${0.1 * (index % 6)}s` }">
|
|
|
<div class="card-image">
|
|
|
<img :src="solution.image" :alt="solution.title" />
|
|
|
</div>
|
|
@@ -30,13 +33,13 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 切换按钮 -->
|
|
|
- <div class="navigation-buttons">
|
|
|
- <button class="nav-btn prev-btn" :class="{ disabled: currentPage === 0 }" @click="prevPage"
|
|
|
- :disabled="currentPage === 0">
|
|
|
+ <div class="navigation-buttons animate-fade-up" :class="{ 'animate-visible': isSolutionsVisible }" style="animation-delay: 0.5s">
|
|
|
+ <button class="nav-btn prev-btn animate-bounce-in" :class="{ disabled: currentPage === 0, 'animate-visible': isSolutionsVisible }" @click="prevPage"
|
|
|
+ :disabled="currentPage === 0" style="animation-delay: 0.6s">
|
|
|
<img src="@assets/slide-arrow-left.png" alt="上一页" />
|
|
|
</button>
|
|
|
- <button class="nav-btn next-btn" :class="{ disabled: currentPage >= maxPage }" @click="nextPage"
|
|
|
- :disabled="currentPage >= maxPage">
|
|
|
+ <button class="nav-btn next-btn animate-bounce-in" :class="{ disabled: currentPage >= maxPage, 'animate-visible': isSolutionsVisible }" @click="nextPage"
|
|
|
+ :disabled="currentPage >= maxPage" style="animation-delay: 0.7s">
|
|
|
<img src="@assets/slide-arrow-right.png" alt="下一页" />
|
|
|
</button>
|
|
|
</div>
|
|
@@ -44,18 +47,20 @@
|
|
|
</section>
|
|
|
|
|
|
<!-- 更多案例 -->
|
|
|
- <section class="more-cases">
|
|
|
+ <section class="more-cases" ref="moreCasesSection">
|
|
|
<div class="container">
|
|
|
<!-- 标题区域 -->
|
|
|
- <div class="title-section">
|
|
|
+ <div class="title-section animate-fade-up" :class="{ 'animate-visible': isMoreCasesVisible }">
|
|
|
<h2 class="main-title">更多案例</h2>
|
|
|
<p class="sub-title">MORE CASES</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- 案例列表 -->
|
|
|
- <div class="cases-list">
|
|
|
- <div v-for="(caseItem, caseIndex) in casesList" :key="caseItem.id" class="case-item"
|
|
|
- :class="{ 'fifth-row': Math.floor(caseIndex / 2) === 4 }" @click="goToCaseDetail(caseItem)">
|
|
|
+ <div class="cases-list animate-fade-up" :class="{ 'animate-visible': isMoreCasesVisible }" style="animation-delay: 0.3s">
|
|
|
+ <div v-for="(caseItem, caseIndex) in casesList" :key="caseItem.id" class="case-item animate-slide-right"
|
|
|
+ :class="{ 'fifth-row': Math.floor(caseIndex / 2) === 4, 'animate-visible': isMoreCasesVisible }"
|
|
|
+ :style="{ 'animation-delay': `${0.05 * (caseIndex % 10)}s` }"
|
|
|
+ @click="goToCaseDetail(caseItem)">
|
|
|
<div class="case-content">
|
|
|
<h3 class="case-title">{{ caseItem.title }}</h3>
|
|
|
</div>
|
|
@@ -67,7 +72,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { ref, computed, onMounted } from 'vue'
|
|
|
+import { ref, computed, onMounted, nextTick } from 'vue'
|
|
|
import { getProjectClassifyList, getProjectList } from '@/api/modules/home'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import DefaultLayout from '@/layouts/DefaultLayout.vue'
|
|
@@ -84,6 +89,14 @@ export default {
|
|
|
const currentPage = ref(0)
|
|
|
const itemsPerPage = 6 // 每页显示6个(2行 × 3列)
|
|
|
|
|
|
+ // 动画状态管理
|
|
|
+ const isSolutionsVisible = ref(false)
|
|
|
+ const isMoreCasesVisible = ref(false)
|
|
|
+
|
|
|
+ // 元素引用
|
|
|
+ const solutionsSection = ref(null)
|
|
|
+ const moreCasesSection = ref(null)
|
|
|
+
|
|
|
// 解决方案数据
|
|
|
let solutions = ref([
|
|
|
{
|
|
@@ -238,8 +251,45 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 设置滚动动画观察器
|
|
|
+ const setupScrollAnimations = () => {
|
|
|
+ const observerOptions = {
|
|
|
+ threshold: 0.2,
|
|
|
+ rootMargin: '0px 0px -50px 0px'
|
|
|
+ }
|
|
|
+
|
|
|
+ const observer = new IntersectionObserver((entries) => {
|
|
|
+ entries.forEach(entry => {
|
|
|
+ if (entry.isIntersecting) {
|
|
|
+ const target = entry.target
|
|
|
+ if (target.classList.contains('smart-solutions')) {
|
|
|
+ isSolutionsVisible.value = true
|
|
|
+ } else if (target.classList.contains('more-cases')) {
|
|
|
+ isMoreCasesVisible.value = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }, observerOptions)
|
|
|
+
|
|
|
+ // 观察各个区域
|
|
|
+ nextTick(() => {
|
|
|
+ if (solutionsSection.value) observer.observe(solutionsSection.value)
|
|
|
+ if (moreCasesSection.value) observer.observe(moreCasesSection.value)
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
loadProductsData()
|
|
|
+
|
|
|
+ // 设置滚动动画
|
|
|
+ nextTick(() => {
|
|
|
+ setupScrollAnimations()
|
|
|
+ })
|
|
|
+
|
|
|
+ // 延迟显示第一个区域
|
|
|
+ setTimeout(() => {
|
|
|
+ isSolutionsVisible.value = true
|
|
|
+ }, 500)
|
|
|
})
|
|
|
|
|
|
return {
|
|
@@ -250,19 +300,132 @@ export default {
|
|
|
prevPage,
|
|
|
nextPage,
|
|
|
casesList,
|
|
|
- goToCaseDetail
|
|
|
+ goToCaseDetail,
|
|
|
+ // 动画状态
|
|
|
+ isSolutionsVisible,
|
|
|
+ isMoreCasesVisible,
|
|
|
+ // 元素引用
|
|
|
+ solutionsSection,
|
|
|
+ moreCasesSection
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+// 动画定义
|
|
|
+@keyframes fadeIn {
|
|
|
+ from { opacity: 0; }
|
|
|
+ to { opacity: 1; }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes fadeUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(40px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(30px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes slideRight {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(-30px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes bounceIn {
|
|
|
+ 0% {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.3);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: scale(1.05);
|
|
|
+ }
|
|
|
+ 70% {
|
|
|
+ transform: scale(0.9);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 动画类
|
|
|
+.animate-fade-in {
|
|
|
+ animation: fadeIn 1s ease-out;
|
|
|
+}
|
|
|
+
|
|
|
+.animate-fade-up {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(40px);
|
|
|
+ transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+
|
|
|
+ &.animate-visible {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.animate-slide-up {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(20px);
|
|
|
+ transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+
|
|
|
+ &.animate-visible {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.animate-slide-right {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateX(-30px);
|
|
|
+ transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+
|
|
|
+ &.animate-visible {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateX(0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.animate-bounce-in {
|
|
|
+ opacity: 0;
|
|
|
+ transform: scale(0.3);
|
|
|
+ transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
|
+
|
|
|
+ &.animate-visible {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// banner区域
|
|
|
.banner-section {
|
|
|
position: relative;
|
|
|
|
|
|
- img {
|
|
|
- width: 100%;
|
|
|
+ .banner-image {
|
|
|
+ img {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -322,31 +485,52 @@ export default {
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
|
- transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(45deg, rgba(24, 144, 255, 0.05), transparent);
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+ }
|
|
|
|
|
|
&:hover {
|
|
|
- transform: translateY(-5px);
|
|
|
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
|
|
+ transform: translateY(-8px) scale(1.02);
|
|
|
+ box-shadow: 0 12px 35px rgba(24, 144, 255, 0.15);
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.card-image {
|
|
|
height: 282px;
|
|
|
overflow: hidden;
|
|
|
+ position: relative;
|
|
|
|
|
|
img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: contain;
|
|
|
- transition: transform 0.3s ease;
|
|
|
+ transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
&:hover .card-image img {
|
|
|
- transform: scale(1.05);
|
|
|
+ transform: scale(1.08);
|
|
|
}
|
|
|
|
|
|
.card-content {
|
|
|
padding: 20px;
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
|
|
|
.card-title {
|
|
|
font-size: 24px;
|
|
@@ -354,6 +538,7 @@ export default {
|
|
|
color: #333;
|
|
|
margin-bottom: 10px;
|
|
|
text-align: center;
|
|
|
+ transition: color 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.card-description {
|
|
@@ -361,6 +546,17 @@ export default {
|
|
|
color: #666;
|
|
|
line-height: 1.6;
|
|
|
text-align: center;
|
|
|
+ transition: color 0.3s ease;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover .card-content {
|
|
|
+ .card-title {
|
|
|
+ color: #1890ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-description {
|
|
|
+ color: #333;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -381,11 +577,33 @@ export default {
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
|
- transition: all 0.3s ease;
|
|
|
+ transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: linear-gradient(45deg, rgba(24, 144, 255, 0.1), transparent);
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
+ }
|
|
|
|
|
|
&:hover:not(.disabled) {
|
|
|
- transform: translateY(-2px);
|
|
|
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
|
|
+ transform: translateY(-3px) scale(1.05);
|
|
|
+ box-shadow: 0 6px 20px rgba(24, 144, 255, 0.2);
|
|
|
+
|
|
|
+ &::before {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active:not(.disabled) {
|
|
|
+ transform: translateY(-1px) scale(1.02);
|
|
|
}
|
|
|
|
|
|
&.disabled {
|
|
@@ -396,6 +614,13 @@ export default {
|
|
|
img {
|
|
|
width: 20px;
|
|
|
height: 20px;
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover:not(.disabled) img {
|
|
|
+ transform: scale(1.1);
|
|
|
}
|
|
|
}
|
|
|
}
|