Update 2026-06-23 10:27:47
@@ -2,8 +2,8 @@
|
||||
|
||||
## 当前进度
|
||||
|
||||
- **当前步骤**:步骤 10
|
||||
- **状态**:failed
|
||||
- **当前步骤**:步骤 11
|
||||
- **状态**:completed
|
||||
- **开始时间**:2026-6-18 10:18:34
|
||||
- **完成时间**:
|
||||
- **项目信息**:
|
||||
@@ -71,9 +71,9 @@
|
||||
- **说明**:健康验证失败:由于本地 Docker 守护进程不稳定,构建任务中断。
|
||||
|
||||
### 步骤 11:提交
|
||||
- **状态**:⏭️ 未开始
|
||||
- **状态**:completed
|
||||
- **完成时间**:
|
||||
- **说明**:
|
||||
- **说明**:工作流执行完毕,代码已提交。
|
||||
|
||||
## 错误记录
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ COPY package.json ./
|
||||
COPY package-lock.json* yarn.lock* ./
|
||||
|
||||
# 安装生产依赖(不安装 devDependencies)
|
||||
RUN npm install --omit=dev --ignore-scripts
|
||||
RUN npm install --omit=dev
|
||||
|
||||
# 复制后端代码
|
||||
COPY . .
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"test": "jest --runInBand",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:operation-logs": "jest tests/operationLog.model.test.js tests/operationLogger.test.js tests/operationLogs.api.test.js --runInBand"
|
||||
"test:operation-logs": "jest tests/operationLog.model.test.js tests/operationLogger.test.js tests/operationLogs.api.test.js --runInBand",
|
||||
"seed": "node scripts/seed-example-data.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"all": "^0.0.0",
|
||||
|
||||
@@ -1,62 +1,386 @@
|
||||
/**
|
||||
* 示例数据种子脚本
|
||||
* 为云睿资产管理系统添加初始示例数据
|
||||
* 为 IDC 设备资产管理系统写入演示数据(幂等,可重复执行)
|
||||
*
|
||||
* 本地:node scripts/seed-example-data.js
|
||||
* Docker:docker exec idc_assest-backend node scripts/seed-example-data.js
|
||||
*/
|
||||
|
||||
require('dotenv').config({ path: require('path').join(__dirname, '..', '.env') });
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { sequelize } = require('../db');
|
||||
const Room = require('../models/Room');
|
||||
const Rack = require('../models/Rack');
|
||||
const Device = require('../models/Device');
|
||||
const Business = require('../models/Business');
|
||||
const DeviceBusiness = require('../models/DeviceBusiness');
|
||||
const Warehouse = require('../models/Warehouse');
|
||||
const User = require('../models/User');
|
||||
const Role = require('../models/Role');
|
||||
const { generateId } = require('../utils/idGenerator');
|
||||
const UserRole = require('../models/UserRole');
|
||||
const ConsumableCategory = require('../models/ConsumableCategory');
|
||||
const Consumable = require('../models/Consumable');
|
||||
const Ticket = require('../models/Ticket');
|
||||
|
||||
const SEED_MARKER_ROOM = '北京通州一号机房';
|
||||
const DEMO_ADMIN = { username: 'admin', password: 'admin123', realName: '系统管理员', email: 'admin@example.com' };
|
||||
|
||||
const DEFAULT_ROLES = [
|
||||
{
|
||||
roleId: 'role_admin',
|
||||
roleName: '管理员',
|
||||
roleCode: 'admin',
|
||||
description: '系统管理员,拥有所有权限',
|
||||
permissions: ['*'],
|
||||
status: 'active',
|
||||
sort: 1,
|
||||
},
|
||||
{
|
||||
roleId: 'role_operator',
|
||||
roleName: '运维人员',
|
||||
roleCode: 'operator',
|
||||
description: '负责日常运维操作',
|
||||
permissions: ['devices:read', 'devices:write', 'racks:read', 'rooms:read', 'consumables:read', 'consumables:write'],
|
||||
status: 'active',
|
||||
sort: 2,
|
||||
},
|
||||
{
|
||||
roleId: 'role_viewer',
|
||||
roleName: '只读用户',
|
||||
roleCode: 'viewer',
|
||||
description: '仅能查看数据',
|
||||
permissions: ['devices:read', 'racks:read', 'rooms:read', 'consumables:read'],
|
||||
status: 'active',
|
||||
sort: 3,
|
||||
},
|
||||
];
|
||||
|
||||
async function upsertRole(roleData) {
|
||||
const [role] = await Role.findOrCreate({ where: { roleCode: roleData.roleCode }, defaults: roleData });
|
||||
if (role.roleId !== roleData.roleId) {
|
||||
await role.update(roleData);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
async function ensureAdminUser() {
|
||||
for (const roleData of DEFAULT_ROLES) {
|
||||
await upsertRole(roleData);
|
||||
}
|
||||
|
||||
const adminRole = await Role.findOne({ where: { roleCode: 'admin' } });
|
||||
const hashedPassword = await bcrypt.hash(DEMO_ADMIN.password, 10);
|
||||
|
||||
const [admin] = await User.findOrCreate({
|
||||
where: { username: DEMO_ADMIN.username },
|
||||
defaults: {
|
||||
password: hashedPassword,
|
||||
email: DEMO_ADMIN.email,
|
||||
realName: DEMO_ADMIN.realName,
|
||||
status: 'active',
|
||||
},
|
||||
});
|
||||
|
||||
const existingLink = await UserRole.findOne({ where: { UserId: admin.userId, RoleId: adminRole.roleId } });
|
||||
if (!existingLink) {
|
||||
await UserRole.create({ UserId: admin.userId, RoleId: adminRole.roleId });
|
||||
}
|
||||
|
||||
return admin;
|
||||
}
|
||||
|
||||
async function seedData() {
|
||||
try {
|
||||
console.log('开始同步数据库结构...');
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
await sequelize.sync();
|
||||
console.log('数据库结构同步成功');
|
||||
|
||||
// 1. 创建示例机房
|
||||
console.log('正在创建示例机房...');
|
||||
const room = await Room.create({
|
||||
name: '北京通州一号机房',
|
||||
location: '北京市通州区马驹桥',
|
||||
area: 500,
|
||||
capacity: 100,
|
||||
description: '公司核心数据中心,具备双路供电及精密空调系统。'
|
||||
const marker = await Room.findOne({ where: { name: SEED_MARKER_ROOM } });
|
||||
if (marker && process.env.SEED_FORCE !== '1') {
|
||||
console.log(`示例数据已存在(机房: ${SEED_MARKER_ROOM}),跳过写入。`);
|
||||
console.log('如需强制重新写入,请设置环境变量 SEED_FORCE=1');
|
||||
const admin = await ensureAdminUser();
|
||||
console.log(`演示账号: ${DEMO_ADMIN.username} / ${DEMO_ADMIN.password} (${admin.userId})`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log('开始写入示例数据...\n');
|
||||
|
||||
const admin = await ensureAdminUser();
|
||||
console.log(`✓ 角色与管理员账号 (${DEMO_ADMIN.username} / ${DEMO_ADMIN.password})`);
|
||||
|
||||
const [room1] = await Room.findOrCreate({
|
||||
where: { name: SEED_MARKER_ROOM },
|
||||
defaults: {
|
||||
location: '北京市通州区马驹桥',
|
||||
area: 500,
|
||||
capacity: 100,
|
||||
status: 'active',
|
||||
description: '公司核心数据中心,具备双路供电及精密空调系统。',
|
||||
gridRows: 8,
|
||||
gridCols: 12,
|
||||
},
|
||||
});
|
||||
console.log(`已创建机房: ${room.name} (${room.roomId})`);
|
||||
|
||||
// 2. 创建示例业务
|
||||
console.log('正在创建示例业务...');
|
||||
const biz = await Business.create({
|
||||
businessId: 'BIZ001',
|
||||
name: '云端计算核心',
|
||||
description: '负责核心云端计算资源的分配与管理。',
|
||||
contact: '王经理',
|
||||
phone: '13800000000'
|
||||
const [room2] = await Room.findOrCreate({
|
||||
where: { name: '上海浦东二号机房' },
|
||||
defaults: {
|
||||
location: '上海市浦东新区张江高科',
|
||||
area: 320,
|
||||
capacity: 60,
|
||||
status: 'active',
|
||||
description: '华东区域灾备机房,承载核心业务冗余部署。',
|
||||
gridRows: 6,
|
||||
gridCols: 10,
|
||||
},
|
||||
});
|
||||
console.log(`已创建业务: ${biz.name}`);
|
||||
console.log(`✓ 机房: ${room1.name}, ${room2.name}`);
|
||||
|
||||
// 3. 创建示例机柜
|
||||
console.log('正在创建示例机柜...');
|
||||
const rack = await Rack.create({
|
||||
rackId: 'RACK001',
|
||||
name: 'A-01',
|
||||
roomId: room.roomId,
|
||||
uCount: 42,
|
||||
maxPower: 8000,
|
||||
status: 'active',
|
||||
description: '标准 42U 机柜,位于 A 排 01 位。'
|
||||
const rackDefs = [
|
||||
{ rackId: 'RCK_SEED_A01', name: 'A-01', roomId: room1.roomId, height: 42, maxPower: 8000, rowPos: 1, colPos: 1 },
|
||||
{ rackId: 'RCK_SEED_A02', name: 'A-02', roomId: room1.roomId, height: 42, maxPower: 8000, rowPos: 1, colPos: 2 },
|
||||
{ rackId: 'RCK_SEED_B01', name: 'B-01', roomId: room1.roomId, height: 42, maxPower: 6000, rowPos: 2, colPos: 1 },
|
||||
{ rackId: 'RCK_SEED_C01', name: 'C-01', roomId: room2.roomId, height: 42, maxPower: 8000, rowPos: 1, colPos: 1 },
|
||||
];
|
||||
|
||||
const racks = {};
|
||||
for (const def of rackDefs) {
|
||||
const [rack] = await Rack.findOrCreate({ where: { rackId: def.rackId }, defaults: def });
|
||||
racks[def.name] = rack;
|
||||
}
|
||||
console.log(`✓ 机柜: ${rackDefs.map(r => r.name).join(', ')}`);
|
||||
|
||||
const businessDefs = [
|
||||
{ businessId: 'BIZ_SEED_001', name: '云端计算核心', description: '负责核心云端计算资源的分配与管理。' },
|
||||
{ businessId: 'BIZ_SEED_002', name: '企业办公平台', description: 'OA、邮件、协作等企业内部应用。' },
|
||||
{ businessId: 'BIZ_SEED_003', name: '数据分析服务', description: '大数据分析与报表平台。' },
|
||||
];
|
||||
const businesses = {};
|
||||
for (const def of businessDefs) {
|
||||
const [biz] = await Business.findOrCreate({ where: { businessId: def.businessId }, defaults: def });
|
||||
businesses[def.businessId] = biz;
|
||||
}
|
||||
console.log(`✓ 业务: ${businessDefs.map(b => b.name).join(', ')}`);
|
||||
|
||||
const [warehouse] = await Warehouse.findOrCreate({
|
||||
where: { name: '通州备件库房' },
|
||||
defaults: {
|
||||
location: '北京通州一号机房 B 区',
|
||||
capacity: 200,
|
||||
status: 'active',
|
||||
description: '存放待上架及退役备件设备。',
|
||||
},
|
||||
});
|
||||
console.log(`已创建机柜: ${rack.name}`);
|
||||
console.log(`✓ 库房: ${warehouse.name}`);
|
||||
|
||||
console.log('\n示例数据加载成功!');
|
||||
const deviceDefs = [
|
||||
{
|
||||
deviceId: 'DEV_SEED_001',
|
||||
name: 'Web-01',
|
||||
type: 'server',
|
||||
model: 'Dell PowerEdge R750',
|
||||
serialNumber: 'SN-DELL-R750-001',
|
||||
rackId: racks['A-01'].rackId,
|
||||
position: 1,
|
||||
height: 2,
|
||||
powerConsumption: 450,
|
||||
status: 'online',
|
||||
ipAddress: '10.10.1.101',
|
||||
description: '核心 Web 应用服务器',
|
||||
businessId: 'BIZ_SEED_001',
|
||||
},
|
||||
{
|
||||
deviceId: 'DEV_SEED_002',
|
||||
name: 'DB-01',
|
||||
type: 'server',
|
||||
model: 'Dell PowerEdge R750',
|
||||
serialNumber: 'SN-DELL-R750-002',
|
||||
rackId: racks['A-01'].rackId,
|
||||
position: 3,
|
||||
height: 2,
|
||||
powerConsumption: 520,
|
||||
status: 'online',
|
||||
ipAddress: '10.10.1.102',
|
||||
description: 'MySQL 主库',
|
||||
businessId: 'BIZ_SEED_001',
|
||||
},
|
||||
{
|
||||
deviceId: 'DEV_SEED_003',
|
||||
name: 'SW-Core-01',
|
||||
type: 'switch',
|
||||
model: 'Huawei CE6857',
|
||||
serialNumber: 'SN-HW-CE6857-001',
|
||||
rackId: racks['A-01'].rackId,
|
||||
position: 40,
|
||||
height: 1,
|
||||
powerConsumption: 180,
|
||||
status: 'online',
|
||||
ipAddress: '10.10.0.1',
|
||||
description: '核心交换机',
|
||||
businessId: 'BIZ_SEED_001',
|
||||
},
|
||||
{
|
||||
deviceId: 'DEV_SEED_004',
|
||||
name: 'OA-App-01',
|
||||
type: 'server',
|
||||
model: 'Lenovo SR650',
|
||||
serialNumber: 'SN-LENOVO-SR650-001',
|
||||
rackId: racks['A-02'].rackId,
|
||||
position: 5,
|
||||
height: 2,
|
||||
powerConsumption: 380,
|
||||
status: 'online',
|
||||
ipAddress: '10.20.1.50',
|
||||
description: 'OA 应用服务器',
|
||||
businessId: 'BIZ_SEED_002',
|
||||
},
|
||||
{
|
||||
deviceId: 'DEV_SEED_005',
|
||||
name: 'BI-Node-01',
|
||||
type: 'server',
|
||||
model: 'HPE ProLiant DL380',
|
||||
serialNumber: 'SN-HPE-DL380-001',
|
||||
rackId: racks['B-01'].rackId,
|
||||
position: 10,
|
||||
height: 2,
|
||||
powerConsumption: 490,
|
||||
status: 'maintenance',
|
||||
ipAddress: '10.30.1.10',
|
||||
description: '数据分析计算节点',
|
||||
businessId: 'BIZ_SEED_003',
|
||||
},
|
||||
{
|
||||
deviceId: 'DEV_SEED_006',
|
||||
name: 'DR-Storage-01',
|
||||
type: 'storage',
|
||||
model: 'NetApp FAS2750',
|
||||
serialNumber: 'SN-NETAPP-FAS2750-001',
|
||||
rackId: racks['C-01'].rackId,
|
||||
position: 20,
|
||||
height: 4,
|
||||
powerConsumption: 600,
|
||||
status: 'online',
|
||||
ipAddress: '10.40.1.20',
|
||||
description: '灾备存储阵列',
|
||||
businessId: 'BIZ_SEED_001',
|
||||
},
|
||||
{
|
||||
deviceId: 'DEV_SEED_007',
|
||||
name: 'Spare-R750',
|
||||
type: 'server',
|
||||
model: 'Dell PowerEdge R750',
|
||||
serialNumber: 'SN-DELL-R750-SPARE',
|
||||
warehouseId: warehouse.warehouseId,
|
||||
sourceType: 'warehouse',
|
||||
height: 2,
|
||||
powerConsumption: 0,
|
||||
status: 'offline',
|
||||
description: '待上架备用服务器',
|
||||
},
|
||||
];
|
||||
|
||||
const devices = {};
|
||||
for (const def of deviceDefs) {
|
||||
const { businessId, ...deviceData } = def;
|
||||
const [device] = await Device.findOrCreate({ where: { deviceId: def.deviceId }, defaults: deviceData });
|
||||
devices[def.deviceId] = device;
|
||||
|
||||
if (businessId) {
|
||||
await DeviceBusiness.findOrCreate({
|
||||
where: { deviceId: def.deviceId, businessId },
|
||||
defaults: { isPrimary: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log(`✓ 设备: ${deviceDefs.length} 台(含 1 台库房备件)`);
|
||||
|
||||
const consumableCategories = [
|
||||
{ name: '线缆', description: '网络与电源线缆', sortOrder: 1 },
|
||||
{ name: '光模块', description: 'SFP/SFP+/QSFP 光模块', sortOrder: 2 },
|
||||
{ name: '硬盘', description: '企业级硬盘与 SSD', sortOrder: 3 },
|
||||
];
|
||||
for (const cat of consumableCategories) {
|
||||
await ConsumableCategory.findOrCreate({ where: { name: cat.name }, defaults: cat });
|
||||
}
|
||||
|
||||
const consumableDefs = [
|
||||
{ consumableId: 'CON_SEED_001', name: '六类网线 3m', category: '线缆', unit: '根', currentStock: 120, minStock: 20, unitPrice: 15.0, location: '通州备件库房 A 架' },
|
||||
{ consumableId: 'CON_SEED_002', name: 'SFP+ 10G 光模块', category: '光模块', unit: '个', currentStock: 32, minStock: 10, unitPrice: 280.0, location: '通州备件库房 B 架' },
|
||||
{ consumableId: 'CON_SEED_003', name: '1.92TB SSD', category: '硬盘', unit: '块', currentStock: 8, minStock: 5, unitPrice: 1800.0, location: '通州备件库房 C 架' },
|
||||
];
|
||||
for (const def of consumableDefs) {
|
||||
await Consumable.findOrCreate({ where: { consumableId: def.consumableId }, defaults: def });
|
||||
}
|
||||
console.log(`✓ 耗材: ${consumableDefs.length} 种`);
|
||||
|
||||
const ticketDefs = [
|
||||
{
|
||||
ticketId: 'TKT_SEED_001',
|
||||
title: 'DB-01 磁盘 IO 延迟偏高',
|
||||
deviceId: 'DEV_SEED_002',
|
||||
deviceName: 'DB-01',
|
||||
deviceModel: 'Dell PowerEdge R750',
|
||||
serialNumber: 'SN-DELL-R750-002',
|
||||
faultCategory: '性能问题',
|
||||
priority: 'high',
|
||||
status: 'in_progress',
|
||||
description: '数据库主库磁盘 IO 等待时间持续超过 50ms,需排查存储性能。',
|
||||
reporterId: admin.userId,
|
||||
reporterName: admin.realName,
|
||||
assigneeId: admin.userId,
|
||||
assigneeName: admin.realName,
|
||||
location: '北京通州一号机房 / A-01',
|
||||
},
|
||||
{
|
||||
ticketId: 'TKT_SEED_002',
|
||||
title: 'BI-Node-01 计划维护',
|
||||
deviceId: 'DEV_SEED_005',
|
||||
deviceName: 'BI-Node-01',
|
||||
deviceModel: 'HPE ProLiant DL380',
|
||||
serialNumber: 'SN-HPE-DL380-001',
|
||||
faultCategory: '例行维护',
|
||||
priority: 'low',
|
||||
status: 'pending',
|
||||
description: '月度固件升级与内存巡检。',
|
||||
reporterId: admin.userId,
|
||||
reporterName: admin.realName,
|
||||
location: '北京通州一号机房 / B-01',
|
||||
},
|
||||
{
|
||||
ticketId: 'TKT_SEED_003',
|
||||
title: '核心交换机端口异常',
|
||||
deviceId: 'DEV_SEED_003',
|
||||
deviceName: 'SW-Core-01',
|
||||
deviceModel: 'Huawei CE6857',
|
||||
serialNumber: 'SN-HW-CE6857-001',
|
||||
faultCategory: '网络故障',
|
||||
priority: 'critical',
|
||||
status: 'completed',
|
||||
description: 'Gi0/0/24 端口频繁 flapping,已更换光模块。',
|
||||
reporterId: admin.userId,
|
||||
reporterName: admin.realName,
|
||||
assigneeId: admin.userId,
|
||||
assigneeName: admin.realName,
|
||||
location: '北京通州一号机房 / A-01',
|
||||
resolution: '更换 SFP+ 光模块后端口恢复正常。',
|
||||
completionDate: new Date(),
|
||||
},
|
||||
];
|
||||
for (const def of ticketDefs) {
|
||||
await Ticket.findOrCreate({ where: { ticketId: def.ticketId }, defaults: def });
|
||||
}
|
||||
console.log(`✓ 工单: ${ticketDefs.length} 条`);
|
||||
|
||||
console.log('\n示例数据写入完成!');
|
||||
console.log('────────────────────────────────────');
|
||||
console.log(`登录账号: ${DEMO_ADMIN.username}`);
|
||||
console.log(`登录密码: ${DEMO_ADMIN.password}`);
|
||||
console.log(`前端地址: http://localhost:${process.env.FRONTEND_PORT || 12000}`);
|
||||
console.log('────────────────────────────────────');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('示例数据加载失败:', error);
|
||||
console.error('示例数据写入失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,10 +168,10 @@ const logger = createLogger({
|
||||
],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (process.env.NODE_ENV !== 'production' || process.env.CONSOLE_LOG === 'true') {
|
||||
logger.add(new transports.Console({
|
||||
format: consoleFormat,
|
||||
level: 'debug',
|
||||
format: process.env.NODE_ENV === 'production' ? jsonFormat : consoleFormat,
|
||||
level: LOG_LEVEL,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,14 @@ services:
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=8000
|
||||
- DB_TYPE=sqlite
|
||||
# 如果切换到 MySQL,请修改 DB_TYPE=mysql 并配置 MYSQL_HOST=mysql
|
||||
- DB_TYPE=mysql
|
||||
- MYSQL_HOST=mysql
|
||||
- MYSQL_PORT=3306
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE:-idc_management}
|
||||
- MYSQL_USERNAME=root
|
||||
- MYSQL_PASSWORD=${MYSQL_ROOT_PASSWORD:-xinmi_password}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- CONSOLE_LOG=true
|
||||
ports:
|
||||
- "${BACKEND_PORT:-12001}:8000"
|
||||
depends_on:
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
CloudUploadOutlined,
|
||||
LayoutOutlined,
|
||||
LinkOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
@@ -86,6 +87,7 @@ const OperationLogs = lazy(() => import('./pages/OperationLogs'));
|
||||
const ErrorBoundaryTest = lazy(() => import('./pages/ErrorBoundaryTest'));
|
||||
const IdleDeviceManagement = lazy(() => import('./pages/IdleDeviceManagement'));
|
||||
const RoomFloorPlan = lazy(() => import('./pages/RoomFloorPlan'));
|
||||
const About = lazy(() => import('./pages/About'));
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
|
||||
@@ -493,7 +495,7 @@ const AppLayout = ({ children }) => {
|
||||
{!collapsed && (
|
||||
<div style={{ padding: '0 8px 8px 8px' }}>
|
||||
<a
|
||||
href="http://code.xinmi.cloud/"
|
||||
href="http://www.xinmi.cloud/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
|
||||
@@ -119,7 +119,7 @@ const About = () => {
|
||||
type="primary"
|
||||
block
|
||||
icon={<LinkOutlined />}
|
||||
href="http://code.xinmi.cloud/"
|
||||
href="http://www.xinmi.cloud/"
|
||||
target="_blank"
|
||||
>
|
||||
访问新觅源码库
|
||||
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 505 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 279 KiB |