solution.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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">
  11. 传统物业管理模式效率低下、成本高昂、服务体验差,急需数字化转型
  12. </p>
  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. <h3 class="inline">{{ solution.title }}解决方案</h3>
  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. <p>{{ solution.hoverDesc }}</p>
  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 solutionRef = ref<HTMLElement | null>(null)
  164. const { isVisible: isSolutionVisible } = useScrollReveal(solutionRef)
  165. const { openConsultation } = useConsultation()
  166. const activeIndex = ref(0)
  167. const solutionListRef = ref<HTMLElement | null>(null)
  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. </script>
  192. <style scoped lang="scss">
  193. .solution {
  194. @extend %landing-container;
  195. @apply py-120px lt-sm:py-120px lt-sm:px-0;
  196. }
  197. .solution-header {
  198. @apply text-center mb-45px;
  199. }
  200. .solution-title {
  201. @apply font-s-36px font-semibold text-#000000 mb-4px lh-60px;
  202. @apply lt-sm:font-s-48px lt-sm:lh-60px;
  203. }
  204. .solution-desc {
  205. @apply font-s-16px text-#091221/70 lh-30px;
  206. @apply lt-sm:font-s-24px lt-sm:px-110px lt-sm:mt-16px lt-sm:lh-40px;
  207. }
  208. .solution-list {
  209. @apply flex h-365px w-full gap-24px;
  210. @apply lt-sm:h-auto lt-sm:gap-16px lt-sm:overflow-x-auto lt-sm:overflow-y-hidden lt-sm:px-24px;
  211. @apply lt-sm:[scroll-snap-type:x_mandatory] lt-sm:[scrollbar-width:none];
  212. &::-webkit-scrollbar {
  213. @apply lt-sm:hidden;
  214. }
  215. }
  216. .solution-card {
  217. position: relative;
  218. display: flex;
  219. height: 100%;
  220. cursor: pointer;
  221. overflow: hidden;
  222. border-radius: 24px;
  223. will-change: transform, opacity;
  224. transition:
  225. transform 600ms cubic-bezier(0.34, 1.56, 0.64, 1),
  226. opacity 600ms ease,
  227. flex 600ms ease-in-out,
  228. background-color 600ms ease-in-out;
  229. @media (min-width: 768px) {
  230. &.active {
  231. flex: 1 1 0%;
  232. }
  233. &:not(.active) {
  234. flex: 0 0 220px;
  235. }
  236. }
  237. @apply lt-sm:flex-[0_0_90%] lt-sm:min-h-750px lt-sm:w-630px;
  238. @apply lt-sm:[scroll-snap-align:center];
  239. &:not(.active) {
  240. @apply lt-sm:scale-96 lt-sm:opacity-50;
  241. }
  242. &.active {
  243. @apply lt-sm:scale-100 lt-sm:opacity-100;
  244. }
  245. &:first-child {
  246. @apply lt-sm:ml-36px;
  247. }
  248. &:last-child {
  249. @apply lt-sm:mr-36px;
  250. }
  251. }
  252. .solution-card-hover {
  253. @apply h-full w-full pt-40px px-48px absolute left-0 top-0 transition-all ease-in-out;
  254. @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;
  255. &.mobile-show {
  256. @apply lt-sm:opacity-100 lt-sm:translate-y-0 lt-sm:pointer-events-auto;
  257. }
  258. }
  259. .solution-card-title {
  260. @apply font-s-22px text-#000000 transition-all;
  261. @apply lt-sm:font-s-32px;
  262. }
  263. .solution-card-desc {
  264. @apply font-s-14px lh-24px mt-8px text-#091221/70 pf-sc-regular text-overflow transition-all;
  265. @apply lt-sm:font-s-24px lt-sm:mt-8px lt-sm:lh-normal;
  266. }
  267. .solution-card-items {
  268. @apply mt-24px flex flex-col gap-8px transition-all;
  269. @apply lt-sm:mt-48px;
  270. }
  271. .solution-card-item {
  272. @apply flex flex-col;
  273. }
  274. .solution-card-item-header {
  275. @apply flex gap-8px items-center font-s-14px;
  276. @apply lt-sm:font-s-24px lt-sm:gap-12px;
  277. }
  278. .solution-card-item-icon {
  279. @apply wh-18px;
  280. @apply lt-sm:wh-32px;
  281. }
  282. .solution-card-item-content {
  283. @apply flex items-center gap-8px;
  284. @apply lt-sm:flex-col lt-sm:items-start lt-sm:gap-8px;
  285. }
  286. .solution-card-btn {
  287. @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;
  288. @apply lt-sm:w-270px lt-sm:h-95px lt-sm:rounded-16px lt-sm:mt-102px;
  289. }
  290. .solution-card-img {
  291. @apply wh-280px absolute bottom--37px right-0 transition-all duration-300 ease-in-out;
  292. @apply lt-sm:wh-264px lt-sm:bottom--26px;
  293. }
  294. .solution-card-normal {
  295. @apply h-full w-220px pt-40px px-24px absolute left-0 top-0 transition-all ease-in-out;
  296. @apply lt-sm:hidden;
  297. }
  298. .solution-card-icon-wrapper {
  299. @apply wh-40px ml-2px bg-white rounded-10px flex-center transition-all ease-in-out;
  300. }
  301. .solution-card-normal-title {
  302. @apply mt-18px font-s-18px text-#091221 pf-sc-semibold transition-all;
  303. }
  304. .solution-card-normal-desc {
  305. @apply mt-8px font-s-14px lh-24px text-#091221/60 pf-sc-regular w-172px transition-all;
  306. }
  307. .solution-card-normal-arrow {
  308. @apply wh-48px absolute bottom-24px left-24px transition-all;
  309. }
  310. .solution-dots {
  311. @apply lt-sm:mt-40px flex-center lt-sm:gap-16px;
  312. .dot {
  313. @apply lt-sm:w-60px lt-sm:h-10px bg-#E5E9F5 rounded-full transition-all cursor-pointer;
  314. &.active {
  315. @apply bg-#0F67F8;
  316. }
  317. }
  318. }
  319. </style>