diff --git a/src/app/globals.css b/src/app/globals.css index 6bd7031..d3f8d19 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -4,6 +4,40 @@ @tailwind components; @tailwind utilities; +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + @layer utilities { .text-balance { text-wrap: balance; diff --git a/src/app/page.tsx b/src/app/page.tsx index 6fe62d1..a557ec8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,100 +1,57 @@ -import Image from "next/image"; +"use client"; + +import { useState } from "react"; +import { items, categories, locations } from "@/data/mockData"; +import ItemList from "@/components/items/ItemList"; +import CategoryManager from "@/components/categories/CategoryManager"; +import LocationManager from "@/components/locations/LocationManager"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. Save and see your changes instantly.
  4. -
+ const [activeTab, setActiveTab] = useState("items"); -
- - Vercel logomark - Deploy now - - - Read our docs - + return ( +
+ {/* 头部 */} +
+
+

+ 🏠 家庭收纳管理系统 +

+

+ 轻松管理家中物品,快速找到所需 +

+
+ + {/* 主内容 */} +
+ + + 📦 物品管理 + 🏷️ 分类管理 + 📍 位置管理 + + + + + + + + + + + + + +
-
); diff --git a/src/components/categories/CategoryManager.tsx b/src/components/categories/CategoryManager.tsx new file mode 100644 index 0000000..e9c2963 --- /dev/null +++ b/src/components/categories/CategoryManager.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { Category } from "@/types"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useState } from "react"; + +interface CategoryManagerProps { + categories: Category[]; + onAdd?: (category: Omit) => void; + onEdit?: (category: Category) => void; + onDelete?: (id: string) => void; +} + +const icons = ["📱", "📚", "👕", "🍳", "🔧", "✏️", "🎮", "🏀", "🎵", "📷"]; +const colors = [ + "bg-blue-500", + "bg-green-500", + "bg-purple-500", + "bg-orange-500", + "bg-gray-500", + "bg-yellow-500", + "bg-red-500", + "bg-pink-500", + "bg-indigo-500", + "bg-teal-500", +]; + +export default function CategoryManager({ + categories, + onAdd, + onEdit, + onDelete, +}: CategoryManagerProps) { + const [isAddOpen, setIsAddOpen] = useState(false); + const [newCategory, setNewCategory] = useState({ + name: "", + icon: "📱", + color: "bg-blue-500", + }); + + const handleAdd = () => { + if (onAdd && newCategory.name) { + onAdd(newCategory as Omit); + setNewCategory({ name: "", icon: "📱", color: "bg-blue-500" }); + setIsAddOpen(false); + } + }; + + return ( +
+
+

分类管理

+ + + + + + + 添加新分类 + +
+
+ + + setNewCategory({ ...newCategory, name: e.target.value }) + } + placeholder="输入分类名称" + /> +
+
+ +
+ {icons.map((icon) => ( + + ))} +
+
+
+ +
+ {colors.map((color) => ( +
+
+ +
+
+
+
+ +
+ {categories.map((category) => ( + + +
+ + {category.icon} + {category.name} + +
+
+ +
+ + +
+
+
+ ))} +
+
+ ); +} diff --git a/src/components/items/ItemDetailDialog.tsx b/src/components/items/ItemDetailDialog.tsx new file mode 100644 index 0000000..24f0f81 --- /dev/null +++ b/src/components/items/ItemDetailDialog.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { Item, Category, Location } from "@/types"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Badge } from "@/components/ui/badge"; + +interface ItemDetailDialogProps { + item: Item; + category?: Category; + location?: Location; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export default function ItemDetailDialog({ + item, + category, + location, + open, + onOpenChange, +}: ItemDetailDialogProps) { + return ( + + + +
+ {category && ( + {category.icon} + )} + {item.name} +
+
+ +
+ {/* 基本信息 */} +
+

描述

+

{item.description}

+
+ + {/* 分类和位置 */} +
+
+

分类

+
+ {category && ( + <> + {category.icon} + {category.name} + + )} +
+
+
+

位置

+
+ 📍 +
+

{location?.name}

+

{location?.path}

+
+
+
+
+ + {/* 数量信息 */} +
+

数量

+

+ {item.quantity} {item.unit} +

+
+ + {/* 标签 */} + {item.tags.length > 0 && ( +
+

标签

+
+ {item.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+ )} + + {/* 时间信息 */} +
+
+

创建时间

+

{item.createdAt}

+
+
+

更新时间

+

{item.updatedAt}

+
+
+
+
+
+ ); +} diff --git a/src/components/items/ItemList.tsx b/src/components/items/ItemList.tsx new file mode 100644 index 0000000..e475601 --- /dev/null +++ b/src/components/items/ItemList.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { useState } from "react"; +import { Item, Category, Location } from "@/types"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import ItemDetailDialog from "./ItemDetailDialog"; + +interface ItemListProps { + items: Item[]; + categories: Category[]; + locations: Location[]; +} + +export default function ItemList({ items, categories, locations }: ItemListProps) { + const [searchKeyword, setSearchKeyword] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("all"); + const [selectedLocation, setSelectedLocation] = useState("all"); + const [selectedItem, setSelectedItem] = useState(null); + const [isDetailOpen, setIsDetailOpen] = useState(false); + + // 筛选物品 + const filteredItems = items.filter((item) => { + const matchKeyword = + searchKeyword === "" || + item.name.toLowerCase().includes(searchKeyword.toLowerCase()) || + item.description.toLowerCase().includes(searchKeyword.toLowerCase()) || + item.tags.some((tag) => tag.toLowerCase().includes(searchKeyword.toLowerCase())); + + const matchCategory = selectedCategory === "all" || item.categoryId === selectedCategory; + const matchLocation = selectedLocation === "all" || item.locationId === selectedLocation; + + return matchKeyword && matchCategory && matchLocation; + }); + + const getCategoryById = (id: string) => categories.find((c) => c.id === id); + const getLocationById = (id: string) => locations.find((l) => l.id === id); + + const handleItemClick = (item: Item) => { + setSelectedItem(item); + setIsDetailOpen(true); + }; + + return ( +
+ {/* 搜索和筛选 */} +
+ setSearchKeyword(e.target.value)} + className="max-w-sm" + /> + + +
+ + {/* 物品列表 */} +
+ {filteredItems.map((item) => { + const category = getCategoryById(item.categoryId); + const location = getLocationById(item.locationId); + return ( + handleItemClick(item)} + > + +
+ {item.name} + {category && ( + {category.icon} + )} +
+
+ +

+ {item.description} +

+
+ + 📍 {location?.name || "未知位置"} + + + 数量:{item.quantity} {item.unit} + +
+ {item.tags.length > 0 && ( +
+ {item.tags.map((tag, index) => ( + + {tag} + + ))} +
+ )} +
+
+ ); + })} +
+ + {filteredItems.length === 0 && ( +
+ 没有找到匹配的物品 +
+ )} + + {/* 物品详情弹窗 */} + {selectedItem && ( + + )} +
+ ); +} diff --git a/src/components/locations/LocationManager.tsx b/src/components/locations/LocationManager.tsx new file mode 100644 index 0000000..3d3636c --- /dev/null +++ b/src/components/locations/LocationManager.tsx @@ -0,0 +1,159 @@ +"use client"; + +import { useState } from "react"; +import { Location } from "@/types"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +interface LocationManagerProps { + locations: Location[]; + onAdd?: (location: Omit) => void; + onEdit?: (location: Location) => void; + onDelete?: (id: string) => void; +} + +export default function LocationManager({ + locations, + onAdd, + onEdit, + onDelete, +}: LocationManagerProps) { + const [isAddOpen, setIsAddOpen] = useState(false); + const [newLocation, setNewLocation] = useState({ + name: "", + code: "", + parentId: "" as string | undefined, + }); + + const handleAdd = () => { + if (onAdd && newLocation.name && newLocation.code) { + onAdd(newLocation); + setNewLocation({ name: "", code: "", parentId: undefined }); + setIsAddOpen(false); + } + }; + + // 获取父位置的路径 + const getParentPath = (parentId?: string) => { + if (!parentId) return ""; + const parent = locations.find((l) => l.id === parentId); + return parent ? parent.path + "/" : ""; + }; + + // 筛选可作为父级的位置(一级位置) + const parentLocations = locations.filter((l) => !l.parentId); + + return ( +
+
+

位置管理

+ + + + + + + 添加新位置 + +
+
+ + + setNewLocation({ ...newLocation, name: e.target.value }) + } + placeholder="例如:客厅、卧室" + /> +
+
+ + + setNewLocation({ ...newLocation, code: e.target.value.toUpperCase() }) + } + placeholder="例如:LR、BR" + /> +
+
+ + +
+ {newLocation.name && newLocation.code && ( +
+ 完整路径:{getParentPath(newLocation.parentId)} + {newLocation.name} +
+ )} + +
+
+
+
+ +
+ {locations.map((location) => ( + + +
+
+ {location.name} +

{location.code}

+
+ {location.parentId && ( + 子位置 + )} +
+
+ +

路径:{location.path}

+
+ + +
+
+
+ ))} +
+
+ ); +} diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx new file mode 100644 index 0000000..b20959d --- /dev/null +++ b/src/components/ui/badge.tsx @@ -0,0 +1,52 @@ +import { mergeProps } from "@base-ui/react/merge-props" +import { useRender } from "@base-ui/react/use-render" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", + secondary: + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", + destructive: + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", + outline: + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + render, + ...props +}: useRender.ComponentProps<"span"> & VariantProps) { + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">( + { + className: cn(badgeVariants({ variant }), className), + }, + props + ), + render, + state: { + slot: "badge", + variant, + }, + }) +} + +export { Badge, badgeVariants } diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx new file mode 100644 index 0000000..9bd5a25 --- /dev/null +++ b/src/components/ui/card.tsx @@ -0,0 +1,103 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { + return ( +
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + className + )} + {...props} + /> + ) +} + +function CardHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardDescription({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function CardFooter({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardAction, + CardDescription, + CardContent, +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..807e1fa --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import { Dialog as DialogPrimitive } from "@base-ui/react/dialog" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { XIcon } from "lucide-react" + +function Dialog({ ...props }: DialogPrimitive.Root.Props) { + return +} + +function DialogTrigger({ ...props }: DialogPrimitive.Trigger.Props) { + return +} + +function DialogPortal({ ...props }: DialogPrimitive.Portal.Props) { + return +} + +function DialogClose({ ...props }: DialogPrimitive.Close.Props) { + return +} + +function DialogOverlay({ + className, + ...props +}: DialogPrimitive.Backdrop.Props) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: DialogPrimitive.Popup.Props & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + } + > + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + }> + Close + + )} +
+ ) +} + +function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: DialogPrimitive.Description.Props) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..7d21bab --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +import { Input as InputPrimitive } from "@base-ui/react/input" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..e8021f5 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,201 @@ +"use client" + +import * as React from "react" +import { Select as SelectPrimitive } from "@base-ui/react/select" + +import { cn } from "@/lib/utils" +import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react" + +const Select = SelectPrimitive.Root + +function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { + return ( + + ) +} + +function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { + return ( + + ) +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: SelectPrimitive.Trigger.Props & { + size?: "sm" | "default" +}) { + return ( + + {children} + + } + /> + + ) +} + +function SelectContent({ + className, + children, + side = "bottom", + sideOffset = 4, + align = "center", + alignOffset = 0, + alignItemWithTrigger = true, + ...props +}: SelectPrimitive.Popup.Props & + Pick< + SelectPrimitive.Positioner.Props, + "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger" + >) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: SelectPrimitive.GroupLabel.Props) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: SelectPrimitive.Item.Props) { + return ( + + + {children} + + + } + > + + + + ) +} + +function SelectSeparator({ + className, + ...props +}: SelectPrimitive.Separator.Props) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..8dc13ae --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,116 @@ +"use client" + +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Table({ className, ...props }: React.ComponentProps<"table">) { + return ( +
+ + + ) +} + +function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { + return ( + + ) +} + +function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { + return ( + + ) +} + +function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { + return ( + tr]:last:border-b-0", + className + )} + {...props} + /> + ) +} + +function TableRow({ className, ...props }: React.ComponentProps<"tr">) { + return ( + + ) +} + +function TableHead({ className, ...props }: React.ComponentProps<"th">) { + return ( +
+ ) +} + +function TableCell({ className, ...props }: React.ComponentProps<"td">) { + return ( + + ) +} + +function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">) { + return ( +
+ ) +} + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx new file mode 100644 index 0000000..56c4288 --- /dev/null +++ b/src/components/ui/tabs.tsx @@ -0,0 +1,82 @@ +"use client" + +import { Tabs as TabsPrimitive } from "@base-ui/react/tabs" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + orientation = "horizontal", + ...props +}: TabsPrimitive.Root.Props) { + return ( + + ) +} + +const tabsListVariants = cva( + "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none", + { + variants: { + variant: { + default: "bg-muted", + line: "gap-1 bg-transparent", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function TabsList({ + className, + variant = "default", + ...props +}: TabsPrimitive.List.Props & VariantProps) { + return ( + + ) +} + +function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) { + return ( + + ) +} + +function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants } diff --git a/src/data/mockData.ts b/src/data/mockData.ts new file mode 100644 index 0000000..4dd8f1c --- /dev/null +++ b/src/data/mockData.ts @@ -0,0 +1,123 @@ +import { Item, Category, Location } from "@/types"; + +// 模拟分类数据 +export const categories: Category[] = [ + { id: "1", name: "电子产品", icon: "📱", color: "bg-blue-500" }, + { id: "2", name: "书籍", icon: "📚", color: "bg-green-500" }, + { id: "3", name: "衣物", icon: "👕", color: "bg-purple-500" }, + { id: "4", name: "厨房用品", icon: "🍳", color: "bg-orange-500" }, + { id: "5", name: "工具", icon: "🔧", color: "bg-gray-500" }, + { id: "6", name: "文具", icon: "✏️", color: "bg-yellow-500" }, +]; + +// 模拟位置数据 +export const locations: Location[] = [ + { id: "1", name: "客厅", code: "LR", path: "客厅" }, + { id: "2", name: "卧室", code: "BR", path: "卧室" }, + { id: "3", name: "书房", code: "ST", path: "书房" }, + { id: "4", name: "厨房", code: "KT", path: "厨房" }, + { id: "5", name: "储物间", code: "STR", path: "储物间" }, + { id: "6", name: "衣柜 A", code: "WD-A", parentId: "2", path: "卧室/衣柜 A" }, + { id: "7", name: "书架 B", code: "BK-B", parentId: "3", path: "书房/书架 B" }, + { id: "8", name: "橱柜 C", code: "CB-C", parentId: "4", path: "厨房/橱柜 C" }, +]; + +// 模拟物品数据 +export const items: Item[] = [ + { + id: "1", + name: "MacBook Pro", + description: "2023 款 14 寸,M3 Pro 芯片", + categoryId: "1", + locationId: "3", + quantity: 1, + unit: "台", + tags: ["电脑", "工作"], + createdAt: "2024-01-15", + updatedAt: "2024-01-15", + }, + { + id: "2", + name: "iPhone 15 Pro", + description: "256GB 原色钛金属", + categoryId: "1", + locationId: "1", + quantity: 1, + unit: "台", + tags: ["手机", "通讯"], + createdAt: "2024-02-20", + updatedAt: "2024-02-20", + }, + { + id: "3", + name: "设计心理学", + description: "唐·诺曼经典著作", + categoryId: "2", + locationId: "7", + quantity: 1, + unit: "本", + tags: ["设计", "产品"], + createdAt: "2023-06-10", + updatedAt: "2023-06-10", + }, + { + id: "4", + name: "冬季外套", + description: "北面羽绒服,黑色 L 码", + categoryId: "3", + locationId: "6", + quantity: 2, + unit: "件", + tags: ["冬季", "外套"], + createdAt: "2023-11-01", + updatedAt: "2023-11-01", + }, + { + id: "5", + name: "不粘锅套装", + description: "双立人三件套", + categoryId: "4", + locationId: "8", + quantity: 1, + unit: "套", + tags: ["厨具", "烹饪"], + createdAt: "2024-03-01", + updatedAt: "2024-03-01", + }, + { + id: "6", + name: "螺丝刀套装", + description: "小米精修螺丝刀", + categoryId: "5", + locationId: "5", + quantity: 1, + unit: "套", + tags: ["工具", "维修"], + createdAt: "2023-08-15", + updatedAt: "2023-08-15", + }, + { + id: "7", + name: "iPad Air", + description: "第五代 64GB 蓝色", + categoryId: "1", + locationId: "1", + quantity: 1, + unit: "台", + tags: ["平板", "娱乐"], + createdAt: "2023-05-20", + updatedAt: "2023-05-20", + }, + { + id: "8", + name: "笔记本", + description: "Moleskine 经典黑色", + categoryId: "6", + locationId: "3", + quantity: 5, + unit: "本", + tags: ["文具", "记录"], + createdAt: "2024-01-10", + updatedAt: "2024-01-10", + }, +]; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..14d6180 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,40 @@ +// 物品类型 +export interface Item { + id: string; + name: string; + description: string; + categoryId: string; + locationId: string; + quantity: number; + unit: string; + imageUrl?: string; + tags: string[]; + createdAt: string; + updatedAt: string; +} + +// 分类类型 +export interface Category { + id: string; + name: string; + icon: string; + color: string; + parentId?: string; +} + +// 位置类型 +export interface Location { + id: string; + name: string; + code: string; + parentId?: string; + path: string; +} + +// 搜索筛选条件 +export interface SearchFilters { + keyword?: string; + categoryId?: string; + locationId?: string; + tags?: string[]; +}