feat: 美食多选 + 自定义输入标签
This commit is contained in:
+63
-20
@@ -39,7 +39,8 @@ export default function PanicPage() {
|
|||||||
const [fetchingSuggestions, setFetchingSuggestions] = useState(false);
|
const [fetchingSuggestions, setFetchingSuggestions] = useState(false);
|
||||||
const [radius, setRadius] = useState(3000);
|
const [radius, setRadius] = useState(3000);
|
||||||
const [priceRange, setPriceRange] = useState("any");
|
const [priceRange, setPriceRange] = useState("any");
|
||||||
const [cuisine, setCuisine] = useState("");
|
const [cuisines, setCuisines] = useState<string[]>([]);
|
||||||
|
const [cuisineInput, setCuisineInput] = useState("");
|
||||||
const suggestRef = useRef<HTMLDivElement>(null);
|
const suggestRef = useRef<HTMLDivElement>(null);
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>(null);
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>(null);
|
||||||
|
|
||||||
@@ -50,14 +51,15 @@ export default function PanicPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prefs = getCachedPreferences();
|
const prefs = getCachedPreferences();
|
||||||
if (prefs.cuisine) setCuisine(prefs.cuisine);
|
if (prefs.cuisines) setCuisines(prefs.cuisines);
|
||||||
|
else if (prefs.cuisine) setCuisines([prefs.cuisine]);
|
||||||
if (prefs.priceRange) setPriceRange(prefs.priceRange);
|
if (prefs.priceRange) setPriceRange(prefs.priceRange);
|
||||||
if (prefs.radius) setRadius(prefs.radius);
|
if (prefs.radius) setRadius(prefs.radius);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSceneChange = useCallback((s: SceneType) => {
|
const handleSceneChange = useCallback((s: SceneType) => {
|
||||||
setScene(s);
|
setScene(s);
|
||||||
setCuisine("");
|
setCuisines([]);
|
||||||
setPriceRange("any");
|
setPriceRange("any");
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ export default function PanicPage() {
|
|||||||
const res = await fetch("/api/room/create", {
|
const res = await fetch("/api/room/create", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ ...coords, radius, priceRange, cuisine, userId: getUserId(), scene }),
|
body: JSON.stringify({ ...coords, radius, priceRange, cuisine: cuisines.join("|"), userId: getUserId(), scene }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -395,16 +397,27 @@ export default function PanicPage() {
|
|||||||
<div className="relative flex flex-1 items-center">
|
<div className="relative flex flex-1 items-center">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={sceneConfig.tagPlaceholder}
|
placeholder={cuisines.length === 0 ? sceneConfig.tagPlaceholder : "继续添加..."}
|
||||||
value={cuisine}
|
value={cuisineInput}
|
||||||
onChange={(e) => setCuisine(e.target.value)}
|
onChange={(e) => setCuisineInput(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
const val = cuisineInput.trim();
|
||||||
|
if (val && !cuisines.includes(val)) {
|
||||||
|
setCuisines((prev) => [...prev, val]);
|
||||||
|
}
|
||||||
|
setCuisineInput("");
|
||||||
|
}
|
||||||
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="h-7 w-full rounded-full border-none bg-elevated pl-3 pr-7 text-xs text-foreground outline-none ring-1 ring-subtle transition-colors placeholder:text-dim focus:ring-2 focus:ring-orange-500/50 disabled:opacity-50"
|
className="h-7 w-full rounded-full border-none bg-elevated pl-3 pr-7 text-xs text-foreground outline-none ring-1 ring-subtle transition-colors placeholder:text-dim focus:ring-2 focus:ring-orange-500/50 disabled:opacity-50"
|
||||||
/>
|
/>
|
||||||
{cuisine && !loading && (
|
{cuisineInput && !loading && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setCuisine("")}
|
type="button"
|
||||||
aria-label="清除口味"
|
onClick={() => setCuisineInput("")}
|
||||||
|
aria-label="清除输入"
|
||||||
className="absolute right-2 flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-secondary"
|
className="absolute right-2 flex h-4 w-4 items-center justify-center rounded-full text-muted hover:text-secondary"
|
||||||
>
|
>
|
||||||
<X size={12} />
|
<X size={12} />
|
||||||
@@ -413,25 +426,55 @@ export default function PanicPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<span className="w-8 shrink-0 text-xs font-medium text-muted"></span>
|
<span className="mt-0.5 w-8 shrink-0 text-xs font-medium text-muted"></span>
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
<div className="flex flex-1 flex-wrap items-center gap-1.5">
|
||||||
<Flame size={11} className="shrink-0 text-orange-400" />
|
<Flame size={11} className="shrink-0 text-orange-400" />
|
||||||
{sceneConfig.hotTags.map((tag) => (
|
{sceneConfig.hotTags.map((tag) => {
|
||||||
|
const selected = cuisines.includes(tag);
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tag}
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setCuisines((prev) =>
|
||||||
|
selected ? prev.filter((t) => t !== tag) : [...prev, tag],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={loading}
|
||||||
|
className={`h-6 rounded-full px-2.5 text-xs font-medium transition-colors disabled:opacity-50 ${
|
||||||
|
selected
|
||||||
|
? "bg-orange-500 text-white shadow-sm shadow-orange-500/25"
|
||||||
|
: "bg-elevated text-muted hover:bg-subtle"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{cuisines.filter((t) => !sceneConfig.hotTags.includes(t)).map((tag) => (
|
||||||
<button
|
<button
|
||||||
key={tag}
|
key={tag}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setCuisine(tag)}
|
onClick={() => setCuisines((prev) => prev.filter((t) => t !== tag))}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className={`h-6 rounded-full px-2.5 text-xs font-medium transition-colors disabled:opacity-50 ${
|
className="flex h-6 items-center gap-1 rounded-full bg-orange-500 pl-2.5 pr-1.5 text-xs font-medium text-white shadow-sm shadow-orange-500/25 disabled:opacity-50"
|
||||||
cuisine === tag
|
|
||||||
? "bg-orange-500 text-white shadow-sm shadow-orange-500/25"
|
|
||||||
: "bg-elevated text-muted hover:bg-subtle"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{tag}
|
{tag}
|
||||||
|
<X size={10} />
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
{cuisines.length > 0 && !loading && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCuisines([])}
|
||||||
|
aria-label="清除口味"
|
||||||
|
className="ml-1 flex h-5 items-center gap-0.5 rounded-full px-1.5 text-xs text-muted hover:text-secondary"
|
||||||
|
>
|
||||||
|
<X size={10} />
|
||||||
|
清空
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export interface UserProfile {
|
|||||||
|
|
||||||
export interface UserPreferences {
|
export interface UserPreferences {
|
||||||
cuisine?: string;
|
cuisine?: string;
|
||||||
|
cuisines?: string[];
|
||||||
priceRange?: string;
|
priceRange?: string;
|
||||||
radius?: number;
|
radius?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user