Cases.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. <template>
  2. <section
  3. id="cases"
  4. ref="casesRef"
  5. class="cases transition-all duration-1000 ease-out"
  6. :class="[isCasesVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-100px']"
  7. >
  8. <div class="cases-header">
  9. <div class="cases-title pf-sc-semibold">知识产权和案例</div>
  10. <div class="cases-desc pf-sc-regular">
  11. 绘家科技凭借强大的技术实力和丰富的实践经验,为客户提供卓越的智慧社区解决方案
  12. </div>
  13. </div>
  14. <div ref="casesListRef" class="cases-list" @scroll="handleCaseScroll">
  15. <div
  16. v-for="(caseItem, index) in cases"
  17. :key="caseItem.title"
  18. class="cases-card"
  19. :class="{ active: index === activeCaseIndex }"
  20. >
  21. <div class="flex flex-col h-full">
  22. <img :src="caseItem.img" class="cases-card-img" alt="" />
  23. <div class="cases-card-title pf-sc-semibold">
  24. {{ caseItem.title }}
  25. </div>
  26. <div class="cases-card-desc pf-sc-regular">
  27. {{ caseItem.desc }}
  28. </div>
  29. <div class="cases-card-points">
  30. <div v-for="point in caseItem.points" :key="point.title" class="cases-card-point">
  31. <i class="i-custom-check-one cases-card-point-icon"></i>
  32. <div class="cases-card-point-title pf-sc-semibold">
  33. {{ point.title }}
  34. </div>
  35. <div class="cases-card-point-desc pf-sc-regular">
  36. {{ point.itemDesc }}
  37. </div>
  38. </div>
  39. </div>
  40. <button class="cases-btn">
  41. {{ caseItem.btnText }}
  42. <i class="i-custom-arrow-right-c cases-card-btn-icon"></i>
  43. </button>
  44. </div>
  45. </div>
  46. </div>
  47. <div class="cases-dots lt-sm:flex hidden">
  48. <div
  49. v-for="(_, index) in cases"
  50. :key="index"
  51. class="dot"
  52. :class="{ active: index === activeCaseIndex }"
  53. @click="scrollToCase(index)"
  54. ></div>
  55. </div>
  56. </section>
  57. </template>
  58. <script setup lang="ts">
  59. import { cases } from '@/constants/common'
  60. const casesRef = ref<HTMLElement | null>(null)
  61. const isCasesVisible = ref(false)
  62. const activeCaseIndex = ref(0)
  63. const casesListRef = ref<HTMLElement | null>(null)
  64. const handleCaseScroll = (e: Event) => {
  65. const el = e.target as HTMLElement
  66. const scrollLeft = el.scrollLeft
  67. const width = el.offsetWidth
  68. const cardWidth = width * 0.9
  69. const gap = 16
  70. const newIndex = Math.round(scrollLeft / (cardWidth + gap))
  71. if (newIndex !== activeCaseIndex.value) {
  72. activeCaseIndex.value = newIndex
  73. }
  74. }
  75. const scrollToCase = (index: number) => {
  76. if (!casesListRef.value) return
  77. const width = casesListRef.value.offsetWidth
  78. const cardWidth = width * 0.9
  79. const gap = 16
  80. casesListRef.value.scrollTo({
  81. left: index * (cardWidth + gap),
  82. behavior: 'smooth',
  83. })
  84. }
  85. onMounted(() => {
  86. const observer = new IntersectionObserver(
  87. (entries) => {
  88. entries.forEach((entry) => {
  89. if (!entry.isIntersecting) return
  90. isCasesVisible.value = true
  91. observer.unobserve(entry.target)
  92. })
  93. },
  94. { threshold: 0.1 }
  95. )
  96. if (casesRef.value) {
  97. observer.observe(casesRef.value)
  98. }
  99. })
  100. </script>
  101. <style scoped lang="scss">
  102. .cases {
  103. @apply pt-120px pb-46px lt-sm:pt-120px lt-sm:pb-60px lt-sm:px-32px;
  104. @extend %landing-container;
  105. }
  106. .cases-header {
  107. @apply text-center mb-60px lt-sm:mb-60px lt-sm:px-32px;
  108. }
  109. .cases-title {
  110. @apply font-s-36px font-semibold text-#000000 mb-4px lh-60px;
  111. @apply lt-sm:font-s-48px lt-sm:mb-16px;
  112. }
  113. .cases-desc {
  114. @apply font-s-16px text-#091221/70 lh-30px;
  115. @apply lt-sm:font-s-24px lt-sm:lh-40px lt-sm:px-70px;
  116. }
  117. .cases-list {
  118. @apply flex gap-24px;
  119. @apply lt-sm:gap-36px lt-sm:overflow-x-auto lt-sm:overflow-y-hidden lt-sm:px-0 lt-sm:pb-10px;
  120. @apply lt-sm:[scroll-snap-type:x_mandatory] lt-sm:[scrollbar-width:none];
  121. &::-webkit-scrollbar {
  122. @apply lt-sm:hidden;
  123. }
  124. }
  125. .cases-card {
  126. @apply group flex-1 overflow-hidden rounded-16px bg-#F6F8FD p-32px transition-all duration-300;
  127. @apply lt-sm:flex-none lt-sm:w-[calc(100vw-84px)] lt-sm:min-w-[calc(100vw-84px)] lt-sm:p-32px lt-sm:rounded-16px;
  128. @apply lt-sm:[scroll-snap-align:center];
  129. &:first-child {
  130. @apply lt-sm:ml-42px;
  131. }
  132. &:last-child {
  133. @apply lt-sm:mr-42px;
  134. }
  135. }
  136. .cases-card-img {
  137. @apply w-full h-218px rounded-12px;
  138. @apply lt-sm:h-320px lt-sm:rounded-12px;
  139. }
  140. .cases-card-title {
  141. @apply font-s-18px font-semibold text-#091221 mt-24px;
  142. @apply lt-sm:font-s-32px lt-sm:mt-32px;
  143. }
  144. .cases-card-desc {
  145. @apply mt-8px font-s-14px lh-24px text-#091221/70;
  146. @apply lt-sm:font-s-24px lt-sm:lh-36px lt-sm:mt-16px;
  147. }
  148. .cases-card-points {
  149. @apply mt-24px flex flex-col gap-8px flex-1;
  150. @apply lt-sm:mt-32px lt-sm:gap-16px;
  151. }
  152. .cases-card-point {
  153. @apply flex items-center gap-8px;
  154. @apply lt-sm:gap-12px;
  155. }
  156. .cases-card-point-icon {
  157. @apply wh-20px mt-2px;
  158. @apply lt-sm:wh-32px;
  159. }
  160. .cases-card-point-title {
  161. @apply font-s-14px font-semibold text-#091221;
  162. @apply lt-sm:font-s-24px;
  163. }
  164. .cases-card-point-desc {
  165. @apply font-s-14px text-#091221/70;
  166. @apply lt-sm:font-s-24px;
  167. }
  168. .cases-btn {
  169. @apply mt-56px flex items-center justify-between gap-8px rounded-8px bg-white px-16px py-15px font-s-16px text-#0F67F8 transition-all hover:bg-#0F67F8 hover:text-white;
  170. @apply lt-sm:mt-48px lt-sm:h-96px lt-sm:rounded-8px lt-sm:px-32px lt-sm:font-s-28px lt-sm:bg-#0F67F8 lt-sm:text-white;
  171. }
  172. .cases-card-btn-icon {
  173. @apply wh-18px;
  174. @apply lt-sm:wh-32px lt-sm:text-white;
  175. }
  176. .cases-dots {
  177. @apply lt-sm:mt-40px lt-sm:flex-center lt-sm:gap-16px;
  178. .dot {
  179. @apply lt-sm:w-60px lt-sm:h-10px bg-#E5E9F5 rounded-full transition-all cursor-pointer;
  180. &.active {
  181. @apply bg-#0F67F8;
  182. }
  183. }
  184. }
  185. </style>