feat(installer): 实现游戏安装与快捷方式创建功能- 添加禁用右键菜单和开发者工具的保护机制

- 引入GAME_CONFIG配置文件统一管理游戏信息
- 安装页面支持记录安装路径到localStorage
- 成功页面增加创建桌面快捷方式选项
- 实现调用系统资源管理器打开游戏目录功能
- 集成Windows COM组件创建快捷方式的能力
- 更新UI界面展示多语言标题和版本信息
- 升级daisyui依赖至5.5.2版本- 调整窗口标题和产品名称为"游戏安装工具 - NoPJ"
This commit is contained in:
Chaos
2025-11-13 23:40:22 +08:00
parent 345acdee91
commit 6b6c61fd08
14 changed files with 326 additions and 81 deletions

18
package-lock.json generated
View File

@@ -14,7 +14,7 @@
"@tauri-apps/plugin-opener": "^2.5.2", "@tauri-apps/plugin-opener": "^2.5.2",
"@tauri-apps/plugin-shell": "^2.3.3", "@tauri-apps/plugin-shell": "^2.3.3",
"@tauri-apps/plugin-store": "^2.4.1", "@tauri-apps/plugin-store": "^2.4.1",
"daisyui": "^5.4.7" "daisyui": "^5.5.2"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-static": "^3.0.10", "@sveltejs/adapter-static": "^3.0.10",
@@ -1204,7 +1204,6 @@
"integrity": "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==", "integrity": "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5", "@sveltejs/acorn-typescript": "^1.0.5",
@@ -1244,7 +1243,6 @@
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"debug": "^4.4.1", "debug": "^4.4.1",
@@ -1842,7 +1840,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -1935,9 +1932,9 @@
} }
}, },
"node_modules/daisyui": { "node_modules/daisyui": {
"version": "5.4.7", "version": "5.5.2",
"resolved": "https://registry.npmmirror.com/daisyui/-/daisyui-5.4.7.tgz", "resolved": "https://registry.npmmirror.com/daisyui/-/daisyui-5.5.2.tgz",
"integrity": "sha512-2wYO61vTPCXk7xEBgnzLZAYoE0xS5IRLu/GSq0vORpB+cTrtubdx69NnA0loc0exvCY1s2fYL4lGZtFHe2ohNQ==", "integrity": "sha512-uhEz8X1urrEhHhK51/vIQAL1fWPEJP8mTqCTEhfHfC8Dgur9XAz9SOWwl41LZgNs/fjWGvWzFcGM5WCw5ovp8g==",
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/saadeghi/daisyui?sponsor=1" "url": "https://github.com/saadeghi/daisyui?sponsor=1"
@@ -2572,7 +2569,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -2615,7 +2611,6 @@
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -2632,7 +2627,6 @@
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"prettier": "^3.0.0", "prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
@@ -2824,7 +2818,6 @@
"integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==", "integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.5.0", "@bufbuild/protobuf": "^2.5.0",
"buffer-builder": "^0.2.0", "buffer-builder": "^0.2.0",
@@ -3222,7 +3215,6 @@
"integrity": "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==", "integrity": "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/remapping": "^2.3.4", "@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -3365,7 +3357,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -3387,7 +3378,6 @@
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",

View File

@@ -37,6 +37,6 @@
"@tauri-apps/plugin-opener": "^2.5.2", "@tauri-apps/plugin-opener": "^2.5.2",
"@tauri-apps/plugin-shell": "^2.3.3", "@tauri-apps/plugin-shell": "^2.3.3",
"@tauri-apps/plugin-store": "^2.4.1", "@tauri-apps/plugin-store": "^2.4.1",
"daisyui": "^5.4.7" "daisyui": "^5.5.2"
} }
} }

1
src-tauri/Cargo.lock generated
View File

@@ -104,6 +104,7 @@ dependencies = [
"tauri-plugin-shell", "tauri-plugin-shell",
"thiserror 2.0.17", "thiserror 2.0.17",
"tokio", "tokio",
"windows",
"zip", "zip",
] ]

View File

@@ -32,3 +32,4 @@ async-runtime = "0.0.0"
tokio = { version = "1.48.0", features = ["time"] } tokio = { version = "1.48.0", features = ["time"] }
tauri-plugin-shell = "2.3.3" tauri-plugin-shell = "2.3.3"
tauri-plugin-opener = "2.5.2" tauri-plugin-opener = "2.5.2"
windows = "0.61.3"

View File

