feat(installer): 实现游戏安装程序页面
- 添加选择安装目录功能,支持 Windows 默认路径 - 集成 Rust 后端 install_game 命令 - 实现安装进度提示与结果反馈- 移除旧网络工具页面与关于页面 - 更新全局样式配置,引入 daisyUI 主题 - 调整窗口控制栏布局与样式 - 添加 Tauri 插件权限配置 (fs, dialog) - 升级依赖库并添加系统组件支持
This commit is contained in:
11
src/app.css
11
src/app.css
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -1,7 +0,0 @@
|
||||
<script>
|
||||
import { goto } from '$app/navigation';
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
<li on:click={() => goto('/')}>首页</li>
|
||||
</ul>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user