index.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. <!--
  2. * @Author: LiZhiWei
  3. * @LastEditors: LiZhiWei
  4. * @LastEditTime: 2026-01-16 10:47:11
  5. * @Description:
  6. -->
  7. <template>
  8. <div class="landing">
  9. <section class="hero">
  10. <div class="hero-content flex items-center pb-48px pt-200px">
  11. <div class="text-left">
  12. <div class="font-s-48px font-semibold text-#000000">智慧社区整体解决方案</div>
  13. <p class="font-s-18px text-#091221/70">
  14. 绘家科技助力物业管理数字化转型,提升服务品质与业主满意度。
  15. </p>
  16. <div class="mt-35px flex items-center gap-48px">
  17. <div class="metric">
  18. <div class="metric-value">300+</div>
  19. <div class="metric-label">设备接入</div>
  20. </div>
  21. <div class="metric">
  22. <div class="metric-value">800+</div>
  23. <div class="metric-label">业务流程</div>
  24. </div>
  25. <div class="metric">
  26. <div class="metric-value">60w+</div>
  27. <div class="metric-label">服务人群</div>
  28. </div>
  29. </div>
  30. <div class="mt-35px flex flex-wrap items-center gap-16px">
  31. <button
  32. class="btn-primary w-127px h-56px! rounded-8px text-white font-s-18px pf-sc-semibold"
  33. >
  34. 查看方案
  35. </button>
  36. <button
  37. class="btn-outline w-127px h-56px! border-[1px] border-#0F67F8! font-s-18px font-semibold text-#0F67F8! bg-white rounded-8px hover:opacity-80"
  38. @click="openConsultation"
  39. >
  40. 立即咨询
  41. </button>
  42. </div>
  43. </div>
  44. </div>
  45. </section>
  46. <section
  47. id="solution"
  48. ref="solutionRef"
  49. class="solution h-744px pt-120px transition-all duration-1000 ease-out"
  50. :class="[isSolutionVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-100px']"
  51. >
  52. <div class="text-center mb-45px">
  53. <div class="font-s-36px font-semibold text-#000000 mb-4px">物业管理正面临这些挑战</div>
  54. <div class="font-s-16px text-#091221/70">
  55. 传统物业管理模式效率低下、成本高昂、服务体验差,急需数字化转型
  56. </div>
  57. </div>
  58. <div class="flex h-365px w-full gap-24px">
  59. <div
  60. v-for="(solution, index) in solutions"
  61. :key="solution.id"
  62. class="relative flex h-full cursor-pointer overflow-hidden rounded-16px transition-[flex,background-color] duration-500 ease-in-out"
  63. :class="[
  64. index === activeIndex
  65. ? 'flex-[1_1_0%] bg-[linear-gradient(0deg,#E5E9F5_0%,#EFF2FB_100%)]'
  66. : 'flex-[0_0_220px] bg-[linear-gradient(180deg,#E8F6FD_50.48%,#A5D7FD_100%)]',
  67. ]"
  68. @mouseenter="activeIndex = index"
  69. >
  70. <!-- Hover State Content -->
  71. <div
  72. class="h-full w-700px pt-40px px-48px absolute left-0 top-0 transition-all ease-in-out"
  73. :class="[
  74. index === activeIndex
  75. ? 'opacity-100 translate-y-0 duration-500'
  76. : 'opacity-0 translate-y-20px pointer-events-none duration-300',
  77. ]"
  78. >
  79. <div
  80. class="font-s-22px text-#000000 pf-sc-semibold transition-all"
  81. :class="[
  82. index === activeIndex
  83. ? 'opacity-100 translate-y-0 delay-100 duration-500'
  84. : 'opacity-0 translate-y-20px delay-0 duration-200',
  85. ]"
  86. >
  87. {{ solution.hoverTitle }}
  88. </div>
  89. <div
  90. class="font-s-14px lh-24px mt-8px text-#091221/70 pf-sc-regular whitespace-nowrap transition-all"
  91. :class="[
  92. index === activeIndex
  93. ? 'opacity-100 translate-y-0 delay-150 duration-500'
  94. : 'opacity-0 translate-y-20px delay-0 duration-200',
  95. ]"
  96. >
  97. {{ solution.hoverDesc }}
  98. </div>
  99. <div
  100. class="mt-24px flex flex-col gap-8px transition-all"
  101. :class="[
  102. index === activeIndex
  103. ? 'opacity-100 translate-y-0 delay-200 duration-500'
  104. : 'opacity-0 translate-y-20px delay-0 duration-200',
  105. ]"
  106. >
  107. <div v-for="item in solution.hoverItems" :key="item.title" class="flex flex-col">
  108. <div class="flex gap-8px items-center font-s-14px">
  109. <i class="i-custom-check-one wh-18px"></i>
  110. <span class="text-#091221 pf-sc-semibold">
  111. {{ item.title }}
  112. </span>
  113. <span class="text-#091221/70 pf-sc-regular">
  114. {{ item.itemDesc }}
  115. </span>
  116. </div>
  117. </div>
  118. </div>
  119. <button
  120. class="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"
  121. :class="[
  122. index === activeIndex
  123. ? 'opacity-100 translate-y-0 delay-300 duration-500'
  124. : 'opacity-0 translate-y-20px delay-0 duration-200',
  125. ]"
  126. @click="openConsultation"
  127. >
  128. <span class="font-s-16px">立即咨询</span>
  129. <i class="i-custom-arrow-right wh-18px"></i>
  130. </button>
  131. <img
  132. :src="solution.hoverImg"
  133. alt=""
  134. class="wh-280px absolute bottom--37px right-0 transition-all ease-out"
  135. :class="[
  136. index === activeIndex
  137. ? 'opacity-100 scale-100 delay-100 duration-700'
  138. : 'opacity-0 scale-50 delay-0 duration-300',
  139. ]"
  140. />
  141. </div>
  142. <!-- Normal State Content -->
  143. <div
  144. class="h-full w-220px pt-40px px-24px absolute left-0 top-0 transition-all ease-in-out"
  145. :class="[
  146. index === activeIndex
  147. ? 'opacity-0 pointer-events-none duration-300'
  148. : 'opacity-100 duration-500',
  149. ]"
  150. >
  151. <div
  152. class="wh-40px ml-2px bg-white rounded-10px flex-center transition-all ease-in-out"
  153. :class="[
  154. index === activeIndex
  155. ? 'scale-150 opacity-0 delay-0 duration-300'
  156. : 'scale-100 opacity-100 delay-0 duration-500',
  157. ]"
  158. >
  159. <img :src="solution.img" alt="" class="wh-35px" />
  160. </div>
  161. <div
  162. class="mt-18px font-s-18px text-#091221 pf-sc-semibold transition-all"
  163. :class="[
  164. index === activeIndex
  165. ? 'opacity-0 translate-y-10px delay-0 duration-200'
  166. : 'opacity-100 translate-y-0 delay-100 duration-500',
  167. ]"
  168. >
  169. {{ solution.title }}
  170. </div>
  171. <div
  172. class="mt-8px font-s-14px lh-24px text-#091221/60 pf-sc-regular w-172px transition-all"
  173. :class="[
  174. index === activeIndex
  175. ? 'opacity-0 translate-y-10px delay-0 duration-200'
  176. : 'opacity-100 translate-y-0 delay-150 duration-500',
  177. ]"
  178. >
  179. {{ solution.desc }}
  180. </div>
  181. <i
  182. class="i-custom-circle-right-up wh-48px absolute bottom-24px left-24px transition-all"
  183. :class="[
  184. index === activeIndex
  185. ? 'opacity-0 scale-50 delay-0 duration-200'
  186. : 'opacity-100 scale-100 delay-100 duration-500',
  187. ]"
  188. ></i>
  189. </div>
  190. </div>
  191. </div>
  192. </section>
  193. <section
  194. id="ability"
  195. ref="abilityRef"
  196. class="ability transition-all duration-1000 ease-out"
  197. :class="[isAbilityVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-100px']"
  198. >
  199. <div class="font-s-36px text-#000000 pf-sc-semibold flex-center lh-60px">
  200. 一体化智慧解决方案
  201. </div>
  202. <div class="font-s-16px text-#091221/70 pf-sc-regular lh-30px flex-center mt-4px">
  203. 我们的一体化智慧解决方案整合了智能收费、智能运维、智能监控等多个功能模块,实现了物业管理的全流程数字化管理。
  204. </div>
  205. <div class="mt-24px flex-center flex-col">
  206. <div class="relative flex items-center rounded-14px border border-#ECEFF6 bg-#F6F8FD p-6px">
  207. <!-- 滑块背景 -->
  208. <div
  209. class="absolute w-136px h-[calc(100%-12px)] transition-all duration-300 ease-out rounded-8px bg-#0F67F8"
  210. :style="abilityTabIndicatorStyle"
  211. ></div>
  212. <div
  213. v-for="tab in abilityTabs"
  214. :key="tab.title"
  215. class="relative z-1 cursor-pointer py-14px px-20px text-center font-s-16px transition-colors duration-300 pf-sc-regular"
  216. :class="[
  217. activeAbilityTab.id === tab.id ? 'text-white' : 'text-#091221 hover:text-#0F67F8',
  218. ]"
  219. @click="handleAbilityTabClick(tab)"
  220. >
  221. {{ tab.title }}
  222. </div>
  223. </div>
  224. <div class="tab-content pt-45px">
  225. <Transition name="tab-fade" mode="out-in">
  226. <div :key="activeAbilityTab.id" class="grid grid-cols-3 gap-24px">
  227. <div
  228. v-for="ability in abilities[activeAbilityTab.id]"
  229. :key="ability.title"
  230. class="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)]"
  231. >
  232. <i :class="ability.icon" class="wh-48px"></i>
  233. <div class="mt-16px font-s-18px font-semibold text-#091221 pf-sc-semibold">
  234. {{ ability.title }}
  235. </div>
  236. <div class="mt-8px flex-1 font-s-14px text-#091221/70 pf-sc-regular">
  237. {{ ability.desc }}
  238. </div>
  239. <div
  240. class="mt-24px flex items-center gap-8px font-s-16px pf-sc-regular text-#0F67F8 transition-colors hover:text-#0A50FF cursor-pointer"
  241. >
  242. 查看详情
  243. <i
  244. class="i-custom-arrow-right-c color-#0F67F8 wh-18px transition-transform group-hover:translate-x-4px"
  245. ></i>
  246. </div>
  247. </div>
  248. </div>
  249. </Transition>
  250. </div>
  251. </div>
  252. </section>
  253. <section
  254. id="cases"
  255. ref="casesRef"
  256. class="cases pt-120px pb-46px transition-all duration-1000 ease-out"
  257. :class="[isCasesVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-100px']"
  258. >
  259. <div class="text-center mb-60px">
  260. <div class="font-s-36px font-semibold text-#000000 pf-sc-semibold mb-12px">
  261. 知识产权和案例
  262. </div>
  263. <div class="font-s-16px text-#091221/70 pf-sc-regular">
  264. 绘家科技凭借强大的技术实力和丰富的实践经验,为客户提供卓越的智慧社区解决方案
  265. </div>
  266. </div>
  267. <div class="flex gap-24px">
  268. <div
  269. v-for="caseItem in cases"
  270. :key="caseItem.title"
  271. class="group flex-1 overflow-hidden rounded-16px bg-#F6F8FD p-32px transition-all duration-300"
  272. >
  273. <div class="flex flex-col">
  274. <img :src="caseItem.img" class="w-full h-218px rounded-12px" alt="" />
  275. <div class="font-s-18px font-semibold text-#091221 pf-sc-semibold mt-24px">
  276. {{ caseItem.title }}
  277. </div>
  278. <div class="mt-8px font-s-14px lh-24px text-#091221/70 pf-sc-regular">
  279. {{ caseItem.desc }}
  280. </div>
  281. <div class="mt-24px flex flex-col gap-8px">
  282. <div
  283. v-for="point in caseItem.points"
  284. :key="point.title"
  285. class="flex items-center gap-8px"
  286. >
  287. <i class="i-custom-check-one wh-20px mt-2px"></i>
  288. <div class="font-s-14px font-semibold text-#091221 pf-sc-semibold">
  289. {{ point.title }}
  290. </div>
  291. <div class="font-s-14px text-#091221/70 pf-sc-regular">
  292. {{ point.itemDesc }}
  293. </div>
  294. </div>
  295. </div>
  296. <button
  297. class="mt-56px flex items-center justify-between gap-8px rounded-8px bg-white px-16px py-15px font-s-16px text-#0F67F8 transition-all hover:bg-#0F67F8 hover:text-white"
  298. >
  299. {{ caseItem.btnText }}
  300. <i class="i-custom-arrow-right-c wh-18px"></i>
  301. </button>
  302. </div>
  303. </div>
  304. </div>
  305. </section>
  306. <section
  307. id="partnership"
  308. ref="partnershipRef"
  309. class="partnership py-60px overflow-hidden transition-all duration-1000 ease-out"
  310. :class="[isPartnershipVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-100px']"
  311. >
  312. <div class="flex-center gap-32px mb-40px">
  313. <div class="h-2px w-320px bg-[linear-gradient(90deg,transparent_0%,#E5E9F5_100%)]"></div>
  314. <div class="text-center font-s-18px lh-21px text-#9CA0A7 pf-sc-regular whitespace-nowrap">
  315. 与合作伙伴携手,深入产业共创价值
  316. </div>
  317. <div class="h-2px w-320px bg-[linear-gradient(90deg,#E5E9F5_0%,transparent_100%)]"></div>
  318. </div>
  319. <div class="relative flex flex-col gap-0px -mt-24px">
  320. <!-- 第一行 -->
  321. <InspiraMarquee class="[--duration:80s] [--gap:24px] mb--31px">
  322. <div
  323. v-for="partner in partnersRow1"
  324. :key="partner"
  325. class="flex-center h-80px w-200px bg-white rounded-8px shadow-[0_2px_8px_2px_rgba(0,0,0,0.06)] px-24px"
  326. >
  327. <img :src="partner" alt="" class="wh-full object-contain" />
  328. </div>
  329. </InspiraMarquee>
  330. <!-- 第二行 -->
  331. <InspiraMarquee reverse class="[--duration:80s] [--delay:-20s] [--gap:24px]">
  332. <div
  333. v-for="partner in partnersRow2"
  334. :key="partner"
  335. class="flex-center h-80px w-200px bg-white rounded-8px shadow-[0_2px_8px_2px_rgba(0,0,0,0.06)] px-24px"
  336. >
  337. <img :src="partner" alt="" class="wh-full object-contain" />
  338. </div>
  339. </InspiraMarquee>
  340. <!-- 左右渐变遮罩 -->
  341. <div
  342. class="pointer-events-none absolute inset-y-0 left-0 w-1/4 bg-[linear-gradient(to_right,#fff,transparent)]"
  343. ></div>
  344. <div
  345. class="pointer-events-none absolute inset-y-0 right-0 w-1/4 bg-[linear-gradient(to_left,#fff,transparent)]"
  346. ></div>
  347. </div>
  348. </section>
  349. <section id="history" ref="timelineRef" class="history">
  350. <div
  351. class="flex flex-col items-center transition-all duration-1000"
  352. :class="[isTimelineVisible ? 'translate-y-0 opacity-100' : 'translate-y-40px opacity-0']"
  353. >
  354. <div
  355. class="pf-sc-semibold font-semibold font-s-36px text-#000000 lh-60px text-center mb-12px"
  356. >
  357. 发展历程
  358. </div>
  359. <div class="font-s-16px text-#091221/70 pf-sc-regular text-center lh-30px mb-50px">
  360. 从探索到引领,绘家科技每一步都坚实有力
  361. </div>
  362. <!-- 上部内容区 -->
  363. <div class="w-full h-255px flex py-50px items-center">
  364. <!-- 左侧大年份 -->
  365. <div v-if="currentYearData" class="relative pl-127px w-600px shrink-0">
  366. <span
  367. class="text-170px font-bold text-#0F67F8 pf-sc-bold select-none"
  368. style="-webkit-text-stroke: 1px #2563eb; text-shadow: 0 0 20px rgba(37, 99, 235, 0.1)"
  369. >
  370. {{ currentYearData.year }}
  371. </span>
  372. </div>
  373. <!-- 右侧事件描述 -->
  374. <div v-if="currentEventData" class="flex-1 h-full flex flex-col justify-start pt-14px">
  375. <div class="flex justify-between items-center">
  376. <span class="text-22px font-semibold lh-30.8px text-#091221 pf-sc-semibold flex-1">
  377. {{ currentEventData.month }}
  378. </span>
  379. <!-- 右上方小切换 -->
  380. <div v-if="(currentYearData?.events?.length ?? 0) > 1" class="flex gap-12px">
  381. <i
  382. class="i-custom-arrow-circle-left wh-32px transition-all"
  383. :class="[
  384. currentEventIndex === 0
  385. ? 'opacity-30 cursor-not-allowed'
  386. : 'cursor-pointer hover:i-custom-arrow-circle-left-active',
  387. ]"
  388. @click="prevEvent"
  389. ></i>
  390. <i
  391. class="i-custom-arrow-circle-right wh-32px transition-all"
  392. :class="[
  393. currentEventIndex === (currentYearData?.events?.length ?? 0) - 1
  394. ? 'opacity-30 cursor-not-allowed'
  395. : 'cursor-pointer hover:i-custom-arrow-circle-right-active',
  396. ]"
  397. @click="nextEvent"
  398. ></i>
  399. </div>
  400. </div>
  401. <div class="h-2px w-34px bg-#0F67F8 mt-11px"></div>
  402. <div class="flex-1 text-20px lh-35px text-#091221/70 pf-sc-regular mt-26px pr-114px">
  403. {{ currentEventData.content }}
  404. </div>
  405. </div>
  406. </div>
  407. <!-- 时间轴 -->
  408. <div class="w-full relative pt-47px pb-60px">
  409. <!-- 轨道容器 -->
  410. <div class="relative h-16px w-full bg-white rounded-full px-60px">
  411. <!-- 进度条:从轨道最左侧开始到当前点 -->
  412. <div
  413. class="absolute left-0 top-0 h-full bg-gradient-to-r from-[#60A5FA] to-[#2563EB] rounded-full transition-all duration-500 ease-out z-1"
  414. :style="progressStyle"
  415. ></div>
  416. <!-- 内部轨道(用于定位时间点) -->
  417. <div class="relative h-full w-full">
  418. <!-- 时间点列表 -->
  419. <div
  420. v-for="(item, index) in historyYears"
  421. :key="item.year"
  422. class="absolute top-1/2 -translate-y-1/2 -translate-x-1/2 flex flex-col items-center cursor-pointer group z-2"
  423. :style="getTimelineNodePositionStyle(index)"
  424. @click="selectYear(index)"
  425. >
  426. <!-- 圆点 -->
  427. <div
  428. class="rounded-full transition-all duration-300 relative bg-white"
  429. :class="getTimelineDotClasses(index)"
  430. :style="getTimelineDotStyle(index)"
  431. ></div>
  432. <!-- 年份文字 -->
  433. <span
  434. class="absolute top-24px text-16px transition-all duration-300 whitespace-nowrap"
  435. :class="getTimelineYearTextClasses(index)"
  436. >
  437. {{ item.year }}
  438. </span>
  439. </div>
  440. </div>
  441. </div>
  442. </div>
  443. <!-- 底部大切换 -->
  444. <div class="flex gap-24px mt-45px">
  445. <i
  446. class="wh-56px i-custom-button-previous hover:i-custom-button-previous-active flex-center cursor-pointer"
  447. :class="{
  448. 'opacity-30 cursor-not-allowed hover:i-custom-button-previous!':
  449. currentYearIndex === 0,
  450. }"
  451. @click="prevYear"
  452. ></i>
  453. <i
  454. class="wh-56px i-custom-button-next hover:i-custom-button-next-active flex-center cursor-pointer"
  455. :class="{
  456. 'opacity-30 cursor-not-allowed hover:i-custom-button-next!':
  457. currentYearIndex === historyYears.length - 1,
  458. }"
  459. @click="nextYear"
  460. ></i>
  461. </div>
  462. </div>
  463. </section>
  464. <section class="cta py-60px flex-center flex-col">
  465. <div class="pf-sc-semibold font-s-40px lh-56px text-center">开启您的智慧社区转型之旅</div>
  466. <div class="font-s-18px text-#232325 pf-sc-light">
  467. 提交您的需求,我们的专属顾问将在24小时内为您提供定制化解决方案
  468. </div>
  469. <button
  470. class="h-52px! mt-22px w-268px! bg-#0F67F8 text-white pf-sc-regular rounded-8px px-16px flex items-center justify-between hover:opacity-80"
  471. @click="openConsultation"
  472. >
  473. <span class="font-s-16px">立即咨询</span>
  474. <i class="i-custom-arrow-right wh-18px"></i>
  475. </button>
  476. </section>
  477. </div>
  478. </template>
  479. <script setup lang="ts">
  480. import {
  481. abilityTabs,
  482. solutions,
  483. abilities,
  484. cases,
  485. partnersRow1,
  486. partnersRow2,
  487. historyYears,
  488. } from '@/constants/common'
  489. const { openConsultation } = useConsultation()
  490. const activeIndex = ref(0)
  491. const solutionRef = ref<HTMLElement | null>(null)
  492. const abilityRef = ref<HTMLElement | null>(null)
  493. const partnershipRef = ref<HTMLElement | null>(null)
  494. const casesRef = ref<HTMLElement | null>(null)
  495. const timelineRef = ref<HTMLElement | null>(null)
  496. const isSolutionVisible = ref(false)
  497. const isAbilityVisible = ref(false)
  498. const isPartnershipVisible = ref(false)
  499. const isCasesVisible = ref(false)
  500. const isTimelineVisible = ref(false)
  501. // 发展历程状态
  502. const currentYearIndex = ref(historyYears.length - 1)
  503. const currentEventIndex = ref(0)
  504. const currentYearData = computed(() => {
  505. const data = historyYears[currentYearIndex.value]
  506. return data || historyYears[0]
  507. })
  508. const currentEventData = computed(() => {
  509. const events = currentYearData.value?.events || []
  510. return events[currentEventIndex.value] || events[0]
  511. })
  512. const progressStyle = computed(() => {
  513. const totalSteps = Math.max(historyYears.length - 1, 1)
  514. if (currentYearIndex.value === totalSteps) {
  515. return {
  516. width: '100%',
  517. }
  518. }
  519. const percentage = currentYearIndex.value / totalSteps
  520. return {
  521. width: `calc(60px + ${percentage} * (100% - 120px))`,
  522. }
  523. })
  524. const nextYear = () => {
  525. if (currentYearIndex.value < historyYears.length - 1) {
  526. currentYearIndex.value++
  527. currentEventIndex.value = 0
  528. }
  529. }
  530. const prevYear = () => {
  531. if (currentYearIndex.value > 0) {
  532. currentYearIndex.value--
  533. currentEventIndex.value = 0
  534. }
  535. }
  536. const nextEvent = () => {
  537. const events = currentYearData.value?.events || []
  538. if (currentEventIndex.value < events.length - 1) {
  539. currentEventIndex.value++
  540. }
  541. }
  542. const prevEvent = () => {
  543. if (currentEventIndex.value > 0) {
  544. currentEventIndex.value--
  545. }
  546. }
  547. const selectYear = (index: number) => {
  548. currentYearIndex.value = index
  549. currentEventIndex.value = 0
  550. }
  551. const activeAbilityTab = ref<AbilityTab>(abilityTabs[0] as AbilityTab)
  552. const abilityTabIndicatorStyle = computed(() => {
  553. const activeTabIndex = abilityTabs.findIndex((tab) => tab.id === activeAbilityTab.value.id)
  554. return {
  555. transform: `translateX(${activeTabIndex * 136}px)`,
  556. }
  557. })
  558. const handleAbilityTabClick = (tab: AbilityTab) => {
  559. activeAbilityTab.value = tab
  560. }
  561. onMounted(() => {
  562. const revealSections = [
  563. { elRef: solutionRef, visible: isSolutionVisible },
  564. { elRef: abilityRef, visible: isAbilityVisible },
  565. { elRef: partnershipRef, visible: isPartnershipVisible },
  566. { elRef: casesRef, visible: isCasesVisible },
  567. { elRef: timelineRef, visible: isTimelineVisible },
  568. ]
  569. const observer = new IntersectionObserver(
  570. (entries) => {
  571. entries.forEach((entry) => {
  572. if (entry.isIntersecting) {
  573. const matched = revealSections.find((item) => item.elRef.value === entry.target)
  574. if (matched) {
  575. matched.visible.value = true
  576. }
  577. observer.unobserve(entry.target)
  578. }
  579. })
  580. },
  581. { threshold: 0.1 }
  582. )
  583. revealSections.forEach(({ elRef }) => {
  584. if (elRef.value) {
  585. observer.observe(elRef.value)
  586. }
  587. })
  588. })
  589. const getTimelineNodePositionStyle = (index: number) => {
  590. return { left: `${(index / (historyYears.length - 1)) * 100}%` }
  591. }
  592. const getTimelineDotClasses = (index: number) => {
  593. return [
  594. index < currentYearIndex.value
  595. ? 'wh-13px'
  596. : 'wh-16px border-#0F67F8 group-hover:border-[#2563EB] border-1',
  597. index === currentYearIndex.value
  598. ? 'wh-16px scale-150 outline-6px outline-#CEE0FF outline-solid border-none'
  599. : '',
  600. ]
  601. }
  602. const getTimelineDotStyle = (index: number) => {
  603. if (index === currentYearIndex.value) {
  604. return { background: 'linear-gradient(90deg, #779EFF 0%, #0A50FF 100%)' }
  605. }
  606. if (index < currentYearIndex.value) {
  607. return { background: '#FFFFFF' }
  608. }
  609. return {}
  610. }
  611. const getTimelineYearTextClasses = (index: number) => {
  612. return [
  613. index === currentYearIndex.value
  614. ? 'text-#2563EB font-bold pf-sc-bold scale-110'
  615. : 'text-#94A3B8 group-hover:text-[#64748B]',
  616. ]
  617. }
  618. </script>
  619. <style scoped lang="scss">
  620. .landing {
  621. color: var(--hj-text);
  622. background: var(--hj-bg);
  623. }
  624. .hero {
  625. @apply h-680px relative overflow-hidden;
  626. background-size: cover;
  627. background-position: center;
  628. background-image: url('@/assets/images/banner.png');
  629. .hero-content {
  630. @extend %landing-container;
  631. }
  632. .metric {
  633. @apply flex items-start flex-col;
  634. .metric-value {
  635. @apply font-s-32px font-semibold text-#0F67F8;
  636. }
  637. .metric-label {
  638. @apply font-s-14px text-#384146;
  639. }
  640. }
  641. }
  642. .solution {
  643. @extend %landing-container;
  644. }
  645. .ability {
  646. @apply py-120px;
  647. @extend %landing-container;
  648. background-size: 100% 100%;
  649. background-image: url('@/assets/images/bg-ability.png');
  650. }
  651. .cases {
  652. @extend %landing-container;
  653. }
  654. .partnership {
  655. @extend %landing-container;
  656. @media screen and (min-width: 1920px) {
  657. padding-left: 220px;
  658. padding-right: 220px;
  659. }
  660. }
  661. .tab-fade-enter-active {
  662. transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  663. }
  664. .tab-fade-leave-active {
  665. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  666. }
  667. .tab-fade-enter-from {
  668. opacity: 0;
  669. transform: translateX(30px);
  670. }
  671. .tab-fade-leave-to {
  672. opacity: 0;
  673. transform: translateX(-30px);
  674. }
  675. /* 卡片级联动画 */
  676. .grid > div {
  677. animation: card-slide-up 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
  678. }
  679. @for $i from 1 through 10 {
  680. .grid > div:nth-child(#{$i}) {
  681. animation-delay: #{$i * 0.05}s;
  682. }
  683. }
  684. @keyframes card-slide-up {
  685. from {
  686. opacity: 0;
  687. transform: translateY(20px);
  688. }
  689. to {
  690. opacity: 1;
  691. transform: translateY(0);
  692. }
  693. }
  694. .btn-primary {
  695. @apply bg-gradient-to-r from-[#779EFF] to-[#0A50FF];
  696. border: none;
  697. outline: none;
  698. //hover 背景颜色
  699. &:hover {
  700. opacity: 0.8;
  701. }
  702. }
  703. .history {
  704. @apply overflow-hidden relative py-60px;
  705. @extend %landing-container;
  706. background-size: cover;
  707. background-position: center;
  708. background-image: url('@/assets/images/history-bg.png');
  709. }
  710. .cta {
  711. @apply w-full h-276px;
  712. background-size: cover;
  713. background-position: center;
  714. background-image: url('@/assets/images/bg-3.png');
  715. }
  716. </style>