Ability.vue 6.3 KB

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