feat(installer): 实现游戏安装程序页面

- 添加选择安装目录功能,支持 Windows 默认路径
- 集成 Rust 后端 install_game 命令
- 实现安装进度提示与结果反馈- 移除旧网络工具页面与关于页面
- 更新全局样式配置,引入 daisyUI 主题
- 调整窗口控制栏布局与样式
- 添加 Tauri 插件权限配置 (fs, dialog)
- 升级依赖库并添加系统组件支持
This commit is contained in:
Chaos
2025-11-11 16:16:37 +08:00
parent 514c025caf
commit a09f4a7e6e
72 changed files with 708 additions and 320 deletions

View File

@@ -1,12 +1,5 @@
@import 'tailwindcss';
:root{
--main-bg-color: #1f1f1f;
--main-border-shadow:1px 0 3px rgba(255, 255, 255, 0.1);
@plugin "daisyui" {
themes: all;
}
body {
background-color: #1f1f1f; /* 示例:一个深灰色 */
margin: 0;
color: white;
}

View File

@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="en" data-theme="black">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

View File

@@ -10,11 +10,11 @@
</script>
<header class="custom-titlebar" data-tauri-drag-region>
<header class="h-8 w-full bg-base absolute top-0 window-controls flex gap-10 px-3 items-center" data-tauri-drag-region>
<div class="app-title" data-tauri-drag-region></div>
<div class="flex-1" data-tauri-drag-region></div>
<div class="window-controls">
<div class="flex gap-2">
<button type="button" title="最小化窗口" data-tauri-drag-region="false" class="h-btn btn-minimize" on:click={minimize}></button>
<button
type="button"
@@ -31,21 +31,6 @@
</header>
<style>
.custom-titlebar {
height: 30px;
background-color: #1f1f1f;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
user-select: none;
position: fixed;
width: 100%;
top: 0;
z-index: 1000;
box-shadow: 1px 0 3px rgba(255, 255, 255, 0.1);
}
.window-controls {
display: flex;
@@ -60,7 +45,6 @@
cursor: pointer;
transition: background-color 0.2s ease, box-shadow 0.2s ease;
}
/* 🟡 最小化按钮 */
.window-controls button.btn-minimize {
background-color: #febc2e;

View File

@@ -1,133 +0,0 @@
<script lang="ts">
// 您可以在此处导入 SvelteKit 的导航链接模块
// import { goto } from '$app/navigation';
// 模拟导航项数据
// 确保对象字面量语法正确无误
import { goto } from '$app/navigation';
import Icon from '$lib/components/Icon.svelte';
import type { IconId } from '$lib/types/icon-ids';
import HomeIcon from '$lib/components/icon/HomeIcon.svelte';
const navItems:[{icon: IconId, label: string, path: string}] = [
{ icon: 'network', label: 'IP工具', path: '/iptools' },
];
let activePath: string = '/'; // 明确声明类型
function navigate(path: string) {
activePath = path;
goto(path);
console.log(`导航到: ${path}`);
}
</script>
<nav class="sidebar">
<!-- 顶部图标/Logo -->
<div class="sidebar-logo pt-2 pb-2">
<button type="button"
class="cursor-pointer"
title="回到主页"
on:click={() => navigate('/')}>
<HomeIcon width="24" height="24" />
</button>
</div>
<!-- 中间导航图标 -->
<div class="nav-list">
{#each navItems as item}
<button
class="nav-item"
class:active={activePath === item.path}
on:click={() => navigate(item.path)}
title={item.label}
>
<Icon id={item.icon} className="hover:text-neutral-400 transition-all duration-1000" size="24" />
</button>
{/each}
</div>
<!-- 底部设置/用户图标 -->
<div class="bottom-icons">
<button class="nav-item" title="用户资料">👤</button>
<button class="nav-item" title="退出">↩️</button>
</div>
</nav>
<style>
/* 侧边栏的基础样式 */
.sidebar {
/* 根据图片,侧边栏宽度较窄,可能在 60px 左右 */
width: 35px;
min-width: 35px; /* 确保它不会收缩 */
height: 100%;
background-color: var(--main-bg-color); /* 比主背景稍亮 */
display: flex;
flex-direction: column; /* 垂直排列内容 */
align-items: center;
padding: 10px 0;
box-shadow: var(--main-border-shadow);
user-select: none; /* 阻止文本选择 */
}
/* Logo/顶部区域 */
.sidebar-logo {
cursor: pointer;
}
/* 导航列表区域 */
.nav-list {
flex: 1; /* 占据中间所有剩余垂直空间,将底部图标推到底部 */
display: flex;
flex-direction: column;
gap: 15px; /* 增加图标间的间隔 */
}
/* 底部图标区域 */
.bottom-icons {
display: flex;
flex-direction: column;
gap: 15px;
}
/* 导航按钮样式 */
.nav-item {
background: none;
border: none;
color: #b0b0b0; /* 默认图标颜色 */
font-size: 1rem;
width: 30px;
height: 30px;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s, color 0.2s;
display: flex;
justify-content: center;
align-items: center;
}
/* 鼠标悬停效果 */
.nav-item:hover {
background-color: #3e3e3e;
color: #ffffff;
}
/* 激活状态效果(关键:模拟选中高亮) */
.nav-item.active {
/* 侧边栏上常见的条状高亮 */
color: #ffffff;
position: relative;
}
.nav-item.active::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px; /* 高亮的条形宽度 */
height: 80%;
background-color: #4CAF50; /* 绿色高亮条 */
border-radius: 0 4px 4px 0;
}
</style>

View File

@@ -2,7 +2,6 @@
import '../app.css';
import favicon from '$lib/assets/favicon.svg';
import Header from '$lib/components/layout/Header.svelte';
import Sidebar from '$lib/components/layout/Sidebar.svelte';
import Sprite from '$lib/components/ui/Sprite.svelte';
@@ -15,29 +14,12 @@
</svelte:head>
<Header/>
<div class="app-container">
<Sidebar />
<main class="main-content">
{@render children()}
</main>
</div>
<style>
/* 确保整个应用容器占满整个视口 */
.app-container {
display: flex;
width: 100vw;
height: 100vh;
overflow: hidden; /* 防止滚动条出现在侧边栏和主内容之间 */
background-color: #1e1e1e; /* 模拟深色背景 */
color: #f1f1f1;
padding-top: 30px;
}
/* 主内容区域占据剩余空间 */
.main-content {
flex: 1; /* 占据 Flex 容器的剩余空间 */
/* 允许主内容区域内部滚动 */
overflow-y: auto;
padding: 1rem;
}
<style>
</style>

View File

@@ -1,14 +1,92 @@
<div class="app-container">
<div class="app-content">
123
<script lang="ts">
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
let installPath = '未选择安装路径';
let isInstalling = false;
// 1. 打开目录选择对话框
async function selectInstallDir() {
// 默认打开 C:/Program Files仅限 Windows
const defaultPath = 'C:\\Program Files';
const selected = await open({
directory: true, // 必须选择目录
multiple: false, // 只能选择一个
title: '请选择游戏的安装目录',
defaultPath: defaultPath, // 初始打开的路径
});
if (typeof selected === 'string') {
installPath = selected;
}
}
// 2. 调用 Rust 后端命令开始安装
async function startInstallation() {
if (installPath === '未选择安装路径') {
alert('请先选择安装路径!');
return;
}
isInstalling = true;
try {
// 调用我们在 Rust 中定义的 `install_game` 命令
// Tauri 会自动处理前端和后端的数据传输
const result: string = await invoke('install_game', {
targetDir: installPath
});
alert(`安装完成!\n${result}`);
} catch (error) {
console.error('安装失败:', error);
alert(`安装失败:${error}`);
} finally {
isInstalling = false;
}
}
</script>
<main class="h-screen flex flex-col bg-base ">
<div class="flex-1 pt-10 px-4">
<h1 class="text-3xl font-bold">游戏安装程序</h1>
</div>
</div>
<div class="h-32 shrink-0 p-4 border-base ">
<div class="flex items-center">
<input class="input flex-1" value={installPath} />
<button class="btn " on:click={selectInstallDir} disabled={isInstalling}>
选择安装目录
</button>
</div>
<div class="pt-4 text-center btn-primary">
<button class="btn btn-block" on:click={startInstallation} disabled={isInstalling}>
{isInstalling ? '正在安装...' : '开始安装'}
</button>
</div>
</div>
</main>
<!-- <h1>游戏安装程序</h1>-->
<!-- <div class="path-display">-->
<!-- <p>目标安装路径: <strong>{installPath}</strong></p>-->
<!-- <button on:click={selectInstallDir} disabled={isInstalling}>-->
<!-- 选择安装目录-->
<!-- </button>-->
<!-- </div>-->
<!-- <button class="btn">Default</button>-->
<!-- <button-->
<!-- on:click={startInstallation}-->
<!-- disabled={isInstalling || installPath === '未选择安装路径'}>-->
<!-- {isInstalling ? '正在安装...' : '开始安装'}-->
<!-- </button>-->
<style>
/* 基础样式 */
</style>
<script>
</script>

View File

@@ -1,7 +0,0 @@
<script>
import { goto } from '$app/navigation';
</script>
<ul>
<li on:click={() => goto('/')}>首页</li>
</ul>

View File

@@ -1,79 +0,0 @@
<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>
<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>
/* 简单的 Svelte 样式 */
table {
width: 100%;
border-collapse: collapse;
margin-top: 1em;
}
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>