Files
sundynix-radio-admin/src/pages/Radio/Channel/index.tsx
T
2026-03-02 15:00:37 +08:00

622 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState, useEffect } from 'react';
import {
getChannelListApi,
saveChannelApi,
updateChannelApi,
deleteChannelApi,
getAllCategoryListApi
} from '../../../api/radio';
import { FileUploader } from '../../../components/FileUploader';
import { DeleteConfirm } from '../../../components/DeleteConfirm';
import { Button } from '../../../components/ui/button';
import { Input } from '../../../components/ui/input';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../../components/ui/table';
import { Dialog, DialogContent, DialogTitle } from '../../../components/ui/dialog';
import { Card, CardContent, CardHeader, CardTitle } from '../../../components/ui/card';
import { Label } from '../../../components/ui/label';
import {
Plus,
Edit,
Trash2,
Search,
Disc3,
Crown,
Zap,
LayoutGrid,
CalendarDays
} from 'lucide-react';
import { toast } from 'sonner';
import { motion, AnimatePresence } from 'framer-motion';
export default function Channel() {
const [data, setData] = useState<any[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [categories, setCategories] = useState<any[]>([]);
const [selectedCategoryId, setSelectedCategoryId] = useState<string>("");
const [page, setPage] = useState(1);
const [pageSize] = useState(10);
const [searchName, setSearchName] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
const [open, setOpen] = useState(false);
const [isEdit, setIsEdit] = useState(false);
const [formData, setFormData] = useState<any>({
id: undefined,
categoryId: '',
name: '',
description: '',
isFree: 0,
isVipOnly: 0,
monthlyPrice: 0,
quarterlyPrice: 0,
annualPrice: 0,
coverId: '',
coverUrl: '',
tags: '',
sort: 0,
status: 1
});
const [deleteOpen, setDeleteOpen] = useState(false);
const [deleteId, setDeleteId] = useState<any>(null);
const fetchCategories = async () => {
try {
const res: any = await getAllCategoryListApi();
setCategories(res.list || res || []);
} catch (e) {
console.error(e);
}
}
const fetchData = async () => {
setLoading(true);
try {
const res: any = await getChannelListApi({
current: page,
pageSize: pageSize,
name: debouncedSearch || "",
categoryId: selectedCategoryId || ""
});
setData(res.list || res || []);
setTotal(res.total || 0);
} catch (e) {
console.error(e);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchCategories();
}, []);
useEffect(() => {
fetchData();
}, [page, pageSize, selectedCategoryId, debouncedSearch]);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedSearch(searchName);
setPage(1);
}, 500);
return () => clearTimeout(timer);
}, [searchName]);
const handleOpenAdd = () => {
setIsEdit(false);
setFormData({
id: undefined,
categoryId: selectedCategoryId || '',
name: '',
description: '',
isFree: 0,
isVipOnly: 0,
monthlyPrice: 0,
quarterlyPrice: 0,
annualPrice: 0,
coverId: '',
coverUrl: '',
tags: '',
sort: 0,
status: 1
});
setOpen(true);
};
const handleOpenEdit = (record: any) => {
setIsEdit(true);
setFormData({
id: String(record.ID || record.id || ""),
categoryId: record.categoryId,
name: record.name,
description: record.description,
isFree: record.isFree || 0,
isVipOnly: record.isVipOnly || 0,
monthlyPrice: record.monthlyPrice || 0,
quarterlyPrice: record.quarterlyPrice || 0,
annualPrice: record.annualPrice || 0,
coverId: record.coverId,
coverUrl: record.cover?.url || record.coverUrl || "",
tags: record.tags,
sort: record.sort,
status: record.status
});
setOpen(true);
};
const handleDeleteClick = (id: any) => {
setDeleteId(id);
setDeleteOpen(true);
};
const confirmDelete = async () => {
if (!deleteId) return;
try {
await deleteChannelApi({ id: String(deleteId) });
toast.success('删除成功');
fetchData();
} catch (e) {
console.error(e);
} finally {
setDeleteOpen(false);
setDeleteId(null);
}
};
const handleSubmit = async () => {
if (!formData.name) return toast.error('请填写频道名称');
if (!formData.categoryId) return toast.error('请选择所属分类');
const submitData = { ...formData };
if (submitData.sort === "" || submitData.sort === undefined || submitData.sort === null) submitData.sort = 0;
if (submitData.monthlyPrice === "" || submitData.monthlyPrice === undefined || submitData.monthlyPrice === null) submitData.monthlyPrice = 0;
if (submitData.quarterlyPrice === "" || submitData.quarterlyPrice === undefined || submitData.quarterlyPrice === null) submitData.quarterlyPrice = 0;
if (submitData.annualPrice === "" || submitData.annualPrice === undefined || submitData.annualPrice === null) submitData.annualPrice = 0;
try {
if (isEdit) {
await updateChannelApi(submitData);
toast.success('更新成功');
} else {
await saveChannelApi(submitData);
toast.success('创建成功');
}
setOpen(false);
fetchData();
} catch (e) {
console.error(e);
}
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="space-y-8 pb-10"
>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h1 className="text-4xl font-black tracking-tight text-[#4A3A2C] flex items-center gap-4">
<div className="w-3 h-3 rounded-full bg-[#D28F4F] shadow-[0_0_20px_rgba(210,143,79,0.5)] animate-pulse" />
</h1>
<p className="text-[#8C7E6C] font-medium mt-2"></p>
</div>
<Button
onClick={handleOpenAdd}
className="rounded-[1.5rem] h-14 px-8 font-black shadow-xl shadow-[#D28F4F]/20 hover:scale-105 transition-all bg-gradient-to-r from-[#D28F4F] to-[#A64452] border-none group"
>
<Plus className="w-5 h-5 mr-3 group-hover:rotate-90 transition-transform" />
</Button>
</div>
<div className="grid grid-cols-1 md:grid-cols-12 gap-8 items-start">
<div className="md:col-span-3 lg:col-span-2 space-y-6 sticky top-28">
<Card className="glass-card warm-noise border-none shadow-glass rounded-[2.5rem] overflow-hidden relative">
<CardHeader className="p-8 border-b border-[#4A3A2C]/5 bg-[#FAF5E6]/60 backdrop-blur-md">
<CardTitle className="text-[10px] font-black uppercase tracking-[0.3em] text-[#D28F4F] flex items-center gap-3">
<LayoutGrid className="w-4 h-4" />
</CardTitle>
</CardHeader>
<CardContent className="p-4 bg-[#FAF5E6]/20">
<div className="space-y-3">
<button
onClick={() => setSelectedCategoryId("")}
className={`w-full flex items-center px-6 py-4 rounded-3xl text-sm font-black transition-all ${selectedCategoryId === ""
? 'bg-white shadow-md text-[#D28F4F] ring-4 ring-[#D28F4F]/10'
: 'text-[#8C7E6C] hover:text-[#4A3A2C] hover:bg-white/40'
}`}
>
</button>
{categories.map((category: any) => {
const catId = String(category.ID || category.id || "");
const isSelected = selectedCategoryId === catId;
return (
<button
key={catId}
onClick={() => setSelectedCategoryId(catId)}
className={`w-full flex items-center justify-between px-6 py-4 rounded-3xl text-sm font-black transition-all ${isSelected
? 'bg-white shadow-md text-[#D28F4F] ring-4 ring-[#D28F4F]/10'
: 'text-[#8C7E6C] hover:text-[#4A3A2C] hover:bg-white/40'
}`}
>
<span className="truncate">{category.name}</span>
{isSelected && <div className="w-2 h-2 rounded-full bg-[#D28F4F] shadow-[0_0_10px_var(--primary)]" />}
</button>
);
})}
</div>
</CardContent>
</Card>
</div>
<Card className="md:col-span-9 lg:col-span-10 glass-card warm-noise border-none shadow-glass rounded-[2.5rem] overflow-hidden min-h-[700px] relative z-10">
<CardHeader className="p-10 border-b border-[#4A3A2C]/5 flex flex-col sm:flex-row sm:items-center justify-between gap-6 bg-[#FAF5E6]/40">
<div className="flex items-center gap-4">
<div className="w-1.5 h-8 bg-[#D28F4F] rounded-full shadow-[0_0_12px_#D28F4F]" />
<CardTitle className="text-2xl font-black tracking-tight text-[#4A3A2C]">
{selectedCategoryId ? categories.find(c => String(c.ID || c.id) === selectedCategoryId)?.name : '电台频道全集'}
</CardTitle>
</div>
<div className="relative group w-full max-w-sm">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-[#8C7E6C] group-focus-within:text-[#D28F4F] transition-colors" />
<Input
placeholder="搜索频道名称..."
value={searchName}
onChange={(e) => setSearchName(e.target.value)}
className="pl-12 h-12 rounded-2xl border-none bg-[#FAF5E6]/80 focus:bg-white shadow-inner transition-all font-bold text-[#4A3A2C]"
/>
</div>
</CardHeader>
<CardContent className="p-0">
<div className="overflow-x-auto">
<Table>
<TableHeader className="bg-[#FAF5E6]/50">
<TableRow className="hover:bg-transparent border-[#4A3A2C]/5">
<TableHead className="w-[100px] pl-10 py-6 text-[10px] font-black uppercase tracking-[0.2em] text-[#8C7E6C]"></TableHead>
<TableHead className="py-6 text-[10px] font-black uppercase tracking-[0.2em] text-[#8C7E6C]"></TableHead>
<TableHead className="py-6 text-[10px] font-black uppercase tracking-[0.2em] text-[#8C7E6C]"> ()</TableHead>
<TableHead className="py-6 text-[10px] font-black uppercase tracking-[0.2em] text-[#8C7E6C] text-center"></TableHead>
<TableHead className="py-6 text-[10px] font-black uppercase tracking-[0.2em] text-[#8C7E6C]"></TableHead>
<TableHead className="text-right pr-10 py-6 text-[10px] font-black uppercase tracking-[0.2em] text-[#8C7E6C]"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow><TableCell colSpan={6} className="h-96 text-center text-[#8C7E6C] font-black uppercase"><Disc3 className="w-12 h-12 mx-auto animate-spin-slow mb-4 text-[#D28F4F]/50" />...</TableCell></TableRow>
) : data.length === 0 ? (
<TableRow><TableCell colSpan={6} className="h-96 text-center text-[#8C7E6C] text-sm font-black tracking-widest uppercase"></TableCell></TableRow>
) : (
data.map((item: any) => (
<TableRow
key={item.ID || item.id}
className="group border-[#4A3A2C]/5 hover:bg-[#FAF5E6]/80 transition-all duration-300 transform hover:scale-[1.002] relative"
>
<TableCell className="pl-10 py-6">
<div className="w-20 h-14 rounded-2xl overflow-hidden shadow-md ring-4 ring-white transition-all group-hover:ring-[#D28F4F]/20 bg-[#F2EDE4]">
<img
src={item.cover?.url || item.coverUrl || 'https://picsum.photos/seed/channel/100/100'}
alt=""
className="w-full h-full object-cover group-hover:scale-125 transition-transform duration-1000"
/>
</div>
</TableCell>
<TableCell className="py-6">
<div className="max-w-[200px]">
<p className="font-black text-[#4A3A2C] group-hover:text-[#D28F4F] transition-colors text-base tracking-tight">{item.name}</p>
<p className="text-[11px] font-bold text-[#8C7E6C]/60 mt-1 line-clamp-1 italic">#{categories.find(c => String(c.ID || c.id) === String(item.categoryId))?.name || '未分类'}</p>
</div>
</TableCell>
<TableCell className="py-6">
<div className="flex gap-4">
<div className="text-center group/price hover:translate-y-[-2px] transition-transform">
<p className="text-[10px] font-black text-[#8C7E6C]/50 uppercase"></p>
<p className="text-xs font-black text-[#4A3A2C]">¥{(item.monthlyPrice / 100).toFixed(2)}</p>
</div>
<div className="w-px h-8 bg-[#4A3A2C]/5 mt-1" />
<div className="text-center group/price hover:translate-y-[-2px] transition-transform">
<p className="text-[10px] font-black text-[#8C7E6C]/50 uppercase"></p>
<p className="text-xs font-black text-[#4A3A2C]">¥{(item.quarterlyPrice / 100).toFixed(2)}</p>
</div>
<div className="w-px h-8 bg-[#4A3A2C]/5 mt-1" />
<div className="text-center group/price hover:translate-y-[-2px] transition-transform">
<p className="text-[10px] font-black text-[#D28F4F]/60 uppercase"></p>
<p className="text-xs font-black text-[#D28F4F]">¥{(item.annualPrice / 100).toFixed(2)}</p>
</div>
</div>
</TableCell>
<TableCell className="py-6 text-center">
<div className="flex justify-center">
{item.isVipOnly === 1 ? (
<div className="flex items-center gap-1.5 px-4 py-1 rounded-full bg-amber-50 text-amber-700 border border-amber-200 shadow-sm">
<Crown className="w-3.5 h-3.5 fill-amber-500" />
<span className="text-[10px] font-black uppercase">VIP </span>
</div>
) : item.isFree === 1 ? (
<div className="flex items-center gap-1.5 px-4 py-1 rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200 shadow-sm">
<Zap className="w-3.5 h-3.5 fill-emerald-500" />
<span className="text-[10px] font-black uppercase tracking-tighter"></span>
</div>
) : (
<span className="text-[11px] font-black text-[#8C7E6C]/40 uppercase tracking-widest text-shadow-sm">访</span>
)}
</div>
</TableCell>
<TableCell className="py-6 text-center">
<div className={`flex items-center mx-auto w-fit px-3 py-1 rounded-full text-[10px] font-black uppercase border shadow-sm ${item.status === 1
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
: 'bg-rose-50 text-rose-600 border-rose-200'
}`}>
<div className={`w-1.5 h-1.5 rounded-full mr-2 ${item.status === 1 ? 'bg-emerald-500 animate-pulse' : 'bg-rose-500'}`} />
{item.status === 1 ? '启用' : '下架'}
</div>
</TableCell>
<TableCell className="text-right pr-10 py-6">
<div className="flex items-center justify-end gap-3 opacity-0 group-hover:opacity-100 transition-all translate-x-4 group-hover:translate-x-0">
<Button
variant="ghost"
size="icon"
onClick={() => handleOpenEdit(item)}
className="w-12 h-12 rounded-[1.2rem] hover:bg-white hover:text-[#D28F4F] transition-all border border-[#4A3A2C]/5 shadow-sm hover:shadow-md"
>
<Edit className="w-5 h-5" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleDeleteClick(item.ID || item.id)}
className="w-12 h-12 rounded-[1.2rem] hover:bg-rose-50 hover:text-rose-600 transition-all border border-[#4A3A2C]/5 shadow-sm"
>
<Trash2 className="w-5 h-5" />
</Button>
</div>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</CardContent>
<AnimatePresence>
{total > pageSize && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
className="p-10 border-t border-[#4A3A2C]/5 flex items-center justify-center bg-[#FAF5E6]/40 backdrop-blur-md"
>
<div className="flex items-center gap-6">
<Button
variant="ghost"
onClick={() => setPage(p => Math.max(1, p - 1))}
disabled={page === 1}
className="rounded-2xl px-8 h-12 font-black uppercase tracking-widest text-[11px] hover:bg-white text-[#8C7E6C] hover:text-[#D28F4F]"
>
</Button>
<div className="px-8 py-2.5 bg-white/60 backdrop-blur-md rounded-full shadow-inner border border-[#D28F4F]/10 text-[11px] font-black uppercase tracking-[0.2em] text-[#8C7E6C]">
<span className="text-[#D28F4F]">{page}</span> / {Math.ceil(total / pageSize)}
</div>
<Button
variant="ghost"
onClick={() => setPage(p => p + 1)}
disabled={data.length < pageSize}
className="rounded-2xl px-8 h-12 font-black uppercase tracking-widest text-[11px] hover:bg-white text-[#8C7E6C] hover:text-[#D28F4F]"
>
</Button>
</div>
</motion.div>
)}
</AnimatePresence>
</Card>
</div>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="sm:max-w-[850px] glass-card warm-noise border-none rounded-[3.5rem] p-0 overflow-hidden shadow-2xl">
<div className="bg-gradient-to-r from-[#D28F4F] to-[#A64452] p-12 text-white relative">
<DialogTitle className="text-3xl font-black tracking-tight">{isEdit ? '编辑频道方案' : '策划全新广播频道'}</DialogTitle>
<p className="text-white/60 text-xs font-black uppercase tracking-[0.3em] mt-3">Channel Ecosystem Design</p>
<Disc3 className="absolute right-12 top-1/2 -translate-y-1/2 w-24 h-24 text-white/10 animate-spin-slow" />
</div>
<div className="p-12 space-y-10 max-h-[75vh] overflow-y-auto custom-scrollbar bg-[#FAF5E6]/60 backdrop-blur-3xl">
<div className="grid grid-cols-2 gap-10">
<div className="space-y-8">
<div className="space-y-3">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"></Label>
<Input
placeholder="输入辨识度高的名称..."
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
className="h-16 rounded-[1.5rem] border-none bg-white shadow-sm font-bold text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all pl-8 text-lg"
/>
</div>
<div className="space-y-3">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"></Label>
<select
className="w-full h-16 rounded-[1.5rem] bg-white shadow-sm px-8 font-bold text-[#4A3A2C] border-none focus:ring-4 ring-[#D28F4F]/10 transition-all outline-none text-lg appearance-none cursor-pointer"
value={formData.categoryId}
onChange={e => setFormData({ ...formData, categoryId: e.target.value })}
>
<option value="" disabled></option>
{categories.map((c: any) => (
<option key={String(c.ID || c.id)} value={String(c.ID || c.id)}>{c.name}</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-6">
<div className="space-y-3">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"></Label>
<Input
type="number"
value={formData.sort ?? ""}
onChange={(e) => {
const val = e.target.value;
setFormData({ ...formData, sort: val === "" ? "" : parseInt(val) });
}}
className="h-16 rounded-[1.5rem] border-none bg-white shadow-sm font-bold text-center text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all text-lg"
/>
</div>
<div className="space-y-3">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"></Label>
<select
className="w-full h-16 rounded-[1.5rem] bg-white shadow-sm px-8 font-bold text-[#4A3A2C] border-none focus:ring-4 ring-[#D28F4F]/10 transition-all outline-none text-lg appearance-none cursor-pointer"
value={formData.status}
onChange={e => setFormData({ ...formData, status: parseInt(e.target.value) })}
>
<option value={1}></option>
<option value={0}></option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-6 p-6 rounded-[2.5rem] bg-[#FAF5E6] border border-[#D28F4F]/10 shadow-inner">
<div className="space-y-3">
<Label className="text-[10px] uppercase font-black text-[#8C7E6C] ml-1"></Label>
<select
className="w-full h-12 rounded-[1.2rem] bg-white text-sm font-black text-[#4A3A2C] border-none shadow-sm appearance-none px-4"
value={formData.isFree}
onChange={e => {
const val = parseInt(e.target.value);
setFormData({ ...formData, isFree: val });
}}
>
<option value={0}></option>
<option value={1}></option>
</select>
</div>
<div className="space-y-3">
<Label className="text-[10px] uppercase font-black text-[#8C7E6C] ml-1">VIP权限</Label>
<select
className="w-full h-12 rounded-[1.2rem] bg-white text-sm font-black text-[#4A3A2C] border-none shadow-sm appearance-none px-4"
value={formData.isVipOnly}
onChange={e => {
const val = parseInt(e.target.value);
setFormData({
...formData,
isVipOnly: val,
isFree: val === 1 ? 0 : formData.isFree
});
}}
>
<option value={0}>访</option>
<option value={1}>VIP</option>
</select>
</div>
</div>
</div>
<div className="space-y-8">
<div className="space-y-3 font-bold">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"></Label>
<FileUploader
label=""
accept="image/*"
placeholder="上传封面图片 (jpg, png, webp)"
value={formData.coverId}
initialPreview={formData.coverUrl}
onChange={(id) => setFormData({ ...formData, coverId: id })}
/>
</div>
<div className="rounded-[2.5rem] border border-[#D28F4F]/10 shadow-sm bg-white/30 p-8 space-y-6">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#D28F4F] flex items-center gap-2">
<CalendarDays className="w-4 h-4" />
()
</Label>
<div className="grid grid-cols-1 gap-4">
<div className="flex items-center gap-6 bg-white p-4 rounded-[1.5rem] shadow-sm">
<span className="text-[11px] font-black text-[#8C7E6C] uppercase w-12 text-center"></span>
<Input
type="number"
step="0.01"
value={formData.monthlyPrice === 0 ? "0" : (formData.monthlyPrice ? (formData.monthlyPrice / 100).toString() : "")}
onChange={e => {
const val = e.target.value;
setFormData({ ...formData, monthlyPrice: val === "" ? "" : Math.round(parseFloat(val) * 100) });
}}
className="h-10 border-none shadow-none font-black text-lg text-right flex-1 bg-transparent text-[#4A3A2C]"
/>
</div>
<div className="flex items-center gap-6 bg-white p-4 rounded-[1.5rem] shadow-sm">
<span className="text-[11px] font-black text-[#8C7E6C] uppercase w-12 text-center"></span>
<Input
type="number"
step="0.01"
value={formData.quarterlyPrice === 0 ? "0" : (formData.quarterlyPrice ? (formData.quarterlyPrice / 100).toString() : "")}
onChange={e => {
const val = e.target.value;
setFormData({ ...formData, quarterlyPrice: val === "" ? "" : Math.round(parseFloat(val) * 100) });
}}
className="h-10 border-none shadow-none font-black text-lg text-right flex-1 bg-transparent text-[#4A3A2C]"
/>
</div>
<div className="flex items-center gap-6 bg-[#D28F4F]/5 p-4 rounded-[1.5rem] ring-2 ring-[#D28F4F]/20 shadow-sm">
<span className="text-[11px] font-black text-[#D28F4F] uppercase w-12 text-center"></span>
<Input
type="number"
step="0.01"
value={formData.annualPrice === 0 ? "0" : (formData.annualPrice ? (formData.annualPrice / 100).toString() : "")}
onChange={e => {
const val = e.target.value;
setFormData({ ...formData, annualPrice: val === "" ? "" : Math.round(parseFloat(val) * 100) });
}}
className="h-10 border-none shadow-none font-black text-lg text-right flex-1 bg-transparent text-[#D28F4F]"
/>
</div>
</div>
</div>
</div>
</div>
<div className="space-y-8">
<div className="space-y-3">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"> ()</Label>
<Input
placeholder="例:故事, 催眠, 纯音乐..."
value={formData.tags}
onChange={(e) => setFormData({ ...formData, tags: e.target.value })}
className="h-16 rounded-[1.5rem] border-none bg-white shadow-sm font-bold text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all pl-8 text-lg"
/>
</div>
<div className="space-y-3">
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1"></Label>
<textarea
className="w-full min-h-[140px] rounded-[2rem] border-none bg-white shadow-sm p-8 font-bold text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all outline-none resize-none placeholder:text-[#8C7E6C]/30"
placeholder="描述此频道的独特魅力与听众受众..."
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
/>
</div>
</div>
</div>
<div className="p-12 bg-white/40 backdrop-blur-xl flex justify-between items-center border-t border-[#4A3A2C]/5 font-black">
<Button
variant="ghost"
onClick={() => setOpen(false)}
className="rounded-[1.5rem] h-14 px-10 uppercase tracking-[0.2em] text-xs hover:bg-white text-[#8C7E6C]"
>
</Button>
<Button
onClick={handleSubmit}
className="rounded-[1.5rem] h-14 px-12 shadow-2xl shadow-[#D28F4F]/30 hover:scale-105 transition-all bg-gradient-to-r from-[#D28F4F] to-[#A64452] border-none"
>
{isEdit ? '保存频道更新' : '立即发布频道'}
</Button>
</div>
</DialogContent>
</Dialog>
<DeleteConfirm
open={deleteOpen}
onOpenChange={setDeleteOpen}
onConfirm={confirmDelete}
/>
</motion.div>
);
}