优化了样式
parent
0d38e1da47
commit
25dfbd9a56
|
|
@ -192,17 +192,9 @@ function AdminShell() {
|
||||||
const avatarUrl = user?.avatar_url ? `/upload/${user.avatar_url}` : undefined;
|
const avatarUrl = user?.avatar_url ? `/upload/${user.avatar_url}` : undefined;
|
||||||
const displayName = (user?.full_name as string) || (user?.username as string) || '未登录';
|
const displayName = (user?.full_name as string) || (user?.username as string) || '未登录';
|
||||||
|
|
||||||
|
// 只保留菜单本身:账号信息已经显示在胶囊上,弹层不再重复
|
||||||
const userMenuContent = (
|
const userMenuContent = (
|
||||||
<div style={{ width: 232 }}>
|
<div className="adm-user-menu-content">
|
||||||
<div className="adm-user-menu-head">
|
|
||||||
<Avatar size={38} src={avatarUrl} icon={<UserOutlined />} />
|
|
||||||
<div className="adm-user-menu-head-meta">
|
|
||||||
<strong>{displayName}</strong>
|
|
||||||
<span>{user?.email || t(roleLabelOf(user))}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="adm-user-menu-group">{t('个人资料')}</div>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`adm-user-menu-item ${location.pathname.endsWith('/profile') ? 'is-active' : ''}`}
|
className={`adm-user-menu-item ${location.pathname.endsWith('/profile') ? 'is-active' : ''}`}
|
||||||
|
|
@ -212,11 +204,11 @@ function AdminShell() {
|
||||||
{t('个人资料')}
|
{t('个人资料')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="adm-user-menu-divider" />
|
|
||||||
<button type="button" className="adm-user-menu-item" onClick={() => go('/')}>
|
<button type="button" className="adm-user-menu-item" onClick={() => go('/')}>
|
||||||
<HomeOutlined />
|
<HomeOutlined />
|
||||||
{t('返回可视化首页')}
|
{t('返回可视化首页')}
|
||||||
</button>
|
</button>
|
||||||
|
<div className="adm-user-menu-divider" />
|
||||||
<button type="button" className="adm-user-menu-item is-danger" onClick={() => void handleLogout()}>
|
<button type="button" className="adm-user-menu-item is-danger" onClick={() => void handleLogout()}>
|
||||||
<LogoutOutlined />
|
<LogoutOutlined />
|
||||||
{t('退出登录')}
|
{t('退出登录')}
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,7 @@ export function CelestialBodies() {
|
||||||
onFilter: (value, record) => record.type === value,
|
onFilter: (value, record) => record.type === value,
|
||||||
render: (type: string) => bodyTypeLabels[type] || type,
|
render: (type: string) => bodyTypeLabels[type] || type,
|
||||||
},
|
},
|
||||||
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
{ title: '描述', dataIndex: 'description', key: 'description', width: 320, ellipsis: true },
|
||||||
{
|
{
|
||||||
title: '资源配置',
|
title: '资源配置',
|
||||||
key: 'resources',
|
key: 'resources',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Button, Col, Form, Input, InputNumber, Modal, Row, Switch, Tabs, Tag } from 'antd';
|
import { Button, Col, Form, Input, InputNumber, Modal, Row, Switch, Tabs } from 'antd';
|
||||||
import { RocketOutlined } from '@ant-design/icons';
|
import { RocketOutlined } from '@ant-design/icons';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import { DataTable } from '../../components/admin/DataTable';
|
import { DataTable } from '../../components/admin/DataTable';
|
||||||
|
|
@ -105,6 +105,17 @@ export function Rockets() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 与其它列表一致:状态列使用统一的启用/停用开关 */
|
||||||
|
const handleStatusChange = async (record: RocketConfig, checked: boolean) => {
|
||||||
|
try {
|
||||||
|
await request.put(`/rockets/admin/${record.id}`, { is_active: checked });
|
||||||
|
setData((current) => current.map((item) => (item.id === record.id ? { ...item, is_active: checked } : item)));
|
||||||
|
toast.success(`${record.name_zh || record.name} 已${checked ? '启用' : '停用'}`);
|
||||||
|
} catch {
|
||||||
|
toast.error('状态更新失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const columns: ColumnsType<RocketConfig> = [
|
const columns: ColumnsType<RocketConfig> = [
|
||||||
{ title: '排序', dataIndex: 'sort_order', width: 70 },
|
{ title: '排序', dataIndex: 'sort_order', width: 70 },
|
||||||
{ title: '火箭', key: 'name', width: 210, render: (_, record) => <div><div className="adm-cell-strong">{record.name_zh || record.name}</div><div className="adm-cell-sub">{record.name} · {record.code}</div></div> },
|
{ title: '火箭', key: 'name', width: 210, render: (_, record) => <div><div className="adm-cell-strong">{record.name_zh || record.name}</div><div className="adm-cell-sub">{record.name} · {record.code}</div></div> },
|
||||||
|
|
@ -114,7 +125,6 @@ export function Rockets() {
|
||||||
{ title: '一级', key: 'stage1', width: 170, render: (_, record) => <span>{record.stage_1.engine_count} 台 / {(record.stage_1.max_thrust_n / 1_000_000).toFixed(2)} MN</span> },
|
{ title: '一级', key: 'stage1', width: 170, render: (_, record) => <span>{record.stage_1.engine_count} 台 / {(record.stage_1.max_thrust_n / 1_000_000).toFixed(2)} MN</span> },
|
||||||
{ title: '二级', key: 'stage2', width: 170, render: (_, record) => <span>{record.stage_2.engine_count} 台 / {(record.stage_2.max_thrust_n / 1_000_000).toFixed(2)} MN</span> },
|
{ title: '二级', key: 'stage2', width: 170, render: (_, record) => <span>{record.stage_2.engine_count} 台 / {(record.stage_2.max_thrust_n / 1_000_000).toFixed(2)} MN</span> },
|
||||||
{ title: '目标轨道', dataIndex: 'target_orbit_km', width: 110, render: (value) => `${value} km` },
|
{ title: '目标轨道', dataIndex: 'target_orbit_km', width: 110, render: (value) => `${value} km` },
|
||||||
{ title: '状态', dataIndex: 'is_active', width: 90, render: (value) => <Tag color={value ? 'green' : 'default'}>{value ? '启用' : '停用'}</Tag> },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -137,6 +147,8 @@ export function Rockets() {
|
||||||
addText="新增火箭"
|
addText="新增火箭"
|
||||||
onEdit={showEdit}
|
onEdit={showEdit}
|
||||||
onDelete={remove}
|
onDelete={remove}
|
||||||
|
onStatusChange={handleStatusChange}
|
||||||
|
statusField="is_active"
|
||||||
deleteConfirmTitle="确认删除该火箭?"
|
deleteConfirmTitle="确认删除该火箭?"
|
||||||
deleteConfirmDescription="删除后前台发射模拟将不再显示该运载器"
|
deleteConfirmDescription="删除后前台发射模拟将不再显示该运载器"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Badge, Button, Form, Tag, Tooltip } from 'antd';
|
import { Button, Form, Tag, Tooltip } from 'antd';
|
||||||
import { ClockCircleOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
import { ClockCircleOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
|
||||||
|
|
@ -119,6 +119,17 @@ export function ScheduledJobs() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 与其它列表一致:状态列使用统一的启用/停用开关 */
|
||||||
|
const handleStatusChange = async (record: ScheduledJob, checked: boolean) => {
|
||||||
|
try {
|
||||||
|
await request.put(`/scheduled-jobs/${record.id}`, { is_active: checked });
|
||||||
|
setData((current) => current.map((item) => (item.id === record.id ? { ...item, is_active: checked } : item)));
|
||||||
|
toast.success(`${record.name} 已${checked ? '启用' : '禁用'}`);
|
||||||
|
} catch {
|
||||||
|
toast.error('状态更新失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleModalOk = async () => {
|
const handleModalOk = async () => {
|
||||||
try {
|
try {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
|
|
@ -164,7 +175,6 @@ export function ScheduledJobs() {
|
||||||
render: (func, record) => record.job_type === 'predefined' ? <Tag color="cyan">{func}</Tag> : <span className="adm-cell-sub">-</span>,
|
render: (func, record) => record.job_type === 'predefined' ? <Tag color="cyan">{func}</Tag> : <span className="adm-cell-sub">-</span>,
|
||||||
},
|
},
|
||||||
{ title: 'Cron 表达式', dataIndex: 'cron_expression', width: 130, render: (text) => <Tag color="green">{text}</Tag> },
|
{ title: 'Cron 表达式', dataIndex: 'cron_expression', width: 130, render: (text) => <Tag color="green">{text}</Tag> },
|
||||||
{ title: '状态', dataIndex: 'is_active', width: 80, render: (active) => <Badge status={active ? 'success' : 'default'} text={active ? '启用' : '禁用'} /> },
|
|
||||||
{
|
{
|
||||||
title: '上次执行', width: 200,
|
title: '上次执行', width: 200,
|
||||||
render: (_, record) => record.last_run_at ? (
|
render: (_, record) => record.last_run_at ? (
|
||||||
|
|
@ -189,6 +199,8 @@ export function ScheduledJobs() {
|
||||||
addText="新增定时任务"
|
addText="新增定时任务"
|
||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
|
onStatusChange={handleStatusChange}
|
||||||
|
statusField="is_active"
|
||||||
deleteConfirmTitle="确认删除该定时任务?"
|
deleteConfirmTitle="确认删除该定时任务?"
|
||||||
deleteConfirmDescription="删除后该任务不再自动执行"
|
deleteConfirmDescription="删除后该任务不再自动执行"
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ export function StaticData() {
|
||||||
{
|
{
|
||||||
title: '数据 (JSON)',
|
title: '数据 (JSON)',
|
||||||
dataIndex: 'data',
|
dataIndex: 'data',
|
||||||
|
width: 320,
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text) => <span className="adm-cell-mono">{JSON.stringify(text)}</span>,
|
render: (text) => <span className="adm-cell-mono">{JSON.stringify(text)}</span>,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export function Tasks() {
|
||||||
width: 160,
|
width: 160,
|
||||||
render: (type: string) => <Tag color="blue">{type}</Tag>,
|
render: (type: string) => <Tag color="blue">{type}</Tag>,
|
||||||
},
|
},
|
||||||
{ title: '描述', dataIndex: 'description', ellipsis: true },
|
{ title: '描述', dataIndex: 'description', width: 280, ellipsis: true },
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@
|
||||||
--adm-chrome-hover: rgba(31, 35, 40, 0.05);
|
--adm-chrome-hover: rgba(31, 35, 40, 0.05);
|
||||||
--adm-pill-bg: rgba(31, 35, 40, 0.04);
|
--adm-pill-bg: rgba(31, 35, 40, 0.04);
|
||||||
--adm-pill-hover: rgba(31, 35, 40, 0.08);
|
--adm-pill-hover: rgba(31, 35, 40, 0.08);
|
||||||
|
--adm-row-hover: rgba(35, 134, 54, 0.06);
|
||||||
|
/* 固定列必须用不透明色,才能在悬停时继续遮住横向滚动的内容 */
|
||||||
|
--adm-row-hover-solid: #f2f8f3;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 深色主题:只切换变量,页面结构与文案不变 */
|
/* 深色主题:只切换变量,页面结构与文案不变 */
|
||||||
|
|
@ -58,6 +61,8 @@
|
||||||
--adm-chrome-hover: rgba(255, 255, 255, 0.06);
|
--adm-chrome-hover: rgba(255, 255, 255, 0.06);
|
||||||
--adm-pill-bg: rgba(255, 255, 255, 0.04);
|
--adm-pill-bg: rgba(255, 255, 255, 0.04);
|
||||||
--adm-pill-hover: rgba(255, 255, 255, 0.1);
|
--adm-pill-hover: rgba(255, 255, 255, 0.1);
|
||||||
|
--adm-row-hover: rgba(46, 160, 67, 0.14);
|
||||||
|
--adm-row-hover-solid: #192e27;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ 布局骨架 */
|
/* ------------------------------------------------------------ 布局骨架 */
|
||||||
|
|
@ -268,42 +273,24 @@
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 用户二级菜单(Popover) */
|
/*
|
||||||
|
* 用户二级菜单(Popover)
|
||||||
|
* 与用户胶囊一体化:同宽、同圆角、贴边对齐,内部只放菜单项。
|
||||||
|
* 胶囊宽度 = 侧栏 236 - 左右各 10 的 dock 内边距 = 216,
|
||||||
|
* 这里减去弹层自身 6px 内边距后取 204。
|
||||||
|
*/
|
||||||
.adm-user-menu .ant-popover-inner {
|
.adm-user-menu .ant-popover-inner {
|
||||||
padding: 8px;
|
padding: 6px;
|
||||||
|
border: 1px solid var(--adm-border);
|
||||||
|
border-radius: var(--adm-radius);
|
||||||
|
box-shadow: 0 -4px 16px rgba(31, 35, 40, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.adm-user-menu-head {
|
.adm-user-menu-content {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 6px 8px 10px;
|
|
||||||
border-bottom: 1px solid var(--adm-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.adm-user-menu-head-meta {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 0;
|
gap: 2px;
|
||||||
line-height: 1.35;
|
width: 204px;
|
||||||
}
|
|
||||||
|
|
||||||
.adm-user-menu-head-meta strong {
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.adm-user-menu-head-meta span {
|
|
||||||
font-size: 11px;
|
|
||||||
color: var(--adm-text-muted);
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.adm-user-menu-group {
|
|
||||||
padding: 8px 8px 2px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
color: var(--adm-text-muted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.adm-user-menu-item {
|
.adm-user-menu-item {
|
||||||
|
|
@ -576,8 +563,24 @@
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 行悬停:
|
||||||
|
* 1) 整行统一高亮,最右侧固定列使用同色不透明底色,视觉上连成一条高亮带;
|
||||||
|
* 2) 固定列始终位于内容之上(不提升行层级),超长内容不会压住操作按钮。
|
||||||
|
*/
|
||||||
.adm-table .ant-table-tbody > tr:hover > td {
|
.adm-table .ant-table-tbody > tr:hover > td {
|
||||||
background: rgba(35, 134, 54, 0.04) !important;
|
background: var(--adm-row-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 固定列:始终不透明,横向滚动时遮住下方内容 */
|
||||||
|
.adm-table .ant-table-tbody > tr > td.ant-table-cell-fix-end,
|
||||||
|
.adm-table .ant-table-tbody > tr > td.ant-table-cell-fix-start {
|
||||||
|
background: var(--adm-surface) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adm-table .ant-table-tbody > tr:hover > td.ant-table-cell-fix-end,
|
||||||
|
.adm-table .ant-table-tbody > tr:hover > td.ant-table-cell-fix-start {
|
||||||
|
background: var(--adm-row-hover-solid) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.adm-table .ant-table-pagination {
|
.adm-table .ant-table-pagination {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue