refactor: 集中管理配置常量并优化样式一致性

This commit is contained in:
zhang1106
2026-03-06 14:39:52 +08:00
parent 0cd0107008
commit 885ac47fd2
19 changed files with 348 additions and 64 deletions
+67 -1
View File
@@ -56,4 +56,70 @@ JWT_SECRET=your-strong-secret-key-minimum-32-characters-change-in-production
# Token 过期时间(格式:数字+单位,如 24h, 2h, 30m
# 建议:开发环境 24h,生产环境 2h 或更短
TOKEN_EXPIRY=24h
TOKEN_EXPIRY=24h
# 密码加密强度(bcrypt salt rounds
# 默认值:10,范围:4-12,值越大越安全但计算越慢
SALT_ROUNDS=10
# 登录失败锁定阈值
# 连续登录失败超过此次数后锁定账户
MAX_LOGIN_ATTEMPTS=5
# 账户锁定时间(分钟)
# 登录失败锁定后的解锁等待时间
LOCK_TIME_MINUTES=30
# 密码最小长度
PASSWORD_MIN_LENGTH=6
# 用户名长度限制
USERNAME_MIN_LENGTH=3
USERNAME_MAX_LENGTH=50
# ==============================================
# API 配置
# ==============================================
# API 请求超时时间(毫秒)
API_TIMEOUT=30000
# 数据库查询超时时间(毫秒)
DB_QUERY_TIMEOUT=30000
# ==============================================
# 分页配置
# ==============================================
# 默认每页条数
DEFAULT_PAGE_SIZE=10
# 最大每页条数
MAX_PAGE_SIZE=1000
# ==============================================
# 文件上传配置
# ==============================================
# 最大文件上传大小(MB
MAX_FILE_SIZE_MB=50
# 最大头像上传大小(MB
MAX_AVATAR_SIZE_MB=5
# ==============================================
# 重试配置
# ==============================================
# 最大重试次数
MAX_RETRIES=3
# 重试延迟(毫秒)
RETRY_DELAY=1000
# ==============================================
# 前端配置
# ==============================================
# 前端默认端口
FRONTEND_PORT=3000
+39
View File
@@ -0,0 +1,39 @@
/**
* 通用配置常量
* 集中管理分页、文件上传、重试等配置
*/
module.exports = {
PAGINATION: {
DEFAULT_PAGE_SIZE: parseInt(process.env.DEFAULT_PAGE_SIZE, 10) || 10,
MAX_PAGE_SIZE: parseInt(process.env.MAX_PAGE_SIZE, 10) || 1000,
PAGE_SIZE_OPTIONS: [10, 20, 30, 50, 100],
},
FILE_UPLOAD: {
MAX_FILE_SIZE: (parseInt(process.env.MAX_FILE_SIZE_MB, 10) || 50) * 1024 * 1024,
MAX_AVATAR_SIZE: (parseInt(process.env.MAX_AVATAR_SIZE_MB, 10) || 5) * 1024 * 1024,
ALLOWED_IMAGE_TYPES: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
ALLOWED_DOC_TYPES: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
],
},
RETRY: {
MAX_RETRIES: parseInt(process.env.MAX_RETRIES, 10) || 3,
RETRY_DELAY: parseInt(process.env.RETRY_DELAY, 10) || 1000,
},
TIMEOUT: {
API_TIMEOUT: parseInt(process.env.API_TIMEOUT, 10) || 30000,
DB_QUERY_TIMEOUT: parseInt(process.env.DB_QUERY_TIMEOUT, 10) || 30000,
},
FRONTEND: {
DEFAULT_PORT: parseInt(process.env.FRONTEND_PORT, 10) || 3000,
},
};
+13
View File
@@ -0,0 +1,13 @@
/**
* 配置统一导出
*/
const security = require('./security');
const constants = require('./constants');
module.exports = {
...security,
...constants,
security,
constants,
};
+19
View File
@@ -0,0 +1,19 @@
/**
* 安全相关配置常量
* 集中管理密码加密、登录限制等安全配置
*/
module.exports = {
SALT_ROUNDS: parseInt(process.env.SALT_ROUNDS, 10) || 10,
MAX_LOGIN_ATTEMPTS: parseInt(process.env.MAX_LOGIN_ATTEMPTS, 10) || 5,
LOCK_TIME: (parseInt(process.env.LOCK_TIME_MINUTES, 10) || 30) * 60 * 1000,
TOKEN_EXPIRY: process.env.TOKEN_EXPIRY || '24h',
PASSWORD_MIN_LENGTH: parseInt(process.env.PASSWORD_MIN_LENGTH, 10) || 6,
USERNAME_MIN_LENGTH: parseInt(process.env.USERNAME_MIN_LENGTH, 10) || 3,
USERNAME_MAX_LENGTH: parseInt(process.env.USERNAME_MAX_LENGTH, 10) || 50,
};
+8 -9
View File
@@ -4,11 +4,10 @@ const User = require('../models/User');
const Role = require('../models/Role');
const UserRole = require('../models/UserRole');
const { generateToken, authMiddleware } = require('../middleware/auth');
const { SALT_ROUNDS, MAX_LOGIN_ATTEMPTS, PASSWORD_MIN_LENGTH, USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH } = require('../config');
const router = express.Router();
const SALT_ROUNDS = 10;
const generateId = () => {
return 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
};
@@ -24,17 +23,17 @@ router.post('/register', async (req, res) => {
});
}
if (username.length < 3 || username.length > 20) {
if (username.length < USERNAME_MIN_LENGTH || username.length > USERNAME_MAX_LENGTH) {
return res.status(400).json({
success: false,
message: '用户名长度必须在3-20个字符之间'
message: `用户名长度必须在${USERNAME_MIN_LENGTH}-${USERNAME_MAX_LENGTH}个字符之间`
});
}
if (password.length < 6) {
if (password.length < PASSWORD_MIN_LENGTH) {
return res.status(400).json({
success: false,
message: '密码长度不能少于6个字符'
message: `密码长度不能少于${PASSWORD_MIN_LENGTH}个字符`
});
}
@@ -175,7 +174,7 @@ router.post('/login', async (req, res) => {
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
user.loginCount = (user.loginCount || 0) + 1;
if (user.loginCount >= 5) {
if (user.loginCount >= MAX_LOGIN_ATTEMPTS) {
user.status = 'locked';
}
await user.save();
@@ -309,10 +308,10 @@ router.put('/password', authMiddleware, async (req, res) => {
});
}
if (newPassword.length < 6) {
if (newPassword.length < PASSWORD_MIN_LENGTH) {
return res.status(400).json({
success: false,
message: '新密码长度不能少于6个字符'
message: `新密码长度不能少于${PASSWORD_MIN_LENGTH}个字符`
});
}
+11 -13
View File
@@ -7,10 +7,11 @@ const Consumable = require('../models/Consumable');
const ConsumableRecord = require('../models/ConsumableRecord');
const ConsumableLog = require('../models/ConsumableLog');
const ConsumableLogArchive = require('../models/ConsumableLogArchive');
const { PAGINATION, RETRY } = require('../config');
router.get('/', async (req, res) => {
try {
const { keyword, category, status, page = 1, pageSize = 10 } = req.query;
const { keyword, category, status, page = 1, pageSize = PAGINATION.DEFAULT_PAGE_SIZE } = req.query;
const offset = (page - 1) * pageSize;
const where = {};
@@ -281,10 +282,9 @@ router.get('/inout/records', async (req, res) => {
});
router.post('/quick-inout', async (req, res) => {
const MAX_RETRIES = 3;
let attempt = 0;
while (attempt < MAX_RETRIES) {
while (attempt < RETRY.MAX_RETRIES) {
const transaction = await sequelize.transaction();
try {
const { consumableId, type, quantity, operator, reason, notes, snList } = req.body;
@@ -348,7 +348,7 @@ router.post('/quick-inout', async (req, res) => {
if (affectedRows === 0) {
await transaction.rollback();
attempt++;
if (attempt >= MAX_RETRIES) {
if (attempt >= RETRY.MAX_RETRIES) {
return res.status(409).json({ error: '并发冲突,请稍后重试' });
}
continue;
@@ -397,7 +397,7 @@ router.post('/quick-inout', async (req, res) => {
return;
} catch (error) {
await transaction.rollback();
if (attempt >= MAX_RETRIES - 1) {
if (attempt >= RETRY.MAX_RETRIES - 1) {
return res.status(500).json({ error: error.message });
}
attempt++;
@@ -406,10 +406,9 @@ router.post('/quick-inout', async (req, res) => {
});
router.post('/inout', async (req, res) => {
const MAX_RETRIES = 3;
let attempt = 0;
while (attempt < MAX_RETRIES) {
while (attempt < RETRY.MAX_RETRIES) {
const transaction = await sequelize.transaction();
try {
const { consumableId, type, quantity, operator, reason, recipient, notes, snList } = req.body;
@@ -470,7 +469,7 @@ router.post('/inout', async (req, res) => {
if (affectedRows === 0) {
await transaction.rollback();
attempt++;
if (attempt >= MAX_RETRIES) {
if (attempt >= RETRY.MAX_RETRIES) {
return res.status(409).json({ error: '并发冲突,请稍后重试' });
}
continue;
@@ -520,7 +519,7 @@ router.post('/inout', async (req, res) => {
return;
} catch (error) {
await transaction.rollback();
if (attempt >= MAX_RETRIES - 1) {
if (attempt >= RETRY.MAX_RETRIES - 1) {
return res.status(500).json({ error: error.message });
}
attempt++;
@@ -529,10 +528,9 @@ router.post('/inout', async (req, res) => {
});
router.post('/adjust', async (req, res) => {
const MAX_RETRIES = 3;
let attempt = 0;
while (attempt < MAX_RETRIES) {
while (attempt < RETRY.MAX_RETRIES) {
const transaction = await sequelize.transaction();
try {
const { consumableId, adjustType, quantity, operator, reason, notes } = req.body;
@@ -582,7 +580,7 @@ router.post('/adjust', async (req, res) => {
if (affectedRows === 0) {
await transaction.rollback();
attempt++;
if (attempt >= MAX_RETRIES) {
if (attempt >= RETRY.MAX_RETRIES) {
return res.status(409).json({ error: '并发冲突,请稍后重试' });
}
continue;
@@ -619,7 +617,7 @@ router.post('/adjust', async (req, res) => {
return;
} catch (error) {
await transaction.rollback();
if (attempt >= MAX_RETRIES - 1) {
if (attempt >= RETRY.MAX_RETRIES - 1) {
return res.status(500).json({ error: error.message });
}
attempt++;
+2 -1
View File
@@ -10,6 +10,7 @@ const Rack = require('../models/Rack');
const Room = require('../models/Room');
const User = require('../models/User');
const { authMiddleware, authorize } = require('../middleware/auth');
const { PAGINATION } = require('../config');
InventoryTask.belongsTo(InventoryPlan, { foreignKey: 'planId', as: 'Plan' });
InventoryPlan.hasMany(InventoryTask, { foreignKey: 'planId', as: 'Tasks' });
@@ -41,7 +42,7 @@ router.use(authMiddleware);
router.get('/plans', async (req, res) => {
try {
const { status, page = 1, pageSize = 10, keyword } = req.query;
const { status, page = 1, pageSize = PAGINATION.DEFAULT_PAGE_SIZE, keyword } = req.query;
const offset = (page - 1) * pageSize;
const where = {};
+4 -3
View File
@@ -4,6 +4,7 @@ const fs = require('fs');
const path = require('path');
const { Op } = require('sequelize');
const SystemSetting = require('../models/SystemSetting');
const { FRONTEND } = require('../config');
// 初始化默认系统设置
const initDefaultSettings = async () => {
@@ -267,7 +268,7 @@ router.post('/reset/:key', async (req, res) => {
idle_warning_time: 60,
max_login_attempts: 5,
maintenance_mode: false,
frontend_port: 3000,
frontend_port: FRONTEND.DEFAULT_PORT,
primary_color: '#667eea',
secondary_color: '#764ba2',
compact_mode: false,
@@ -563,7 +564,7 @@ router.get('/system/info', async (req, res) => {
router.get('/frontend/port', async (req, res) => {
try {
const portSetting = await SystemSetting.findByPk('frontend_port');
const port = portSetting ? JSON.parse(portSetting.settingValue) : 3000;
const port = portSetting ? JSON.parse(portSetting.settingValue) : FRONTEND.DEFAULT_PORT;
res.json({ port });
} catch (error) {
res.status(500).json({ error: error.message });
@@ -577,7 +578,7 @@ router.post('/frontend/port/sync', async (req, res) => {
const path = require('path');
const portSetting = await SystemSetting.findByPk('frontend_port');
const port = portSetting ? JSON.parse(portSetting.settingValue) : 3000;
const port = portSetting ? JSON.parse(portSetting.settingValue) : FRONTEND.DEFAULT_PORT;
// 写入前端配置文件
const frontendDir = path.join(__dirname, '../../frontend');
+8 -9
View File
@@ -6,11 +6,10 @@ const User = require('../models/User');
const Role = require('../models/Role');
const UserRole = require('../models/UserRole');
const { authMiddleware } = require('../middleware/auth');
const { SALT_ROUNDS, PASSWORD_MIN_LENGTH, FILE_UPLOAD, PAGINATION } = require('../config');
const router = express.Router();
const SALT_ROUNDS = 10;
const generateId = () => {
return 'user_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
};
@@ -37,9 +36,9 @@ const { Op } = require('sequelize');
router.get('/', authMiddleware, async (req, res) => {
try {
const { page = 1, pageSize = 10, username, status, realName } = req.query;
const { page = 1, pageSize = PAGINATION.DEFAULT_PAGE_SIZE, username, status, realName } = req.query;
const offset = (parseInt(page) - 1) * parseInt(pageSize);
const limit = parseInt(pageSize);
const limit = Math.min(parseInt(pageSize), PAGINATION.MAX_PAGE_SIZE);
const where = getWhereClause({ username, status, realName });
@@ -256,7 +255,7 @@ router.put('/:userId', authMiddleware, async (req, res) => {
if (status !== undefined) user.status = status;
if (remark !== undefined) user.remark = remark;
if (newPassword && newPassword.length >= 6) {
if (newPassword && newPassword.length >= PASSWORD_MIN_LENGTH) {
user.password = await bcrypt.hash(newPassword, SALT_ROUNDS);
}
@@ -303,10 +302,10 @@ router.put('/:userId/password', authMiddleware, async (req, res) => {
});
}
if (!newPassword || newPassword.length < 6) {
if (!newPassword || newPassword.length < PASSWORD_MIN_LENGTH) {
return res.status(400).json({
success: false,
message: '密码长度不能少于6个字符'
message: `密码长度不能少于${PASSWORD_MIN_LENGTH}个字符`
});
}
@@ -388,10 +387,10 @@ router.post('/:userId/avatar', authMiddleware, async (req, res) => {
});
}
if (avatarFile.size > 5 * 1024 * 1024) {
if (avatarFile.size > FILE_UPLOAD.MAX_AVATAR_SIZE) {
return res.status(400).json({
success: false,
message: '图片大小不能超过 5MB'
message: `图片大小不能超过 ${FILE_UPLOAD.MAX_AVATAR_SIZE / 1024 / 1024}MB`
});
}
+2 -3
View File
@@ -3,15 +3,14 @@ const express = require('express');
const cors = require('cors');
const fileUpload = require('express-fileupload');
const { sequelize } = require('./db');
const { FILE_UPLOAD } = require('./config');
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 8000;
// 中间件
app.use(cors());
app.use(express.json());
app.use(fileUpload({ limits: { fileSize: 50 * 1024 * 1024 } }));
app.use(fileUpload({ limits: { fileSize: FILE_UPLOAD.MAX_FILE_SIZE } }));
app.use('/temp', express.static('temp'));
// 数据库连接已从db.js导入
+15 -1
View File
@@ -1,4 +1,18 @@
# 用户空闲超时配置(毫秒)
# ==============================================
# API 配置
# ==============================================
# API 请求超时时间(毫秒)
VITE_API_TIMEOUT=30000
# API 基础路径
VITE_API_BASE_URL=/api
# ==============================================
# 用户空闲超时配置
# ==============================================
# 用户空闲超时时间(毫秒)
# 默认30分钟 = 30 * 60 * 1000 = 1800000
VITE_IDLE_TIMEOUT=1800000
+3 -4
View File
@@ -1,10 +1,9 @@
import axios from 'axios';
const API_BASE_URL = '/api';
import { API_CONFIG } from '../config/api';
const api = axios.create({
baseURL: API_BASE_URL,
timeout: 30000,
baseURL: API_CONFIG.baseURL,
timeout: API_CONFIG.timeout,
headers: {
'Content-Type': 'application/json',
},
+25
View File
@@ -0,0 +1,25 @@
/**
* API 相关配置
* 集中管理 API 超时、分页、防抖等配置
*/
export const API_CONFIG = {
timeout: parseInt(import.meta.env.VITE_API_TIMEOUT, 10) || 30000,
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
pagination: {
defaultPageSize: 10,
maxPageSize: 1000,
pageSizeOptions: [10, 20, 30, 50, 100],
},
debounceDelay: 300,
retry: {
maxRetries: 3,
retryDelay: 1000,
},
};
export default API_CONFIG;
+20
View File
@@ -89,6 +89,26 @@ export const designTokens = {
lg: '24px',
xl: '32px',
},
typography: {
xs: '12px',
sm: '13px',
base: '14px',
md: '16px',
lg: '18px',
xl: '20px',
'2xl': '24px',
'3xl': '32px',
'4xl': '40px',
},
zIndex: {
dropdown: 1000,
sticky: 1020,
fixed: 1030,
modalBackdrop: 1040,
modal: 1050,
popover: 1060,
tooltip: 1070,
},
};
export default designTokens;
+3 -2
View File
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react';
import { API_CONFIG } from '../config/api';
/**
* 防抖 Hook
@@ -15,7 +16,7 @@ import { useState, useEffect } from 'react';
* fetchData(debouncedKeyword);
* }, [debouncedKeyword]);
*/
export function useDebounce(value, delay = 300) {
export function useDebounce(value, delay = API_CONFIG.debounceDelay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
@@ -44,7 +45,7 @@ export function useDebounce(value, delay = 300) {
*
* <Input onChange={e => debouncedSearch(e.target.value)} />
*/
export function useDebouncedCallback(callback, delay = 300) {
export function useDebouncedCallback(callback, delay = API_CONFIG.debounceDelay) {
const [timeoutId, setTimeoutId] = useState(null);
const debouncedFn = (...args) => {
+36 -12
View File
@@ -1620,7 +1620,7 @@ function DeviceManagement() {
<Modal
title={
<div style={modalHeaderStyle}>
<div style={{ ...modalHeaderStyle, paddingRight: '32px' }}>
{editingDevice ? (
<EditOutlined style={{ color: '#667eea' }} />
) : (
@@ -1635,7 +1635,11 @@ function DeviceManagement() {
width={700}
style={{ borderRadius: '16px' }}
styles={{
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 50px 16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
body: { padding: '24px' },
}}
className="device-modal"
@@ -1941,7 +1945,7 @@ function DeviceManagement() {
<Modal
title={
<div style={modalHeaderStyle}>
<div style={{ ...modalHeaderStyle, paddingRight: '32px' }}>
<SettingOutlined style={{ color: '#667eea' }} />
字段配置
</div>
@@ -1951,7 +1955,11 @@ function DeviceManagement() {
footer={null}
width={600}
styles={{
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
body: { padding: '24px' },
}}
>
@@ -2036,7 +2044,7 @@ function DeviceManagement() {
<Modal
title={
<div style={modalHeaderStyle}>
<div style={{ ...modalHeaderStyle, paddingRight: '32px' }}>
<UploadOutlined style={{ color: '#667eea' }} />
导入设备
</div>
@@ -2053,7 +2061,11 @@ function DeviceManagement() {
width={650}
destroyOnHidden
styles={{
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
body: { padding: '24px' },
}}
>
@@ -2369,7 +2381,7 @@ function DeviceManagement() {
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '18px', fontWeight: 600 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '18px', fontWeight: 600, paddingRight: '32px' }}>
<AppstoreOutlined style={{ color: '#667eea' }} />
设备详情
</div>
@@ -2434,7 +2446,11 @@ function DeviceManagement() {
width={700}
destroyOnHidden
styles={{
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
body: { padding: '0', overflow: 'auto' },
}}
>
@@ -2599,7 +2615,7 @@ function DeviceManagement() {
<Modal
title={
<div style={modalHeaderStyle}>
<div style={{ ...modalHeaderStyle, paddingRight: '32px' }}>
<ReloadOutlined style={{ color: '#52c41a' }} />
批量状态变更
</div>
@@ -2637,7 +2653,11 @@ function DeviceManagement() {
]}
destroyOnHidden
styles={{
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
body: { padding: '24px' },
}}
>
@@ -2664,7 +2684,7 @@ function DeviceManagement() {
<Modal
title={
<div style={modalHeaderStyle}>
<div style={{ ...modalHeaderStyle, paddingRight: '32px' }}>
<ExportOutlined style={{ color: '#fa8c16' }} />
导出设备数据
</div>
@@ -2702,7 +2722,11 @@ function DeviceManagement() {
]}
destroyOnHidden
styles={{
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
body: { padding: '24px' },
}}
width={600}
+32 -4
View File
@@ -755,6 +755,23 @@ function RackManagement() {
return (
<div style={containerStyle}>
<style>{`
.ant-modal-close {
top: 16px !important;
right: 16px !important;
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
}
.ant-modal-close-x {
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
`}</style>
<div style={headerStyle}>
<div
style={{
@@ -969,7 +986,7 @@ function RackManagement() {
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', paddingRight: '32px' }}>
<div
style={{
width: '4px',
@@ -988,9 +1005,16 @@ function RackManagement() {
destroyOnHidden
styles={{
body: { padding: '24px' },
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 50px 16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
}}
style={{ borderRadius: '16px' }}
classNames={{
header: 'modal-header-fix',
}}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Row gutter={16}>
@@ -1231,7 +1255,7 @@ function RackManagement() {
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', paddingRight: '32px' }}>
<div
style={{
width: '4px',
@@ -1256,7 +1280,11 @@ function RackManagement() {
destroyOnHidden
styles={{
body: { padding: '24px' },
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
}}
style={{ borderRadius: '16px' }}
>
+23 -2
View File
@@ -591,6 +591,23 @@ function RoomManagement() {
return (
<div style={containerStyle}>
<style>{`
.ant-modal-close {
top: 16px !important;
right: 16px !important;
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
}
.ant-modal-close-x {
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
`}</style>
<div style={headerStyle}>
<div
style={{
@@ -765,7 +782,7 @@ function RoomManagement() {
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', paddingRight: '32px' }}>
<div
style={{
width: '4px',
@@ -784,7 +801,11 @@ function RoomManagement() {
destroyOnHidden
styles={{
body: { padding: '24px' },
header: { borderBottom: '1px solid #f0f0f0', padding: '16px 24px' },
header: {
borderBottom: '1px solid #f0f0f0',
padding: '16px 24px',
position: 'relative',
},
}}
style={{ borderRadius: '16px' }}
>
@@ -815,6 +815,24 @@ export const inputPlaceholders = {
// CSS-in-JS 样式字符串生成函数
export const generateGlobalStyles = tokens => `
/* Modal 关闭按钮通用修复 */
.ant-modal-close {
top: 16px !important;
right: 16px !important;
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
}
.ant-modal-close-x {
width: 32px !important;
height: 32px !important;
line-height: 32px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.device-modal .ant-modal-close {
top: 16px;
right: 24px;