style: 移除多余的rounded-xl样式并整理代码格式
1. 移除多个组件冗余的rounded-xl类名,统一圆角样式配置 2. 调整Dashboard页面代码缩进与引号风格为统一的双引号格式 3. 简化DramaDetail页面布局结构,重构详情展示区域 4. 清理index.css中多余的descriptions自定义样式 5. 更新gitignore忽略.trae和.mimocode目录
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,6 +14,8 @@ dist-ssr
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
.trae/*
|
||||
.mimocode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
|
||||
@@ -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
|
||||
|
||||
## 实施顺序
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" }}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user