123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- <template>
- <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-cyan-400 rounded-full border-t-transparent"
- ></div>
- </div>
-
- <div v-else>
- <div class="max-w-full xl:px-2 lg:px-2 md:px-4 px-4 mt-6 mb-20">
- <div class="max-w-screen-2xl mx-auto">
- <nuxt-link
- :to="`${homepagePath}/`"
- class="justify-start text-white/60 text-base font-normal"
- >{{ t("common.home") }}</nuxt-link
- >
- <span class="text-white/60 text-base font-normal px-2"> / </span>
- <nuxt-link
- :to="`${homepagePath}/contact`"
- class="text-white text-base font-normal"
- >{{ t("contact.title") }}</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-2 gap-8">
- <!-- 联系表单 -->
- <div
- class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg"
- >
- <div class="text-white text-3xl font-medium mb-6">
- {{ t("contact.title") }}
- </div>
- <form @submit="handleSubmit" class="flex flex-col gap-6">
- <div class="relative">
- <input
- v-model="form.name"
- type="text"
- id="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] border-gray-600 focus:border-cyan-400"
- :placeholder="t('contact.name')"
- required
- />
- <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 text-gray-400 peer-focus:text-cyan-400"
- >
- {{ t("contact.name") }}
- </label>
- </div>
-
- <div class="relative">
- <input
- v-model="form.email"
- type="email"
- id="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] border-gray-600 focus:border-cyan-400"
- :placeholder="t('contact.email')"
- required
- />
- <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 text-gray-400 peer-focus:text-cyan-400"
- >
- {{ t("contact.email") }}
- </label>
- </div>
-
- <div class="relative">
- <textarea
- v-model="form.message"
- id="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] border-gray-600 focus:border-cyan-400"
- :placeholder="t('contact.message')"
- required
- ></textarea>
- <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 text-gray-400 peer-focus:text-cyan-400"
- >
- {{ t("contact.message") }}
- </label>
- </div>
-
- <!-- 验证码部分 -->
- <div class="relative pt-2">
- <div class="flex items-center space-x-3">
- <div class="flex-grow relative">
- <input
- type="text"
- id="captcha"
- v-model="captcha.state.userInput"
- 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-cyan-400',
- ]"
- :placeholder="t('contact.captcha')"
- 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-cyan-400',
- ]"
- >
- {{ t("contact.captcha") }}
- </label>
- </div>
- <div
- 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"
- v-html="captcha.captchaSvg.value"
- @click="refreshCaptcha"
- :title="t('contact.refreshCaptcha')"
- style="line-height: 0"
- ></div>
- <button
- type="button"
- @click="refreshCaptcha"
- class="flex-shrink-0 p-2 text-gray-500 hover:text-cyan-400 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 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.refreshCaptcha')"
- :title="t('contact.refreshCaptcha')"
- >
- <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 }}
- </p>
- </div>
-
- <!-- 表单提交状态提示 -->
- <div
- v-if="formStatus.show"
- class="px-1 py-2 rounded-md transition-all duration-300"
- :class="[
- formStatus.type === 'success'
- ? 'bg-green-500/20 text-green-400'
- : 'bg-red-500/20 text-red-400',
- ]"
- >
- {{ formStatus.message }}
- </div>
-
- <button
- type="submit"
- class="bg-cyan-400 text-zinc-900 relative text-base font-normal py-4 px-8 rounded-lg transition-all duration-300 hover:shadow-lg hover:bg-cyan-500"
- :disabled="isSubmitting"
- >
- {{
- isSubmitting
- ? t("contact.submitting")
- : t("contact.submit")
- }}
- </button>
- </form>
- </div>
-
- <!-- 公司信息和营业时间 -->
- <div class="flex flex-col gap-8">
- <div
- class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg"
- >
- <div class="text-white text-3xl font-medium mb-6">
- {{ t("about.companyInfo.name") }}
- </div>
- <div class="flex flex-col gap-4">
- <div class="flex items-center gap-4">
- <div class="text-white/60 text-base font-normal">
- {{ t("about.overview.companyName") }}
- </div>
- <div class="text-white text-base font-normal">
- {{ t("about.companyInfo.companyName") }}
- </div>
- </div>
- <div class="flex items-center gap-4">
- <div class="text-white/60 text-base font-normal">
- {{ t("about.overview.location") }}
- </div>
- <div class="text-white text-base font-normal">
- {{ t("about.companyInfo.location") }}
- </div>
- </div>
- <div class="flex items-center gap-4">
- <div class="text-white/60 text-base font-normal">
- {{ t("about.overview.tel") }}
- </div>
- <div class="text-white text-base font-normal">
- +86-024-83990696
- </div>
- </div>
- <div class="flex items-center gap-4">
- <div class="text-white/60 text-base font-normal">
- {{ t("about.overview.email") }}
- </div>
- <div class="text-white text-base font-normal">
- hanye@hanye.cn
- </div>
- </div>
- </div>
- </div>
-
- <div
- class="bg-zinc-900 rounded-lg p-8 transition-all duration-300 hover:bg-zinc-800 hover:shadow-lg"
- >
- <div class="text-white text-3xl font-medium mb-6">
- {{ t("about.overview.businessHours") }}
- </div>
- <div class="text-white text-base font-normal">
- {{ t("about.companyInfo.businessHours") }}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </ErrorBoundary>
- </div>
- </template>
-
- <script setup lang="ts">
- /**
- * 联系页面
- * 展示公司信息和联系表单
- */
- import { useErrorHandler } from "~/composables/useErrorHandler";
- import { useCaptcha } from "~/composables/useCaptcha";
- const { t, locale } = useI18n();
-
- const homepagePath = computed(() => {
- return locale.value === "zh" ? "" : `/${locale.value}`;
- });
-
- const { error, isLoading, wrapAsync } = useErrorHandler();
- const captcha = useCaptcha();
- const isSubmitting = ref(false);
- const submitSuccess = ref(false);
-
- // 确保页面载入后验证码正确初始化
- onMounted(() => {
- // 给页面元素加载的时间
- setTimeout(() => {
- refreshCaptcha();
- }, 100);
- });
-
- // 表单状态信息
- const formStatus = reactive({
- show: false,
- type: "success",
- message: "",
- });
-
- /**
- * 刷新验证码
- */
- function refreshCaptcha() {
- captcha.generateCaptcha();
- }
-
- // 显示表单状态信息
- function showFormStatus(type: "success" | "error", message: string) {
- formStatus.show = true;
- formStatus.type = type;
- formStatus.message = message;
-
- // 5秒后自动隐藏
- setTimeout(() => {
- formStatus.show = false;
- }, 5000);
- }
-
- const form = ref({
- name: "",
- email: "",
- message: "",
- });
- /**
- * 处理表单提交
- */
- async function handleSubmit(event: Event) {
- // 阻止表单默认提交行为,防止重复提交
- event.preventDefault();
-
- if (isSubmitting.value) {
- return; // 如果已经在提交中,不重复处理
- }
-
- isSubmitting.value = true;
- submitSuccess.value = false;
-
- try {
- // 验证验证码
- const isCaptchaValid = captcha.validateCaptcha();
-
- if (!isCaptchaValid) {
- isSubmitting.value = false;
- return;
- }
-
- // 使用单独的异步函数发送邮件,减少嵌套
- await sendEmail();
- } catch (error) {
- console.error("Error during form submission:", error);
- showFormStatus("error", t("contact.submitError"));
- } finally {
- isSubmitting.value = false;
- }
- }
-
- /**
- * 发送邮件的函数
- */
- async function sendEmail() {
- try {
- const result = await wrapAsync(async () => {
- const message_template = `
- 📨 来自Hanye官网的联系表单
-
- 姓名:${form.value.name}
- 邮箱:${form.value.email}
-
- 消息内容:
- ${form.value.message}
-
- 提交时间:${new Date().toLocaleString()}
- 来源页面:${window.location.href}
-
- ---
- 此邮件由Hanye官网的联系表单自动发送,请勿回复。
- `;
- const title = `您有一封来自Hanye官网的联系表单`;
- const receiveEmail = "hanye@hanye.cn";
-
- // 对包含HTML标签的参数进行URL编码
- const encodedMessage = encodeURIComponent(message_template);
- const encodedTitle = encodeURIComponent(title);
-
- // 发送邮件
- const response = await fetch(
- `https://digital.sohomall.jp/prod-api/system/zsEmail/sendEmailWeb?receiveEmail=${receiveEmail}&context=${encodedMessage}&title=${encodedTitle}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- }
- );
-
- if (response.status === 200) {
- // 清空表单
- form.value = {
- name: "",
- email: "",
- message: "",
- };
- captcha.state.userInput = ""; // 清空验证码输入
- captcha.generateCaptcha(); // 成功后刷新验证码
- submitSuccess.value = true;
- showFormStatus("success", t("contact.submitSuccess"));
- return { success: true };
- } else {
- showFormStatus("error", t("contact.submitError"));
- throw new Error("Failed to send email");
- }
- });
-
- return result;
- } catch (error) {
- console.error("Error sending email:", error);
- showFormStatus("error", t("contact.submitError"));
- throw error;
- }
- }
-
- // SEO优化
- useHead({
- title: t("contact.title") + " - Hanye",
- meta: [
- {
- name: "description",
- content: t("contact.description"),
- },
- {
- name: "keywords",
- content: t("contact.keywords"),
- },
- ],
- });
- </script>
|