This commit is contained in:
parent
7fa53d2fd7
commit
6895975e9e
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(dir:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
node_modules/
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"useDefineForClassFields": true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
"use strict";
|
||||
const electron = require("electron");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const Store = require("electron-store");
|
||||
let store = null;
|
||||
function initStore() {
|
||||
if (store) {
|
||||
console.warn("Store 已经初始化过了");
|
||||
return;
|
||||
}
|
||||
store = new Store({
|
||||
// 存储文件名
|
||||
name: "asset-pro-config",
|
||||
// 默认值
|
||||
defaults: {
|
||||
settings: {
|
||||
count: 0,
|
||||
theme: "system"
|
||||
},
|
||||
windowBounds: {
|
||||
width: 1600,
|
||||
height: 900
|
||||
}
|
||||
},
|
||||
// Schema 验证(可选但推荐)
|
||||
schema: {
|
||||
settings: {
|
||||
type: "object",
|
||||
properties: {
|
||||
count: { type: "number", default: 0 },
|
||||
theme: {
|
||||
type: "string",
|
||||
enum: ["light", "dark", "system"],
|
||||
default: "system"
|
||||
}
|
||||
},
|
||||
default: {}
|
||||
},
|
||||
windowBounds: {
|
||||
type: "object",
|
||||
properties: {
|
||||
width: { type: "number", default: 1600 },
|
||||
height: { type: "number", default: 900 },
|
||||
x: { type: "number" },
|
||||
y: { type: "number" }
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("Store 初始化完成,存储路径:", store.path);
|
||||
}
|
||||
function getStore() {
|
||||
if (!store) {
|
||||
throw new Error("Store 未初始化,请先调用 initStore()");
|
||||
}
|
||||
return store;
|
||||
}
|
||||
function getSettings(key) {
|
||||
const s = getStore();
|
||||
if (key) {
|
||||
return s.get(`settings.${key}`);
|
||||
}
|
||||
return s.get("settings");
|
||||
}
|
||||
function saveSettings(key, value) {
|
||||
const s = getStore();
|
||||
s.set(`settings.${key}`, value);
|
||||
}
|
||||
function getWindowBounds() {
|
||||
const s = getStore();
|
||||
const bounds = s.get("windowBounds");
|
||||
const newBounds = {
|
||||
width: 1600,
|
||||
height: 900
|
||||
};
|
||||
if (bounds.x !== void 0 && bounds.y !== void 0) {
|
||||
newBounds.x = bounds.x;
|
||||
newBounds.y = bounds.y;
|
||||
}
|
||||
if (bounds.width !== 1600 || bounds.height !== 900) {
|
||||
s.set("windowBounds", newBounds);
|
||||
}
|
||||
return newBounds;
|
||||
}
|
||||
function saveWindowBounds(bounds) {
|
||||
const s = getStore();
|
||||
s.set("windowBounds", bounds);
|
||||
}
|
||||
const IPC_CHANNELS = {
|
||||
// 文件操作
|
||||
SAVE_FILE: "file:save",
|
||||
READ_FILE: "file:read",
|
||||
// 设置操作
|
||||
GET_SETTINGS: "settings:get",
|
||||
SAVE_SETTINGS: "settings:save"
|
||||
};
|
||||
function setupIpcHandlers() {
|
||||
electron.ipcMain.handle(
|
||||
IPC_CHANNELS.SAVE_FILE,
|
||||
async (_event, content, filename = "demo-note.txt") => {
|
||||
try {
|
||||
const desktopPath = electron.app.getPath("desktop");
|
||||
const filePath = path.join(desktopPath, filename);
|
||||
await fs.promises.writeFile(filePath, content, "utf-8");
|
||||
return {
|
||||
success: true,
|
||||
message: `文件已保存到: ${filePath}`,
|
||||
path: filePath
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||
console.error("保存文件失败:", errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
message: `保存失败: ${errorMessage}`,
|
||||
path: null
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
electron.ipcMain.handle(
|
||||
IPC_CHANNELS.READ_FILE,
|
||||
async (_event, filename = "demo-note.txt") => {
|
||||
try {
|
||||
const desktopPath = electron.app.getPath("desktop");
|
||||
const filePath = path.join(desktopPath, filename);
|
||||
try {
|
||||
await fs.promises.access(filePath);
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
message: "文件不存在,请先保存一个文件",
|
||||
content: null
|
||||
};
|
||||
}
|
||||
const content = await fs.promises.readFile(filePath, "utf-8");
|
||||
return {
|
||||
success: true,
|
||||
message: "文件读取成功",
|
||||
content
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||
console.error("读取文件失败:", errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
message: `读取失败: ${errorMessage}`,
|
||||
content: null
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
electron.ipcMain.handle(IPC_CHANNELS.GET_SETTINGS, async (_event, key) => {
|
||||
try {
|
||||
const settings = getSettings(key);
|
||||
return {
|
||||
success: true,
|
||||
data: settings
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||
console.error("获取设置失败:", errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
message: errorMessage
|
||||
};
|
||||
}
|
||||
});
|
||||
electron.ipcMain.handle(
|
||||
IPC_CHANNELS.SAVE_SETTINGS,
|
||||
async (_event, key, value) => {
|
||||
try {
|
||||
saveSettings(key, value);
|
||||
return {
|
||||
success: true,
|
||||
message: "设置已保存"
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||
console.error("保存设置失败:", errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
message: `保存失败: ${errorMessage}`
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"];
|
||||
let mainWindow = null;
|
||||
function createWindow() {
|
||||
const bounds = getWindowBounds();
|
||||
const windowOptions = {
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
webPreferences: {
|
||||
// 安全配置:启用上下文隔离
|
||||
contextIsolation: true,
|
||||
// 安全配置:禁用 Node.js 集成
|
||||
nodeIntegration: false,
|
||||
// 预加载脚本路径
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
// 启用沙盒模式
|
||||
sandbox: false
|
||||
// 需要设为 false 才能使用 preload
|
||||
},
|
||||
// 窗口样式
|
||||
title: "AssetPro",
|
||||
show: false,
|
||||
// 先隐藏,等准备好再显示
|
||||
backgroundColor: "#ffffff",
|
||||
// 无边框窗口
|
||||
frame: false
|
||||
};
|
||||
if (bounds.x !== void 0 && bounds.y !== void 0) {
|
||||
windowOptions.x = bounds.x;
|
||||
windowOptions.y = bounds.y;
|
||||
} else {
|
||||
windowOptions.center = true;
|
||||
}
|
||||
mainWindow = new electron.BrowserWindow(windowOptions);
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
mainWindow == null ? void 0 : mainWindow.show();
|
||||
});
|
||||
setTimeout(() => {
|
||||
if (mainWindow && !mainWindow.isVisible()) {
|
||||
console.log("超时后强制显示窗口");
|
||||
mainWindow.show();
|
||||
}
|
||||
}, 3e3);
|
||||
mainWindow.on("close", () => {
|
||||
if (mainWindow) {
|
||||
const bounds2 = mainWindow.getBounds();
|
||||
saveWindowBounds(bounds2);
|
||||
}
|
||||
});
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
mainWindow.loadURL(VITE_DEV_SERVER_URL);
|
||||
} else {
|
||||
const fs2 = require("fs");
|
||||
const possiblePaths = [
|
||||
path.join(electron.app.getAppPath(), "dist-renderer", "index.html"),
|
||||
// asar 内部(主要路径)
|
||||
path.join(process.resourcesPath, "dist-renderer", "index.html"),
|
||||
// resources/dist-renderer(如果 extraResources 存在)
|
||||
path.join(process.resourcesPath, "dist", "index.html")
|
||||
// resources/dist(备用)
|
||||
];
|
||||
console.log("尝试加载页面,检查以下路径:");
|
||||
possiblePaths.forEach((p) => {
|
||||
const exists = fs2.existsSync(p);
|
||||
console.log(` ${exists ? "✓" : "✗"} ${p}`);
|
||||
});
|
||||
console.log("process.resourcesPath:", process.resourcesPath);
|
||||
console.log("app.getAppPath():", electron.app.getAppPath());
|
||||
let indexPath = null;
|
||||
for (const testPath of possiblePaths) {
|
||||
if (fs2.existsSync(testPath)) {
|
||||
indexPath = testPath;
|
||||
console.log("找到页面文件:", indexPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!indexPath) {
|
||||
console.error("无法找到 index.html 文件!");
|
||||
mainWindow.loadURL("data:text/html,<h1>无法找到页面文件</h1><p>请检查控制台输出</p>");
|
||||
return;
|
||||
}
|
||||
mainWindow.loadFile(indexPath).catch((error) => {
|
||||
console.error("loadFile 失败:", error);
|
||||
let fileUrl = indexPath.replace(/\\/g, "/");
|
||||
if (fileUrl.match(/^[A-Z]:/)) {
|
||||
fileUrl = `file:///${fileUrl}`;
|
||||
} else {
|
||||
fileUrl = `file://${fileUrl}`;
|
||||
}
|
||||
console.log("尝试使用 loadURL:", fileUrl);
|
||||
mainWindow.loadURL(fileUrl).catch((urlError) => {
|
||||
console.error("loadURL 也失败:", urlError);
|
||||
mainWindow.loadURL("data:text/html,<h1>页面加载失败</h1><p>请查看控制台错误信息</p>");
|
||||
});
|
||||
});
|
||||
}
|
||||
mainWindow.webContents.on("did-fail-load", (event, errorCode, errorDescription) => {
|
||||
console.error("页面加载失败:", errorCode, errorDescription);
|
||||
});
|
||||
}
|
||||
electron.app.whenReady().then(() => {
|
||||
initStore();
|
||||
setupIpcHandlers();
|
||||
createWindow();
|
||||
electron.app.on("activate", () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
electron.app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
electron.app.quit();
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
"use strict";
|
||||
const electron = require("electron");
|
||||
const IPC_CHANNELS = {
|
||||
SAVE_FILE: "file:save",
|
||||
READ_FILE: "file:read",
|
||||
GET_SETTINGS: "settings:get",
|
||||
SAVE_SETTINGS: "settings:save"
|
||||
};
|
||||
const electronAPI = {
|
||||
// ============================================
|
||||
// 文件操作 API
|
||||
// ============================================
|
||||
file: {
|
||||
/**
|
||||
* 保存文件到桌面
|
||||
* @param content - 文件内容
|
||||
* @param filename - 文件名(可选,默认为 demo-note.txt)
|
||||
*/
|
||||
save: (content, filename) => {
|
||||
return electron.ipcRenderer.invoke(IPC_CHANNELS.SAVE_FILE, content, filename);
|
||||
},
|
||||
/**
|
||||
* 从桌面读取文件
|
||||
* @param filename - 文件名(可选,默认为 demo-note.txt)
|
||||
*/
|
||||
read: (filename) => {
|
||||
return electron.ipcRenderer.invoke(IPC_CHANNELS.READ_FILE, filename);
|
||||
}
|
||||
},
|
||||
// ============================================
|
||||
// 设置操作 API
|
||||
// ============================================
|
||||
settings: {
|
||||
/**
|
||||
* 获取用户设置
|
||||
* @param key - 设置键名(可选,不传返回所有设置)
|
||||
*/
|
||||
get: (key) => {
|
||||
return electron.ipcRenderer.invoke(IPC_CHANNELS.GET_SETTINGS, key);
|
||||
},
|
||||
/**
|
||||
* 保存用户设置
|
||||
* @param key - 设置键名
|
||||
* @param value - 设置值
|
||||
*/
|
||||
save: (key, value) => {
|
||||
return electron.ipcRenderer.invoke(IPC_CHANNELS.SAVE_SETTINGS, key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
electron.contextBridge.exposeInMainWorld("electronAPI", electronAPI);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" />
|
||||
<title>AssetPro</title>
|
||||
<script type="module" crossorigin src="./assets/index-C1_JoRdR.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-Cnn4LfhN.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,217 @@
|
|||
x64:
|
||||
firstOrDefaultFilePatterns:
|
||||
- '**/*'
|
||||
- '!**/node_modules'
|
||||
- '!build{,/**/*}'
|
||||
- '!dist{,/**/*}'
|
||||
- '!**/*.{iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts,mk,a,o,forge-meta,pdb}'
|
||||
- '!**/._*'
|
||||
- '!**/electron-builder.{yaml,yml,json,json5,toml,ts}'
|
||||
- '!**/{.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore,.idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci,.yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github,electron-builder.env}'
|
||||
- '!.yarn{,/**/*}'
|
||||
- '!.editorconfig'
|
||||
- '!.yarnrc.yml'
|
||||
nodeModuleFilePatterns: []
|
||||
nsis:
|
||||
script: |-
|
||||
!include "D:\Project\AssetPro\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh"
|
||||
!addincludedir "D:\Project\AssetPro\node_modules\app-builder-lib\templates\nsis\include"
|
||||
!macro _isUpdated _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "updated"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isUpdated `"" isUpdated ""`
|
||||
|
||||
!macro _isForceRun _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "force-run"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isForceRun `"" isForceRun ""`
|
||||
|
||||
!macro _isKeepShortcuts _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "keep-shortcuts"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isKeepShortcuts `"" isKeepShortcuts ""`
|
||||
|
||||
!macro _isNoDesktopShortcut _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "no-desktop-shortcut"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isNoDesktopShortcut `"" isNoDesktopShortcut ""`
|
||||
|
||||
!macro _isDeleteAppData _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "delete-app-data"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isDeleteAppData `"" isDeleteAppData ""`
|
||||
|
||||
!macro _isForAllUsers _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "allusers"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isForAllUsers `"" isForAllUsers ""`
|
||||
|
||||
!macro _isForCurrentUser _a _b _t _f
|
||||
${StdUtils.TestParameter} $R9 "currentuser"
|
||||
StrCmp "$R9" "true" `${_t}` `${_f}`
|
||||
!macroend
|
||||
!define isForCurrentUser `"" isForCurrentUser ""`
|
||||
|
||||
!macro addLangs
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
!insertmacro MUI_LANGUAGE "German"
|
||||
!insertmacro MUI_LANGUAGE "French"
|
||||
!insertmacro MUI_LANGUAGE "SpanishInternational"
|
||||
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||
!insertmacro MUI_LANGUAGE "TradChinese"
|
||||
!insertmacro MUI_LANGUAGE "Japanese"
|
||||
!insertmacro MUI_LANGUAGE "Korean"
|
||||
!insertmacro MUI_LANGUAGE "Italian"
|
||||
!insertmacro MUI_LANGUAGE "Dutch"
|
||||
!insertmacro MUI_LANGUAGE "Danish"
|
||||
!insertmacro MUI_LANGUAGE "Swedish"
|
||||
!insertmacro MUI_LANGUAGE "Norwegian"
|
||||
!insertmacro MUI_LANGUAGE "Finnish"
|
||||
!insertmacro MUI_LANGUAGE "Russian"
|
||||
!insertmacro MUI_LANGUAGE "Portuguese"
|
||||
!insertmacro MUI_LANGUAGE "PortugueseBR"
|
||||
!insertmacro MUI_LANGUAGE "Polish"
|
||||
!insertmacro MUI_LANGUAGE "Ukrainian"
|
||||
!insertmacro MUI_LANGUAGE "Czech"
|
||||
!insertmacro MUI_LANGUAGE "Slovak"
|
||||
!insertmacro MUI_LANGUAGE "Hungarian"
|
||||
!insertmacro MUI_LANGUAGE "Arabic"
|
||||
!insertmacro MUI_LANGUAGE "Turkish"
|
||||
!insertmacro MUI_LANGUAGE "Thai"
|
||||
!insertmacro MUI_LANGUAGE "Vietnamese"
|
||||
!macroend
|
||||
|
||||
!include "C:\Users\admin\AppData\Local\Temp\t-PKnz4i\0-messages.nsh"
|
||||
!addplugindir /x86-unicode "C:\Users\admin\AppData\Local\electron-builder\Cache\nsis\nsis-resources-3.4.1\plugins\x86-unicode"
|
||||
|
||||
Var newStartMenuLink
|
||||
Var oldStartMenuLink
|
||||
Var newDesktopLink
|
||||
Var oldDesktopLink
|
||||
Var oldShortcutName
|
||||
Var oldMenuDirectory
|
||||
|
||||
!include "common.nsh"
|
||||
!include "MUI2.nsh"
|
||||
!include "multiUser.nsh"
|
||||
!include "allowOnlyOneInstallerInstance.nsh"
|
||||
|
||||
!ifdef INSTALL_MODE_PER_ALL_USERS
|
||||
!ifdef BUILD_UNINSTALLER
|
||||
RequestExecutionLevel user
|
||||
!else
|
||||
RequestExecutionLevel admin
|
||||
!endif
|
||||
!else
|
||||
RequestExecutionLevel user
|
||||
!endif
|
||||
|
||||
!ifdef BUILD_UNINSTALLER
|
||||
SilentInstall silent
|
||||
!else
|
||||
Var appExe
|
||||
Var launchLink
|
||||
!endif
|
||||
|
||||
!ifdef ONE_CLICK
|
||||
!include "oneClick.nsh"
|
||||
!else
|
||||
!include "assistedInstaller.nsh"
|
||||
!endif
|
||||
|
||||
!insertmacro addLangs
|
||||
|
||||
!ifmacrodef customHeader
|
||||
!insertmacro customHeader
|
||||
!endif
|
||||
|
||||
Function .onInit
|
||||
Call setInstallSectionSpaceRequired
|
||||
|
||||
SetOutPath $INSTDIR
|
||||
${LogSet} on
|
||||
|
||||
!ifmacrodef preInit
|
||||
!insertmacro preInit
|
||||
!endif
|
||||
|
||||
!ifdef DISPLAY_LANG_SELECTOR
|
||||
!insertmacro MUI_LANGDLL_DISPLAY
|
||||
!endif
|
||||
|
||||
!ifdef BUILD_UNINSTALLER
|
||||
WriteUninstaller "${UNINSTALLER_OUT_FILE}"
|
||||
!insertmacro quitSuccess
|
||||
!else
|
||||
!insertmacro check64BitAndSetRegView
|
||||
|
||||
!ifdef ONE_CLICK
|
||||
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
|
||||
!else
|
||||
${IfNot} ${UAC_IsInnerInstance}
|
||||
!insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
|
||||
${EndIf}
|
||||
!endif
|
||||
|
||||
!insertmacro initMultiUser
|
||||
|
||||
!ifmacrodef customInit
|
||||
!insertmacro customInit
|
||||
!endif
|
||||
|
||||
!ifmacrodef addLicenseFiles
|
||||
InitPluginsDir
|
||||
!insertmacro addLicenseFiles
|
||||
!endif
|
||||
!endif
|
||||
FunctionEnd
|
||||
|
||||
!ifndef BUILD_UNINSTALLER
|
||||
!include "installUtil.nsh"
|
||||
!endif
|
||||
|
||||
Section "install" INSTALL_SECTION_ID
|
||||
!ifndef BUILD_UNINSTALLER
|
||||
# If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed.
|
||||
# For a non-silent install, the elevation will be triggered when the install mode is selected in the UI,
|
||||
# but that won't be executed when silent.
|
||||
!ifndef INSTALL_MODE_PER_ALL_USERS
|
||||
!ifndef ONE_CLICK
|
||||
${if} $hasPerMachineInstallation == "1" # set in onInit by initMultiUser
|
||||
${andIf} ${Silent}
|
||||
${ifNot} ${UAC_IsAdmin}
|
||||
ShowWindow $HWNDPARENT ${SW_HIDE}
|
||||
!insertmacro UAC_RunElevated
|
||||
${Switch} $0
|
||||
${Case} 0
|
||||
${Break}
|
||||
${Case} 1223 ;user aborted
|
||||
${Break}
|
||||
${Default}
|
||||
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0"
|
||||
${Break}
|
||||
${EndSwitch}
|
||||
Quit
|
||||
${else}
|
||||
!insertmacro setInstallModePerAllUsers
|
||||
${endIf}
|
||||
${endIf}
|
||||
!endif
|
||||
!endif
|
||||
!include "installSection.nsh"
|
||||
!endif
|
||||
SectionEnd
|
||||
|
||||
Function setInstallSectionSpaceRequired
|
||||
!insertmacro setSpaceRequired ${INSTALL_SECTION_ID}
|
||||
FunctionEnd
|
||||
|
||||
!ifdef BUILD_UNINSTALLER
|
||||
!include "uninstaller.nsh"
|
||||
!endif
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
directories:
|
||||
output: dist
|
||||
buildResources: build
|
||||
files: []
|
||||
electronVersion: 28.3.3
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
Copyright (c) Electron contributors
|
||||
Copyright (c) 2013-2020 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
{"file_format_version": "1.0.0", "ICD": {"library_path": ".\\vk_swiftshader.dll", "api_version": "1.0.5"}}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* electron-builder 配置文件
|
||||
* 处理 winCodeSign 解压时的符号链接问题
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// 修复 winCodeSign 解压问题
|
||||
function fixWinCodeSign() {
|
||||
const cacheDir = path.join(
|
||||
process.env.LOCALAPPDATA || process.env.HOME,
|
||||
'electron-builder',
|
||||
'Cache',
|
||||
'winCodeSign'
|
||||
);
|
||||
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sevenZip = path.join(__dirname, 'node_modules', '7zip-bin', 'win', 'x64', '7za.exe');
|
||||
|
||||
if (!fs.existsSync(sevenZip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(cacheDir);
|
||||
const archives = files.filter(f => f.endsWith('.7z'));
|
||||
|
||||
for (const archive of archives) {
|
||||
const archivePath = path.join(cacheDir, archive);
|
||||
const extractDir = path.join(cacheDir, archive.replace('.7z', ''));
|
||||
|
||||
// 如果已经解压过,检查是否有 darwin 目录,如果有则删除
|
||||
if (fs.existsSync(extractDir)) {
|
||||
const darwinPath = path.join(extractDir, 'darwin');
|
||||
if (fs.existsSync(darwinPath)) {
|
||||
try {
|
||||
fs.rmSync(darwinPath, { recursive: true, force: true });
|
||||
console.log(`已删除 darwin 目录: ${darwinPath}`);
|
||||
} catch (e) {
|
||||
// 忽略删除错误
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`正在解压 ${archive},跳过符号链接...`);
|
||||
// 使用 -snl 跳过符号链接,-xr!darwin 排除 darwin 目录
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -xr!darwin -y`;
|
||||
execSync(cmd, { stdio: 'pipe', cwd: cacheDir });
|
||||
console.log(`成功解压 ${archive}`);
|
||||
} catch (error) {
|
||||
// 如果解压失败,尝试只跳过符号链接
|
||||
try {
|
||||
console.log(`尝试使用 -snl 选项重新解压 ${archive}...`);
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -y`;
|
||||
execSync(cmd, { stdio: 'pipe', cwd: cacheDir });
|
||||
// 删除 darwin 目录(Windows 上不需要)
|
||||
const darwinPath = path.join(extractDir, 'darwin');
|
||||
if (fs.existsSync(darwinPath)) {
|
||||
try {
|
||||
fs.rmSync(darwinPath, { recursive: true, force: true });
|
||||
console.log(`已删除 darwin 目录`);
|
||||
} catch (e) {
|
||||
// 忽略删除错误
|
||||
}
|
||||
}
|
||||
console.log(`成功解压 ${archive}`);
|
||||
} catch (e) {
|
||||
console.warn(`解压 ${archive} 时出错:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误,继续构建
|
||||
console.warn('修复 winCodeSign 时出错:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 在配置加载时运行修复
|
||||
fixWinCodeSign();
|
||||
|
||||
module.exports = {
|
||||
appId: 'com.assetpro.app',
|
||||
productName: 'AssetPro',
|
||||
directories: {
|
||||
output: 'dist',
|
||||
buildResources: 'build'
|
||||
},
|
||||
files: [
|
||||
'dist-electron/**/*',
|
||||
'dist-renderer/**/*',
|
||||
'package.json'
|
||||
],
|
||||
afterSign: null,
|
||||
win: {
|
||||
target: [
|
||||
{
|
||||
target: 'nsis',
|
||||
arch: ['x64']
|
||||
}
|
||||
],
|
||||
sign: null,
|
||||
signingHashAlgorithms: [],
|
||||
signDlls: false,
|
||||
forceCodeSigning: false,
|
||||
verifyUpdateCodeSignature: false
|
||||
},
|
||||
nsis: {
|
||||
oneClick: false,
|
||||
allowToChangeInstallationDirectory: true
|
||||
},
|
||||
beforePack: async (context) => {
|
||||
// 在打包前再次尝试修复
|
||||
fixWinCodeSign();
|
||||
|
||||
// 确保 dist-renderer 存在
|
||||
const distRendererPath = path.join(__dirname, 'dist-renderer');
|
||||
if (!fs.existsSync(distRendererPath)) {
|
||||
console.warn('警告: dist-renderer 目录不存在,请先运行 vite build');
|
||||
} else {
|
||||
console.log('dist-renderer 目录存在,将被复制到 resources/dist');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* IPC 通信处理器
|
||||
* 安全地暴露主进程功能给渲染进程
|
||||
*/
|
||||
import { ipcMain, app } from 'electron'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { getSettings, saveSettings } from './store'
|
||||
|
||||
// IPC 通道名称常量
|
||||
export const IPC_CHANNELS = {
|
||||
// 文件操作
|
||||
SAVE_FILE: 'file:save',
|
||||
READ_FILE: 'file:read',
|
||||
// 设置操作
|
||||
GET_SETTINGS: 'settings:get',
|
||||
SAVE_SETTINGS: 'settings:save'
|
||||
} as const
|
||||
|
||||
/**
|
||||
* 设置所有 IPC 处理器
|
||||
*/
|
||||
export function setupIpcHandlers() {
|
||||
// ============================================
|
||||
// 文件操作相关 IPC
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 保存文件到桌面
|
||||
* @param content - 要保存的文件内容
|
||||
* @param filename - 文件名(默认为 demo-note.txt)
|
||||
* @returns 保存结果
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.SAVE_FILE,
|
||||
async (_event, content: string, filename: string = 'demo-note.txt') => {
|
||||
try {
|
||||
// 获取桌面路径
|
||||
const desktopPath = app.getPath('desktop')
|
||||
const filePath = path.join(desktopPath, filename)
|
||||
|
||||
// 写入文件(使用 UTF-8 编码)
|
||||
await fs.writeFile(filePath, content, 'utf-8')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `文件已保存到: ${filePath}`,
|
||||
path: filePath
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
console.error('保存文件失败:', errorMessage)
|
||||
return {
|
||||
success: false,
|
||||
message: `保存失败: ${errorMessage}`,
|
||||
path: null
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 从桌面读取文件
|
||||
* @param filename - 文件名(默认为 demo-note.txt)
|
||||
* @returns 文件内容
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.READ_FILE,
|
||||
async (_event, filename: string = 'demo-note.txt') => {
|
||||
try {
|
||||
// 获取桌面路径
|
||||
const desktopPath = app.getPath('desktop')
|
||||
const filePath = path.join(desktopPath, filename)
|
||||
|
||||
// 检查文件是否存在
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
message: '文件不存在,请先保存一个文件',
|
||||
content: null
|
||||
}
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '文件读取成功',
|
||||
content
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
console.error('读取文件失败:', errorMessage)
|
||||
return {
|
||||
success: false,
|
||||
message: `读取失败: ${errorMessage}`,
|
||||
content: null
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// ============================================
|
||||
// 用户设置相关 IPC
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 获取用户设置
|
||||
* @param key - 可选的设置键名,不传则返回所有设置
|
||||
* @returns 设置值
|
||||
*/
|
||||
ipcMain.handle(IPC_CHANNELS.GET_SETTINGS, async (_event, key?: string) => {
|
||||
try {
|
||||
const settings = getSettings(key)
|
||||
return {
|
||||
success: true,
|
||||
data: settings
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
console.error('获取设置失败:', errorMessage)
|
||||
return {
|
||||
success: false,
|
||||
data: null,
|
||||
message: errorMessage
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 保存用户设置
|
||||
* @param key - 设置键名
|
||||
* @param value - 设置值
|
||||
* @returns 保存结果
|
||||
*/
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.SAVE_SETTINGS,
|
||||
async (_event, key: string, value: unknown) => {
|
||||
try {
|
||||
saveSettings(key, value)
|
||||
return {
|
||||
success: true,
|
||||
message: '设置已保存'
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
console.error('保存设置失败:', errorMessage)
|
||||
return {
|
||||
success: false,
|
||||
message: `保存失败: ${errorMessage}`
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* Electron 主进程入口文件
|
||||
* 负责创建窗口、管理应用生命周期
|
||||
*/
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
import path from 'path'
|
||||
import { setupIpcHandlers } from './ipc'
|
||||
import { initStore, getWindowBounds, saveWindowBounds } from './store'
|
||||
|
||||
// 开发环境下的 Vite 开发服务器地址
|
||||
const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
|
||||
|
||||
// 主窗口引用
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
/**
|
||||
* 创建主窗口
|
||||
*/
|
||||
function createWindow() {
|
||||
// 获取保存的窗口尺寸
|
||||
const bounds = getWindowBounds()
|
||||
|
||||
// 构建窗口配置
|
||||
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
webPreferences: {
|
||||
// 安全配置:启用上下文隔离
|
||||
contextIsolation: true,
|
||||
// 安全配置:禁用 Node.js 集成
|
||||
nodeIntegration: false,
|
||||
// 预加载脚本路径
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
// 启用沙盒模式
|
||||
sandbox: false // 需要设为 false 才能使用 preload
|
||||
},
|
||||
// 窗口样式
|
||||
title: 'AssetPro',
|
||||
show: false, // 先隐藏,等准备好再显示
|
||||
backgroundColor: '#ffffff',
|
||||
// 无边框窗口
|
||||
frame: false
|
||||
}
|
||||
|
||||
// 如果有保存的位置,使用保存的位置;否则居中显示
|
||||
if (bounds.x !== undefined && bounds.y !== undefined) {
|
||||
windowOptions.x = bounds.x
|
||||
windowOptions.y = bounds.y
|
||||
} else {
|
||||
windowOptions.center = true // 居中显示
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow(windowOptions)
|
||||
|
||||
// 窗口准备好后显示
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow?.show()
|
||||
})
|
||||
|
||||
// 如果 3 秒后还没显示,强制显示窗口(防止页面加载失败导致窗口不显示)
|
||||
setTimeout(() => {
|
||||
if (mainWindow && !mainWindow.isVisible()) {
|
||||
console.log('超时后强制显示窗口')
|
||||
mainWindow.show()
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
// 监听窗口关闭事件,保存窗口位置和大小
|
||||
mainWindow.on('close', () => {
|
||||
if (mainWindow) {
|
||||
const bounds = mainWindow.getBounds()
|
||||
saveWindowBounds(bounds)
|
||||
}
|
||||
})
|
||||
|
||||
// 加载页面
|
||||
if (VITE_DEV_SERVER_URL) {
|
||||
// 开发环境:加载 Vite 开发服务器
|
||||
mainWindow.loadURL(VITE_DEV_SERVER_URL)
|
||||
// 开发者工具默认关闭,可按 F12 手动打开
|
||||
} else {
|
||||
// 生产环境:加载打包后的文件
|
||||
const fs = require('fs')
|
||||
|
||||
// dist-renderer 被打包到 asar 中,从 asar 内部加载
|
||||
const possiblePaths = [
|
||||
path.join(app.getAppPath(), 'dist-renderer', 'index.html'), // asar 内部(主要路径)
|
||||
path.join(process.resourcesPath, 'dist-renderer', 'index.html'), // resources/dist-renderer(如果 extraResources 存在)
|
||||
path.join(process.resourcesPath, 'dist', 'index.html'), // resources/dist(备用)
|
||||
]
|
||||
|
||||
console.log('尝试加载页面,检查以下路径:')
|
||||
possiblePaths.forEach(p => {
|
||||
const exists = fs.existsSync(p)
|
||||
console.log(` ${exists ? '✓' : '✗'} ${p}`)
|
||||
})
|
||||
console.log('process.resourcesPath:', process.resourcesPath)
|
||||
console.log('app.getAppPath():', app.getAppPath())
|
||||
|
||||
let indexPath: string | null = null
|
||||
for (const testPath of possiblePaths) {
|
||||
if (fs.existsSync(testPath)) {
|
||||
indexPath = testPath
|
||||
console.log('找到页面文件:', indexPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!indexPath) {
|
||||
console.error('无法找到 index.html 文件!')
|
||||
// 显示一个错误页面
|
||||
mainWindow.loadURL('data:text/html,<h1>无法找到页面文件</h1><p>请检查控制台输出</p>')
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 loadFile 加载文件
|
||||
mainWindow.loadFile(indexPath).catch((error) => {
|
||||
console.error('loadFile 失败:', error)
|
||||
// 如果 loadFile 失败,尝试使用 loadURL
|
||||
// Windows 路径需要特殊处理
|
||||
let fileUrl = indexPath.replace(/\\/g, '/')
|
||||
if (fileUrl.match(/^[A-Z]:/)) {
|
||||
fileUrl = `file:///${fileUrl}`
|
||||
} else {
|
||||
fileUrl = `file://${fileUrl}`
|
||||
}
|
||||
console.log('尝试使用 loadURL:', fileUrl)
|
||||
mainWindow.loadURL(fileUrl).catch((urlError) => {
|
||||
console.error('loadURL 也失败:', urlError)
|
||||
// 显示错误页面
|
||||
mainWindow.loadURL('data:text/html,<h1>页面加载失败</h1><p>请查看控制台错误信息</p>')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 监听页面加载错误
|
||||
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
|
||||
console.error('页面加载失败:', errorCode, errorDescription)
|
||||
// 开发者工具默认关闭,可按 F12 手动打开
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用初始化
|
||||
*/
|
||||
app.whenReady().then(() => {
|
||||
// 初始化 electron-store
|
||||
initStore()
|
||||
|
||||
// 设置 IPC 处理器
|
||||
setupIpcHandlers()
|
||||
|
||||
// 创建窗口
|
||||
createWindow()
|
||||
|
||||
// macOS 特殊处理:点击 dock 图标时重新创建窗口
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 所有窗口关闭时退出应用(macOS 除外)
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Electron Preload 脚本
|
||||
*
|
||||
* 这是渲染进程和主进程之间的安全桥梁
|
||||
* 通过 contextBridge 安全地暴露 API 给渲染进程
|
||||
*
|
||||
* 安全说明:
|
||||
* - contextIsolation: true 确保渲染进程无法直接访问 Node.js API
|
||||
* - 所有暴露的方法都经过 IPC 通道与主进程通信
|
||||
* - 不要在这里导入 electron-store 或其他 Node.js 模块
|
||||
*/
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
// IPC 通道名称(与 ipc.ts 保持一致)
|
||||
const IPC_CHANNELS = {
|
||||
SAVE_FILE: 'file:save',
|
||||
READ_FILE: 'file:read',
|
||||
GET_SETTINGS: 'settings:get',
|
||||
SAVE_SETTINGS: 'settings:save'
|
||||
} as const
|
||||
|
||||
// API 响应类型定义
|
||||
interface FileOperationResult {
|
||||
success: boolean
|
||||
message: string
|
||||
path?: string | null
|
||||
content?: string | null
|
||||
}
|
||||
|
||||
interface SettingsOperationResult {
|
||||
success: boolean
|
||||
message?: string
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
// 暴露给渲染进程的 API 接口
|
||||
interface ElectronAPI {
|
||||
// 文件操作
|
||||
file: {
|
||||
save: (content: string, filename?: string) => Promise<FileOperationResult>
|
||||
read: (filename?: string) => Promise<FileOperationResult>
|
||||
}
|
||||
// 设置操作
|
||||
settings: {
|
||||
get: (key?: string) => Promise<SettingsOperationResult>
|
||||
save: (key: string, value: unknown) => Promise<SettingsOperationResult>
|
||||
}
|
||||
}
|
||||
|
||||
// 通过 contextBridge 安全地暴露 API
|
||||
const electronAPI: ElectronAPI = {
|
||||
// ============================================
|
||||
// 文件操作 API
|
||||
// ============================================
|
||||
file: {
|
||||
/**
|
||||
* 保存文件到桌面
|
||||
* @param content - 文件内容
|
||||
* @param filename - 文件名(可选,默认为 demo-note.txt)
|
||||
*/
|
||||
save: (content: string, filename?: string) => {
|
||||
return ipcRenderer.invoke(IPC_CHANNELS.SAVE_FILE, content, filename)
|
||||
},
|
||||
|
||||
/**
|
||||
* 从桌面读取文件
|
||||
* @param filename - 文件名(可选,默认为 demo-note.txt)
|
||||
*/
|
||||
read: (filename?: string) => {
|
||||
return ipcRenderer.invoke(IPC_CHANNELS.READ_FILE, filename)
|
||||
}
|
||||
},
|
||||
|
||||
// ============================================
|
||||
// 设置操作 API
|
||||
// ============================================
|
||||
settings: {
|
||||
/**
|
||||
* 获取用户设置
|
||||
* @param key - 设置键名(可选,不传返回所有设置)
|
||||
*/
|
||||
get: (key?: string) => {
|
||||
return ipcRenderer.invoke(IPC_CHANNELS.GET_SETTINGS, key)
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存用户设置
|
||||
* @param key - 设置键名
|
||||
* @param value - 设置值
|
||||
*/
|
||||
save: (key: string, value: unknown) => {
|
||||
return ipcRenderer.invoke(IPC_CHANNELS.SAVE_SETTINGS, key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露 API 到 window.electronAPI
|
||||
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
|
||||
|
||||
// TypeScript 类型声明(供渲染进程使用)
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* electron-store 配置和管理
|
||||
*
|
||||
* 重要说明:
|
||||
* electron-store 必须在主进程中初始化和使用!
|
||||
*
|
||||
* 配置陷阱:
|
||||
* 1. electron-store 使用 Node.js 的 fs 模块,只能在主进程中运行
|
||||
* 2. 渲染进程无法直接使用 electron-store(contextIsolation: true)
|
||||
* 3. 必须通过 IPC 通信让主进程代理所有 store 操作
|
||||
* 4. 不要尝试在 preload 中导入 electron-store
|
||||
*/
|
||||
import Store from 'electron-store'
|
||||
|
||||
// 用户设置的类型定义
|
||||
interface UserSettings {
|
||||
// 计数器值
|
||||
count: number
|
||||
// 主题设置
|
||||
theme: 'light' | 'dark' | 'system'
|
||||
// 其他可扩展的设置...
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
// 窗口边界的类型定义
|
||||
interface WindowBounds {
|
||||
width: number
|
||||
height: number
|
||||
x?: number
|
||||
y?: number
|
||||
}
|
||||
|
||||
// Store 的 Schema 定义
|
||||
interface StoreSchema {
|
||||
// 用户设置
|
||||
settings: UserSettings
|
||||
// 窗口位置和大小
|
||||
windowBounds: WindowBounds
|
||||
}
|
||||
|
||||
// Store 实例(延迟初始化)
|
||||
let store: Store<StoreSchema> | null = null
|
||||
|
||||
/**
|
||||
* 初始化 Store
|
||||
* 必须在 app.whenReady() 之后调用
|
||||
*/
|
||||
export function initStore(): void {
|
||||
if (store) {
|
||||
console.warn('Store 已经初始化过了')
|
||||
return
|
||||
}
|
||||
|
||||
store = new Store<StoreSchema>({
|
||||
// 存储文件名
|
||||
name: 'asset-pro-config',
|
||||
|
||||
// 默认值
|
||||
defaults: {
|
||||
settings: {
|
||||
count: 0,
|
||||
theme: 'system'
|
||||
},
|
||||
windowBounds: {
|
||||
width: 1600,
|
||||
height: 900
|
||||
}
|
||||
},
|
||||
|
||||
// Schema 验证(可选但推荐)
|
||||
schema: {
|
||||
settings: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
count: { type: 'number', default: 0 },
|
||||
theme: {
|
||||
type: 'string',
|
||||
enum: ['light', 'dark', 'system'],
|
||||
default: 'system'
|
||||
}
|
||||
},
|
||||
default: {}
|
||||
},
|
||||
windowBounds: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
width: { type: 'number', default: 1600 },
|
||||
height: { type: 'number', default: 900 },
|
||||
x: { type: 'number' },
|
||||
y: { type: 'number' }
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Store 初始化完成,存储路径:', store.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Store 实例
|
||||
* @throws 如果 Store 未初始化
|
||||
*/
|
||||
function getStore(): Store<StoreSchema> {
|
||||
if (!store) {
|
||||
throw new Error('Store 未初始化,请先调用 initStore()')
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户设置
|
||||
* @param key - 可选的设置键名
|
||||
* @returns 设置值或所有设置
|
||||
*/
|
||||
export function getSettings(key?: string): unknown {
|
||||
const s = getStore()
|
||||
if (key) {
|
||||
return s.get(`settings.${key}`)
|
||||
}
|
||||
return s.get('settings')
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户设置
|
||||
* @param key - 设置键名
|
||||
* @param value - 设置值
|
||||
*/
|
||||
export function saveSettings(key: string, value: unknown): void {
|
||||
const s = getStore()
|
||||
s.set(`settings.${key}`, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取窗口边界设置
|
||||
* @returns 窗口边界
|
||||
*/
|
||||
export function getWindowBounds(): WindowBounds {
|
||||
const s = getStore()
|
||||
const bounds = s.get('windowBounds')
|
||||
|
||||
// 强制使用新尺寸 1600x900
|
||||
const newBounds: WindowBounds = {
|
||||
width: 1600,
|
||||
height: 900
|
||||
}
|
||||
|
||||
// 如果之前保存了位置且位置有效,保留位置信息
|
||||
// 否则不设置 x 和 y,让窗口居中显示
|
||||
if (bounds.x !== undefined && bounds.y !== undefined) {
|
||||
newBounds.x = bounds.x
|
||||
newBounds.y = bounds.y
|
||||
}
|
||||
|
||||
// 如果尺寸不是 1600x900,则更新并保存
|
||||
if (bounds.width !== 1600 || bounds.height !== 900) {
|
||||
s.set('windowBounds', newBounds)
|
||||
}
|
||||
|
||||
return newBounds
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存窗口边界设置
|
||||
* @param bounds - 窗口边界
|
||||
*/
|
||||
export function saveWindowBounds(bounds: WindowBounds): void {
|
||||
const s = getStore()
|
||||
s.set('windowBounds', bounds)
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" />
|
||||
<title>AssetPro</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "asset-pro",
|
||||
"version": "1.0.0",
|
||||
"description": "Electron + React + TypeScript 资产管理应用",
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"prebuild": "node scripts/kill-app.js && node scripts/fix-wincodesign.js",
|
||||
"build": "tsc && vite build && node scripts/build-with-fix.js",
|
||||
"preview": "vite preview",
|
||||
"electron:dev": "vite",
|
||||
"electron:build": "vite build && node scripts/build-with-fix.js"
|
||||
},
|
||||
"keywords": [
|
||||
"electron",
|
||||
"react",
|
||||
"typescript",
|
||||
"vite"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"electron-store": "^8.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^7.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^28.0.0",
|
||||
"electron-builder": "^24.9.1",
|
||||
"lucide-react": "^0.294.0",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwind-merge": "^2.1.0",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.3.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-plugin-electron": "^0.28.0",
|
||||
"vite-plugin-electron-renderer": "^0.14.5",
|
||||
"wait-on": "^7.2.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* 构建脚本包装器
|
||||
* 在 electron-builder 运行后自动修复 winCodeSign 解压问题
|
||||
*/
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const cacheDir = path.join(
|
||||
process.env.LOCALAPPDATA || process.env.HOME,
|
||||
'electron-builder',
|
||||
'Cache',
|
||||
'winCodeSign'
|
||||
);
|
||||
|
||||
const sevenZip = path.join(__dirname, '..', 'node_modules', '7zip-bin', 'win', 'x64', '7za.exe');
|
||||
|
||||
function fixWinCodeSign() {
|
||||
if (!fs.existsSync(cacheDir) || !fs.existsSync(sevenZip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(cacheDir);
|
||||
const archives = files.filter(f => f.endsWith('.7z'));
|
||||
|
||||
for (const archive of archives) {
|
||||
const archivePath = path.join(cacheDir, archive);
|
||||
const extractDir = path.join(cacheDir, archive.replace('.7z', ''));
|
||||
|
||||
// 如果 .7z 存在但解压目录不存在或有问题,尝试修复
|
||||
if (fs.existsSync(archivePath)) {
|
||||
const darwinPath = path.join(extractDir, 'darwin');
|
||||
const needsFix = !fs.existsSync(extractDir) ||
|
||||
(fs.existsSync(extractDir) && fs.existsSync(darwinPath));
|
||||
|
||||
if (needsFix) {
|
||||
try {
|
||||
console.log(`修复 winCodeSign 归档: ${archive}`);
|
||||
// 删除旧的解压目录
|
||||
if (fs.existsSync(extractDir)) {
|
||||
try {
|
||||
fs.rmSync(extractDir, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
// 忽略删除错误
|
||||
}
|
||||
}
|
||||
// 重新解压,跳过符号链接和 darwin 目录
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -xr!darwin -y`;
|
||||
execSync(cmd, { stdio: 'pipe', cwd: cacheDir });
|
||||
console.log(`成功修复: ${archive}`);
|
||||
} catch (error) {
|
||||
// 如果失败,尝试只跳过符号链接
|
||||
try {
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -y`;
|
||||
execSync(cmd, { stdio: 'pipe', cwd: cacheDir });
|
||||
if (fs.existsSync(darwinPath)) {
|
||||
fs.rmSync(darwinPath, { recursive: true, force: true });
|
||||
}
|
||||
console.log(`成功修复: ${archive}`);
|
||||
} catch (e) {
|
||||
console.warn(`修复 ${archive} 失败:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误
|
||||
}
|
||||
}
|
||||
|
||||
// 运行 electron-builder
|
||||
const electronBuilder = spawn('electron-builder', process.argv.slice(2), {
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env: {
|
||||
...process.env,
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
||||
}
|
||||
});
|
||||
|
||||
// 监听 electron-builder 的输出,当检测到解压错误时尝试修复
|
||||
let outputBuffer = '';
|
||||
electronBuilder.stdout?.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
outputBuffer += text;
|
||||
|
||||
// 检测到解压错误
|
||||
if (text.includes('Cannot create symbolic link') ||
|
||||
text.includes('Sub items Errors')) {
|
||||
setTimeout(() => {
|
||||
fixWinCodeSign();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
electronBuilder.stderr?.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
outputBuffer += text;
|
||||
|
||||
// 检测到解压错误
|
||||
if (text.includes('Cannot create symbolic link') ||
|
||||
text.includes('Sub items Errors')) {
|
||||
setTimeout(() => {
|
||||
fixWinCodeSign();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
electronBuilder.on('close', (code) => {
|
||||
// 构建完成后再次尝试修复
|
||||
fixWinCodeSign();
|
||||
process.exit(code);
|
||||
});
|
||||
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* 修复 winCodeSign 解压时的符号链接问题
|
||||
* 在 electron-builder 尝试解压之前,手动解压并跳过 darwin 目录
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const cacheDir = path.join(process.env.LOCALAPPDATA || process.env.HOME, 'electron-builder', 'Cache', 'winCodeSign');
|
||||
const sevenZip = path.join(__dirname, '..', 'node_modules', '7zip-bin', 'win', 'x64', '7za.exe');
|
||||
|
||||
// 查找所有 .7z 文件
|
||||
function findAndFixArchives() {
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(cacheDir);
|
||||
const archives = files.filter(f => f.endsWith('.7z'));
|
||||
|
||||
for (const archive of archives) {
|
||||
const archivePath = path.join(cacheDir, archive);
|
||||
const extractDir = path.join(cacheDir, archive.replace('.7z', ''));
|
||||
|
||||
// 如果已经解压过,跳过
|
||||
if (fs.existsSync(extractDir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`正在解压 ${archive},跳过符号链接...`);
|
||||
// 使用 -snl 跳过符号链接,-xr!darwin 排除 darwin 目录
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -xr!darwin -y`;
|
||||
execSync(cmd, { stdio: 'inherit', cwd: cacheDir });
|
||||
console.log(`成功解压 ${archive}`);
|
||||
} catch (error) {
|
||||
// 如果失败,尝试只跳过符号链接
|
||||
try {
|
||||
console.log(`尝试使用 -snl 选项重新解压...`);
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -y`;
|
||||
execSync(cmd, { stdio: 'inherit', cwd: cacheDir });
|
||||
// 删除 darwin 目录
|
||||
const darwinPath = path.join(extractDir, 'darwin');
|
||||
if (fs.existsSync(darwinPath)) {
|
||||
fs.rmSync(darwinPath, { recursive: true, force: true });
|
||||
}
|
||||
console.log(`成功解压 ${archive}`);
|
||||
} catch (e) {
|
||||
console.warn(`解压 ${archive} 时出错:`, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在构建前运行
|
||||
if (require.main === module) {
|
||||
findAndFixArchives();
|
||||
}
|
||||
|
||||
module.exports = { findAndFixArchives };
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 构建前脚本:关闭正在运行的 asset-pro.exe
|
||||
*/
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
try {
|
||||
// 在 Windows 上查找并关闭 asset-pro.exe
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
// 查找进程
|
||||
const result = execSync('tasklist /FI "IMAGENAME eq asset-pro.exe" /FO CSV', {
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
if (result.includes('asset-pro.exe')) {
|
||||
console.log('检测到正在运行的 asset-pro.exe,正在关闭...');
|
||||
// 强制关闭进程
|
||||
execSync('taskkill /F /IM asset-pro.exe', {
|
||||
stdio: 'pipe'
|
||||
});
|
||||
console.log('已关闭 asset-pro.exe');
|
||||
// 等待一下确保进程完全关闭
|
||||
setTimeout(() => {}, 500);
|
||||
} else {
|
||||
console.log('未检测到正在运行的 asset-pro.exe');
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果 taskkill 失败(进程不存在),忽略错误
|
||||
if (!error.message.includes('not found') && !error.message.includes('找不到')) {
|
||||
console.log('关闭进程时出错(可能进程不存在):', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略所有错误,继续构建
|
||||
console.log('检查进程时出错:', error.message);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* 监控并修复 winCodeSign 解压问题
|
||||
* 在 electron-builder 运行期间持续检查并修复
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const cacheDir = path.join(
|
||||
process.env.LOCALAPPDATA || process.env.HOME,
|
||||
'electron-builder',
|
||||
'Cache',
|
||||
'winCodeSign'
|
||||
);
|
||||
|
||||
const sevenZip = path.join(__dirname, '..', 'node_modules', '7zip-bin', 'win', 'x64', '7za.exe');
|
||||
|
||||
function fixArchive(archivePath, extractDir) {
|
||||
if (!fs.existsSync(sevenZip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// 首先尝试跳过符号链接和 darwin 目录
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -xr!darwin -y`;
|
||||
execSync(cmd, { stdio: 'pipe', cwd: path.dirname(archivePath) });
|
||||
return true;
|
||||
} catch (error) {
|
||||
try {
|
||||
// 如果失败,只跳过符号链接
|
||||
const cmd = `"${sevenZip}" x "${archivePath}" -o"${extractDir}" -snl -y`;
|
||||
execSync(cmd, { stdio: 'pipe', cwd: path.dirname(archivePath) });
|
||||
// 删除 darwin 目录
|
||||
const darwinPath = path.join(extractDir, 'darwin');
|
||||
if (fs.existsSync(darwinPath)) {
|
||||
try {
|
||||
fs.rmSync(darwinPath, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
// 忽略删除错误
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function watchAndFix() {
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkInterval = setInterval(() => {
|
||||
try {
|
||||
const files = fs.readdirSync(cacheDir);
|
||||
const archives = files.filter(f => f.endsWith('.7z'));
|
||||
|
||||
for (const archive of archives) {
|
||||
const archivePath = path.join(cacheDir, archive);
|
||||
const extractDir = path.join(cacheDir, archive.replace('.7z', ''));
|
||||
|
||||
// 如果 .7z 文件存在但解压目录不存在或损坏,尝试修复
|
||||
if (fs.existsSync(archivePath)) {
|
||||
const stats = fs.statSync(archivePath);
|
||||
const archiveTime = stats.mtime.getTime();
|
||||
|
||||
// 检查解压目录是否存在且完整
|
||||
let needsFix = false;
|
||||
if (!fs.existsSync(extractDir)) {
|
||||
needsFix = true;
|
||||
} else {
|
||||
// 检查是否有 darwin 目录(可能导致问题)
|
||||
const darwinPath = path.join(extractDir, 'darwin');
|
||||
if (fs.existsSync(darwinPath)) {
|
||||
needsFix = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsFix) {
|
||||
console.log(`检测到需要修复的归档: ${archive}`);
|
||||
if (fixArchive(archivePath, extractDir)) {
|
||||
console.log(`成功修复: ${archive}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误,继续监控
|
||||
}
|
||||
}, 1000); // 每秒检查一次
|
||||
|
||||
// 30 秒后停止监控(构建应该已经完成或失败)
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval);
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
watchAndFix();
|
||||
}
|
||||
|
||||
module.exports = { watchAndFix, fixArchive };
|
||||
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { HashRouter, Routes, Route, Navigate } from 'react-router-dom'
|
||||
import Login from '@/pages/Login'
|
||||
import MainLayout from '@/layouts/MainLayout'
|
||||
import AssetLibrary from '@/pages/assets/AssetLibrary'
|
||||
|
||||
// Placeholder for other pages to avoid 404s during demo
|
||||
const PlaceholderPage = ({ title }: { title: string }) => (
|
||||
<div className="flex items-center justify-center h-full text-slate-400">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold mb-2">{title}</h2>
|
||||
<p>功能开发中...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Login />} />
|
||||
|
||||
{/* Main Application Routes */}
|
||||
<Route path="/app" element={<MainLayout />}>
|
||||
<Route index element={<Navigate to="/app/plugins" replace />} />
|
||||
<Route path="plugins" element={<AssetLibrary />} />
|
||||
<Route path="components" element={<PlaceholderPage title="组件库" />} />
|
||||
<Route path="tools" element={<PlaceholderPage title="工具库" />} />
|
||||
<Route path="prototypes" element={<PlaceholderPage title="原型库" />} />
|
||||
<Route path="design" element={<PlaceholderPage title="设计库" />} />
|
||||
<Route path="projects" element={<PlaceholderPage title="项目库" />} />
|
||||
<Route path="files" element={<PlaceholderPage title="文件库" />} />
|
||||
<Route path="cases" element={<PlaceholderPage title="案例库" />} />
|
||||
<Route path="pending" element={<PlaceholderPage title="待审核" />} />
|
||||
</Route>
|
||||
|
||||
{/* Legacy/Redirects */}
|
||||
<Route path="/dashboard" element={<Navigate to="/app" replace />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { Search, Plus, Power, User } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
export function Header() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div className="h-16 border-b bg-white flex items-center justify-between px-6 shadow-sm z-10 relative drag-region">
|
||||
{/* Left: Logo */}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 bg-[#0e7490] rounded-md flex items-center justify-center">
|
||||
{/* Simple Logo Icon Placeholder */}
|
||||
<svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span className="text-xl font-bold text-slate-800">资产库</span>
|
||||
</div>
|
||||
|
||||
{/* Center: Search (aligned slightly left or center) */}
|
||||
<div className="flex-1 max-w-xl mx-12 flex justify-center">
|
||||
<div className="relative w-full no-drag">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
|
||||
<Input
|
||||
placeholder="搜索资源名称/标签/ID..."
|
||||
className="pl-10 bg-slate-50 border-slate-200 focus-visible:ring-[#0e7490]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-4 no-drag">
|
||||
<Button className="bg-[#e0f2fe] text-[#0e7490] hover:bg-[#bae6fd] border-none shadow-none gap-2 font-medium">
|
||||
<Plus className="w-4 h-4" />
|
||||
上传资源
|
||||
</Button>
|
||||
|
||||
<div className="h-6 w-px bg-slate-200 mx-2"></div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 bg-slate-100 rounded-full flex items-center justify-center overflow-hidden border border-slate-200">
|
||||
<User className="w-4 h-4 text-slate-400" />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-slate-600">欢迎!XXX</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-slate-400 hover:text-slate-900 ml-2 hover:bg-slate-100 rounded-full"
|
||||
onClick={() => navigate('/')}
|
||||
>
|
||||
<Power className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue