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
.vscode/*
.trae/*
.mimocode/*
!.vscode/extensions.json
.idea
.DS_Store

View File

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

View File

@@ -1,11 +1,11 @@
import { IconPlayCircle, IconUser, IconFile, IconCheckCircle } from '@arco-design/web-react/icon';
import { dramaApi, logApi, userApi } from '../api';
import { usePageData } from '../hooks/usePageData';
import { hasPermissions } from '../utils/permission';
import { NoAccessPage } from '../components/NoAccessPage';
import { Panel } from '../components/Panel';
import { RequestLogTable } from '../components/RequestLogTable';
import type { AppUser, DashboardPageProps, Drama, MetricCardProps, RequestLog } from './interface';
import { IconPlayCircle, IconUser, IconFile, IconCheckCircle } from "@arco-design/web-react/icon";
import { dramaApi, logApi, userApi } from "../api";
import { usePageData } from "../hooks/usePageData";
import { hasPermissions } from "../utils/permission";
import { NoAccessPage } from "../components/NoAccessPage";
import { Panel } from "../components/Panel";
import { RequestLogTable } from "../components/RequestLogTable";
import type { AppUser, DashboardPageProps, Drama, MetricCardProps, RequestLog } from "./interface";
const METRIC_ICONS: Record<string, React.ReactNode> = {
dramas: <IconPlayCircle className="text-2xl text-brand" />,
@@ -15,30 +15,28 @@ const METRIC_ICONS: Record<string, React.ReactNode> = {
};
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',
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 animate-slide-up bg-gradient-to-br ${METRIC_COLORS[icon]}`}>
<div className="mb-3.5 flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-white/80 shadow-sm ring-1 ring-black/[0.03]">
{METRIC_ICONS[icon]}
<div className="mb-3.5 flex items-center gap-2">
<div className="flex h-10 w-10 items-center justify-center">{METRIC_ICONS[icon]}</div>
<span className="text-[1.1rem] font-medium text-muted">{label}</span>
</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 className="text-[1.6rem] font-bold tracking-tight text-ink">{value}</div>
</div>
);
}
function DashboardPage({ profile }: DashboardPageProps) {
const canReadDramas = hasPermissions(profile, ['drama:read']);
const canReadUsers = hasPermissions(profile, ['user:read']);
const canReadLogs = hasPermissions(profile, ['log:read']);
const canReadDramas = hasPermissions(profile, ["drama:read"]);
const canReadUsers = hasPermissions(profile, ["user:read"]);
const canReadLogs = hasPermissions(profile, ["log:read"]);
const dramas = usePageData<Drama>(dramaApi.listDramas, { page: 1, pageSize: 20 }, canReadDramas);
const users = usePageData<AppUser>(userApi.listUsers, { page: 1, pageSize: 20 }, canReadUsers);
const logs = usePageData<RequestLog>(logApi.listRequestLogs, { page: 1, pageSize: 20 }, canReadLogs);

View File

@@ -38,11 +38,7 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
const loadDetail = async () => {
setLoading(true);
try {
const [dramaData, episodeList, channelPage] = await Promise.all([
dramaApi.getDrama(dramaId),
dramaApi.getDramaEpisodes(dramaId),
channelApi.listChannels({ page: 1, pageSize: 100 }).catch(() => ({ list: [] })),
]);
const [dramaData, episodeList, channelPage] = await Promise.all([dramaApi.getDrama(dramaId), dramaApi.getDramaEpisodes(dramaId), channelApi.listChannels({ page: 1, pageSize: 100 }).catch(() => ({ list: [] }))]);
setDrama(dramaData);
setEpisodeCount(dramaData.totalEpisodes || 1);
setEpisodes(episodeList);
@@ -115,18 +111,27 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
return (
<div className="page-stack">
<Panel
title={
<Space>
{/* 导航栏 */}
<nav className="w-full box-border bg-[#fff] p-4">
<div className="w-full flex justify-between items-center font-bold text-[1rem]">
<Space size={12}>
<Button icon={<IconArrowLeft />} onClick={() => navigate("/dramas")} />
<span></span>
</Space>
}
extra={
<Space>
<Button icon={<IconRefresh />} onClick={loadDetail}>
</Button>
</div>
</nav>
{/* 内容区域 */}
<div className="grid grid-cols-1 gap-5 xl:grid-cols-[360px_minmax(0,1fr)]">
<Panel>
{loading && !drama ? (
<div className="py-12 text-center text-muted">...</div>
) : drama ? (
<div className="flex flex-col gap-4">
<Space wrap>
<Button disabled={!drama} onClick={() => setEditVisible(true)}>
</Button>
@@ -135,26 +140,28 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
</Button>
</Space>
}
>
{loading && !drama ? (
<div className="py-12 text-center text-muted">...</div>
) : drama ? (
<div>
<div>
<img src={drama.coverUrl} alt={drama.title} className="w-full h-[13rem] object-cover" />
</div>
</div>
<Descriptions
column={3}
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.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 || "-" },
]}
/>
</div>
) : null}
</Panel>
@@ -170,15 +177,9 @@ export function DramaDetail({ dramaId }: DramaDetailPageProps) {
}
noPadding
>
<EpisodeTable
episodes={episodes}
loading={loading}
uploadingEpisodeId={uploadingEpisodeId}
onUploadVideo={uploadEpisodeVideo}
onPublishEpisode={publishEpisode}
onUpdateEpisode={updateEpisode}
/>
<EpisodeTable episodes={episodes} loading={loading} uploadingEpisodeId={uploadingEpisodeId} onUploadVideo={uploadEpisodeVideo} onPublishEpisode={publishEpisode} onUpdateEpisode={updateEpisode} />
</Panel>
</div>
{drama && (
<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 }]}>
<Input.Password prefix={<IconLock className="text-muted" />} placeholder="请输入密码" size="large" />
</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>
</Form>

