优化了样式
parent
0d38e1da47
commit
25dfbd9a56
|
|
@ -192,17 +192,9 @@ function AdminShell() {
|
|||
const avatarUrl = user?.avatar_url ? `/upload/${user.avatar_url}` : undefined;
|
||||
const displayName = (user?.full_name as string) || (user?.username as string) || '未登录';
|
||||
|
||||
// 只保留菜单本身:账号信息已经显示在胶囊上,弹层不再重复
|
||||
const userMenuContent = (
|
||||
<div style={{ width: 232 }}>
|
||||
<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>
|
||||
<div className="adm-user-menu-content">
|
||||
<button
|
||||
type="button"
|
||||
className={`adm-user-menu-item ${location.pathname.endsWith('/profile') ? 'is-active' : ''}`}
|
||||
|
|
@ -212,11 +204,11 @@ function AdminShell() {
|
|||
{t('个人资料')}
|
||||
</button>
|
||||
|
||||
<div className="adm-user-menu-divider" />
|
||||
<button type="button" className="adm-user-menu-item" onClick={() => go('/')}>
|
||||
<HomeOutlined />
|
||||
{t('返回可视化首页')}
|
||||
</button>
|
||||
<div className="adm-user-menu-divider" />
|
||||
<button type="button" className="adm-user-menu-item is-danger" onClick={() => void handleLogout()}>
|
||||
<LogoutOutlined />
|
||||
{t('退出登录')}
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ export function CelestialBodies() {
|
|||
onFilter: (value, record) => record.type === value,
|
||||
render: (type: string) => bodyTypeLabels[type] || type,
|
||||
},
|
||||
{ title: '描述', dataIndex: 'description', key: 'description', ellipsis: true },
|
||||
{ title: '描述', dataIndex: 'description', key: 'description', width: 320, ellipsis: true },
|
||||
{
|
||||
title: '资源配置',
|
||||
key: 'resources',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 type { ColumnsType } from 'antd/es/table';
|
||||
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> = [
|
||||
{ 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> },
|
||||
|
|
@ -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: '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: 'is_active', width: 90, render: (value) => <Tag color={value ? 'green' : 'default'}>{value ? '启用' : '停用'}</Tag> },
|
||||
];
|
||||
|
||||
return (
|
||||
|
|
@ -137,6 +147,8 @@ export function Rockets() {
|
|||
addText="新增火箭"
|
||||
onEdit={showEdit}
|
||||
onDelete={remove}
|
||||
onStatusChange={handleStatusChange}
|
||||
statusField="is_active"
|
||||
deleteConfirmTitle="确认删除该火箭?"
|
||||
deleteConfirmDescription="删除后前台发射模拟将不再显示该运载器"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 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 () => {
|
||||
try {
|
||||
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>,
|
||||
},
|
||||
{ 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,
|
||||
render: (_, record) => record.last_run_at ? (
|
||||
|
|
@ -189,6 +199,8 @@ export function ScheduledJobs() {
|
|||
addText="新增定时任务"
|
||||
onEdit={handleEdit}
|
||||
onDelete={handleDelete}
|
||||
onStatusChange={handleStatusChange}
|
||||
statusField="is_active"
|
||||
deleteConfirmTitle="确认删除该定时任务?"
|
||||
deleteConfirmDescription="删除后该任务不再自动执行"
|
||||
rowKey="id"
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ export function StaticData() {
|
|||
{
|
||||
title: '数据 (JSON)',
|
||||
dataIndex: 'data',
|
||||
width: 320,
|
||||
ellipsis: true,
|
||||
render: (text) => <span className="adm-cell-mono">{JSON.stringify(text)}</span>,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export function Tasks() {
|
|||
width: 160,
|
||||
render: (type: string) => <Tag color="blue">{type}</Tag>,
|
||||
},
|
||||
{ title: '描述', dataIndex: 'description', ellipsis: true },
|
||||
{ title: '描述', dataIndex: 'description', width: 280, ellipsis: true },
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@
|
|||
--adm-chrome-hover: rgba(31, 35, 40, 0.05);
|
||||
--adm-pill-bg: rgba(31, 35, 40, 0.04);
|
||||
--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-pill-bg: rgba(255, 255, 255, 0.04);
|
||||
--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;
|
||||
}
|
||||
|
||||
/* 用户二级菜单(Popover) */
|
||||
/*
|
||||
* 用户二级菜单(Popover)
|
||||
* 与用户胶囊一体化:同宽、同圆角、贴边对齐,内部只放菜单项。
|
||||
* 胶囊宽度 = 侧栏 236 - 左右各 10 的 dock 内边距 = 216,
|
||||
* 这里减去弹层自身 6px 内边距后取 204。
|
||||
*/
|
||||
.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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 6px 8px 10px;
|
||||
border-bottom: 1px solid var(--adm-border);
|
||||
}
|
||||
|
||||
.adm-user-menu-head-meta {
|
||||
.adm-user-menu-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.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);
|
||||
gap: 2px;
|
||||
width: 204px;
|
||||
}
|
||||
|
||||
.adm-user-menu-item {
|
||||
|
|
@ -576,8 +563,24 @@
|
|||
font-size: 13px;
|
||||
}
|
||||
|
||||
/*
|
||||
* 行悬停:
|
||||
* 1) 整行统一高亮,最右侧固定列使用同色不透明底色,视觉上连成一条高亮带;
|
||||
* 2) 固定列始终位于内容之上(不提升行层级),超长内容不会压住操作按钮。
|
||||
*/
|
||||
.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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue