Initial CMPP frontend prototype

This commit is contained in:
hectorzhao
2026-06-30 16:09:46 +08:00
commit 2f3c274a30
98 changed files with 25255 additions and 0 deletions
+157
View File
@@ -0,0 +1,157 @@
import { useMemo, useState } from 'react';
import { Edit3, Layers3, Plus, Search, Trash2, UsersRound } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { Button, Input, Pagination } from '@/components/ui';
type ChannelGroup = {
id: string;
name: string;
count: number;
channels: string[];
};
const initialGroups: ChannelGroup[] = [
{
id: 'medical-a',
name: '学医三网专用群',
count: 16,
channels: [
'三网行北-黄峰-三网-编号3.3',
'三网行北-黄峰(循环号用)-三网-编号3.4',
'移动映华北-上海富煌C60289-移动2.7',
'三网行北-北京富惠互联-三网-编号3.5',
'移动行北-上海悉斯4pp0131-移动2.8',
],
},
{
id: 'medical-b',
name: '学医三网专用群',
count: 5,
channels: [
'三网行北-黄峰-三网-编号3.3',
'三网行北-黄峰(循环号用)-三网-编号3.4',
'移动映华北-上海富煌C60289-移动2.7',
],
},
{
id: 'group-a',
name: 'XXXX通道组',
count: 3,
channels: [
'三网行北-黄峰-三网-编号3.3',
'三网行北-黄峰(循环号用)-三网-编号3.4',
],
},
{
id: 'group-b',
name: 'XXXX通道组',
count: 1,
channels: ['三网行北-黄峰-三网-编号3.3'],
},
{
id: 'test-a',
name: '测试通道组A',
count: 8,
channels: [
'三网行北-黄峰-三网-编号3.3',
'移动映华北-上海富煌C60289-移动2.7',
],
},
{
id: 'test-b',
name: '测试通道组B',
count: 12,
channels: ['三网行北-黄峰-三网-编号3.3'],
},
{
id: 'test-c',
name: '测试通道组C',
count: 6,
channels: [
'三网行北-黄峰-三网-编号3.3',
'三网行北-黄峰(循环号用)-三网-编号3.4',
],
},
{
id: 'test-d',
name: '测试通道组D',
count: 2,
channels: ['三网行北-黄峰-三网-编号3.3'],
},
];
export function AdminChannelGroupsPage() {
const navigate = useNavigate();
const [groupName, setGroupName] = useState('');
const [channelKeyword, setChannelKeyword] = useState('');
const [groups, setGroups] = useState(initialGroups);
const filteredGroups = useMemo(() => groups.filter((group) => {
const nameMatched = group.name.includes(groupName.trim());
const channelMatched = group.channels.some((channel) => channel.includes(channelKeyword.trim()));
return nameMatched && channelMatched;
}), [channelKeyword, groupName, groups]);
function resetFilters() {
setGroupName('');
setChannelKeyword('');
}
function removeGroup(id: string) {
setGroups((current) => current.filter((group) => group.id !== id));
}
return (
<div className="page-stack sms-channel-group-page">
<div className="page-heading">
<div>
<div className="eyebrow"> / </div>
<h1></h1>
</div>
<Button icon={<Plus size={16} />} onClick={() => navigate('/admin/channel-groups/new')}>
</Button>
</div>
<section className="surface channel-group-filter">
<Input label="通道组名称" onChange={(event) => setGroupName(event.target.value)} placeholder="请输入通道组名称" value={groupName} />
<Input label="包含通道" onChange={(event) => setChannelKeyword(event.target.value)} placeholder="请输入包含的通道" value={channelKeyword} />
<div className="channel-group-filter__actions">
<Button icon={<Search size={16} />}></Button>
<Button onClick={resetFilters} variant="ghost"></Button>
</div>
</section>
<section className="surface channel-group-list">
<div className="channel-group-grid">
{filteredGroups.map((group) => (
<article className="channel-group-card" key={group.id}>
<header>
<div>
<Layers3 size={18} />
<strong>{group.name}</strong>
</div>
<span title="包含通道数"><UsersRound size={16} />{group.count}</span>
</header>
<div className="channel-group-card__body">
{group.channels.slice(0, 5).map((channel) => (
<p key={channel}>{channel}</p>
))}
{group.count > group.channels.length ? <small> {group.count - group.channels.length} ...</small> : null}
</div>
<footer>
<Button icon={<Edit3 size={16} />} onClick={() => navigate(`/admin/channel-groups/${group.id}/edit`)} size="sm" variant="ghost">
</Button>
<Button icon={<Trash2 size={16} />} onClick={() => removeGroup(group.id)} size="sm" variant="danger">
</Button>
</footer>
</article>
))}
</div>
<Pagination nextDisabled={false} page={1} total={filteredGroups.length} />
</section>
</div>
);
}