SolutionSection.vue 11 KB

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