feat(frontend): 完成品牌 SVG 与后台 Phase 1 基础切片
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import {
|
||||
Avatar,
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Dropdown,
|
||||
Layout,
|
||||
Menu,
|
||||
Space,
|
||||
Tag,
|
||||
Typography,
|
||||
type MenuProps,
|
||||
} from 'antd'
|
||||
import {
|
||||
AccountBookOutlined,
|
||||
BellOutlined,
|
||||
DashboardOutlined,
|
||||
FileDoneOutlined,
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
SettingOutlined,
|
||||
ShareAltOutlined,
|
||||
ShopOutlined,
|
||||
ShoppingCartOutlined,
|
||||
TeamOutlined,
|
||||
} from '@ant-design/icons'
|
||||
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { BrandLogo } from '@/components/BrandLogo'
|
||||
import { getAdminBasePath, getAdminLoginPath } from '@/shared/api/adminClient'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
|
||||
const { Sider, Header, Content } = Layout
|
||||
|
||||
interface NavItem {
|
||||
key: string
|
||||
label: string
|
||||
permission?: string
|
||||
}
|
||||
|
||||
interface NavGroup {
|
||||
key: string
|
||||
label: string
|
||||
icon: ReactNode
|
||||
children: NavItem[]
|
||||
}
|
||||
|
||||
const allNavGroups: NavGroup[] = [
|
||||
{
|
||||
key: 'workspace',
|
||||
label: '工作台',
|
||||
icon: <DashboardOutlined />,
|
||||
children: [{ key: '/', label: '仪表盘', permission: 'dashboard:view' }],
|
||||
},
|
||||
{
|
||||
key: 'trade',
|
||||
label: '交易',
|
||||
icon: <ShoppingCartOutlined />,
|
||||
children: [
|
||||
{ key: '/orders', label: '订单管理', permission: 'order:view' },
|
||||
{ key: '/orders/refund-review', label: '退款审核', permission: 'order:close' },
|
||||
{ key: '/disputes', label: '申诉仲裁', permission: 'dispute:view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'listings',
|
||||
label: '商品',
|
||||
icon: <ShopOutlined />,
|
||||
children: [
|
||||
{ key: '/listings', label: '商品管理', permission: 'listing:view' },
|
||||
{ key: '/listings/review', label: '商品审核', permission: 'listing:approve' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'users',
|
||||
label: '用户',
|
||||
icon: <TeamOutlined />,
|
||||
children: [{ key: '/users', label: '用户管理', permission: 'user:view' }],
|
||||
},
|
||||
{
|
||||
key: 'finance',
|
||||
label: '财务',
|
||||
icon: <AccountBookOutlined />,
|
||||
children: [
|
||||
{ key: '/finance', label: '财务看板', permission: 'finance:view' },
|
||||
{ key: '/withdrawals', label: '提现审核', permission: 'withdrawal:view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'distribution',
|
||||
label: '分销',
|
||||
icon: <ShareAltOutlined />,
|
||||
children: [
|
||||
{ key: '/distribution/sites', label: '站点管理', permission: 'distribution:site_view' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'operations',
|
||||
label: '运营',
|
||||
icon: <BellOutlined />,
|
||||
children: [{ key: '/announcements', label: '公告管理', permission: 'announcement:view' }],
|
||||
},
|
||||
{
|
||||
key: 'system',
|
||||
label: '系统',
|
||||
icon: <SettingOutlined />,
|
||||
children: [
|
||||
{ key: '/system-configs', label: '系统配置', permission: 'systemconfig:view' },
|
||||
{ key: '/audit-logs', label: '审计日志', permission: 'audit:view' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const sectionMeta: Record<string, { title: string; description: string }> = {
|
||||
'/orders': { title: '订单管理', description: '查看全量订单、交接记录与结账状态。' },
|
||||
'/orders/refund-review': { title: '退款审核', description: '审核退款申请并核对资金影响。' },
|
||||
'/disputes': { title: '申诉仲裁', description: '处理订单争议、证据与仲裁结果。' },
|
||||
'/listings': { title: '商品管理', description: '查看商品、账号资产与上下架状态。' },
|
||||
'/listings/review': { title: '商品审核', description: '审核待上架商品与账号截图。' },
|
||||
'/users': { title: '用户管理', description: '查看用户、实名状态与交易记录。' },
|
||||
'/finance': { title: '财务看板', description: '查看收入、退款、结算与资金趋势。' },
|
||||
'/withdrawals': { title: '提现审核', description: '审核提现申请与打款确认。' },
|
||||
'/distribution/sites': { title: '分销站点', description: '管理分站、规则、结算与站点钱包。' },
|
||||
'/announcements': { title: '公告管理', description: '维护主站与分站公告。' },
|
||||
'/system-configs': { title: '系统配置', description: '维护首页、发布、订单与支付配置。' },
|
||||
'/audit-logs': { title: '审计日志', description: '查看高风险操作与变更记录。' },
|
||||
}
|
||||
|
||||
export function AdminLayout() {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const admin = useAdminSessionStore(state => state.admin)
|
||||
const logout = useAdminSessionStore(state => state.logout)
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
|
||||
const basePath = getAdminBasePath()
|
||||
const relativePath =
|
||||
location.pathname.startsWith(basePath) && location.pathname !== basePath
|
||||
? location.pathname.slice(basePath.length)
|
||||
: '/'
|
||||
|
||||
const can = (permission?: string) => {
|
||||
if (!permission) return true
|
||||
const permissions = admin?.permissions ?? []
|
||||
return permissions.includes('*') || permissions.includes(permission)
|
||||
}
|
||||
|
||||
const visibleGroups = allNavGroups
|
||||
.map(group => ({
|
||||
...group,
|
||||
children: group.children.filter(item => can(item.permission)),
|
||||
}))
|
||||
.filter(group => group.children.length > 0)
|
||||
|
||||
const menuItems: MenuProps['items'] = visibleGroups.map(group => ({
|
||||
key: group.key,
|
||||
icon: group.icon,
|
||||
label: group.label,
|
||||
children: group.children.map(item => ({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
})),
|
||||
}))
|
||||
|
||||
const currentLabel =
|
||||
visibleGroups.flatMap(group => group.children).find(item => item.key === relativePath)?.label ||
|
||||
sectionMeta[relativePath]?.title ||
|
||||
(relativePath === '/' ? '仪表盘' : '功能建设中')
|
||||
|
||||
const onLogout = async () => {
|
||||
await logout()
|
||||
navigate(getAdminLoginPath(), { replace: true })
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout className="admin-shell">
|
||||
<Sider
|
||||
className="admin-sider"
|
||||
theme="light"
|
||||
width={240}
|
||||
collapsedWidth={72}
|
||||
collapsed={collapsed}
|
||||
trigger={null}
|
||||
>
|
||||
<Link to={basePath} className="admin-sider__brand" aria-label="解忧商行后台首页">
|
||||
<BrandLogo height={34} variant="light" />
|
||||
{!collapsed ? <span className="admin-sider__phase">OPS · P1</span> : null}
|
||||
</Link>
|
||||
|
||||
<Menu
|
||||
className="admin-menu"
|
||||
mode="inline"
|
||||
theme="light"
|
||||
inlineCollapsed={collapsed}
|
||||
items={menuItems}
|
||||
selectedKeys={[relativePath]}
|
||||
defaultOpenKeys={visibleGroups.map(group => group.key)}
|
||||
onClick={({ key }) => {
|
||||
if (key.startsWith('/')) {
|
||||
navigate(key === '/' ? basePath : `${basePath}${key}`)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{!collapsed ? (
|
||||
<div className="admin-sider__foot">
|
||||
<Typography.Text type="secondary">解忧商行 · 运营后台</Typography.Text>
|
||||
<Typography.Text type="secondary">Phase 1 Shell</Typography.Text>
|
||||
</div>
|
||||
) : null}
|
||||
</Sider>
|
||||
|
||||
<Layout className="admin-main">
|
||||
<Header className="admin-header">
|
||||
<div className="admin-header__left">
|
||||
<Button
|
||||
type="text"
|
||||
className="admin-collapse"
|
||||
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
onClick={() => setCollapsed(value => !value)}
|
||||
aria-label={collapsed ? '展开侧栏' : '收起侧栏'}
|
||||
/>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ title: '解忧商行' },
|
||||
{ title: currentLabel },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Space size={16}>
|
||||
{admin?.roles?.[0] ? <Tag className="admin-role-tag">{admin.roles[0].name}</Tag> : null}
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'account',
|
||||
label: `${admin?.nickname || admin?.username || '管理员'} · ${admin?.username || ''}`,
|
||||
disabled: true,
|
||||
},
|
||||
{ type: 'divider' },
|
||||
{ key: 'logout', label: '退出后台', icon: <LogoutOutlined />, onClick: onLogout },
|
||||
],
|
||||
}}
|
||||
placement="bottomRight"
|
||||
>
|
||||
<Button type="text" className="admin-account">
|
||||
<Avatar size={30} className="admin-avatar">
|
||||
{(admin?.nickname || admin?.username || '管').slice(0, 1)}
|
||||
</Avatar>
|
||||
<span className="admin-account__name">{admin?.nickname || admin?.username}</span>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
</Header>
|
||||
|
||||
<Content className="admin-content">
|
||||
<Outlet />
|
||||
</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export function AdminSectionPlaceholder({ title, description }: { title: string; description: string }) {
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<div className="admin-page__head">
|
||||
<div>
|
||||
<Typography.Text className="admin-page__eyebrow">JIEYOU OPS</Typography.Text>
|
||||
<Typography.Title level={2} className="brand-title">
|
||||
{title}
|
||||
</Typography.Title>
|
||||
<Typography.Text type="secondary">{description}</Typography.Text>
|
||||
</div>
|
||||
<Tag icon={<FileDoneOutlined />} className="admin-phase-tag">
|
||||
Phase 1 · 待迁移
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
<div className="admin-placeholder-card">
|
||||
<span className="admin-placeholder-card__seal">解忧</span>
|
||||
<Typography.Title level={4}>这个页面还没开始迁移</Typography.Title>
|
||||
<Typography.Paragraph type="secondary">
|
||||
当前批次先把后台登录、权限会话、浅色 Shell 和真实仪表盘跑通。{title}
|
||||
会在后续批次按统一列表/详情/表单原型迁移,不会直接复用旧站页面。
|
||||
</Typography.Paragraph>
|
||||
<Button type="primary" onClick={() => window.history.back()}>
|
||||
返回上一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function getAdminSectionMeta(key: string) {
|
||||
return sectionMeta[key]
|
||||
}
|
||||
Reference in New Issue
Block a user