Ability.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <template>
  2. <section
  3. id="ability"
  4. ref="abilityRef"
  5. class="ability transition-all duration-1000 ease-out"
  6. :class="[isAbilityVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-100px']"
  7. >
  8. <div class="ability-title pf-sc-semibold">一体化智慧解决方案</div>
  9. <div class="ability-desc pf-sc-regular">
  10. 绘家科技提供从软件到硬件的完整解决方案,覆盖物业管理的全场景需求
  11. </div>
  12. <div class="ability-content">
  13. <div class="ability-tabs">
  14. <div class="ability-tab-indicator" :style="abilityTabIndicatorStyle"></div>
  15. <div
  16. v-for="tab in abilityTabs"
  17. :key="tab.title"
  18. class="ability-tab-item"
  19. :class="[activeAbilityTab.id === tab.id ? 'text-white' : 'text-#091221 hover:text-#0F67F8']"
  20. @click="handleAbilityTabClick(tab)"
  21. >
  22. {{ tab.title }}
  23. </div>
  24. </div>
  25. <div class="tab-content">
  26. <Transition name="tab-fade" mode="out-in">
  27. <div :key="activeAbilityTab.id" class="ability-grid">
  28. <div
  29. v-for="ability in abilities[activeAbilityTab.id]"
  30. :key="ability.title"
  31. class="ability-card"
  32. >
  33. <i :class="ability.icon" class="ability-card-icon"></i>
  34. <div class="ability-card-title pf-sc-semibold">
  35. {{ ability.title }}
  36. </div>
  37. <div class="ability-card-desc pf-sc-regular">
  38. {{ ability.desc }}
  39. </div>
  40. <div class="ability-card-link">
  41. 查看详情
  42. <i
  43. class="i-custom-arrow-right-c color-#0F67F8 wh-18px lt-sm:wh-28px transition-transform group-hover:translate-x-4px"
  44. ></i>
  45. </div>
  46. </div>
  47. </div>
  48. </Transition>
  49. </div>
  50. </div>
  51. </section>
  52. </template>
  53. <script setup lang="ts">
  54. import { abilityTabs, abilities } from '@/constants/common'
  55. const abilityRef = ref<HTMLElement | null>(null)
  56. const isAbilityVisible = ref(false)
  57. const activeAbilityTab = ref<AbilityTab>(abilityTabs[0] as AbilityTab)
  58. const abilityTabIndicatorStyle = computed(() => {
  59. const activeTabIndex = abilityTabs.findIndex((tab) => tab.id === activeAbilityTab.value.id)
  60. return {
  61. transform: `translateX(${activeTabIndex * 100}%)`,
  62. }
  63. })
  64. const handleAbilityTabClick = (tab: AbilityTab) => {
  65. activeAbilityTab.value = tab
  66. }
  67. onMounted(() => {
  68. const observer = new IntersectionObserver(
  69. (entries) => {
  70. entries.forEach((entry) => {
  71. if (!entry.isIntersecting) return
  72. isAbilityVisible.value = true
  73. observer.unobserve(entry.target)
  74. })
  75. },
  76. { threshold: 0.1 }
  77. )
  78. if (abilityRef.value) {
  79. observer.observe(abilityRef.value)
  80. }
  81. })
  82. </script>
  83. <style scoped lang="scss">
  84. .ability {
  85. @apply py-120px;
  86. @extend %landing-container;
  87. @apply lt-sm:py-120px lt-sm:px-32px;
  88. background-size: 100% 100%;
  89. background-image: url('@/assets/images/bg-ability.png');
  90. @screen lt-sm {
  91. background-size: 100% 100%;
  92. background-image: url('@/assets/images/bg-ability-mobile.png');
  93. }
  94. }
  95. .ability-title {
  96. @apply font-s-36px text-#000000 pf-sc-semibold flex-center lh-60px;
  97. @apply lt-sm:font-s-48px lt-sm:lh-60px;
  98. }
  99. .ability-desc {
  100. @apply font-s-16px text-#091221/70 pf-sc-regular lh-30px flex-center mt-4px;
  101. @apply lt-sm:font-s-24px lt-sm:lh-40px lt-sm:text-center lt-sm:px-60px;
  102. }
  103. .ability-content {
  104. @apply mt-24px flex-center flex-col;
  105. @apply lt-sm:mt-40px;
  106. }
  107. .ability-tabs {
  108. @apply relative flex items-center rounded-14px border border-#ECEFF6 bg-#F6F8FD p-6px;
  109. @apply lt-sm:rounded-16px lt-sm:p-8px lt-sm:w-[calc(100%-40px)];
  110. }
  111. .ability-tab-indicator {
  112. @apply absolute w-136px h-[calc(100%-12px)] transition-all duration-300 ease-out rounded-8px bg-#0F67F8;
  113. @apply lt-sm:w-[calc((100%-16px)/3)] lt-sm:h-[calc(100%-16px)] lt-sm:rounded-12px;
  114. }
  115. .ability-tab-item {
  116. @apply relative z-1 cursor-pointer py-14px px-20px text-center font-s-16px transition-colors duration-300 pf-sc-regular;
  117. @apply lt-sm:flex-1 lt-sm:px-0 lt-sm:py-20px lt-sm:font-s-24px;
  118. }
  119. .tab-content {
  120. @apply pt-45px;
  121. @apply lt-sm:pt-40px lt-sm:w-full;
  122. }
  123. .ability-grid {
  124. @apply grid grid-cols-3 gap-24px;
  125. @apply lt-sm:grid-cols-2 lt-sm:gap-25px;
  126. }
  127. .ability-card {
  128. @apply group relative flex flex-col rounded-16px border border-#ECEFF6 bg-[linear-gradient(0deg,_#FFFFFF_0%,_rgba(255,255,255,0.6)_100%)] p-24px transition-all duration-300 hover:border-#0F67F8/30 hover:shadow-[0_8px_24px_rgba(15,103,248,0.08)];
  129. @apply lt-sm:p-24px lt-sm:rounded-16px;
  130. }
  131. .ability-card-icon {
  132. @apply wh-48px;
  133. @apply lt-sm:wh-78px;
  134. }
  135. .ability-card-title {
  136. @apply mt-16px font-s-18px font-semibold text-#091221 pf-sc-semibold;
  137. @apply lt-sm:font-s-32px;
  138. }
  139. .ability-card-desc {
  140. @apply mt-8px flex-1 font-s-14px text-#091221/70 pf-sc-regular;
  141. @apply lt-sm:font-s-24px lt-sm:mt-16px;
  142. }
  143. .ability-card-link {
  144. @apply mt-24px flex items-center gap-8px font-s-16px pf-sc-regular text-#0F67F8 transition-colors hover:text-#0A50FF cursor-pointer;
  145. @apply lt-sm:mt-48px lt-sm:font-s-28px;
  146. }
  147. .tab-fade-enter-active {
  148. transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  149. }
  150. .tab-fade-leave-active {
  151. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  152. }
  153. .tab-fade-enter-from {
  154. opacity: 0;
  155. transform: translateX(30px);
  156. }
  157. .tab-fade-leave-to {
  158. opacity: 0;
  159. transform: translateX(-30px);
  160. }
  161. .grid > div {
  162. animation: card-slide-up 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
  163. }
  164. .grid > div:nth-child(1) {
  165. animation-delay: 0.05s;
  166. }
  167. .grid > div:nth-child(2) {
  168. animation-delay: 0.1s;
  169. }
  170. .grid > div:nth-child(3) {
  171. animation-delay: 0.15s;
  172. }
  173. .grid > div:nth-child(4) {
  174. animation-delay: 0.2s;
  175. }
  176. .grid > div:nth-child(5) {
  177. animation-delay: 0.25s;
  178. }
  179. .grid > div:nth-child(6) {
  180. animation-delay: 0.3s;
  181. }
  182. .grid > div:nth-child(7) {
  183. animation-delay: 0.35s;
  184. }
  185. .grid > div:nth-child(8) {
  186. animation-delay: 0.4s;
  187. }
  188. @keyframes card-slide-up {
  189. from {
  190. opacity: 0;
  191. transform: translateY(20px);
  192. }
  193. to {
  194. opacity: 1;
  195. transform: translateY(0);
  196. }
  197. }
  198. </style>