- 在icomoon中新增了两个图标:enter和star,并更新了相关的SVG和CSS文件。 - 更新了demo.html文件,增加了新图标的展示。 - 新增了多个图片资源,包括home-b-1.webp、product-banner.webp和video.webp,丰富了页面内容。 - 在LanguageSwitcher组件中优化了下拉菜单的交互逻辑,增加了鼠标悬停效果。 - 更新了TheHeader和TheFooter组件的样式,提升了页面的视觉效果。 - 在关于我们、联系我们和产品页面中进行了内容更新,增加了SEO优化。 - 新增FAQ页面,提供常见问题解答,提升用户支持。 - 在index.vue和products/index.vue中优化了产品展示逻辑,改善了用户体验。master
<link rel="stylesheet" href="style.css"></head> | <link rel="stylesheet" href="style.css"></head> | ||||
<body> | <body> | ||||
<div class="bgc1 clearfix"> | <div class="bgc1 clearfix"> | ||||
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs: 8)</small></h1> | |||||
<h1 class="mhmm mvm"><span class="fgc1">Font Name:</span> icomoon <small class="fgc1">(Glyphs: 10)</small></h1> | |||||
</div> | </div> | ||||
<div class="clearfix mhl ptl"> | <div class="clearfix mhl ptl"> | ||||
<h1 class="mvm mtn fgc1">Grid Size: Unknown</h1> | <h1 class="mvm mtn fgc1">Grid Size: Unknown</h1> | ||||
<div class="glyph fs1"> | |||||
<div class="clearfix bshadow0 pbs"> | |||||
<span class="icon-enter"></span> | |||||
<span class="mls"> icon-enter</span> | |||||
</div> | |||||
<fieldset class="fs0 size1of1 clearfix hidden-false"> | |||||
<input type="text" readonly value="e909" class="unit size1of2" /> | |||||
<input type="text" maxlength="1" readonly value="" class="unitRight size1of2 talign-right" /> | |||||
</fieldset> | |||||
<div class="fs0 bshadow0 clearfix hidden-true"> | |||||
<span class="unit pvs fgc1">liga: </span> | |||||
<input type="text" readonly value="" class="liga unitRight" /> | |||||
</div> | |||||
</div> | |||||
<div class="glyph fs1"> | |||||
<div class="clearfix bshadow0 pbs"> | |||||
<span class="icon-star"></span> | |||||
<span class="mls"> icon-star</span> | |||||
</div> | |||||
<fieldset class="fs0 size1of1 clearfix hidden-false"> | |||||
<input type="text" readonly value="e908" class="unit size1of2" /> | |||||
<input type="text" maxlength="1" readonly value="" class="unitRight size1of2 talign-right" /> | |||||
</fieldset> | |||||
<div class="fs0 bshadow0 clearfix hidden-true"> | |||||
<span class="unit pvs fgc1">liga: </span> | |||||
<input type="text" readonly value="" class="liga unitRight" /> | |||||
</div> | |||||
</div> | |||||
<div class="glyph fs1"> | <div class="glyph fs1"> | ||||
<div class="clearfix bshadow0 pbs"> | <div class="clearfix bshadow0 pbs"> | ||||
<span class="icon-h1"></span> | <span class="icon-h1"></span> |
<glyph unicode="" glyph-name="h2" horiz-adv-x="1044" d="M671.059 752.461c-7.629 0.006-14.948-3.017-20.352-8.405l-76.928-76.928h-215.593c-7.639 0-14.964-3.035-20.366-8.436-5.402-5.4-8.434-12.725-8.434-20.364s3.033-14.964 8.434-20.364c5.402-5.402 12.727-8.436 20.366-8.436h215.593l76.928-76.885c5.396-5.404 12.717-8.442 20.352-8.448h113.792c7.639 0 14.964 3.035 20.366 8.434 5.4 5.402 8.434 12.727 8.434 20.366 0 7.637-3.035 14.962-8.434 20.364-5.402 5.402-12.727 8.436-20.366 8.436h-101.845l-56.533 56.533 56.533 56.533h101.845c7.639 0 14.964 3.035 20.366 8.436 5.4 5.402 8.434 12.727 8.434 20.364 0 7.639-3.035 14.964-8.434 20.366-5.402 5.402-12.727 8.434-20.366 8.434h-113.792zM215.692 638.352c0.006 14.029 3.454 27.843 10.045 40.225 6.591 12.384 16.122 22.961 27.756 30.799 11.634 7.84 25.015 12.702 38.969 14.161 13.952 1.457 28.050-0.534 41.053-5.797s24.515-13.641 33.524-24.393c9.009-10.754 15.24-23.556 18.146-37.28 2.905-13.726 2.395-27.953-1.485-41.435s-11.010-25.803-20.766-35.885l98.987-210.261h308.777c31.616 0 57.259-25.6 57.259-57.259v-99.541c0-7.639-3.035-14.964-8.436-20.366-5.4-5.4-12.725-8.434-20.364-8.434h-540.459c-7.639 0-14.962 3.035-20.364 8.434-5.402 5.402-8.436 12.727-8.436 20.366v99.541c0 31.616 25.643 57.259 57.259 57.259h111.061l-86.953 184.745c-12.008-1.396-24.174-0.236-35.7 3.405-11.528 3.641-22.154 9.679-31.181 17.719-9.027 8.038-16.25 17.896-21.195 28.928-4.947 11.030-7.501 22.981-7.499 35.068zM301.367 666.47c-7.452 0-14.598-2.96-19.868-8.229-5.268-5.27-8.229-12.416-8.229-19.868s2.962-14.598 8.229-19.866c5.27-5.27 12.416-8.229 19.868-8.229s14.598 2.96 19.868 8.229c5.268 5.268 8.227 12.414 8.227 19.866s-2.96 14.598-8.227 19.868c-5.27 5.27-12.416 8.229-19.868 8.229zM443.106 310.886h-155.607v-70.4h482.859v70.4h-327.253z" /> | <glyph unicode="" glyph-name="h2" horiz-adv-x="1044" d="M671.059 752.461c-7.629 0.006-14.948-3.017-20.352-8.405l-76.928-76.928h-215.593c-7.639 0-14.964-3.035-20.366-8.436-5.402-5.4-8.434-12.725-8.434-20.364s3.033-14.964 8.434-20.364c5.402-5.402 12.727-8.436 20.366-8.436h215.593l76.928-76.885c5.396-5.404 12.717-8.442 20.352-8.448h113.792c7.639 0 14.964 3.035 20.366 8.434 5.4 5.402 8.434 12.727 8.434 20.366 0 7.637-3.035 14.962-8.434 20.364-5.402 5.402-12.727 8.436-20.366 8.436h-101.845l-56.533 56.533 56.533 56.533h101.845c7.639 0 14.964 3.035 20.366 8.436 5.4 5.402 8.434 12.727 8.434 20.364 0 7.639-3.035 14.964-8.434 20.366-5.402 5.402-12.727 8.434-20.366 8.434h-113.792zM215.692 638.352c0.006 14.029 3.454 27.843 10.045 40.225 6.591 12.384 16.122 22.961 27.756 30.799 11.634 7.84 25.015 12.702 38.969 14.161 13.952 1.457 28.050-0.534 41.053-5.797s24.515-13.641 33.524-24.393c9.009-10.754 15.24-23.556 18.146-37.28 2.905-13.726 2.395-27.953-1.485-41.435s-11.010-25.803-20.766-35.885l98.987-210.261h308.777c31.616 0 57.259-25.6 57.259-57.259v-99.541c0-7.639-3.035-14.964-8.436-20.366-5.4-5.4-12.725-8.434-20.364-8.434h-540.459c-7.639 0-14.962 3.035-20.364 8.434-5.402 5.402-8.436 12.727-8.436 20.366v99.541c0 31.616 25.643 57.259 57.259 57.259h111.061l-86.953 184.745c-12.008-1.396-24.174-0.236-35.7 3.405-11.528 3.641-22.154 9.679-31.181 17.719-9.027 8.038-16.25 17.896-21.195 28.928-4.947 11.030-7.501 22.981-7.499 35.068zM301.367 666.47c-7.452 0-14.598-2.96-19.868-8.229-5.268-5.27-8.229-12.416-8.229-19.868s2.962-14.598 8.229-19.866c5.27-5.27 12.416-8.229 19.868-8.229s14.598 2.96 19.868 8.229c5.268 5.268 8.227 12.414 8.227 19.866s-2.96 14.598-8.227 19.868c-5.27 5.27-12.416 8.229-19.868 8.229zM443.106 310.886h-155.607v-70.4h482.859v70.4h-327.253z" /> | ||||
<glyph unicode="" glyph-name="h3" horiz-adv-x="1044" d="M506.72 812.567c4.59 2.701 9.821 4.126 15.145 4.126 5.327 0 10.557-1.425 15.147-4.126l288-169.386c4.488-2.641 8.206-6.408 10.787-10.927 2.582-4.521 3.938-9.637 3.933-14.844v-338.816c0.006-5.205-1.351-10.323-3.933-14.842-2.582-4.521-6.3-8.288-10.787-10.929l-288-169.385c-4.59-2.702-9.821-4.126-15.147-4.126s-10.555 1.424-15.145 4.126l-288 169.385c-4.488 2.641-8.206 6.408-10.787 10.929-2.582 4.519-3.938 9.637-3.933 14.842v338.773c-0.006 5.207 1.351 10.323 3.933 14.844s6.3 8.287 10.787 10.927l288 169.43zM263.733 600.3v-304.64l258.133-151.895 258.135 151.851v304.683l-258.135 151.893-258.133-151.893zM657.347 579.499c7.92 0 15.518-3.147 21.118-8.747 5.602-5.6 8.749-13.198 8.749-21.118v-203.264c0-3.923-0.772-7.808-2.272-11.431-1.501-3.625-3.7-6.918-6.475-9.693-2.773-2.773-6.065-4.974-9.689-6.475-3.625-1.501-7.509-2.274-11.431-2.274s-7.806 0.774-11.431 2.274c-3.623 1.501-6.916 3.702-9.689 6.475-2.775 2.775-4.974 6.067-6.475 9.693-1.501 3.623-2.272 7.509-2.272 11.431v203.264c0 7.92 3.147 15.518 8.747 21.118 5.602 5.6 13.2 8.747 21.12 8.747zM521.838 511.746c7.92 0 15.518-3.147 21.118-8.749 5.6-5.6 8.747-13.198 8.747-21.118v-135.509c0-7.922-3.147-15.518-8.747-21.12-5.6-5.6-13.198-8.747-21.118-8.747-7.922 0-15.52 3.147-21.12 8.747-5.6 5.602-8.747 13.198-8.747 21.12v135.509c0 7.92 3.147 15.518 8.747 21.118 5.6 5.602 13.198 8.749 21.12 8.749zM416.195 414.123c-0.002 7.92-3.149 15.516-8.749 21.116s-13.198 8.745-21.118 8.745c-7.92 0-15.516-3.145-21.116-8.745s-8.749-13.196-8.749-21.116v-67.753c-0.002-3.923 0.77-7.808 2.271-11.431 1.501-3.625 3.7-6.918 6.475-9.693 2.773-2.773 6.065-4.974 9.691-6.475 3.623-1.501 7.507-2.274 11.429-2.274s7.808 0.774 11.431 2.274c3.623 1.501 6.916 3.702 9.691 6.475 2.773 2.775 4.972 6.067 6.473 9.693 1.501 3.623 2.272 7.509 2.272 11.431v67.753z" /> | <glyph unicode="" glyph-name="h3" horiz-adv-x="1044" d="M506.72 812.567c4.59 2.701 9.821 4.126 15.145 4.126 5.327 0 10.557-1.425 15.147-4.126l288-169.386c4.488-2.641 8.206-6.408 10.787-10.927 2.582-4.521 3.938-9.637 3.933-14.844v-338.816c0.006-5.205-1.351-10.323-3.933-14.842-2.582-4.521-6.3-8.288-10.787-10.929l-288-169.385c-4.59-2.702-9.821-4.126-15.147-4.126s-10.555 1.424-15.145 4.126l-288 169.385c-4.488 2.641-8.206 6.408-10.787 10.929-2.582 4.519-3.938 9.637-3.933 14.842v338.773c-0.006 5.207 1.351 10.323 3.933 14.844s6.3 8.287 10.787 10.927l288 169.43zM263.733 600.3v-304.64l258.133-151.895 258.135 151.851v304.683l-258.135 151.893-258.133-151.893zM657.347 579.499c7.92 0 15.518-3.147 21.118-8.747 5.602-5.6 8.749-13.198 8.749-21.118v-203.264c0-3.923-0.772-7.808-2.272-11.431-1.501-3.625-3.7-6.918-6.475-9.693-2.773-2.773-6.065-4.974-9.689-6.475-3.625-1.501-7.509-2.274-11.431-2.274s-7.806 0.774-11.431 2.274c-3.623 1.501-6.916 3.702-9.689 6.475-2.775 2.775-4.974 6.067-6.475 9.693-1.501 3.623-2.272 7.509-2.272 11.431v203.264c0 7.92 3.147 15.518 8.747 21.118 5.602 5.6 13.2 8.747 21.12 8.747zM521.838 511.746c7.92 0 15.518-3.147 21.118-8.749 5.6-5.6 8.747-13.198 8.747-21.118v-135.509c0-7.922-3.147-15.518-8.747-21.12-5.6-5.6-13.198-8.747-21.118-8.747-7.922 0-15.52 3.147-21.12 8.747-5.6 5.602-8.747 13.198-8.747 21.12v135.509c0 7.92 3.147 15.518 8.747 21.118 5.6 5.602 13.198 8.749 21.12 8.749zM416.195 414.123c-0.002 7.92-3.149 15.516-8.749 21.116s-13.198 8.745-21.118 8.745c-7.92 0-15.516-3.145-21.116-8.745s-8.749-13.196-8.749-21.116v-67.753c-0.002-3.923 0.77-7.808 2.271-11.431 1.501-3.625 3.7-6.918 6.475-9.693 2.773-2.773 6.065-4.974 9.691-6.475 3.623-1.501 7.507-2.274 11.429-2.274s7.808 0.774 11.431 2.274c3.623 1.501 6.916 3.702 9.691 6.475 2.773 2.775 4.972 6.067 6.473 9.693 1.501 3.623 2.272 7.509 2.272 11.431v67.753z" /> | ||||
<glyph unicode="" glyph-name="arrow-left" horiz-adv-x="1182" d="M1113.038 501.613h-946.074l362.27 344.949c18.969 18.063 19.704 48.073 1.637 67.037-18.044 18.94-48.063 19.699-67.056 1.636l-413.72-393.971c-17.904-17.92-27.79-41.72-27.79-67.061 0-25.316 9.886-49.139 28.625-67.842l412.909-393.167c9.176-8.743 20.937-13.084 32.698-13.084 12.52 0 25.039 4.931 34.358 14.714 18.068 18.968 17.333 48.955-1.636 67.017l-363.782 344.953h947.563c26.178 0 47.423 21.239 47.423 47.409s-21.246 47.41-47.423 47.41z" /> | <glyph unicode="" glyph-name="arrow-left" horiz-adv-x="1182" d="M1113.038 501.613h-946.074l362.27 344.949c18.969 18.063 19.704 48.073 1.637 67.037-18.044 18.94-48.063 19.699-67.056 1.636l-413.72-393.971c-17.904-17.92-27.79-41.72-27.79-67.061 0-25.316 9.886-49.139 28.625-67.842l412.909-393.167c9.176-8.743 20.937-13.084 32.698-13.084 12.52 0 25.039 4.931 34.358 14.714 18.068 18.968 17.333 48.955-1.636 67.017l-363.782 344.953h947.563c26.178 0 47.423 21.239 47.423 47.409s-21.246 47.41-47.423 47.41z" /> | ||||
<glyph unicode="" glyph-name="star" d="M497.388 957.076c12.138-3.498 23.19-10.018 32.122-18.95s15.452-19.984 18.95-32.122l78.4-272.533 272.536-78.4c15.565-4.481 29.261-13.903 38.997-26.848 9.745-12.943 15.019-28.705 15.019-44.908s-5.274-31.963-15.019-44.907c-9.737-12.944-23.433-22.368-38.997-26.848l-272.536-78.4-78.4-272.532c-4.481-15.573-13.903-29.261-26.848-39.006s-28.705-15.010-44.908-15.010c-16.201 0-31.963 5.265-44.907 15.010s-22.367 23.433-26.848 39.006l-78.4 272.532-272.533 78.4c-15.57 4.48-29.26 13.903-39.004 26.848s-15.014 28.705-15.014 44.907c0 16.202 5.27 31.964 15.014 44.908s23.434 22.367 39.004 26.848l272.533 78.4 78.4 272.533c2.715 9.424 7.26 18.22 13.375 25.887s13.681 14.054 22.264 18.797c8.584 4.743 18.018 7.748 27.763 8.844s19.612 0.262 29.035-2.456zM476.631 831.487l-63.989-222.731-13.74-47.562-270.442-77.952 270.442-77.728 13.74-47.562 63.989-222.805 64.138 222.805 13.739 47.562 47.563 13.739 222.805 64.064-270.368 77.803-13.739 47.563-64.138 222.805zM476.631 831.487l-63.989-222.731-13.74-47.562-270.442-77.952 270.442-77.728 13.74-47.562 63.989-222.805 64.138 222.805 13.739 47.562 47.563 13.739 222.805 64.064-270.368 77.803-13.739 47.563-64.138 222.805z" /> | |||||
<glyph unicode="" glyph-name="enter" d="M810.667 704c-23.564 0-42.667-19.103-42.667-42.667v-170.667c0-23.564-19.103-42.667-42.667-42.667v0h-409.173l55.467 55.040c7.753 7.753 12.548 18.463 12.548 30.293 0 23.661-19.181 42.841-42.841 42.841-11.83 0-22.541-4.795-30.293-12.548v0l-128-128c-3.773-3.937-6.808-8.616-8.859-13.791l-0.101-0.289c-2.024-4.793-3.201-10.366-3.201-16.213s1.176-11.42 3.305-16.495l-0.105 0.281c2.152-5.464 5.187-10.143 8.974-14.095l-0.014 0.015 128-128c7.733-7.795 18.45-12.621 30.293-12.621s22.56 4.826 30.291 12.618l0.003 0.003c7.795 7.733 12.621 18.45 12.621 30.293s-4.826 22.56-12.618 30.291l-0.003 0.003-55.467 55.040h409.173c70.692 0 128 57.308 128 128v0 170.667c0 23.564-19.103 42.667-42.667 42.667v0z" /> | |||||
</font></defs></svg> | </font></defs></svg> |
@font-face { | @font-face { | ||||
font-family: 'icomoon'; | font-family: 'icomoon'; | ||||
src: url('fonts/icomoon.eot?ezztx0'); | |||||
src: url('fonts/icomoon.eot?ezztx0#iefix') format('embedded-opentype'), | |||||
url('fonts/icomoon.ttf?ezztx0') format('truetype'), | |||||
url('fonts/icomoon.woff?ezztx0') format('woff'), | |||||
url('fonts/icomoon.svg?ezztx0#icomoon') format('svg'); | |||||
src: url('fonts/icomoon.eot?7g9lqp'); | |||||
src: url('fonts/icomoon.eot?7g9lqp#iefix') format('embedded-opentype'), | |||||
url('fonts/icomoon.ttf?7g9lqp') format('truetype'), | |||||
url('fonts/icomoon.woff?7g9lqp') format('woff'), | |||||
url('fonts/icomoon.svg?7g9lqp#icomoon') format('svg'); | |||||
font-weight: normal; | font-weight: normal; | ||||
font-style: normal; | font-style: normal; | ||||
font-display: block; | font-display: block; | ||||
-moz-osx-font-smoothing: grayscale; | -moz-osx-font-smoothing: grayscale; | ||||
} | } | ||||
.icon-enter:before { | |||||
content: "\e909"; | |||||
} | |||||
.icon-star:before { | |||||
content: "\e908"; | |||||
} | |||||
.icon-h1:before { | .icon-h1:before { | ||||
content: "\e904"; | content: "\e904"; | ||||
} | } |
<template> | <template> | ||||
<div class="relative inline-block text-left " ref="dropdownContainerRef"> | |||||
<div class="relative inline-block text-left" ref="dropdownContainerRef" @mouseleave="handleMouseLeave"> | |||||
<div | <div | ||||
@click="toggleDropdown" | |||||
class="flex justify-center items-center gap-2 text-white opacity-80 text-sm" | |||||
@mouseenter="handleMouseEnter" | |||||
class="flex items-center gap-1 text-white opacity-80 text-sm hover:opacity-100 cursor-pointer py-2 transition-opacity" | |||||
> | > | ||||
<i class="icon-i18n"></i> | |||||
{{ currentLocaleName || "Language" }} | |||||
<i class="icon-i18n mr-1"></i> | |||||
<span>{{ currentLocaleName || "Language" }}</span> | |||||
<svg | |||||
class="h-3 w-3 text-white/60 transition-transform duration-200" | |||||
:class="{'rotate-180': isDropdownOpen}" | |||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" | |||||
> | |||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M19 9l-7 7-7-7" /> | |||||
</svg> | |||||
</div> | </div> | ||||
<transition | |||||
enter-active-class="transition ease-out duration-100" | |||||
enter-from-class="transform opacity-0 scale-95" | |||||
enter-to-class="transform opacity-100 scale-100" | |||||
leave-active-class="transition ease-in duration-75" | |||||
leave-from-class="transform opacity-100 scale-100" | |||||
leave-to-class="transform opacity-0 scale-95" | |||||
> | |||||
<transition name="fade-down"> | |||||
<div | <div | ||||
v-if="isDropdownOpen" | v-if="isDropdownOpen" | ||||
class="origin-top-right absolute right-0 mt-2 w-36 rounded-md shadow-lg bg-[var(--color-bg)] ring-1 ring-black ring-opacity-5 focus:outline-none z-10" | |||||
role="menu" | |||||
aria-orientation="vertical" | |||||
aria-labelledby="options-menu" | |||||
@mouseenter="handleMouseEnter" | |||||
class="absolute right-0 top-full mt-1 w-max min-w-[120px] bg-slate-800/90 backdrop-blur-md rounded-lg shadow-xl p-2 z-10" | |||||
> | > | ||||
<div class="py-1" role="none"> | |||||
<a | |||||
v-for="locale in availableLocales" | |||||
:key="locale.code" | |||||
href="#" | |||||
@click.prevent="selectLanguage(locale.code)" | |||||
:class="[ | |||||
'block px-4 py-2 text-sm opacity-80', | |||||
currentLocale === locale.code | |||||
? 'text-white opacity-100 font-bold' | |||||
: 'text-white hover: opacity-100', | |||||
]" | |||||
role="menuitem" | |||||
> | |||||
{{ locale.name }} | |||||
</a> | |||||
</div> | |||||
<ul class="space-y-1"> | |||||
<li v-for="locale in availableLocales" :key="locale.code"> | |||||
<button | |||||
@click="selectLanguage(locale.code)" | |||||
class="block w-full text-left text-base text-gray-200 hover:text-white hover:bg-white/10 transition-all duration-150 rounded px-3 py-1.5" | |||||
:class="[ locale.code === currentLocale ? 'font-bold opacity-100 bg-white/15' : '' ]" | |||||
> | |||||
{{ locale.name }} | |||||
</button> | |||||
</li> | |||||
</ul> | |||||
</div> | </div> | ||||
</transition> | </transition> | ||||
</div> | </div> | ||||
*/ | */ | ||||
import { ref, computed } from "vue"; | import { ref, computed } from "vue"; | ||||
import { useI18n } from "#imports"; // 修正 useI18n 导入 | import { useI18n } from "#imports"; // 修正 useI18n 导入 | ||||
import { onClickOutside } from "@vueuse/core"; // 导入 onClickOutside | |||||
// 定义语言代码的类型,应该与 i18n 配置中的一致 | // 定义语言代码的类型,应该与 i18n 配置中的一致 | ||||
type LocaleCode = "zh" | "en" | "ja"; // 你需要根据你的 i18n 配置更新这个类型 | type LocaleCode = "zh" | "en" | "ja"; // 你需要根据你的 i18n 配置更新这个类型 | ||||
const { locale, locales, setLocale } = useI18n(); | const { locale, locales, setLocale } = useI18n(); | ||||
const currentLocale = computed(() => locale.value); | const currentLocale = computed(() => locale.value); | ||||
const isDropdownOpen = ref(false); | const isDropdownOpen = ref(false); | ||||
const dropdownContainerRef = ref(null); // 创建 ref | |||||
const dropdownContainerRef = ref(null); // 保留 ref,虽然 onClickOutside 移除了,但未来可能有用 | |||||
let leaveTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for mouseleave delay | |||||
// 可用语言列表 | // 可用语言列表 | ||||
const availableLocales = computed(() => { | const availableLocales = computed(() => { | ||||
// 当前选中语言的名称 | // 当前选中语言的名称 | ||||
const currentLocaleName = computed(() => { | const currentLocaleName = computed(() => { | ||||
const current = availableLocales.value.find( | |||||
(l) => l.code === currentLocale.value | |||||
const current = availableLocales.value.find((l: { code: string; name: string }) => | |||||
l.code === locale.value | |||||
); | ); | ||||
return current ? current.name : ""; | return current ? current.name : ""; | ||||
}); | }); | ||||
/** | |||||
* 切换下拉菜单的显示/隐藏状态 | |||||
*/ | |||||
function toggleDropdown() { | |||||
isDropdownOpen.value = !isDropdownOpen.value; | |||||
} | |||||
/** | /** | ||||
* 选择语言并关闭下拉菜单 | * 选择语言并关闭下拉菜单 | ||||
* @param {string} langCode - 选择的语言代码 | * @param {string} langCode - 选择的语言代码 | ||||
// 这里可以添加用户反馈,例如显示一个错误提示 | // 这里可以添加用户反馈,例如显示一个错误提示 | ||||
} | } | ||||
} | } | ||||
isDropdownOpen.value = false; // 关闭下拉菜单 | |||||
handleMouseLeave(); // 使用 handleMouseLeave 关闭 | |||||
} | } | ||||
// 点击外部关闭下拉菜单 | |||||
onClickOutside(dropdownContainerRef, () => { | |||||
if (isDropdownOpen.value) { | |||||
isDropdownOpen.value = false; | |||||
// --- Dropdown Logic (like Products dropdown) --- | |||||
function handleMouseEnter() { | |||||
if (leaveTimeout) { | |||||
clearTimeout(leaveTimeout); | |||||
leaveTimeout = null; | |||||
} | } | ||||
}); | |||||
isDropdownOpen.value = true; | |||||
} | |||||
function handleMouseLeave() { | |||||
// Delay closing the dropdown slightly | |||||
leaveTimeout = setTimeout(() => { | |||||
isDropdownOpen.value = false; | |||||
}, 150); // 150ms delay | |||||
} | |||||
// --- End Dropdown Logic --- | |||||
</script> | </script> |
> | > | ||||
<!-- Logo & 描述 --> | <!-- Logo & 描述 --> | ||||
<div | <div | ||||
class="flex flex-col lg:items-start :lg:justify-start sm:items-center text-center lg:text-left" | |||||
class="flex flex-col lg:items-start :lg:justify-start sm:items-center text-center lg:text-left col-span-2 lg:col-span-1" | |||||
> | > | ||||
<h3 class="mb-4"> | <h3 class="mb-4"> | ||||
<i class="icon-brand text-white text-2xl"></i> | <i class="icon-brand text-white text-2xl"></i> |
<template> | <template> | ||||
<!-- Header --> | |||||
<header class="fixed top-0 z-50 w-full bg-slate-900/70 backdrop-blur-[50px]"> | <header class="fixed top-0 z-50 w-full bg-slate-900/70 backdrop-blur-[50px]"> | ||||
<div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10"> | <div class="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-10"> | ||||
<div class="h-[55px] flex justify-between items-center sm:h-[72px]"> | <div class="h-[55px] flex justify-between items-center sm:h-[72px]"> | ||||
<div class="flex justify-start items-center gap-12 lg:gap-24"> | <div class="flex justify-start items-center gap-12 lg:gap-24"> | ||||
<nuxt-link :to="homePath" class="mt-[5px] flex-shrink-0"> | |||||
<i class="icon-brand text-white text-1xl sm:text-2xl"></i> | |||||
<nuxt-link :to="homePath" class="brand-link mt-[5px] flex-shrink-0"> | |||||
<i | |||||
class="icon-brand text-white text-1xl sm:text-2xl block transition-[transform,filter] duration-500 ease-in-out" | |||||
></i> | |||||
</nuxt-link> | </nuxt-link> | ||||
<!-- Desktop Menu --> | <!-- Desktop Menu --> | ||||
<nav class="hidden md:flex justify-start items-start gap-7 lg:gap-14"> | <nav class="hidden md:flex justify-start items-start gap-7 lg:gap-14"> | ||||
<nuxt-link | |||||
v-for="item in menuItems" | |||||
:key="item.path" | |||||
class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity" | |||||
:to="item.path" | |||||
:class="{ | |||||
'text-white font-bold opacity-100': $route.path === item.path, | |||||
}" | |||||
> | |||||
{{ $t(item.label) }} | |||||
</nuxt-link> | |||||
<template v-for="item in menuItems" :key="item.label"> | |||||
<!-- Regular Link --> | |||||
<nuxt-link | |||||
v-if="!item.isDropdown" | |||||
class="main-nav-link relative inline-block justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity py-2 px-3 rounded-md" | |||||
:to="item.path" | |||||
:class="[ | |||||
$route.path === item.path | |||||
? 'font-bold opacity-100 bg-white/15' | |||||
: '', | |||||
]" | |||||
> | |||||
{{ $t(item.label) }} | |||||
</nuxt-link> | |||||
<!-- Dropdown Container --> | |||||
<div | |||||
v-else-if="item.isDropdown" | |||||
class="relative" | |||||
@mouseleave="handleMouseLeave" | |||||
> | |||||
<!-- Dropdown Trigger --> | |||||
<div | |||||
@mouseenter="handleMouseEnter(item.label)" | |||||
class="justify-start text-white text-sm opacity-80 hover:opacity-100 transition-opacity cursor-pointer flex items-center gap-1 py-2" | |||||
:class="{ | |||||
'text-white font-bold opacity-100': | |||||
openDropdown === item.label || | |||||
$route.path.startsWith(item.pathPrefix), | |||||
}" | |||||
> | |||||
<span>{{ $t(item.label) }}</span> | |||||
<!-- Dropdown Arrow --> | |||||
<svg | |||||
class="h-3 w-3 text-white/60" | |||||
fill="none" | |||||
viewBox="0 0 24 24" | |||||
stroke="currentColor" | |||||
> | |||||
<path | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
stroke-width="3" | |||||
d="M19 9l-7 7-7-7" | |||||
/> | |||||
</svg> | |||||
</div> | |||||
<!-- Dropdown Panel --> | |||||
<transition name="fade-down"> | |||||
<div | |||||
v-if="item.isDropdown && openDropdown === item.label" | |||||
@mouseenter="handleMouseEnter(item.label)" | |||||
class="absolute left-0 top-full mt-1 w-max min-w-[450px] bg-slate-800/90 backdrop-blur-md rounded-lg shadow-xl p-4 z-10 grid grid-cols-2 gap-4" | |||||
> | |||||
<div | |||||
v-for="(section, index) in item.children" | |||||
:key="index" | |||||
class="bg-black/10 p-4 rounded-md" | |||||
> | |||||
<h3 | |||||
class="text-sm font-semibold text-gray-300 uppercase tracking-wider mb-4 flex items-center gap-2" | |||||
> | |||||
<i | |||||
:class="[ | |||||
index === 0 ? 'icon-tag' : 'icon-target', | |||||
'text-gray-400', | |||||
]" | |||||
></i> | |||||
<span>{{ $t(section.title) }}</span> | |||||
</h3> | |||||
<ul class="space-y-3"> | |||||
<li v-for="link in section.items" :key="link.path"> | |||||
<nuxt-link | |||||
:to="link.path" | |||||
@click="handleMouseLeave" | |||||
class="block text-base text-gray-200 hover:text-white hover:bg-white/10 transition-all duration-150 rounded px-2 py-1" | |||||
:class="{ | |||||
'text-white font-bold bg-white/5': | |||||
$route.path === link.path, | |||||
}" | |||||
> | |||||
{{ $t(link.label) }} | |||||
</nuxt-link> | |||||
</li> | |||||
</ul> | |||||
</div> | |||||
</div> | |||||
</transition> | |||||
</div> | |||||
</template> | |||||
</nav> | </nav> | ||||
</div> | </div> | ||||
<div class="flex justify-start items-center gap-4 md:gap-6"> | <div class="flex justify-start items-center gap-4 md:gap-6"> | ||||
<!-- Search --> | <!-- Search --> | ||||
<div | <div | ||||
class="w-auto h-8 relative items-center opacity-40 rounded-2xl border border-color-[rgba(255,255,255,0.4)] pr-4 hover:opacity-100 transition-opacity duration-300 hidden md:flex" | |||||
@click="openSearch" | |||||
class="w-auto h-8 relative items-center opacity-40 rounded-2xl pr-4 hover:opacity-100 transition-opacity duration-300 hidden md:flex cursor-pointer" | |||||
style="border: 0.5px solid rgba(255, 255, 255, 0.4)" | |||||
> | > | ||||
<button | |||||
<span | |||||
class="flex items-center justify-center w-8 h-8 opacity-80 hover:opacity-100 text-white" | class="flex items-center justify-center w-8 h-8 opacity-80 hover:opacity-100 text-white" | ||||
> | > | ||||
<i class="icon-search text-sm"></i> | <i class="icon-search text-sm"></i> | ||||
</button> | |||||
</span> | |||||
<span | <span | ||||
class="hidden lg:inline-block ml-1 text-white text-sm opacity-80" | class="hidden lg:inline-block ml-1 text-white text-sm opacity-80" | ||||
> | > | ||||
id="mobile-menu" | id="mobile-menu" | ||||
> | > | ||||
<div class="px-2 pt-2 pb-3 space-y-1 sm:px-3"> | <div class="px-2 pt-2 pb-3 space-y-1 sm:px-3"> | ||||
<nuxt-link | |||||
v-for="item in menuItems" | |||||
:key="item.path" | |||||
:to="item.path" | |||||
@click="closeMobileMenu" | |||||
class="block px-3 py-2 rounded-md text-base font-medium" | |||||
:class="[ | |||||
$route.path === item.path | |||||
? 'bg-gray-900 text-white' | |||||
: 'text-gray-300 hover:bg-gray-700 hover:text-white', | |||||
]" | |||||
>{{ $t(item.label) }}</nuxt-link | |||||
> | |||||
<template v-for="item in menuItems" :key="item.label"> | |||||
<!-- Mobile Regular Link --> | |||||
<nuxt-link | |||||
v-if="!item.isDropdown" | |||||
:to="item.path" | |||||
@click="closeMobileMenu" | |||||
class="block px-3 py-2 rounded-md text-base font-medium" | |||||
:class="[ | |||||
$route.path === item.path | |||||
? 'bg-gray-900 text-white' | |||||
: 'text-gray-300 hover:bg-gray-700 hover:text-white', | |||||
]" | |||||
> | |||||
{{ $t(item.label) }} | |||||
</nuxt-link> | |||||
<!-- Mobile Dropdown Section --> | |||||
<div v-else class="mt-2"> | |||||
<h3 | |||||
class="px-3 pt-2 pb-1 text-sm font-semibold text-gray-400 uppercase tracking-wider" | |||||
> | |||||
{{ $t(item.label) }} | |||||
</h3> | |||||
<div | |||||
v-for="section in item.children" | |||||
:key="section.title" | |||||
class="mt-1" | |||||
> | |||||
<!-- Optional: Section title for mobile? --> | |||||
<!-- <h4 class="px-3 pt-1 text-xs font-medium text-gray-500">{{ $t(section.title) }}</h4> --> | |||||
<nuxt-link | |||||
v-for="link in section.items" | |||||
:key="link.path" | |||||
:to="link.path" | |||||
@click="closeMobileMenu" | |||||
class="block pl-6 pr-3 py-2 rounded-md text-base font-medium text-gray-300 hover:bg-gray-700 hover:text-white" | |||||
:class="{ | |||||
'bg-gray-900 text-white': $route.path === link.path, | |||||
}" | |||||
> | |||||
{{ $t(link.label) }} | |||||
</nuxt-link> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</transition> | </transition> | ||||
</header> | </header> | ||||
<!-- Search Layer (Moved outside header) --> | |||||
<transition name="search-fade-scale"> | |||||
<div | |||||
v-if="isSearchOpen" | |||||
class="fixed inset-0 z-[60] bg-black/80 backdrop-blur-md flex items-start justify-center pt-20" | |||||
@click.self="closeSearch" | |||||
> | |||||
<div | |||||
class="bg-gradient-to-br from-slate-800 to-slate-900 p-8 rounded-lg relative w-full max-w-2xl mx-4 search-modal-content shadow-xl" | |||||
> | |||||
<button | |||||
@click="closeSearch" | |||||
class="absolute top-3 right-3 text-gray-500 hover:text-white hover:bg-white/10 rounded-full p-2 transition-colors" | |||||
> | |||||
<svg | |||||
class="h-5 w-5" | |||||
fill="none" | |||||
viewBox="0 0 24 24" | |||||
stroke="currentColor" | |||||
> | |||||
<path | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
stroke-width="2" | |||||
d="M6 18L18 6M6 6l12 12" | |||||
/> | |||||
</svg> | |||||
</button> | |||||
<h2 class="text-white text-xl mb-6">{{ $t("common.search") }}</h2> | |||||
<!-- Input with Icon --> | |||||
<div class="relative mb-6"> | |||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3"> | |||||
<i class="icon-search text-gray-400 text-sm"></i> | |||||
</span> | |||||
<input | |||||
ref="searchInputRef" | |||||
type="text" | |||||
:placeholder=" | |||||
$t('common.searchPlaceholder') || 'Enter search term...' | |||||
" | |||||
class="w-full p-3 pl-10 pr-10 rounded bg-slate-700 text-white border border-slate-600 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500" | |||||
/> | |||||
<!-- Enter Icon --> | |||||
<span | |||||
class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer group" | |||||
> | |||||
<i | |||||
class="icon-enter text-gray-400 group-hover:text-blue-400 transition-colors" | |||||
></i> | |||||
</span> | |||||
</div> | |||||
<!-- Search results could go here --> | |||||
<!-- Hot Keywords Section --> | |||||
<div class="mt-6"> | |||||
<h3 class="text-gray-400 text-sm mb-3"> | |||||
{{ $t("common.hotKeywords") || "热门搜索" }} | |||||
</h3> | |||||
<div class="flex flex-wrap gap-3"> | |||||
<button | |||||
v-for="keyword in hotKeywords" | |||||
:key="keyword" | |||||
@click="searchHotKeyword(keyword)" | |||||
class="px-4 py-1.5 bg-slate-700 text-white/80 rounded-full text-sm hover:bg-blue-600 hover:text-white transition-colors duration-200" | |||||
> | |||||
{{ keyword }} | |||||
</button> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</transition> | |||||
</template> | </template> | ||||
<script setup lang="ts"> | <script setup lang="ts"> | ||||
import { ref, computed } from "vue"; | |||||
import { ref, computed, watch, nextTick } from "#imports"; | |||||
import { useI18n } from "vue-i18n"; | import { useI18n } from "vue-i18n"; | ||||
import { useRuntimeConfig } from '#app'; // 导入 useRuntimeConfig | |||||
/** | /** | ||||
* 页面头部组件 | * 页面头部组件 | ||||
const { t, locale } = useI18n(); | const { t, locale } = useI18n(); | ||||
const config = useRuntimeConfig(); | const config = useRuntimeConfig(); | ||||
// 从运行时配置获取默认语言,如果未配置则默认为 'en' | // 从运行时配置获取默认语言,如果未配置则默认为 'en' | ||||
const defaultLocale = config.public.i18n?.defaultLocale || 'en'; | |||||
const defaultLocale = config.public.i18n?.defaultLocale || "en"; | |||||
const mobileMenuOpen = ref(false); | const mobileMenuOpen = ref(false); | ||||
const isSearchOpen = ref(false); | |||||
const searchInputRef = ref<HTMLInputElement | null>(null); | |||||
const openDropdown = ref<string | null>(null); | |||||
let leaveTimeout: ReturnType<typeof setTimeout> | null = null; // Timeout for mouseleave delay | |||||
// 添加热门关键字 | |||||
const hotKeywords = ref(["SSD", "SD", "DDR4"]); | |||||
// 使用 computed 来定义 homePath,根据是否为默认语言调整路径 | // 使用 computed 来定义 homePath,根据是否为默认语言调整路径 | ||||
const homePath = computed(() => { | const homePath = computed(() => { | ||||
// 如果是默认语言,路径为根路径 '/' | // 如果是默认语言,路径为根路径 '/' | ||||
// 否则,路径为 '/<locale>/' | // 否则,路径为 '/<locale>/' | ||||
return locale.value === defaultLocale ? '/' : `/${locale.value}/`; | |||||
return locale.value === defaultLocale ? "/" : `/${locale.value}/`; | |||||
}); | }); | ||||
// 使用 computed 来定义 menuItems,根据是否为默认语言调整路径 | // 使用 computed 来定义 menuItems,根据是否为默认语言调整路径 | ||||
const menuItems = computed(() => { | const menuItems = computed(() => { | ||||
// 判断当前是否为默认语言 | // 判断当前是否为默认语言 | ||||
const isDefaultLocale = locale.value === defaultLocale; | const isDefaultLocale = locale.value === defaultLocale; | ||||
// 如果是默认语言,路径前缀为空字符串,否则为 '/<locale>' | // 如果是默认语言,路径前缀为空字符串,否则为 '/<locale>' | ||||
const prefix = isDefaultLocale ? '' : `/${locale.value}`; | |||||
const prefix = isDefaultLocale ? "" : `/${locale.value}`; | |||||
return [ | return [ | ||||
// 首页路径特殊处理:默认语言为 '/', 其他语言为 '/<locale>/' | // 首页路径特殊处理:默认语言为 '/', 其他语言为 '/<locale>/' | ||||
{ label: "common.home", path: isDefaultLocale ? '/' : `${prefix}/` }, | |||||
{ label: "common.products", path: `${prefix}/products` }, | |||||
{ label: "common.home", path: isDefaultLocale ? "/" : `${prefix}/` }, | |||||
{ | |||||
label: "common.products", | |||||
isDropdown: true, | |||||
pathPrefix: `${prefix}/products`, | |||||
children: [ | |||||
{ | |||||
title: "common.productCategories", | |||||
items: [ | |||||
{ label: "SSD", path: `${prefix}/products?category=ssd` }, | |||||
{ label: "DRAM", path: `${prefix}/products?category=dram` }, | |||||
{ label: "NAND", path: `${prefix}/products?category=nand` }, | |||||
], | |||||
}, | |||||
{ | |||||
title: "common.byUsage", | |||||
items: [ | |||||
{ | |||||
label: "Enterprise", | |||||
path: `${prefix}/products?usage=enterprise`, | |||||
}, | |||||
{ label: "Consumer", path: `${prefix}/products?usage=consumer` }, | |||||
{ | |||||
label: "Industrial", | |||||
path: `${prefix}/products?usage=industrial`, | |||||
}, | |||||
], | |||||
}, | |||||
], | |||||
}, | |||||
{ label: "common.faq", path: `${prefix}/faq` }, | { label: "common.faq", path: `${prefix}/faq` }, | ||||
{ label: "common.about", path: `${prefix}/about` }, | { label: "common.about", path: `${prefix}/about` }, | ||||
{ label: "common.contact", path: `${prefix}/contact` }, | { label: "common.contact", path: `${prefix}/contact` }, | ||||
function closeMobileMenu() { | function closeMobileMenu() { | ||||
mobileMenuOpen.value = false; | mobileMenuOpen.value = false; | ||||
} | } | ||||
/** | |||||
* 打开搜索层 | |||||
*/ | |||||
function openSearch() { | |||||
isSearchOpen.value = true; | |||||
} | |||||
/** | |||||
* 关闭搜索层 | |||||
*/ | |||||
function closeSearch() { | |||||
isSearchOpen.value = false; | |||||
} | |||||
/** | |||||
* 搜索热门关键字 (示例) | |||||
* @param keyword | |||||
*/ | |||||
function searchHotKeyword(keyword: string) { | |||||
console.log("Searching for hot keyword:", keyword); | |||||
// 可以在这里实现填充输入框或直接执行搜索的逻辑 | |||||
// 例如: searchInputValue.value = keyword; | |||||
closeSearch(); // 点击后可以关闭搜索层 | |||||
} | |||||
// 监听搜索层状态,打开时自动聚焦输入框 | |||||
watch(isSearchOpen, (newValue: boolean) => { | |||||
if (newValue) { | |||||
nextTick(() => { | |||||
searchInputRef.value?.focus(); | |||||
}); | |||||
} | |||||
}); | |||||
// --- Dropdown Logic --- | |||||
function handleMouseEnter(label: string) { | |||||
if (leaveTimeout) { | |||||
clearTimeout(leaveTimeout); | |||||
leaveTimeout = null; | |||||
} | |||||
openDropdown.value = label; | |||||
} | |||||
function handleMouseLeave() { | |||||
// Delay closing the dropdown slightly | |||||
leaveTimeout = setTimeout(() => { | |||||
openDropdown.value = null; | |||||
}, 150); // 150ms delay | |||||
} | |||||
// --- End Dropdown Logic --- | |||||
</script> | </script> | ||||
<style lang="scss" scoped> | <style lang="scss" scoped> | ||||
user-select: none; | user-select: none; | ||||
} | } | ||||
/* Brand icon hover effect */ | |||||
.brand-link { | |||||
&:hover { | |||||
.icon-brand { | |||||
transform: scale(1.05); | |||||
filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.6)); | |||||
} | |||||
} | |||||
} | |||||
/* Base style for icon to apply will-change */ | |||||
.icon-brand { | |||||
will-change: transform, filter; /* Hint for browser optimization */ | |||||
} | |||||
/* Transition for mobile menu */ | /* Transition for mobile menu */ | ||||
.slide-fade-enter-active { | .slide-fade-enter-active { | ||||
transition: all 0.3s ease-out; | transition: all 0.3s ease-out; | ||||
opacity: 0; | opacity: 0; | ||||
} | } | ||||
/* Transition for dropdown */ | |||||
.fade-down-enter-active, | |||||
.fade-down-leave-active { | |||||
transition: all 0.2s ease-out; | |||||
} | |||||
.fade-down-enter-from, | |||||
.fade-down-leave-to { | |||||
opacity: 0; | |||||
transform: translateY(-10px); | |||||
} | |||||
/* Transition for search overlay */ | |||||
.search-fade-scale-enter-active, | |||||
.search-fade-scale-leave-active { | |||||
transition: opacity 0.3s ease-out; | |||||
} | |||||
.search-fade-scale-enter-from, | |||||
.search-fade-scale-leave-to { | |||||
opacity: 0; | |||||
} | |||||
/* 为搜索框内容添加独立的、稍延迟的动画 */ | |||||
.search-modal-content { | |||||
transition: transform 0.3s ease-out 0.05s; | |||||
} | |||||
.search-fade-scale-enter-from .search-modal-content, | |||||
.search-fade-scale-leave-to .search-modal-content { | |||||
transform: scale(0.95) translateY(10px); | |||||
} | |||||
/* Keep the sticky header consistent */ | /* Keep the sticky header consistent */ | ||||
.sticky { | .sticky { | ||||
position: sticky; | position: sticky; |
import { ref, computed } from 'vue'; | |||||
/** | |||||
* 生成随机字符串 | |||||
* @param length 字符串长度 | |||||
* @returns 随机字符串 | |||||
*/ | |||||
function generateRandomString(length: number): string { | |||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |||||
let result = ''; | |||||
for (let i = 0; i < length; i++) { | |||||
result += characters.charAt(Math.floor(Math.random() * characters.length)); | |||||
} | |||||
return result; | |||||
} | |||||
/** | |||||
* 为验证码文本生成扭曲的 SVG | |||||
* @param text 验证码文本 | |||||
* @returns SVG 字符串 | |||||
*/ | |||||
function generateCaptchaSvg(text: string): string { | |||||
const width = 150; | |||||
const height = 50; | |||||
const fontSize = 30; | |||||
const letters = text.split(''); | |||||
// 应用于整个 SVG 的滤镜,增加噪点和轻微扭曲 | |||||
const filter = ` | |||||
<filter id="captchaNoise"> | |||||
<feTurbulence baseFrequency="0.7 0.8" numOctaves="3" seed="${Math.random() * 100}" stitchTiles="stitch" type="fractalNoise" result="turbulence"/> | |||||
<feDisplacementMap in="SourceGraphic" in2="turbulence" scale="3" xChannelSelector="R" yChannelSelector="G" result="displacement"/> | |||||
<feGaussianBlur in="displacement" stdDeviation="0.5" /> | |||||
</filter> | |||||
`; | |||||
// 为每个字母生成 <text> 元素,并应用随机变换 | |||||
const textElements = letters.map((char, index) => { | |||||
const x = (width / text.length) * (index + 0.5); | |||||
const y = height / 2 + fontSize / 3; // 基线调整 | |||||
const rotate = Math.random() * 30 - 15; // -15 到 15 度旋转 | |||||
const skewX = Math.random() * 10 - 5; // 轻微倾斜 | |||||
const fill = `hsl(${Math.random() * 360}, 70%, 70%)`; // 随机 HSL 颜色 | |||||
return `<text | |||||
x="${x}" | |||||
y="${y}" | |||||
font-size="${fontSize}" | |||||
font-family="Arial, sans-serif" | |||||
font-weight="bold" | |||||
text-anchor="middle" | |||||
fill="${fill}" | |||||
transform="rotate(${rotate} ${x} ${y}) skewX(${skewX})" | |||||
style="letter-spacing: 1px;" | |||||
>${char}</text>`; | |||||
}).join(''); | |||||
// 添加几条干扰线 | |||||
let lines = ''; | |||||
for (let i = 0; i < 3; i++) { | |||||
lines += `<line | |||||
x1="${Math.random() * width * 0.8}" | |||||
y1="${Math.random() * height}" | |||||
x2="${Math.random() * width * 0.8 + width * 0.2}" | |||||
y2="${Math.random() * height}" | |||||
stroke="rgba(180, 180, 200, 0.3)" | |||||
stroke-width="2" | |||||
/>`; | |||||
} | |||||
return ` | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
width="${width}" | |||||
height="${height}" | |||||
viewBox="0 0 ${width} ${height}" | |||||
style="background-color: rgba(55, 65, 81, 0.3); border-radius: 6px; border: 1px solid rgba(107, 114, 128, 0.5);" | |||||
> | |||||
<defs> | |||||
${filter} | |||||
</defs> | |||||
<g style="filter: url(#captchaNoise);"> | |||||
${textElements} | |||||
${lines} | |||||
</g> | |||||
</svg> | |||||
`; | |||||
} | |||||
export function useCaptcha(length: number = 5) { | |||||
const captchaText = ref(''); | |||||
const userInput = ref(''); | |||||
const captchaSvg = ref(''); | |||||
const error = ref(''); | |||||
/** | |||||
* 生成新的验证码 | |||||
*/ | |||||
const generateCaptcha = () => { | |||||
const newText = generateRandomString(length); | |||||
captchaText.value = newText; | |||||
captchaSvg.value = generateCaptchaSvg(newText); | |||||
userInput.value = ''; // 清空用户输入 | |||||
error.value = ''; // 清除错误 | |||||
}; | |||||
/** | |||||
* 验证用户输入 | |||||
* @returns 是否验证通过 | |||||
*/ | |||||
const validateCaptcha = (): boolean => { | |||||
if (!userInput.value) { | |||||
error.value = '请输入验证码'; | |||||
return false; | |||||
} | |||||
if (userInput.value.toLowerCase() !== captchaText.value.toLowerCase()) { | |||||
error.value = '验证码错误'; | |||||
generateCaptcha(); // 错误后自动刷新 | |||||
return false; | |||||
} | |||||
error.value = ''; | |||||
return true; | |||||
}; | |||||
// 初始化生成第一个验证码 | |||||
generateCaptcha(); | |||||
return { | |||||
userInput, | |||||
captchaSvg, | |||||
error, | |||||
generateCaptcha, | |||||
validateCaptcha, | |||||
}; | |||||
} |
export default { | export default { | ||||
common: { | common: { | ||||
home: 'Home', | |||||
products: 'Products', | |||||
faq: 'FAQ', | |||||
about: 'About Us', | |||||
contact: 'Contact', | |||||
search: 'Search', | |||||
language: 'Language' | |||||
home: "Home", | |||||
products: "Products", | |||||
faq: "FAQ", | |||||
about: "About Us", | |||||
contact: "Contact", | |||||
search: "Search", | |||||
language: "Language", | |||||
searchPlaceholder: "Search products, FAQ, etc.", | |||||
hotKeywords: "Hot Keywords", | |||||
productCategories: "Product Categories", | |||||
byUsage: "By Usage", | |||||
}, | }, | ||||
home: { | home: { | ||||
title: 'Welcome to Hanye Website', | |||||
description: 'We provide high quality products and services', | |||||
learnMore: 'Learn More' | |||||
title: "Welcome to Hanye Website", | |||||
description: "Providing high-quality products and services.", | |||||
learnMore: "Learn More", | |||||
}, | }, | ||||
products: { | products: { | ||||
title: 'Our Products', | |||||
viewDetails: 'View Details', | |||||
consultation: 'Product consultation, quotation is welcome', | |||||
consultation_button: 'Contact Us', | |||||
usage:'Storage by Use', | |||||
usage_title:'Choose storage by use case.', | |||||
support:'Support', | |||||
support_description:'10+ years of professional tech support.', | |||||
development:'Own R&D and Manufacturing', | |||||
development_description:'We develop, manufacture, and sell memory products online.', | |||||
develop:'Innovation', | |||||
develop_description:'Developing innovative solutions continuously.', | |||||
strong_point:'Our strengths', | |||||
strong_point_title:'Our strengths/Why choose us', | |||||
title: "Our Products", | |||||
viewDetails: "View Details", | |||||
consultation: | |||||
"Feel free to inquire about product consultations and quotes.", | |||||
consultation_button: "Contact Us", | |||||
usage: "Choose storage by usage", | |||||
usage_title: "Select storage products based on your needs.", | |||||
support: "Support", | |||||
support_description: | |||||
"Expert technical support from our team with over 10 years of experience.", | |||||
development: "Independent Development, Manufacturing, and Sales", | |||||
development_description: | |||||
"Independent development, manufacturing, and online sales of memory and related products.", | |||||
develop: "Towards Continuous Development", | |||||
develop_description: | |||||
"Diversifying products and relentlessly pursuing new creativity.", | |||||
strong_point: "Our Strengths", | |||||
strong_point_title: "Our Strengths / Why Choose Us", | |||||
}, | }, | ||||
faq: { | faq: { | ||||
title: 'Frequently Asked Questions', | |||||
searchPlaceholder: 'Search questions' | |||||
title: "Frequently Asked Questions", | |||||
searchPlaceholder: "Search questions", | |||||
}, | }, | ||||
about: { | about: { | ||||
title: 'About Us', | |||||
history: 'Company History', | |||||
team: 'Our Team', | |||||
values: 'Our Values' | |||||
title: "About Us", | |||||
meta: { | |||||
title: "About Us - Hanye", | |||||
description: | |||||
"Learn about Hanye's company information, history, and business scope. We are dedicated to the development, manufacturing, and sales of memory and related products.", | |||||
}, | |||||
intro: { | |||||
title: "About Hanye", | |||||
paragraph1: | |||||
"Hanye was established in 2003 with its operational headquarters in Shenyang, China.", | |||||
paragraph2: | |||||
"From its founding to the present, we have grown into a comprehensive enterprise integrating the development, manufacturing, and sales of memory (storage media) and related products.", | |||||
paragraph3: | |||||
"We have also established a complete after-sales service system to provide full support. We strive to be a trusted partner for our customers and will continue to make further efforts.", | |||||
}, | |||||
overview: { | |||||
title: "Company Overview", | |||||
companyNameLabel: "Company Name", | |||||
companyNameValue: "Hanye", | |||||
englishNameLabel: "English Name", | |||||
englishNameValue: "Hanye Technology Co., Ltd.", | |||||
ceoLabel: "CEO / Representative Director", | |||||
ceoValue: "ZHENG XIAO DONG", | |||||
employeesLabel: "Number of Employees", | |||||
employeesValue: "30", | |||||
addressLabel: "Address", | |||||
addressValue: | |||||
"803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||||
telLabel: "TEL", | |||||
telValue: "86)024-8399-0696", | |||||
faxLabel: "FAX", | |||||
faxValue: "86)024-8399-0696", | |||||
businessLabel: "Business Activities", | |||||
businessValue1: | |||||
"Development, manufacturing, and sales of flash memory products", | |||||
businessValue2: "Development, manufacturing, and sales of SSD products", | |||||
businessValue3: "Related businesses", | |||||
}, | |||||
contact: { | |||||
title: "Contact Information", | |||||
emailLabel: "E-mail", | |||||
emailValue: "hanye#hanye.cn", | |||||
hoursLabel: "Business Hours", | |||||
hoursValue1: "Weekdays 9:00 - 18:00", | |||||
hoursValue2: "Closed on Saturdays, Sundays, and holidays", | |||||
phoneLabel: "Phone", | |||||
phoneValue: "86)024-8399-0696", | |||||
phoneNote: "Please call for inquiries regarding visits.", | |||||
}, | |||||
}, | }, | ||||
contact: { | contact: { | ||||
title: 'Contact Us', | |||||
name: 'Name', | |||||
email: 'Email', | |||||
message: 'Message', | |||||
submit: 'Submit' | |||||
} | |||||
} | |||||
title: "Contact Us", | |||||
name: "Name", | |||||
email: "Email Address", | |||||
message: "Message", | |||||
submit: "Send Message", | |||||
form: { | |||||
title: "Leave us a message", | |||||
nameLabel: "Name", | |||||
emailLabel: "Email Address", | |||||
messageLabel: "Message", | |||||
captchaLabel: "Captcha", | |||||
submitLabel: "Send Message", | |||||
successMessage: "Message sent successfully. We will contact you soon.", | |||||
submitLoading: "Sending...", | |||||
captchaRefresh: "Refresh Captcha", | |||||
captchaRequired: "Please enter the captcha code", | |||||
captchaIncorrect: "Incorrect captcha code", | |||||
nameRequired: "Please enter your name", | |||||
emailRequired: "Please enter your email address", | |||||
emailInvalid: "Please enter a valid email address", | |||||
messageRequired: "Please enter your message", | |||||
}, | |||||
info: { | |||||
title: "Contact Info", | |||||
description: "Leave us a message, and we will get back to you shortly.", | |||||
addressLabel: "Address", | |||||
addressValue1: "803, NO.6, AiTe, 90-6# SanHao Street,", | |||||
addressValue2: "Heping District, ShenYang, China", | |||||
phoneLabel: "Phone", | |||||
phoneValue: "86)024-8399-0696", | |||||
emailLabel: "Email", | |||||
emailValue: "info#hanye.com", | |||||
hoursLabel: "Business Hours", | |||||
hoursValue1: "Mon - Fri: 9:00 AM - 6:00 PM", | |||||
hoursValue2: "Sat - Sun: Closed", | |||||
}, | |||||
}, | |||||
}; |
export default { | export default { | ||||
common: { | common: { | ||||
home: 'ホーム', | |||||
products: '製品', | |||||
faq: 'よくある質問', | |||||
about: '会社概要', | |||||
contact: 'お問い合わせ', | |||||
search: '検索', | |||||
language: '言語' | |||||
home: "ホーム", | |||||
products: "製品", | |||||
faq: "よくある質問", | |||||
about: "会社概要", | |||||
contact: "お問い合わせ", | |||||
search: "検索", | |||||
language: "言語", | |||||
searchPlaceholder: "製品、FAQ などを検索", | |||||
hotKeywords: "人気のキーワード", | |||||
productCategories: "製品カテゴリー", | |||||
byUsage: "用途で選ぶ", | |||||
}, | }, | ||||
home: { | home: { | ||||
title: 'Hanye ウェブサイトへようこそ', | |||||
description: '高品質の製品とサービスを提供しています', | |||||
learnMore: '詳細を見る' | |||||
title: "Hanye ウェブサイトへようこそ", | |||||
description: "高品質の製品とサービスを提供しています", | |||||
learnMore: "詳細を見る", | |||||
}, | }, | ||||
products: { | products: { | ||||
title: '当社の製品', | |||||
viewDetails: '詳細を見る', | |||||
consultation: '製品に関するご相談、お見積もりはお気軽にどうぞ', | |||||
consultation_button: 'お問い合わせ', | |||||
usage:'使い方でストレージを選ぼう', | |||||
usage_title:'用途に応じてストレージ製品を選ぶ。', | |||||
support:'サポート', | |||||
support_description:'10年以上のキャリアを誇るチームが専門技術でサポート', | |||||
development:'独自に開発・製造、販売', | |||||
development_description:'メモリ及び関連製品の独自開発、製造・オンラインショップでの販売', | |||||
develop:'たゆまぬ発展へ', | |||||
develop_description:'製品の多様化を図り、新たな創意への飽くなき挑戦', | |||||
strong_point:'当社の強み', | |||||
strong_point_title:'当社の強み/選ばれる理由', | |||||
title: "当社の製品", | |||||
viewDetails: "詳細を見る", | |||||
consultation: "製品に関するご相談、お見積もりはお気軽にどうぞ", | |||||
consultation_button: "お問い合わせ", | |||||
usage: "使い方でストレージを選ぼう", | |||||
usage_title: "用途に応じてストレージ製品を選ぶ。", | |||||
support: "サポート", | |||||
support_description: "10年以上のキャリアを誇るチームが専門技術でサポート", | |||||
development: "独自に開発・製造、販売", | |||||
development_description: | |||||
"メモリ及び関連製品の独自開発、製造・オンラインショップでの販売", | |||||
develop: "たゆまぬ発展へ", | |||||
develop_description: "製品の多様化を図り、新たな創意への飽くなき挑戦", | |||||
strong_point: "当社の強み", | |||||
strong_point_title: "当社の強み/選ばれる理由", | |||||
}, | }, | ||||
faq: { | faq: { | ||||
title: 'よくある質問', | |||||
searchPlaceholder: '質問を検索' | |||||
title: "よくある質問", | |||||
searchPlaceholder: "質問を検索", | |||||
}, | }, | ||||
about: { | about: { | ||||
title: '会社概要', | |||||
history: '会社の歴史', | |||||
team: 'チームメンバー', | |||||
values: '企業価値' | |||||
title: "当社について", | |||||
meta: { | |||||
title: "当社について - Hanye", | |||||
description: "Hanyeの会社情報、沿革、事業内容をご覧ください。メモリ及び関連製品の開発・製造・販売に取り組んでいます。" | |||||
}, | |||||
intro: { | |||||
title: "当社について", | |||||
paragraph1: "Hanye は中国瀋陽に運営本部をおき 2003年に設立されました。", | |||||
paragraph2: "創業から現在に至るまで、メモリ(記憶媒体) 及び 関連製品の開発・製造・販売を統べる総合企業に成長いたしました。", | |||||
paragraph3: "また万全なアフターサービス体制も構築し全力でサポートいたします。お客様に信頼いただけるパートナーを目指し 一層の努力を重ねてまいります。", | |||||
}, | |||||
overview: { | |||||
title: "会社概要", | |||||
companyNameLabel: "会社名", | |||||
companyNameValue: "Hanye", | |||||
englishNameLabel: "英文社名", | |||||
englishNameValue: "Hanye Technology Co., Ltd.", | |||||
ceoLabel: "代表取締役", | |||||
ceoValue: "ZHENG XIAO DONG", | |||||
employeesLabel: "従業員数", | |||||
employeesValue: "30名", | |||||
addressLabel: "所在地", | |||||
addressValue: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||||
telLabel: "TEL", | |||||
telValue: "86)024-8399-0696", | |||||
faxLabel: "FAX", | |||||
faxValue: "86)024-8399-0696", | |||||
businessLabel: "事業内容", | |||||
businessValue1: "フラッシュメモリーの開発・製造・販売", | |||||
businessValue2: "SSD製品の開発・製造・販売", | |||||
businessValue3: "その関連事業", | |||||
}, | |||||
contact: { | |||||
title: "連絡先", | |||||
emailLabel: "E-mail", | |||||
emailValue: "hanye#hanye.cn", | |||||
hoursLabel: "営業時間", | |||||
hoursValue1: "平日9:00~18:00", | |||||
hoursValue2: "土日祝定休", | |||||
phoneLabel: "電話", | |||||
phoneValue: "86)024-8399-0696", | |||||
phoneNote: "ご来社の場合はお電話でお問い合わせください", | |||||
} | |||||
}, | }, | ||||
contact: { | contact: { | ||||
title: 'お問い合わせ', | |||||
name: 'お名前', | |||||
email: 'メールアドレス', | |||||
message: 'メッセージ', | |||||
submit: '送信' | |||||
} | |||||
} | |||||
title: "お問い合わせ", | |||||
name: "お名前", | |||||
email: "メールアドレス", | |||||
message: "メッセージ", | |||||
submit: "送信", | |||||
form: { | |||||
title: "メッセージを残してください", | |||||
nameLabel: "お名前", | |||||
emailLabel: "メールアドレス", | |||||
messageLabel: "メッセージ", | |||||
captchaLabel: "検証コード", | |||||
submitLabel: "送信", | |||||
successMessage: "メッセージを送信しました。すぐに連絡いたします。", | |||||
submitLoading: "送信中...", | |||||
captchaRefresh: "検証コードを更新", | |||||
captchaRequired: "検証コードを入力してください", | |||||
captchaIncorrect: "検証コードが間違っています", | |||||
nameRequired: "お名前を入力してください", | |||||
}, | |||||
info: { | |||||
title: "お問い合わせ", | |||||
description: "メッセージを残してください。すぐに連絡いたします。", | |||||
addressLabel: "住所", | |||||
addressValue1: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||||
addressValue2: "", | |||||
phoneLabel: "電話番号", | |||||
phoneValue: "86)024-8399-0696", | |||||
emailLabel: "メールアドレス", | |||||
emailValue: "info#hanye.com", | |||||
hoursLabel: "営業時間", | |||||
hoursValue1: "月曜日 - 金曜日 9:00-18:00", | |||||
hoursValue2: "土曜日 - 日曜日 9:00-18:00", | |||||
}, | |||||
}, | |||||
}; |
export default { | export default { | ||||
common: { | common: { | ||||
home: '首页', | |||||
products: '产品', | |||||
faq: '常见问题', | |||||
about: '关于我们', | |||||
contact: '联系我们', | |||||
search: '搜索', | |||||
language: '语言' | |||||
home: "首页", | |||||
products: "产品", | |||||
faq: "常见问题", | |||||
about: "关于我们", | |||||
contact: "联系我们", | |||||
search: "搜索", | |||||
language: "语言", | |||||
searchPlaceholder: "搜索关键字, 产品、FAQ 等", | |||||
hotKeywords: "热门搜索", | |||||
productCategories: "产品分类", | |||||
byUsage: "按用途", | |||||
}, | }, | ||||
home: { | home: { | ||||
title: '欢迎来到Hanye官网', | |||||
description: '我们提供高质量的产品和服务', | |||||
learnMore: '了解更多' | |||||
title: "欢迎来到Hanye官网", | |||||
description: "我们提供高质量的产品和服务", | |||||
learnMore: "了解更多", | |||||
}, | }, | ||||
products: { | products: { | ||||
title: '我们的产品', | |||||
viewDetails: '查看详情', | |||||
consultation: '欢迎进行产品咨询,我们将在第一时间回复您', | |||||
consultation_button: '联系我们', | |||||
usage:'使用方法选择存储', | |||||
usage_title:'根据用途选择存储产品。', | |||||
support:'支持', | |||||
support_description:'拥有10年以上经验的团队提供专业技术支持', | |||||
development:'独立开发和制造', | |||||
development_description:'独立开发和制造内存及相关产品,并在网上商店销售', | |||||
develop:'不断发展', | |||||
develop_description:'不断开发和制造产品,提供创新解决方案', | |||||
strong_point:'我们的优势', | |||||
strong_point_title:'我们的优势/选择我们的理由', | |||||
title: "我们的产品", | |||||
viewDetails: "查看详情", | |||||
consultation: "欢迎进行产品咨询,我们将在第一时间回复您", | |||||
consultation_button: "联系我们", | |||||
usage: "使用方法选择存储", | |||||
usage_title: "根据用途选择存储产品。", | |||||
support: "支持", | |||||
support_description: "拥有10年以上经验的团队提供专业技术支持", | |||||
development: "独立开发和制造", | |||||
development_description: "独立开发和制造内存及相关产品,并在网上商店销售", | |||||
develop: "不断发展", | |||||
develop_description: "不断开发和制造产品,提供创新解决方案", | |||||
strong_point: "我们的优势", | |||||
strong_point_title: "我们的优势/选择我们的理由", | |||||
}, | }, | ||||
faq: { | faq: { | ||||
title: '常见问题', | |||||
searchPlaceholder: '搜索问题' | |||||
title: "常见问题", | |||||
searchPlaceholder: "搜索问题", | |||||
}, | }, | ||||
about: { | about: { | ||||
title: '关于我们', | |||||
history: '公司历史', | |||||
team: '团队成员', | |||||
values: '企业价值观' | |||||
title: "关于我们", | |||||
meta: { | |||||
title: "关于我们 - Hanye", | |||||
description: "了解 Hanye 的公司信息、发展历程和业务范围。我们致力于内存及相关产品的开发、制造和销售。" | |||||
}, | |||||
intro: { | |||||
title: "公司简介", | |||||
paragraph1: "Hanye 成立于2003年,运营总部位于中国沈阳。", | |||||
paragraph2: "自创立至今,我们已成长为集内存(存储介质)及相关产品的研发、制造、销售于一体的综合性企业。", | |||||
paragraph3: "我们建立了完善的售后服务体系,竭诚为您提供支持。我们致力于成为客户信赖的合作伙伴,并将为此付出更多努力。", | |||||
}, | |||||
overview: { | |||||
title: "公司概要", | |||||
companyNameLabel: "公司名称", | |||||
companyNameValue: "Hanye", | |||||
englishNameLabel: "英文名称", | |||||
englishNameValue: "Hanye Technology Co., Ltd.", | |||||
ceoLabel: "法人代表", | |||||
ceoValue: "郑晓东", // Translated name if appropriate, otherwise keep original | |||||
employeesLabel: "员工人数", | |||||
employeesValue: "30人", | |||||
addressLabel: "地址", | |||||
addressValue: "中国辽宁省沈阳市和平区三好街90-6号艾特国际6号楼803室", // More detailed Chinese address | |||||
telLabel: "电话", | |||||
telValue: "86)024-8399-0696", | |||||
faxLabel: "传真", | |||||
faxValue: "86)024-8399-0696", | |||||
businessLabel: "业务范围", | |||||
businessValue1: "闪存产品的开发、制造、销售", | |||||
businessValue2: "SSD产品的开发、制造、销售", | |||||
businessValue3: "及相关业务", | |||||
}, | |||||
contact: { | |||||
title: "联系方式", | |||||
emailLabel: "电子邮箱", | |||||
emailValue: "hanye#hanye.cn", | |||||
hoursLabel: "营业时间", | |||||
hoursValue1: "工作日 9:00 - 18:00", | |||||
hoursValue2: "周六、周日及法定节假日休息", | |||||
phoneLabel: "电话", | |||||
phoneValue: "86)024-8399-0696", | |||||
phoneNote: "如需来访,请先电话联系。", | |||||
} | |||||
}, | }, | ||||
contact: { | contact: { | ||||
title: '联系我们', | |||||
name: '姓名', | |||||
email: '邮箱', | |||||
message: '消息', | |||||
submit: '提交' | |||||
} | |||||
} | |||||
title: "联系我们", | |||||
name: "姓名", | |||||
email: "邮箱", | |||||
message: "消息", | |||||
submit: "提交", | |||||
form: { | |||||
title: "给我们留言", | |||||
nameLabel: "姓名", | |||||
emailLabel: "邮箱", | |||||
messageLabel: "消息", | |||||
captchaLabel: "验证码", | |||||
submitLabel: "提交", | |||||
successMessage: "消息已成功发送,我们会尽快与您联系。", | |||||
submitLoading: "正在发送...", | |||||
captchaRefresh: "点击刷新验证码", | |||||
captchaRequired: "请输入验证码", | |||||
captchaIncorrect: "验证码不正确", | |||||
nameRequired: "请输入您的姓名", | |||||
emailRequired: "请输入您的邮箱", | |||||
messageRequired: "请输入您的消息", | |||||
emailInvalid: "请输入有效的邮箱地址", | |||||
}, | |||||
validation: { | |||||
nameRequired: "请输入您的姓名", | |||||
emailRequired: "请输入您的邮箱", | |||||
messageRequired: "请输入您的消息", | |||||
emailInvalid: "请输入有效的邮箱地址", | |||||
}, | |||||
captcha: { | |||||
required: "请输入验证码", | |||||
incorrect: "验证码不正确", | |||||
}, | |||||
info: { | |||||
title: "联系我们", | |||||
description: "欢迎给我们留言,我们将在第一时间回复您", | |||||
addressLabel: "地址", | |||||
addressValue1: "803, NO.6, AiTe, 90-6# SanHao Street, Heping District, ShenYang, China", | |||||
addressValue2: "中国辽宁省沈阳市和平区三好街90-6号艾特国际大厦803室", | |||||
phoneLabel: "电话", | |||||
emailLabel: "邮箱", | |||||
hoursLabel: "工作时间", | |||||
hoursValue1: "周一至周五 9:00-18:00 (节假日除外)", | |||||
hoursValue2: "周六至周日 9:00-18:00 (节假日除外)", | |||||
}, | |||||
}, | |||||
}; |
<template> | <template> | ||||
<div class="py-8"> | |||||
<div class="container-custom"> | |||||
<h1 class="text-3xl font-bold mb-8">{{ $t('about.title') }}</h1> | |||||
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden"> | |||||
<div class="p-8"> | |||||
<section class="mb-12"> | |||||
<h2 class="text-2xl font-semibold mb-4">{{ $t('about.history') }}</h2> | |||||
<p class="text-gray-700 mb-4"> | |||||
汉业科技成立于2020年,是一家专注于提供高质量产品和服务的科技公司。 | |||||
从创立之初,我们就致力于通过创新技术解决客户面临的挑战,为客户创造价值。 | |||||
</p> | |||||
<p class="text-gray-700"> | |||||
经过多年的发展,我们已成为行业内备受认可的品牌,拥有众多忠实客户。 | |||||
我们的产品覆盖了多个领域,为客户提供了全方位的解决方案。 | |||||
</p> | |||||
</section> | |||||
<section class="mb-12"> | |||||
<h2 class="text-2xl font-semibold mb-4">{{ $t('about.values') }}</h2> | |||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |||||
<div class="bg-blue-50 p-6 rounded-lg"> | |||||
<h3 class="text-xl font-medium mb-2 text-blue-700">创新精神</h3> | |||||
<p class="text-gray-700"> | |||||
我们不断探索新技术、新方法,为客户提供创新的解决方案。 | |||||
</p> | |||||
</div> | |||||
<div class="bg-green-50 p-6 rounded-lg"> | |||||
<h3 class="text-xl font-medium mb-2 text-green-700">专业品质</h3> | |||||
<p class="text-gray-700"> | |||||
我们坚持高标准,确保每一个产品都经过严格的质量控制。 | |||||
</p> | |||||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||||
<div | |||||
class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gray-900 text-gray-300 min-h-screen relative overflow-hidden" | |||||
> | |||||
<!-- Subtle Pattern Background --> | |||||
<div class="absolute inset-0 opacity-[0.03] pointer-events-none" | |||||
style="background-image: linear-gradient(45deg, #fff 12%, transparent 12.5%, transparent 87%, #fff 87.5%, #fff), | |||||
linear-gradient(-45deg, #fff 12%, transparent 12.5%, transparent 87%, #fff 87.5%, #fff); | |||||
background-size: 8px 8px;"> | |||||
</div> | |||||
<!-- Content Wrapper --> | |||||
<div class="relative max-w-screen-xl mx-auto"> | |||||
<h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white"> | |||||
{{ $t("about.title") }} | |||||
</h1> | |||||
<div class="space-y-12"> | |||||
<!-- Introduction Section --> | |||||
<section | |||||
class="relative bg-gray-800/80 border border-gray-700 rounded-xl overflow-hidden shadow-xl backdrop-blur-sm p-6 sm:p-8 lg:p-10" | |||||
> | |||||
<div class="mb-8"> | |||||
<h2 | |||||
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block" | |||||
> | |||||
{{ $t("about.intro.title") }} | |||||
</h2> | |||||
<div | |||||
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2" | |||||
></div> | |||||
</div> | |||||
<div class="space-y-4 text-lg text-gray-300 leading-relaxed"> | |||||
<p>{{ $t("about.intro.paragraph1") }}</p> | |||||
<p>{{ $t("about.intro.paragraph2") }}</p> | |||||
<p>{{ $t("about.intro.paragraph3") }}</p> | |||||
</div> | |||||
</section> | |||||
<!-- Company Overview Section --> | |||||
<section | |||||
class="relative bg-gray-800/80 border border-gray-700 rounded-xl overflow-hidden shadow-xl backdrop-blur-sm p-6 sm:p-8 lg:p-10" | |||||
> | |||||
<div class="mb-8"> | |||||
<h2 | |||||
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block" | |||||
> | |||||
{{ $t("about.overview.title") }} | |||||
</h2> | |||||
<div | |||||
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2" | |||||
></div> | |||||
</div> | |||||
<dl | |||||
class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-2 lg:grid-cols-3" | |||||
> | |||||
<div class="sm:col-span-1"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.companyNameLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.companyNameValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-1"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.englishNameLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.englishNameValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-1"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.ceoLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.ceoValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-1"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.employeesLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.employeesValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-2"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.addressLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.addressValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-1"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.telLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.telValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-1"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.faxLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.faxValue") }} | |||||
</dd> | |||||
</div> | |||||
<div class="sm:col-span-2 lg:col-span-3"> | |||||
<dt class="text-base font-semibold text-gray-100"> | |||||
{{ $t("about.overview.businessLabel") }} | |||||
</dt> | |||||
<dd class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.overview.businessValue1") }}<br /> | |||||
{{ $t("about.overview.businessValue2") }}<br /> | |||||
{{ $t("about.overview.businessValue3") }} | |||||
</dd> | |||||
</div> | |||||
</dl> | |||||
</section> | |||||
<!-- Contact Info Section --> | |||||
<section | |||||
class="relative bg-gray-800/80 border border-gray-700 rounded-xl overflow-hidden shadow-xl backdrop-blur-sm p-6 sm:p-8 lg:p-10" | |||||
> | |||||
<div class="mb-8"> | |||||
<h2 | |||||
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block" | |||||
> | |||||
{{ $t("about.contact.title") }} | |||||
</h2> | |||||
<div | |||||
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2" | |||||
></div> | |||||
</div> | |||||
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3"> | |||||
<div class="flex items-start space-x-4"> | |||||
<div | |||||
class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-lg shadow-md" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-5 w-5" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
> | |||||
<path | |||||
d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" | |||||
/> | |||||
<path | |||||
d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" | |||||
/> | |||||
</svg> | |||||
</div> | </div> | ||||
<div class="bg-yellow-50 p-6 rounded-lg"> | |||||
<h3 class="text-xl font-medium mb-2 text-yellow-700">客户至上</h3> | |||||
<p class="text-gray-700"> | |||||
客户的满意是我们最大的追求,我们致力于超越客户期望。 | |||||
<div> | |||||
<h3 class="text-base font-medium text-gray-100"> | |||||
{{ $t("about.contact.emailLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.contact.emailValue") }} | |||||
</p> | </p> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</section> | |||||
<section> | |||||
<h2 class="text-2xl font-semibold mb-4">{{ $t('about.team') }}</h2> | |||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> | |||||
<div class="text-center"> | |||||
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div> | |||||
<h3 class="text-xl font-medium">张三</h3> | |||||
<p class="text-gray-600">创始人 & CEO</p> | |||||
<div class="flex items-start space-x-4"> | |||||
<div | |||||
class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-lg shadow-md" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-5 w-5" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
> | |||||
<path | |||||
fill-rule="evenodd" | |||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" | |||||
clip-rule="evenodd" | |||||
/> | |||||
</svg> | |||||
</div> | </div> | ||||
<div class="text-center"> | |||||
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div> | |||||
<h3 class="text-xl font-medium">李四</h3> | |||||
<p class="text-gray-600">技术总监</p> | |||||
<div> | |||||
<h3 class="text-base font-medium text-gray-100"> | |||||
{{ $t("about.contact.hoursLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.contact.hoursValue1") }} | |||||
</p> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.contact.hoursValue2") }} | |||||
</p> | |||||
</div> | </div> | ||||
<div class="text-center"> | |||||
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div> | |||||
<h3 class="text-xl font-medium">王五</h3> | |||||
<p class="text-gray-600">产品经理</p> | |||||
</div> | |||||
<div class="flex items-start space-x-4"> | |||||
<div | |||||
class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-lg shadow-md" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-5 w-5" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
> | |||||
<path | |||||
d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" | |||||
/> | |||||
</svg> | |||||
</div> | </div> | ||||
<div class="text-center"> | |||||
<div class="h-40 w-40 bg-gray-200 rounded-full mx-auto mb-4"></div> | |||||
<h3 class="text-xl font-medium">赵六</h3> | |||||
<p class="text-gray-600">营销总监</p> | |||||
<div> | |||||
<h3 class="text-base font-medium text-gray-100"> | |||||
{{ $t("about.contact.phoneLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
{{ $t("about.contact.phoneValue") }} | |||||
</p> | |||||
<p class="mt-1 text-sm text-gray-500"> | |||||
{{ $t("about.contact.phoneNote") }} | |||||
</p> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</section> | |||||
</div> | |||||
</div> | |||||
</section> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</template> | </template> | ||||
<script setup lang="ts"> | <script setup lang="ts"> | ||||
/** | |||||
* 关于我们页面 | |||||
* 展示公司历史、价值观和团队成员 | |||||
*/ | |||||
import { useI18n } from "vue-i18n"; | |||||
const { t } = useI18n(); | |||||
// SEO优化 | |||||
// SEO | |||||
useHead({ | useHead({ | ||||
title: '关于我们 - Hanye', | |||||
title: t("about.meta.title"), | |||||
meta: [ | meta: [ | ||||
{ name: 'description', content: '了解我们的公司历史、企业价值观和团队成员。汉业科技致力于提供高质量的产品和服务。' } | |||||
] | |||||
{ | |||||
name: "description", | |||||
content: t("about.meta.description"), | |||||
}, | |||||
], | |||||
}); | }); | ||||
</script> | |||||
</script> |
<template> | <template> | ||||
<div class="py-8"> | |||||
<div class="container-custom"> | |||||
<h1 class="text-3xl font-bold mb-8">{{ $t('contact.title') }}</h1> | |||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |||||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||||
<div | |||||
class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gradient-to-br from-gray-900 via-gray-900 to-black text-gray-300 min-h-screen" | |||||
> | |||||
<div class="max-w-screen-xl mx-auto"> | |||||
<h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white"> | |||||
{{ $t("contact.title") }} | |||||
</h1> | |||||
<div class="grid grid-cols-1 gap-10 lg:grid-cols-2 lg:gap-16"> | |||||
<!-- 联系表单 --> | <!-- 联系表单 --> | ||||
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden"> | |||||
<div class="p-8"> | |||||
<h2 class="text-2xl font-semibold mb-6">给我们留言</h2> | |||||
<div | |||||
class="relative bg-gray-800/70 border border-gray-700 rounded-xl overflow-hidden shadow-2xl backdrop-blur-sm transition-all duration-300 ease-in-out hover:shadow-blue-500/30 hover:border-blue-500/50 group" | |||||
> | |||||
<div | |||||
class="absolute inset-0 bg-gradient-to-r from-transparent via-blue-900/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" | |||||
></div> | |||||
<div class="relative p-6 sm:p-8 lg:p-10"> | |||||
<div class="mb-8"> | |||||
<h2 | |||||
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block" | |||||
> | |||||
{{ $t("contact.form.title") }} | |||||
</h2> | |||||
<div | |||||
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2" | |||||
></div> | |||||
</div> | |||||
<ErrorBoundary :error="error"> | <ErrorBoundary :error="error"> | ||||
<form @submit.prevent="submitForm"> | |||||
<div class="mb-4"> | |||||
<label for="name" class="block text-gray-700 font-medium mb-2">{{ $t('contact.name') }}</label> | |||||
<input | |||||
type="text" | |||||
id="name" | |||||
v-model="formData.name" | |||||
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" | |||||
:class="{ 'border-red-500': formErrors.name }" | |||||
<form @submit.prevent="submitForm" class="space-y-10"> | |||||
<div class="relative"> | |||||
<input | |||||
type="text" | |||||
id="name" | |||||
v-model="formData.name" | |||||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px]" | |||||
:class="[ | |||||
formErrors.name | |||||
? 'border-red-500 focus:border-red-500' | |||||
: 'border-gray-600 focus:border-blue-500', | |||||
]" | |||||
:placeholder="$t('contact.name')" | |||||
required | required | ||||
:aria-invalid="formErrors.name ? 'true' : 'false'" | |||||
aria-describedby="name-error" | |||||
/> | |||||
<label | |||||
for="name" | |||||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium" | |||||
:class="[ | |||||
formErrors.name | |||||
? 'text-red-400 peer-focus:text-red-400' | |||||
: 'text-gray-400 peer-focus:text-blue-400', | |||||
]" | |||||
>{{ $t("contact.name") }}</label | |||||
> | > | ||||
<p v-if="formErrors.name" class="mt-1 text-sm text-red-600">{{ formErrors.name }}</p> | |||||
<p | |||||
v-if="formErrors.name" | |||||
id="name-error" | |||||
class="mt-1.5 text-xs text-red-400 sm:text-sm" | |||||
> | |||||
{{ formErrors.name }} | |||||
</p> | |||||
</div> | </div> | ||||
<div class="mb-4"> | |||||
<label for="email" class="block text-gray-700 font-medium mb-2">{{ $t('contact.email') }}</label> | |||||
<input | |||||
type="email" | |||||
id="email" | |||||
v-model="formData.email" | |||||
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" | |||||
:class="{ 'border-red-500': formErrors.email }" | |||||
<div class="relative"> | |||||
<input | |||||
type="email" | |||||
id="email" | |||||
v-model="formData.email" | |||||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px]" | |||||
:class="[ | |||||
formErrors.email | |||||
? 'border-red-500 focus:border-red-500' | |||||
: 'border-gray-600 focus:border-blue-500', | |||||
]" | |||||
:placeholder="$t('contact.email')" | |||||
required | required | ||||
:aria-invalid="formErrors.email ? 'true' : 'false'" | |||||
aria-describedby="email-error" | |||||
/> | |||||
<label | |||||
for="email" | |||||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium" | |||||
:class="[ | |||||
formErrors.email | |||||
? 'text-red-400 peer-focus:text-red-400' | |||||
: 'text-gray-400 peer-focus:text-blue-400', | |||||
]" | |||||
>{{ $t("contact.email") }}</label | |||||
> | > | ||||
<p v-if="formErrors.email" class="mt-1 text-sm text-red-600">{{ formErrors.email }}</p> | |||||
<p | |||||
v-if="formErrors.email" | |||||
id="email-error" | |||||
class="mt-1.5 text-xs text-red-400 sm:text-sm" | |||||
> | |||||
{{ formErrors.email }} | |||||
</p> | |||||
</div> | </div> | ||||
<div class="mb-6"> | |||||
<label for="message" class="block text-gray-700 font-medium mb-2">{{ $t('contact.message') }}</label> | |||||
<textarea | |||||
id="message" | |||||
v-model="formData.message" | |||||
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 h-32" | |||||
:class="{ 'border-red-500': formErrors.message }" | |||||
<div class="relative"> | |||||
<textarea | |||||
id="message" | |||||
v-model="formData.message" | |||||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent h-36 resize-none transition-colors duration-300 focus:border-b-[3px]" | |||||
:class="[ | |||||
formErrors.message | |||||
? 'border-red-500 focus:border-red-500' | |||||
: 'border-gray-600 focus:border-blue-500', | |||||
]" | |||||
:placeholder="$t('contact.message')" | |||||
required | required | ||||
rows="5" | |||||
:aria-invalid="formErrors.message ? 'true' : 'false'" | |||||
aria-describedby="message-error" | |||||
></textarea> | ></textarea> | ||||
<p v-if="formErrors.message" class="mt-1 text-sm text-red-600">{{ formErrors.message }}</p> | |||||
<label | |||||
for="message" | |||||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium" | |||||
:class="[ | |||||
formErrors.message | |||||
? 'text-red-400 peer-focus:text-red-400' | |||||
: 'text-gray-400 peer-focus:text-blue-400', | |||||
]" | |||||
>{{ $t("contact.message") }}</label | |||||
> | |||||
<p | |||||
v-if="formErrors.message" | |||||
id="message-error" | |||||
class="mt-1.5 text-xs text-red-400 sm:text-sm" | |||||
> | |||||
{{ formErrors.message }} | |||||
</p> | |||||
</div> | |||||
<!-- Captcha Section --> | |||||
<div class="relative pt-2"> | |||||
<div class="flex items-center space-x-3"> | |||||
<!-- Captcha Input --> | |||||
<div class="flex-grow relative"> | |||||
<input | |||||
type="text" | |||||
id="captcha" | |||||
v-model="captcha.userInput.value" | |||||
class="peer block w-full appearance-none bg-transparent border-0 border-b-2 px-1 pt-5 pb-2 text-base text-gray-100 focus:outline-none focus:ring-0 placeholder-transparent transition-colors duration-300 focus:border-b-[3px]" | |||||
:class="[ | |||||
captcha.error.value | |||||
? 'border-red-500 focus:border-red-500' | |||||
: 'border-gray-600 focus:border-blue-500', | |||||
]" | |||||
:placeholder="$t('contact.form.captchaLabel')" | |||||
required | |||||
autocomplete="off" | |||||
aria-describedby="captcha-error" | |||||
:aria-invalid="captcha.error.value ? 'true' : 'false'" | |||||
/> | |||||
<label | |||||
for="captcha" | |||||
class="absolute left-1 top-5 origin-[0] -translate-y-4 scale-75 transform text-sm duration-300 peer-placeholder-shown:translate-y-0 peer-placeholder-shown:scale-100 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:font-medium" | |||||
:class="[ | |||||
captcha.error.value | |||||
? 'text-red-400 peer-focus:text-red-400' | |||||
: 'text-gray-400 peer-focus:text-blue-400', | |||||
]" | |||||
>{{ $t("contact.form.captchaLabel") }}</label | |||||
> | |||||
</div> | |||||
<!-- Captcha Image/SVG --> | |||||
<div | |||||
class="flex-shrink-0 cursor-pointer rounded-md overflow-hidden transition-all duration-200 ease-in-out hover:scale-105 hover:shadow-md active:scale-100" | |||||
v-html="captcha.captchaSvg.value" | |||||
@click="captcha.generateCaptcha()" | |||||
:title="$t('contact.form.captchaRefresh')" | |||||
style="line-height: 0" | |||||
></div> | |||||
<!-- Refresh Button --> | |||||
<button | |||||
type="button" | |||||
@click="captcha.generateCaptcha()" | |||||
class="flex-shrink-0 p-2 text-gray-500 hover:text-blue-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800 rounded-full hover:bg-gray-700/50 transition-all duration-200 ease-in-out" | |||||
:aria-label="$t('contact.form.captchaRefresh')" | |||||
:title="$t('contact.form.captchaRefresh')" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-5 w-5" | |||||
fill="none" | |||||
viewBox="0 0 24 24" | |||||
stroke="currentColor" | |||||
stroke-width="2" | |||||
> | |||||
<path | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" | |||||
/> | |||||
</svg> | |||||
</button> | |||||
</div> | |||||
<p | |||||
v-if="captcha.error.value" | |||||
id="captcha-error" | |||||
class="mt-1.5 text-xs text-red-400 sm:text-sm" | |||||
> | |||||
{{ | |||||
captcha.error.value === "请输入验证码" | |||||
? $t("contact.validation.captchaRequired") | |||||
: $t("contact.validation.captchaIncorrect") | |||||
}} | |||||
</p> | |||||
</div> | </div> | ||||
<div> | |||||
<button | |||||
type="submit" | |||||
class="btn btn-primary w-full" | |||||
<div class="pt-6"> | |||||
<button | |||||
type="submit" | |||||
class="w-full bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold py-3.5 px-6 rounded-lg shadow-lg hover:shadow-xl hover:from-blue-500 hover:to-purple-500 transform hover:-translate-y-0.5 transition-all duration-300 ease-in-out disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-md text-base tracking-wide focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-purple-500 focus-visible:ring-offset-gray-900" | |||||
:disabled="isLoading" | :disabled="isLoading" | ||||
> | > | ||||
<span v-if="isLoading" class="flex items-center justify-center"> | |||||
<span class="animate-spin h-4 w-4 border-2 border-white rounded-full border-t-transparent mr-2"></span> | |||||
提交中... | |||||
<span | |||||
v-if="isLoading" | |||||
class="flex items-center justify-center" | |||||
> | |||||
<span | |||||
class="animate-spin h-5 w-5 border-2 border-white rounded-full border-t-transparent mr-2.5" | |||||
></span> | |||||
{{ $t("contact.form.submitLoading") }} | |||||
</span> | </span> | ||||
<span v-else>{{ $t('contact.submit') }}</span> | |||||
<span v-else>{{ $t("contact.submit") }}</span> | |||||
</button> | </button> | ||||
</div> | </div> | ||||
<div v-if="submitSuccess" class="mt-4 p-3 bg-green-50 text-green-800 rounded-md"> | |||||
消息已成功发送,我们会尽快与您联系。 | |||||
<div | |||||
v-if="submitSuccess" | |||||
class="mt-6 p-4 bg-green-600/20 text-green-300 rounded-lg border border-green-500/50 text-sm flex items-center space-x-2" | |||||
role="alert" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-5 w-5 flex-shrink-0" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
> | |||||
<path | |||||
fill-rule="evenodd" | |||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" | |||||
clip-rule="evenodd" | |||||
/> | |||||
</svg> | |||||
<span>{{ $t("contact.form.successMessage") }}</span> | |||||
</div> | </div> | ||||
</form> | </form> | ||||
</ErrorBoundary> | </ErrorBoundary> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<!-- 联系信息 --> | <!-- 联系信息 --> | ||||
<div class="bg-white border border-gray-200 rounded-lg overflow-hidden"> | |||||
<div class="p-8"> | |||||
<h2 class="text-2xl font-semibold mb-6">联系方式</h2> | |||||
<div class="space-y-6"> | |||||
<div class="flex items-start"> | |||||
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | |||||
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" /> | |||||
<div | |||||
class="relative bg-gray-800/70 border border-gray-700 rounded-xl overflow-hidden shadow-2xl backdrop-blur-sm transition-all duration-300 ease-in-out hover:shadow-purple-500/30 hover:border-purple-500/50 group" | |||||
> | |||||
<div | |||||
class="absolute inset-0 bg-gradient-to-r from-transparent via-purple-900/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" | |||||
></div> | |||||
<div class="relative p-6 sm:p-8 lg:p-10"> | |||||
<div class="mb-8"> | |||||
<h2 | |||||
class="text-2xl font-semibold text-gray-100 sm:text-3xl inline-block" | |||||
> | |||||
{{ $t("contact.info.title") }} | |||||
</h2> | |||||
<div | |||||
class="h-1 w-20 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full mt-2" | |||||
></div> | |||||
</div> | |||||
<div class="space-y-8"> | |||||
<div class="flex items-center"> | |||||
<div | |||||
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-6 w-6" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
aria-hidden="true" | |||||
> | |||||
<path | |||||
fill-rule="evenodd" | |||||
d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" | |||||
clip-rule="evenodd" | |||||
/> | |||||
</svg> | </svg> | ||||
</div> | </div> | ||||
<div class="ml-4"> | <div class="ml-4"> | ||||
<h3 class="text-lg font-medium text-gray-900">地址</h3> | |||||
<p class="mt-1 text-gray-600"> | |||||
中国上海市浦东新区张江高科技园区<br> | |||||
科技大道123号 | |||||
<h3 class="text-lg font-medium text-gray-100"> | |||||
{{ $t("contact.info.addressLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
{{ $t("contact.info.addressValue1") }}<br /> | |||||
{{ $t("contact.info.addressValue2") }} | |||||
</p> | </p> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="flex items-start"> | |||||
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | |||||
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" /> | |||||
<div class="flex items-center"> | |||||
<div | |||||
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-6 w-6" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
aria-hidden="true" | |||||
> | |||||
<path | |||||
d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" | |||||
/> | |||||
</svg> | </svg> | ||||
</div> | </div> | ||||
<div class="ml-4"> | <div class="ml-4"> | ||||
<h3 class="text-lg font-medium text-gray-900">电话</h3> | |||||
<p class="mt-1 text-gray-600">+86 123 456 7890</p> | |||||
<h3 class="text-lg font-medium text-gray-100"> | |||||
{{ $t("contact.info.phoneLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400">+86 123 456 7890</p> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="flex items-start"> | |||||
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | |||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" /> | |||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" /> | |||||
<div class="flex items-center"> | |||||
<div | |||||
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-6 w-6" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
aria-hidden="true" | |||||
> | |||||
<path | |||||
d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" | |||||
/> | |||||
<path | |||||
d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" | |||||
/> | |||||
</svg> | </svg> | ||||
</div> | </div> | ||||
<div class="ml-4"> | <div class="ml-4"> | ||||
<h3 class="text-lg font-medium text-gray-900">邮箱</h3> | |||||
<p class="mt-1 text-gray-600">contact@example.com</p> | |||||
<h3 class="text-lg font-medium text-gray-100"> | |||||
{{ $t("contact.info.emailLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
contact@example.com | |||||
</p> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="flex items-start"> | |||||
<div class="flex-shrink-0 h-10 w-10 flex items-center justify-center bg-blue-100 text-blue-600 rounded-full"> | |||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | |||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd" /> | |||||
<div class="flex items-center"> | |||||
<div | |||||
class="flex-shrink-0 h-12 w-12 flex items-center justify-center bg-gradient-to-br from-blue-500 to-purple-600 text-white rounded-full shadow-lg" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-6 w-6" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
aria-hidden="true" | |||||
> | |||||
<path | |||||
fill-rule="evenodd" | |||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" | |||||
clip-rule="evenodd" | |||||
/> | |||||
</svg> | </svg> | ||||
</div> | </div> | ||||
<div class="ml-4"> | <div class="ml-4"> | ||||
<h3 class="text-lg font-medium text-gray-900">工作时间</h3> | |||||
<p class="mt-1 text-gray-600"> | |||||
周一至周五: 9:00 - 18:00<br> | |||||
周六、周日: 休息 | |||||
<h3 class="text-lg font-medium text-gray-100"> | |||||
{{ $t("contact.info.hoursLabel") }} | |||||
</h3> | |||||
<p class="mt-1 text-base text-gray-400"> | |||||
{{ $t("contact.info.hoursValue1") }}<br /> | |||||
{{ $t("contact.info.hoursValue2") }} | |||||
</p> | </p> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
* 联系我们页面 | * 联系我们页面 | ||||
* 提供联系表单和联系信息 | * 提供联系表单和联系信息 | ||||
*/ | */ | ||||
import { ref, reactive } from 'vue'; | |||||
import { useErrorHandler } from '~/composables/useErrorHandler'; | |||||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||||
import { useCaptcha } from "~/composables/useCaptcha"; | |||||
import { useI18n } from "vue-i18n"; | |||||
const { error, isLoading, wrapAsync } = useErrorHandler(); | const { error, isLoading, wrapAsync } = useErrorHandler(); | ||||
const captcha = useCaptcha(); | |||||
const submitSuccess = ref(false); | const submitSuccess = ref(false); | ||||
const { t } = useI18n(); | |||||
// 表单数据 | // 表单数据 | ||||
const formData = reactive({ | const formData = reactive({ | ||||
name: '', | |||||
email: '', | |||||
message: '' | |||||
name: "", | |||||
email: "", | |||||
message: "", | |||||
}); | }); | ||||
// 表单错误 | // 表单错误 | ||||
const formErrors = reactive({ | const formErrors = reactive({ | ||||
name: '', | |||||
email: '', | |||||
message: '' | |||||
name: "", | |||||
email: "", | |||||
message: "", | |||||
}); | }); | ||||
/** | /** | ||||
*/ | */ | ||||
function validateForm(): boolean { | function validateForm(): boolean { | ||||
let isValid = true; | let isValid = true; | ||||
// 重置错误 | // 重置错误 | ||||
formErrors.name = ''; | |||||
formErrors.email = ''; | |||||
formErrors.message = ''; | |||||
formErrors.name = ""; | |||||
formErrors.email = ""; | |||||
formErrors.message = ""; | |||||
// 验证姓名 | // 验证姓名 | ||||
if (!formData.name.trim()) { | if (!formData.name.trim()) { | ||||
formErrors.name = '请输入您的姓名'; | |||||
formErrors.name = t("contact.validation.nameRequired"); | |||||
isValid = false; | isValid = false; | ||||
} | } | ||||
// 验证邮箱 | // 验证邮箱 | ||||
if (!formData.email.trim()) { | if (!formData.email.trim()) { | ||||
formErrors.email = '请输入您的邮箱'; | |||||
formErrors.email = t("contact.validation.emailRequired"); | |||||
isValid = false; | isValid = false; | ||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { | } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { | ||||
formErrors.email = '请输入有效的邮箱地址'; | |||||
formErrors.email = t("contact.validation.emailInvalid"); | |||||
isValid = false; | isValid = false; | ||||
} | } | ||||
// 验证消息 | // 验证消息 | ||||
if (!formData.message.trim()) { | if (!formData.message.trim()) { | ||||
formErrors.message = '请输入您的消息'; | |||||
formErrors.message = t("contact.validation.messageRequired"); | |||||
isValid = false; | isValid = false; | ||||
} | } | ||||
return isValid; | return isValid; | ||||
} | } | ||||
async function submitForm() { | async function submitForm() { | ||||
// 重置成功状态 | // 重置成功状态 | ||||
submitSuccess.value = false; | submitSuccess.value = false; | ||||
// 验证表单 | |||||
// 验证表单(姓名、邮箱、消息) | |||||
if (!validateForm()) { | if (!validateForm()) { | ||||
return; | return; | ||||
} | } | ||||
// 验证验证码 | |||||
if (!captcha.validateCaptcha()) { | |||||
return; | |||||
} | |||||
// 提交表单数据 | // 提交表单数据 | ||||
await wrapAsync(async () => { | await wrapAsync(async () => { | ||||
// 模拟API请求 | // 模拟API请求 | ||||
await new Promise(resolve => setTimeout(resolve, 1000)); | |||||
console.log("Form Data:", formData); | |||||
console.log("Captcha Validated!"); | |||||
await new Promise((resolve) => setTimeout(resolve, 1500)); | |||||
// 模拟成功响应 | // 模拟成功响应 | ||||
submitSuccess.value = true; | submitSuccess.value = true; | ||||
// 清空表单 | |||||
formData.name = ''; | |||||
formData.email = ''; | |||||
formData.message = ''; | |||||
// 清空表单和验证码 | |||||
formData.name = ""; | |||||
formData.email = ""; | |||||
formData.message = ""; | |||||
captcha.generateCaptcha(); // 成功后也刷新验证码 | |||||
return true; | return true; | ||||
}); | }); | ||||
} | } | ||||
// SEO优化 | // SEO优化 | ||||
useHead({ | useHead({ | ||||
title: '联系我们 - Hanye', | |||||
title: t("contact.meta.title"), | |||||
meta: [ | meta: [ | ||||
{ name: 'description', content: '联系我们获取更多信息或咨询服务。我们期待收到您的留言。' } | |||||
] | |||||
{ | |||||
name: "description", | |||||
content: t("contact.meta.description"), | |||||
}, | |||||
], | |||||
}); | }); | ||||
</script> | |||||
</script> |
<template> | |||||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||||
<div | |||||
class="max-w-full px-4 py-16 md:px-8 lg:px-10 bg-gradient-to-br from-gray-900 via-gray-900 to-black text-gray-300 min-h-screen" | |||||
> | |||||
<div class="max-w-screen-xl mx-auto"> | |||||
<h1 class="text-4xl md:text-6xl mb-12 text-center font-normal text-white"> | |||||
{{ $t("faq.title") }} | |||||
</h1> | |||||
<!-- Search Bar --> | |||||
<div class="mb-12 max-w-xl mx-auto"> | |||||
<div class="relative"> | |||||
<input | |||||
type="search" | |||||
v-model="searchTerm" | |||||
:placeholder="$t('faq.searchPlaceholder')" | |||||
class="block w-full appearance-none rounded-lg border border-gray-600 bg-gray-700/50 px-4 py-3 pl-10 pr-4 text-base text-gray-100 placeholder-gray-400 shadow-inner transition duration-200 ease-in-out focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:ring-offset-2 focus:ring-offset-gray-900" | |||||
/> | |||||
<div | |||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="h-5 w-5 text-gray-400" | |||||
viewBox="0 0 20 20" | |||||
fill="currentColor" | |||||
> | |||||
<path | |||||
fill-rule="evenodd" | |||||
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" | |||||
clip-rule="evenodd" | |||||
/> | |||||
</svg> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<!-- FAQ Accordion --> | |||||
<div class="space-y-5"> | |||||
<div | |||||
v-for="faq in filteredFaqs" | |||||
:key="faq.id" | |||||
class="relative border rounded-xl overflow-hidden shadow-lg backdrop-blur-sm transition-all duration-300 ease-in-out group" | |||||
:class="[ | |||||
openAccordionIds.has(faq.id) | |||||
? 'bg-gray-750/70 border-blue-500/60 shadow-blue-500/20' | |||||
: 'bg-gray-800/60 border-gray-700 hover:border-blue-500/40 hover:bg-gray-750/70', | |||||
]" | |||||
> | |||||
<button | |||||
@click="toggleAccordion(faq.id)" | |||||
class="flex w-full items-center justify-between px-6 py-5 text-left text-lg font-medium text-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 focus-visible:ring-offset-gray-800 transition-colors duration-200" | |||||
:class="{ 'bg-gray-700/50': openAccordionIds.has(faq.id) }" | |||||
:aria-expanded="openAccordionIds.has(faq.id)" | |||||
:aria-controls="`faq-answer-${faq.id}`" | |||||
> | |||||
<span class="pr-4">{{ faq.question }}</span> | |||||
<!-- Plus/Minus Icon --> | |||||
<div | |||||
class="relative h-6 w-6 flex-shrink-0 text-blue-400 group-hover:text-blue-300" | |||||
> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="absolute h-6 w-6 transition-opacity duration-300 ease-in-out" | |||||
:class=" | |||||
openAccordionIds.has(faq.id) ? 'opacity-0' : 'opacity-100' | |||||
" | |||||
fill="none" | |||||
viewBox="0 0 24 24" | |||||
stroke="currentColor" | |||||
stroke-width="2" | |||||
> | |||||
<path | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
d="M12 4v16m8-8H4" | |||||
/> | |||||
</svg> | |||||
<svg | |||||
xmlns="http://www.w3.org/2000/svg" | |||||
class="absolute h-6 w-6 transition-opacity duration-300 ease-in-out" | |||||
:class=" | |||||
openAccordionIds.has(faq.id) ? 'opacity-100' : 'opacity-0' | |||||
" | |||||
fill="none" | |||||
viewBox="0 0 24 24" | |||||
stroke="currentColor" | |||||
stroke-width="2" | |||||
> | |||||
<path | |||||
stroke-linecap="round" | |||||
stroke-linejoin="round" | |||||
d="M20 12H4" | |||||
/> | |||||
</svg> | |||||
</div> | |||||
</button> | |||||
<transition | |||||
enter-active-class="transition-[grid-template-rows,opacity] ease-in-out duration-300" | |||||
enter-from-class="grid-template-rows-[0fr] opacity-0" | |||||
enter-to-class="grid-template-rows-[1fr] opacity-100" | |||||
leave-active-class="transition-[grid-template-rows,opacity] ease-in-out duration-300" | |||||
leave-from-class="grid-template-rows-[1fr] opacity-100" | |||||
leave-to-class="grid-template-rows-[0fr] opacity-0" | |||||
> | |||||
<div | |||||
v-show="openAccordionIds.has(faq.id)" | |||||
:id="`faq-answer-${faq.id}`" | |||||
class="grid overflow-hidden" | |||||
role="region" | |||||
> | |||||
<div class="overflow-hidden"> | |||||
<div | |||||
class="px-6 pt-4 pb-8 text-base text-gray-300 leading-relaxed" | |||||
> | |||||
<div | |||||
class="prose prose-invert max-w-none prose-p:text-gray-300 prose-a:text-blue-400 hover:prose-a:text-blue-300" | |||||
> | |||||
<p v-html="faq.answer"></p> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</transition> | |||||
</div> | |||||
<div | |||||
v-if="filteredFaqs.length === 0" | |||||
class="text-center text-gray-400 py-8" | |||||
> | |||||
{{ $t("faq.noResults") }} | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script setup lang="ts"> | |||||
import { ref, computed } from "vue"; | |||||
import { useI18n } from "vue-i18n"; | |||||
const { t } = useI18n(); | |||||
const searchTerm = ref(""); | |||||
const openAccordionIds = ref<Set<string>>(new Set()); | |||||
// Define FAQ item type with actual strings | |||||
interface FaqItem { | |||||
id: string; | |||||
question: string; | |||||
answer: string; | |||||
} | |||||
// Placeholder FAQ data with actual strings (replace with your real data) | |||||
const faqs = ref<FaqItem[]>([ | |||||
{ | |||||
id: "faq-1", | |||||
question: "如何购买 Hanye 产品?", // Example Question 1 | |||||
answer: | |||||
"您可以通过我们的官方在线商店或授权的零售商处购买 Hanye 产品。我们建议您在官方渠道购买以确保正品和售后服务。", // Example Answer 1 | |||||
}, | |||||
{ | |||||
id: "faq-2", | |||||
question: "产品保修期是多久?", | |||||
answer: | |||||
"不同产品的保修期可能不同,请参考具体产品的说明页面或联系我们的客服获取详细信息。通常固态硬盘提供3-5年保修,内存条提供终身保固。", | |||||
}, | |||||
{ | |||||
id: "faq-3", | |||||
question: "如何申请售后服务?", | |||||
answer: | |||||
"如果您需要售后服务,请准备好您的购买凭证,并通过我们的官方网站提交售后申请或直接联系客服中心。", | |||||
}, | |||||
{ | |||||
id: "faq-4", | |||||
question: "Hanye SSD 是否兼容我的电脑?", | |||||
answer: | |||||
"Hanye SSD 兼容大多数台式机和笔记本电脑。请确认您的设备支持相应的接口(如 SATA 或 NVMe)和规格。具体兼容性列表请参考产品页面。", | |||||
}, | |||||
{ | |||||
id: "faq-5", | |||||
question: "忘记密码怎么办?", | |||||
answer: | |||||
'如果是指 Hanye 相关的在线服务账户密码,请使用"忘记密码"功能进行重置。如果是加密 U 盘或 SSD 的密码,很抱歉,为了数据安全,我们无法提供密码破解服务。', | |||||
}, | |||||
]); | |||||
// Filter FAQs based on actual string content | |||||
const filteredFaqs = computed(() => { | |||||
if (!searchTerm.value) { | |||||
return faqs.value; | |||||
} | |||||
const lowerSearchTerm = searchTerm.value.toLowerCase(); | |||||
return faqs.value.filter( | |||||
(faq: FaqItem) => | |||||
faq.question.toLowerCase().includes(lowerSearchTerm) || | |||||
faq.answer.toLowerCase().includes(lowerSearchTerm) | |||||
); | |||||
}); | |||||
// Toggle accordion item (modified for Set) | |||||
const toggleAccordion = (id: string) => { | |||||
if (openAccordionIds.value.has(id)) { | |||||
openAccordionIds.value.delete(id); | |||||
} else { | |||||
openAccordionIds.value.add(id); | |||||
} | |||||
}; | |||||
// SEO (Still uses i18n) | |||||
useHead({ | |||||
title: t("faq.meta.title"), | |||||
meta: [ | |||||
{ | |||||
name: "description", | |||||
content: t("faq.meta.description"), | |||||
}, | |||||
], | |||||
}); | |||||
</script> |
</Swiper> | </Swiper> | ||||
</section> | </section> | ||||
<!-- 分类产品展示 --> | |||||
<section class="max-w-full mb-12 md:mb-32"> | |||||
<!-- 按用途产品展示 --> | |||||
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4"> | |||||
<div class="max-w-screen-2xl mx-auto relative"> | <div class="max-w-screen-2xl mx-auto relative"> | ||||
<div | <div | ||||
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4 px-4 md:px-0" | |||||
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4" | |||||
> | > | ||||
{{ $t("products.usage") }} | {{ $t("products.usage") }} | ||||
</div> | </div> | ||||
<div | <div | ||||
class="justify-center text-white font-normal mb-8 px-4 md:px-0 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl" | |||||
class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl" | |||||
> | > | ||||
{{ $t("products.usage_title") }} | {{ $t("products.usage_title") }} | ||||
</div> | </div> | ||||
<div | <div | ||||
class="max-w-screen-2xl mx-auto relative overflow-x-auto whitespace-nowrap scrollbar-hide scroll-smooth" | class="max-w-screen-2xl mx-auto relative overflow-x-auto whitespace-nowrap scrollbar-hide scroll-smooth" | ||||
> | > | ||||
<div class="w-full mb-8 inline-flex items-center gap-4 px-4 md:px-0"> | |||||
<div class="w-full mb-8 inline-flex items-center gap-4"> | |||||
<div | <div | ||||
class="cursor-pointer select-none px-7 py-3 rounded-full outline outline-1 outline-offset-[-1px] outline-cyan-400 inline-flex justify-center items-center gap-2.5" | class="cursor-pointer select-none px-7 py-3 rounded-full outline outline-1 outline-offset-[-1px] outline-cyan-400 inline-flex justify-center items-center gap-2.5" | ||||
> | > | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div | |||||
class="w-full relative pl-[calc((100vw-1536px)/2)] 2xl:pl-[calc((100vw-1536px)/2)] lg:pl-[0] md:pl-[0] sm:pl-[0] max-w-full" | |||||
> | |||||
<div class="max-w-screen-2xl mx-auto"> | |||||
<div class="w-full"> | <div class="w-full"> | ||||
<Swiper | <Swiper | ||||
:modules="[Navigation]" | :modules="[Navigation]" | ||||
:spaceBetween="30" | :spaceBetween="30" | ||||
slidesPerView="auto" | |||||
:grid="{ | |||||
fill: 'column', | |||||
rows: 1, | |||||
:slidesPerView="4" | |||||
:breakpoints="{ | |||||
320: { slidesPerView: 2, spaceBetween: 10 }, | |||||
640: { slidesPerView: 3, spaceBetween: 20 }, | |||||
1024: { slidesPerView: 4, spaceBetween: 20 }, | |||||
1280: { slidesPerView: 4, spaceBetween: 30 }, | |||||
1536: { slidesPerView: 4, spaceBetween: 30 }, | |||||
}" | }" | ||||
:navigation="{ | :navigation="{ | ||||
prevEl: '.swiper-button-prev-2', | prevEl: '.swiper-button-prev-2', | ||||
nextEl: '.swiper-button-next-2', | nextEl: '.swiper-button-next-2', | ||||
}" | }" | ||||
class="h-32 sm:h-32 md:h-64 lg:h-96 max-w-full" | |||||
class="max-w-full" | |||||
> | > | ||||
<SwiperSlide | <SwiperSlide | ||||
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96" | |||||
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96" | |||||
> | > | ||||
<div | <div | ||||
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | ||||
class="w-full h-full object-contain" | class="w-full h-full object-contain" | ||||
/> | /> | ||||
<div | <div | ||||
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block" | |||||
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4" | |||||
> | > | ||||
Hanye Q60-2TST3 | Hanye Q60-2TST3 | ||||
</div> | </div> | ||||
<div | <div | ||||
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80" | |||||
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80" | |||||
> | > | ||||
2TB SSD UP TO 550MB/s | 2TB SSD UP TO 550MB/s | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</SwiperSlide> | </SwiperSlide> | ||||
<SwiperSlide | <SwiperSlide | ||||
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96" | |||||
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96" | |||||
> | > | ||||
<div | <div | ||||
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | ||||
class="w-full h-full object-contain" | class="w-full h-full object-contain" | ||||
/> | /> | ||||
<div | <div | ||||
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block" | |||||
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4" | |||||
> | > | ||||
Hanye Q60-2TST3 | Hanye Q60-2TST3 | ||||
</div> | </div> | ||||
<div | <div | ||||
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80" | |||||
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80" | |||||
> | > | ||||
2TB SSD UP TO 550MB/s | 2TB SSD UP TO 550MB/s | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</SwiperSlide> | </SwiperSlide> | ||||
<SwiperSlide | <SwiperSlide | ||||
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96" | |||||
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96" | |||||
> | > | ||||
<div | <div | ||||
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | ||||
class="w-full h-full object-contain" | class="w-full h-full object-contain" | ||||
/> | /> | ||||
<div | <div | ||||
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block" | |||||
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4" | |||||
> | > | ||||
Hanye Q60-2TST3 | Hanye Q60-2TST3 | ||||
</div> | </div> | ||||
<div | <div | ||||
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80" | |||||
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80" | |||||
> | > | ||||
2TB SSD UP TO 550MB/s | 2TB SSD UP TO 550MB/s | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</SwiperSlide> | </SwiperSlide> | ||||
<SwiperSlide | <SwiperSlide | ||||
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96" | |||||
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96" | |||||
> | > | ||||
<div | <div | ||||
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | ||||
class="w-full h-full object-contain" | class="w-full h-full object-contain" | ||||
/> | /> | ||||
<div | <div | ||||
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block" | |||||
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4" | |||||
> | > | ||||
Hanye Q60-2TST3 | Hanye Q60-2TST3 | ||||
</div> | </div> | ||||
<div | <div | ||||
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80" | |||||
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80" | |||||
> | > | ||||
2TB SSD UP TO 550MB/s | 2TB SSD UP TO 550MB/s | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</SwiperSlide> | </SwiperSlide> | ||||
<SwiperSlide | <SwiperSlide | ||||
class="!w-32 sm:!w-32 md:!w-64 lg:!w-96 !h-32 sm:!h-32 md:!h-64 lg:!h-96" | |||||
class="w-32 sm:w-32 md:w-64 lg:w-96 h-32 sm:h-32 md:h-64 lg:h-96" | |||||
> | > | ||||
<div | <div | ||||
class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | class="w-full h-full [background:#181818] rounded-2xl px-2 md:px-10 flex flex-col items-center justify-center" | ||||
class="w-full h-full object-contain" | class="w-full h-full object-contain" | ||||
/> | /> | ||||
<div | <div | ||||
class="text-center text-white text-base font-bold leading-tight mb-4 hidden md:block" | |||||
class="text-center text-white text-xs sm:text-sm md:text-base font-bold leading-tight mb-2 md:mb-4" | |||||
> | > | ||||
Hanye Q60-2TST3 | Hanye Q60-2TST3 | ||||
</div> | </div> | ||||
<div | <div | ||||
class="text-center justify-center text-white text-xs font-normal mb-8 leading-tight hidden md:block opacity-80" | |||||
class="text-center justify-center text-white text-[10px] sm:text-xs font-normal mb-4 md:mb-8 leading-tight opacity-80" | |||||
> | > | ||||
2TB SSD UP TO 550MB/s | 2TB SSD UP TO 550MB/s | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</section> | </section> | ||||
<!-- 按用途产品展示 --> | |||||
<!-- 按分类产品展示 --> | |||||
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4"> | |||||
<div class="max-w-screen-2xl mx-auto relative"> | |||||
<div | |||||
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4" | |||||
> | |||||
Hanye独自に開発・製造、販売 | |||||
</div> | |||||
<div | |||||
class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl" | |||||
> | |||||
製品カテゴリー | |||||
</div> | |||||
</div> | |||||
<div class="max-w-screen-2xl mx-auto"> | |||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> | |||||
<nuxt-link | |||||
to="/products" | |||||
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" | |||||
> | |||||
<div class="col-span-1 flex flex-col gap-4"> | |||||
<div class="flex flex-col gap-2 opacity-80"> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>PC高速化</span> | |||||
</div> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>起動・読込 高速</span> | |||||
</div> | |||||
</div> | |||||
<div | |||||
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" | |||||
> | |||||
<div | |||||
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed" | |||||
> | |||||
2.5-inch SSD & M.2 SSD | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="w-32 h-32 md:w-44 md:h-44"> | |||||
<img | |||||
:src="product" | |||||
alt="h1" | |||||
class="w-full h-full object-contain" | |||||
/> | |||||
</div> | |||||
</nuxt-link> | |||||
<nuxt-link | |||||
to="/products" | |||||
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" | |||||
> | |||||
<div class="col-span-1 flex flex-col gap-4"> | |||||
<div class="flex flex-col gap-2 opacity-80"> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>PC高速化</span> | |||||
</div> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>起動・読込 高速</span> | |||||
</div> | |||||
</div> | |||||
<div | |||||
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" | |||||
> | |||||
<div | |||||
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed" | |||||
> | |||||
2.5-inch SSD & M.2 SSD | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="w-32 h-32 md:w-44 md:h-44"> | |||||
<img | |||||
:src="product" | |||||
alt="h1" | |||||
class="w-full h-full object-contain" | |||||
/> | |||||
</div> | |||||
</nuxt-link> | |||||
<nuxt-link | |||||
to="/products" | |||||
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" | |||||
> | |||||
<div class="col-span-1 flex flex-col gap-4"> | |||||
<div class="flex flex-col gap-2 opacity-80"> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>PC高速化</span> | |||||
</div> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>起動・読込 高速</span> | |||||
</div> | |||||
</div> | |||||
<div | |||||
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" | |||||
> | |||||
<div | |||||
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed" | |||||
> | |||||
2.5-inch SSD & M.2 SSD | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="w-32 h-32 md:w-44 md:h-44"> | |||||
<img | |||||
:src="product" | |||||
alt="h1" | |||||
class="w-full h-full object-contain" | |||||
/> | |||||
</div> | |||||
</nuxt-link> | |||||
<nuxt-link | |||||
to="/products" | |||||
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" | |||||
> | |||||
<div class="col-span-1 flex flex-col gap-4"> | |||||
<div class="flex flex-col gap-2 opacity-80"> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>PC高速化</span> | |||||
</div> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>起動・読込 高速</span> | |||||
</div> | |||||
</div> | |||||
<div | |||||
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" | |||||
> | |||||
<div | |||||
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed" | |||||
> | |||||
2.5-inch SSD & M.2 SSD | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="w-32 h-32 md:w-44 md:h-44"> | |||||
<img | |||||
:src="product" | |||||
alt="h1" | |||||
class="w-full h-full object-contain" | |||||
/> | |||||
</div> | |||||
</nuxt-link> | |||||
<nuxt-link | |||||
to="/products" | |||||
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" | |||||
> | |||||
<div class="col-span-1 flex flex-col gap-4"> | |||||
<div class="flex flex-col gap-2 opacity-80"> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>PC高速化</span> | |||||
</div> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>起動・読込 高速</span> | |||||
</div> | |||||
</div> | |||||
<div | |||||
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" | |||||
> | |||||
<div | |||||
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed" | |||||
> | |||||
2.5-inch SSD & M.2 SSD | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="w-32 h-32 md:w-44 md:h-44"> | |||||
<img | |||||
:src="product" | |||||
alt="h1" | |||||
class="w-full h-full object-contain" | |||||
/> | |||||
</div> | |||||
</nuxt-link> | |||||
<nuxt-link | |||||
to="/products" | |||||
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" | |||||
> | |||||
<div class="col-span-1 flex flex-col gap-4"> | |||||
<div class="flex flex-col gap-2 opacity-80"> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>PC高速化</span> | |||||
</div> | |||||
<div | |||||
class="text-white text-sm md:text-base font-normal leading-tight flex gap-2 items-center" | |||||
> | |||||
<i class="icon-star text-sm"></i> | |||||
<span>起動・読込 高速</span> | |||||
</div> | |||||
</div> | |||||
<div | |||||
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" | |||||
> | |||||
<div | |||||
class="justify-start text-neutral-200 text-xs md:text-sm font-medium uppercase leading-relaxed" | |||||
> | |||||
2.5-inch SSD & M.2 SSD | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="w-32 h-32 md:w-44 md:h-44"> | |||||
<img | |||||
:src="product" | |||||
alt="h1" | |||||
class="w-full h-full object-contain" | |||||
/> | |||||
</div> | |||||
</nuxt-link> | |||||
</div> | |||||
</div> | |||||
</section> | |||||
<!-- Tag description --> | |||||
<section class="max-w-full mb-12 md:mb-32"> | |||||
<!-- 产品核心展示 --> | |||||
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4"> | |||||
<div | <div | ||||
class="max-w-screen-2xl mx-auto grid grid-cols-1 justify-items-center gap-4 sm:grid-cols-2 md:grid-cols-3" | |||||
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" | |||||
> | > | ||||
<div class="inline-flex justify-start items-center gap-5 px-4"> | |||||
<div class="inline-flex justify-start items-center gap-5"> | |||||
<div | <div | ||||
class="w-16 h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden" | |||||
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" | |||||
> | > | ||||
<div | |||||
class="w-12 h-12 left-[9.50px] top-[9.50px] absolute overflow-hidden flex items-center justify-center" | |||||
> | |||||
<div class="w-full h-full flex items-center justify-center"> | |||||
<i class="icon-h1 text-white text-5xl"></i> | <i class="icon-h1 text-white text-5xl"></i> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="inline-flex justify-start items-center gap-5 px-4"> | |||||
<div class="inline-flex justify-start items-center gap-5"> | |||||
<div | <div | ||||
class="w-16 h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden" | |||||
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" | |||||
> | > | ||||
<div | |||||
class="w-12 h-12 left-[9.50px] top-[9.50px] absolute overflow-hidden flex items-center justify-center" | |||||
> | |||||
<div class="w-full h-full flex items-center justify-center"> | |||||
<i class="icon-h2 text-white text-5xl"></i> | <i class="icon-h2 text-white text-5xl"></i> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="inline-flex justify-start items-center gap-5 px-4"> | |||||
<div class="inline-flex justify-start items-center gap-5"> | |||||
<div | <div | ||||
class="w-16 h-16 relative bg-gradient-to-b from-neutral-600 to-slate-400 rounded-xl border-b-[1.50px] border-neutral-700 overflow-hidden" | |||||
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" | |||||
> | > | ||||
<div | |||||
class="w-12 h-12 left-[9.50px] top-[9.50px] absolute overflow-hidden flex items-center justify-center" | |||||
> | |||||
<div class="w-full h-full flex items-center justify-center"> | |||||
<i class="icon-h3 text-white text-5xl"></i> | <i class="icon-h3 text-white text-5xl"></i> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</section> | </section> | ||||
<!-- 当社の強み --> | <!-- 当社の強み --> | ||||
<section class="max-w-full mb-0 md:mb-28"> | |||||
<section class="max-w-full mb-12 md:mb-32 xl:px-2 lg:px-2 md:px-4 px-4"> | |||||
<div class="max-w-screen-2xl mx-auto relative"> | <div class="max-w-screen-2xl mx-auto relative"> | ||||
<div | <div | ||||
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4 px-4 md:px-0" | |||||
class="justify-center text-cyan-400 text-base font-normal leading-tight mb-4" | |||||
> | > | ||||
{{ $t("products.strong_point") }} | {{ $t("products.strong_point") }} | ||||
</div> | </div> | ||||
<div | <div | ||||
class="justify-center text-white font-normal mb-8 px-4 md:px-0 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl" | |||||
class="justify-center text-white font-normal mb-8 md:mb-16 text-xl sm:text-2xl md:text-4xl lg:text-6xl" | |||||
> | > | ||||
{{ $t("products.strong_point_title") }} | {{ $t("products.strong_point_title") }} | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
<div class="overflow-hidden w-full"> | |||||
<div | |||||
class="w-full relative pl-[calc((100vw-1536px)/2)] 2xl:pl-[calc((100vw-1536px)/2)] lg:pl-[0] md:pl-[0] sm:pl-[0] max-w-full" | |||||
> | |||||
<div class="max-w-screen-2xl mx-auto"> | |||||
<div class="w-full relative max-w-full"> | |||||
<Swiper | <Swiper | ||||
:modules="[Navigation, Pagination]" | :modules="[Navigation, Pagination]" | ||||
slides-per-view="auto" | slides-per-view="auto" | ||||
> | > | ||||
<SwiperSlide class="!max-w-screen-2xl !w-full"> | <SwiperSlide class="!max-w-screen-2xl !w-full"> | ||||
<div | <div | ||||
class="w-full h-full flex items-center px-4 md:px-20" | |||||
class="w-full h-full flex items-center px-0 md:px-20" | |||||
:style="{ | :style="{ | ||||
backgroundImage: `url(${homeC1Webp})`, | backgroundImage: `url(${homeC1Webp})`, | ||||
backgroundSize: 'cover', | backgroundSize: 'cover', | ||||
}" | }" | ||||
> | > | ||||
<div | <div | ||||
class="w-[100%] md:w-[50%] bg-white/5 rounded-2xl backdrop-blur-[50px] px-8 py-12 flex flex-col gap-8 border border-white/10" | |||||
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" | |||||
> | > | ||||
<div | <div | ||||
class="opacity-90 justify-start text-white text-2xl font-normal md:text-4xl" | class="opacity-90 justify-start text-white text-2xl font-normal md:text-4xl" | ||||
一貫体制による高品質と安定供給 | 一貫体制による高品質と安定供給 | ||||
</div> | </div> | ||||
<div | <div | ||||
class="opacity-70 justify-start text-white text-lg font-normal leading-relaxed hidden sm:block" | |||||
class="opacity-70 justify-start text-white text-base md:text-lg font-normal leading-relaxed" | |||||
> | > | ||||
「企画・開発から製造、品質管理、販売、オンラインショップ運営まで自社で完結。ISO認証取得の工場で生産された信頼性の高い製品を、安定してお届けします。」 | 「企画・開発から製造、品質管理、販売、オンラインショップ運営まで自社で完結。ISO認証取得の工場で生産された信頼性の高い製品を、安定してお届けします。」 | ||||
</div> | </div> | ||||
</section> | </section> | ||||
<!-- 产品咨询 --> | <!-- 产品咨询 --> | ||||
<section | |||||
class="max-w-full h-[240px] md:h-[480px] bg-black/80 hidden md:block" | |||||
> | |||||
<section class="max-w-full h-[240px] md:h-[480px] bg-black/80 md:block"> | |||||
<div class="h-full relative"> | <div class="h-full relative"> | ||||
<h1 | |||||
class="text-center justify-start text-white font-normal absolute top-1/3 left-4 right-4 -translate-x-0 md:left-1/2 md:-translate-x-1/2 md:right-auto text-xl sm:text-2xl md:text-3xl" | |||||
> | |||||
{{ $t("products.consultation") }} | |||||
</h1> | |||||
<div | <div | ||||
class="w-40 h-11 bg-zinc-300/10 rounded-lg outline outline-1 flex items-center justify-center gap-2 outline-white/20 backdrop-blur-[10px] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/5" | |||||
class="absolute top-0 left-0 w-full h-full flex flex-col gap-6 items-center justify-center z-10" | |||||
> | > | ||||
<span class="text-white text-sm font-normal">{{ | |||||
$t("products.consultation_button") | |||||
}}</span> | |||||
<i class="icon-arrow-right text-white text-sm font-normal"></i> | |||||
<h1 | |||||
class="text-center justify-start text-white font-normal text-xl sm:text-2xl md:text-3xl px-2" | |||||
> | |||||
{{ $t("products.consultation") }} | |||||
</h1> | |||||
<nuxt-link | |||||
:to="locale === defaultLocale ? '/contact' : `/${locale}/contact`" | |||||
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" | |||||
> | |||||
<span class="text-xs md:text-sm font-normal">{{ | |||||
$t("products.consultation_button") | |||||
}}</span> | |||||
<i class="icon-arrow-right text-sm font-normal"></i> | |||||
</nuxt-link> | |||||
</div> | </div> | ||||
<img | |||||
v-if="isMobile" | |||||
:src="videoWebp" | |||||
alt="video" | |||||
class="w-full h-full object-cover opacity-20" | |||||
/> | |||||
<video | <video | ||||
v-else | |||||
:src="videoSrc" | :src="videoSrc" | ||||
autoplay | autoplay | ||||
muted | muted | ||||
loop | loop | ||||
:poster="videoWebp" | |||||
class="w-full h-full object-cover opacity-20" | class="w-full h-full object-cover opacity-20" | ||||
></video> | ></video> | ||||
</div> | </div> | ||||
import "swiper/css/navigation"; | import "swiper/css/navigation"; | ||||
import "swiper/css/pagination"; | import "swiper/css/pagination"; | ||||
import { useBreakpoints, breakpointsTailwind } from "@vueuse/core"; | |||||
import { useI18n } from "vue-i18n"; | |||||
import video from "@/assets/videos/video.mp4"; | import video from "@/assets/videos/video.mp4"; | ||||
import videoWebp from "@/assets/videos/video.webp"; | |||||
import homeA1Webp from "@/assets/images/home-a-1.webp"; | import homeA1Webp from "@/assets/images/home-a-1.webp"; | ||||
import homeC1Webp from "@/assets/images/home-c-1.webp"; | import homeC1Webp from "@/assets/images/home-c-1.webp"; | ||||
import product from "@/assets/images/product.png"; | import product from "@/assets/images/product.png"; | ||||
const { t, locale } = useI18n(); | |||||
const config = useRuntimeConfig(); | |||||
// 从运行时配置获取默认语言,如果未配置则默认为 'en' | |||||
const defaultLocale = config.public.i18n?.defaultLocale || "en"; | |||||
const videoSrc = ref(video); | const videoSrc = ref(video); | ||||
// Define breakpoints | |||||
const breakpoints = useBreakpoints(breakpointsTailwind); | |||||
// Check if the device is mobile (smaller than md) | |||||
const isMobile = breakpoints.smaller("md"); | |||||
/** | /** | ||||
* 网站首页 | * 网站首页 | ||||
* 展示网站主要内容和精选产品 | * 展示网站主要内容和精选产品 | ||||
:deep(.swiper-pagination-bullet-active) { | :deep(.swiper-pagination-bullet-active) { | ||||
background-color: var(--color-text); /* Example color */ | background-color: var(--color-text); /* Example color */ | ||||
} | } | ||||
.category-item { | |||||
background-image: url("@/assets/images/home-b-1.webp"); | |||||
background-size: cover; | |||||
background-position: center; | |||||
background-repeat: no-repeat; | |||||
opacity: 0.8; | |||||
transition: opacity 0.3s ease; | |||||
&:hover { | |||||
opacity: 1; | |||||
} | |||||
} | |||||
</style> | </style> |
<template> | <template> | ||||
<div class="py-8"> | |||||
<div class="container-custom"> | |||||
<h1 class="text-3xl font-bold mb-8">{{ $t('products.title') }}</h1> | |||||
<ErrorBoundary :error="error"> | |||||
<div v-if="isLoading" class="flex justify-center py-12"> | |||||
<!-- 加载中 --> | |||||
<div class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent"></div> | |||||
</div> | |||||
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> | |||||
<div v-for="product in products" :key="product.id" class="bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow"> | |||||
<div class="h-48 bg-gray-100 flex items-center justify-center"> | |||||
<!-- 产品图片占位符 --> | |||||
<span class="text-gray-400">{{ product.title }}</span> | |||||
<div> | |||||
<div class="w-full h-[55px] sm:h-[72px]"></div> | |||||
<ErrorBoundary :error="error"> | |||||
<div v-if="isLoading" class="flex justify-center py-12"> | |||||
<!-- 加载中 --> | |||||
<div | |||||
class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent" | |||||
></div> | |||||
</div> | |||||
<div v-else> | |||||
<div class="w-full mb-12 relative"> | |||||
<div class="absolute top-0 left-0 w-full h-full z-10"> | |||||
<div | |||||
class="max-w-screen-2xl mx-auto h-full flex flex-col justify-center gap-4 p-4" | |||||
> | |||||
<div | |||||
class="justify-start text-white text-2xl font-normal md:text-4xl lg:text-6xl" | |||||
> | |||||
製品一覧 | |||||
</div> | |||||
<div | |||||
class="text-white text-sm lg:text-lg font-normal leading-loose" | |||||
> | |||||
卓越した製品は、実績に裏打ちされた優れた技術と、<br />継続的な革新デザインとの融合により、生み出されます。 | |||||
</div> | |||||
</div> | </div> | ||||
<div class="p-6"> | |||||
<h2 class="text-xl font-semibold mb-2">{{ product.title }}</h2> | |||||
<p class="text-gray-600 mb-4">{{ product.description }}</p> | |||||
<NuxtLink :to="`/products/${product.id}`" class="text-blue-600 hover:text-blue-800 font-medium"> | |||||
{{ $t('products.viewDetails') }} | |||||
</NuxtLink> | |||||
</div> | |||||
<img | |||||
:src="banner" | |||||
alt="products-banner" | |||||
class="w-full object-cover h-60 lg:h-full" | |||||
/> | |||||
</div> | |||||
<div class="max-w-full mb-6 xl:px-2 lg:px-2 md:px-4 px-4"> | |||||
<div class="max-w-screen-2xl mx-auto"> | |||||
<nuxt-link | |||||
to="/" | |||||
class="justify-start text-white/60 text-base font-normal" | |||||
>ホーム</nuxt-link | |||||
> | |||||
<span class="text-white/60 text-base font-normal px-2"> / </span> | |||||
<nuxt-link to="/products" class="text-white text-base font-normal" | |||||
>製品一覧</nuxt-link | |||||
> | |||||
</div> | |||||
</div> | |||||
<div | |||||
class="max-w-full mb-12 md:mb-20 lg:mb-32 xl:px-2 lg:px-2 md:px-4 px-4" | |||||
> | |||||
<div class="max-w-screen-2xl mx-auto"> | |||||
<div class="w-full grid grid-cols-1 md:grid-cols-10 gap-8 md:gap-2"> | |||||
<div | |||||
class="col-span-1 md:col-span-2 flex flex-col gap-16 mb-8 md:mb-0" | |||||
> | |||||
<div class="flex flex-col gap-4"> | |||||
<div class="text-white text-3xl font-medium"> | |||||
製品カテゴリー | |||||
</div> | |||||
<div class="flex flex-col gap-4"> | |||||
<div | |||||
class="opacity-80 justify-start text-white text-base font-normal" | |||||
> | |||||
PCメモリ | |||||
</div> | |||||
<div | |||||
class="opacity-80 justify-start text-white text-base font-normal" | |||||
> | |||||
PCメモリ | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="flex flex-col gap-4"> | |||||
<div class="text-white text-3xl font-medium"> | |||||
製品カテゴリー | |||||
</div> | |||||
<div class="flex flex-col gap-4"> | |||||
<div | |||||
class="opacity-80 justify-start text-white text-base font-normal" | |||||
> | |||||
PCメモリ | |||||
</div> | |||||
<div | |||||
class="opacity-80 justify-start text-white text-base font-normal" | |||||
> | |||||
PCメモリ | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="col-span-1 md:col-span-8"> | |||||
<div class="flex flex-col gap-16"> | |||||
<div class="flex flex-col gap-4"> | |||||
<div class="w-full text-white text-4xl font-normal mb-4"> | |||||
2.5-inch SSD | |||||
</div> | |||||
<div | |||||
class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" | |||||
> | |||||
<div class="bg-zinc-900 rounded-lg"> | |||||
<div class="w-full p-8"> | |||||
<img | |||||
src="https://placehold.co/400x400" | |||||
alt="" | |||||
class="w-full h-full object-cover rounded-lg mb-4" | |||||
/> | |||||
<div | |||||
class="text-center justify-start text-white text-xl font-normal" | |||||
> | |||||
Hanye Q60-256GST3 | |||||
</div> | |||||
<div | |||||
class="text-center justify-start text-stone-400 text-base font-normal leading-normal" | |||||
> | |||||
256GB / 512GB / 1TB / 2TB | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="bg-zinc-900 rounded-lg"></div> | |||||
<div class="bg-zinc-900 rounded-lg"></div> | |||||
<div class="bg-zinc-900 rounded-lg"></div> | |||||
</div> | |||||
</div> | |||||
<div class="flex flex-col gap-4"> | |||||
<div class="w-full text-white text-4xl font-normal mb-4"> | |||||
2.5-inch SSD | |||||
</div> | |||||
<div | |||||
class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" | |||||
> | |||||
<div class="bg-zinc-900 rounded-lg"> | |||||
<div class="w-full p-8"> | |||||
<img | |||||
src="https://placehold.co/400x400" | |||||
alt="" | |||||
class="w-full h-full object-cover rounded-lg mb-4" | |||||
/> | |||||
<div | |||||
class="text-center justify-start text-white text-xl font-normal" | |||||
> | |||||
Hanye Q60-256GST3 | |||||
</div> | |||||
<div | |||||
class="text-center justify-start text-stone-400 text-base font-normal leading-normal" | |||||
> | |||||
256GB / 512GB / 1TB / 2TB | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="bg-zinc-900 rounded-lg"> | |||||
<div class="w-full p-8"> | |||||
<img | |||||
src="https://placehold.co/400x400" | |||||
alt="" | |||||
class="w-full h-full object-cover rounded-lg mb-4" | |||||
/> | |||||
<div | |||||
class="text-center justify-start text-white text-xl font-normal" | |||||
> | |||||
Hanye Q60-256GST3 | |||||
</div> | |||||
<div | |||||
class="text-center justify-start text-stone-400 text-base font-normal leading-normal" | |||||
> | |||||
256GB / 512GB / 1TB / 2TB | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="bg-zinc-900 rounded-lg"> | |||||
<div class="w-full p-8"> | |||||
<img | |||||
src="https://placehold.co/400x400" | |||||
alt="" | |||||
class="w-full h-full object-cover rounded-lg mb-4" | |||||
/> | |||||
<div | |||||
class="text-center justify-start text-white text-xl font-normal" | |||||
> | |||||
Hanye Q60-256GST3 | |||||
</div> | |||||
<div | |||||
class="text-center justify-start text-stone-400 text-base font-normal leading-normal" | |||||
> | |||||
256GB / 512GB / 1TB / 2TB | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div class="bg-zinc-900 rounded-lg"> | |||||
<div class="w-full p-8"> | |||||
<img | |||||
src="https://placehold.co/400x400" | |||||
alt="" | |||||
class="w-full h-full object-cover rounded-lg mb-4" | |||||
/> | |||||
<div | |||||
class="text-center justify-start text-white text-xl font-normal" | |||||
> | |||||
Hanye Q60-256GST3 | |||||
</div> | |||||
<div | |||||
class="text-center justify-start text-stone-400 text-base font-normal leading-normal" | |||||
> | |||||
256GB / 512GB / 1TB / 2TB | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
</ErrorBoundary> | |||||
</div> | |||||
</div> | |||||
</ErrorBoundary> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
* 产品列表页面 | * 产品列表页面 | ||||
* 展示所有产品 | * 展示所有产品 | ||||
*/ | */ | ||||
import { ref, onMounted } from 'vue'; | |||||
import { useErrorHandler } from '~/composables/useErrorHandler'; | |||||
import { useErrorHandler } from "~/composables/useErrorHandler"; | |||||
import banner from "@/assets/images/product-banner.webp"; | |||||
// 产品接口定义 | // 产品接口定义 | ||||
interface Product { | interface Product { | ||||
async function loadProducts() { | async function loadProducts() { | ||||
await wrapAsync(async () => { | await wrapAsync(async () => { | ||||
// 模拟API请求延迟 | // 模拟API请求延迟 | ||||
await new Promise(resolve => setTimeout(resolve, 500)); | |||||
await new Promise((resolve) => setTimeout(resolve, 500)); | |||||
// 模拟数据,实际项目中应从API获取 | // 模拟数据,实际项目中应从API获取 | ||||
products.value = [ | products.value = [ | ||||
{ | { | ||||
id: 1, | id: 1, | ||||
title: '产品一', | |||||
description: '这是产品一的详细描述,介绍产品特点和用途。' | |||||
title: "产品一", | |||||
description: "这是产品一的详细描述,介绍产品特点和用途。", | |||||
}, | }, | ||||
{ | { | ||||
id: 2, | id: 2, | ||||
title: '产品二', | |||||
description: '这是产品二的详细描述,介绍产品特点和用途。' | |||||
title: "产品二", | |||||
description: "这是产品二的详细描述,介绍产品特点和用途。", | |||||
}, | }, | ||||
{ | { | ||||
id: 3, | id: 3, | ||||
title: '产品三', | |||||
description: '这是产品三的详细描述,介绍产品特点和用途。' | |||||
title: "产品三", | |||||
description: "这是产品三的详细描述,介绍产品特点和用途。", | |||||
}, | }, | ||||
{ | { | ||||
id: 4, | id: 4, | ||||
title: '产品四', | |||||
description: '这是产品四的详细描述,介绍产品特点和用途。' | |||||
title: "产品四", | |||||
description: "这是产品四的详细描述,介绍产品特点和用途。", | |||||
}, | }, | ||||
{ | { | ||||
id: 5, | id: 5, | ||||
title: '产品五', | |||||
description: '这是产品五的详细描述,介绍产品特点和用途。' | |||||
title: "产品五", | |||||
description: "这是产品五的详细描述,介绍产品特点和用途。", | |||||
}, | }, | ||||
{ | { | ||||
id: 6, | id: 6, | ||||
title: '产品六', | |||||
description: '这是产品六的详细描述,介绍产品特点和用途。' | |||||
} | |||||
title: "产品六", | |||||
description: "这是产品六的详细描述,介绍产品特点和用途。", | |||||
}, | |||||
]; | ]; | ||||
return products.value; | return products.value; | ||||
}); | }); | ||||
} | } | ||||
// SEO优化 | // SEO优化 | ||||
useHead({ | useHead({ | ||||
title: '产品列表 - Hanye', | |||||
title: "产品列表 - Hanye", | |||||
meta: [ | meta: [ | ||||
{ name: 'description', content: '浏览我们的产品列表,找到适合您的解决方案。' } | |||||
] | |||||
{ | |||||
name: "description", | |||||
content: "浏览我们的产品列表,找到适合您的解决方案。", | |||||
}, | |||||
], | |||||
}); | }); | ||||
</script> | |||||
</script> |