This commit is contained in:
DESKTOP-PB0N82B\admin 2026-01-08 13:55:39 +08:00
parent 7fa53d2fd7
commit 6895975e9e
120 changed files with 174869 additions and 0 deletions

View File

@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(dir:*)"
]
}
}

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

5
components.json Normal file
View File

@ -0,0 +1,5 @@
{
"compilerOptions": {
"useDefineForClassFields": true
}
}

305
dist-electron/main.js Normal file
View File

@ -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();
}
});

51
dist-electron/preload.js Normal file
View File

@ -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

15
dist-renderer/index.html Normal file
View File

@ -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>

BIN
dist/asset-pro Setup 1.0.0.exe vendored Normal file

Binary file not shown.

BIN
dist/asset-pro Setup 1.0.0.exe.blockmap vendored Normal file

Binary file not shown.

217
dist/builder-debug.yml vendored Normal file
View File

@ -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

5
dist/builder-effective-config.yaml vendored Normal file
View File

@ -0,0 +1,5 @@
directories:
output: dist
buildResources: build
files: []
electronVersion: 28.3.3

21
dist/win-unpacked/LICENSE.electron.txt vendored Normal file
View File

@ -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.

164587
dist/win-unpacked/LICENSES.chromium.html vendored Normal file

File diff suppressed because it is too large Load Diff

BIN
dist/win-unpacked/asset-pro.exe vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/chrome_100_percent.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/chrome_200_percent.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/d3dcompiler_47.dll vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/ffmpeg.dll vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/icudtl.dat vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/libEGL.dll vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/libGLESv2.dll vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/af.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/am.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ar.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/bg.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/bn.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ca.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/cs.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/da.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/de.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/el.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/en-GB.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/en-US.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/es-419.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/es.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/et.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/fa.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/fi.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/fil.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/fr.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/gu.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/he.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/hi.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/hr.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/hu.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/id.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/it.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ja.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/kn.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ko.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/lt.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/lv.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ml.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/mr.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ms.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/nb.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/nl.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/pl.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/pt-BR.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/pt-PT.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ro.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ru.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/sk.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/sl.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/sr.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/sv.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/sw.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ta.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/te.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/th.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/tr.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/uk.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/ur.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/vi.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/zh-CN.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/locales/zh-TW.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/resources.pak vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/resources/app.asar vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/resources/elevate.exe vendored Normal file

Binary file not shown.

BIN
dist/win-unpacked/snapshot_blob.bin vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
dist/win-unpacked/vk_swiftshader.dll vendored Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
{"file_format_version": "1.0.0", "ICD": {"library_path": ".\\vk_swiftshader.dll", "api_version": "1.0.5"}}

BIN
dist/win-unpacked/vulkan-1.dll vendored Normal file

Binary file not shown.

130
electron-builder.config.js Normal file
View File

@ -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');
}
}
};

158
electron/ipc.ts Normal file
View File

@ -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}`
}
}
}
)
}

169
electron/main.ts Normal file
View File

@ -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()
}
})

105
electron/preload.ts Normal file
View File

@ -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
}
}

170
electron/store.ts Normal file
View File

@ -0,0 +1,170 @@
/**
* electron-store
*
*
* electron-store 使
*
*
* 1. electron-store 使 Node.js fs
* 2. 使 electron-storecontextIsolation: 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)
}

14
index.html Normal file
View File

@ -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>

7372
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@ -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"
}
}

7
postcss.config.mjs Normal file
View File

@ -0,0 +1,7 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

117
scripts/build-with-fix.js Normal file
View File

@ -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);
});

View File

@ -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 };

39
scripts/kill-app.js Normal file
View File

@ -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);
}

View File

@ -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 };

43
src/App.tsx Normal file
View File

@ -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

View File

@ -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