test/1114/app/components/weather/WeatherSearchForm.tsx

111 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//天气搜索表单组件
import { cn } from "@/components/lib/utils";
import React ,{useState,type FormEvent,type ChangeEvent, use} from "react";
import { Button } from "../ui/button";
import { Input} from "../ui/input";
import {useWeatherStore} from "@/store/weatherStore";
import { AlertCircle, Loader2, RefreshCw, Search } from "lucide-react";
//城市输入验证搜索刷新
export function WeatherSearchForm() {
const {isLoading,currentWeather,setCurrentWeather,refreshWeather} = useWeatherStore();
const [city, setCity] = useState('');
const [inputError, setInputError] = useState('');
//输入框内容
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setCity(event.target.value);
setInputError('');
};
//表单提交验证
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const trimmed = city.trim();
if (!trimmed) {
setInputError('请输入城市名称');
return;
}
if (trimmed.length < 2) {
setInputError('城市名称至少需要2个字符');
return;
}
if (!/^[\u4e00-\u9fa5a-zA-Z\s-]+$/.test(trimmed)) {
setInputError('城市名称包含无效字符');
return;
}
await searchWeather(trimmed);
};
return (
<div className="p-6 border-b boarder-slate-200/50 dark:border-slate-700/50">
<form onSubmit={handleSubmit} className="space-by-3">
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<Search className="w-5 h-5 text-slate-400 dark:text-slate-500" />
</div>
<input
type='text'
value={city}
onChange={handleInputChange}
placeholder="搜索城市如北京、上海、London..."
disabled ={isLoading}
className={cn(
'h-12 pl-12 pr-4 text-base rouded-xl',
'bg-slate-50/50 dark:bg-slate-900/50',
'focus:bg-white dark:focus:bg-slate-900',
'transition-all duration-200',
inputError && 'border-red-300 dark:border-red-700'
)}
/>
</div>
//输入错误提示
{inputError && (
<div className="flex items-start gap-2 px-3 py-2 rouded-lg bg-red-50/80
dark:bg-red-900/20 animate-in slide-in-form-top-1 fade-in">
<AlertCircle className="w-4 h-4 text-red-500 mt-0.5" />
<p className="text-xs text-red-600 dark:text-red-400 font-medium">
{inputError}
</p>
</div>
)}
//搜索刷新按钮
<div className="flex gap-2">
<Button
type ='submit'
disabled ={isLoading || !city.trim()}
className="flex-1 h-11 rouded-xl bg-gradient-to-r from-blue-600
to-cyan-600 hover:from-blue-700 hover:to-cyan-700 shadow-blue-500/30
honer:shadow-xl hover:shadow-cyan-500/40 disabled:opacity-50 transition-all duration-300">
{isLoading ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
<span className="ml-2">...</span>
</>
):(
<>
<Search className="w-4 h-4 animate-spin" />
<span className="ml-2"></span>
</>
)}
</Button>
{currentWeather && (
<Button
type="button"
variant ="outline"
onClick={refreshWeather}
disabled={isLoading}
className="h-11 px-4 rouded-xl"
title="刷新数据"
>
<RefreshCw className={cn("w-4 h-4",isLoading && "animate-spin")} />
</Button>
)
}
</div>
</form>
</div>
);
}