solution.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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="solution.title + '解决方案示意图'"
  91. loading="lazy"
  92. class="solution-card-img"
  93. :class="[
  94. index === activeIndex
  95. ? 'opacity-100 scale-100 delay-200 duration-500'
  96. : 'opacity-0 scale-50 delay-0 duration-300',
  97. ]"
  98. />
  99. </div>
  100. <div
  101. class="solution-card-normal"
  102. :class="[
  103. index === activeIndex
  104. ? 'opacity-0 pointer-events-none duration-300'
  105. : 'opacity-100 duration-500',
  106. ]"
  107. >
  108. <div
  109. class="solution-card-icon-wrapper"
  110. :class="[
  111. index === activeIndex
  112. ? 'scale-150 opacity-0 delay-0 duration-300'
  113. : 'scale-100 opacity-100 delay-0 duration-500',
  114. ]"
  115. >
  116. <img
  117. :src="solution.img"
  118. :alt="solution.title + '图标'"
  119. loading="lazy"
  120. class="wh-35px"
  121. />
  122. </div>
  123. <div
  124. class="solution-card-normal-title pf-sc-semibold"
  125. :class="[
  126. index === activeIndex
  127. ? 'opacity-0 translate-y-10px delay-0 duration-200'
  128. : 'opacity-100 translate-y-0 delay-100 duration-500',
  129. ]"
  130. >
  131. {{ solution.title }}
  132. </div>
  133. <div
  134. class="solution-card-normal-desc pf-sc-regular"
  135. :class="[
  136. index === activeIndex
  137. ? 'opacity-0 translate-y-10px delay-0 duration-200'
  138. : 'opacity-100 translate-y-0 delay-150 duration-500',
  139. ]"
  140. >
  141. {{ solution.desc }}
  142. </div>
  143. <i
  144. class="i-custom-circle-right-up solution-card-normal-arrow"
  145. :class="[
  146. index === activeIndex
  147. ? 'opacity-0 scale-50 delay-0 duration-200'
  148. : 'opacity-100 scale-100 delay-100 duration-500',
  149. ]"
  150. ></i>
  151. </div>
  152. </div>
  153. </div>
  154. <div class="solution-dots lt-sm:flex hidden">
  155. <div
  156. v-for="(_, index) in solutions"
  157. :key="index"
  158. class="dot"
  159. :class="{ active: index === activeIndex }"
  160. @click="scrollToSolution(index)"
  161. ></div>
  162. </div>
  163. </section>
  164. </template>
  165. <script setup lang="ts">
  166. import { solutions } from '@/constants/common'
  167. const solutionRef = ref<HTMLElement | null>(null)
  168. const { isVisible: isSolutionVisible } = useScrollReveal(solutionRef)
  169. const { openConsultation } = useConsultation()
  170. const activeIndex = ref(0)
  171. const solutionListRef = ref<HTMLElement | null>(null)
  172. const handleSolutionScroll = (e: Event) => {
  173. const el = e.target as HTMLElement
  174. const scrollLeft = el.scrollLeft
  175. const width = el.offsetWidth
  176. const cardWidth = width * 0.85
  177. const gap = 16
  178. const newIndex = Math.round(scrollLeft / (cardWidth + gap))
  179. if (newIndex !== activeIndex.value) {
  180. activeIndex.value = newIndex
  181. }
  182. }
  183. const scrollToSolution = (index: number) => {
  184. if (window.innerWidth >= 640) return
  185. if (!solutionListRef.value) return
  186. activeIndex.value = index
  187. const width = solutionListRef.value.offsetWidth
  188. const cardWidth = width * 0.85
  189. const gap = 16
  190. solutionListRef.value.scrollTo({
  191. left: index * (cardWidth + gap),
  192. behavior: 'smooth',
  193. })
  194. }
  195. </script>
  196. <style scoped lang="scss">
  197. .solution {
  198. @extend %landing-container;
  199. @apply py-120px lt-sm:py-120px lt-sm:px-0;
  200. }
  201. .solution-header {
  202. @apply text-center mb-45px;
  203. }
  204. .solution-title {
  205. @apply font-s-36px font-semibold text-#000000 mb-4px lh-60px;
  206. @apply lt-sm:font-s-48px lt-sm:lh-60px;
  207. }
  208. .solution-desc {
  209. @apply font-s-16px text-#091221/70 lh-30px;
  210. @apply lt-sm:font-s-24px lt-sm:px-110px lt-sm:mt-16px lt-sm:lh-40px;
  211. }
  212. .solution-list {
  213. @apply flex h-365px w-full gap-24px;
  214. @apply lt-sm:h-auto lt-sm:gap-16px lt-sm:overflow-x-auto lt-sm:overflow-y-hidden lt-sm:px-24px;
  215. @apply lt-sm:[scroll-snap-type:x_mandatory] lt-sm:[scrollbar-width:none];
  216. &::-webkit-scrollbar {
  217. @apply lt-sm:hidden;
  218. }
  219. }
  220. .solution-card {
  221. position: relative;
  222. display: flex;
  223. height: 100%;
  224. cursor: pointer;
  225. overflow: hidden;
  226. border-radius: 24px;
  227. will-change: transform, opacity;
  228. transition:
  229. transform 600ms cubic-bezier(0.34, 1.56, 0.64, 1),
  230. opacity 600ms ease,
  231. flex 600ms ease-in-out,
  232. background-color 600ms ease-in-out;
  233. @media (min-width: 768px) {
  234. &.active {
  235. flex: 1 1 0%;
  236. }
  237. &:not(.active) {
  238. flex: 0 0 220px;
  239. }
  240. }
  241. @apply lt-sm:flex-[0_0_90%] lt-sm:min-h-750px lt-sm:w-630px;
  242. @apply lt-sm:[scroll-snap-align:center];
  243. &:not(.active) {
  244. @apply lt-sm:scale-96 lt-sm:opacity-50;
  245. }
  246. &.active {
  247. @apply lt-sm:scale-100 lt-sm:opacity-100;
  248. }
  249. &:first-child {
  250. @apply lt-sm:ml-36px;
  251. }
  252. &:last-child {
  253. @apply lt-sm:mr-36px;
  254. }
  255. }
  256. .solution-card-hover {
  257. @apply h-full w-full pt-40px px-48px absolute left-0 top-0 transition-all ease-in-out;
  258. @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;
  259. &.mobile-show {
  260. @apply lt-sm:opacity-100 lt-sm:translate-y-0 lt-sm:pointer-events-auto;
  261. }
  262. }
  263. .solution-card-title {
  264. @apply font-s-22px text-#000000 transition-all;
  265. @apply lt-sm:font-s-32px;
  266. }
  267. .solution-card-desc {
  268. @apply font-s-14px lh-24px mt-8px text-#091221/70 pf-sc-regular text-overflow transition-all;
  269. @apply lt-sm:font-s-24px lt-sm:mt-8px lt-sm:lh-normal;
  270. }
  271. .solution-card-items {
  272. @apply mt-24px flex flex-col gap-8px transition-all;
  273. @apply lt-sm:mt-48px;
  274. }
  275. .solution-card-item {
  276. @apply flex flex-col;
  277. }
  278. .solution-card-item-header {
  279. @apply flex gap-8px items-center font-s-14px;
  280. @apply lt-sm:font-s-24px lt-sm:gap-12px;
  281. }
  282. .solution-card-item-icon {
  283. @apply wh-18px;
  284. @apply lt-sm:wh-32px;
  285. }
  286. .solution-card-item-content {
  287. @apply flex items-center gap-8px;
  288. @apply lt-sm:flex-col lt-sm:items-start lt-sm:gap-8px;
  289. }
  290. .solution-card-btn {
  291. @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;
  292. @apply lt-sm:w-270px lt-sm:h-95px lt-sm:rounded-16px lt-sm:mt-102px;
  293. }
  294. .solution-card-img {
  295. @apply wh-280px absolute bottom--37px right-0 transition-all duration-300 ease-in-out;
  296. @apply lt-sm:wh-264px lt-sm:bottom--26px;
  297. }
  298. .solution-card-normal {
  299. @apply h-full w-220px pt-40px px-24px absolute left-0 top-0 transition-all ease-in-out;
  300. @apply lt-sm:hidden;
  301. }
  302. .solution-card-icon-wrapper {
  303. @apply wh-40px ml-2px bg-white rounded-10px flex-center transition-all ease-in-out;
  304. }
  305. .solution-card-normal-title {
  306. @apply mt-18px font-s-18px text-#091221 pf-sc-semibold transition-all;
  307. }
  308. .solution-card-normal-desc {
  309. @apply mt-8px font-s-14px lh-24px text-#091221/60 pf-sc-regular w-172px transition-all;
  310. }
  311. .solution-card-normal-arrow {
  312. @apply wh-48px absolute bottom-24px left-24px transition-all;
  313. }
  314. .solution-dots {
  315. @apply lt-sm:mt-40px flex-center lt-sm:gap-16px;
  316. .dot {
  317. @apply lt-sm:w-60px lt-sm:h-10px bg-#E5E9F5 rounded-full transition-all cursor-pointer;
  318. &.active {
  319. @apply bg-#0F67F8;
  320. }
  321. }
  322. }
  323. </style>