Full 1:1 migration preserving all visuals, animations and behavior: - Nuxt 4 + Vue 3 (script setup, TypeScript strict) with SSG output - Tailwind CSS v4 with @tailwindcss/vite - Ant Design Vue 4 (dark theme, RTL, Persian locale) for design system - Pinia for state management (ui + contact stores) - motion-v (framer-motion port) for all animations - lucide-vue-next for icons - SEO improvements: og:image, canonical fix, real 404 page, JSON-LD services - JS bundle reduced from 302KB to 239KB gzip - Docker/nginx deployment preserved (out/ -> nginx) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
191 lines
7.8 KiB
Vue
191 lines
7.8 KiB
Vue
<script setup lang="ts">
|
||
import { Button as AButton, Form as AForm, FormItem as AFormItem, Input as AInput, Textarea as ATextarea } from "ant-design-vue";
|
||
import type { Rule } from "ant-design-vue/es/form";
|
||
import {
|
||
CheckCircle2,
|
||
Clock,
|
||
Loader2,
|
||
Mail,
|
||
MapPin,
|
||
Phone,
|
||
Send,
|
||
Siren,
|
||
} from "lucide-vue-next";
|
||
import Reveal from "~/components/ui/Reveal.vue";
|
||
import SectionHead from "~/components/ui/SectionHead.vue";
|
||
|
||
const INFO = [
|
||
{
|
||
icon: MapPin,
|
||
label: "نشانی",
|
||
value: "تهران، خیابان کارگر شمالی، خیابان فر، کوچه دوم، پلاک ۱۲",
|
||
},
|
||
{ icon: Phone, label: "تلفن تماس", value: "۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰", ltr: true },
|
||
{ icon: Mail, label: "ایمیل", value: "info@ariaiman.ir", ltr: true },
|
||
{ icon: Clock, label: "ساعات کاری", value: "شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰" },
|
||
];
|
||
|
||
const required = (message: string): Rule[] => [
|
||
{ required: true, whitespace: true, message, trigger: "blur" },
|
||
];
|
||
|
||
const rules: Record<string, Rule[]> = {
|
||
name: required("لطفاً نام و نام خانوادگی را وارد کنید"),
|
||
org: required("لطفاً نام سازمان را وارد کنید"),
|
||
phone: required("لطفاً شماره تماس را وارد کنید"),
|
||
};
|
||
|
||
const contact = useContactStore();
|
||
</script>
|
||
|
||
<template>
|
||
<section
|
||
id="contact"
|
||
aria-labelledby="contact-title"
|
||
class="relative overflow-hidden border-t border-line py-24 md:py-32"
|
||
>
|
||
<div class="absolute inset-0 bg-grid-fine opacity-40" />
|
||
|
||
<div class="relative mx-auto max-w-7xl px-5 md:px-8">
|
||
<div class="grid gap-14 lg:grid-cols-2">
|
||
<!-- ---- info ---- -->
|
||
<div>
|
||
<SectionHead
|
||
heading-id="contact-title"
|
||
tag="۰۷ — تماس"
|
||
desc="فرم را پر کنید تا کارشناسان ما برای هماهنگی جلسه بازدید رایگان سایت شما تماس بگیرند؛ یا مستقیم با ما در ارتباط باشید."
|
||
>
|
||
<template #title>
|
||
بیایید ایمنی را
|
||
<br />
|
||
با هم جدی بگیریم
|
||
</template>
|
||
</SectionHead>
|
||
|
||
<div class="mt-10 space-y-1">
|
||
<Reveal v-for="(item, i) in INFO" :key="item.label" :delay="0.15 + i * 0.06">
|
||
<div class="group flex items-center gap-4 border-b border-line/70 py-4">
|
||
<span
|
||
class="flex size-11 shrink-0 items-center justify-center rounded-lg border border-line text-accent transition-colors duration-300 group-hover:border-accent/50"
|
||
>
|
||
<component :is="item.icon" class="size-5" />
|
||
</span>
|
||
<div class="min-w-0">
|
||
<div class="text-xs text-muted">{{ item.label }}</div>
|
||
<div
|
||
:class="`mt-1 text-sm font-medium ${item.ltr ? 'font-mono tracking-wider' : ''}`"
|
||
:dir="item.ltr ? 'ltr' : 'rtl'"
|
||
>
|
||
{{ item.value }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Reveal>
|
||
</div>
|
||
|
||
<Reveal :delay="0.45">
|
||
<div class="mt-8 flex items-start gap-4 rounded-xl border border-hazard/30 bg-hazard/5 p-5">
|
||
<Siren class="size-6 shrink-0 text-hazard" />
|
||
<div>
|
||
<div class="font-bold text-hazard">پاسخگویی اضطراری ۲۴/۷</div>
|
||
<p class="mt-1.5 text-sm leading-7 text-muted">
|
||
برای مشتریان دارای قرارداد سازمانی، خط ویژه حوادث و تأمین
|
||
فوری تجهیزات در تمام ساعات شبانهروز فعال است.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Reveal>
|
||
</div>
|
||
|
||
<!-- ---- form ---- -->
|
||
<Reveal :delay="0.15">
|
||
<div class="rounded-2xl border border-line bg-panel/60 p-6 backdrop-blur md:p-9">
|
||
<div class="font-mono text-xs tracking-[0.25em] text-accent">// فرم درخواست مشاوره</div>
|
||
<h3 class="mt-3 text-2xl font-black leading-snug">
|
||
در کمتر از ۲۴ ساعت
|
||
<br />
|
||
با شما تماس میگیریم
|
||
</h3>
|
||
|
||
<div
|
||
v-if="contact.status === 'success'"
|
||
class="mt-10 flex flex-col items-center rounded-xl border border-emerald-500/30 bg-emerald-500/5 p-10 text-center"
|
||
>
|
||
<CheckCircle2 class="size-14 text-emerald-400" />
|
||
<div class="mt-5 text-xl font-black">درخواست شما ثبت شد</div>
|
||
<p class="mt-2.5 max-w-sm text-sm leading-7 text-muted">
|
||
کارشناسان ما تا ۲۴ ساعت آینده برای هماهنگی جلسه بازدید با
|
||
شما تماس میگیرند.
|
||
</p>
|
||
<button
|
||
type="button"
|
||
class="mt-7 rounded-full border border-line px-7 py-3 text-sm transition-colors hover:border-accent hover:text-accent"
|
||
@click="contact.reset"
|
||
>
|
||
ثبت درخواست جدید
|
||
</button>
|
||
</div>
|
||
|
||
<AForm
|
||
v-else
|
||
class="contact-form"
|
||
layout="vertical"
|
||
:model="contact.form"
|
||
:rules="rules"
|
||
hide-required-mark
|
||
@finish="contact.submit"
|
||
>
|
||
<div class="mt-8 grid gap-5 sm:grid-cols-2">
|
||
<AFormItem name="name" label="نام و نام خانوادگی *" html-for="f-name">
|
||
<AInput id="f-name" v-model:value="contact.form.name" placeholder="مثلاً: علی رضایی" />
|
||
</AFormItem>
|
||
<AFormItem name="org" label="نام سازمان *" html-for="f-org">
|
||
<AInput
|
||
id="f-org"
|
||
v-model:value="contact.form.org"
|
||
placeholder="مثلاً: شرکت پتروشیمی خلیج"
|
||
/>
|
||
</AFormItem>
|
||
<AFormItem class="sm:col-span-2" name="phone" label="شماره تماس *" html-for="f-phone">
|
||
<AInput
|
||
id="f-phone"
|
||
v-model:value="contact.form.phone"
|
||
type="tel"
|
||
dir="ltr"
|
||
placeholder="0912 000 0000"
|
||
/>
|
||
</AFormItem>
|
||
<AFormItem class="sm:col-span-2" name="message" label="شرح درخواست" html-for="f-msg">
|
||
<ATextarea
|
||
id="f-msg"
|
||
v-model:value="contact.form.message"
|
||
:rows="4"
|
||
placeholder="تجهیزات موردنیاز، تعداد پرسنل یا هر نکتهای که لازم است بدانیم..."
|
||
/>
|
||
</AFormItem>
|
||
<div class="sm:col-span-2">
|
||
<AButton
|
||
type="primary"
|
||
html-type="submit"
|
||
class="contact-submit"
|
||
:disabled="contact.status === 'loading'"
|
||
>
|
||
<template v-if="contact.status === 'loading'">
|
||
<Loader2 class="size-5 animate-spin" />
|
||
در حال ارسال...
|
||
</template>
|
||
<template v-else>
|
||
<Send class="size-5" />
|
||
ارسال درخواست مشاوره
|
||
</template>
|
||
</AButton>
|
||
</div>
|
||
</div>
|
||
</AForm>
|
||
</div>
|
||
</Reveal>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</template>
|