feat(桌面): DHCP+静态IP
parent
c3c8f11939
commit
7ac0003942
|
@ -20,6 +20,7 @@ export default defineConfig({
|
||||||
"@assets": path.resolve(rootdir, "src/assets"),
|
"@assets": path.resolve(rootdir, "src/assets"),
|
||||||
"@components": path.resolve(rootdir, "src/components"),
|
"@components": path.resolve(rootdir, "src/components"),
|
||||||
"@utils": path.resolve(rootdir, "src/utils"),
|
"@utils": path.resolve(rootdir, "src/utils"),
|
||||||
|
"@types": path.resolve(rootdir, "src/types"),
|
||||||
},
|
},
|
||||||
// 路由配置
|
// 路由配置
|
||||||
routes: [
|
routes: [
|
||||||
|
@ -43,6 +44,10 @@ export default defineConfig({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: '@/pages/login',
|
component: '@/pages/login',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/configSteps',
|
||||||
|
component: '@/pages/configSteps',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: '/welcome',
|
redirect: '/welcome',
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default {
|
||||||
|
'POST /api/v1/sendMessage': (req,res)=>{
|
||||||
|
res.send({
|
||||||
|
code: 200,
|
||||||
|
data: '发送成功'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"antd": "^5.26.6",
|
"antd": "^5.26.6",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"umi": "^4.0.42"
|
"umi": "^4.0.42"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"antd": "^5.26.6",
|
"antd": "^5.26.6",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"umi": "^4.0.42"
|
"umi": "^4.0.42"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 287 B |
|
@ -1,4 +1,8 @@
|
||||||
import { ipcMain,app } from 'electron';
|
import { ipcMain,app } from 'electron';
|
||||||
|
import { getDeviceId, getWiredConnectionName,netmaskToCidr } from '../utils/utils';
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
const window = getBrowserWindowRuntime();
|
const window = getBrowserWindowRuntime();
|
||||||
|
|
||||||
|
@ -21,3 +25,66 @@ ipcMain.on('exit-kiosk', () => {
|
||||||
window.setFullScreen(false);
|
window.setFullScreen(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ipcMain.handle('get-deviceid',async()=>{
|
||||||
|
const deviceId = await getDeviceId();
|
||||||
|
console.log(`Using device ID: ${deviceId}`);
|
||||||
|
// TODO:传给后端
|
||||||
|
})
|
||||||
|
|
||||||
|
/* IPC 处理应用有线网络配置 */
|
||||||
|
ipcMain.handle('apply-wired-config',async(event,config)=>{
|
||||||
|
// return {
|
||||||
|
// success: true,
|
||||||
|
// message: '网络配置已成功应用'
|
||||||
|
// };
|
||||||
|
try{
|
||||||
|
console.log('应用网络配置:', config);
|
||||||
|
// 获取有线连接名称
|
||||||
|
const connectionName = await getWiredConnectionName();
|
||||||
|
console.log('有线连接名称:', connectionName);
|
||||||
|
|
||||||
|
if(config.method==='static'){
|
||||||
|
// 使用nmcli配置静态IP,需要使用sudo权限,一次性设置所有参数
|
||||||
|
let modifyCmd = `echo "unis@123" | sudo -S nmcli connection modify "${connectionName}" ipv4.method manual ipv4.addresses "${config.ipv4}/${netmaskToCidr(config.subnetMask)}" ipv4.gateway "${config.ipv4Gateway}"`;
|
||||||
|
const dnsServers = [config.primaryDns, config.secondaryDns].filter(Boolean).join(',');
|
||||||
|
modifyCmd += ` ipv4.dns "${dnsServers}"`;
|
||||||
|
|
||||||
|
// 添加 IPv6 配置(如果存在 ipv6Gateway)????ipv6和长度需要吗?ui只写了ipv6网关
|
||||||
|
// ipv6PrefixLength 是 IPv6 地址的前缀长度,类似于 IPv4 中的子网掩码。????
|
||||||
|
if (config.ipv6 && config.ipv6Gateway) {
|
||||||
|
modifyCmd += ` ipv6.method manual ipv6.addresses "${config.ipv6}/${config.ipv6PrefixLength || 64}" ipv6.gateway "${config.ipv6Gateway}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行配置命令
|
||||||
|
console.log('执行命令:', modifyCmd.replace('unis@123', '***'));
|
||||||
|
await execAsync(modifyCmd);
|
||||||
|
|
||||||
|
// 重新激活连接
|
||||||
|
await execAsync(`echo "unis@123" | sudo -S nmcli connection up "${connectionName}"`);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
// DHCP配置,一次性设置所有参数
|
||||||
|
const modifyCmd = `echo "unis@123" | sudo -S nmcli connection modify "${connectionName}" ipv4.method auto ipv4.addresses "" ipv4.gateway "" ipv4.dns ""`;
|
||||||
|
|
||||||
|
// 执行配置命令
|
||||||
|
console.log('执行命令:', modifyCmd.replace('unis@123', '***'));
|
||||||
|
await execAsync(modifyCmd);
|
||||||
|
|
||||||
|
// 重新激活连接
|
||||||
|
await execAsync(`echo "unis@123" | sudo -S nmcli connection up "${connectionName}"`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '网络配置已成功应用'
|
||||||
|
};
|
||||||
|
}catch(error:unknown){
|
||||||
|
console.error('应用网络配置失败:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `配置失败: ${error instanceof Error ? error.message : String(error || '未知错误')}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
|
@ -0,0 +1,102 @@
|
||||||
|
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const os = require('os');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
/** 获取设备ID(芯片序列号)
|
||||||
|
* TODO: 增加获取mac地址的逻辑,需要传给后端
|
||||||
|
*/
|
||||||
|
export async function getDeviceId() {
|
||||||
|
try {
|
||||||
|
// 尝试多种方法获取唯一的设备标识
|
||||||
|
const methods = [
|
||||||
|
// 方法1: CPU序列号
|
||||||
|
'cat /proc/cpuinfo | grep Serial | head -1 | awk \'{print $3}\'',
|
||||||
|
// 方法2: 机器ID
|
||||||
|
'cat /etc/machine-id 2>/dev/null || echo ""',
|
||||||
|
// 方法3: DMI产品UUID
|
||||||
|
'cat /sys/class/dmi/id/product_uuid 2>/dev/null || echo ""',
|
||||||
|
// 方法4: 主板序列号
|
||||||
|
'cat /sys/class/dmi/id/board_serial 2>/dev/null || echo ""'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const command of methods) {
|
||||||
|
try {
|
||||||
|
const { stdout } = await execAsync(command);
|
||||||
|
const deviceId = stdout.trim();
|
||||||
|
|
||||||
|
if (deviceId && deviceId !== '' && deviceId !== 'unknown' && deviceId !== '0000000000000000') {
|
||||||
|
console.log(`Device ID obtained using command: ${command}`);
|
||||||
|
console.log(`Device ID: ${deviceId}`);
|
||||||
|
return deviceId;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Method failed: ${command}, error: ${(error as Error).message}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有方法都失败,生成一个基于MAC地址的fallback ID
|
||||||
|
const networkInterfaces = os.networkInterfaces();
|
||||||
|
for (const interfaceName of Object.keys(networkInterfaces)) {
|
||||||
|
const interfaces = networkInterfaces[interfaceName];
|
||||||
|
for (const iface of interfaces) {
|
||||||
|
if (iface.mac && iface.mac !== '00:00:00:00:00:00') {
|
||||||
|
const fallbackId = iface.mac.replace(/:/g, '').toUpperCase();
|
||||||
|
console.log(`Using MAC address as fallback device ID: ${fallbackId}`);
|
||||||
|
return fallbackId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最后的fallback - 使用hostname
|
||||||
|
const hostname = os.hostname();
|
||||||
|
console.log(`Using hostname as final fallback device ID: ${hostname}`);
|
||||||
|
return hostname;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting device ID:', error);
|
||||||
|
// 返回一个默认的设备ID
|
||||||
|
return 'UNKNOWN_DEVICE';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取有线网络连接名称 */
|
||||||
|
export async function getWiredConnectionName() {
|
||||||
|
try {
|
||||||
|
// 首先尝试获取活动的有线连接
|
||||||
|
const { stdout: activeConn } = await execAsync('nmcli -t -f NAME,TYPE connection show --active | grep ethernet | head -1 | cut -d: -f1');
|
||||||
|
if (activeConn.trim()) {
|
||||||
|
return activeConn.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有活动连接,获取所有有线连接
|
||||||
|
const { stdout: allConn } = await execAsync('nmcli -t -f NAME,TYPE connection show | grep ethernet | head -1 | cut -d: -f1');
|
||||||
|
if (allConn.trim()) {
|
||||||
|
return allConn.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认连接名称
|
||||||
|
return 'Wired connection 1';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取有线连接名称失败:', error);
|
||||||
|
return 'Wired connection 1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 子网掩码转CIDR */
|
||||||
|
export function netmaskToCidr(netmask:string) {
|
||||||
|
const netmaskMap: { [key: string]: string } = {
|
||||||
|
'255.255.255.0': '24',
|
||||||
|
'255.255.0.0': '16',
|
||||||
|
'255.0.0.0': '8',
|
||||||
|
'255.255.255.128': '25',
|
||||||
|
'255.255.255.192': '26',
|
||||||
|
'255.255.255.224': '27',
|
||||||
|
'255.255.255.240': '28',
|
||||||
|
'255.255.255.248': '29',
|
||||||
|
'255.255.255.252': '30'
|
||||||
|
};
|
||||||
|
return netmaskMap[netmask] || '24';
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 15px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
gap: 40px;
|
||||||
|
|
||||||
|
.cancel-button {
|
||||||
|
width: 140px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 32px;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(134, 133, 158, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirm-button {
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 32px;
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 30px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 8px;
|
||||||
|
background-image: url('../../../assets/stepIcon.png');
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
interface ButtonComProps {
|
||||||
|
// 取消按钮配置
|
||||||
|
cancelText?: string;
|
||||||
|
onCancel?: () => void;
|
||||||
|
showCancel?: boolean;
|
||||||
|
|
||||||
|
// 确认按钮配置
|
||||||
|
confirmText?: string;
|
||||||
|
onConfirm?: () => void;
|
||||||
|
showConfirm?: boolean;
|
||||||
|
|
||||||
|
// 样式类名
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ButtonCom: React.FC<ButtonComProps> = ({
|
||||||
|
cancelText = '取消',
|
||||||
|
onCancel,
|
||||||
|
showCancel = true,
|
||||||
|
confirmText = '确认',
|
||||||
|
onConfirm,
|
||||||
|
showConfirm = true,
|
||||||
|
className
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={classNames(styles["button-container"], className)}>
|
||||||
|
{showCancel && (
|
||||||
|
<button
|
||||||
|
className={styles["cancel-button"]}
|
||||||
|
onClick={onCancel}
|
||||||
|
>
|
||||||
|
<span>{cancelText}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showConfirm && (
|
||||||
|
<button
|
||||||
|
className={styles["confirm-button"]}
|
||||||
|
onClick={onConfirm}
|
||||||
|
>
|
||||||
|
<span>{confirmText}</span>
|
||||||
|
<div className={styles["arrow-icon"]}></div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ButtonCom;
|
|
@ -1,9 +1,12 @@
|
||||||
.main-layout {
|
.main-layout {
|
||||||
min-height: 100vh;
|
background-color: rgba(0, 9, 51, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
min-height: 100vh;
|
// background-size: 100% 100%;
|
||||||
|
background-color: rgba(0, 9, 51, 0.9);
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,19 @@ const MainLayout: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: 第一次来:判断是否配置ip/DHCP、服务ip绑定终端 绑定:直接到版本更新页面 未绑定:到配置ip/DHCP页面
|
// TODO: 第一次来:判断是否配置ip/DHCP、服务ip绑定终端 绑定:直接到版本更新页面 未绑定:到配置ip/DHCP页面
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
history.push('/login');
|
// history.push('/configSteps');
|
||||||
},9000)
|
// },1000)
|
||||||
|
// const fetchDeviceId = async () => {
|
||||||
|
// try {
|
||||||
|
// const res = await window.electronAPI.invoke('get-deviceid');
|
||||||
|
// console.log('获取设备ID:', res);
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error('获取设备ID失败:', error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// fetchDeviceId()
|
||||||
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleMenuClick = (key: string) => {
|
const handleMenuClick = (key: string) => {
|
||||||
|
@ -43,11 +53,11 @@ const MainLayout: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout className="main-layout">
|
<div className="main-layout">
|
||||||
<Content className="main-content">
|
<div className="main-content">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Content>
|
</div>
|
||||||
</Layout>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
.network-config {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.tab-container {
|
||||||
|
height: 70px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: linear-gradient(180deg, rgba(229, 229, 229, 0.05) 0%, rgba(229, 229, 229, 0.05) 100%);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
top: -5px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 30px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
padding: 0 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
color: rgba(229, 229, 229, 0.5);
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -15px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: rgba(229, 229, 229, 1);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dhcp-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
font-size: 24px;
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-size: 18px;
|
||||||
|
color: rgba(229, 229, 229, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.static-ip-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px 0px;
|
||||||
|
padding-right: 60px;
|
||||||
|
|
||||||
|
// 滚动条样式
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 28px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 28px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单项样式
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-label {
|
||||||
|
padding: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
width: 100%;
|
||||||
|
height: 56px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 28px;
|
||||||
|
border: none;
|
||||||
|
padding: 0 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(229, 229, 229, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 覆盖 Ant Design 的默认样式
|
||||||
|
.ant-input {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 28px;
|
||||||
|
border: none;
|
||||||
|
padding: 0 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: PingFang SC;
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Heavy;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 32px;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: rgba(229, 229, 229, 1);
|
||||||
|
height: 56px;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(229, 229, 229, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import styles from './index.less';
|
||||||
|
import cs from 'classnames';
|
||||||
|
import { Form, Input, message } from 'antd';
|
||||||
|
import ButtonCom from '../../../components/ButtonCom';
|
||||||
|
|
||||||
|
const staticIpFormFields: CONFIG_STEPS.StaticFormFieldConfig[] = [
|
||||||
|
{
|
||||||
|
name: "ipv4",
|
||||||
|
label: "IPv4",
|
||||||
|
type: "input",
|
||||||
|
placeholder: "请输入",
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入IPv4地址' },
|
||||||
|
{
|
||||||
|
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||||
|
message: '请输入正确的IPv4地址格式'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subnetMask",
|
||||||
|
label: "子网掩码",
|
||||||
|
type: "input",
|
||||||
|
placeholder: "请输入",
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入子网掩码' },
|
||||||
|
{
|
||||||
|
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||||
|
message: '请输入正确的子网掩码格式'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipv4Gateway",
|
||||||
|
label: "IPv4网关",
|
||||||
|
type: "input",
|
||||||
|
placeholder: "请输入",
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入IPv4网关' },
|
||||||
|
{
|
||||||
|
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||||
|
message: '请输入正确的IPv4网关格式'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ipv6Gateway",
|
||||||
|
label: "IPv6网关",
|
||||||
|
type: "input",
|
||||||
|
placeholder: "请输入",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
pattern: /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:)*::([0-9a-fA-F]{1,4}:)*[0-9a-fA-F]{1,4}$/,
|
||||||
|
message: '请输入正确的IPv6地址格式'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "primaryDns",
|
||||||
|
label: "首选DNS",
|
||||||
|
type: "input",
|
||||||
|
placeholder: "请输入",
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入首选DNS' },
|
||||||
|
{
|
||||||
|
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||||
|
message: '请输入正确的DNS地址格式'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "secondaryDns",
|
||||||
|
label: "备用DNS",
|
||||||
|
type: "input",
|
||||||
|
placeholder: "请输入",
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||||
|
message: '请输入正确的DNS地址格式'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const NetworkConfig: React.FC = () => {
|
||||||
|
const [activeTab, setActiveTab] = useState<'dhcp' | 'static'>('dhcp');
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (activeTab === 'dhcp') {
|
||||||
|
// 如果是 DHCP 模式,直接IPC 处理应用有线网络配置
|
||||||
|
try {
|
||||||
|
const res = await window.electronAPI.invoke('apply-wired-config',{ method: 'dhcp' });
|
||||||
|
console.log('网络配置返回信息成功:', res);
|
||||||
|
if(res.success){
|
||||||
|
message.success('网络配置成功');
|
||||||
|
}else{
|
||||||
|
message.error(res.message || '网络配置失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('网络配置返回信息失败:', error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果是静态IP模式,进行表单校验,再IPC 处理应用有线网络配置
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
console.log('表单提交数据:', values);
|
||||||
|
const res = await window.electronAPI.invoke('apply-wired-config',{ method: 'static', ...values });
|
||||||
|
console.log('网络配置返回信息成功:', res);
|
||||||
|
if(res.success){
|
||||||
|
message.success('网络配置成功');
|
||||||
|
}else{
|
||||||
|
message.error(res.message || '网络配置失败');
|
||||||
|
}
|
||||||
|
} catch (errorInfo) {
|
||||||
|
console.log('网络配置返回信息失败:', errorInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: 处理网络配置成功后的逻辑,跳转到下一个步骤或页面
|
||||||
|
};
|
||||||
|
|
||||||
|
const DhcpComponent = () => (
|
||||||
|
<div className={styles["dhcp-content"]}>
|
||||||
|
<h2>DHCP 配置</h2>
|
||||||
|
<p>正在使用 DHCP 自动获取网络配置</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const StaticIpComponent = () => (
|
||||||
|
<div className={styles["static-ip-container"]}>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
requiredMark={false}
|
||||||
|
className={styles["form-container"]}
|
||||||
|
>
|
||||||
|
{staticIpFormFields.map(field => (
|
||||||
|
<Form.Item
|
||||||
|
key={field.name}
|
||||||
|
name={field.name}
|
||||||
|
label={<div className={styles["label"]}>{field.label}</div>}
|
||||||
|
rules={field.rules}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
className={styles["input-field"]}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles["network-config"]}>
|
||||||
|
<div className={styles["tab-container"]}>
|
||||||
|
<div
|
||||||
|
className={cs(styles["tab-item"], { [styles.active]: activeTab === 'dhcp' })}
|
||||||
|
onClick={() => setActiveTab('dhcp')}
|
||||||
|
>
|
||||||
|
<span>DHCP</span>
|
||||||
|
{activeTab === 'dhcp' && <div className={styles["indicator"]}></div>}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cs(styles["tab-item"], { [styles.active]: activeTab === 'static' })}
|
||||||
|
onClick={() => setActiveTab('static')}
|
||||||
|
>
|
||||||
|
<span>静态IP</span>
|
||||||
|
{activeTab === 'static' && <div className={styles["indicator"]}></div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles["content-container"]}>
|
||||||
|
{activeTab === 'dhcp' ? <DhcpComponent /> : <StaticIpComponent />}
|
||||||
|
</div>
|
||||||
|
<ButtonCom confirmText="确认并进入下一步" onConfirm={handleSubmit}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NetworkConfig;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Index = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
终端获取镜像信息
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Index = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
侦测管理平台
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -0,0 +1,70 @@
|
||||||
|
.config-step-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding-top: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.tabs-container {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
// background: rgba(229, 229, 229, 0.2);
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.tab-label {
|
||||||
|
font-size: 22px;
|
||||||
|
color: rgba(255, 255, 255, 0.6); // 未激活tab文字透明
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-indicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
background: rgba(229, 229, 229, 0.2);
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.tab-label {
|
||||||
|
color: white; // 激活tab文字为白色
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-indicator {
|
||||||
|
background: white; // 激活tab指示器为白色
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyBox{
|
||||||
|
height: 60px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// src/pages/configSteps/index.tsx
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import styles from './index.less';
|
||||||
|
import cs from 'classnames';
|
||||||
|
import NetworkConfig from './components/networkConfig';
|
||||||
|
import WatchManagement from './components/watchManagement';
|
||||||
|
import TerminalGetImage from './components/terminalGetImage';
|
||||||
|
|
||||||
|
const Index: React.FC = () => {
|
||||||
|
const [activeTab, setActiveTab] = useState<string>("networkConfig");
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ key: "networkConfig", label: '平台网络配置', component: <NetworkConfig /> },
|
||||||
|
{ key: "watchManagement", label: '侦测管理平台', component: <WatchManagement /> },
|
||||||
|
{ key: "terminalGetImage", label: '终端获取镜像信息', component: <TerminalGetImage /> },
|
||||||
|
];
|
||||||
|
|
||||||
|
const activeTabItem = tabs.find(tab => tab.key === activeTab);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles["config-step-container"]}>
|
||||||
|
<div className={styles["tabs-container"]}>
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<div
|
||||||
|
key={tab.key}
|
||||||
|
className={cs(styles["tab-item"], {
|
||||||
|
[styles.active]: activeTab === tab.key
|
||||||
|
})}
|
||||||
|
onClick={() => setActiveTab(tab.key)}
|
||||||
|
>
|
||||||
|
<span className={styles["tab-label"]}>{tab.label}</span>
|
||||||
|
<div className={styles["tab-indicator"]} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles["tab-content-container"]}>
|
||||||
|
{activeTabItem?.component || <div>未找到对应内容</div>}
|
||||||
|
</div>
|
||||||
|
<div className={styles.emptyBox}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Index;
|
|
@ -1,8 +1,6 @@
|
||||||
.welcomeCon{
|
.welcomeCon{
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
background-color: rgba(0, 9, 51, 1);
|
|
||||||
background-size: 100% 100%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import axios from '@/utils/axios';
|
import axios from '@utils/axios';
|
||||||
|
|
||||||
export const BASE_URL = '/api/v1/test';
|
export const BASE_URL = '/api/v1/test';
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
declare namespace CONFIG_STEPS {
|
||||||
|
interface StaticFormFieldConfig {
|
||||||
|
name: string;
|
||||||
|
label: string;
|
||||||
|
type: 'input' | 'select'; // 可扩展更多类型
|
||||||
|
rules?: any[];
|
||||||
|
placeholder?: string;
|
||||||
|
options?: { label: string; value: string }[]; // select 专用
|
||||||
|
required?: boolean;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"extends": "./src/.umi/tsconfig.json"
|
"extends": "./src/.umi/tsconfig.json",
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue