feat: 初始版本 - 家庭收纳管理系统
- 创建 Next.js 14 + TypeScript + TailwindCSS 项目 - 集成 Shadcn/ui 组件库 - 实现物品列表和详情展示 - 实现分类管理功能 - 实现位置管理功能 - 实现搜索和筛选功能 - 添加模拟数据 技术栈:Next.js 14, React, TailwindCSS, Shadcn/ui
This commit is contained in:
@@ -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<Location, "id" | "path">) => 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 (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-xl font-semibold">位置管理</h2>
|
||||
<Dialog open={isAddOpen} onOpenChange={setIsAddOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>添加位置</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>添加新位置</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 pt-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">名称</label>
|
||||
<Input
|
||||
value={newLocation.name}
|
||||
onChange={(e) =>
|
||||
setNewLocation({ ...newLocation, name: e.target.value })
|
||||
}
|
||||
placeholder="例如:客厅、卧室"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">编码</label>
|
||||
<Input
|
||||
value={newLocation.code}
|
||||
onChange={(e) =>
|
||||
setNewLocation({ ...newLocation, code: e.target.value.toUpperCase() })
|
||||
}
|
||||
placeholder="例如:LR、BR"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">父位置(可选)</label>
|
||||
<Select
|
||||
value={newLocation.parentId}
|
||||
onValueChange={(value) =>
|
||||
setNewLocation({ ...newLocation, parentId: value || undefined })
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="选择父位置" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">无(一级位置)</SelectItem>
|
||||
{parentLocations.map((location) => (
|
||||
<SelectItem key={location.id} value={location.id}>
|
||||
{location.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{newLocation.name && newLocation.code && (
|
||||
<div className="text-sm text-gray-500">
|
||||
完整路径:{getParentPath(newLocation.parentId)}
|
||||
{newLocation.name}
|
||||
</div>
|
||||
)}
|
||||
<Button onClick={handleAdd} className="w-full">
|
||||
确认添加
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{locations.map((location) => (
|
||||
<Card key={location.id}>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<CardTitle className="text-lg">{location.name}</CardTitle>
|
||||
<p className="text-xs text-gray-500">{location.code}</p>
|
||||
</div>
|
||||
{location.parentId && (
|
||||
<span className="text-xs bg-gray-100 px-2 py-1 rounded">子位置</span>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-gray-600 mb-3">路径:{location.path}</p>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => onEdit?.(location)}>
|
||||
编辑
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" onClick={() => onDelete?.(location.id)}>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user