diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3d294e0..5a46dd9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -101,7 +101,9 @@ model BlindBoxIdea { category String? timeSlot String? estimatedMinutes Int? - outdoor Boolean? + costLevel String? + intensity String? + needsBooking Boolean? searchQuery String? searchType String? diff --git a/src/__tests__/helpers/fixtures.ts b/src/__tests__/helpers/fixtures.ts index 667cb59..25c53d1 100644 --- a/src/__tests__/helpers/fixtures.ts +++ b/src/__tests__/helpers/fixtures.ts @@ -97,7 +97,9 @@ export const TEST_BLINDBOX_IDEA = { category: "outdoor", timeSlot: "morning", estimatedMinutes: 120, - outdoor: true, + costLevel: "free", + intensity: "active", + needsBooking: false, searchQuery: "公园", searchType: "category", drawnById: null, diff --git a/src/app/api/blindbox/route.test.ts b/src/app/api/blindbox/route.test.ts index 6a9e39d..ceb9503 100644 --- a/src/app/api/blindbox/route.test.ts +++ b/src/app/api/blindbox/route.test.ts @@ -12,7 +12,9 @@ vi.mock("@/lib/ai", () => ({ category: "outdoor", timeSlot: "morning", estimatedMinutes: 120, - outdoor: true, + costLevel: "free", + intensity: "active", + needsBooking: false, searchQuery: "公园", searchType: "category", }), diff --git a/src/app/api/blindbox/route.ts b/src/app/api/blindbox/route.ts index 91d4a42..ad406b4 100644 --- a/src/app/api/blindbox/route.ts +++ b/src/app/api/blindbox/route.ts @@ -32,7 +32,9 @@ export const POST = apiHandler(async (req) => { category: tags.category, timeSlot: tags.timeSlot, estimatedMinutes: tags.estimatedMinutes, - outdoor: tags.outdoor, + costLevel: tags.costLevel, + intensity: tags.intensity, + needsBooking: tags.needsBooking, searchQuery: tags.searchQuery, searchType: tags.searchType, }, @@ -65,7 +67,9 @@ export const GET = apiHandler(async (req) => { category: true, timeSlot: true, estimatedMinutes: true, - outdoor: true, + costLevel: true, + intensity: true, + needsBooking: true, searchQuery: true, searchType: true, }, @@ -109,7 +113,9 @@ export const PUT = apiHandler(async (req) => { category: tags.category, timeSlot: tags.timeSlot, estimatedMinutes: tags.estimatedMinutes, - outdoor: tags.outdoor, + costLevel: tags.costLevel, + intensity: tags.intensity, + needsBooking: tags.needsBooking, searchQuery: tags.searchQuery, searchType: tags.searchType, }, diff --git a/src/app/blindbox/[code]/page.tsx b/src/app/blindbox/[code]/page.tsx index 4650768..1001535 100644 --- a/src/app/blindbox/[code]/page.tsx +++ b/src/app/blindbox/[code]/page.tsx @@ -448,7 +448,9 @@ export default function BlindboxRoomPage() { category: data.tags.category, timeSlot: data.tags.timeSlot, estimatedMinutes: data.tags.estimatedMinutes, - outdoor: data.tags.outdoor, + costLevel: data.tags.costLevel, + intensity: data.tags.intensity, + needsBooking: data.tags.needsBooking, searchQuery: data.tags.searchQuery, searchType: data.tags.searchType, }, @@ -490,7 +492,9 @@ export default function BlindboxRoomPage() { category: data.tags.category, timeSlot: data.tags.timeSlot, estimatedMinutes: data.tags.estimatedMinutes, - outdoor: data.tags.outdoor, + costLevel: data.tags.costLevel, + intensity: data.tags.intensity, + needsBooking: data.tags.needsBooking, searchQuery: data.tags.searchQuery, searchType: data.tags.searchType, }, diff --git a/src/components/BlindboxMyIdeas.tsx b/src/components/BlindboxMyIdeas.tsx index 0b85877..e2724b9 100644 --- a/src/components/BlindboxMyIdeas.tsx +++ b/src/components/BlindboxMyIdeas.tsx @@ -16,6 +16,8 @@ import { Dumbbell, Landmark, Coffee, + Palette, + Mountain, } from "lucide-react"; import type { IdeaCategory } from "@/types"; @@ -26,7 +28,9 @@ export interface MyIdea { category?: string | null; timeSlot?: string | null; estimatedMinutes?: number | null; - outdoor?: boolean | null; + costLevel?: string | null; + intensity?: string | null; + needsBooking?: boolean | null; searchQuery?: string | null; searchType?: string | null; } @@ -42,6 +46,8 @@ const CATEGORY_CONFIG: Record< sports: { icon: Dumbbell, color: "text-amber-400", label: "运动" }, culture: { icon: Landmark, color: "text-violet-400", label: "文化" }, relaxation: { icon: Coffee, color: "text-teal-400", label: "休闲" }, + experience: { icon: Palette, color: "text-rose-400", label: "体验" }, + nature: { icon: Mountain, color: "text-lime-400", label: "自然" }, }; function CategoryBadge({ category }: { category?: string | null }) { @@ -49,7 +55,7 @@ function CategoryBadge({ category }: { category?: string | null }) { const cfg = CATEGORY_CONFIG[category as IdeaCategory]; if (!cfg) return 💡; const Icon = cfg.icon; - return ; + return ; } function DurationLabel({ minutes }: { minutes?: number | null }) { @@ -62,6 +68,39 @@ function DurationLabel({ minutes }: { minutes?: number | null }) { ); } +const COST_LABELS: Record = { + free: "免费", + budget: "实惠", + moderate: "适中", + premium: "高端", +}; + +const INTENSITY_LABELS: Record = { + chill: "轻松", + moderate: "适度", + active: "活跃", +}; + +function TagPills({ idea }: { idea: MyIdea }) { + const pills: string[] = []; + if (idea.costLevel && COST_LABELS[idea.costLevel]) pills.push(COST_LABELS[idea.costLevel]); + if (idea.intensity && INTENSITY_LABELS[idea.intensity]) pills.push(INTENSITY_LABELS[idea.intensity]); + if (idea.needsBooking) pills.push("需预约"); + + if (pills.length === 0 && !idea.estimatedMinutes) return null; + + return ( +
+ {pills.map((label) => ( + + {label} + + ))} + +
+ ); +} + function MyIdeaItem({ idea, onEdit, @@ -86,7 +125,7 @@ function MyIdeaItem({ return ( -

{idea.content}

- +
+

{idea.content}

+ +