Hanye官网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. <template>
  2. <div>
  3. <!-- 轮播图 -->
  4. <section class="max-w-full mb-12 md:mb-32">
  5. <Swiper
  6. :modules="[Pagination, Autoplay, EffectCreative]"
  7. :slides-per-view="1"
  8. :space-between="30"
  9. :loop="true"
  10. :pagination="{ el: '.swiper-pagination-1', clickable: true }"
  11. :autoplay="{
  12. delay: 5000,
  13. disableOnInteraction: false,
  14. pauseOnMouseEnter: true,
  15. waitForTransition: true
  16. }"
  17. effect="creative"
  18. :creativeEffect="{
  19. prev: {
  20. shadow: true,
  21. translate: ['-120%', 0, -500],
  22. rotate: [0, 0, -90],
  23. opacity: 0,
  24. },
  25. next: {
  26. shadow: true,
  27. translate: ['120%', 0, -500],
  28. rotate: [0, 0, 90],
  29. opacity: 0,
  30. },
  31. }"
  32. :speed="1000"
  33. :grabCursor="true"
  34. :parallax="true"
  35. class="h-[320px] sm:h-[320px] md:h-[768px] lg:h-[900px] swiper-container-1"
  36. >
  37. <SwiperSlide v-for="item in carouselList" :key="item.id">
  38. <nuxt-link :to="item.link">
  39. <div
  40. class="max-w-screen-2xl mx-auto h-full"
  41. :style="{
  42. backgroundImage: `url(${item.image})`,
  43. backgroundSize: 'cover',
  44. backgroundPosition: 'center',
  45. backgroundRepeat: 'no-repeat',
  46. }"
  47. ></div>
  48. </nuxt-link>
  49. </SwiperSlide>
  50. <div class="max-w-screen-2xl mx-auto relative">
  51. <div
  52. class="swiper-pagination swiper-pagination-1 text-left bottom-1 top-0"
  53. ></div>
  54. </div>
  55. </Swiper>
  56. </section>
  57. <!-- 按用途产品展示 -->
  58. <section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
  59. <div class="max-w-screen-2xl mx-auto relative">
  60. <div
  61. class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4"
  62. >
  63. {{ $t("products.usage") }}
  64. </div>
  65. <div
  66. class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
  67. >
  68. {{ $t("products.usage_title") }}
  69. </div>
  70. </div>
  71. <div class="max-w-screen-2xl mx-auto">
  72. <div class="w-full mb-8">
  73. <div class="flex flex-wrap items-center gap-2">
  74. <div
  75. v-for="(usage, index) in typedUsageList"
  76. :key="usage.id"
  77. class="cursor-pointer select-none px-4 sm:px-7 py-2 sm:py-3 rounded-full border border-zinc-700 text-white transition-all duration-300 relative group"
  78. :class="{
  79. 'bg-cyan-400/10 border-cyan-400 text-cyan-400': activeIndex === index,
  80. 'hover:border-zinc-600': activeIndex !== index
  81. }"
  82. @click="handleUsageClick(usage.id)"
  83. >
  84. <div class="text-center text-xs sm:text-sm font-normal leading-tight md:text-base transition-colors duration-300 relative z-10">
  85. {{ usage.name }}
  86. </div>
  87. <div class="absolute inset-0 rounded-full bg-cyan-400/20 scale-0 transition-transform duration-300 group-hover:scale-100"></div>
  88. </div>
  89. <div class="flex items-center justify-center gap-4 ml-auto">
  90. <div
  91. class="swiper-button-prev-2 bg-zinc-700 w-8 h-8 sm:w-10 sm:h-10 rounded-full flex items-center justify-center cursor-pointer hover:bg-zinc-600 transition-colors duration-200"
  92. >
  93. <i class="icon-arrow-left text-zinc-300 text-xs sm:text-sm font-normal"></i>
  94. </div>
  95. <div
  96. class="swiper-button-next-2 bg-zinc-700 w-8 h-8 sm:w-10 sm:h-10 rounded-full flex items-center justify-center cursor-pointer hover:bg-zinc-600 transition-colors duration-200"
  97. >
  98. <i class="icon-arrow-right text-zinc-300 text-xs sm:text-sm font-normal"></i>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. </div>
  104. <div class="max-w-screen-2xl mx-auto">
  105. <div
  106. class="w-full h-[300px] sm:h-[350px] md:h-[400px] lg:h-[450px] relative"
  107. >
  108. <TransitionGroup name="slide-fade" tag="div" class="relative h-full">
  109. <div :key="activeUsageId" class="w-full h-full">
  110. <Swiper
  111. :modules="[Navigation]"
  112. :spaceBetween="30"
  113. :slidesPerView="4"
  114. :breakpoints="{
  115. 320: { slidesPerView: 2, spaceBetween: 10 },
  116. 640: { slidesPerView: 3, spaceBetween: 20 },
  117. 1024: { slidesPerView: 4, spaceBetween: 20 },
  118. 1280: { slidesPerView: 4, spaceBetween: 30 },
  119. 1536: { slidesPerView: 4, spaceBetween: 30 },
  120. }"
  121. :navigation="{
  122. prevEl: '.swiper-button-prev-2',
  123. nextEl: '.swiper-button-next-2',
  124. }"
  125. class="h-full"
  126. >
  127. <SwiperSlide
  128. v-for="product in activeProducts"
  129. :key="product.id"
  130. class="w-full sm:w-1/2 md:w-1/3 lg:w-1/4"
  131. >
  132. <div class="w-full h-full p-2">
  133. <nuxt-link
  134. :to="product.link"
  135. class="block w-full h-full"
  136. >
  137. <div
  138. class="w-full h-full bg-zinc-900 rounded-2xl p-4 flex flex-col items-center justify-start relative overflow-hidden group hover:bg-zinc-800 transition-all duration-300 hover:shadow-lg hover:shadow-cyan-400/10"
  139. >
  140. <!-- 图片加载占位 -->
  141. <div
  142. v-if="!isImageLoaded[product.id]"
  143. class="absolute inset-0 bg-gradient-to-br from-zinc-800 via-zinc-700 to-zinc-800 animate-gradient"
  144. >
  145. <div
  146. class="absolute inset-0 flex items-center justify-center"
  147. >
  148. <div
  149. class="w-8 h-8 border-4 border-cyan-400/20 border-t-cyan-400 rounded-full animate-spin"
  150. ></div>
  151. </div>
  152. </div>
  153. <!-- 图片容器 -->
  154. <div
  155. class="w-full aspect-square relative transition-all duration-300 overflow-hidden rounded-lg"
  156. :class="{ 'opacity-0': !isImageLoaded[product.id] }"
  157. >
  158. <img
  159. :src="product.image"
  160. :alt="product.title"
  161. class="w-full h-full object-cover transition-all duration-500 group-hover:scale-110"
  162. @load="handleImageLoad(product.id)"
  163. @error="handleImageError(product.id)"
  164. loading="lazy"
  165. />
  166. <div
  167. class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"
  168. ></div>
  169. <!-- 添加查看详情按钮 -->
  170. <div
  171. class="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all duration-300 transform translate-y-4 group-hover:translate-y-0"
  172. >
  173. <div
  174. class="px-4 py-2 bg-cyan-400/20 backdrop-blur-sm rounded-full text-white text-sm font-medium border border-cyan-400/30 transform transition-transform duration-300 group-hover:scale-105"
  175. >
  176. 查看详情
  177. </div>
  178. </div>
  179. </div>
  180. <!-- 文字内容 -->
  181. <div
  182. class="w-full mt-4 min-h-[80px] transition-all duration-300 transform"
  183. :class="{
  184. 'opacity-0 translate-y-4': !isImageLoaded[product.id],
  185. 'opacity-100 translate-y-0':
  186. isImageLoaded[product.id],
  187. }"
  188. >
  189. <div
  190. class="text-center text-white text-base font-bold leading-tight mb-2 line-clamp-2 group-hover:text-cyan-400 transition-colors duration-300"
  191. >
  192. {{ product.title }}
  193. </div>
  194. <div
  195. class="text-center text-zinc-400 text-sm font-normal leading-tight line-clamp-2 group-hover:text-zinc-300 transition-colors duration-300"
  196. >
  197. {{ product.description }}
  198. </div>
  199. </div>
  200. </div>
  201. </nuxt-link>
  202. </div>
  203. </SwiperSlide>
  204. </Swiper>
  205. </div>
  206. </TransitionGroup>
  207. </div>
  208. </div>
  209. </section>
  210. <!-- 按分类栏目展示 -->
  211. <section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
  212. <div class="max-w-screen-2xl mx-auto relative">
  213. <div
  214. class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4"
  215. >
  216. Hanye独自に開発・製造、販売
  217. </div>
  218. <div
  219. class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
  220. >
  221. 製品カテゴリー
  222. </div>
  223. </div>
  224. <div class="max-w-screen-2xl mx-auto">
  225. <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  226. <nuxt-link
  227. v-for="category in typedCategoryList"
  228. :key="category.id"
  229. :to="category.link"
  230. class="bg-zinc-950/10 backdrop-blur-[50px] border border-white/10 rounded-lg flex gap-8 p-4 sm:p-8 justify-between category-item group hover:border-cyan-400/30 transition-all duration-300"
  231. >
  232. <div class="col-span-1 flex flex-col gap-4">
  233. <div class="flex flex-col gap-2 opacity-80 group-hover:opacity-100 transition-opacity duration-300">
  234. <div
  235. v-for="feature in category.features"
  236. :key="feature"
  237. class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center group-hover:text-cyan-400 transition-colors duration-300"
  238. >
  239. <i class="icon-star text-sm group-hover:scale-110 transition-transform duration-300"></i>
  240. <span>{{ feature }}</span>
  241. </div>
  242. </div>
  243. <div
  244. class="p-2 sm:p-4 mt-auto bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-white/10 backdrop-blur-xl inline-flex justify-center items-center gap-3 overflow-hidden group-hover:bg-cyan-400/10 group-hover:outline-cyan-400/30 transition-all duration-300"
  245. >
  246. <div
  247. class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed group-hover:text-cyan-400 transition-colors duration-300"
  248. >
  249. {{ category.description }}
  250. </div>
  251. </div>
  252. </div>
  253. <div class="w-32 h-32 md:w-44 md:h-44 relative overflow-hidden rounded-lg">
  254. <img
  255. :src="category.image"
  256. :alt="category.title"
  257. class="w-full h-full object-contain transition-transform duration-500 group-hover:scale-110"
  258. />
  259. <div class="absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
  260. </div>
  261. </nuxt-link>
  262. </div>
  263. </div>
  264. </section>
  265. <!-- 产品核心展示 -->
  266. <section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
  267. <div
  268. class="max-w-screen-2xl mx-auto grid grid-cols-1 lg:grid-cols-3 md:grid-cols-2 sm:grid-cols-1 gap-4"
  269. >
  270. <div class="inline-flex justify-start items-center gap-5">
  271. <div
  272. class="w-12 h-12 md:w-16 md:h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
  273. >
  274. <div class="w-full h-full flex items-center justify-center">
  275. <i class="icon-h1 text-white text-5xl"></i>
  276. </div>
  277. </div>
  278. <div class="inline-flex flex-col justify-center items-start flex-1">
  279. <div
  280. class="justify-start text-white font-medium text-1xl md:text-lg"
  281. >
  282. {{ $t("products.support") }}
  283. </div>
  284. <div
  285. class="justify-start text-zinc-300 text-xs font-normal md:text-sm"
  286. >
  287. {{ $t("products.support_description") }}
  288. </div>
  289. </div>
  290. </div>
  291. <div class="inline-flex justify-start items-center gap-5">
  292. <div
  293. class="w-12 h-12 md:w-16 md:h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
  294. >
  295. <div class="w-full h-full flex items-center justify-center">
  296. <i class="icon-h2 text-white text-5xl"></i>
  297. </div>
  298. </div>
  299. <div class="inline-flex flex-col justify-center items-start flex-1">
  300. <div
  301. class="justify-start text-white font-medium text-1xl md:text-lg"
  302. >
  303. {{ $t("products.development") }}
  304. </div>
  305. <div
  306. class="justify-start text-zinc-300 text-xs font-normal md:text-sm"
  307. >
  308. {{ $t("products.development_description") }}
  309. </div>
  310. </div>
  311. </div>
  312. <div class="inline-flex justify-start items-center gap-5">
  313. <div
  314. class="w-12 h-12 md:w-16 md:h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden"
  315. >
  316. <div class="w-full h-full flex items-center justify-center">
  317. <i class="icon-h3 text-white text-5xl"></i>
  318. </div>
  319. </div>
  320. <div class="inline-flex flex-col justify-center items-start flex-1">
  321. <div
  322. class="justify-start text-white font-medium text-1xl md:text-lg"
  323. >
  324. {{ $t("products.develop") }}
  325. </div>
  326. <div
  327. class="justify-start text-zinc-300 text-xs font-normal md:text-sm"
  328. >
  329. {{ $t("products.develop_description") }}
  330. </div>
  331. </div>
  332. </div>
  333. </div>
  334. </section>
  335. <!-- 当社の強み -->
  336. <section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4">
  337. <div class="max-w-screen-2xl mx-auto relative">
  338. <div
  339. class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4"
  340. >
  341. {{ $t("products.strong_point") }}
  342. </div>
  343. <div
  344. class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl"
  345. >
  346. {{ $t("products.strong_point_title") }}
  347. </div>
  348. <div
  349. class="absolute right-0 top-1/2 -translate-y-1/2 z-10 lg:block hidden"
  350. >
  351. <div class="flex items-center justify-center gap-4">
  352. <div
  353. class="swiper-button-prev-3 bg-zinc-700 w-10 h-10 rounded-full flex items-center justify-center cursor-pointer hover:bg-zinc-600 transition-colors duration-200"
  354. >
  355. <i class="icon-arrow-left text-zinc-300 text-sm font-normal"></i>
  356. </div>
  357. <div
  358. class="swiper-button-next-3 bg-zinc-700 w-10 h-10 rounded-full flex items-center justify-center cursor-pointer hover:bg-zinc-600 transition-colors duration-200"
  359. >
  360. <i class="icon-arrow-right text-zinc-300 text-sm font-normal"></i>
  361. </div>
  362. </div>
  363. </div>
  364. </div>
  365. <div class="max-w-screen-2xl mx-auto">
  366. <div class="w-full relative max-w-full">
  367. <Swiper
  368. :modules="[Navigation, Pagination]"
  369. slides-per-view="auto"
  370. :space-between="30"
  371. :pagination="{ el: '.swiper-pagination-3', clickable: true }"
  372. :navigation="{
  373. prevEl: '.swiper-button-prev-3',
  374. nextEl: '.swiper-button-next-3',
  375. }"
  376. class="h-[320px] sm:h-[320px] md:h-[480px] lg:h-[720px] max-w-full"
  377. >
  378. <SwiperSlide class="!max-w-screen-2xl !w-full">
  379. <div
  380. class="w-full h-full flex items-center px-0 md:px-20"
  381. :style="{
  382. backgroundImage: `url(${homeC1Webp})`,
  383. backgroundSize: 'cover',
  384. backgroundPosition: 'center',
  385. backgroundRepeat: 'no-repeat',
  386. }"
  387. >
  388. <div
  389. class="w-full lg:w-[50%] h-full md:h-auto bg-white/5 rounded-0 md:rounded-2xl backdrop-blur-[50px] px-4 py-8 md:py-12 md:px-8 flex flex-col gap-8 border border-white/10"
  390. >
  391. <div
  392. class="opacity-90 justify-start text-white text-2xl font-normal md:text-4xl"
  393. >
  394. 一貫体制による高品質と安定供給
  395. </div>
  396. <div
  397. class="opacity-70 justify-start text-white text-base md:text-lg font-normal leading-relaxed"
  398. >
  399. 「企画・開発から製造、品質管理、販売、オンラインショップ運営まで自社で完結。ISO認証取得の工場で生産された信頼性の高い製品を、安定してお届けします。」
  400. </div>
  401. </div>
  402. </div>
  403. </SwiperSlide>
  404. </Swiper>
  405. </div>
  406. <div class="max-w-screen-2xl mx-auto relative">
  407. <div class="swiper-pagination swiper-pagination-3"></div>
  408. </div>
  409. </div>
  410. </section>
  411. <!-- 产品咨询 -->
  412. <section class="max-w-full h-[240px] md:h-[480px] bg-black/80 md:block">
  413. <div class="h-full relative">
  414. <div
  415. class="absolute top-0 left-0 w-full h-full flex flex-col gap-6 items-center justify-center z-10"
  416. >
  417. <h1
  418. class="text-center justify-start text-white font-normal text-xl sm:text-2xl md:text-3xl px-2"
  419. >
  420. {{ $t("products.consultation") }}
  421. </h1>
  422. <nuxt-link
  423. :to="locale === defaultLocale ? '/contact' : `/${locale}/contact`"
  424. class="w-32 h-10 md:w-40 md:h-11 bg-zinc-300/10 rounded-lg outline outline-1 flex items-center justify-center gap-2 outline-white/20 backdrop-blur-[10px] cursor-pointer hover:bg-zinc-300/20 transition-colors duration-200"
  425. >
  426. <span class="text-xs md:text-sm font-normal">{{
  427. $t("products.consultation_button")
  428. }}</span>
  429. <i class="icon-arrow-right text-sm font-normal"></i>
  430. </nuxt-link>
  431. </div>
  432. <img
  433. v-if="isMobile"
  434. :src="videoWebp"
  435. alt="video"
  436. class="w-full h-full object-cover opacity-20"
  437. />
  438. <video
  439. v-else
  440. :src="videoSrc"
  441. autoplay
  442. muted
  443. loop
  444. :poster="videoWebp"
  445. class="w-full h-full object-cover opacity-20"
  446. ></video>
  447. </div>
  448. </section>
  449. </div>
  450. </template>
  451. <script setup lang="ts">
  452. import { Swiper, SwiperSlide } from "swiper/vue";
  453. import {
  454. Navigation,
  455. Pagination,
  456. Autoplay,
  457. EffectCreative,
  458. } from "swiper/modules";
  459. import "swiper/css";
  460. import "swiper/css/navigation";
  461. import "swiper/css/pagination";
  462. import { useBreakpoints, breakpointsTailwind } from "@vueuse/core";
  463. import { useI18n } from "vue-i18n";
  464. import video from "@/assets/videos/video.mp4";
  465. import videoWebp from "@/assets/videos/video.webp";
  466. import homeC1Webp from "@/assets/images/home-c-1.webp";
  467. import product from "@/assets/images/product.png";
  468. const { t, locale } = useI18n();
  469. const config = useRuntimeConfig();
  470. // 从运行时配置获取默认语言,如果未配置则默认为 'en'
  471. const defaultLocale = config.public.i18n?.defaultLocale || "en";
  472. const videoSrc = ref(video);
  473. // Define breakpoints
  474. const breakpoints = useBreakpoints(breakpointsTailwind);
  475. // Check if the device is mobile (smaller than md)
  476. const isMobile = breakpoints.smaller("md");
  477. // 获取轮播图数据
  478. const { data: carouselData, error: carouselError } = await useFetch(
  479. "/api/home"
  480. );
  481. const carouselList = ref(carouselData.value?.data || []);
  482. // 获取按用途产品数据
  483. const { data: usageData, error: usageError } = await useFetch(
  484. "/api/products/usage"
  485. );
  486. const usageList = ref(usageData.value?.data || []);
  487. const activeUsageId = ref(1);
  488. // 获取按分类栏目数据
  489. const { data: categoryData, error: categoryError } = await useFetch(
  490. "/api/products/category"
  491. );
  492. const categoryList = ref(categoryData.value?.data || []);
  493. // 调试信息
  494. console.log("Carousel Data:", carouselData.value);
  495. console.log("Carousel Error:", carouselError.value);
  496. console.log("Usage Data:", usageData.value);
  497. console.log("Usage Error:", usageError.value);
  498. console.log("Category Data:", categoryData.value);
  499. console.log("Category Error:", categoryError.value);
  500. interface Product {
  501. id: number;
  502. title: string;
  503. image: string;
  504. link: string;
  505. description?: string;
  506. }
  507. interface Usage {
  508. id: number;
  509. name: string;
  510. products: Product[];
  511. }
  512. interface Category {
  513. id: number;
  514. title: string;
  515. description: string;
  516. features: string[];
  517. image: string;
  518. link: string;
  519. }
  520. // 计算当前用途列表
  521. const typedUsageList = computed(() => {
  522. console.log("Typed Usage List:", usageList.value);
  523. return usageList.value as Usage[];
  524. });
  525. // 计算当前激活的索引
  526. const activeIndex = computed(() => {
  527. return typedUsageList.value.findIndex(item => item.id === activeUsageId.value);
  528. });
  529. const activeProducts = computed(() => {
  530. const currentUsage = typedUsageList.value.find(
  531. (item: Usage) => item.id === activeUsageId.value
  532. );
  533. console.log("Current Usage:", currentUsage);
  534. if (currentUsage?.products) {
  535. console.log(
  536. "Products:",
  537. currentUsage.products.map((p: Product) => ({
  538. id: p.id,
  539. title: p.title,
  540. image: p.image,
  541. link: p.link,
  542. }))
  543. );
  544. }
  545. return currentUsage?.products || [];
  546. });
  547. // 图片加载状态
  548. const isImageLoaded = ref<Record<number, boolean>>({});
  549. const imageErrors = ref<Record<number, boolean>>({});
  550. // 监听图片加载状态
  551. watch(
  552. activeProducts,
  553. (newProducts: Product[]) => {
  554. console.log("Active Products Changed:", newProducts);
  555. if (process.client) {
  556. newProducts.forEach((product: Product) => {
  557. if (product.image) {
  558. console.log(
  559. `Checking image for product ${product.id}:`,
  560. product.image
  561. );
  562. const img = new window.Image();
  563. img.onload = () => handleImageLoad(product.id);
  564. img.onerror = () => handleImageError(product.id);
  565. img.src = product.image;
  566. }
  567. });
  568. }
  569. },
  570. { immediate: true }
  571. );
  572. // 处理图片加载
  573. const handleImageLoad = (id: number) => {
  574. if (process.client) {
  575. console.log(`Image loaded for product ${id}`);
  576. isImageLoaded.value[id] = true;
  577. imageErrors.value[id] = false;
  578. }
  579. };
  580. // 处理图片加载错误
  581. const handleImageError = (id: number) => {
  582. if (process.client) {
  583. console.error(`Failed to load image for product ${id}`);
  584. isImageLoaded.value[id] = true;
  585. imageErrors.value[id] = true;
  586. }
  587. };
  588. // 处理用途点击
  589. const handleUsageClick = (id: number) => {
  590. if (process.client) {
  591. console.log("Usage clicked:", id);
  592. // 重置图片加载状态
  593. isImageLoaded.value = {};
  594. imageErrors.value = {};
  595. activeUsageId.value = id;
  596. }
  597. };
  598. // 计算当前分类列表
  599. const typedCategoryList = computed(() => {
  600. console.log("Typed Category List:", categoryList.value);
  601. return categoryList.value as Category[];
  602. });
  603. // SEO优化
  604. useHead({
  605. title: "Hanye - 首页",
  606. meta: [
  607. {
  608. name: "description",
  609. content: "基于 Nuxt3 的静态网站脚手架,支持多语言(中文、英文、日文)。",
  610. },
  611. ],
  612. });
  613. </script>
  614. <style lang="scss" scoped>
  615. :deep(.swiper-pagination-3) {
  616. margin: auto;
  617. position: static;
  618. padding: 1rem 0;
  619. }
  620. :deep(.swiper-pagination-bullet) {
  621. background-color: var(--color-bg); /* Example color */
  622. border: 2px solid var(--color-text);
  623. }
  624. :deep(.swiper-pagination-bullet-active) {
  625. background-color: var(--color-text); /* Example color */
  626. }
  627. .category-item {
  628. background-image: url("@/assets/images/home-b-1.webp");
  629. background-size: cover;
  630. background-position: center;
  631. background-repeat: no-repeat;
  632. opacity: 0.8;
  633. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  634. position: relative;
  635. overflow: hidden;
  636. &:hover {
  637. opacity: 1;
  638. transform: translateY(-2px);
  639. box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
  640. &::before {
  641. opacity: 1;
  642. }
  643. }
  644. &::before {
  645. content: '';
  646. position: absolute;
  647. inset: 0;
  648. background: linear-gradient(45deg, rgba(6, 182, 212, 0.1), transparent);
  649. opacity: 0;
  650. transition: opacity 0.3s ease;
  651. pointer-events: none;
  652. }
  653. }
  654. // 优化过渡动画
  655. .slide-fade-enter-active,
  656. .slide-fade-leave-active {
  657. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  658. position: absolute;
  659. width: 100%;
  660. height: 300px;
  661. }
  662. @media (min-width: 640px) {
  663. .slide-fade-enter-active,
  664. .slide-fade-leave-active {
  665. height: 350px;
  666. }
  667. }
  668. @media (min-width: 768px) {
  669. .slide-fade-enter-active,
  670. .slide-fade-leave-active {
  671. height: 400px;
  672. }
  673. }
  674. @media (min-width: 1024px) {
  675. .slide-fade-enter-active,
  676. .slide-fade-leave-active {
  677. height: 450px;
  678. }
  679. }
  680. .slide-fade-enter-from {
  681. opacity: 0;
  682. transform: translateX(30px);
  683. }
  684. .slide-fade-leave-to {
  685. opacity: 0;
  686. transform: translateX(-30px);
  687. }
  688. // 添加响应式优化
  689. @media (max-width: 640px) {
  690. .slide-fade-enter-from,
  691. .slide-fade-leave-to {
  692. transform: translateX(15px);
  693. }
  694. }
  695. // 优化图片加载动画
  696. @keyframes gradient {
  697. 0% {
  698. background-position: 0% 50%;
  699. }
  700. 50% {
  701. background-position: 100% 50%;
  702. }
  703. 100% {
  704. background-position: 0% 50%;
  705. }
  706. }
  707. .animate-gradient {
  708. background-size: 200% 200%;
  709. animation: gradient 3s ease infinite;
  710. }
  711. // 移除 pagination 相关样式
  712. :deep(.swiper-pagination-2) {
  713. display: none;
  714. }
  715. // 添加导航按钮禁用样式
  716. :deep(.swiper-button-disabled) {
  717. opacity: 0.35;
  718. cursor: not-allowed;
  719. pointer-events: none;
  720. }
  721. // 优化轮播图样式
  722. :deep(.swiper-container-1) {
  723. .swiper-slide {
  724. transition: all 0.5s ease;
  725. transform-origin: center center;
  726. backface-visibility: hidden;
  727. perspective: 1000px;
  728. }
  729. .swiper-pagination-bullet {
  730. width: 12px;
  731. height: 12px;
  732. background: transparent;
  733. border: 2px solid rgba(255, 255, 255, 0.5);
  734. opacity: 1;
  735. transition: all 0.3s ease;
  736. margin: 0 8px !important;
  737. &-active {
  738. background: #fff;
  739. border-color: #fff;
  740. transform: scale(1.2);
  741. }
  742. }
  743. .swiper-pagination {
  744. display: flex;
  745. justify-content: center;
  746. align-items: center;
  747. padding: 20px 0;
  748. }
  749. }
  750. // 添加轮播图遮罩效果
  751. .swiper-slide {
  752. &::before {
  753. content: '';
  754. position: absolute;
  755. top: 0;
  756. left: 0;
  757. right: 0;
  758. bottom: 0;
  759. background: linear-gradient(
  760. to bottom,
  761. rgba(0, 0, 0, 0.2) 0%,
  762. rgba(0, 0, 0, 0.4) 100%
  763. );
  764. z-index: 1;
  765. }
  766. }
  767. // 优化图片加载动画
  768. @keyframes fadeIn {
  769. from {
  770. opacity: 0;
  771. transform: scale(1.1);
  772. }
  773. to {
  774. opacity: 1;
  775. transform: scale(1);
  776. }
  777. }
  778. .swiper-slide-active {
  779. img {
  780. animation: fadeIn 0.8s ease-out forwards;
  781. }
  782. }
  783. // 优化产品卡片动画和交互
  784. .swiper-slide {
  785. a {
  786. text-decoration: none;
  787. display: block;
  788. height: 100%;
  789. &:hover {
  790. text-decoration: none;
  791. }
  792. > div {
  793. position: relative;
  794. z-index: 1;
  795. cursor: pointer;
  796. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  797. &:hover {
  798. transform: translateY(-2px);
  799. box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
  800. }
  801. }
  802. }
  803. }
  804. // 移除卡片抖动动画
  805. @keyframes cardHover {
  806. 0% {
  807. transform: translateY(0);
  808. }
  809. 100% {
  810. transform: translateY(-2px);
  811. }
  812. }
  813. </style>