@@ -4,7 +4,8 @@ use thiserror::Error;
use zip::ZipArchive; use zip::ZipArchive;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io; use std::io;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::process::Command;
use serde::Serialize; use serde::Serialize;
use tauri::Emitter; use tauri::Emitter;
@@ -145,3 +146,20 @@ pub async fn install_game(window: tauri::Window, target_dir: String) -> Result<S
Ok(format!("成功解压 {} 个文件到指定目录!", total_files as u32)) Ok(format!("成功解压 {} 个文件到指定目录!", total_files as u32))
} }
//调用资源管理器打开游戏目录
#[tauri::command]
pub async fn open_game_dir(install_path:String) -> Result<String,String> {
let game_path = Path::new(&install_path);
if !game_path.exists() {
return Err("游戏目录不存在".to_string());
}
let result = Command::new("explorer")
.arg(game_path)
.spawn()
.map_err(|e| format!("打开游戏目录失败: {}", e))?;
println!("资源管理器进程ID: {}", result.id());
Ok("已打开游戏目录".to_string())
}

View File

@@ -1,5 +1,6 @@
mod network; mod network;
mod gameins; mod gameins;
mod shortcut;
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
@@ -19,7 +20,9 @@ pub fn run() {
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
network::get_network_interfaces, network::get_network_interfaces,
gameins::install_game gameins::install_game,
gameins::open_game_dir,
shortcut::create_shortcut,
]) ])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

132
src-tauri/src/shortcut.rs Normal file
View File

@@ -0,0 +1,132 @@
use std::path::Path;
use windows::{
core::{Interface, PCWSTR, HRESULT},
Win32::{
System::{
Com::{
CoCreateInstance, CoInitializeEx, CoUninitialize,
IPersistFile, CLSCTX_INPROC_SERVER, COINIT_APARTMENTTHREADED
},
},
UI::Shell::{IShellLinkW, ShellLink},
},
};
#[tauri::command]
pub fn create_shortcut(
install_path: String,
shortcut_name: String,
exe_name: String
) -> Result<String, String> {
unsafe {
// 初始化COM库
let com_result = CoInitializeEx(None, COINIT_APARTMENTTHREADED);
let needs_uninit = com_result.is_ok() || com_result == HRESULT(0x80010106u32 as i32); // S_FALSE or RPC_E_CHANGED_MODE
let result = create_shortcut_internal(&install_path, &shortcut_name, &exe_name);
if needs_uninit {
CoUninitialize();
}
result
}
}
unsafe fn create_shortcut_internal(
install_path: &str,
shortcut_name: &str,
exe_name: &str
) -> Result<String, String> {
// 获取当前用户桌面路径
let desktop_path = get_desktop_path()?;
let shortcut_file = format!("{}.lnk", shortcut_name);
let shortcut_full_path = Path::new(&desktop_path).join(&shortcut_file);
// 构建目标程序路径
let target_path = Path::new(install_path).join(exe_name);
if !target_path.exists() {
return Err(format!("目标程序不存在: {}", target_path.display()));
}
// 创建IShellLink对象
let shell_link: IShellLinkW = CoCreateInstance(
&ShellLink,
None,
CLSCTX_INPROC_SERVER
).map_err(|e| format!("创建ShellLink失败: {:?}", e))?;
// 设置快捷方式属性
set_shell_link_properties(&shell_link, &target_path, install_path)?;
// 保存快捷方式
save_shortcut(&shell_link, &shortcut_full_path)?;
Ok(format!("快捷方式创建成功: {}", shortcut_full_path.display()))
}
unsafe fn get_desktop_path() -> Result<String, String> {
std::env::var("USERPROFILE")
.map(|profile| format!("{}\\Desktop", profile))
.or_else(|_| {
std::env::var("USERNAME")
.map(|username| format!("C:\\Users\\{}\\Desktop", username))
})
.map_err(|_| "无法获取用户桌面路径".to_string())
}
unsafe fn set_shell_link_properties(
shell_link: &IShellLinkW,
target_path: &Path,
working_dir: &str
) -> Result<(), String> {
// 设置目标路径
let target_path_str = target_path.to_string_lossy();
let wide_target: Vec<u16> = to_wide_string(&target_path_str);
// 用 map_err + ? 处理错误并转换为 String
shell_link
.SetPath(PCWSTR(wide_target.as_ptr()))
.map_err(|e| format!("设置目标路径失败: {:?}", e))?;
// 设置工作目录
let wide_working_dir: Vec<u16> = to_wide_string(working_dir);
shell_link
.SetWorkingDirectory(PCWSTR(wide_working_dir.as_ptr()))
.map_err(|e| format!("设置工作目录失败: {:?}", e))?;
// 设置描述
let description = "NoPJ创建的快捷方式 - nopj.cn";
let wide_desc: Vec<u16> = to_wide_string(description);
shell_link
.SetDescription(PCWSTR(wide_desc.as_ptr()))
.map_err(|e| format!("设置描述失败: {:?}", e))?;
Ok(())
}
unsafe fn save_shortcut(
shell_link: &IShellLinkW,
shortcut_path: &Path
) -> Result<(), String> {
// 转换为 IPersistFile 接口
let persist_file: IPersistFile = shell_link
.cast()
.map_err(|e| format!("接口转换失败: {:?}", e))?;
// 保存文件
let path_str = shortcut_path.to_string_lossy();
let wide_path: Vec<u16> = to_wide_string(&path_str);
// Save 可能返回 windows::core::Result<()>,同样用 map_err ? 处理
persist_file
.Save(PCWSTR(wide_path.as_ptr()), true)
.map_err(|e| format!("保存快捷方式失败: {:?}", e))?;
Ok(())
}
fn to_wide_string(s: &str) -> Vec<u16> {
s.encode_utf16().chain(std::iter::once(0)).collect()
}

