style: 优化后台整体UI风格与组件样式
1. 新增并配置全局颜色、阴影、圆角等主题变量 2. 统一各页面卡片、表单、按钮等组件的样式规范 3. 优化登录页、个人中心、仪表盘等页面的布局与视觉效果 4. 添加全局滚动条样式与元素过渡动画
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Card } from '@arco-design/web-react';
|
import { Card } from '@arco-design/web-react';
|
||||||
|
import { IconPlayCircle, IconUser, IconFile, IconCheckCircle } from '@arco-design/web-react/icon';
|
||||||
import { dramaApi, logApi, userApi } from '../api';
|
import { dramaApi, logApi, userApi } from '../api';
|
||||||
import { usePageData } from '../hooks/usePageData';
|
import { usePageData } from '../hooks/usePageData';
|
||||||
import { hasPermissions } from '../utils/permission';
|
import { hasPermissions } from '../utils/permission';
|
||||||
@@ -6,8 +7,32 @@ import { NoAccessPage } from '../components/NoAccessPage';
|
|||||||
import { RequestLogTable } from '../components/RequestLogTable';
|
import { RequestLogTable } from '../components/RequestLogTable';
|
||||||
import type { AppUser, DashboardPageProps, Drama, MetricCardProps, RequestLog } from './interface';
|
import type { AppUser, DashboardPageProps, Drama, MetricCardProps, RequestLog } from './interface';
|
||||||
|
|
||||||
function MetricCard({ label, value }: MetricCardProps) {
|
const METRIC_ICONS: Record<string, React.ReactNode> = {
|
||||||
return <Card><div className="mb-2 text-muted">{label}</div><div className="text-[28px] font-bold">{value}</div></Card>;
|
dramas: <IconPlayCircle className="text-2xl text-brand" />,
|
||||||
|
users: <IconUser className="text-2xl text-success" />,
|
||||||
|
logs: <IconFile className="text-2xl text-warning" />,
|
||||||
|
status: <IconCheckCircle className="text-2xl text-success" />,
|
||||||
|
};
|
||||||
|
|
||||||
|
const METRIC_COLORS: Record<string, string> = {
|
||||||
|
dramas: 'from-brand/5 to-brand/10',
|
||||||
|
users: 'from-success/5 to-success/10',
|
||||||
|
logs: 'from-warning/5 to-warning/10',
|
||||||
|
status: 'from-success/5 to-success/10',
|
||||||
|
};
|
||||||
|
|
||||||
|
function MetricCard({ label, value, icon }: MetricCardProps & { icon: string }) {
|
||||||
|
return (
|
||||||
|
<div className={`stat-card bg-gradient-to-br ${METRIC_COLORS[icon]}`}>
|
||||||
|
<div className="mb-3 flex items-center gap-3">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-white/80 shadow-sm">
|
||||||
|
{METRIC_ICONS[icon]}
|
||||||
|
</div>
|
||||||
|
<span className="text-[13px] font-medium text-muted">{label}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-[28px] font-bold tracking-tight text-ink">{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DashboardPage({ profile }: DashboardPageProps) {
|
function DashboardPage({ profile }: DashboardPageProps) {
|
||||||
@@ -21,11 +46,11 @@ function DashboardPage({ profile }: DashboardPageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4 grid grid-cols-1 gap-4 sm:grid-cols-2 min-[901px]:grid-cols-4">
|
<div className="mb-5 grid grid-cols-1 gap-4 sm:grid-cols-2 min-[901px]:grid-cols-4">
|
||||||
{canReadDramas && <MetricCard label="短剧管理" value={dramas.data.pagination.total} />}
|
{canReadDramas && <MetricCard label="短剧管理" value={dramas.data.pagination.total} icon="dramas" />}
|
||||||
{canReadUsers && <MetricCard label="用户管理" value={users.data.pagination.total} />}
|
{canReadUsers && <MetricCard label="用户管理" value={users.data.pagination.total} icon="users" />}
|
||||||
{canReadLogs && <MetricCard label="请求日志" value={logs.data.pagination.total} />}
|
{canReadLogs && <MetricCard label="请求日志" value={logs.data.pagination.total} icon="logs" />}
|
||||||
<MetricCard label="接口状态" value="运行中" />
|
<MetricCard label="接口状态" value="运行中" icon="status" />
|
||||||
</div>
|
</div>
|
||||||
{canReadLogs ? (
|
{canReadLogs ? (
|
||||||
<Card className="section-card" title="最近请求">
|
<Card className="section-card" title="最近请求">
|
||||||
|
|||||||
@@ -42,13 +42,16 @@ export function DramaList() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="page-stack">
|
||||||
<nav className="flex justify-end border border border-[#e5e5e5] pb-4">
|
<Card
|
||||||
|
className="section-card"
|
||||||
|
title="短剧管理"
|
||||||
|
extra={
|
||||||
<Button type="primary" icon={<IconPlus />} onClick={() => setVisible(true)}>
|
<Button type="primary" icon={<IconPlus />} onClick={() => setVisible(true)}>
|
||||||
新增短剧
|
新增短剧
|
||||||
</Button>
|
</Button>
|
||||||
</nav>
|
}
|
||||||
<Card className="section-card" title="短剧管理">
|
>
|
||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
|||||||
@@ -2,30 +2,26 @@ import { Button, Form, Input } from "@arco-design/web-react";
|
|||||||
import { IconLock, IconUser } from "@arco-design/web-react/icon";
|
import { IconLock, IconUser } from "@arco-design/web-react/icon";
|
||||||
import type { LoginPanelProps } from "../interface";
|
import type { LoginPanelProps } from "../interface";
|
||||||
|
|
||||||
export function LoginPanel({ loading, apiBaseUrl, onSubmit }: LoginPanelProps) {
|
export const LoginPanel = ({ loading, onSubmit }: LoginPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center bg-[#f7f8fa] bg-[image:linear-gradient(180deg,rgba(255,255,255,0.86),rgba(247,248,250,0.94))] p-12">
|
<div className="flex items-center justify-center bg-[#f7f8fa] bg-[image:linear-gradient(180deg,rgba(255,255,255,0.86),rgba(247,248,250,0.94))] p-12">
|
||||||
<div className="w-full max-w-[420px] rounded-lg border border-line bg-white p-9 shadow-login">
|
<div className="w-full max-w-[420px] rounded-2xl border border-line/60 bg-white p-9 shadow-login">
|
||||||
<div className="mb-[30px] flex items-center gap-3.5">
|
<div className="mb-8 flex items-center flex-col text-center gap-3.5">
|
||||||
<span className="grid h-11 w-11 place-items-center rounded-lg bg-brand font-bold text-white">CT</span>
|
<h2 className="mb-1 w-full mt-0 text-2xl font-semibold text-ink">后台登录</h2>
|
||||||
<div>
|
<p className="m-0 w-full text-[13px] text-muted">使用管理员账号进入控制台</p>
|
||||||
<h2 className="mb-2 mt-0 text-2xl">后台登录</h2>
|
|
||||||
<p className="m-0 text-muted">使用管理员账号进入控制台。</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Form layout="vertical" onSubmit={onSubmit} initialValues={{ username: "admin", password: "admin123" }}>
|
<Form layout="vertical" onSubmit={onSubmit} initialValues={{ username: "admin", password: "admin123" }}>
|
||||||
<Form.Item label="用户名" field="username" rules={[{ required: true }]}>
|
<Form.Item label="用户名" field="username" rules={[{ required: true }]}>
|
||||||
<Input prefix={<IconUser />} placeholder="admin" />
|
<Input prefix={<IconUser className="text-muted" />} placeholder="请输入用户名" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="密码" field="password" rules={[{ required: true }]}>
|
<Form.Item label="密码" field="password" rules={[{ required: true }]}>
|
||||||
<Input.Password prefix={<IconLock />} placeholder="admin123" />
|
<Input.Password prefix={<IconLock className="text-muted" />} placeholder="请输入密码" size="large" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" htmlType="submit" loading={loading} long>
|
<Button type="primary" htmlType="submit" loading={loading} long size="large" className="!h-11 mt-6 !rounded-lg !text-[15px] !font-medium">
|
||||||
登录
|
登录
|
||||||
</Button>
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
<div className="mt-[22px] border-t border-canvas pt-[18px] text-xs text-muted">API 服务:{apiBaseUrl}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -2,26 +2,26 @@ export function LoginVisual() {
|
|||||||
return (
|
return (
|
||||||
<div className="relative hidden flex-col justify-between overflow-hidden px-14 pb-12 pt-11 text-white after:pointer-events-none after:absolute after:inset-0 after:bg-[linear-gradient(180deg,rgba(255,255,255,0.08),rgba(255,255,255,0))] min-[901px]:flex bg-[linear-gradient(135deg,rgba(15,23,42,0.98)_0%,rgba(22,93,255,0.94)_54%,rgba(15,118,110,0.96)_100%),radial-gradient(circle_at_18%_18%,rgba(255,255,255,0.18),transparent_28%),radial-gradient(circle_at_82%_72%,rgba(255,255,255,0.12),transparent_32%)]">
|
<div className="relative hidden flex-col justify-between overflow-hidden px-14 pb-12 pt-11 text-white after:pointer-events-none after:absolute after:inset-0 after:bg-[linear-gradient(180deg,rgba(255,255,255,0.08),rgba(255,255,255,0))] min-[901px]:flex bg-[linear-gradient(135deg,rgba(15,23,42,0.98)_0%,rgba(22,93,255,0.94)_54%,rgba(15,118,110,0.96)_100%),radial-gradient(circle_at_18%_18%,rgba(255,255,255,0.18),transparent_28%),radial-gradient(circle_at_82%_72%,rgba(255,255,255,0.12),transparent_32%)]">
|
||||||
<div className="relative z-[1] flex items-center gap-2.5 font-bold">
|
<div className="relative z-[1] flex items-center gap-2.5 font-bold">
|
||||||
<span className="grid h-7 w-7 place-items-center rounded-md bg-brand text-sm text-white">CT</span>
|
<span className="grid h-8 w-8 place-items-center rounded-lg bg-white/20 text-sm backdrop-blur-sm">CT</span>
|
||||||
<span>cth-tk-admin</span>
|
<span className="text-white/90">cth-tk-admin</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-[1] max-w-[640px]">
|
<div className="relative z-[1] max-w-[640px]">
|
||||||
<span className="mb-[18px] inline-flex rounded-full border border-white/20 px-3 py-1.5 text-[13px] text-white/80">TikTok 短剧运营后台</span>
|
<span className="mb-5 inline-flex rounded-full border border-white/20 bg-white/10 px-4 py-2 text-[13px] text-white/80 backdrop-blur-sm">TikTok 短剧运营后台</span>
|
||||||
<h1 className="mb-4 mt-0 text-[42px] leading-[1.16]">CTH TikTok 短剧管理后台</h1>
|
<h1 className="mb-5 mt-0 text-[44px] font-bold leading-[1.16] tracking-tight">CTH TikTok 短剧管理后台</h1>
|
||||||
<p className="m-0 max-w-[560px] text-base leading-[1.8] text-white/75">集中处理短剧内容、剧集发布、订单支付、播放日志和系统权限,帮助运营人员稳定管理短剧业务。</p>
|
<p className="m-0 max-w-[560px] text-base leading-[1.8] text-white/70">集中处理短剧内容、剧集发布、订单支付、播放日志和系统权限,帮助运营人员稳定管理短剧业务。</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-[1] grid max-w-[640px] grid-cols-3 gap-3">
|
<div className="relative z-[1] grid max-w-[640px] grid-cols-3 gap-4">
|
||||||
<div className="rounded-lg border border-white/20 bg-white/10 p-4 backdrop-blur-[10px]">
|
<div className="rounded-xl border border-white/15 bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15">
|
||||||
<strong className="mb-1.5 block text-xl">内容</strong>
|
<strong className="mb-2 block text-xl font-semibold">内容</strong>
|
||||||
<span className="text-white/70">短剧与剧集管理</span>
|
<span className="text-[13px] text-white/65">短剧与剧集管理</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border border-white/20 bg-white/10 p-4 backdrop-blur-[10px]">
|
<div className="rounded-xl border border-white/15 bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15">
|
||||||
<strong className="mb-1.5 block text-xl">权限</strong>
|
<strong className="mb-2 block text-xl font-semibold">权限</strong>
|
||||||
<span className="text-white/70">角色化后台授权</span>
|
<span className="text-[13px] text-white/65">角色化后台授权</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border border-white/20 bg-white/10 p-4 backdrop-blur-[10px]">
|
<div className="rounded-xl border border-white/15 bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15">
|
||||||
<strong className="mb-1.5 block text-xl">日志</strong>
|
<strong className="mb-2 block text-xl font-semibold">日志</strong>
|
||||||
<span className="text-white/70">请求与播放追踪</span>
|
<span className="text-[13px] text-white/65">请求与播放追踪</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export function ProfileInfoList({ profile }: ProfileInfoListProps) {
|
|||||||
|
|
||||||
function ProfileInfoItem({ label, value }: ProfileInfoItemProps) {
|
function ProfileInfoItem({ label, value }: ProfileInfoItemProps) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-line bg-panel p-3.5">
|
<div className="rounded-xl border border-line/60 bg-canvas/50 p-4 transition-all duration-200 hover:bg-canvas">
|
||||||
<span className="mb-2 block text-[13px] text-muted">{label}</span>
|
<span className="mb-2 block text-[12px] font-medium uppercase tracking-wider text-muted">{label}</span>
|
||||||
<strong className="break-words font-medium text-ink">{value}</strong>
|
<strong className="break-words text-[15px] font-semibold text-ink">{value}</strong>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import type { ProfileSummaryProps } from "../interface";
|
|||||||
|
|
||||||
export function ProfileSummary({ profile }: ProfileSummaryProps) {
|
export function ProfileSummary({ profile }: ProfileSummaryProps) {
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 flex items-center gap-4">
|
<div className="mb-6 flex items-center gap-4 rounded-xl bg-gradient-to-br from-brand/5 to-brand/10 p-5">
|
||||||
<Avatar size={56}>{profile?.username?.slice(0, 1).toUpperCase() || "A"}</Avatar>
|
<Avatar size={60} className="!bg-brand !text-white !text-xl !font-bold !shadow-lg !shadow-brand/30">
|
||||||
|
{profile?.username?.slice(0, 1).toUpperCase() || "A"}
|
||||||
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="mb-1.5 mt-0 text-[22px]">{profile?.nickname || profile?.username || "管理员"}</h2>
|
<h2 className="mb-1 mt-0 text-xl font-semibold text-ink">{profile?.nickname || profile?.username || "管理员"}</h2>
|
||||||
<p className="m-0 text-muted">{profile?.email || "未设置邮箱"}</p>
|
<p className="m-0 text-[13px] text-muted">{profile?.email || "未设置邮箱"}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ function ProfilePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div className="page-stack">
|
||||||
<div className="grid items-start gap-4 min-[901px]:grid-cols-[minmax(0,1fr)_420px]">
|
<div className="grid items-start gap-4 min-[901px]:grid-cols-[minmax(0,1fr)_420px]">
|
||||||
<Card className="section-card min-[901px]:row-span-2" title="个人中心" loading={loading}>
|
<Card className="section-card min-[901px]:row-span-2" title="个人中心" loading={loading}>
|
||||||
<ProfileSummary profile={profile} />
|
<ProfileSummary profile={profile} />
|
||||||
@@ -96,6 +97,7 @@ function ProfilePage() {
|
|||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export function PermissionTreePicker({ permissions, value, onChange }: Permissio
|
|||||||
const checkedKeys = value.map((id) => permissionKey(id));
|
const checkedKeys = value.map((id) => permissionKey(id));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-[15px] h-[360px] overflow-auto rounded-md border border-line bg-panel px-4 py-3">
|
<div className="mb-[15px] h-[360px] overflow-auto rounded-xl border border-line/60 bg-canvas/30 px-4 py-3">
|
||||||
<Tree
|
<Tree
|
||||||
blockNode
|
blockNode
|
||||||
checkable
|
checkable
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { Card, Typography } from "@arco-design/web-react";
|
import { Card, Typography } from "@arco-design/web-react";
|
||||||
|
import { IconLock } from "@arco-design/web-react/icon";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export function NoAccessPage() {
|
export function NoAccessPage() {
|
||||||
return (
|
return (
|
||||||
<Card className="section-card" title="暂无权限">
|
<Card className="section-card" title="暂无权限">
|
||||||
<Text type="secondary">当前账号没有访问该功能的权限,请联系系统管理员分配角色。</Text>
|
<div className="flex flex-col items-center py-8">
|
||||||
|
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-warning/10">
|
||||||
|
<IconLock className="text-3xl text-warning" />
|
||||||
|
</div>
|
||||||
|
<Text type="secondary" className="text-center">当前账号没有访问该功能的权限,请联系系统管理员分配角色。</Text>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,27 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply transition-colors duration-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
@apply w-1.5 h-1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
@apply bg-transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
@apply rounded-full bg-line hover:bg-muted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@layer components {
|
@layer components {
|
||||||
.section-card {
|
.section-card {
|
||||||
@apply rounded-lg;
|
@apply rounded-xl border border-line/60 bg-white shadow-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-stack {
|
.page-stack {
|
||||||
@@ -22,4 +40,82 @@
|
|||||||
.form-help {
|
.form-help {
|
||||||
@apply -mt-3 mb-[18px] text-[13px] leading-[1.6] text-muted;
|
@apply -mt-3 mb-[18px] text-[13px] leading-[1.6] text-muted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
@apply rounded-xl border border-line/60 bg-white p-5 shadow-sm transition-all duration-300 hover:shadow-md hover:-translate-y-0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-layout-sider {
|
||||||
|
transition: width 0.2s cubic-bezier(0.2, 0, 0, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-layout-sider .arco-menu {
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-card {
|
||||||
|
border-radius: 0.75rem !important;
|
||||||
|
border: 1px solid rgba(229, 230, 235, 0.6) !important;
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.04) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-table-container {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-table-th {
|
||||||
|
background-color: #fafbfc !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
color: #4e5969 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-table-tr:hover .arco-table-td {
|
||||||
|
background-color: #f7f8fa !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn-primary {
|
||||||
|
box-shadow: 0 2px 6px rgba(22, 93, 251, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn-primary:hover {
|
||||||
|
box-shadow: 0 4px 12px rgba(22, 93, 251, 0.4) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-modal {
|
||||||
|
border-radius: 0.75rem !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-modal-header {
|
||||||
|
border-bottom: 1px solid rgba(229, 230, 235, 0.6) !important;
|
||||||
|
padding: 16px 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-modal-body {
|
||||||
|
padding: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper {
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
transition: all 0.2s !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper:hover {
|
||||||
|
border-color: #c9cdd4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper.arco-input-focus {
|
||||||
|
border-color: #165dff !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(22, 93, 251, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-select-view {
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-form-item-label {
|
||||||
|
font-weight: 500 !important;
|
||||||
|
color: #4e5969 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,50 +48,56 @@ export function AdminLayout() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout className="min-h-screen">
|
<Layout className="min-h-screen bg-canvas">
|
||||||
<Sider className="shadow-[1px_0_0_#e5e6eb]" width={220} collapsed={collapsed} collapsible trigger={null}>
|
<Sider className="shadow-[1px_0_0_#e5e6eb] bg-white" width={220} collapsed={collapsed} collapsible trigger={null}>
|
||||||
<div className="flex h-14 items-center gap-2.5 border-b border-line px-[18px] font-bold">
|
<div className="flex h-14 items-center gap-2.5 border-b border-line/60 px-[18px]">
|
||||||
<span className="grid h-7 w-7 place-items-center rounded-md bg-brand text-sm text-white">TK</span>
|
<span className="grid h-8 w-8 place-items-center rounded-lg bg-brand text-sm font-bold text-white shadow-sm">TK</span>
|
||||||
{!collapsed && <span>TK短剧管理后台</span>}
|
{!collapsed && <span className="font-semibold text-ink">TK短剧管理后台</span>}
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
selectedKeys={[current?.item.key || "/dashboard"]}
|
selectedKeys={[current?.item.key || "/dashboard"]}
|
||||||
openKeys={openMenuKeys}
|
openKeys={openMenuKeys}
|
||||||
onClickMenuItem={(key) => navigate(key)}
|
onClickMenuItem={(key) => navigate(key)}
|
||||||
onClickSubMenu={(_, openKeys) => setOpenMenuKeys(openKeys)}
|
onClickSubMenu={(_, openKeys) => setOpenMenuKeys(openKeys)}
|
||||||
|
className="!border-none"
|
||||||
>
|
>
|
||||||
{visibleMenuItems.map((item) => renderMenuItem(item))}
|
{visibleMenuItems.map((item) => renderMenuItem(item))}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Sider>
|
</Sider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<Header className="flex h-14 items-center justify-between border-b border-line bg-white px-5">
|
<Header className="flex h-14 items-center justify-between border-b border-line/60 bg-white/80 px-5 backdrop-blur-md">
|
||||||
<Space>
|
<Space>
|
||||||
<Button icon={collapsed ? <IconMenuUnfold /> : <IconMenuFold />} onClick={() => setCollapsed(!collapsed)} />
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={collapsed ? <IconMenuUnfold /> : <IconMenuFold />}
|
||||||
|
onClick={() => setCollapsed(!collapsed)}
|
||||||
|
className="!text-muted hover:!text-ink"
|
||||||
|
/>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
<strong>{current?.item.label || "工作台"}</strong>
|
<strong className="text-[15px] text-ink">{current?.item.label || "工作台"}</strong>
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
position="br"
|
position="br"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
droplist={
|
droplist={
|
||||||
<Menu>
|
<Menu className="!rounded-lg !border-line/60 !shadow-lg">
|
||||||
<Menu.Item key="profile" onClick={() => navigate("/profile")}>
|
<Menu.Item key="profile" onClick={() => navigate("/profile")} className="!rounded-md">
|
||||||
个人中心
|
个人中心
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="logout" onClick={logout}>
|
<Menu.Item key="logout" onClick={logout} className="!rounded-md">
|
||||||
退出登录
|
退出登录
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<button className="inline-flex cursor-pointer items-center gap-2 rounded-md border-0 bg-transparent px-2 py-1 text-ink hover:bg-canvas" type="button">
|
<button className="inline-flex cursor-pointer items-center gap-2 rounded-lg border-0 bg-transparent px-2.5 py-1.5 text-ink transition-all duration-200 hover:bg-canvas" type="button">
|
||||||
<Avatar size={28}>{profile?.username?.slice(0, 1).toUpperCase() || "A"}</Avatar>
|
<Avatar size={30} className="!bg-brand !text-white">{profile?.username?.slice(0, 1).toUpperCase() || "A"}</Avatar>
|
||||||
<Text>{profile?.nickname || profile?.username || "管理员"}</Text>
|
<Text className="!text-[13px]">{profile?.nickname || profile?.username || "管理员"}</Text>
|
||||||
</button>
|
</button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Header>
|
</Header>
|
||||||
<Content className="p-3 sm:p-5">
|
<Content className="p-4 sm:p-6">
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/dashboard" element={<DashboardPage profile={profile} />} />
|
<Route path="/dashboard" element={<DashboardPage profile={profile} />} />
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export default {
|
|||||||
canvas: '#f2f3f5',
|
canvas: '#f2f3f5',
|
||||||
panel: '#fbfcfd',
|
panel: '#fbfcfd',
|
||||||
brand: '#165dff',
|
brand: '#165dff',
|
||||||
|
'brand-light': '#e8f3ff',
|
||||||
|
success: '#00b42a',
|
||||||
|
warning: '#ff7d00',
|
||||||
|
danger: '#f53f3f',
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: [
|
sans: [
|
||||||
@@ -28,6 +32,11 @@ export default {
|
|||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
login: '0 18px 48px rgba(15, 23, 42, 0.08)',
|
login: '0 18px 48px rgba(15, 23, 42, 0.08)',
|
||||||
|
card: '0 1px 3px rgba(0,0,0,0.04), 0 1px 2px rgba(0,0,0,0.02)',
|
||||||
|
'card-hover': '0 4px 12px rgba(0,0,0,0.08)',
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
xl: '0.75rem',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user