Hanye官网
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <template>
  2. <div>
  3. <div class="w-full h-[55px] sm:h-[72px]"></div>
  4. <ErrorBoundary :error="error">
  5. <div v-if="isLoading" class="flex justify-center py-12">
  6. <div
  7. class="animate-spin h-8 w-8 border-4 border-blue-500 rounded-full border-t-transparent"
  8. ></div>
  9. </div>
  10. <div v-else>
  11. <div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-20">
  12. <div class="max-w-screen-2xl mx-auto">
  13. <nuxt-link
  14. to="/"
  15. class="justify-start text-white/60 text-base font-normal"
  16. >{{ t("common.home") }}</nuxt-link
  17. >
  18. <span class="text-white/60 text-base font-normal px-2"> / </span>
  19. <nuxt-link to="/contact" class="text-white text-base font-normal">{{
  20. t("contact.title")
  21. }}</nuxt-link>
  22. </div>
  23. </div>
  24. <div
  25. class="max-w-full mb-12 md:mb-20 lg:mb-32 xl:px-2 lg:px-2 md:px-4 px-4"
  26. >
  27. <div class="max-w-screen-2xl mx-auto">
  28. <div class="w-full grid grid-cols-1 md:grid-cols-2 gap-8">
  29. <!-- 联系表单 -->
  30. <div
  31. class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg"
  32. >
  33. <div class="text-white text-3xl font-medium mb-6">
  34. {{ t("contact.title") }}
  35. </div>
  36. <form
  37. @submit.prevent="handleSubmit"
  38. class="flex flex-col gap-6"
  39. >
  40. <div class="relative">
  41. <input
  42. v-model="form.name"
  43. type="text"
  44. id="name"
  45. 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] border-gray-600 focus:border-blue-500"
  46. :placeholder="t('contact.name')"
  47. required
  48. />
  49. <label
  50. for="name"
  51. 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 text-gray-400 peer-focus:text-blue-400"
  52. >
  53. {{ t("contact.name") }}
  54. </label>
  55. </div>
  56. <div class="relative">
  57. <input
  58. v-model="form.email"
  59. type="email"
  60. id="email"
  61. 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] border-gray-600 focus:border-blue-500"
  62. :placeholder="t('contact.email')"
  63. required
  64. />
  65. <label
  66. for="email"
  67. 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 text-gray-400 peer-focus:text-blue-400"
  68. >
  69. {{ t("contact.email") }}
  70. </label>
  71. </div>
  72. <div class="relative">
  73. <textarea
  74. v-model="form.message"
  75. id="message"
  76. 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] border-gray-600 focus:border-blue-500"
  77. :placeholder="t('contact.message')"
  78. required
  79. ></textarea>
  80. <label
  81. for="message"
  82. 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 text-gray-400 peer-focus:text-blue-400"
  83. >
  84. {{ t("contact.message") }}
  85. </label>
  86. </div>
  87. <!-- 验证码部分 -->
  88. <div class="relative pt-2">
  89. <div class="flex items-center space-x-3">
  90. <div class="flex-grow relative">
  91. <input
  92. type="text"
  93. id="captcha"
  94. v-model="captcha.userInput.value"
  95. 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]"
  96. :class="[
  97. captcha.error.value
  98. ? 'border-red-500 focus:border-red-500'
  99. : 'border-gray-600 focus:border-blue-500',
  100. ]"
  101. :placeholder="t('contact.captcha')"
  102. required
  103. autocomplete="off"
  104. aria-describedby="captcha-error"
  105. :aria-invalid="captcha.error.value ? 'true' : 'false'"
  106. />
  107. <label
  108. for="captcha"
  109. 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"
  110. :class="[
  111. captcha.error.value
  112. ? 'text-red-400 peer-focus:text-red-400'
  113. : 'text-gray-400 peer-focus:text-blue-400',
  114. ]"
  115. >
  116. {{ t("contact.captcha") }}
  117. </label>
  118. </div>
  119. <div
  120. class="flex-shrink-0 cursor-pointer select-none rounded-md overflow-hidden transition-all duration-200 ease-in-out hover:shadow-md active:scale-100"
  121. v-html="captcha.captchaSvg.value"
  122. @click="captcha.generateCaptcha()"
  123. :title="t('contact.refreshCaptcha')"
  124. style="line-height: 0"
  125. ></div>
  126. <button
  127. type="button"
  128. @click="captcha.generateCaptcha()"
  129. 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"
  130. :aria-label="t('contact.refreshCaptcha')"
  131. :title="t('contact.refreshCaptcha')"
  132. >
  133. <svg
  134. xmlns="http://www.w3.org/2000/svg"
  135. class="h-5 w-5"
  136. fill="none"
  137. viewBox="0 0 24 24"
  138. stroke="currentColor"
  139. stroke-width="2"
  140. >
  141. <path
  142. stroke-linecap="round"
  143. stroke-linejoin="round"
  144. 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"
  145. />
  146. </svg>
  147. </button>
  148. </div>
  149. <p
  150. v-if="captcha.error.value"
  151. id="captcha-error"
  152. class="mt-1.5 text-xs text-red-400 sm:text-sm"
  153. >
  154. {{ captcha.error.value }}
  155. </p>
  156. </div>
  157. <button
  158. type="submit"
  159. class="bg-gradient-to-r from-blue-700 to-blue-400 text-white text-base font-normal py-4 px-8 rounded-lg transition-all duration-300 hover:scale-105 hover:shadow-lg"
  160. :disabled="isSubmitting"
  161. >
  162. {{
  163. isSubmitting
  164. ? t("contact.submitting")
  165. : t("contact.submit")
  166. }}
  167. </button>
  168. </form>
  169. </div>
  170. <!-- 公司信息和营业时间 -->
  171. <div class="flex flex-col gap-8">
  172. <div
  173. class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg"
  174. >
  175. <div class="text-white text-3xl font-medium mb-6">
  176. {{ t("about.companyInfo.name") }}
  177. </div>
  178. <div class="flex flex-col gap-4">
  179. <div class="flex items-center gap-4">
  180. <div class="text-white/60 text-base font-normal">
  181. {{ t("about.overview.companyName") }}
  182. </div>
  183. <div class="text-white text-base font-normal">
  184. {{ t("about.companyInfo.companyName") }}
  185. </div>
  186. </div>
  187. <div class="flex items-center gap-4">
  188. <div class="text-white/60 text-base font-normal">
  189. {{ t("about.overview.location") }}
  190. </div>
  191. <div class="text-white text-base font-normal">
  192. {{ t("about.companyInfo.location") }}
  193. </div>
  194. </div>
  195. <div class="flex items-center gap-4">
  196. <div class="text-white/60 text-base font-normal">
  197. {{ t("about.overview.tel") }}
  198. </div>
  199. <div class="text-white text-base font-normal">
  200. 86)024-8399-0696
  201. </div>
  202. </div>
  203. <div class="flex items-center gap-4">
  204. <div class="text-white/60 text-base font-normal">
  205. {{ t("about.overview.email") }}
  206. </div>
  207. <div class="text-white text-base font-normal">
  208. hanye@hanye.cn
  209. </div>
  210. </div>
  211. </div>
  212. </div>
  213. <div
  214. class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg"
  215. >
  216. <div class="text-white text-3xl font-medium mb-6">
  217. {{ t("about.overview.businessHours") }}
  218. </div>
  219. <div class="text-white text-base font-normal">
  220. {{ t("about.companyInfo.businessHours") }}
  221. </div>
  222. </div>
  223. </div>
  224. </div>
  225. </div>
  226. </div>
  227. </div>
  228. </ErrorBoundary>
  229. </div>
  230. </template>
  231. <script setup lang="ts">
  232. /**
  233. * 联系页面
  234. * 展示公司信息和联系表单
  235. */
  236. import { useErrorHandler } from "~/composables/useErrorHandler";
  237. import { useCaptcha } from "~/composables/useCaptcha";
  238. import emailjs from '@emailjs/browser';
  239. const { t, locale } = useI18n();
  240. // EmailJS 配置
  241. const EMAILJS_SERVICE_ID = 'YOUR_SERVICE_ID'; // 替换为你的 Service ID
  242. const EMAILJS_TEMPLATE_ID = 'YOUR_TEMPLATE_ID'; // 替换为你的 Template ID
  243. const EMAILJS_PUBLIC_KEY = 'YOUR_PUBLIC_KEY'; // 替换为你的 Public Key
  244. const { error, isLoading, wrapAsync } = useErrorHandler();
  245. const captcha = useCaptcha();
  246. const isSubmitting = ref(false);
  247. const submitSuccess = ref(false);
  248. const form = ref({
  249. name: "",
  250. email: "",
  251. message: "",
  252. });
  253. /**
  254. * 处理表单提交
  255. * 使用 EmailJS 发送邮件
  256. */
  257. async function handleSubmit() {
  258. isSubmitting.value = true;
  259. submitSuccess.value = false;
  260. try {
  261. await wrapAsync(async () => {
  262. // 验证验证码
  263. if (!captcha.validateCaptcha()) {
  264. return;
  265. }
  266. // 准备邮件模板参数
  267. const templateParams = {
  268. from_name: form.value.name,
  269. from_email: form.value.email,
  270. message: form.value.message,
  271. to_name: 'Hanye Team',
  272. };
  273. // 发送邮件
  274. const response = await emailjs.send(
  275. EMAILJS_SERVICE_ID,
  276. EMAILJS_TEMPLATE_ID,
  277. templateParams,
  278. EMAILJS_PUBLIC_KEY
  279. );
  280. if (response.status === 200) {
  281. // 清空表单
  282. form.value = {
  283. name: "",
  284. email: "",
  285. message: "",
  286. };
  287. captcha.generateCaptcha(); // 成功后刷新验证码
  288. submitSuccess.value = true;
  289. return { success: true };
  290. } else {
  291. throw new Error('Failed to send email');
  292. }
  293. });
  294. } catch (error) {
  295. console.error('Error sending email:', error);
  296. throw error;
  297. } finally {
  298. isSubmitting.value = false;
  299. }
  300. }
  301. // SEO优化
  302. useHead({
  303. title: t("contact.title") + " - Hanye",
  304. meta: [
  305. {
  306. name: "description",
  307. content: t("contact.description"),
  308. },
  309. {
  310. name: "keywords",
  311. content: t("contact.keywords"),
  312. },
  313. ],
  314. });
  315. </script>