feat: 炫酷的登录页
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { useState } from 'react'
|
||||
import { Plus, Search, Edit, Trash2, Award, RefreshCw, MoreHorizontal, Image } from 'lucide-react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
|
||||
// Mock data
|
||||
const mockBadges = [
|
||||
{ id: '1', name: '绿色新手', icon: '🌱', description: '完成首次浇水', condition: '首次浇水', category: 'beginner', sort: 0, isActive: true },
|
||||
{ id: '2', name: '植物猎人', icon: '🌿', description: '识别10种植物', condition: '识别10种植物', category: 'explore', sort: 1, isActive: true },
|
||||
{ id: '3', name: '花园大师', icon: '🌸', description: '养活50株植物', condition: '养活50株植物', category: 'master', sort: 2, isActive: true },
|
||||
{ id: '4', name: '社区之星', icon: '⭐', description: '帖子获得100赞', condition: '获得100赞', category: 'social', sort: 3, isActive: true },
|
||||
{ id: '5', name: '百科贡献者', icon: '📚', description: '贡献5篇百科', condition: '贡献5篇百科', category: 'wiki', sort: 4, isActive: false },
|
||||
{ id: '6', name: '连续打卡7天', icon: '🔥', description: '连续签到7天', condition: '连续7天签到', category: 'checkin', sort: 5, isActive: true },
|
||||
]
|
||||
|
||||
const categoryMap: Record<string, string> = {
|
||||
beginner: '新手入门', explore: '探索发现', master: '大师成就',
|
||||
social: '社区互动', wiki: '百科贡献', checkin: '签到打卡',
|
||||
}
|
||||
|
||||
export default function BadgePage() {
|
||||
const [badges] = useState(mockBadges)
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const filtered = badges.filter(b => !search || b.name.includes(search))
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fadeIn">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">徽章配置</h1>
|
||||
<p className="text-muted-foreground mt-1">管理用户成就徽章,配置获取条件和展示规则。</p>
|
||||
</div>
|
||||
|
||||
<Card className="border-border/60 shadow-soft">
|
||||
<CardHeader className="pb-3 border-b border-border/40">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Award className="h-5 w-5 text-primary" /> 徽章列表
|
||||
<Badge variant="secondary" className="text-xs">{filtered.length}</Badge>
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="搜索徽章..." className="pl-9 w-48 h-9" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
<Button size="sm" onClick={() => setDialogOpen(true)} className="h-9 gap-1.5">
|
||||
<Plus className="h-4 w-4" /> 新增徽章
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/30">
|
||||
<TableRow>
|
||||
<TableHead className="pl-6 w-[60px]">图标</TableHead>
|
||||
<TableHead>徽章名称</TableHead>
|
||||
<TableHead>获取条件</TableHead>
|
||||
<TableHead>分类</TableHead>
|
||||
<TableHead>排序</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="w-[60px]" />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.map(b => (
|
||||
<TableRow key={b.id} className="group hover:bg-muted/20">
|
||||
<TableCell className="pl-6 text-2xl">{b.icon}</TableCell>
|
||||
<TableCell>
|
||||
<div><span className="font-medium">{b.name}</span></div>
|
||||
<div className="text-xs text-muted-foreground">{b.description}</div>
|
||||
</TableCell>
|
||||
<TableCell><span className="text-sm bg-muted px-2 py-0.5 rounded font-mono">{b.condition}</span></TableCell>
|
||||
<TableCell><Badge variant="outline" className="bg-primary/5">{categoryMap[b.category]}</Badge></TableCell>
|
||||
<TableCell className="font-mono text-muted-foreground">{b.sort}</TableCell>
|
||||
<TableCell>
|
||||
{b.isActive
|
||||
? <Badge className="bg-emerald-500/10 text-emerald-600 border-emerald-200 shadow-none">启用</Badge>
|
||||
: <Badge variant="secondary" className="shadow-none">禁用</Badge>}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 opacity-0 group-hover:opacity-100">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem><Edit className="mr-2 h-4 w-4" /> 编辑</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-500"><Trash2 className="mr-2 h-4 w-4" /> 删除</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新增徽章</DialogTitle>
|
||||
<DialogDescription>配置徽章的名称、图标和获取条件。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">名称</Label>
|
||||
<Input className="col-span-3" placeholder="如: 绿色新手" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">图标</Label>
|
||||
<Input className="col-span-3" placeholder="使用 emoji 或上传图片" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">获取条件</Label>
|
||||
<Input className="col-span-3" placeholder="如: 首次浇水" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">分类</Label>
|
||||
<Select defaultValue="beginner">
|
||||
<SelectTrigger className="col-span-3"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{Object.entries(categoryMap).map(([k, v]) => <SelectItem key={k} value={k}>{v}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)}>取消</Button>
|
||||
<Button onClick={() => setDialogOpen(false)}>保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { useState } from 'react'
|
||||
import { Plus, Search, Edit, Trash2, Star, MoreHorizontal, ArrowUp } from 'lucide-react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
|
||||
const mockLevels = [
|
||||
{ id: '1', level: 1, name: '种子', icon: '🌱', minExp: 0, maxExp: 100, color: '#a3a3a3', rewards: '解锁基本功能' },
|
||||
{ id: '2', level: 2, name: '嫩芽', icon: '🌿', minExp: 100, maxExp: 300, color: '#86efac', rewards: '解锁社区发帖' },
|
||||
{ id: '3', level: 3, name: '绿叶', icon: '🍃', minExp: 300, maxExp: 600, color: '#4ade80', rewards: '解锁识别加速' },
|
||||
{ id: '4', level: 4, name: '花蕾', icon: '🌷', minExp: 600, maxExp: 1000, color: '#f9a8d4', rewards: '头像框·花蕾' },
|
||||
{ id: '5', level: 5, name: '盛花', icon: '🌸', minExp: 1000, maxExp: 2000, color: '#f472b6', rewards: '专属称号·花园达人' },
|
||||
{ id: '6', level: 6, name: '果实', icon: '🍎', minExp: 2000, maxExp: 5000, color: '#ef4444', rewards: '兑换折扣·9折' },
|
||||
{ id: '7', level: 7, name: '大树', icon: '🌳', minExp: 5000, maxExp: 99999, color: '#22c55e', rewards: '至尊头像框 + 专属客服' },
|
||||
]
|
||||
|
||||
export default function LevelPage() {
|
||||
const [levels] = useState(mockLevels)
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fadeIn">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">等级配置</h1>
|
||||
<p className="text-muted-foreground mt-1">配置用户成长等级体系,设定经验阶梯和等级奖励。</p>
|
||||
</div>
|
||||
|
||||
<Card className="border-border/60 shadow-soft">
|
||||
<CardHeader className="pb-3 border-b border-border/40">
|
||||
<div className="flex justify-between items-center">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Star className="h-5 w-5 text-primary" /> 等级阶梯
|
||||
<Badge variant="secondary" className="text-xs">{levels.length} 级</Badge>
|
||||
</CardTitle>
|
||||
<Button size="sm" onClick={() => setDialogOpen(true)} className="h-9 gap-1.5">
|
||||
<Plus className="h-4 w-4" /> 新增等级
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/30">
|
||||
<TableRow>
|
||||
<TableHead className="pl-6 w-[70px]">等级</TableHead>
|
||||
<TableHead className="w-[60px]">图标</TableHead>
|
||||
<TableHead>名称</TableHead>
|
||||
<TableHead>经验区间</TableHead>
|
||||
<TableHead>升级奖励</TableHead>
|
||||
<TableHead className="w-[60px]" />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{levels.map(lv => (
|
||||
<TableRow key={lv.id} className="group hover:bg-muted/20">
|
||||
<TableCell className="pl-6">
|
||||
<div className="flex items-center justify-center w-8 h-8 rounded-full font-bold text-white text-sm" style={{ background: lv.color }}>
|
||||
{lv.level}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-2xl">{lv.icon}</TableCell>
|
||||
<TableCell className="font-semibold">{lv.name}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-1.5 text-sm">
|
||||
<span className="font-mono bg-muted px-1.5 py-0.5 rounded">{lv.minExp}</span>
|
||||
<ArrowUp className="h-3 w-3 text-muted-foreground rotate-90" />
|
||||
<span className="font-mono bg-muted px-1.5 py-0.5 rounded">{lv.maxExp}</span>
|
||||
<span className="text-xs text-muted-foreground ml-1">EXP</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground max-w-[200px] truncate">{lv.rewards}</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 opacity-0 group-hover:opacity-100">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem><Edit className="mr-2 h-4 w-4" /> 编辑</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-500"><Trash2 className="mr-2 h-4 w-4" /> 删除</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新增等级</DialogTitle>
|
||||
<DialogDescription>配置等级名称、经验区间和升级奖励。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">等级</Label>
|
||||
<Input className="col-span-3" type="number" placeholder="如: 8" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">名称</Label>
|
||||
<Input className="col-span-3" placeholder="如: 花仙" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">最低经验</Label>
|
||||
<Input className="col-span-3" type="number" placeholder="10000" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">升级奖励</Label>
|
||||
<Input className="col-span-3" placeholder="描述升级后获得的奖励" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)}>取消</Button>
|
||||
<Button onClick={() => setDialogOpen(false)}>保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { useState } from 'react'
|
||||
import { Plus, Search, Edit, Trash2, Gift, MoreHorizontal } from 'lucide-react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
|
||||
const mockItems = [
|
||||
{ id: '1', name: '多肉植物盆栽', icon: '🪴', points: 500, stock: 50, sold: 23, category: '绿植', isActive: true, description: '精选多肉植物,含陶瓷花盆' },
|
||||
{ id: '2', name: '园艺工具套装', icon: '🔧', points: 800, stock: 30, sold: 12, category: '工具', isActive: true, description: '包含铲子、剪刀、喷壶' },
|
||||
{ id: '3', name: '植物生长灯', icon: '💡', points: 1200, stock: 20, sold: 8, category: '设备', isActive: true, description: 'LED全光谱补光灯' },
|
||||
{ id: '4', name: '有机肥料包', icon: '🌿', points: 300, stock: 100, sold: 67, category: '肥料', isActive: true, description: '天然有机肥料 500g' },
|
||||
{ id: '5', name: '花盆三件套', icon: '🏺', points: 600, stock: 0, sold: 45, category: '花盆', isActive: false, description: '陶瓷花盆 大中小三件套' },
|
||||
{ id: '6', name: '种子礼盒', icon: '🌱', points: 200, stock: 200, sold: 156, category: '种子', isActive: true, description: '含向日葵、薄荷、薰衣草种子各一包' },
|
||||
]
|
||||
|
||||
export default function ExchangeConfigPage() {
|
||||
const [items] = useState(mockItems)
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const filtered = items.filter(i => !search || i.name.includes(search))
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fadeIn">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">兑换配置</h1>
|
||||
<p className="text-muted-foreground mt-1">管理积分商城的兑换商品,配置积分价格和库存。</p>
|
||||
</div>
|
||||
|
||||
<Card className="border-border/60 shadow-soft">
|
||||
<CardHeader className="pb-3 border-b border-border/40">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Gift className="h-5 w-5 text-primary" /> 商品列表
|
||||
<Badge variant="secondary" className="text-xs">{filtered.length}</Badge>
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="搜索商品..." className="pl-9 w-48 h-9" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
<Button size="sm" onClick={() => setDialogOpen(true)} className="h-9 gap-1.5">
|
||||
<Plus className="h-4 w-4" /> 新增商品
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/30">
|
||||
<TableRow>
|
||||
<TableHead className="pl-6 w-[60px]">图标</TableHead>
|
||||
<TableHead>商品名称</TableHead>
|
||||
<TableHead>分类</TableHead>
|
||||
<TableHead>兑换积分</TableHead>
|
||||
<TableHead>库存 / 已兑</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead className="w-[60px]" />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.map(item => (
|
||||
<TableRow key={item.id} className="group hover:bg-muted/20">
|
||||
<TableCell className="pl-6 text-2xl">{item.icon}</TableCell>
|
||||
<TableCell>
|
||||
<div><span className="font-medium">{item.name}</span></div>
|
||||
<div className="text-xs text-muted-foreground truncate max-w-[200px]">{item.description}</div>
|
||||
</TableCell>
|
||||
<TableCell><Badge variant="outline" className="bg-primary/5">{item.category}</Badge></TableCell>
|
||||
<TableCell><span className="font-mono text-amber-600 font-semibold">{item.points}</span></TableCell>
|
||||
<TableCell>
|
||||
<span className={`font-mono text-sm ${item.stock === 0 ? 'text-red-500' : 'text-foreground'}`}>
|
||||
{item.stock}
|
||||
</span>
|
||||
<span className="text-muted-foreground mx-1">/</span>
|
||||
<span className="font-mono text-sm text-muted-foreground">{item.sold}</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{item.isActive
|
||||
? <Badge className="bg-emerald-500/10 text-emerald-600 border-emerald-200 shadow-none">上架</Badge>
|
||||
: <Badge variant="secondary" className="shadow-none">下架</Badge>}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 opacity-0 group-hover:opacity-100">
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem><Edit className="mr-2 h-4 w-4" /> 编辑</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-500"><Trash2 className="mr-2 h-4 w-4" /> 删除</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>新增兑换商品</DialogTitle>
|
||||
<DialogDescription>配置商品信息、积分价格和库存数量。</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">商品名称</Label>
|
||||
<Input className="col-span-3" placeholder="如: 多肉植物盆栽" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">兑换积分</Label>
|
||||
<Input className="col-span-3" type="number" placeholder="500" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label className="text-right">库存</Label>
|
||||
<Input className="col-span-3" type="number" placeholder="100" />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 items-start gap-4">
|
||||
<Label className="text-right pt-2">描述</Label>
|
||||
<Textarea className="col-span-3" placeholder="商品描述..." rows={3} />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setDialogOpen(false)}>取消</Button>
|
||||
<Button onClick={() => setDialogOpen(false)}>保存</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { useState } from 'react'
|
||||
import { Search, RefreshCw, Eye, Package, Clock, CheckCircle2, XCircle, Truck } from 'lucide-react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
|
||||
const statusMap: Record<string, { label: string; color: string; icon: React.ReactNode }> = {
|
||||
pending: { label: '待发货', color: 'bg-amber-500/10 text-amber-600 border-amber-200', icon: <Clock className="h-3.5 w-3.5" /> },
|
||||
shipped: { label: '已发货', color: 'bg-blue-500/10 text-blue-600 border-blue-200', icon: <Truck className="h-3.5 w-3.5" /> },
|
||||
completed: { label: '已完成', color: 'bg-emerald-500/10 text-emerald-600 border-emerald-200', icon: <CheckCircle2 className="h-3.5 w-3.5" /> },
|
||||
cancelled: { label: '已取消', color: 'bg-red-500/10 text-red-500 border-red-200', icon: <XCircle className="h-3.5 w-3.5" /> },
|
||||
}
|
||||
|
||||
const mockOrders = [
|
||||
{ id: 'EX20260401001', userId: 'u1', userName: '张三', itemName: '多肉植物盆栽', itemIcon: '🪴', points: 500, status: 'pending', createdAt: '2026-04-01 10:30', address: '广州市天河区xxx路' },
|
||||
{ id: 'EX20260402002', userId: 'u2', userName: '李四', itemName: '园艺工具套装', itemIcon: '🔧', points: 800, status: 'shipped', createdAt: '2026-04-02 14:20', address: '深圳市南山区xxx路' },
|
||||
{ id: 'EX20260403003', userId: 'u3', userName: '王五', itemName: '植物生长灯', itemIcon: '💡', points: 1200, status: 'completed', createdAt: '2026-04-03 09:15', address: '北京市朝阳区xxx路' },
|
||||
{ id: 'EX20260404004', userId: 'u1', userName: '张三', itemName: '有机肥料包', itemIcon: '🌿', points: 300, status: 'cancelled', createdAt: '2026-04-04 16:45', address: '广州市天河区xxx路' },
|
||||
{ id: 'EX20260405005', userId: 'u4', userName: '赵六', itemName: '花盆三件套', itemIcon: '🏺', points: 600, status: 'pending', createdAt: '2026-04-05 11:00', address: '上海市浦东新区xxx路' },
|
||||
{ id: 'EX20260406006', userId: 'u2', userName: '李四', itemName: '种子礼盒', itemIcon: '🌱', points: 200, status: 'completed', createdAt: '2026-04-06 08:30', address: '深圳市南山区xxx路' },
|
||||
]
|
||||
|
||||
export default function ExchangeOrderPage() {
|
||||
const [orders] = useState(mockOrders)
|
||||
const [statusFilter, setStatusFilter] = useState('all')
|
||||
const [search, setSearch] = useState('')
|
||||
const [detailOpen, setDetailOpen] = useState(false)
|
||||
const [activeOrder, setActiveOrder] = useState<typeof mockOrders[0] | null>(null)
|
||||
|
||||
const filtered = orders.filter(o => {
|
||||
if (statusFilter !== 'all' && o.status !== statusFilter) return false
|
||||
if (search && !o.userName.includes(search) && !o.id.includes(search)) return false
|
||||
return true
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fadeIn">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">兑换订单</h1>
|
||||
<p className="text-muted-foreground mt-1">查看和管理用户的积分兑换订单,处理发货和售后。</p>
|
||||
</div>
|
||||
|
||||
<Card className="border-border/60 shadow-soft">
|
||||
<CardHeader className="pb-3 border-b border-border/40">
|
||||
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Package className="h-5 w-5 text-primary" /> 订单列表
|
||||
<Badge variant="secondary" className="text-xs">{filtered.length}</Badge>
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-3">
|
||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||
<SelectTrigger className="w-[130px] h-9"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部状态</SelectItem>
|
||||
{Object.entries(statusMap).map(([k, v]) => <SelectItem key={k} value={k}>{v.label}</SelectItem>)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="订单号/用户名..." className="pl-9 w-48 h-9" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<Table>
|
||||
<TableHeader className="bg-muted/30">
|
||||
<TableRow>
|
||||
<TableHead className="pl-6">订单号</TableHead>
|
||||
<TableHead>用户</TableHead>
|
||||
<TableHead>兑换商品</TableHead>
|
||||
<TableHead>消耗积分</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>下单时间</TableHead>
|
||||
<TableHead className="w-[60px]" />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.map(o => {
|
||||
const st = statusMap[o.status]
|
||||
return (
|
||||
<TableRow key={o.id} className="group hover:bg-muted/20">
|
||||
<TableCell className="pl-6 font-mono text-xs">{o.id}</TableCell>
|
||||
<TableCell className="font-medium">{o.userName}</TableCell>
|
||||
<TableCell>
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="text-lg">{o.itemIcon}</span>{o.itemName}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell><span className="font-mono text-amber-600 font-semibold">{o.points}</span></TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline" className={`${st.color} shadow-none gap-1`}>{st.icon}{st.label}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground text-sm">{o.createdAt}</TableCell>
|
||||
<TableCell>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 opacity-0 group-hover:opacity-100 text-blue-500"
|
||||
onClick={() => { setActiveOrder(o); setDetailOpen(true) }}>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Dialog open={detailOpen} onOpenChange={setDetailOpen}>
|
||||
<DialogContent className="sm:max-w-[450px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>订单详情</DialogTitle>
|
||||
<DialogDescription>订单号: {activeOrder?.id}</DialogDescription>
|
||||
</DialogHeader>
|
||||
{activeOrder && (
|
||||
<div className="grid gap-3 text-sm p-4 bg-muted/30 rounded-lg border">
|
||||
<div className="flex justify-between"><span className="text-muted-foreground">用户</span><span className="font-medium">{activeOrder.userName}</span></div>
|
||||
<div className="flex justify-between"><span className="text-muted-foreground">商品</span><span>{activeOrder.itemIcon} {activeOrder.itemName}</span></div>
|
||||
<div className="flex justify-between"><span className="text-muted-foreground">消耗积分</span><span className="font-mono text-amber-600">{activeOrder.points}</span></div>
|
||||
<div className="flex justify-between"><span className="text-muted-foreground">收货地址</span><span>{activeOrder.address}</span></div>
|
||||
<div className="flex justify-between"><span className="text-muted-foreground">下单时间</span><span>{activeOrder.createdAt}</span></div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user