index.vue 29 KB

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