| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- import React, { useState } from 'react';
- import { useForm } from 'react-hook-form';
- import { zodResolver } from '@hookform/resolvers/zod';
- import { z } from 'zod';
- import { Button } from '@/client/components/ui/button';
- import { Input } from '@/client/components/ui/input';
- import { Textarea } from '@/client/components/ui/textarea';
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
- import { Label } from '@/client/components/ui/label';
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
- import { toast } from 'react-toastify';
- import { publicConsultationRequestClient } from '@/client/api';
- import type { InferRequestType } from 'hono/client';
- // 表单验证Schema
- const ConsultationRequestFormSchema = z.object({
- customerName: z.string().min(2, '姓名至少2个字符').max(255),
- companyName: z.string().max(255).optional(),
- phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号'),
- email: z.string().email('请输入正确的邮箱地址').optional(),
- projectType: z.string().min(1, '请选择项目类型'),
- projectDescription: z.string().min(10, '项目描述至少10个字符').max(2000),
- budgetRange: z.string().max(100).optional(),
- timeline: z.string().max(100).optional(),
- isGuest: z.boolean().default(true)
- });
- type ConsultationRequestFormData = z.infer<typeof ConsultationRequestFormSchema>;
- // 项目类型选项
- const projectTypes = [
- { value: '企业ERP系统', label: '企业ERP系统' },
- { value: '智慧政务平台', label: '智慧政务平台' },
- { value: '医疗信息化系统', label: '医疗信息化系统' },
- { value: '教育信息化平台', label: '教育信息化平台' },
- { value: '电商平台', label: '电商平台' },
- { value: '移动应用开发', label: '移动应用开发' },
- { value: '大数据分析平台', label: '大数据分析平台' },
- { value: '人工智能应用', label: '人工智能应用' },
- { value: '物联网系统', label: '物联网系统' },
- { value: '其他', label: '其他' }
- ];
- // 预算范围选项
- const budgetRanges = [
- { value: '10万以下', label: '10万以下' },
- { value: '10-50万', label: '10-50万' },
- { value: '50-100万', label: '50-100万' },
- { value: '100-500万', label: '100-500万' },
- { value: '500万以上', label: '500万以上' }
- ];
- // 时间要求选项
- const timelines = [
- { value: '1-3个月', label: '1-3个月' },
- { value: '3-6个月', label: '3-6个月' },
- { value: '6-12个月', label: '6-12个月' },
- { value: '12个月以上', label: '12个月以上' }
- ];
- interface ConsultationRequestFormProps {
- onSuccess?: () => void;
- onCancel?: () => void;
- className?: string;
- }
- export default function ConsultationRequestForm({
- onSuccess,
- onCancel,
- className = ''
- }: ConsultationRequestFormProps) {
- const [isSubmitting, setIsSubmitting] = useState(false);
- const {
- register,
- handleSubmit,
- formState: { errors },
- setValue,
- watch
- } = useForm<ConsultationRequestFormData>({
- resolver: zodResolver(ConsultationRequestFormSchema),
- defaultValues: {
- isGuest: true
- }
- });
- const onSubmit = async (data: ConsultationRequestFormData) => {
- setIsSubmitting(true);
- try {
- const response = await publicConsultationRequestClient.$post({
- json: data
- });
- if (response.status === 200) {
- const result = await response.json();
- toast.success(result.message || '客户需求提交成功!');
- onSuccess?.();
- } else {
- const error = await response.json();
- toast.error(error.message || '提交失败,请稍后重试');
- }
- } catch (error) {
- console.error('提交客户需求失败:', error);
- toast.error('网络错误,请检查网络连接后重试');
- } finally {
- setIsSubmitting(false);
- }
- };
- return (
- <Card className={`w-full max-w-2xl mx-auto ${className}`}>
- <CardHeader>
- <CardTitle className="text-2xl font-bold">项目咨询需求</CardTitle>
- <CardDescription>
- 请填写您的项目需求信息,我们将尽快与您联系并提供专业的咨询服务
- </CardDescription>
- </CardHeader>
- <CardContent>
- <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
- {/* 基本信息 */}
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
- <div className="space-y-2">
- <Label htmlFor="customerName">客户姓名 *</Label>
- <Input
- id="customerName"
- placeholder="请输入您的姓名"
- {...register('customerName')}
- className={errors.customerName ? 'border-red-500' : ''}
- />
- {errors.customerName && (
- <p className="text-sm text-red-500">{errors.customerName.message}</p>
- )}
- </div>
- <div className="space-y-2">
- <Label htmlFor="companyName">公司名称</Label>
- <Input
- id="companyName"
- placeholder="请输入公司名称(选填)"
- {...register('companyName')}
- />
- </div>
- <div className="space-y-2">
- <Label htmlFor="phone">手机号 *</Label>
- <Input
- id="phone"
- placeholder="请输入手机号"
- {...register('phone')}
- className={errors.phone ? 'border-red-500' : ''}
- />
- {errors.phone && (
- <p className="text-sm text-red-500">{errors.phone.message}</p>
- )}
- </div>
- <div className="space-y-2">
- <Label htmlFor="email">邮箱地址</Label>
- <Input
- id="email"
- type="email"
- placeholder="请输入邮箱地址(选填)"
- {...register('email')}
- className={errors.email ? 'border-red-500' : ''}
- />
- {errors.email && (
- <p className="text-sm text-red-500">{errors.email.message}</p>
- )}
- </div>
- </div>
- {/* 项目信息 */}
- <div className="space-y-4">
- <div className="space-y-2">
- <Label htmlFor="projectType">项目类型 *</Label>
- <Select onValueChange={(value) => setValue('projectType', value)}>
- <SelectTrigger className={errors.projectType ? 'border-red-500' : ''}>
- <SelectValue placeholder="请选择项目类型" />
- </SelectTrigger>
- <SelectContent>
- {projectTypes.map((type) => (
- <SelectItem key={type.value} value={type.value}>
- {type.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- {errors.projectType && (
- <p className="text-sm text-red-500">{errors.projectType.message}</p>
- )}
- </div>
- <div className="space-y-2">
- <Label htmlFor="projectDescription">项目描述 *</Label>
- <Textarea
- id="projectDescription"
- placeholder="请详细描述您的项目需求、目标和期望效果..."
- rows={4}
- {...register('projectDescription')}
- className={errors.projectDescription ? 'border-red-500' : ''}
- />
- {errors.projectDescription && (
- <p className="text-sm text-red-500">{errors.projectDescription.message}</p>
- )}
- <p className="text-sm text-gray-500">
- 已输入 {watch('projectDescription')?.length || 0} / 2000 字符
- </p>
- </div>
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
- <div className="space-y-2">
- <Label htmlFor="budgetRange">预算范围</Label>
- <Select onValueChange={(value) => setValue('budgetRange', value)}>
- <SelectTrigger>
- <SelectValue placeholder="请选择预算范围(选填)" />
- </SelectTrigger>
- <SelectContent>
- {budgetRanges.map((range) => (
- <SelectItem key={range.value} value={range.value}>
- {range.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- <div className="space-y-2">
- <Label htmlFor="timeline">时间要求</Label>
- <Select onValueChange={(value) => setValue('timeline', value)}>
- <SelectTrigger>
- <SelectValue placeholder="请选择时间要求(选填)" />
- </SelectTrigger>
- <SelectContent>
- {timelines.map((timeline) => (
- <SelectItem key={timeline.value} value={timeline.value}>
- {timeline.label}
- </SelectItem>
- ))}
- </SelectContent>
- </Select>
- </div>
- </div>
- </div>
- {/* 操作按钮 */}
- <div className="flex gap-4 pt-4">
- <Button
- type="submit"
- className="flex-1 bg-blue-600 hover:bg-blue-700"
- disabled={isSubmitting}
- >
- {isSubmitting ? '提交中...' : '提交咨询需求'}
- </Button>
- {onCancel && (
- <Button
- type="button"
- variant="outline"
- onClick={onCancel}
- disabled={isSubmitting}
- >
- 取消
- </Button>
- )}
- </div>
- <p className="text-sm text-gray-500 text-center">
- 提交即表示您同意我们的服务条款和隐私政策
- </p>
- </form>
- </CardContent>
- </Card>
- );
- }
|