View File

@@ -1,6 +1,6 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "NoPJ安装器", "productName": "游戏安装工具 - NoPJ",
"version": "0.1.0", "version": "0.1.0",
"identifier": "cn.nopj.gametools", "identifier": "cn.nopj.gametools",
"build": { "build": {
@@ -12,13 +12,13 @@
"app": { "app": {
"windows": [ "windows": [
{ {
"title": "Game Install", "title": "游戏安装工具 - NoPJ",
"width": 1280, "width": 1280,
"height": 720, "height": 720,
"resizable": false, "resizable": false,
"fullscreen": false, "fullscreen": false,
"decorations": false "decorations": false,
"devtools": false
} }
], ],
"security": { "security": {

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body data-sveltekit-preload-data="hover"> <body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>

10
src/lib/config.ts Normal file
View File

@@ -0,0 +1,10 @@
export const GAME_CONFIG = {
title: '逃离鸭科夫',
title_rus: 'Побег из Дакова',
title_en: 'Escape From Duckov',
version: '1.1.6',
description: "「逃离鸭科夫」是一款鸭子题材PVE俯视角撤离射击游戏。你要在鸭科夫的世界里搜寻物资建造藏身处升级装备从一无所有到做大做强面对虎视眈眈的敌鸭想方设法生存下去——或者逃离。",
pathname: '\\Duckov',
launch: 'Duckov.exe',
shortcut_name: "逃离鸭科夫",
};

View File

@@ -12,6 +12,20 @@
<svelte:head> <svelte:head>
<link rel="icon" href={favicon} /> <link rel="icon" href={favicon} />
<Sprite /> <Sprite />
<script>
document.addEventListener('contextmenu', (e) => {
e.preventDefault(); // 禁用右键菜单
});
window.addEventListener('keydown', (event) => {
// 禁用 F12 和 Ctrl+Shift+I
if (event.key === 'F12' || (event.ctrlKey && event.shiftKey && event.key === 'I')) {
event.preventDefault();
event.stopImmediatePropagation();
}
});
</script>
</svelte:head> </svelte:head>
<Header/> <Header/>

View File

@@ -1,61 +1,40 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { GAME_CONFIG } from '$lib/config';
let isHover = false;
// 提取函数:处理“激活”状态
function handleFocus() {
isHover = true;
}
// 提取函数:处理“失活”状态
function handleBlur() {
isHover = false;
}
</script> </script>
<!--<div class={'relative pt-10 px-4 h-screen w-screen flex justify-center items-center transition-all ' }}--> <div class="hero min-h-screen transition-all duration-300 "
<!-- class:backdrop-blur-xs={isHover}-->
<!--&gt;-->
<!-- <button class="btn btn-wide" on:mouseenter={()=>{isHover = true}} on:mouseout={()=>{isHover = false}}>安装游戏</button>-->
<!--</div>-->
<div class="hero min-h-screen transition-all {isHover ? 'bg-neutral-700/30' : ''} "
class:backdrop-blur-xs={isHover}
> >
<div class="hero-content text-center select-none"> <div class="hero-content text-center select-none p-12 group hover:backdrop-blur hover:bg-base-100/70">
<div class="max-w-md" > <div class="w-xl group-hover:w-2xl transition-all duration-1000 ease-in-out ">
<h1 class="text-5xl font-bold" <p class="text-5xl font-bold ">
on:mouseenter={()=>{isHover = true}} <span class="text-rotate">
on:mouseout={()=>{isHover = false}} <span class="justify-items-center">
on:blur={handleBlur} <span>{GAME_CONFIG.title}</span>
on:focus={handleFocus} <span>{GAME_CONFIG.title_en}</span>
>逃离鸭科夫</h1> <span>{GAME_CONFIG.title_rus}</span>
<p class="py-6" </span>
on:mouseenter={()=>{isHover = true}} </span>
on:mouseout={()=>{isHover = false}}
on:blur={handleBlur}
on:focus={handleFocus}
>
<span class="font-bold">《逃离鸭科夫》</span>是一款鸭子题材PVE俯视角撤离射击游戏。你要在鸭科夫的世界里搜寻物资建造藏身处升级装备从一无所有到做大做强面对虎视眈眈的敌鸭想方设法生存下去——或者逃离。
</p> </p>
<button class="btn btn-primary btn-wide " <p class="pt-2">versin {GAME_CONFIG.version}</p>
on:mouseenter={()=>{isHover = true}} <p class="py-6 text-left">
on:mouseout={()=>{isHover = false}} {GAME_CONFIG.description}
on:blur={handleBlur} </p>
on:focus={handleFocus} <!-- 事件会冒泡到父元素hero-content因此这些按钮可以不用显式绑定事件 -->
on:click={()=>{goto('/install')}} <div class="w-full">
>安装游戏</button> <button class="btn btn-ghost btn-wide group-hover:btn-primary "
<button class="btn btn-primary btn-wide " on:click="{() => goto('/install')}"
on:mouseenter={()=>{isHover = true}} >
on:mouseout={()=>{isHover = false}} 安装游戏
on:blur={handleBlur} </button>
on:focus={handleFocus} </div>
on:click={()=>{goto('/success')}}
>完成</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,6 +5,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { GAME_CONFIG } from '$lib/config';
let installPath = ''; let installPath = '';
let isInstalling = false; let isInstalling = false;
@@ -30,10 +31,6 @@
progressPercent = event.payload.percentage; progressPercent = event.payload.percentage;
}); });
// 组件销毁时停止监听
return () => {
unlisten();
};
}); });
@@ -60,7 +57,7 @@
installPath = ''; installPath = '';
return; return;
} }
installPath = selected + '\\DK'; // 您的逻辑 installPath = selected +GAME_CONFIG.pathname; // 您的逻辑
log('已选择安装目录:'+ installPath +",点击开始安装即可开始自动安装"); log('已选择安装目录:'+ installPath +",点击开始安装即可开始自动安装");
}else if (selected === null) { }else if (selected === null) {
log("未选择安装目录"); log("未选择安装目录");
@@ -82,7 +79,7 @@
progressPercent = 0; // 重置进度条 progressPercent = 0; // 重置进度条
log("安装程序启动..."); log("安装程序启动...");
localStorage.setItem('installPath', installPath)
try { try {
// 我们仍然调用 invoke但现在它只负责启动 // 我们仍然调用 invoke但现在它只负责启动
// 真正的日志将通过 'install_progress' 事件传来 // 真正的日志将通过 'install_progress' 事件传来

View File

@@ -2,10 +2,39 @@
import tl from '$lib/assets/images/tl.png' import tl from '$lib/assets/images/tl.png'
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { openUrl } from '@tauri-apps/plugin-opener'; import { openUrl } from '@tauri-apps/plugin-opener';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { invoke } from '@tauri-apps/api/core';
import { GAME_CONFIG } from '$lib/config';
let data:[{attributes:{title:string, slug:string, id:string}}] = [];
const win = getCurrentWindow();
const closeWindow = async () => {
if (openGameDir) {
try {
const result = await invoke('open_game_dir', {
installPath: installPath
});
console.log(result);
} catch (error) {
console.error('启动游戏失败:', error);
}
}
await create_shortcut()
await win.close()
};
let data:[{attributes:{title:string, slug:string, id:string}}];
let loading = true; let loading = true;
let error; let error;
//是否创建快捷方式变量
let createShortcut = true;
//是否退出后启动变量
let openGameDir = true;
let installPath = localStorage.getItem('installPath');
const url = "https://nopj.cn/api/discussions?include=tags.parent&filter[tag]=resources&sort=-createdAt&page[offset]=0&page[limit]=5" const url = "https://nopj.cn/api/discussions?include=tags.parent&filter[tag]=resources&sort=-createdAt&page[offset]=0&page[limit]=5"
@@ -44,11 +73,68 @@
} }
} }
const create_shortcut = async () => {
if (createShortcut){
try{
const result = await invoke('create_shortcut', {
installPath: localStorage.getItem('installPath'),
shortcutName: GAME_CONFIG.shortcut_name,
exeName: GAME_CONFIG.launch,
})
console.log(result);
}catch (e){
console.error("创建快捷方式失败:", e);
alert(e)
}
}
}
</script> </script>
<div class="h-screen w-screen flex z-10 pt-8"> <div class="h-screen w-screen flex z-10 pt-8 select-none relative">
<div class="flex-1"> <div class="flex-1 flex items-center justify-center">
123 <div class="w-2xl p-8 backdrop-blur-xl shadow-xl shadow-base/30">
<div class=" text-center">
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 32 32" class="m-auto">
<g fill="none">
<path fill="url(#SVGFnVq5bNm)" d="M30 16c0 7.732-6.268 14-14 14S2 23.732 2 16S8.268 2 16 2s14 6.268 14 14" />
<path fill="url(#SVGcpquMdXZ)" d="M22.707 12.707a1 1 0 0 0-1.414-1.414L14.5 18.086l-3.293-3.293a1 1 0 0 0-1.414 1.414l4 4a1 1 0 0 0 1.414 0z" />
<defs>
<linearGradient id="SVGFnVq5bNm" x1="3" x2="22.323" y1="7.25" y2="27.326" gradientUnits="userSpaceOnUse">
<stop stop-color="#52d17c" />
<stop offset="1" stop-color="#22918b" />
</linearGradient>
<linearGradient id="SVGcpquMdXZ" x1="12.031" x2="14.162" y1="11.969" y2="22.66" gradientUnits="userSpaceOnUse">
<stop stop-color="#fff" />
<stop offset="1" stop-color="#e3ffd9" />
</linearGradient>
</defs>
</g>
</svg>
<div class="font-bold text-3xl pt-2 pb-4">
安装完成
</div>
</div>
<fieldset class="fieldset bg-base-100/30 border-base-300/10 rounded-box w-full border p-4">
<legend class="fieldset-legend">启动设置</legend>
<label class="label">
<input type="checkbox" checked={createShortcut} class="checkbox" />
设置桌面快捷方式
</label>
<label class="label pt-4">
<input type="checkbox" checked={openGameDir} class="checkbox" />
打开游戏目录
</label>
</fieldset>
<div class="w-full text-center pt-12">
<button class="btn btn-primary btn-wide "
on:click={closeWindow}
>退出</button>
</div>
</div>
</div> </div>
<div class=" w-96 bg-base-200 bg-cover bg-center text-base-300 overflow-hidden relative" style="background-image: url({tl})"> <div class=" w-96 bg-base-200 bg-cover bg-center text-base-300 overflow-hidden relative" style="background-image: url({tl})">
<div class="h-full w-full absolute top-0 z-0"> <div class="h-full w-full absolute top-0 z-0">
@@ -56,9 +142,16 @@
</div> </div>
<div class="z-10 relative p-4 w-full "> <div class="z-10 relative p-4 w-full ">
<h1 class="font-bold text-4xl text-right">NoPJ</h1> <h1 class="font-bold text-4xl text-right">NoPJ</h1>
<p class="font-bold text-right pt-2 ">最新资源</p> <p class="pt-2 ">
<span class="text-rotate font-bold text-right ">
<span>
<span>最新资源</span>
<span>Latest Resources</span>
<span>Последний ресурс</span>
</span>
</span>
</p>
<div class="pt-4"> <div class="pt-4">
<ul class="list"> <ul class="list">
{#each data as item} {#each data as item}
<li class=" list-row backdrop-blur-xs hover:backdrop-blur-xl hover:font-bold transition-all mt-4 font-misans"> <li class=" list-row backdrop-blur-xs hover:backdrop-blur-xl hover:font-bold transition-all mt-4 font-misans">
@@ -74,5 +167,11 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="absolute bottom-3 text-center z-10 left-0 w-full">
<p class="font-bold">Designed by Chaos & <a href="https://nopj.cn"
on:click={(e)=>handleExternalLink(e, 'https://nopj.cn')}
>Nopj</a></p>
</div> </div>
</div> </div>
</div>