feat(network): 添加网络接口信息获取功能

- 新增 network-interface 依赖用于获取系统网络接口- 实现 Rust 端 get_network_interfaces 命令- 在 Svelte 前端调用 Tauri 命令并展示网络接口信息
- 添加接口信息表格展示及错误处理
- 更新布局结构移除多余注释和空行
This commit is contained in:
Chaos
2025-11-10 16:50:26 +08:00
parent cacb25a2ca
commit 514c025caf
11 changed files with 169 additions and 14 deletions

8
src-tauri/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
src-tauri/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/src-tauri.iml" filepath="$PROJECT_DIR$/.idea/src-tauri.iml" />
</modules>
</component>
</project>

11
src-tauri/.idea/src-tauri.iml generated Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
src-tauri/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

13
src-tauri/Cargo.lock generated
View File

@@ -80,6 +80,7 @@ name = "app"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"log", "log",
"network-interface",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
@@ -1904,6 +1905,18 @@ dependencies = [
"jni-sys", "jni-sys",
] ]
[[package]]
name = "network-interface"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07709a6d4eba90ab10ec170a0530b3aafc81cb8a2d380e4423ae41fc55fe5745"
dependencies = [
"cc",
"libc",
"thiserror 2.0.17",
"winapi",
]
[[package]] [[package]]
name = "new_debug_unreachable" name = "new_debug_unreachable"
version = "1.0.6" version = "1.0.6"

View File

@@ -19,7 +19,8 @@ tauri-build = { version = "2.5.1", features = [] }
[dependencies] [dependencies]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0.228" }
log = "0.4" log = "0.4"
tauri = { version = "2.9.2", features = [] } tauri = { version = "2.9.2", features = [] }
tauri-plugin-log = "2" tauri-plugin-log = "2"
network-interface = "2.0.3"

View File

@@ -1,3 +1,5 @@
mod network;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
@@ -11,6 +13,9 @@ pub fn run() {
} }
Ok(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![
network::get_network_interfaces
])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

View File

@@ -1,6 +1,10 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() { fn main() {
app_lib::run(); app_lib::run();
} }

37
src-tauri/src/network.rs Normal file
View File

@@ -0,0 +1,37 @@
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct InterfaceInfo {
pub name: String,
pub addr: String, // IP 地址
pub mac: Option<String>, // MAC 地址,可能不存在
}
fn map_interface_to_info(interface: &NetworkInterface) -> InterfaceInfo {
let mac_address = interface.mac_addr.as_ref().map(|mac| mac.to_string());
let ip_addr = interface.addr.iter().find(|addr| addr.ip().is_ipv4())
.map(|addr| addr.ip().to_string())
.unwrap_or_else(|| "N/A".to_string());
InterfaceInfo {
name: interface.name.clone(),
addr: ip_addr,
mac: mac_address,
}
}
#[tauri::command]
pub async fn get_network_interfaces() -> Result<Vec<InterfaceInfo>, String> {
match NetworkInterface::show() {
Ok(interfaces) => {
let info_list: Vec<InterfaceInfo> = interfaces.into_iter()
.map(|interface| map_interface_to_info(&interface))
.collect();
Ok(info_list)
}
Err(e) => Err(format!("无法获取网络接口信息: {}", e)),
}
}

View File

@@ -15,14 +15,8 @@
</svelte:head> </svelte:head>
<Header/> <Header/>
<div class="app-container"> <div class="app-container">
<!-- 1. 侧边栏: 固定宽度,全高 -->
<Sidebar /> <Sidebar />
<!-- 2. 主内容区域: 占据剩余所有空间 (flex-1) -->
<main class="main-content"> <main class="main-content">
<!-- 渲染子路由的内容 (+page.svelte 或下级 +layout.svelte)
使用标准的 <slot /> 替换了 {@render children()} -->
{@render children()} {@render children()}
</main> </main>
</div> </div>

View File

@@ -1,11 +1,79 @@
<script> <script lang="ts">
import { invoke } from '@tauri-apps/api/core';
import { onMount } from 'svelte';
// 定义与 Rust 端 InterfaceInfo 匹配的 TypeScript 类型
interface InterfaceInfo {
name: string;
addr: string;
mac?: string;
}
let interfaces: InterfaceInfo[] = [];
let loading: boolean = true;
let error: string | null = null;
onMount(async () => {
try {
// 调用 Rust 后端命令
interfaces = await invoke<InterfaceInfo[]>("get_network_interfaces");
console.log("Network interfaces:", interfaces);
} catch (err) {
console.error("Failed to get network interfaces:", err);
// 错误处理,将错误信息展示给用户
error = (err as string) || "未知错误";
} finally {
loading = false;
}
});
</script> </script>
<div class="network-info">
<h2>🌐 网络接口信息</h2>
{#if loading}
<p>正在加载...</p>
{:else if error}
<p class="error-message">错误: {error}</p>
{:else}
<table>
<thead>
<tr>
<th>名称</th>
<th>IP 地址 (IPv4)</th>
<th>MAC 地址</th>
</tr>
</thead>
<tbody>
{#each interfaces as iface (iface.name)}
<tr>
<td><strong>{iface.name}</strong></td>
<td>{iface.addr}</td>
<td>{iface.mac || 'N/A'}</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
<style> <style>
/* 简单的 Svelte 样式 */
</style> table {
width: 100%;
<div> border-collapse: collapse;
IP 工具箱 margin-top: 1em;
</div> }
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: var(--main-bg-color);
}
.error-message {
color: red;
font-weight: bold;
}
</style>