View File

@@ -2,7 +2,7 @@ export function LoginVisual() {
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 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>
</div>
<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>
</div>
<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>
<span className="text-[0.8125rem] text-white/65"></span>
</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>
<span className="text-[0.8125rem] text-white/65"></span>
</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>
<span className="text-[0.8125rem] text-white/65"></span>
</div>

View File

@@ -13,7 +13,7 @@ export function ProfileInfoList({ profile }: ProfileInfoListProps) {
function ProfileInfoItem({ label, value }: ProfileInfoItemProps) {
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>
<strong className="break-words text-[0.9375rem] font-semibold text-ink">{value}</strong>
</div>

View File

@@ -118,7 +118,7 @@ export function PermissionTreePicker({ permissions, value, onChange }: Permissio
const checkedKeys = value.map((id) => permissionKey(id));
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
blockNode
checkable

View File

@@ -26,7 +26,7 @@
@layer components {
.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 {
@@ -50,7 +50,7 @@
}
.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 ──────────────────────────────────────────── */
.arco-table-container {
overflow: hidden;
margin: 0 0.5rem 0.5rem !important;
}
.arco-table-th {
@@ -137,8 +138,11 @@
}
/* ── Pagination ─────────────────────────────────────── */
.arco-table-pagination{
margin: 1rem !important;
}
.arco-pagination {
margin-top: 1rem !important;
}
.arco-pagination-item {
@@ -262,17 +266,6 @@
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 ───────────────────────────────────────── */
.arco-dropdown-menu {
padding: 0.25rem !important;

View File

@@ -58,7 +58,7 @@ export function AdminLayout() {
trigger={null}
>
<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>}
</div>
<div className="mx-3 mb-2 h-px bg-line/50" />
@@ -89,7 +89,7 @@ export function AdminLayout() {
position="br"
trigger="click"
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>
@@ -99,7 +99,7 @@ export function AdminLayout() {
</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>
<Text className="!text-[0.8125rem] !font-medium">{profile?.nickname || profile?.username || "管理员"}</Text>
</button>