diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ab99453..9d5dea4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -80,8 +80,32 @@ export default function App() { locale={zhCN} theme={{ token: { - colorPrimary: '#1677ff', - borderRadius: 6, + colorPrimary: '#2563eb', + colorInfo: '#2563eb', + colorSuccess: '#16a34a', + colorWarning: '#d97706', + colorError: '#dc2626', + borderRadius: 8, + fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif`, + }, + components: { + Card: { + paddingLG: 20, + borderRadiusLG: 10, + }, + Table: { + headerBg: '#f8fafc', + headerColor: '#475569', + headerSplitColor: 'transparent', + rowHoverBg: '#f1f5f9', + }, + Button: { + fontWeight: 600, + borderRadius: 6, + }, + Tag: { + borderRadiusSM: 4, + }, }, }} > diff --git a/frontend/src/components/PageHeader.tsx b/frontend/src/components/PageHeader.tsx new file mode 100644 index 0000000..c21c88e --- /dev/null +++ b/frontend/src/components/PageHeader.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import { Breadcrumb, Typography, Space } from 'antd' +import { HomeOutlined } from '@ant-design/icons' +import { Link } from 'react-router-dom' + +export interface BreadcrumbItem { + title: string + path?: string +} + +interface PageHeaderProps { + title: string + subtitle?: string + breadcrumbs?: BreadcrumbItem[] + extra?: React.ReactNode +} + +export const PageHeader: React.FC = ({ + title, + subtitle, + breadcrumbs = [], + extra, +}) => { + const items = [ + { + title: ( + + + 首页 + + ), + }, + ...breadcrumbs.map((item) => ({ + title: item.path ? {item.title} : item.title, + })), + ] + + return ( +
+
+ {breadcrumbs.length > 0 && ( + + )} +
+
+ + {title} + + {subtitle && ( + + {subtitle} + + )} +
+ {extra && {extra}} +
+
+
+ ) +} + +export default PageHeader diff --git a/frontend/src/index.css b/frontend/src/index.css index 1d2ed23..fd1931b 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,3 +1,23 @@ +/* ============ 设计变量 & 全局重置 ============ */ +:root { + --primary-color: #2563eb; + --primary-hover: #1d4ed8; + --primary-light: #eff6ff; + --primary-border: #bfdbfe; + --bg-app: #f8fafc; + --bg-card: #ffffff; + --text-main: #0f172a; + --text-muted: #64748b; + --border-color: #e2e8f0; + --border-light: #f1f5f9; + --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02); + --shadow-md: 0 4px 12px -2px rgba(15, 23, 42, 0.05), 0 2px 6px -1px rgba(15, 23, 42, 0.03); + --shadow-lg: 0 10px 25px -5px rgba(15, 23, 42, 0.08), 0 8px 10px -6px rgba(15, 23, 42, 0.04); + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; +} + * { box-sizing: border-box; } @@ -10,44 +30,132 @@ body, min-height: 100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; + background-color: var(--bg-app); + color: var(--text-main); + -webkit-font-smoothing: antialiased; } a { - color: #1677ff; + color: var(--primary-color); + text-decoration: none; + transition: color 0.15s ease; +} + +a:hover { + color: var(--primary-hover); +} + +/* 优化全局滚动条 */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 999px; +} +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; } /* ============ 后台主布局与侧边栏 ============ */ .app-shell { min-height: 100vh; + background: var(--bg-app); } .app-main { min-width: 0; - background: #f5f8fc; + background: var(--bg-app); + display: flex; + flex-direction: column; } .app-header { - height: 50px !important; + height: 60px !important; padding: 0 24px !important; - line-height: 50px !important; - background: #fff !important; - border-bottom: 1px solid #edf1f7; + line-height: 60px !important; + background: rgba(255, 255, 255, 0.95) !important; + backdrop-filter: blur(8px); + border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; + position: sticky; + top: 0; + z-index: 99; +} + +.app-header__left { + display: flex; + align-items: center; + gap: 16px; +} + +.app-header__right { + display: flex; + align-items: center; + gap: 16px; +} + +.app-header__collapse { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 1px solid var(--border-color); + border-radius: var(--radius-sm); + background: #ffffff; + color: var(--text-muted); + cursor: pointer; + transition: all 0.2s ease; +} + +.app-header__collapse:hover { + border-color: var(--primary-border); + background: var(--primary-light); + color: var(--primary-color); +} + +.app-header__user { + display: flex; + align-items: center; + gap: 10px; + padding: 4px 10px; + border-radius: 20px; + border: 1px solid transparent; + cursor: pointer; + transition: all 0.2s ease; +} + +.app-header__user:hover { + background: #f1f5f9; + border-color: #e2e8f0; +} + +.app-header__user-avatar { + background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%) !important; + color: #fff !important; + font-weight: 600; } .app-content { min-width: 0; - margin: 16px; + margin: 20px 24px 24px; + flex: 1; } .app-content__body { - min-height: calc(100vh - 50px - 32px); - padding: 20px; - background: #fff; - border: 1px solid #edf1f7; - border-radius: 8px; + min-height: calc(100vh - 60px - 44px); + padding: 24px; + background: #ffffff; + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); } .app-main--flush .app-content { @@ -55,71 +163,57 @@ a { } .app-main--flush .app-content__body { - height: calc(100vh - 50px); + height: calc(100vh - 60px); min-height: 0; padding: 0; border: 0; border-radius: 0; - background: #fff; + background: #ffffff; overflow: hidden; } +/* 侧边栏样式(浅色固定导航栏) */ .app-sidebar { - position: relative; - background: #f4f8fc !important; - border-right: 1px solid #e3eaf2; - box-shadow: inset -1px 0 0 rgba(225, 233, 242, 0.8); + position: sticky; + top: 0; + height: 100vh; + background: #ffffff !important; + border-right: 1px solid #e2e8f0; + box-shadow: 2px 0 8px rgba(15, 23, 42, 0.03); + z-index: 100; + flex-shrink: 0; } .app-sidebar .ant-layout-sider-children { display: flex; flex-direction: column; - min-height: 100%; -} - -.app-header__collapse { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 26px; - width: 26px; - height: 26px; - border: 1px solid #dce7f3; - border-radius: 7px; - background: rgba(255, 255, 255, 0.74); - color: #7d8a99; - cursor: pointer; - transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease; -} - -.app-header__collapse:hover { - border-color: #b8d7ff; - background: #fff; - color: #1677ff; + height: 100%; } .app-sidebar__brand { display: flex; align-items: center; - gap: 10px; - height: 50px; - padding: 0 18px; - color: #1f2329; - font-size: 17px; - font-weight: 800; + gap: 12px; + height: 60px; + padding: 0 20px; + color: #0f172a; + font-size: 18px; + font-weight: 700; + letter-spacing: 0.3px; + border-bottom: 1px solid #f1f5f9; } .app-sidebar__brand-mark { display: inline-flex; align-items: center; justify-content: center; - flex: 0 0 28px; - width: 28px; - height: 28px; - border-radius: 8px; - background: linear-gradient(135deg, #1677ff 0%, #39c5ff 100%); - color: #fff; - font-size: 15px; + width: 32px; + height: 32px; + border-radius: var(--radius-sm); + background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); + box-shadow: 0 4px 10px rgba(59, 130, 246, 0.3); + color: #ffffff; + font-size: 17px; font-weight: 800; } @@ -129,17 +223,19 @@ a { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + color: #0f172a; + font-weight: 700; } .app-sidebar__nav { flex: 1; min-height: 0; - padding: 8px 14px 24px; + padding: 16px 12px 24px; overflow-y: auto; } .app-sidebar__group { - margin-bottom: 13px; + margin-bottom: 6px; } .app-sidebar__item, @@ -147,44 +243,65 @@ a { width: 100%; border: 0; background: transparent; - color: #555f6d; + color: #475569; cursor: pointer; font-family: inherit; text-align: left; - transition: background 0.16s ease, color 0.16s ease; + transition: all 0.2s ease; + position: relative; } .app-sidebar__item { display: flex; align-items: center; - min-height: 42px; - padding: 8px 12px; - border-radius: 6px; - font-size: 16px; - font-weight: 650; + min-height: 44px; + padding: 10px 14px; + border-radius: var(--radius-md); + font-size: 14px; + font-weight: 600; } .app-sidebar__item:hover { - color: #1677ff; - background: #edf5ff; + color: #2563eb; + background: #eff6ff; } .app-sidebar__item--active { - color: #1677ff; + color: #2563eb !important; + background: #eff6ff !important; + font-weight: 700; +} + +.app-sidebar__item--active::before { + content: ''; + position: absolute; + left: 0; + top: 8px; + bottom: 8px; + width: 3.5px; + border-radius: 0 4px 4px 0; + background: #2563eb; } .app-sidebar__item--disabled { - color: #9aa4b2; - cursor: default; + color: #94a3b8; + cursor: not-allowed; } .app-sidebar__icon { display: inline-flex; align-items: center; justify-content: center; - flex: 0 0 26px; - margin-right: 10px; - font-size: 18px; + width: 22px; + margin-right: 12px; + font-size: 17px; + transition: transform 0.2s ease; +} + +.app-sidebar__item:hover .app-sidebar__icon, +.app-sidebar__item--active .app-sidebar__icon { + transform: scale(1.08); + color: #2563eb; } .app-sidebar__label { @@ -197,50 +314,42 @@ a { .app-sidebar__arrow { margin-left: 8px; - color: currentColor; - font-size: 12px; + color: #94a3b8; + font-size: 11px; transform: rotate(0deg); - transition: transform 0.16s ease; + transition: transform 0.2s ease; } .app-sidebar__arrow--open { transform: rotate(180deg); + color: #2563eb; } .app-sidebar__children { display: flex; flex-direction: column; - gap: 5px; - margin-top: 6px; - padding-left: 44px; + gap: 4px; + margin-top: 4px; + padding-left: 36px; } .app-sidebar__child { min-height: 38px; - padding: 8px 14px; - border-radius: 6px; - font-size: 15px; - font-weight: 600; + padding: 8px 12px; + border-radius: var(--radius-sm); + font-size: 13.5px; + font-weight: 500; } .app-sidebar__child:hover:not(:disabled) { - color: #1677ff; - background: #eef6ff; + color: #2563eb; + background: #eff6ff; } .app-sidebar__child--active { - color: #1677ff; - background: #dcecff; -} - -.app-sidebar__child--disabled { - color: #5f6875; - cursor: default; -} - -.app-sidebar__child:disabled { - cursor: default; - opacity: 1; + color: #1d4ed8 !important; + background: #dbeafe !important; + font-weight: 600; } .app-sidebar--collapsed .app-sidebar__brand { @@ -249,163 +358,508 @@ a { } .app-sidebar--collapsed .app-sidebar__nav { - padding: 8px 10px 24px; -} - -.app-sidebar--collapsed .app-sidebar__group { - margin-bottom: 8px; + padding: 16px 8px 24px; } .app-sidebar--collapsed .app-sidebar__item { justify-content: center; - min-height: 44px; - padding: 8px 0; + padding: 10px 0; } .app-sidebar--collapsed .app-sidebar__icon { margin-right: 0; } -/* ============ 开放 API 对接文档 ============ */ +/* ============ 通用 PageHeader 组件样式 ============ */ +.page-header-wrapper { + margin-bottom: 20px; + padding-bottom: 16px; + border-bottom: 1px solid var(--border-light); +} + +.page-header-breadcrumb { + margin-bottom: 8px !important; + font-size: 13px !important; +} + +.page-header-heading { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 16px; +} + +.page-header-extra { + display: flex; + align-items: center; + gap: 12px; +} + +/* ============ 现代 Dashboard 样式 ============ */ +.dash-welcome-card { + background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%); + border: 1px solid #bfdbfe; + border-radius: var(--radius-lg); + padding: 24px 28px; + color: #0f172a; + margin-bottom: 24px; + position: relative; + overflow: hidden; + box-shadow: 0 4px 20px -2px rgba(37, 99, 235, 0.08); +} + +.dash-welcome-card::after { + content: ''; + position: absolute; + top: -40px; + right: -40px; + width: 260px; + height: 260px; + background: radial-gradient(circle, rgba(37, 99, 235, 0.15) 0%, rgba(37, 99, 235, 0) 70%); + border-radius: 50%; + pointer-events: none; +} + +.dash-welcome-title { + font-size: 22px; + font-weight: 700; + color: #0f172a !important; + margin: 0 0 6px 0 !important; +} + +.dash-welcome-sub { + font-size: 14px; + color: #475569; + max-width: 600px; + margin: 0; +} + +.dash-quick-launch { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-top: 18px; +} + +.dash-quick-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + border-radius: var(--radius-md); + background: #ffffff; + border: 1px solid #cbd5e1; + color: #1e293b; + font-size: 13px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); + transition: all 0.2s ease; +} + +.dash-quick-btn:hover { + background: #2563eb; + border-color: #2563eb; + color: #ffffff; + transform: translateY(-1px); + box-shadow: 0 4px 14px rgba(37, 99, 235, 0.3); +} + +/* 高质感 Metric Card */ +.metric-card-box { + background: #ffffff; + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + padding: 20px; + transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + box-shadow: var(--shadow-sm); +} + +.metric-card-box:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); + border-color: var(--primary-border); +} + +.metric-card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 12px; +} + +.metric-card-title { + font-size: 13.5px; + font-weight: 600; + color: var(--text-muted); +} + +.metric-card-icon { + width: 40px; + height: 40px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + background: var(--primary-light); + color: var(--primary-color); +} + +.metric-card-icon--emerald { + background: #ecfdf5; + color: #10b981; +} + +.metric-card-icon--purple { + background: #f3e8ff; + color: #a855f7; +} + +.metric-card-icon--amber { + background: #fffbeb; + color: #f59e0b; +} + +.metric-card-icon--rose { + background: #ffe4e6; + color: #f43f5e; +} + +.metric-card-value { + font-size: 26px; + font-weight: 700; + color: var(--text-main); + line-height: 1.2; +} + +.metric-card-footer { + margin-top: 10px; + padding-top: 8px; + border-top: 1px dashed var(--border-light); + display: flex; + align-items: center; + justify-content: space-between; + font-size: 12px; + color: var(--text-muted); +} + +/* 自定义 Status Tag */ +.status-tag { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + border-radius: 20px; + font-size: 12.5px; + font-weight: 600; +} + +.status-dot { + width: 6px; + height: 6px; + border-radius: 50%; +} + +.status-tag--blue { + background: #eff6ff; + color: #2563eb; + border: 1px solid #bfdbfe; +} +.status-tag--blue .status-dot { + background: #2563eb; +} + +.status-tag--cyan { + background: #ecfeff; + color: #0891b2; + border: 1px solid #a5f3fc; +} +.status-tag--cyan .status-dot { + background: #0891b2; +} + +.status-tag--green { + background: #f0fdf4; + color: #16a34a; + border: 1px solid #bbf7d0; +} +.status-tag--green .status-dot { + background: #16a34a; +} + +.status-tag--red { + background: #fef2f2; + color: #dc2626; + border: 1px solid #fecaca; +} +.status-tag--red .status-dot { + background: #dc2626; +} + +.status-tag--gray { + background: #f8fafc; + color: #64748b; + border: 1px solid #e2e8f0; +} +.status-tag--gray .status-dot { + background: #64748b; +} + +/* ============ 开发者工具与 API 调试器 CSS ============ */ +.debug-console { + background: #090d16; + border: 1px solid #1e293b; + border-radius: var(--radius-md); + overflow: hidden; + box-shadow: var(--shadow-lg); +} + +.debug-console-head { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 18px; + background: #0f172a; + border-bottom: 1px solid #1e293b; + color: #94a3b8; + font-size: 13px; + font-weight: 600; +} + +.debug-console-body { + padding: 16px; + color: #e2e8f0; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + font-size: 13px; + line-height: 1.6; + max-height: 520px; + overflow: auto; +} + +/* ============ 登录页面 Glassmorphism 样式 ============ */ +.login-wrapper { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: radial-gradient(circle at 15% 15%, #1e1b4b 0%, #0f172a 50%, #020617 100%); + position: relative; + overflow: hidden; +} + +.login-wrapper::before { + content: ''; + position: absolute; + width: 500px; + height: 500px; + background: radial-gradient(circle, rgba(59, 130, 246, 0.25) 0%, rgba(59, 130, 246, 0) 70%); + top: 10%; + left: 15%; + border-radius: 50%; + pointer-events: none; +} + +.login-wrapper::after { + content: ''; + position: absolute; + width: 450px; + height: 450px; + background: radial-gradient(circle, rgba(168, 85, 247, 0.2) 0%, rgba(168, 85, 247, 0) 70%); + bottom: 10%; + right: 15%; + border-radius: 50%; + pointer-events: none; +} + +.login-card { + width: 420px; + padding: 36px 32px; + background: rgba(15, 23, 42, 0.75) !important; + backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.12) !important; + border-radius: var(--radius-lg) !important; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5) !important; + color: #ffffff; +} + +.login-logo { + display: inline-flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 14px; + background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); + box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4); + color: #ffffff; + font-size: 24px; + font-weight: 800; + margin-bottom: 16px; +} + +/* ============ 开放 API 对接文档适配 ============ */ .api-docs { display: flex; align-items: stretch; width: 100%; height: 100%; min-height: 0; - background: #fff; - border-radius: 0; + background: #ffffff; overflow: hidden; } -/* 左侧目录 */ .api-docs__toc { - flex: 0 0 240px; - width: 240px; + flex: 0 0 230px; + width: 230px; height: 100%; overflow-y: auto; - border-right: 1px solid #e7edf5; - background: #f8fbff; - padding: 16px 12px 20px; + border-right: 1px solid #cbd5e1; + background: #f1f5f9; + padding: 16px 12px 24px; +} + +.api-docs__toc-header { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 6px 12px; + margin-bottom: 12px; + border-bottom: 1px solid #cbd5e1; + font-size: 13px; + font-weight: 700; + color: #0f172a; + letter-spacing: 0.3px; } .api-docs__nav { display: flex; flex-direction: column; - gap: 16px; + gap: 14px; } .api-docs__nav-group { display: flex; flex-direction: column; - gap: 2px; + gap: 4px; + padding-bottom: 12px; + border-bottom: 1px dashed #cbd5e1; } -.api-docs__nav-group + .api-docs__nav-group { - border-top: 1px solid #e7edf5; - padding-top: 12px; +.api-docs__nav-group:last-child { + border-bottom: 0; + padding-bottom: 0; } .api-docs__nav-heading { - padding: 0 10px 8px; - font-size: 14px; - font-weight: 700; - color: #1677ff; + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + margin-bottom: 4px; + font-size: 11.5px; + font-weight: 800; + color: #1e293b; + background: #e2e8f0; + border-radius: 4px; letter-spacing: 0.5px; } -.api-docs__nav-row { - min-width: 0; +.api-docs__nav-heading::before { + content: ''; + display: inline-block; + width: 5px; + height: 5px; + border-radius: 50%; + background: #2563eb; } -.api-docs__nav-item, -.api-docs__nav-sub-item { +.api-docs__nav-item { display: block; - min-height: 36px; - padding: 8px 12px; - font-size: 14px; - color: #3d4553; + min-height: 32px; + padding: 6px 10px 6px 12px; + font-size: 13px; + color: #475569; cursor: pointer; - border-radius: 5px; - transition: all 0.15s; + border-radius: 6px; + transition: all 0.15s ease; user-select: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.api-docs__nav-item:hover, -.api-docs__nav-sub-item:hover { - color: #1677ff; - background: #f2f7ff; +.api-docs__nav-item:hover { + color: #2563eb; + background: #e2e8f0; } .api-docs__nav-item--active { - color: #1677ff; - background: #edf5ff; - font-weight: 600; + color: #2563eb !important; + background: #ffffff !important; + font-weight: 700; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + border-left: 3.5px solid #2563eb; } .api-docs__nav-sub { display: flex; flex-direction: column; - margin: 2px 0 8px 12px; - padding-left: 12px; - border-left: 1px solid #e6edf6; + gap: 2px; + margin: 4px 0 6px 10px; + padding-left: 10px; + border-left: 2px solid #cbd5e1; } .api-docs__nav-sub-item { - min-height: 32px; - padding: 6px 10px; - font-size: 13.5px; - color: #7a8599; + display: block; + min-height: 28px; + padding: 4px 8px; + font-size: 12px; + color: #64748b; + cursor: pointer; + border-radius: 4px; + transition: all 0.15s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.api-docs__nav-sub-item:hover { + color: #2563eb; + background: #e2e8f0; } .api-docs__nav-sub-item--active { - color: #1677ff; - font-weight: 600; - background: #f4f8ff; + color: #1d4ed8 !important; + font-weight: 700; + background: #ffffff !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04); } -/* 右侧文档 */ .api-docs__content { flex: 1 1 auto; min-width: 0; height: 100%; overflow-y: auto; scroll-behavior: smooth; - background: #fff; + background: #ffffff; padding: 0; } .api-docs__inner { - width: min(1120px, calc(100% - 48px)); - max-width: 1120px; + max-width: 1160px; min-height: 100%; margin: 0 auto 0 0; - padding: 28px 0 64px 32px; - background: #fff; - border: 0; - border-radius: 0; - box-shadow: none; -} - -.api-docs__section { - padding-top: 0; - margin-top: 0; - border-top: none; -} - -.api-docs__section:first-child { - padding-top: 0; - margin-top: 0; - border-top: none; + padding: 32px 40px 64px; } .api-docs__h2 { - font-size: 20px; + font-size: 22px; font-weight: 700; - color: #1d2129; + color: #0f172a; margin: 0 0 12px; scroll-margin-top: 24px; } @@ -413,322 +867,215 @@ a { .api-docs__summary { max-width: 1080px; margin: 0 0 16px !important; - color: #4e5969 !important; - line-height: 1.8; + color: #475569 !important; + line-height: 1.7; } -.api-docs__endpoint-head { +.api-docs__table .ant-table-cell { + word-break: break-word !important; + white-space: normal !important; +} + +.api-docs__table .ant-table-thead > tr > th { + background: #f8fafc !important; + color: #475569 !important; + font-weight: 700 !important; + border-bottom: 1px solid #e2e8f0 !important; +} + +/* 方法与路径行样式(浅色风格) */ +.api-docs__endpoint-line { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + margin: 12px 0; +} + +.api-docs__method-badge { + padding: 3px 8px; + border-radius: 4px; + font-weight: 800; + font-size: 12px; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; +} + +.api-docs__method-badge--get { + background: #eff6ff; + color: #2563eb; + border: 1px solid #bfdbfe; +} + +.api-docs__method-badge--post { + background: #f0fdf4; + color: #16a34a; + border: 1px solid #bbf7d0; +} + +.api-docs__path-text { + color: #0f172a; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + font-size: 13.5px; + font-weight: 600; +} + +/* 浅色精致 JSON 代码块 */ +.doc-code-card { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); + margin: 12px 0; +} + +.doc-code-header { display: flex; align-items: center; justify-content: space-between; - gap: 16px; - margin-bottom: 4px; + padding: 8px 14px; + background: #f1f5f9; + border-bottom: 1px solid #e2e8f0; } -.api-docs__endpoint-meta { - display: flex; - align-items: center; - gap: 8px; - flex: 0 0 auto; +.doc-code-title { + font-size: 12px; + font-weight: 700; + color: #475569; + letter-spacing: 0.5px; + text-transform: uppercase; } -.api-docs__endpoint { - color: #1f2329; -} - -.api-docs__notice { - margin: 0 0 2px; - padding: 13px 16px; - border-left: 4px solid #1890ff; +.doc-code-copy { + border: 1px solid #cbd5e1; + background: #ffffff; + color: #334155; + font-size: 11.5px; + padding: 3px 10px; border-radius: 4px; - background: #f1f7ff; - color: #4e5969; - line-height: 1.75; + cursor: pointer; + font-weight: 500; + transition: all 0.15s ease; } -.api-docs__notice-label { - color: #1f2329; +.doc-code-copy:hover { + background: #2563eb; + border-color: #2563eb; + color: #ffffff; +} + +.api-docs__code { + margin: 0; + padding: 14px 18px; + background: #f8fafc !important; + color: #0f172a; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; + font-size: 13px; + line-height: 1.65; + overflow-x: auto; + white-space: pre; +} + +/* 浅色 JSON 语法高亮专配颜色 */ +.json-key { + color: #0284c7 !important; font-weight: 700; } -/* EndpointDoc 子小节标题 */ -.doc-section-title { - font-size: 16px; - font-weight: 700; - color: #1f3a6e; - margin: 22px 0 12px; - scroll-margin-top: 24px; - padding-left: 10px; - border-left: 3px solid #1677ff; +.json-string { + color: #059669 !important; } -.doc-subtitle { - font-size: 14px; +.json-number { + color: #d97706 !important; +} + +.json-boolean { + color: #7c3aed !important; font-weight: 600; - color: #3d4553; - margin: 10px 0 10px; } -.api-docs__param-group { - margin-bottom: 18px; +.json-null { + color: #64748b !important; +} + +.api-docs__pre { + margin: 0; + padding: 10px 12px; + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 6px; + font-size: 12px; + line-height: 1.6; + color: #0f172a; + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; } .api-docs__card { margin-top: 16px; - border-color: #eef1f5 !important; - border-radius: 6px !important; + border-color: #e2e8f0 !important; + border-radius: 8px !important; box-shadow: none !important; } .api-docs__card .ant-card-head { min-height: 42px; - border-bottom-color: #eef1f5; + border-bottom-color: #e2e8f0; + background: #f8fafc; } .api-docs__card .ant-card-head-title { font-size: 14px; font-weight: 700; - color: #1d2129; -} - -.api-docs__common { - margin-top: 18px; + color: #0f172a; } .api-docs__pager { display: grid; grid-template-columns: 1fr 1fr; - gap: 14px; - margin-top: 32px; - padding-top: 20px; - border-top: 1px solid #eef1f5; + gap: 16px; + margin-top: 40px; + padding-top: 24px; + border-top: 1px solid #e2e8f0; } .api-docs__pager-btn { - min-height: 70px; - padding: 12px 16px; - border: 1px solid #e7edf5; - border-radius: 6px; - background: #fbfdff; - color: #4e5969; + min-height: 64px; + padding: 12px 18px; + border: 1px solid #e2e8f0; + border-radius: 8px; + background: #ffffff; + color: #475569; text-align: left; cursor: pointer; - transition: all 0.16s ease; + transition: all 0.2s ease; } .api-docs__pager-btn:hover:not(:disabled) { - border-color: #91caff; - background: #f2f8ff; -} - -.api-docs__pager-btn:disabled { - cursor: not-allowed; - opacity: 0.45; -} - -.api-docs__pager-btn span { - display: block; - margin-bottom: 6px; - font-size: 12px; - color: #86909c; -} - -.api-docs__pager-btn strong { - display: block; - color: #1d2129; - font-size: 14px; -} - -.api-docs__pager-btn--next { - text-align: right; -} - -.api-docs__alert { - border-radius: 4px !important; -} - -.api-docs__table { - border-top: 1px solid #edf1f7; -} - -.api-docs__table .ant-table { - color: #1f2329; -} - -.api-docs__table .ant-table-thead > tr > th { - background: #fafcff !important; - color: #1d2129 !important; - font-weight: 700 !important; - border-bottom: 1px solid #edf1f7 !important; - border-right: 1px solid #f1f4f8 !important; -} - -.api-docs__table .ant-table-tbody > tr > td { - padding-top: 14px !important; - padding-bottom: 14px !important; - border-bottom: 1px solid #edf1f7 !important; - border-right: 1px solid #f3f6fa !important; -} - -.api-docs__table .ant-table-thead > tr > th:last-child, -.api-docs__table .ant-table-tbody > tr > td:last-child { - border-right: 0 !important; -} - -.api-docs__table .ant-table-tbody > tr:hover > td { - background: #fbfdff !important; + border-color: #93c5fd; + background: #eff6ff; + color: #2563eb; } .api-docs__field { - display: inline-block; - max-width: 100%; - overflow-wrap: anywhere; - color: #2454a6 !important; - background: transparent !important; - border: 0 !important; - padding: 0; + color: #2563eb; font-weight: 700; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; } -.api-docs__type { - color: #1f2329; - font-size: 13px; - font-weight: 500; -} - -.api-docs__inline-example { - font-size: 12px; - color: #2454a6 !important; - background: transparent !important; - border: 0 !important; - padding: 0; - font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; - font-weight: 600; -} - .api-docs__required { display: inline-flex; align-items: center; justify-content: center; - min-width: 20px; - height: 22px; - color: #cf1322; - background: #fff1f0; + padding: 2px 6px; + color: #dc2626; + background: #fef2f2; + border: 1px solid #fecaca; border-radius: 4px; - font-size: 13px; + font-size: 12px; font-weight: 700; } - -.api-docs__pre { - margin: 0; - padding: 12px 14px; - background: #f7f9fc; - border: 1px solid #e7edf5; - border-radius: 4px; - font-size: 12.5px; - line-height: 1.7; - overflow: auto; - white-space: pre-wrap; - word-break: break-all; - color: #1f2329; - font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; -} - -.api-docs__pre--plain { - background: transparent; - border: 0; - padding: 0; -} - -.api-docs__code, -.api-docs__endpoint-line { - margin: 0; - padding: 17px 18px; - background: #f7f9fc; - border: 1px solid #e7edf5; - border-radius: 4px; - color: #1f2329; - font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace; - font-size: 13px; - line-height: 1.75; - overflow: auto; - white-space: pre; -} - -.api-docs__endpoint-line { - min-height: 56px; - display: flex; - align-items: center; - gap: 8px; -} - -.api-docs__method { - font-weight: 800; -} - -.api-docs__path { - color: #202632; - font-weight: 700; -} - -/* JSON 语法高亮配色 */ -.json-key { - color: #2454a6; - font-weight: 600; -} - -.json-string { - color: #0f8a5f; -} - -.json-number { - color: #b86b00; -} - -.json-boolean { - color: #1677ff; -} - -.json-null { - color: #86909c; -} - -@media (max-width: 1024px) { - .api-docs { - flex-direction: column; - } - - .api-docs__toc { - position: relative; - flex: none; - width: 100%; - max-height: 320px; - border-right: 0; - border-bottom: 1px solid #e7edf5; - } - - .api-docs__content { - width: 100%; - height: auto; - min-height: calc(100vh - 50px - 320px); - } - - .api-docs__inner { - padding: 22px 18px 64px; - } - - .api-docs__endpoint-head { - align-items: flex-start; - flex-direction: column; - } - - .api-docs__pager { - grid-template-columns: 1fr; - } - - .api-docs__pager-btn--next { - text-align: left; - } -} diff --git a/frontend/src/layouts/MainLayout.tsx b/frontend/src/layouts/MainLayout.tsx index 9aa3bc1..5c52916 100644 --- a/frontend/src/layouts/MainLayout.tsx +++ b/frontend/src/layouts/MainLayout.tsx @@ -7,12 +7,17 @@ import { Space, Typography, Avatar, + Tag, + Button, + Tooltip, } from 'antd' import { ApiOutlined, AppstoreOutlined, + CodeOutlined, DashboardOutlined, DownOutlined, + FileTextOutlined, LogoutOutlined, MenuOutlined, OrderedListOutlined, @@ -53,13 +58,13 @@ const adminSections: SidebarSection[] = [ key: 'products', label: '商品管理', icon: , - children: [{ key: 'merchant-products', label: '商品', path: '/merchant-products' }], + children: [{ key: 'merchant-products', label: '商品列表', path: '/merchant-products' }], }, { key: 'shops', label: '商户管理', icon: , - children: [{ key: 'shop-list', label: '商户列表', path: '/platform-merchants' }], + children: [{ key: 'shop-list', label: '平台商户', path: '/platform-merchants' }], }, { key: 'orders', @@ -71,11 +76,11 @@ const adminSections: SidebarSection[] = [ key: 'staff', label: '员工管理', icon: , - children: [{ key: 'merchant-members', label: '成员', path: '/merchant-members' }], + children: [{ key: 'merchant-members', label: '成员管理', path: '/merchant-members' }], }, { key: 'funds', - label: '积分', + label: '积分管理', icon: , children: [{ key: 'merchant-wallet', label: '积分明细', path: '/merchant-wallet' }], }, @@ -85,7 +90,7 @@ const adminSections: SidebarSection[] = [ icon: , children: [ { key: 'api-keys', label: 'API 密钥', path: '/merchant-api-keys' }, - { key: 'api-callbacks', label: '回调', path: '/merchant-callbacks' }, + { key: 'api-callbacks', label: '回调订阅', path: '/merchant-callbacks' }, { key: 'api-docs', label: '对接文档', path: '/open-api' }, { key: 'api-debug', label: '调用调试', path: '/api-debug' }, ], @@ -103,7 +108,7 @@ const merchantSections: SidebarSection[] = [ key: 'products', label: '商品管理', icon: , - children: [{ key: 'merchant-products', label: '商品', path: '/merchant-products' }], + children: [{ key: 'merchant-products', label: '商品列表', path: '/merchant-products' }], }, { key: 'orders', @@ -113,7 +118,7 @@ const merchantSections: SidebarSection[] = [ }, { key: 'funds', - label: '积分', + label: '积分管理', icon: , children: [{ key: 'merchant-wallet', label: '积分明细', path: '/merchant-wallet' }], }, @@ -121,7 +126,7 @@ const merchantSections: SidebarSection[] = [ key: 'staff', label: '员工管理', icon: , - children: [{ key: 'merchant-members', label: '成员', path: '/merchant-members' }], + children: [{ key: 'merchant-members', label: '成员管理', path: '/merchant-members' }], }, { key: 'open-api', @@ -129,7 +134,7 @@ const merchantSections: SidebarSection[] = [ icon: , children: [ { key: 'api-keys', label: 'API 密钥', path: '/merchant-api-keys' }, - { key: 'api-callbacks', label: '回调', path: '/merchant-callbacks' }, + { key: 'api-callbacks', label: '回调订阅', path: '/merchant-callbacks' }, ], }, ] @@ -205,10 +210,21 @@ export default function MainLayout() { } const userMenu: MenuProps['items'] = [ + { + key: 'user-info', + disabled: true, + label: ( +
+
{user?.nickname || user?.username}
+
角色:{isAdmin ? '平台超级管理员' : '商户成员'}
+
+ ), + }, + { type: 'divider' }, { key: 'logout', - icon: , - label: '退出登录', + icon: , + label: 退出登录, onClick: () => { logout() navigate('/login') @@ -223,8 +239,15 @@ export default function MainLayout() { trigger={null} collapsible collapsed={collapsed} - width={230} + width={240} collapsedWidth={74} + style={{ + position: 'sticky', + top: 0, + height: '100vh', + left: 0, + zIndex: 100, + }} >
S @@ -270,27 +293,61 @@ export default function MainLayout() { -
- - - - } /> - - {user?.nickname || user?.username} - {isAdmin ? '(平台管理员)' : '(商户账号)'} - - - +
+
+ +
+
+ {isAdmin && ( + + + + + + + + + )} + +
+ } /> + + {user?.nickname || user?.username} + + {isAdmin ? ( + + 管理员 + + ) : ( + + 商户 + + )} +
+
+
diff --git a/frontend/src/openapi/EndpointDoc.tsx b/frontend/src/openapi/EndpointDoc.tsx index 4a538dc..00dd03d 100644 --- a/frontend/src/openapi/EndpointDoc.tsx +++ b/frontend/src/openapi/EndpointDoc.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react' +import { useState, type ReactNode } from 'react' import { Space, Table, Tag, Typography } from 'antd' import type { ColumnsType } from 'antd/es/table' import type { EndpointSpec, ParamSpec } from './types' @@ -53,17 +53,35 @@ function highlightJson(json: string): string { ) } -function JsonBlock({ code }: { code: string }) { - return
+function JsonBlock({ code, title = 'JSON Response' }: { code: string; title?: string }) {
+  const [copied, setCopied] = useState(false)
+  const handleCopy = () => {
+    navigator.clipboard.writeText(code).then(() => {
+      setCopied(true)
+      setTimeout(() => setCopied(false), 2000)
+    })
+  }
+  return (
+    
+
+ {title} + +
+
+    
+ ) } function MethodPathBlock({ method, path }: { method: string; path: string }) { return ( -
-      {method}
-      {' '}
-      {path}
-    
+
+ + {method} + + {path} +
) } diff --git a/frontend/src/pages/ApiDebugger.tsx b/frontend/src/pages/ApiDebugger.tsx index d30b5cd..2a64e09 100644 --- a/frontend/src/pages/ApiDebugger.tsx +++ b/frontend/src/pages/ApiDebugger.tsx @@ -15,11 +15,12 @@ import { } from 'antd' import { ClearOutlined, SendOutlined } from '@ant-design/icons' import { endpoints } from '../openapi/endpoints' +import { PageHeader } from '../components/PageHeader' const STORAGE_KEY_APP = 'api_debug_app_key' const STORAGE_KEY_SECRET = 'api_debug_secret' -const { Title, Text, Paragraph } = Typography +const { Text } = Typography const { TextArea } = Input const methodColor: Record = { @@ -228,12 +229,11 @@ export default function ApiDebugger() { return (
- - API 调试工具 - - - 填写商户 API 密钥的 AppKey 与 Secret,选择接口并发送请求。签名在浏览器端使用 Web Crypto API 计算。 - + (value ?? 0).toLocaleString('zh-CN') @@ -23,51 +28,74 @@ function ratio(part: number, total: number) { return Math.round((part / total) * 100) } -function MetricCard({ +function ModernMetricCard({ title, value, icon, - suffix, - color, + unit = '', + iconTheme = '', + footerText, + footerValue, }: { title: string value: number icon: ReactNode - suffix?: string - color?: string + unit?: string + iconTheme?: string + footerText?: string + footerValue?: string | number }) { return ( - - numberText(value)} - prefix={icon} - suffix={suffix} - valueStyle={color ? { color } : undefined} - /> - +
+
+ {title} +
{icon}
+
+
+ {numberText(value)} + {unit && {unit}} +
+ {footerText && ( +
+ {footerText} + {footerValue} +
+ )} +
) } -function StatusLine({ +function CustomStatusLine({ label, value, total, color, + tagClass, }: { label: string value: number total: number color: string + tagClass: string }) { + const percent = ratio(value, total) return ( -
- - {label} - {numberText(value)} - - +
+
+
+ + {label} +
+ + + {numberText(value)} 单 + + + ({percent}%) + + +
+
) } @@ -75,6 +103,8 @@ function StatusLine({ export default function Dashboard() { const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) + const { user, isAdmin } = useAuth() + const navigate = useNavigate() useEffect(() => { dashboardApi @@ -86,137 +116,238 @@ export default function Dashboard() { if (loading) { return ( -
- +
+
) } return (
- -
- - 数据概览 - - - {stats?.scope === 'platform' ? '平台全局运营数据' : '当前商户运营数据'} - -
-
+ + {stats?.scope === 'platform' ? '全局视图' : '商户视图'} + + } + /> - + {/* 顶部欢迎卡片 & 快捷 LaunchPad */} +
+

+ 你好,{user?.nickname || user?.username} 👋 +

+

+ 欢迎回到 Skin Hub 游戏皮肤分销管理平台。以下是系统的最新业务状态与发货订单追踪数据。 +

+
+ + + {isAdmin && ( + <> + + + + + )} +
+
+ + {/* 第一组:核心业务指标 */} + + 基础数据指标 + + - } + iconTheme="" + footerText="活跃商品库" + footerValue="已上线" /> - } + iconTheme="metric-card-icon--purple" + footerText="商户状态" + footerValue="正常运行" /> - } /> + } + iconTheme="metric-card-icon--emerald" + footerText="团队成员" + footerValue="活跃" + /> - } /> + } + iconTheme="metric-card-icon--amber" + footerText="今日新增订单" + footerValue={`${stats?.today_order_count ?? 0} 单`} + /> - } /> + } + iconTheme="" + footerText="占比全量" + footerValue={`${ratio(stats?.today_order_count ?? 0, stats?.order_count ?? 0)}%`} + /> - } - color={(stats?.paid_order_count ?? 0) > 0 ? '#cf1322' : undefined} + iconTheme={(stats?.paid_order_count ?? 0) > 0 ? 'metric-card-icon--rose' : ''} + footerText="需及时发货" + footerValue={(stats?.paid_order_count ?? 0) > 0 ? '需处理' : '无积压'} /> - - 积分与费用 + {/* 第二组:积分与资金监控 */} + + 积分变动与账户额度 - + - } suffix="积分" /> + } + iconTheme="metric-card-icon--emerald" + /> - } suffix="积分" /> + } + iconTheme="metric-card-icon--amber" + /> - } suffix="积分" /> + } + iconTheme="metric-card-icon--purple" + /> - } suffix="积分" /> + } + iconTheme="" + /> - + {/* 第三组:订单发货分布与 API 监控 */} + - - - - - - - - + + + + + + - - + + } + prefix={} suffix={`/ ${numberText(stats?.api_client_count)}`} /> - } /> - - - } /> + } + /> } + /> + + + } - valueStyle={(stats?.failed_callback_count ?? 0) > 0 ? { color: '#cf1322' } : undefined} + prefix={} + valueStyle={(stats?.failed_callback_count ?? 0) > 0 ? { color: '#dc2626' } : undefined} /> diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index b521a5d..1ea1483 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,66 +1,132 @@ import { useState } from 'react' import { useNavigate } from 'react-router-dom' -import { Card, Form, Input, Button, Typography, message, Space } from 'antd' -import { UserOutlined, LockOutlined } from '@ant-design/icons' +import { Form, Input, Button, Typography, message, Space, Tag } from 'antd' +import { UserOutlined, LockOutlined, RightOutlined } from '@ant-design/icons' import { useAuth } from '../store/auth' export default function Login() { const { login } = useAuth() const navigate = useNavigate() const [loading, setLoading] = useState(false) + const [form] = Form.useForm() const onFinish = async (values: { username: string; password: string }) => { setLoading(true) try { await login(values.username, values.password) - message.success('登录成功') + message.success('登录成功,欢迎回来!') navigate('/') } catch (e) { - message.error(e instanceof Error ? e.message : '登录失败') + message.error(e instanceof Error ? e.message : '登录失败,请检查账号密码') } finally { setLoading(false) } } + const fillCredentials = (user: string, pass: string) => { + form.setFieldsValue({ username: user, password: pass }) + message.info(`已填入账号: ${user}`) + } + return ( -
- - +
+
+
- - 游戏皮肤供货平台 +
S
+ + Skin Hub 游戏皮肤分销平台 - 登录后台管理 + + 基于 Go + React 的高并发皮肤分发管理系统 +
-
- - } placeholder="用户名" size="large" /> + + + + } + placeholder="用户名 / 手机号" + size="large" + style={{ borderRadius: 8 }} + /> - - } placeholder="密码" size="large" /> + + + } + placeholder="密码" + size="large" + style={{ borderRadius: 8 }} + /> - - - - 商户账号请联系平台管理员创建 - - - 默认管理员:admin / admin123 + + {/* 快捷测试账号填充 */} +
+
+ 快速体验(演示账号列表): +
+ + fillCredentials('admin', 'admin123')} + > + 管理员 (admin) + + +
+ + + 商户账号与成员密钥请联系平台管理员统一配置与创建
- +
) } diff --git a/frontend/src/pages/MerchantCenter.tsx b/frontend/src/pages/MerchantCenter.tsx index 1b4f2fc..f0b93b9 100644 --- a/frontend/src/pages/MerchantCenter.tsx +++ b/frontend/src/pages/MerchantCenter.tsx @@ -1096,11 +1096,31 @@ function moneyWithSign(value?: number | null) { } function productStatusTag(value: string) { - return value === 'active' ? 上架 : 下架 + return value === 'active' ? ( + + + 已上架 + + ) : ( + + + 已下架 + + ) } function activeStatusTag(value: string) { - return value === 'active' ? 启用 : 禁用 + return value === 'active' ? ( + + + 已启用 + + ) : ( + + + 已禁用 + + ) } function productOptionLabel(item: MerchantProduct) { @@ -1110,7 +1130,20 @@ function productOptionLabel(item: MerchantProduct) { function orderStatusTag(value: string) { const item = orderStatusMap[value] || { color: 'default', text: value } - return {item.text} + const tagClassMap: Record = { + paid: 'status-tag--blue', + delivering: 'status-tag--cyan', + delivered: 'status-tag--green', + ship_failed: 'status-tag--red', + cancelled: 'status-tag--gray', + } + const tagClass = tagClassMap[value] || 'status-tag--gray' + return ( + + + {item.text} + + ) } function ledgerTypeTag(value: string) { diff --git a/frontend/src/pages/OpenApiDocs.tsx b/frontend/src/pages/OpenApiDocs.tsx index ec8431f..9ac8b25 100644 --- a/frontend/src/pages/OpenApiDocs.tsx +++ b/frontend/src/pages/OpenApiDocs.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import { Alert, Card, Descriptions, Table, Tag, Typography } from 'antd' +import { BookOutlined } from '@ant-design/icons' import EndpointDoc, { CommonResponseDoc } from '../openapi/EndpointDoc' import { commonResponseFields, @@ -180,6 +181,10 @@ export default function OpenApiDocs() {
{/* 左侧目录 */}