style: 移除多余的rounded-xl样式并整理代码格式

1. 移除多个组件冗余的rounded-xl类名,统一圆角样式配置
2. 调整Dashboard页面代码缩进与引号风格为统一的双引号格式
3. 简化DramaDetail页面布局结构,重构详情展示区域
4. 清理index.css中多余的descriptions自定义样式
5. 更新gitignore忽略.trae和.mimocode目录
This commit is contained in:
2026-07-03 16:12:00 +08:00
parent 885d17cec7
commit 82524c4312
10 changed files with 133 additions and 140 deletions

2
.gitignore vendored
View File

@@ -14,6 +14,8 @@ dist-ssr
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
.trae/*
.mimocode/*
!.vscode/extensions.json !.vscode/extensions.json
.idea .idea
.DS_Store .DS_Store

View File

@@ -122,7 +122,6 @@
- `border-line/60` 等边框 — 保持 1px - `border-line/60` 等边框 — 保持 1px
- `shadow-*` 类 — shadow 值保持 px - `shadow-*` 类 — shadow 值保持 px
- Arco 组件的 `size` prop如 Avatar `size={32}`)— Arco 内部用 px保持数字 - Arco 组件的 `size` prop如 Avatar `size={32}`)— Arco 内部用 px保持数字
- `rounded-xl`, `rounded-2xl` 等 — 已在 config 中定义为 rem
## 实施顺序 ## 实施顺序

View File

@@ -1,65 +1,63 @@
import { IconPlayCircle, IconUser, IconFile, IconCheckCircle } from '@arco-design/web-react/icon'; 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";
import { NoAccessPage } from '../components/NoAccessPage'; import { NoAccessPage } from "../components/NoAccessPage";
import { Panel } from '../components/Panel'; import { Panel } from "../components/Panel";
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";
const METRIC_ICONS: Record<string, React.ReactNode> = { const METRIC_ICONS: Record<string, React.ReactNode> = {
dramas: <IconPlayCircle className="text-2xl text-brand" />, dramas: <IconPlayCircle className="text-2xl text-brand" />,
users: <IconUser className="text-2xl text-success" />, users: <IconUser className="text-2xl text-success" />,
logs: <IconFile className="text-2xl text-warning" />, logs: <IconFile className="text-2xl text-warning" />,
status: <IconCheckCircle className="text-2xl text-success" />, status: <IconCheckCircle className="text-2xl text-success" />,
}; };
const METRIC_COLORS: Record<string, string> = { const METRIC_COLORS: Record<string, string> = {
dramas: 'from-brand/5 to-brand/10', dramas: "from-brand/5 to-brand/10",
users: 'from-success/5 to-success/10', users: "from-success/5 to-success/10",
logs: 'from-warning/5 to-warning/10', logs: "from-warning/5 to-warning/10",
status: 'from-success/5 to-success/10', status: "from-success/5 to-success/10",
}; };
function MetricCard({ label, value, icon }: MetricCardProps & { icon: string }) { function MetricCard({ label, value, icon }: MetricCardProps & { icon: string }) {
return ( return (
<div className={`stat-card animate-slide-up bg-gradient-to-br ${METRIC_COLORS[icon]}`}> <div className={`stat-card animate-slide-up bg-gradient-to-br ${METRIC_COLORS[icon]}`}>
<div className="mb-3.5 flex items-center gap-3"> <div className="mb-3.5 flex items-center gap-2">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-white/80 shadow-sm ring-1 ring-black/[0.03]"> <div className="flex h-10 w-10 items-center justify-center">{METRIC_ICONS[icon]}</div>
{METRIC_ICONS[icon]} <span className="text-[1.1rem] font-medium text-muted">{label}</span>
</div>
<div className="text-[1.6rem] font-bold tracking-tight text-ink">{value}</div>
</div> </div>
<span className="text-[1.125rem] font-medium text-muted">{label}</span> );
</div>
<div className="text-[1.875rem] font-bold tracking-tight text-ink">{value}</div>
</div>
);
} }
function DashboardPage({ profile }: DashboardPageProps) { function DashboardPage({ profile }: DashboardPageProps) {
const canReadDramas = hasPermissions(profile, ['drama:read']); const canReadDramas = hasPermissions(profile, ["drama:read"]);
const canReadUsers = hasPermissions(profile, ['user:read']); const canReadUsers = hasPermissions(profile, ["user:read"]);
const canReadLogs = hasPermissions(profile, ['log:read']); const canReadLogs = hasPermissions(profile, ["log:read"]);
const dramas = usePageData<Drama>(dramaApi.listDramas, { page: 1, pageSize: 20 }, canReadDramas); const dramas = usePageData<Drama>(dramaApi.listDramas, { page: 1, pageSize: 20 }, canReadDramas);
const users = usePageData<AppUser>(userApi.listUsers, { page: 1, pageSize: 20 }, canReadUsers); const users = usePageData<AppUser>(userApi.listUsers, { page: 1, pageSize: 20 }, canReadUsers);
const logs = usePageData<RequestLog>(logApi.listRequestLogs, { page: 1, pageSize: 20 }, canReadLogs); const logs = usePageData<RequestLog>(logApi.listRequestLogs, { page: 1, pageSize: 20 }, canReadLogs);
const hasAnyDataModule = canReadDramas || canReadUsers || canReadLogs; const hasAnyDataModule = canReadDramas || canReadUsers || canReadLogs;
return ( return (
<div className="page-stack"> <div className="page-stack">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 min-[901px]:grid-cols-4"> <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 min-[901px]:grid-cols-4">
{canReadDramas && <MetricCard label="短剧管理" value={dramas.data.pagination.total} icon="dramas" />} {canReadDramas && <MetricCard label="短剧管理" value={dramas.data.pagination.total} icon="dramas" />}
{canReadUsers && <MetricCard label="用户管理" value={users.data.pagination.total} icon="users" />} {canReadUsers && <MetricCard label="用户管理" value={users.data.pagination.total} icon="users" />}
{canReadLogs && <MetricCard label="请求日志" value={logs.data.pagination.total} icon="logs" />} {canReadLogs && <MetricCard label="请求日志" value={logs.data.pagination.total} icon="logs" />}
<MetricCard label="接口状态" value="运行中" icon="status" /> <MetricCard label="接口状态" value="运行中" icon="status" />
</div> </div>
{canReadLogs ? ( {canReadLogs ? (
<Panel title="最近请求" noPadding> <Panel title="最近请求" noPadding>
<RequestLogTable data={logs.data.list.slice(0, 8)} loading={logs.loading} /> <RequestLogTable data={logs.data.list.slice(0, 8)} loading={logs.loading} />
</Panel> </Panel>
) : null} ) : null}
{!hasAnyDataModule ? <NoAccessPage /> : null} {!hasAnyDataModule ? <NoAccessPage /> : null}
</div> </div>
); );
} }
export default DashboardPage; export default DashboardPage;

View File

@@ -38,11 +38,7 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
const loadDetail = async () => { const loadDetail = async () => {
setLoading(true); setLoading(true);
try { try {
const [dramaData, episodeList, channelPage] = await Promise.all([ const [dramaData, episodeList, channelPage] = await Promise.all([dramaApi.getDrama(dramaId), dramaApi.getDramaEpisodes(dramaId), channelApi.listChannels({ page: 1, pageSize: 100 }).catch(() => ({ list: [] }))]);
dramaApi.getDrama(dramaId),
dramaApi.getDramaEpisodes(dramaId),
channelApi.listChannels({ page: 1, pageSize: 100 }).catch(() => ({ list: [] })),
]);
setDrama(dramaData); setDrama(dramaData);
setEpisodeCount(dramaData.totalEpisodes || 1); setEpisodeCount(dramaData.totalEpisodes || 1);
setEpisodes(episodeList); setEpisodes(episodeList);
@@ -115,70 +111,75 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
return ( return (
<div className="page-stack"> <div className="page-stack">
<Panel {/* 导航栏 */}
title={ <nav className="w-full box-border bg-[#fff] p-4">
<Space> <div className="w-full flex justify-between items-center font-bold text-[1rem]">
<Space size={12}>
<Button icon={<IconArrowLeft />} onClick={() => navigate("/dramas")} /> <Button icon={<IconArrowLeft />} onClick={() => navigate("/dramas")} />
<span></span>
</Space> </Space>
} <Button icon={<IconRefresh />} onClick={loadDetail}>
extra={
<Space> </Button>
<Button icon={<IconRefresh />} onClick={loadDetail}> </div>
</nav>
</Button>
<Button disabled={!drama} onClick={() => setEditVisible(true)}>
</Button>
<Button onClick={() => updateDramaStatus("draft")}>稿</Button>
<Button type="primary" onClick={() => updateDramaStatus("published")}>
</Button>
</Space>
}
>
{loading && !drama ? (
<div className="py-12 text-center text-muted">...</div>
) : drama ? (
<Descriptions
column={3}
data={[
{ label: "短剧ID", value: drama.id },
{ label: "名称", value: drama.title },
{ label: "频道", value: drama.channelName || "-" },
{ label: "地区", value: drama.region || "-" },
{ label: "类型", value: drama.type },
{ label: "总集数", value: drama.totalEpisodes || 0 },
{ label: "已发布集数", value: drama.publishedEpisodes || 0 },
{ label: "状态", value: PUBLISH_STATUS_LABEL[drama.status] || drama.status },
{ label: "封面", value: drama.coverUrl },
{ label: "简介", value: drama.description || "-" },
]}
/>
) : null}
</Panel>
<Panel {/* 内容区域 */}
title="剧集配置" <div className="grid grid-cols-1 gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
extra={ <Panel>
<Space> {loading && !drama ? (
<InputNumber min={1} max={500} value={episodeCount} onChange={(value) => setEpisodeCount(Number(value) || 1)} /> <div className="py-12 text-center text-muted">...</div>
<Button type="primary" onClick={configureEpisodes}> ) : drama ? (
<div className="flex flex-col gap-4">
</Button> <Space wrap>
</Space> <Button disabled={!drama} onClick={() => setEditVisible(true)}>
}
noPadding </Button>
> <Button onClick={() => updateDramaStatus("draft")}>稿</Button>
<EpisodeTable <Button type="primary" onClick={() => updateDramaStatus("published")}>
episodes={episodes}
loading={loading} </Button>
uploadingEpisodeId={uploadingEpisodeId} </Space>
onUploadVideo={uploadEpisodeVideo}
onPublishEpisode={publishEpisode} <div>
onUpdateEpisode={updateEpisode} <div>
/> <img src={drama.coverUrl} alt={drama.title} className="w-full h-[13rem] object-cover" />
</Panel> </div>
</div>
<Descriptions
column={1}
data={[
{ label: "短剧ID", value: drama.id },
{ label: "名称", value: drama.title },
{ label: "频道", value: drama.channelName || "-" },
{ label: "地区", value: drama.region || "-" },
{ label: "类型", value: drama.type || "-" },
{ label: "总集数", value: drama.totalEpisodes || 0 },
{ label: "已发布集数", value: drama.publishedEpisodes || 0 },
{ label: "状态", value: PUBLISH_STATUS_LABEL[drama.status] || drama.status },
{ label: "简介", value: drama.description || "-" },
]}
/>
</div>
) : null}
</Panel>
<Panel
title="剧集配置"
extra={
<Space>
<InputNumber min={1} max={500} value={episodeCount} onChange={(value) => setEpisodeCount(Number(value) || 1)} />
<Button type="primary" onClick={configureEpisodes}>
</Button>
</Space>
}
noPadding
>
<EpisodeTable episodes={episodes} loading={loading} uploadingEpisodeId={uploadingEpisodeId} onUploadVideo={uploadEpisodeVideo} onPublishEpisode={publishEpisode} onUpdateEpisode={updateEpisode} />
</Panel>
</div>
{drama && ( {drama && (
<Modal title="编辑短剧" visible={editVisible} onCancel={() => setEditVisible(false)} footer={null} style={{ width: "45rem" }}> <Modal title="编辑短剧" visible={editVisible} onCancel={() => setEditVisible(false)} footer={null} style={{ width: "45rem" }}>

View File

@@ -17,7 +17,7 @@ export const LoginPanel = ({ loading, onSubmit }: LoginPanelProps) => {
<Form.Item label="密码" field="password" rules={[{ required: true }]}> <Form.Item label="密码" field="password" rules={[{ required: true }]}>
<Input.Password prefix={<IconLock className="text-muted" />} placeholder="请输入密码" size="large" /> <Input.Password prefix={<IconLock className="text-muted" />} placeholder="请输入密码" size="large" />
</Form.Item> </Form.Item>
<Button type="primary" htmlType="submit" loading={loading} long size="large" className="!h-11 mt-6 !rounded-xl !text-[0.9375rem] !font-semibold !shadow-brand/30"> <Button type="primary" htmlType="submit" loading={loading} long size="large" className="!h-11 mt-6 !text-[0.9375rem] !font-semibold !shadow-brand/30">
</Button> </Button>
</Form> </Form>

View File

@@ -2,7 +2,7 @@ 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-9 w-9 place-items-center rounded-xl bg-white/20 text-sm font-bold backdrop-blur-sm">CT</span> <span className="grid h-9 w-9 place-items-center bg-white/20 text-sm font-bold backdrop-blur-sm">CT</span>
<span className="text-white/90 text-[0.9375rem]">cth-tk-admin</span> <span className="text-white/90 text-[0.9375rem]">cth-tk-admin</span>
</div> </div>
<div className="relative z-[1] max-w-[40rem]"> <div className="relative z-[1] max-w-[40rem]">
@@ -11,15 +11,15 @@ export function LoginVisual() {
<p className="m-0 max-w-[35rem] text-base leading-[1.8] text-white/70"></p> <p className="m-0 max-w-[35rem] text-base leading-[1.8] text-white/70"></p>
</div> </div>
<div className="relative z-[1] grid max-w-[40rem] grid-cols-3 gap-4"> <div className="relative z-[1] grid max-w-[40rem] grid-cols-3 gap-4">
<div className="rounded-xl border border-white/15 bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15 hover:border-white/25"> <div className="border bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15 hover:border-white/25">
<strong className="mb-2 block text-xl font-semibold"></strong> <strong className="mb-2 block text-xl font-semibold"></strong>
<span className="text-[0.8125rem] text-white/65"></span> <span className="text-[0.8125rem] text-white/65"></span>
</div> </div>
<div className="rounded-xl border border-white/15 bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15 hover:border-white/25"> <div className="border bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15 hover:border-white/25">
<strong className="mb-2 block text-xl font-semibold"></strong> <strong className="mb-2 block text-xl font-semibold"></strong>
<span className="text-[0.8125rem] text-white/65"></span> <span className="text-[0.8125rem] text-white/65"></span>
</div> </div>
<div className="rounded-xl border border-white/15 bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15 hover:border-white/25"> <div className="border bg-white/10 p-5 backdrop-blur-md transition-all duration-300 hover:bg-white/15 hover:border-white/25">
<strong className="mb-2 block text-xl font-semibold"></strong> <strong className="mb-2 block text-xl font-semibold"></strong>
<span className="text-[0.8125rem] text-white/65"></span> <span className="text-[0.8125rem] text-white/65"></span>
</div> </div>

View File

@@ -13,7 +13,7 @@ export function ProfileInfoList({ profile }: ProfileInfoListProps) {
function ProfileInfoItem({ label, value }: ProfileInfoItemProps) { function ProfileInfoItem({ label, value }: ProfileInfoItemProps) {
return ( return (
<div className="rounded-xl border border-line/50 bg-canvas/50 p-4 transition-all duration-200 hover:border-line hover:bg-canvas"> <div className="border border-line/50 bg-canvas/50 p-4 transition-all duration-200 hover:border-line hover:bg-canvas">
<span className="mb-2 block text-[0.75rem] font-medium uppercase tracking-wider text-muted">{label}</span> <span className="mb-2 block text-[0.75rem] font-medium uppercase tracking-wider text-muted">{label}</span>
<strong className="break-words text-[0.9375rem] font-semibold text-ink">{value}</strong> <strong className="break-words text-[0.9375rem] font-semibold text-ink">{value}</strong>
</div> </div>

View File

@@ -118,7 +118,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-[0.9375rem] h-[22.5rem] overflow-auto rounded-xl border border-line/60 bg-canvas/30 px-4 py-3"> <div className="mb-[0.9375rem] h-[22.5rem] overflow-auto border border-line/60 bg-canvas/30 px-4 py-3">
<Tree <Tree
blockNode blockNode
checkable checkable

View File

@@ -26,7 +26,7 @@
@layer components { @layer components {
.section-card { .section-card {
@apply rounded-xl border border-line/60 bg-white shadow-sm transition-shadow duration-300; @apply border border-line/60 bg-white shadow-sm transition-shadow duration-300;
} }
.section-card:hover { .section-card:hover {
@@ -50,7 +50,7 @@
} }
.stat-card { .stat-card {
@apply rounded-xl border border-line/60 bg-white p-5 shadow-sm transition-shadow duration-300 hover:shadow-card-hover cursor-default; @apply border border-line/60 bg-white p-5 shadow-sm transition-shadow duration-300 hover:shadow-card-hover cursor-default;
} }
} }
@@ -112,6 +112,7 @@
/* ── Table ──────────────────────────────────────────── */ /* ── Table ──────────────────────────────────────────── */
.arco-table-container { .arco-table-container {
overflow: hidden; overflow: hidden;
margin: 0 0.5rem 0.5rem !important;
} }
.arco-table-th { .arco-table-th {
@@ -137,8 +138,11 @@
} }
/* ── Pagination ─────────────────────────────────────── */ /* ── Pagination ─────────────────────────────────────── */
.arco-table-pagination{
margin: 1rem !important;
}
.arco-pagination { .arco-pagination {
margin-top: 1rem !important;
} }
.arco-pagination-item { .arco-pagination-item {
@@ -262,17 +266,6 @@
letter-spacing: 0.01em; letter-spacing: 0.01em;
} }
/* ── Descriptions ───────────────────────────────────── */
.arco-descriptions-item-label {
font-weight: 500 !important;
color: theme('colors.muted') !important;
background-color: theme('colors.header-bg') !important;
}
.arco-descriptions-item-value {
color: theme('colors.ink') !important;
}
/* ── Dropdown ───────────────────────────────────────── */ /* ── Dropdown ───────────────────────────────────────── */
.arco-dropdown-menu { .arco-dropdown-menu {
padding: 0.25rem !important; padding: 0.25rem !important;

View File

@@ -58,7 +58,7 @@ export function AdminLayout() {
trigger={null} trigger={null}
> >
<div className="flex h-[3.75rem] items-center gap-2.5 px-5"> <div className="flex h-[3.75rem] items-center gap-2.5 px-5">
<span className="grid h-9 w-9 place-items-center rounded-xl bg-brand text-sm font-bold text-white shadow-md shadow-brand/25">TK</span> <span className="grid h-9 w-9 place-items-center bg-brand text-sm font-bold text-white shadow-md shadow-brand/25">TK</span>
{!collapsed && <span className="text-[0.9375rem] font-bold tracking-tight text-ink">TK短剧管理后台</span>} {!collapsed && <span className="text-[0.9375rem] font-bold tracking-tight text-ink">TK短剧管理后台</span>}
</div> </div>
<div className="mx-3 mb-2 h-px bg-line/50" /> <div className="mx-3 mb-2 h-px bg-line/50" />
@@ -89,7 +89,7 @@ export function AdminLayout() {
position="br" position="br"
trigger="click" trigger="click"
droplist={ droplist={
<Menu className="!rounded-xl !border-line/40 !shadow-card-lg !p-1.5"> <Menu className="!p-1.5">
<Menu.Item key="profile" onClick={() => navigate("/profile")} className="!rounded-lg !h-9"> <Menu.Item key="profile" onClick={() => navigate("/profile")} className="!rounded-lg !h-9">
</Menu.Item> </Menu.Item>
@@ -99,7 +99,7 @@ export function AdminLayout() {
</Menu> </Menu>
} }
> >
<button className="inline-flex cursor-pointer items-center gap-2.5 rounded-xl border border-transparent bg-transparent px-3 py-2 text-ink transition-all duration-200 hover:bg-canvas/80" type="button"> <button className="inline-flex border-0 cursor-pointer items-center gap-2.5 bg-transparent text-ink transition-all duration-200 hover:bg-canvas/80" type="button">
<Avatar size={32} className="!bg-brand !text-white !text-[13px] !font-semibold !shadow-sm !shadow-brand/20">{profile?.username?.slice(0, 1).toUpperCase() || "A"}</Avatar> <Avatar size={32} className="!bg-brand !text-white !text-[13px] !font-semibold !shadow-sm !shadow-brand/20">{profile?.username?.slice(0, 1).toUpperCase() || "A"}</Avatar>
<Text className="!text-[0.8125rem] !font-medium">{profile?.nickname || profile?.username || "管理员"}</Text> <Text className="!text-[0.8125rem] !font-medium">{profile?.nickname || profile?.username || "管理员"}</Text>
</button> </button>