重命名前端目录

This commit is contained in:
yml2213
2026-07-07 19:55:18 +08:00
parent 8268a3b906
commit dbcb5a4979
182 changed files with 7019 additions and 7017 deletions
+192
View File
@@ -0,0 +1,192 @@
import {
AuditOutlined,
DashboardOutlined,
FileSearchOutlined,
LogoutOutlined,
MenuFoldOutlined,
MenuUnfoldOutlined,
OrderedListOutlined,
SettingOutlined,
ShopOutlined,
TeamOutlined,
UnorderedListOutlined,
UserOutlined,
} from '@ant-design/icons'
import { App, Button, Dropdown, Layout, Menu, Space, Typography } from 'antd'
import type { MenuProps } from 'antd'
import { useMemo, useState } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router'
import { logoutAdmin } from '@/services/admin'
import {
clearAdminSession,
getAdminRole,
getAdminTokenExpiresAt,
getAdminUsername,
} from '@/utils/admin-auth'
import { formatAdminDateTime } from '@/utils/admin-time'
const SIDEBAR_COLLAPSED_KEY = 'react-admin-sidebar-collapsed'
export default function AdminLayout() {
const navigate = useNavigate()
const location = useLocation()
const { message } = App.useApp()
const [collapsed, setCollapsed] = useState(
() => window.localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === '1',
)
const role = getAdminRole()
const username = getAdminUsername() || 'admin'
const expiresAt = getAdminTokenExpiresAt()
const menuItems = useMemo<MenuProps['items']>(() => {
const operationItems: MenuProps['items'] = [
{ key: '/admin/dashboard', icon: <DashboardOutlined />, label: '概览' },
{ key: '/admin/orders', icon: <OrderedListOutlined />, label: '订单' },
{ key: '/admin/tasks', icon: <UnorderedListOutlined />, label: '任务' },
{ key: '/admin/cloudtentacles-records', icon: <FileSearchOutlined />, label: '发货记录' },
]
const items: MenuProps['items'] = [
{
key: 'group-operation',
type: 'group',
label: '运营',
children: operationItems,
},
]
if (role === 'admin') {
items.push(
{
key: 'group-config',
type: 'group',
label: '配置',
children: [
{ key: '/admin/users', icon: <TeamOutlined />, label: '后台用户' },
{ key: '/admin/platform-shops', icon: <SettingOutlined />, label: '平台配置' },
{ key: '/admin/platform-fulfillment', icon: <ShopOutlined />, label: '履约配置' },
],
},
{
key: 'group-system',
type: 'group',
label: '系统',
children: [{ key: '/admin/audit-logs', icon: <AuditOutlined />, label: '审计日志' }],
},
)
}
return items
}, [role])
async function submitLogout() {
try {
await logoutAdmin()
} catch {
// 后端退出是无状态的,本地清理优先。
} finally {
clearAdminSession()
message.success('已退出后台')
navigate('/admin/login', { replace: true })
}
}
function toggleCollapsed() {
const next = !collapsed
setCollapsed(next)
window.localStorage.setItem(SIDEBAR_COLLAPSED_KEY, next ? '1' : '0')
}
const selectedKey = resolveSelectedKey(location.pathname)
return (
<Layout className="admin-shell">
<Layout.Sider
width={232}
collapsedWidth={72}
collapsed={collapsed}
className="admin-sider"
trigger={null}
>
<div className="admin-sider-inner">
<div className="admin-brand">
<div className="admin-brand-copy">
<strong>{collapsed ? 'OS' : '运营后台'}</strong>
{!collapsed ? <span></span> : null}
</div>
<Button
className="admin-collapse-button"
aria-label={collapsed ? '展开导航' : '折叠导航'}
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={toggleCollapsed}
/>
</div>
{!collapsed ? (
<div className="admin-sidebar-profile">
<Typography.Text strong>{username}</Typography.Text>
<Typography.Text type="secondary">{roleLabel(role)}</Typography.Text>
{expiresAt ? (
<Typography.Text type="secondary">
{formatAdminDateTime(expiresAt)}
</Typography.Text>
) : null}
</div>
) : null}
<Menu
mode="inline"
inlineIndent={16}
selectedKeys={[selectedKey]}
items={menuItems}
className="admin-menu"
onClick={({ key }) => navigate(String(key))}
/>
<div className="admin-sider-footer">
<Button
block={!collapsed}
icon={<LogoutOutlined />}
onClick={submitLogout}
aria-label="退出登录"
>
{collapsed ? null : '退出登录'}
</Button>
</div>
</div>
</Layout.Sider>
<Layout className="admin-main-shell">
<Layout.Header className="admin-topbar">
<Space className="admin-user" size={12}>
<Dropdown
menu={{
items: [{ key: 'logout', label: '退出登录' }],
onClick: submitLogout,
}}
placement="bottomRight"
>
<Button icon={<UserOutlined />}></Button>
</Dropdown>
</Space>
</Layout.Header>
<Layout.Content className="admin-content">
<Outlet />
</Layout.Content>
</Layout>
</Layout>
)
}
function resolveSelectedKey(pathname: string) {
if (pathname.startsWith('/admin/orders')) return '/admin/orders'
if (pathname.startsWith('/admin/tasks')) return '/admin/tasks'
if (pathname === '/admin/platform-shops') return '/admin/platform-shops'
return pathname
}
function roleLabel(role: string) {
if (role === 'admin') return '管理员'
if (role === 'operator') return '普通运营'
return '客服'
}