solution.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <section
  3. id="solution"
  4. ref="solutionRef"
  5. class="solution transition-all duration-1000 ease-out"
  6. :class="[isSolutionVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-50px']"
  7. >
  8. <div class="solution-header">
  9. <h2 class="solution-title">物业管理正面临这些挑战</h2>
  10. <p class="solution-desc">传统物业管理模式效率低下、成本高昂、服务体验差,急需数字化转型</p>
  11. </div>
  12. <div ref="solutionListRef" class="solution-list" @scroll="handleSolutionScroll">
  13. <div
  14. v-for="(solution, index) in solutions"
  15. :key="solution.id"
  16. class="solution-card"
  17. :class="[
  18. index === activeIndex
  19. ? 'active bg-[linear-gradient(0deg,#E5E9F5_0%,#EFF2FB_100%)]'
  20. : 'bg-[linear-gradient(180deg,#E8F6FD_50.48%,#A5D7FD_100%)] lt-sm:bg-[linear-gradient(0deg,#E5E9F5_0%,#EFF2FB_100%)]',
  21. ]"
  22. @mouseenter="activeIndex = index"
  23. @click="scrollToSolution(index)"
  24. >
  25. <div
  26. class="solution-card-hover"
  27. :class="[
  28. index === activeIndex
  29. ? 'opacity-100 translate-y-0 duration-500'
  30. : 'opacity-0 translate-y-20px pointer-events-none duration-300 mobile-show',
  31. ]"
  32. >
  33. <div
  34. class="solution-card-title pf-sc-semibold text-overflow"
  35. :class="[
  36. index === activeIndex
  37. ? 'opacity-100 translate-y-0 delay-100 duration-500'
  38. : 'opacity-0 translate-y-20px delay-0 duration-200',
  39. ]"
  40. >
  41. <span class="lt-sm:hidden">{{ solution.hoverTitle }}</span>
  42. <h3 class="inline sm:hidden">{{ solution.title }}解决方案</h3>
  43. </div>
  44. <div
  45. class="solution-card-desc pf-sc-regular"
  46. :class="[
  47. index === activeIndex
  48. ? 'opacity-100 translate-y-0 delay-150 duration-500'
  49. : 'opacity-0 translate-y-20px delay-0 duration-200',
  50. ]"
  51. >
  52. <p class="text-overflow">{{ solution.hoverDesc }}</p>
  53. </div>
  54. <div
  55. class="solution-card-items"
  56. :class="[
  57. index === activeIndex
  58. ? 'opacity-100 translate-y-0 delay-400 duration-700'
  59. : 'opacity-0 translate-y-20px delay-0 duration-300',
  60. ]"
  61. >
  62. <div v-for="item in solution.hoverItems" :key="item.title" class="solution-card-item">
  63. <div class="solution-card-item-header">
  64. <i class="i-custom-check-one solution-card-item-icon"></i>
  65. <div class="solution-card-item-content">
  66. <span class="text-#091221 pf-sc-semibold">
  67. {{ item.title }}
  68. </span>
  69. <span class="text-#091221/70 pf-sc-regular line-clamp-1">
  70. {{ item.itemDesc }}
  71. </span>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. <button
  77. class="solution-card-btn pf-sc-regular"
  78. :class="[
  79. index === activeIndex
  80. ? 'opacity-100 translate-y-0 delay-500 duration-700'
  81. : 'opacity-0 translate-y-20px delay-0 duration-200',
  82. ]"
  83. @click="openConsultation"
  84. >
  85. <span class="font-s-16px lt-sm:font-s-26px">立即咨询</span>
  86. <i class="i-custom-arrow-right wh-18px lt-sm:wh-28px"></i>
  87. </button>
  88. <img
  89. :src="solution.hoverImg"
  90. alt=""
  91. class="solution-card-img"
  92. :class="[
  93. index === activeIndex
  94. ? 'opacity-100 scale-100 delay-200 duration-500'
  95. : 'opacity-0 scale-50 delay-0 duration-300',
  96. ]"
  97. />
  98. </div>
  99. <div
  100. class="solution-card-normal"
  101. :class="[
  102. index === activeIndex
  103. ? 'opacity-0 pointer-events-none duration-300'
  104. : 'opacity-100 duration-500',
  105. ]"
  106. >
  107. <div
  108. class="solution-card-icon-wrapper"
  109. :class="[
  110. index === activeIndex
  111. ? 'scale-150 opacity-0 delay-0 duration-300'
  112. : 'scale-100 opacity-100 delay-0 duration-500',
  113. ]"
  114. >
  115. <img :src="solution.img" alt="" class="wh-35px" />
  116. </div>
  117. <div
  118. class="solution-card-normal-title pf-sc-semibold"
  119. :class="[
  120. index === activeIndex
  121. ? 'opacity-0 translate-y-10px delay-0 duration-200'
  122. : 'opacity-100 translate-y-0 delay-100 duration-500',
  123. ]"
  124. >
  125. {{ solution.title }}
  126. </div>
  127. <div
  128. class="solution-card-normal-desc pf-sc-regular"
  129. :class="[
  130. index === activeIndex
  131. ? 'opacity-0 translate-y-10px delay-0 duration-200'
  132. : 'opacity-100 translate-y-0 delay-150 duration-500',
  133. ]"
  134. >
  135. {{ solution.desc }}
  136. </div>
  137. <i
  138. class="i-custom-circle-right-up solution-card-normal-arrow"
  139. :class="[
  140. index === activeIndex
  141. ? 'opacity-0 scale-50 delay-0 duration-200'
  142. : 'opacity-100 scale-100 delay-100 duration-500',
  143. ]"
  144. ></i>
  145. </div>
  146. </div>
  147. </div>
  148. <div class="solution-dots lt-sm:flex hidden">
  149. <div
  150. v-for="(_, index) in solutions"
  151. :key="index"
  152. class="dot"
  153. :class="{ active: index === activeIndex }"
  154. @click="scrollToSolution(index)"
  155. ></div>
  156. </div>
  157. </section>
  158. </template>
  159. <script setup lang="ts">
  160. import { solutions } from '@/constants/common'
  161. const solutionRef = ref<HTMLElement | null>(null)
  162. const { isVisible: isSolutionVisible } = useScrollReveal(solutionRef)
  163. const { openConsultation } = useConsultation()
  164. const activeIndex = ref(0)
  165. const solutionListRef = ref<HTMLElement | null>(null)
  166. const handleSolutionScroll = (e: Event) => {
  167. const el = e.target as HTMLElement
  168. const scrollLeft = el.scrollLeft
  169. const width = el.offsetWidth
  170. const cardWidth = width * 0.85
  171. const gap = 16
  172. const newIndex = Math.round(scrollLeft / (cardWidth + gap))
  173. if (newIndex !== activeIndex.value) {
  174. activeIndex.value = newIndex
  175. }
  176. }
  177. const scrollToSolution = (index: number) => {
  178. if (window.innerWidth >= 640) return
  179. if (!solutionListRef.value) return
  180. activeIndex.value = index
  181. const width = solutionListRef.value.offsetWidth
  182. const cardWidth = width * 0.85
  183. const gap = 16
  184. solutionListRef.value.scrollTo({
  185. left: index * (cardWidth + gap),
  186. behavior: 'smooth',
  187. })
  188. }
  189. </script>
  190. <style scoped lang="scss">
  191. .solution {
  192. @extend %landing-container;
  193. @apply py-120px lt-sm:py-120px lt-sm:px-0;
  194. }
  195. .solution-header {
  196. @apply text-center mb-45px;
  197. }
  198. .solution-title {
  199. @apply font-s-36px font-semibold text-#000000 mb-4px lh-60px;
  200. @apply lt-sm:font-s-48px lt-sm:lh-60px;
  201. }
  202. .solution-desc {
  203. @apply font-s-16px text-#091221/70 lh-30px;
  204. @apply lt-sm:font-s-24px lt-sm:px-110px lt-sm:mt-16px lt-sm:lh-40px;
  205. }
  206. .solution-list {
  207. @apply flex h-365px w-full gap-24px;
  208. @apply lt-sm:h-auto lt-sm:gap-16px lt-sm:overflow-x-auto lt-sm:overflow-y-hidden lt-sm:px-24px;
  209. @apply lt-sm:[scroll-snap-type:x_mandatory] lt-sm:[scrollbar-width:none];
  210. &::-webkit-scrollbar {
  211. @apply lt-sm:hidden;
  212. }
  213. }
  214. .solution-card {
  215. position: relative;
  216. display: flex;
  217. height: 100%;
  218. cursor: pointer;
  219. overflow: hidden;
  220. border-radius: 24px;
  221. will-change: transform, opacity;
  222. transition:
  223. transform 600ms cubic-bezier(0.34, 1.56, 0.64, 1),
  224. opacity 600ms ease,
  225. flex 600ms ease-in-out,
  226. background-color 600ms ease-in-out;
  227. @media (min-width: 768px) {
  228. &.active {
  229. flex: 1 1 0%;
  230. }
  231. &:not(.active) {
  232. flex: 0 0 220px;
  233. }
  234. }
  235. @apply lt-sm:flex-[0_0_90%] lt-sm:min-h-750px lt-sm:w-630px;
  236. @apply lt-sm:[scroll-snap-align:center];
  237. &:not(.active) {
  238. @apply lt-sm:scale-96 lt-sm:opacity-50;
  239. }
  240. &.active {
  241. @apply lt-sm:scale-100 lt-sm:opacity-100;
  242. }
  243. &:first-child {
  244. @apply lt-sm:ml-36px;
  245. }
  246. &:last-child {
  247. @apply lt-sm:mr-36px;
  248. }
  249. }
  250. .solution-card-hover {
  251. @apply h-full w-full pt-40px px-48px absolute left-0 top-0 transition-all ease-in-out;
  252. @apply lt-sm:relative lt-sm:pt-40px lt-sm:px-36px lt-sm:pb-32px lt-sm:opacity-100 lt-sm:translate-y-0 lt-sm:pointer-events-auto;
  253. &.mobile-show {
  254. @apply lt-sm:opacity-100 lt-sm:translate-y-0 lt-sm:pointer-events-auto;
  255. }
  256. }
  257. .solution-card-title {
  258. @apply font-s-22px text-#000000 transition-all;
  259. @apply lt-sm:font-s-32px;
  260. }
  261. .solution-card-desc {
  262. @apply font-s-14px lh-24px mt-8px text-#091221/70 pf-sc-regular text-overflow transition-all;
  263. @apply lt-sm:font-s-24px lt-sm:mt-8px lt-sm:lh-normal;
  264. }
  265. .solution-card-items {
  266. @apply mt-24px flex flex-col gap-8px transition-all;
  267. @apply lt-sm:mt-48px;
  268. }
  269. .solution-card-item {
  270. @apply flex flex-col;
  271. }
  272. .solution-card-item-header {
  273. @apply flex gap-8px items-center font-s-14px;
  274. @apply lt-sm:font-s-24px lt-sm:gap-12px;
  275. }
  276. .solution-card-item-icon {
  277. @apply wh-18px;
  278. @apply lt-sm:wh-32px;
  279. }
  280. .solution-card-item-content {
  281. @apply flex items-center gap-8px;
  282. @apply lt-sm:flex-col lt-sm:items-start lt-sm:gap-8px;
  283. }
  284. .solution-card-btn {
  285. @apply mt-56px h-48px w-268px bg-#0F67F8 text-white pf-sc-regular rounded-8px px-16px flex items-center justify-between hover:opacity-80 transition-all;
  286. @apply lt-sm:w-270px lt-sm:h-95px lt-sm:rounded-16px lt-sm:mt-102px;
  287. }
  288. .solution-card-img {
  289. @apply wh-280px absolute bottom--37px right-0 transition-all duration-300 ease-in-out;
  290. @apply lt-sm:wh-264px lt-sm:bottom--26px;
  291. }
  292. .solution-card-normal {
  293. @apply h-full w-220px pt-40px px-24px absolute left-0 top-0 transition-all ease-in-out;
  294. @apply lt-sm:hidden;
  295. }
  296. .solution-card-icon-wrapper {
  297. @apply wh-40px ml-2px bg-white rounded-10px flex-center transition-all ease-in-out;
  298. }
  299. .solution-card-normal-title {
  300. @apply mt-18px font-s-18px text-#091221 pf-sc-semibold transition-all;
  301. }
  302. .solution-card-normal-desc {
  303. @apply mt-8px font-s-14px lh-24px text-#091221/60 pf-sc-regular w-172px transition-all;
  304. }
  305. .solution-card-normal-arrow {
  306. @apply wh-48px absolute bottom-24px left-24px transition-all;
  307. }
  308. .solution-dots {
  309. @apply lt-sm:mt-40px flex-center lt-sm:gap-16px;
  310. .dot {
  311. @apply lt-sm:w-60px lt-sm:h-10px bg-#E5E9F5 rounded-full transition-all cursor-pointer;
  312. &.active {
  313. @apply bg-#0F67F8;
  314. }
  315. }
  316. }
  317. </style>