基础框架完成
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
||||
# https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = false
|
117
.electron-vite/build.ts
Normal file
@ -0,0 +1,117 @@
|
||||
process.env.NODE_ENV = "production";
|
||||
|
||||
import { join } from "path";
|
||||
import { say } from "cfonts";
|
||||
import { deleteAsync } from "del";
|
||||
import chalk from "chalk";
|
||||
import { rollup, OutputOptions } from "rollup";
|
||||
import { Listr } from "listr2";
|
||||
import rollupOptions from "./rollup.config";
|
||||
import { errorLog, doneLog } from "./log";
|
||||
|
||||
const mainOpt = rollupOptions(process.env.NODE_ENV, "main");
|
||||
const preloadOpt = rollupOptions(process.env.NODE_ENV, "preload");
|
||||
const isCI = process.env.CI || false;
|
||||
|
||||
if (process.env.BUILD_TARGET === "web") web().then(r => {
|
||||
console.log(r)
|
||||
});
|
||||
else unionBuild().then(r => {
|
||||
console.log(r)
|
||||
});
|
||||
|
||||
async function clean() {
|
||||
await deleteAsync([
|
||||
"dist/electron/main/*",
|
||||
"dist/electron/renderer/*",
|
||||
"dist/web/*",
|
||||
"build/*",
|
||||
"!build/icons",
|
||||
"!build/lib",
|
||||
"!build/lib/electron-build.*",
|
||||
"!build/icons/icon.*",
|
||||
]);
|
||||
doneLog(`clear done`);
|
||||
if (process.env.BUILD_TARGET === "onlyClean") process.exit();
|
||||
}
|
||||
|
||||
async function unionBuild() {
|
||||
greeting();
|
||||
await clean();
|
||||
|
||||
const tasksLister = new Listr(
|
||||
[
|
||||
{
|
||||
title: "building main process",
|
||||
task: async () => {
|
||||
try {
|
||||
const build = await rollup(mainOpt);
|
||||
await build.write(mainOpt.output as OutputOptions);
|
||||
} catch (error) {
|
||||
errorLog(`failed to build main process\n`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "building preload process",
|
||||
task: async () => {
|
||||
try {
|
||||
const build = await rollup(preloadOpt);
|
||||
await build.write(preloadOpt.output as OutputOptions);
|
||||
} catch (error) {
|
||||
errorLog(`failed to build main process\n`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "building renderer process",
|
||||
task: async (_, tasks) => {
|
||||
try {
|
||||
const { build } = await import("vite");
|
||||
await build({ configFile: join(__dirname, "vite.config.mts") });
|
||||
tasks.output = `take it away ${chalk.yellow(
|
||||
"`electron-builder`"
|
||||
)}\n`;
|
||||
} catch (error) {
|
||||
errorLog(`failed to build renderer process\n`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
concurrent: true,
|
||||
exitOnError: true,
|
||||
}
|
||||
);
|
||||
await tasksLister.run();
|
||||
}
|
||||
|
||||
async function web() {
|
||||
await deleteAsync(["dist/web/*", "!.gitkeep"]);
|
||||
const { build } = await import("vite");
|
||||
build({ configFile: join(__dirname, "vite.config.mts") }).then((res) => {
|
||||
doneLog(`web build success`);
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
function greeting() {
|
||||
const cols = process.stdout.columns;
|
||||
let text: boolean | string = "";
|
||||
|
||||
if (cols > 85) text = `let's-build`;
|
||||
else if (cols > 60) text = `let's-|build`;
|
||||
else text = false;
|
||||
|
||||
if (text && !isCI) {
|
||||
say(text, {
|
||||
colors: ["yellow"],
|
||||
font: "simple3d",
|
||||
space: false,
|
||||
});
|
||||
} else console.log(chalk.yellow.bold(`\n let's-build`));
|
||||
console.log();
|
||||
}
|
263
.electron-vite/dev-runner.ts
Normal file
@ -0,0 +1,263 @@
|
||||
process.env.NODE_ENV = "development";
|
||||
|
||||
import electron from "electron";
|
||||
import chalk from "chalk";
|
||||
import {join} from "path";
|
||||
import {watch} from "rollup";
|
||||
import Portfinder from "portfinder";
|
||||
import config from "../config";
|
||||
import {say} from "cfonts";
|
||||
import {spawn} from "child_process";
|
||||
import type {ChildProcess} from "child_process";
|
||||
import rollupOptions from "./rollup.config";
|
||||
|
||||
const mainOpt = rollupOptions(process.env.NODE_ENV, "main");
|
||||
const preloadOpt = rollupOptions(process.env.NODE_ENV, "preload");
|
||||
|
||||
let electronProcess: ChildProcess | null = null;
|
||||
let manualRestart = false;
|
||||
|
||||
function logStats(proc: string, data: any) {
|
||||
let log = "";
|
||||
|
||||
log += chalk.yellow.bold(
|
||||
`┏ ${proc} ${config.dev.chineseLog ? "编译过程" : "Process"} ${new Array(
|
||||
19 - proc.length + 1
|
||||
).join("-")}`
|
||||
);
|
||||
log += "\n\n";
|
||||
|
||||
if (typeof data === "object") {
|
||||
data
|
||||
.toString({
|
||||
colors: true,
|
||||
chunks: false,
|
||||
})
|
||||
.split(/\r?\n/)
|
||||
.forEach((line) => {
|
||||
log += " " + line + "\n";
|
||||
});
|
||||
} else {
|
||||
log += ` ${data}\n`;
|
||||
}
|
||||
|
||||
log += "\n" + chalk.yellow.bold(`┗ ${new Array(28 + 1).join("-")}`) + "\n";
|
||||
console.log(log);
|
||||
}
|
||||
|
||||
function removeJunk(chunk: string) {
|
||||
if (config.dev.removeElectronJunk) {
|
||||
// Example: 2018-08-10 22:48:42.866 Electron[90311:4883863] *** WARNING: Textured window <AtomNSWindow: 0x7fb75f68a770>
|
||||
if (
|
||||
/\d+-\d+-\d+ \d+:\d+:\d+\.\d+ Electron(?: Helper)?\[\d+:\d+] /.test(chunk)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Example: [90789:0810/225804.894349:ERROR:CONSOLE(105)] "Uncaught (in promise) Error: Could not instantiate: ProductRegistryImpl.Registry", source: chrome-devtools://devtools/bundled/inspector.js (105)
|
||||
if (/\[\d+:\d+\/|\d+\.\d+:ERROR:CONSOLE\(\d+\)\]/.test(chunk)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Example: ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
|
||||
if (/ALSA lib [a-z]+\.c:\d+:\([a-z_]+\)/.test(chunk)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
function startRenderer(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
Portfinder.basePort = config.dev.port || 9080;
|
||||
Portfinder.getPort(async (err, port) => {
|
||||
if (err) {
|
||||
reject("PortError:" + err);
|
||||
} else {
|
||||
const {createServer} = await import("vite");
|
||||
const server = await createServer({
|
||||
configFile: join(__dirname, "vite.config.mts"),
|
||||
});
|
||||
process.env.PORT = String(port);
|
||||
await server.listen(port);
|
||||
console.log(
|
||||
"\n\n" +
|
||||
chalk.blue(
|
||||
`${
|
||||
config.dev.chineseLog
|
||||
? " 正在准备主进程,请等待..."
|
||||
: " Preparing main process, please wait..."
|
||||
}`
|
||||
) +
|
||||
"\n\n"
|
||||
);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startMain(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const MainWatcher = watch(mainOpt);
|
||||
MainWatcher.on("change", (filename) => {
|
||||
// 主进程日志部分
|
||||
logStats(
|
||||
`${config.dev.chineseLog ? "主进程文件变更" : "Main-FileChange"}`,
|
||||
filename
|
||||
);
|
||||
});
|
||||
MainWatcher.on("event", (event) => {
|
||||
if (event.code === "END") {
|
||||
if (electronProcess) {
|
||||
manualRestart = true;
|
||||
electronProcess.pid && process.kill(electronProcess.pid);
|
||||
electronProcess = null;
|
||||
startElectron();
|
||||
|
||||
setTimeout(() => {
|
||||
manualRestart = false;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
resolve();
|
||||
} else if (event.code === "ERROR") {
|
||||
reject(event.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startPreload(): Promise<void> {
|
||||
console.log(
|
||||
"\n\n" +
|
||||
chalk.blue(
|
||||
`${
|
||||
config.dev.chineseLog
|
||||
? " 正在准备预加载脚本,请等待..."
|
||||
: " Preparing preLoad File, please wait..."
|
||||
}`
|
||||
) +
|
||||
"\n\n"
|
||||
);
|
||||
return new Promise((resolve, reject) => {
|
||||
const PreloadWatcher = watch(preloadOpt);
|
||||
PreloadWatcher.on("change", (filename) => {
|
||||
// 预加载脚本日志部分
|
||||
logStats(
|
||||
`${
|
||||
config.dev.chineseLog ? "预加载脚本文件变更" : "preLoad-FileChange"
|
||||
}`,
|
||||
filename
|
||||
);
|
||||
});
|
||||
PreloadWatcher.on("event", (event) => {
|
||||
if (event.code === "END") {
|
||||
if (electronProcess) {
|
||||
manualRestart = true;
|
||||
electronProcess.pid && process.kill(electronProcess.pid);
|
||||
electronProcess = null;
|
||||
startElectron();
|
||||
|
||||
setTimeout(() => {
|
||||
manualRestart = false;
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
resolve();
|
||||
} else if (event.code === "ERROR") {
|
||||
reject(event.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startElectron() {
|
||||
let args = [
|
||||
"--inspect=5858",
|
||||
join(__dirname, "../dist/electron/main/main.js"),
|
||||
];
|
||||
|
||||
// detect yarn or npm and process commandline args accordingly
|
||||
if (process.env.npm_execpath?.endsWith("yarn.js")) {
|
||||
args = args.concat(process.argv.slice(3));
|
||||
} else if (process.env.npm_execpath?.endsWith("npm-cli.js")) {
|
||||
args = args.concat(process.argv.slice(2));
|
||||
}
|
||||
|
||||
electronProcess = spawn(electron as any, args);
|
||||
|
||||
electronProcess.stdout?.on("data", (data: string) => {
|
||||
electronLog(removeJunk(data), "blue");
|
||||
});
|
||||
electronProcess.stderr?.on("data", (data: string) => {
|
||||
electronLog(removeJunk(data), "red");
|
||||
});
|
||||
|
||||
electronProcess.on("close", () => {
|
||||
if (!manualRestart) process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
function electronLog(data: any, color: string) {
|
||||
if (data) {
|
||||
let log = "";
|
||||
data = data.toString().split(/\r?\n/);
|
||||
data.forEach((line) => {
|
||||
log += ` ${line}\n`;
|
||||
});
|
||||
console.log(
|
||||
chalk[color].bold(
|
||||
`┏ ${
|
||||
config.dev.chineseLog ? "主程序日志" : "Electron"
|
||||
} -------------------`
|
||||
) +
|
||||
"\n\n" +
|
||||
log +
|
||||
chalk[color].bold("┗ ----------------------------") +
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function greeting(arg: string) {
|
||||
const cols = process.stdout.columns;
|
||||
let text: string;
|
||||
text = arg ? arg : "Hi-Sass";
|
||||
let length = text ? text.length : 0;
|
||||
if (cols > 104) {
|
||||
text = length > 104 ? text.slice(0, 103) : text;
|
||||
} else if (cols > 76) {
|
||||
text = length > 76 ? text.slice(0, 75) : text;
|
||||
} else text = "";
|
||||
|
||||
if (text) {
|
||||
say(text, {
|
||||
colors: ["yellow"],
|
||||
font: "simple3d",
|
||||
space: false,
|
||||
});
|
||||
} else console.log(chalk.yellow.bold("\n electron-vite"));
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`${config.dev.chineseLog ? " 准备启动..." : " getting ready..."}`
|
||||
) + "\n"
|
||||
);
|
||||
}
|
||||
|
||||
async function init() {
|
||||
greeting("Hi-Sass");
|
||||
|
||||
try {
|
||||
await startRenderer();
|
||||
await startMain();
|
||||
await startPreload();
|
||||
startElectron();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
104
.electron-vite/hot-updater.ts
Normal file
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* power by biuuu
|
||||
*/
|
||||
|
||||
import chalk from 'chalk'
|
||||
import {join} from 'path'
|
||||
import {ensureDir, emptyDir, copy, outputJSON, remove, stat, readFile} from 'fs-extra'
|
||||
import {createHmac} from 'crypto'
|
||||
import {platform} from 'os'
|
||||
import AdmZip from 'adm-zip'
|
||||
import packageFile from '../package.json'
|
||||
import buildConfig from '../build.json'
|
||||
import config from '../config'
|
||||
import {okayLog, errorLog, doneLog} from './log'
|
||||
|
||||
|
||||
const platformName = platform().includes('win32') ? 'win' : platform().includes('darwin') ? 'mac' : 'linux'
|
||||
const buildPath = join('.', 'build', `${platformName === 'mac' ? 'mac' : platformName + '-unpacked'}`)
|
||||
|
||||
const hash = (data, type = 'sha256') => {
|
||||
const hmac = createHmac(type, 'Sky')
|
||||
hmac.update(data)
|
||||
return hmac.digest('hex')
|
||||
}
|
||||
|
||||
const createZip = (filePath: string, dest: string) => {
|
||||
const zip = new AdmZip()
|
||||
zip.addLocalFolder(filePath, "", undefined)
|
||||
zip.toBuffer()
|
||||
zip.writeZip(dest, undefined)
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
console.log(chalk.green.bold(`Start packing \n`))
|
||||
|
||||
if (buildConfig.asar) {
|
||||
errorLog(`${chalk.red("Please make sure the build.asar option in the Package.json file is set to false")}\n`)
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.build.hotPublishConfigName === '') {
|
||||
errorLog(`${chalk.red("HotPublishConfigName is not set, which will cause the update to fail, please set it in the config/index.js \n") + chalk.red.bold(`\n Packing failed \n`)}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
stat(join(buildPath, 'resources', 'app'), async (err, stats) => {
|
||||
if (err) {
|
||||
errorLog(`${chalk.red("No resource files were found, please execute this command after the build command")}\n`)
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(chalk.green.bold(`Check the resource files \n`))
|
||||
const packResourcesPath = join('.', 'build', 'resources', 'dist');
|
||||
const packPackagePath = join('.', 'build', 'resources');
|
||||
const resourcesPath = join('.', 'dist');
|
||||
const appPath = join('.', 'build', 'resources');
|
||||
const name = "app.zip";
|
||||
const outputPath = join('.', 'build', 'update');
|
||||
const zipPath = join(outputPath, name);
|
||||
|
||||
|
||||
await ensureDir(packResourcesPath);
|
||||
await emptyDir(packResourcesPath);
|
||||
await copy(resourcesPath, packResourcesPath);
|
||||
okayLog(chalk.cyan.bold(`File copy complete \n`))
|
||||
await outputJSON(join(packPackagePath, "package.json"), {
|
||||
name: packageFile.name,
|
||||
productName: buildConfig.productName,
|
||||
version: packageFile.version,
|
||||
description: packageFile.description,
|
||||
main: packageFile.main,
|
||||
author: packageFile.author,
|
||||
dependencies: packageFile.dependencies
|
||||
});
|
||||
okayLog(chalk.cyan.bold(`Rewrite package file complete \n`))
|
||||
await ensureDir(outputPath);
|
||||
await emptyDir(outputPath);
|
||||
createZip(appPath, zipPath);
|
||||
const buffer = await readFile(zipPath);
|
||||
const sha256 = hash(buffer);
|
||||
const hashName = sha256.slice(7, 12);
|
||||
await copy(zipPath, join(outputPath, `${hashName}.zip`));
|
||||
await outputJSON(join(outputPath, `${config.build.hotPublishConfigName}.json`),
|
||||
{
|
||||
version: packageFile.version,
|
||||
name: `${hashName}.zip`,
|
||||
hash: sha256
|
||||
}
|
||||
);
|
||||
okayLog(chalk.cyan.bold(`Zip file complete, Start cleaning up redundant files \n`))
|
||||
await remove(zipPath);
|
||||
await remove(appPath)
|
||||
okayLog(chalk.cyan.bold(`Cleaning up redundant files completed \n`))
|
||||
doneLog('The resource file is packaged!\n')
|
||||
console.log("File location: " + chalk.green(outputPath) + "\n");
|
||||
} catch (error) {
|
||||
errorLog(`${chalk.red(error.message || error)}\n`)
|
||||
process.exit(1)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start()
|
17
.electron-vite/log/index.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
export const doneLog = (text: string) => {
|
||||
console.log('\n' + chalk.bgGreen.white(' DONE ') + ' ' + text)
|
||||
}
|
||||
export const errorLog = (text: string) => {
|
||||
console.log('\n ' + chalk.bgRed.white(' ERROR ') + ' ' + text)
|
||||
}
|
||||
export const okayLog = (text: string) => {
|
||||
console.log('\n ' + chalk.bgBlue.white(' OKAY ') + ' ' + text)
|
||||
}
|
||||
export const warningLog = (text: string) => {
|
||||
console.log('\n ' + chalk.bgYellow.white(' WARNING ') + ' ' + text)
|
||||
}
|
||||
export const infoLog = (text: string) => {
|
||||
console.log('\n ' + chalk.bgCyan.white(' INFO ') + ' ' + text)
|
||||
}
|
17
.electron-vite/plugin/vite-ikaros-tools.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { ResolvedConfig } from "vite";
|
||||
|
||||
export default () => {
|
||||
let command = "";
|
||||
return {
|
||||
name: "ikaros-tools",
|
||||
configResolved(resolvedConfig: ResolvedConfig) {
|
||||
command = resolvedConfig.command;
|
||||
},
|
||||
buildStart: () => {
|
||||
if (command.includes("serve")) {
|
||||
globalThis.__name = (target: string, value: Record<string, any>) =>
|
||||
Object.defineProperty(target, "name", { value, configurable: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
72
.electron-vite/preloads.ts
Normal file
@ -0,0 +1,72 @@
|
||||
export const preloads = {
|
||||
include: [
|
||||
"vue",
|
||||
"vue-router",
|
||||
"pinia",
|
||||
"axios",
|
||||
"@vueuse/core",
|
||||
"sortablejs",
|
||||
"path-to-regexp",
|
||||
"echarts",
|
||||
"@wangeditor/editor",
|
||||
"@wangeditor/editor-for-vue",
|
||||
"vue-i18n",
|
||||
"path-browserify",
|
||||
"unocss",
|
||||
"node",
|
||||
"element-plus/es/components/form/style/css",
|
||||
"element-plus/es/components/form-item/style/css",
|
||||
"element-plus/es/components/button/style/css",
|
||||
"element-plus/es/components/input/style/css",
|
||||
"element-plus/es/components/input-number/style/css",
|
||||
"element-plus/es/components/switch/style/css",
|
||||
"element-plus/es/components/upload/style/css",
|
||||
"element-plus/es/components/menu/style/css",
|
||||
"element-plus/es/components/col/style/css",
|
||||
"element-plus/es/components/icon/style/css",
|
||||
"element-plus/es/components/row/style/css",
|
||||
"element-plus/es/components/tag/style/css",
|
||||
"element-plus/es/components/dialog/style/css",
|
||||
"element-plus/es/components/loading/style/css",
|
||||
"element-plus/es/components/radio/style/css",
|
||||
"element-plus/es/components/radio-group/style/css",
|
||||
"element-plus/es/components/popover/style/css",
|
||||
"element-plus/es/components/scrollbar/style/css",
|
||||
"element-plus/es/components/tooltip/style/css",
|
||||
"element-plus/es/components/dropdown/style/css",
|
||||
"element-plus/es/components/dropdown-menu/style/css",
|
||||
"element-plus/es/components/dropdown-item/style/css",
|
||||
"element-plus/es/components/sub-menu/style/css",
|
||||
"element-plus/es/components/menu-item/style/css",
|
||||
"element-plus/es/components/divider/style/css",
|
||||
"element-plus/es/components/card/style/css",
|
||||
"element-plus/es/components/link/style/css",
|
||||
"element-plus/es/components/breadcrumb/style/css",
|
||||
"element-plus/es/components/breadcrumb-item/style/css",
|
||||
"element-plus/es/components/table/style/css",
|
||||
"element-plus/es/components/tree-select/style/css",
|
||||
"element-plus/es/components/table-column/style/css",
|
||||
"element-plus/es/components/select/style/css",
|
||||
"element-plus/es/components/option/style/css",
|
||||
"element-plus/es/components/pagination/style/css",
|
||||
"element-plus/es/components/tree/style/css",
|
||||
"element-plus/es/components/alert/style/css",
|
||||
"element-plus/es/components/radio-button/style/css",
|
||||
"element-plus/es/components/checkbox-group/style/css",
|
||||
"element-plus/es/components/checkbox/style/css",
|
||||
"element-plus/es/components/tabs/style/css",
|
||||
"element-plus/es/components/tab-pane/style/css",
|
||||
"element-plus/es/components/rate/style/css",
|
||||
"element-plus/es/components/date-picker/style/css",
|
||||
"element-plus/es/components/notification/style/css",
|
||||
"element-plus/es/components/image/style/css",
|
||||
"element-plus/es/components/statistic/style/css",
|
||||
"element-plus/es/components/watermark/style/css",
|
||||
"element-plus/es/components/config-provider/style/css",
|
||||
"element-plus/es/components/text/style/css",
|
||||
"element-plus/es/components/drawer/style/css",
|
||||
"element-plus/es/components/color-picker/style/css",
|
||||
"element-plus/es/components/backtop/style/css",
|
||||
"element-plus/es/components/message-box/style/css",
|
||||
],
|
||||
}
|
92
.electron-vite/rollup.config.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import path from "path";
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import { builtinModules } from "module";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import replace from "@rollup/plugin-replace";
|
||||
import alias from "@rollup/plugin-alias";
|
||||
import json from "@rollup/plugin-json";
|
||||
import esbuild from "rollup-plugin-esbuild";
|
||||
import obfuscator from "rollup-plugin-obfuscator";
|
||||
import { defineConfig } from "rollup";
|
||||
import { getConfig } from "./utils";
|
||||
const config = getConfig();
|
||||
|
||||
export default (env = "production", type = "main") => {
|
||||
return defineConfig({
|
||||
input:
|
||||
type === "main"
|
||||
? path.join(__dirname, "..", "src", "main", "index.ts")
|
||||
: path.join(__dirname, "..", "src", "preload", "index.ts"),
|
||||
output: {
|
||||
file: path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"dist",
|
||||
"electron",
|
||||
"main",
|
||||
`${type === "main" ? type : "preload"}.js`
|
||||
),
|
||||
format: "cjs",
|
||||
name: type === "main" ? "MainProcess" : "MainPreloadProcess",
|
||||
sourcemap: false,
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
"process.env.userConfig": config ? JSON.stringify(config) : "{}",
|
||||
}),
|
||||
// 提供路径和读取别名
|
||||
nodeResolve({
|
||||
preferBuiltins: true,
|
||||
browser: false,
|
||||
extensions: [".mjs", ".ts", ".js", ".json", ".node"],
|
||||
}),
|
||||
commonjs({
|
||||
sourceMap: false,
|
||||
}),
|
||||
json(),
|
||||
esbuild({
|
||||
// All options are optional
|
||||
include: /\.[jt]s?$/, // default, inferred from `loaders` option
|
||||
exclude: /node_modules/, // default
|
||||
// watch: process.argv.includes('--watch'), // rollup 中有配置
|
||||
sourceMap: false, // default
|
||||
minify: env === "production",
|
||||
target: "esnext", // default, or 'es20XX', 'esnext' //es2017
|
||||
// Like @rollup/plugin-replace
|
||||
define: {
|
||||
__VERSION__: '"x.y.z"',
|
||||
},
|
||||
// Add extra loaders
|
||||
loaders: {
|
||||
// Add .json files support
|
||||
// require @rollup/plugin-commonjs
|
||||
".json": "json",
|
||||
// Enable JSX in .js files too
|
||||
".js": "jsx",
|
||||
},
|
||||
}),
|
||||
alias({
|
||||
entries: [
|
||||
{ find: "@main", replacement: path.join(__dirname, "../src/main") },
|
||||
{
|
||||
find: "@config",
|
||||
replacement: path.join(__dirname, "..", "config"),
|
||||
},
|
||||
],
|
||||
}),
|
||||
process.env.NODE_ENV === "production" && obfuscator({}),
|
||||
],
|
||||
external: [
|
||||
...builtinModules,
|
||||
"axios",
|
||||
"electron",
|
||||
"express",
|
||||
"ffi-napi",
|
||||
"ref-napi",
|
||||
"ref-struct-napi",
|
||||
"semver",
|
||||
"glob",
|
||||
],
|
||||
});
|
||||
};
|
20
.electron-vite/utils.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { config } from "dotenv";
|
||||
import { join } from "path";
|
||||
import minimist from "minimist";
|
||||
|
||||
const argv = minimist(process.argv.slice(2));
|
||||
const rootResolve = (...pathSegments: string[]) => join(__dirname, "..", ...pathSegments);
|
||||
|
||||
export const getEnv = () => argv["m"];
|
||||
|
||||
const getEnvPath = () => {
|
||||
if (
|
||||
String(typeof getEnv()) === "boolean" ||
|
||||
String(typeof getEnv()) === "undefined"
|
||||
) {
|
||||
return rootResolve("env/dev.env");
|
||||
}
|
||||
return rootResolve(`env/${getEnv()}.env`);
|
||||
};
|
||||
|
||||
export const getConfig = () => config({ path: getEnvPath() }).parsed;
|
175
.electron-vite/vite.config.mts
Normal file
@ -0,0 +1,175 @@
|
||||
import {join} from "path";
|
||||
import {UserConfig, ConfigEnv, loadEnv, defineConfig} from "vite";
|
||||
import vuePlugin from "@vitejs/plugin-vue";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import viteIkarosTools from "./plugin/vite-ikaros-tools";
|
||||
import {getConfig} from "./utils";
|
||||
|
||||
/**
|
||||
* 自动引用功能
|
||||
*/
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import {ElementPlusResolver} from "unplugin-vue-components/resolvers";
|
||||
import Icons from "unplugin-icons/vite";
|
||||
import IconsResolver from "unplugin-icons/resolver";
|
||||
/**
|
||||
* SVG图标
|
||||
*/
|
||||
import {createSvgIconsPlugin} from "vite-plugin-svg-icons";
|
||||
|
||||
/**
|
||||
* univerJs插件
|
||||
*/
|
||||
import {univerPlugin} from "@univerjs/vite-plugin";
|
||||
|
||||
import UnoCSS from "unocss/vite";
|
||||
|
||||
/**
|
||||
* 需要预加载的资源
|
||||
*/
|
||||
import {preloads} from "./preloads";
|
||||
|
||||
function resolve(dir: string) {
|
||||
return join(__dirname, "..", dir);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
import pkg from "../package.json";
|
||||
|
||||
/** 平台的名称、版本、运行所需的`node`版本、依赖、构建时间的类型提示 */
|
||||
const __APP_INFO__ = {
|
||||
pkg: pkg,
|
||||
buildTimestamp: Date.now(),
|
||||
};
|
||||
|
||||
const root = resolve("src/renderer");
|
||||
const src = resolve("src");
|
||||
const theme: string = resolve("src/renderer/themes/default");
|
||||
const mode = config && config.NODE_ENV;
|
||||
|
||||
export default defineConfig({
|
||||
mode: mode,
|
||||
root: root,
|
||||
define: {
|
||||
__CONFIG__: config,
|
||||
__ISWEB__: Number(config && config.target),
|
||||
__APP_INFO__: JSON.stringify(__APP_INFO__),
|
||||
'process.env': process.env,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": root,
|
||||
"@api": join(root, "/api"),
|
||||
"@config": join(src, "../config"),
|
||||
"@main": join(src, "/main"),
|
||||
"@mock": join(root, "/mock"),
|
||||
"@renderer": root,
|
||||
"@src": src,
|
||||
"@store": join(root, "/store/modules"),
|
||||
"@theme": theme
|
||||
},
|
||||
},
|
||||
base: "./",
|
||||
build: {
|
||||
outDir:
|
||||
config && config.target
|
||||
? resolve("dist/web")
|
||||
: resolve("dist/electron/renderer"),
|
||||
emptyOutDir: true,
|
||||
target: "esnext",
|
||||
cssCodeSplit: false,
|
||||
},
|
||||
css: {
|
||||
// CSS 预处理器
|
||||
preprocessorOptions: {
|
||||
// 定义全局 SCSS 变量
|
||||
scss: {
|
||||
javascriptEnabled: true,
|
||||
additionalData: `
|
||||
@use "@theme/styles/variables.scss" as *;
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
// 无须配置
|
||||
},
|
||||
plugins: [
|
||||
vuePlugin(),
|
||||
// jsx、tsx语法支持
|
||||
vueJsx(),
|
||||
viteIkarosTools(),
|
||||
//CSS预处理
|
||||
UnoCSS({
|
||||
hmrTopLevelAwait: false,
|
||||
}),
|
||||
// univerjs的虚拟化插件技术,用于语言包的自动引用
|
||||
univerPlugin(),
|
||||
|
||||
// 自动导入参考: https://github.com/sxzz/element-plus-best-practices/blob/main/vite.config.ts
|
||||
AutoImport({
|
||||
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
|
||||
imports: ["vue", "@vueuse/core", "pinia", "vue-router", "vue-i18n"],
|
||||
resolvers: [
|
||||
// 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
|
||||
ElementPlusResolver(),
|
||||
// 自动导入图标组件
|
||||
IconsResolver({}),
|
||||
],
|
||||
eslintrc: {
|
||||
// 是否自动生成 eslint 规则,建议生成之后设置 false
|
||||
enabled: true,
|
||||
// 指定自动导入函数 eslint 规则的文件
|
||||
filepath: "../.eslintrc-auto-import.json",
|
||||
globalsPropValue: true,
|
||||
},
|
||||
// 是否在 vue 模板中自动导入
|
||||
vueTemplate: true,
|
||||
// 指定自动导入函数TS类型声明文件路径 (false:关闭自动生成)
|
||||
// dts: false,
|
||||
dts: "fixTypes/auto-imports.d.ts",
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
// 自动导入 Element Plus 组件
|
||||
ElementPlusResolver(),
|
||||
/**
|
||||
* 自动注册图标组件
|
||||
*/
|
||||
IconsResolver({
|
||||
// element-plus图标库,其他图标库 https://icon-sets.iconify.design/
|
||||
enabledCollections: ["ep"],
|
||||
}),
|
||||
],
|
||||
/**
|
||||
* 指定自定义组件位置(默认:src/components)
|
||||
* TBD: resolve("@theme/components"),测试用法,效果未知
|
||||
*/
|
||||
dirs: ["src/components", join(theme, "/components"), join(theme, "/**/components")],
|
||||
// 指定自动导入组件TS类型声明文件路径 (false:关闭自动生成)
|
||||
// dts: false,
|
||||
dts: "fixTypes/components.d.ts",
|
||||
}),
|
||||
Icons({
|
||||
// 自动安装图标库
|
||||
autoInstall: true,
|
||||
}),
|
||||
createSvgIconsPlugin({
|
||||
// 指定需要缓存的图标文件夹
|
||||
iconDirs: [join(theme, "assets/icons")],
|
||||
// 指定symbolId格式
|
||||
symbolId: "icon-[dir]-[name]",
|
||||
}),
|
||||
|
||||
// node({
|
||||
// // 默认情况下,`node` 插件会重写 `process` 和全局变量。
|
||||
// // 如果你不想要这个行为,可以将 `mock` 设置为 `false`。
|
||||
// mock: true,
|
||||
//
|
||||
// // 如果你想要包括一些特定的Node.js全局变量,可以在 `additional` 中指定。
|
||||
// additional: ['process', 'fs', 'path']
|
||||
// })
|
||||
],
|
||||
optimizeDeps: preloads,
|
||||
});
|
25
.gitignore
vendored
@ -1,11 +1,18 @@
|
||||
# ---> Vue
|
||||
# gitignore template for Vue.js projects
|
||||
#
|
||||
# Recommended template: Node.gitignore
|
||||
.DS_Store
|
||||
node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
docs/_book
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
test/
|
||||
dist/
|
||||
build/
|
||||
!build/icons
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
build-说明.json
|
||||
/release/
|
||||
|
13
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/Thumbs.db": true,
|
||||
"**/node_modules": true
|
||||
},
|
||||
"npm.packageManager": "yarn",
|
||||
"workbench.editor.highlightModifiedTabs": true
|
||||
}
|
152
CHANGELOG.md
Normal file
@ -0,0 +1,152 @@
|
||||
# 2024.09.07
|
||||
|
||||
- 新增已知问题:
|
||||
- preload预加载,不能直接传导出process,会导致程序假死、主界面无显;
|
||||
- 渲染进程中,要使用通过预加载传导的变量,最好还是用window.*的方式
|
||||
|
||||
# 2024.09.06
|
||||
|
||||
- 修复增加了系统托盘按钮功能,主要涉及的文件
|
||||
- src/renderer/public/tray.html (这个是用于定制化的托盘,我因为没有精力搞,简化了,所以直接无视了它)
|
||||
- src/main/中,添加了trayHook、修改了trayManager
|
||||
|
||||
# 2024.09.05
|
||||
|
||||
- 关于新增自定义主题,需要调用nodejs模块的API时,不应直接调用(在electron中会失效),而是参考
|
||||
src/utils/ipcRender的进程通信方式。大部分情况下,这是不需要的,因为我已经写好了很多来适配。在*
|
||||
/views/demo中可以找到主|子进程之间通信的示例。
|
||||
- 需要新增主|子 进程间通信的,已知要进行以下操作:
|
||||
- 强烈建议任何文件修改前,请在其所在目录直接完整复制保存一份!!!
|
||||
- 任何时候,不要破坏这几个文件的结构,只按照以下说明添加具体功能即可!!!
|
||||
- src/ipc.ts中,添加类型声明,在enum(枚举)channel中,参照添加即可。
|
||||
- src/main/services/ipcMain.ts中,添加需要增加的通信功能。
|
||||
|
||||
# 2024.09.04
|
||||
|
||||
- 修复了几个开发依赖,至此,要求必须使用yarn为包管理工具
|
||||
- 修复了preload.js调用,修复主进程与渲染进程的通信;尝试将主进程脚本与渲染进程脚本完全分离
|
||||
- 主要涉及文件:
|
||||
- src/main/config/ => staticPath.ts、windowsConfig.ts
|
||||
- src/main/services => windowManager.ts
|
||||
- 修改了dev-runner.ts脚本中的欢迎函数,自定义了chalk脚本的欢迎词“hi-sass”
|
||||
- 修复热更新脚本语法错误:
|
||||
- npm i --save-dev @types/adm-zip, 使adm-zip插件支持ts
|
||||
- const zip = new AdmZip()
|
||||
- 修复改进了主进程中的部分循环引用,减少不必要的错误。具体文件归类尚待最终确认。
|
||||
|
||||
# 2024.09.01
|
||||
|
||||
- 修复express来模拟mock服务的参数路由问题;路由中带参数时,body是函数,不能直接输出结果,需要执行该函数。具体代码在@main/server/server.ts中。
|
||||
- 添加热键支持,修复全局复制、粘贴等按钮的响应。具体代码在@main/config/hotkeys.ts中。
|
||||
- 重整文件夹,相应调整对应的设置;注意: 文件夹位置如有移动,需要相应地调整引用
|
||||
- 在vite.config.mts及ts.config中均配置了文件路径别名,方便后期开发使用;文件夹移动也方便一些;
|
||||
- 别名路径文件夹移动后,要注意重新配置上述两个文件
|
||||
- mock文件夹不可以放置在根目录,即使配置了别名,也可能会有各种乱七八糟的报错
|
||||
- 非特殊情况,都建议将运行代码放置在根目录别名@下(也就是src/renderer/)
|
||||
- 如果对框架没有修改的需求,只需要修改前端,就无须修改src/renderer/目录以外的文件。
|
||||
|
||||
# 2024.08.28
|
||||
|
||||
- 添加univerJs组件,由于使用yarn方式无法自动为它安装peerDependencies依赖,所以需要额外手动补充添加;
|
||||
- 另外,已知还需要依赖外部库:
|
||||
- react
|
||||
- @wendellhu/redi
|
||||
- clsx
|
||||
- rxjs
|
||||
- dayjs
|
||||
- @grpc/grpc-js
|
||||
|
||||
- 使用express-ws做wss服务器模拟,依赖于express,ts中还需要安装@types/express-ws
|
||||
|
||||
# 2024.08.27
|
||||
|
||||
- 去掉mock尝试及相关依赖,改为使用express做服务器进行模拟(配置容易、编写简洁,简直不要太方便),ts需要安装 @types/express
|
||||
|
||||
# 2024.08.25
|
||||
|
||||
- TBD:修复模拟浏览器,升级相应语法到最新;
|
||||
- 浏览器函数文件在src/main/services/browserHandle.ts中
|
||||
- 里面默认的url地址,是在src/main/config/const.ts中配置
|
||||
|
||||
# 2024.08.21
|
||||
|
||||
- 修复依赖,更新到最新包
|
||||
- 添加express依赖,制作内置服务器使用
|
||||
- 添加element-plus作为前端默认UI组件,相应地,有以下编译前变量定义
|
||||
- 默认主题:@theme ,在 .electron-vite/vite-config.mts 文件;
|
||||
- 组件库形成的主题文件,应该有一套完整的、可独立访问的前端路径;
|
||||
- 主题目录: src/renderer/themes/ 下;默认 default
|
||||
- 非必要不建议对themes进行任何更改。
|
||||
- 如果使用其他组件,可在主题目录下新建一个目录,并将一应文件存入。具体可参考themes里的default目录
|
||||
- 如果需要完善补充默认主题,建议复制default目录全部内容后再改。
|
||||
- 启用新的主题,需要修改 @theme 的路径定义。
|
||||
- 主题内,建议使用相对路径进行各内容的引用。
|
||||
|
||||
- 其他补充:
|
||||
- 添加mock,做本地调试
|
||||
- 尝试:修改了src下的request.ts中的baseURL,(有可能还需要改回来)
|
||||
|
||||
# 2024.08.20
|
||||
|
||||
- 修复依赖 minimist
|
||||
- 检查是否存在: npm i --save-dev @types/minimist
|
||||
- 安装: yarn add @types/minimist
|
||||
- 修复依赖 semver
|
||||
- 检查是否存在: npm i --save-dev @types/semver
|
||||
- 安装: yarn add @types/semver
|
||||
- 修改主应用菜单
|
||||
- 文件目录: src/main/hooks/menu-hooks.ts
|
||||
- 暂时去掉了“关于”菜单中的多余按钮(对话框相应的事件没写好,暂时去掉)
|
||||
- 项目定名:hi-sass-frame-front
|
||||
- 修改package.json
|
||||
- 修改build.json
|
||||
- 修复build脚本
|
||||
- 文件: .electron-vite/build.ts
|
||||
- web()或unionBuild()为异步方法,需要添加.then()来操作回调结果。
|
||||
- 修复语言包加载报错问题 :vue: Property glob does not exist on type ImportMeta
|
||||
- 报错文件:src/i18n/index.ts
|
||||
- 问题:import.meta.glob函数报错
|
||||
- 原因:使用了ts类型的文件,需要处理相应的types类型
|
||||
- 解决:(1)修改tsconfig.json,types中添加“vite/client”;此时,大部分vite构建的项目都基本OK了;
|
||||
(2)添加glob.d.ts声明文件,放置在合适的位置;对于electron-vite配置,本项目指定了customTypes目录存放这些额外的声明文件,我又自定义了一个fixTypes目录来存放开发中需要补充的声明文件。文件内添加声明头注释:
|
||||
```/// <reference types="vite/client" />```
|
||||
- 修改启动界面提示文字
|
||||
- 文件: src/render/public/loader.html
|
||||
- 修改:"资源加载中"
|
||||
- 添加release目录,用于存放打包好的APP
|
||||
- 目前以版本号为子目录
|
||||
- 替换程序的默认图标
|
||||
- mac 系统创建icns图标需要用到命令行.
|
||||
- 第一步:选定png图标文件(高清,1024*1028分辨率以上);
|
||||
- 第二步:在当前目录下创建文件夹 pngpic.iconset;
|
||||
- 第三步:命令sips生成图标文件(7组共14张),全部存放在pngpic.iconset目录下;
|
||||
- 第四步:命令iconutil生成icns文件
|
||||
- 命令参考
|
||||
```bash
|
||||
sips -z 16 16 hi.png --out icon_16x16.png
|
||||
sips -z 32 32 hi.png --out icon_16x16@2x.png
|
||||
sips -z 32 32 hi.png --out icon_32x32.png
|
||||
sips -z 64 64 hi.png --out icon_32x32@2x.png
|
||||
sips -z 64 64 hi.png --out icon_64x64.png
|
||||
sips -z 128 128 hi.png --out icon_64x64@2x.png
|
||||
sips -z 128 128 hi.png --out icon_128x128.png
|
||||
sips -z 256 256 hi.png --out icon_128x128@2x.png
|
||||
sips -z 256 256 hi.png --out icon_256x256.png
|
||||
sips -z 512 512 hi.png --out icon_256x256@2x.png
|
||||
sips -z 512 512 hi.png --out icon_512x512.png
|
||||
sips -z 1024 1024 hi.png --out icon_512x512@2x.png
|
||||
sips -z 1024 1024 hi.png --out icon_1024x1024.png
|
||||
sips -z 2048 2048 hi.png --out icon_1024x1024@2x.png
|
||||
```
|
||||
```bash
|
||||
iconutil -c icns pngpic.iconset -o icon.icns
|
||||
```
|
||||
|
||||
# 2024.08.19
|
||||
|
||||
- 项目创建。
|
||||
- 项目基础构成:
|
||||
- electron@31.4.0
|
||||
- vue@3.4.21
|
||||
- element-plus@2.7.5
|
||||
- vite@5.2.7
|
112
DIR.md
Normal file
@ -0,0 +1,112 @@
|
||||
# 目录结构说明
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
├── .electron-vue # 构建相关(非必要不要修改,修改请先做好源文件备份)
|
||||
│ ├── log # 控制台日志,利用chalk插件。(非必要不更改)
|
||||
│ ├── plugin # vite插件目录
|
||||
│ │ ├── vite-ikaros-tools.ts # 参考项目中带的,未测试
|
||||
│ ├── build.ts # 构建命令
|
||||
│ ├── dev-runner.ts # 开发调试命令
|
||||
│ ├── hot-updater.ts # 热更新命令
|
||||
│ ├── rollup.config.ts # 回滚命令
|
||||
│ ├── utils.ts # 全局设置(从根目录的*.env文件取出运行环境的定义-dotenv插件,如果没有取到,就判定为开发环境),根据运行环境再从env目录取相应的配置文件
|
||||
│ ├── vite.config.mts # VITE运行配置,切记不要改为.ts后缀(.mts支持兼容某些插件js的require引用,例如unocss)
|
||||
├── build # 项目打包目录
|
||||
│ │── icon # 图标
|
||||
│ └── lib # 打包依赖(win)
|
||||
├── config # 构建相关的配置
|
||||
├── dist # 构建内容的临时存放目录(每次打包时会先清空该文件夹)
|
||||
├── env # 开发/运行环境相关
|
||||
│ │── xxx.env # 环境变量配置
|
||||
├── fixTypes # 自动生成ts语法的auto-import.d.ts(目前未在项目中使用)
|
||||
├─ public # 公共目录(无视)
|
||||
├─ release # 可以将已打包好的版本存放在此(根据自己项目的情况自行考虑),对项目本身无影响
|
||||
├─ src # 源码目录
|
||||
│ ├─main # 主进程目录
|
||||
│ │ ├──config # 主进程配置
|
||||
│ │ │ ├── const # 静态变量
|
||||
│ │ │ ├── domains # 打算用于域名白名单配置的(未启用)
|
||||
│ │ │ ├── hotPublish # 热更新配置
|
||||
│ │ │ ├── staticPath # 静态路径
|
||||
│ │ │ └── windowsConfig # 窗口配置
|
||||
│ │ ├──handle # 进程事件定义
|
||||
│ │ │ ├── browserHandle # 浏览器进程
|
||||
│ │ │ ├── mainHandle # 其他主要进程
|
||||
│ │ │ ├── serverHanle # 内置服务器进程
|
||||
│ │ │ ├── printHanle # 打印进程
|
||||
│ │ │ └── serverHanle # 内置服务器进程
|
||||
│ │ ├──hooks # 事件响应
|
||||
│ │ │ ├── browser # 内置浏览器
|
||||
│ │ │ ├── devTool # 打开开发者调试窗口
|
||||
│ │ │ ├── disableButton # 禁用指定按钮
|
||||
│ │ │ ├── exception # 进程渲染响应事件
|
||||
│ │ │ ├── hotkeys # 快捷键
|
||||
│ │ │ ├── menu # 应用主菜单
|
||||
│ │ │ ├── print # 打印功能
|
||||
│ │ │ └── tray # 系统托盘
|
||||
│ │ ├─server # 内置服务端文件夹
|
||||
│ │ │ ├─index # 内置服务端启停
|
||||
│ │ │ ├─server # 内置服务端主体
|
||||
│ │ │ ├─wsRouters # 内置Ws服务端路由配置
|
||||
│ │ │ └─wsServer # 内置WS服务端主体启停
|
||||
│ │ ├─services # 主进程服务文件夹
|
||||
│ │ │ ├── HotUpdater # 热更新
|
||||
│ │ │ ├── checkupdate # electron-updater
|
||||
│ │ │ ├── downloadFile # 下载文件
|
||||
│ │ │ ├── ipcMain # ipc通讯
|
||||
│ │ │ └── windowManager # 窗口管理
|
||||
│ │ ├─index.ts # 主进程入口
|
||||
│ │ └─ipc.ts # IPC相关的ts类定义声明
|
||||
│ ├─preload # 预加载(使渲染进程-子进程可以使用主进程的一些接口)
|
||||
│ └─renderer # 渲染进程文件夹
|
||||
├── .editorconfig # 编辑器配置,统一编码风格(可以根据自己的编程习惯来,但强烈建议别修改)
|
||||
├── tsconfig.json # typeScript配置
|
||||
├── updateConfig.json # update升级配置
|
||||
├── build.json # 项目打包配置
|
||||
└── package.json # package.json;推荐使用yarn安装各种包
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
├── renderer # 渲染进程补充说明
|
||||
│ ├─api # 请求以及数据库操作文件夹
|
||||
│ ├─assets # 渲染进程主题 字体等静态资源
|
||||
│ ├─components # 全局公用组件
|
||||
│ ├─fixTypes # TS模块补充
|
||||
│ ├─i18n # 多语言配置
|
||||
│ ├─mock # 本地模拟数据
|
||||
│ ├─public # 启动页等(非必要不要改)
|
||||
│ ├─router # 路由(各自定义主题的路由请按规范在主题内添加就好,这里不用修改)
|
||||
│ ├─store # 全局 store管理
|
||||
│ ├─styles # 全局样式
|
||||
│ ├─themes # 自定义主题框架
|
||||
│ │ ├─default # 默认主题框架(UI组件主要使用element-plus)
|
||||
│ ├─types # 全局TS模块补充
|
||||
│ ├─utils # 全局公共方法
|
||||
│ └─views # views 所有页面
|
||||
│ ├─App*.vue # vue主模块(需要自定义的可以复制新建一份修改后再引用即可)
|
||||
│ ├─error.ts # 错误捕捉
|
||||
│ ├─index.html # 入口html
|
||||
│ ├─interceptor.ts # 路由权限的基础拦截(更细致的拦截方法可以自行编写,如通过数据库获取等)
|
||||
│ ├─main-multi-theme.ts # 自定义主题入口文件
|
||||
|
||||
|
||||
│ ├─icons # 项目svg icons
|
||||
│ ├─layout # 全局 layout
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# [更新日志](./CHANGELOG.md)
|
36
LICENSE
@ -1,23 +1,21 @@
|
||||
Copyright <yyyy, yyyy> The Open Group
|
||||
MIT License
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this software and
|
||||
its documentation for any purpose is hereby granted without fee,
|
||||
provided that the above copyright notice appear in all copies and that
|
||||
both that copyright notice and this permission notice appear in
|
||||
supporting documentation.
|
||||
Modified work Copyright (c) 2021-present umbrella22
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
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 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 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.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
Except as contained in this notice, the name of The Open Group
|
||||
shall not be used in advertising or otherwise to promote the sale, use
|
||||
or other dealings in this Software without prior written authorization
|
||||
from The Open Group.
|
||||
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.
|
63
README.md
@ -1,3 +1,62 @@
|
||||
# hi-sass-frame
|
||||
# electron-vite-template
|
||||
|
||||
基于https://gitee.com/Zh-Sky/electron-vite-template.git项目进行改造,升级内置组件electron31+vite5+vue3,添加引入elementUi2.7.5。框架思路借鉴有来开源的https://gitee.com/youlaiorg/vue3-element-admin.git项目。
|
||||
[Open in Visual Studio Code](https://open.vscode.dev/umbrella22/electron-vite-template/)
|
||||

|
||||
[](https://github.com/vuejs/vue-next)
|
||||
[](https://github.com/vitejs/vite)
|
||||
[](https://www.npmjs.org/package/element-plus)
|
||||
[](https://github.com/electron/electron)
|
||||
[](https://github.com/umbrella22/electron-vite-template/blob/master/LICENSE)
|
||||
|
||||
# Installation
|
||||
|
||||
You can choose to clone the project or fork repository, or download the zip file directly. It is recommended to clone the repository so that you can receive the latest patches.
|
||||
|
||||
To run a project, you need to have **node version 18** or higher and **use npm as your dependency management tool**
|
||||
|
||||
[Document (Chinese only)](https://umbrella22.github.io/electron-vue-template-doc/)
|
||||
|
||||
[For Chinese Developers](/README_ZH.md)
|
||||
|
||||
[](https://github.com/umbrella22/electron-vite-template/actions/workflows/Build.yml)
|
||||
|
||||
# Build Setup
|
||||
|
||||
```bash
|
||||
# Clone this repository
|
||||
$ git clone https://github.com/umbrella22/electron-vite-template.git
|
||||
# Go into the repository
|
||||
$ cd electron-vite-template
|
||||
# install dependencies
|
||||
$ npm install
|
||||
|
||||
# serve with hot reload at localhost:9080
|
||||
$ npm run dev
|
||||
|
||||
# build electron application for production
|
||||
$ npm run build
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Function list
|
||||
|
||||
[x] Auto update
|
||||
[x] Incremental update
|
||||
[x] Loading animation before startup
|
||||
[x] i18n
|
||||
|
||||
# Built-in
|
||||
|
||||
- [vue-router](https://next.router.vuejs.org/index.html)
|
||||
- [pinia](https://pinia.esm.dev/)
|
||||
- [electron](http://www.electronjs.org/docs)
|
||||
- electron-updater
|
||||
- typescript
|
||||
|
||||
# Note
|
||||
|
||||
- [gitee](https://gitee.com/Zh-Sky/electron-vite-template) is only for domestic users to pull code,from github to synchronize,please visit github for PR
|
||||
- **Welcome to Issues and PR**
|
||||
|
37
README_ZH.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Electron-Vite-template
|
||||
|
||||

|
||||
[](https://github.com/vuejs/vue-next)
|
||||
[](https://github.com/vitejs/vite)
|
||||
[](https://www.npmjs.org/package/element-plus)
|
||||
[](https://github.com/electron/electron)
|
||||
[](https://github.com/umbrella22/electron-vite-template/blob/master/LICENSE)
|
||||
|
||||
[国内访问地址](https://gitee.com/Zh-Sky/electron-vite-template)
|
||||
|
||||
---
|
||||
### 请确保您的 node 版本大于等于 16.
|
||||
|
||||
#### 如何安装
|
||||
|
||||
```bash
|
||||
npm config edit
|
||||
# 该命令会打开npm的配置文件,请在空白处添加
|
||||
# registry=https://registry.npmmirror.com
|
||||
# ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/
|
||||
# ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
# 然后关闭该窗口,重启命令行.
|
||||
# 使用yarn安装
|
||||
npm install
|
||||
|
||||
# 启动之后,会在9080端口监听
|
||||
npm run dev
|
||||
|
||||
# build命令在不同系统环境中,需要的的不一样,需要自己根据自身环境进行配置
|
||||
npm run build
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
# [文件目录说明](DIR.md)
|
||||
# [更新日志](./CHANGELOG.md)
|
44
build.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"asar": false,
|
||||
"extraFiles": [],
|
||||
"publish": [
|
||||
{
|
||||
"provider": "generic",
|
||||
"url": "http://127.0.0.1"
|
||||
}
|
||||
],
|
||||
"productName": "hi-sass",
|
||||
"appId": "cn.fm453.hi-sass-frame-front.template",
|
||||
"directories": {
|
||||
"output": "build"
|
||||
},
|
||||
"files": [
|
||||
"dist/electron/**/*"
|
||||
],
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 410,
|
||||
"y": 150,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 130,
|
||||
"y": 150,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"icon": "build/icons/icon.icns"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icons/icon.ico",
|
||||
"target": "nsis"
|
||||
},
|
||||
"linux": {
|
||||
"target": "deb",
|
||||
"icon": "build/icons"
|
||||
}
|
||||
}
|
12
config/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export default {
|
||||
build: {
|
||||
cleanConsole: true,
|
||||
hotPublishConfigName:"updater",
|
||||
},
|
||||
dev: {
|
||||
removeElectronJunk: true,
|
||||
chineseLog: true,
|
||||
port: 9080,
|
||||
},
|
||||
DllFolder: "",
|
||||
};
|
11
env/dev.env
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
BASE_API='http://127.0.0.1:25565'
|
||||
NODE_ENV='development'
|
||||
|
||||
# 远程服务
|
||||
REMOTE_PORT=3000
|
||||
REMOTE_API='/api/v1/'
|
||||
# 线上接口地址
|
||||
REMOTE_API_URL='http://api.frame.sass.hiluker.cn'
|
||||
# 开发接口地址
|
||||
# REMOTE_API_URL='http://127.0.0.1:8989'
|
||||
REMOTE_SERVER=false
|
2
env/prod.env
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
BASE_API='http://127.0.0.1:25565'
|
||||
NODE_ENV='production'
|
1780
fixTypes/auto-imports.d.ts
vendored
Normal file
23
fixTypes/electron-env.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/// <reference types="vite-plugin-electron/electron-env" />
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
VSCODE_DEBUG?: 'true'
|
||||
/**
|
||||
* The built directory structure
|
||||
*
|
||||
* ```tree
|
||||
* ├─┬ dist-electron
|
||||
* │ ├─┬ main
|
||||
* │ │ └── index.js > Electron-Main
|
||||
* │ └─┬ preload
|
||||
* │ └── index.mjs > Preload-Scripts
|
||||
* ├─┬ dist
|
||||
* │ └── index.html > Electron-Renderer
|
||||
* ```
|
||||
*/
|
||||
APP_ROOT: string
|
||||
/** /dist/ or /public/ */
|
||||
VITE_PUBLIC: string
|
||||
}
|
||||
}
|
12
fixTypes/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
interface Window {
|
||||
// expose in the `electron/preload/index.ts`
|
||||
ipcRenderer: import('electron').IpcRenderer
|
||||
}
|
17076
package-lock.json
generated
Normal file
176
package.json
Normal file
@ -0,0 +1,176 @@
|
||||
{
|
||||
"name": "hi-sass-frame-front",
|
||||
"title": "Hi-sass系统框架",
|
||||
"version": "0.0.1",
|
||||
"main": "./dist/electron/main/main.js",
|
||||
"author": "fm453 <https://gitea.hiluker.com/fm453/hi-sass-frame>",
|
||||
"homepage": "https://gitea.hiluker.com/fm453/hi-sass-frame",
|
||||
"repository": "https://gitea.hiluker.com/fm453/hi-sass-frame.git",
|
||||
"description": "基于https://gitee.com/Zh-Sky/electron-vite-template.git项目进行改造,升级内置组件electron31+vite5+vue3,添加引入elementUi2.7.5。框架思路借鉴有来开源的https://gitee.com/youlaiorg/vue3-element-admin.git项目。",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "cross-env tsx .electron-vite/dev-runner.ts -m dev",
|
||||
"build": "cross-env BUILD_TARGET=clean tsx .electron-vite/build.ts -m prod && electron-builder -c build.json",
|
||||
"build:win32": "cross-env BUILD_TARGET=clean tsx .electron-vite/build.ts -w prod && electron-builder -c build.json --win --ia32",
|
||||
"build:win64": "cross-env BUILD_TARGET=clean tsx .electron-vite/build.ts -w prod && electron-builder -c build.json --win --x64",
|
||||
"build:mac": "cross-env BUILD_TARGET=clean tsx .electron-vite/build.ts -m prod && electron-builder -c build.json --mac",
|
||||
"build:dir": "cross-env BUILD_TARGET=clean tsx .electron-vite/build.ts -m prod && electron-builder -c build.json --dir",
|
||||
"build:deb": "cross-env BUILD_TARGET=clean tsx .electron-vite/build.ts -l prod && electron-builder -c build.json --linux",
|
||||
"build:clean": "cross-env BUILD_TARGET=onlyClean tsx .electron-vite/build.ts",
|
||||
"build:web": "cross-env BUILD_TARGET=web tsx .electron-vite/build.ts -m prod",
|
||||
"pack:resources": "tsx .electron-vite/hot-updater.ts",
|
||||
"dep:upgrade": "yarn upgrade-interactive --latest",
|
||||
"postinstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.11.1",
|
||||
"@univerjs/core": "^0.2.8",
|
||||
"@univerjs/data-validation": "^0.2.8",
|
||||
"@univerjs/design": "^0.2.8",
|
||||
"@univerjs/docs": "^0.2.8",
|
||||
"@univerjs/docs-hyper-link": "^0.2.9",
|
||||
"@univerjs/docs-ui": "^0.2.8",
|
||||
"@univerjs/drawing": "^0.2.8",
|
||||
"@univerjs/drawing-ui": "^0.2.8",
|
||||
"@univerjs/engine-formula": "^0.2.8",
|
||||
"@univerjs/engine-numfmt": "^0.2.8",
|
||||
"@univerjs/engine-render": "^0.2.8",
|
||||
"@univerjs/facade": "^0.2.8",
|
||||
"@univerjs/find-replace": "^0.2.8",
|
||||
"@univerjs/network": "^0.2.9",
|
||||
"@univerjs/rpc": "^0.2.9",
|
||||
"@univerjs/sheets": "^0.2.8",
|
||||
"@univerjs/sheets-conditional-formatting": "^0.2.8",
|
||||
"@univerjs/sheets-conditional-formatting-ui": "^0.2.8",
|
||||
"@univerjs/sheets-crosshair-highlight": "^0.2.9",
|
||||
"@univerjs/sheets-data-validation": "^0.2.8",
|
||||
"@univerjs/sheets-drawing": "^0.2.8",
|
||||
"@univerjs/sheets-drawing-ui": "^0.2.8",
|
||||
"@univerjs/sheets-filter": "^0.2.8",
|
||||
"@univerjs/sheets-filter-ui": "^0.2.8",
|
||||
"@univerjs/sheets-find-replace": "^0.2.8",
|
||||
"@univerjs/sheets-formula": "^0.2.8",
|
||||
"@univerjs/sheets-hyper-link": "^0.2.8",
|
||||
"@univerjs/sheets-hyper-link-ui": "^0.2.8",
|
||||
"@univerjs/sheets-numfmt": "^0.2.8",
|
||||
"@univerjs/sheets-sort": "^0.2.8",
|
||||
"@univerjs/sheets-sort-ui": "^0.2.8",
|
||||
"@univerjs/sheets-thread-comment": "^0.2.8",
|
||||
"@univerjs/sheets-thread-comment-base": "^0.2.8",
|
||||
"@univerjs/sheets-ui": "^0.2.8",
|
||||
"@univerjs/sheets-zen-editor": "^0.2.8",
|
||||
"@univerjs/thread-comment": "^0.2.8",
|
||||
"@univerjs/thread-comment-ui": "^0.2.8",
|
||||
"@univerjs/ui": "^0.2.8",
|
||||
"@univerjs/vite-plugin": "^0.5.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.7.4",
|
||||
"clsx": "^2.1.1",
|
||||
"color": "^4.2.3",
|
||||
"csstype": "^3.1.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"electron-log": "^5.1.7",
|
||||
"electron-updater": "^6.2.1",
|
||||
"electron_updater_node_cli": "^0.3.3",
|
||||
"electron_updater_node_core": "^0.3.3",
|
||||
"element-plus": "^2.8.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^4.19.2",
|
||||
"express-ws": "^5.0.2",
|
||||
"glob": "^11.0.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pinia": "^2.2.2",
|
||||
"regedit": "^5.1.3",
|
||||
"rxjs": "^7.8.1",
|
||||
"semver": "^7.6.3",
|
||||
"sockjs-client": "^1.6.1",
|
||||
"sortablejs": "^1.15.3",
|
||||
"stompjs": "^2.3.3",
|
||||
"unocss": "^0.62.3",
|
||||
"uuid": "^10.0.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "9.14.0",
|
||||
"vue-router": "^4.4.3",
|
||||
"webstomp-client": "^1.2.6",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ep": "^1.1.16",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/color": "^3.0.6",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-ws": "^3.0.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^22.5.4",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/path-browserify": "^1.0.3",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/sockjs-client": "^1.5.4",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@types/stompjs": "^2.3.9",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.1",
|
||||
"@vue/compiler-sfc": "^3.4.38",
|
||||
"adm-zip": "^0.5.15",
|
||||
"cfonts": "^3.3.0",
|
||||
"chalk": "5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"del": "^7.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"electron": "^32.0.2",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-builder-squirrel-windows": "^24.13.3",
|
||||
"electron-devtools-vendor": "^3.0.0",
|
||||
"esbuild": "^0.23.1",
|
||||
"extract-zip": "^2.0.1",
|
||||
"fs-extra": "^11.2.0",
|
||||
"inquirer": "^10.1.8",
|
||||
"javascript-obfuscator": "^4.1.1",
|
||||
"listr2": "^8.2.4",
|
||||
"minimist": "^1.2.8",
|
||||
"path-to-regexp": "^7.1.0",
|
||||
"portfinder": "^1.0.32",
|
||||
"postcss": "^8.4.41",
|
||||
"postcss-html": "^1.7.0",
|
||||
"postcss-scss": "^4.0.9",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"rollup": "^4.21.0",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-obfuscator": "^1.1.0",
|
||||
"sass": "^1.77.8",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.17.0",
|
||||
"typescript": "^5.5.4",
|
||||
"unplugin-auto-import": "^0.17.6",
|
||||
"unplugin-icons": "^0.18.5",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.4.1",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-vue-devtools": "^7.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"vite",
|
||||
"electron",
|
||||
"vue3",
|
||||
"rollup",
|
||||
"element-ui"
|
||||
]
|
||||
}
|
12048
pnpm-lock.yaml
generated
Normal file
7
src/main/config/alias.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* electron的主进程默认无法使用路径别名,故此想法自己搞一个 TBD 未开始
|
||||
*/
|
||||
export const alias = {
|
||||
"@": "src/renderer",
|
||||
"@src": "src",
|
||||
};
|
213
src/main/config/channels.ts
Normal file
@ -0,0 +1,213 @@
|
||||
// TBD 将进程通信的channel拎出来单独写,方便清晰调用,未使用
|
||||
export default {
|
||||
"IsUseSysTitle": {
|
||||
"key": "",
|
||||
"handle": "IsUseSysTitle",
|
||||
"des": "是否使用无边框"
|
||||
},
|
||||
"WindowMini": {
|
||||
"key": "",
|
||||
"handle": "windows-mini",
|
||||
"des": "窗口最小化"
|
||||
},
|
||||
"WindowMax": {
|
||||
"key": "",
|
||||
"handle": "windows-max",
|
||||
"des": "窗口最大化"
|
||||
},
|
||||
"WindowClose": {
|
||||
"key": "",
|
||||
"handle": "windows-close",
|
||||
"des": "窗口关闭"
|
||||
},
|
||||
"CheckUpdate": {
|
||||
"key": "",
|
||||
"handle": "check-update",
|
||||
"des": "检查更新"
|
||||
},
|
||||
"ConfirmUpdate": {
|
||||
"key": "",
|
||||
"handle": "confirm-update",
|
||||
"des": "确认更新"
|
||||
},
|
||||
"AppClose": {
|
||||
"key": "",
|
||||
"handle": "app-close",
|
||||
"des": "app退出"
|
||||
},
|
||||
"GetStaticPath": {
|
||||
"key": "",
|
||||
"handle": "get-static-path",
|
||||
"des": "获取静态资源路径"
|
||||
},
|
||||
"OpenMessagebox": {
|
||||
"key": "",
|
||||
"handle": "open-messagebox",
|
||||
"des": "打开系统弹窗信息"
|
||||
},
|
||||
"OpenErrorbox": {
|
||||
"key": "",
|
||||
"handle": "open-errorbox",
|
||||
"des": "打开系统错误弹窗信息"
|
||||
},
|
||||
"StartServer": {
|
||||
"key": "",
|
||||
"handle": "start-server",
|
||||
"des": "开启http服务"
|
||||
},
|
||||
"StopServer": {
|
||||
"key": "",
|
||||
"handle": "stop-server",
|
||||
"des": "停止http服务"
|
||||
},
|
||||
"StartWsServer": {
|
||||
"key": "",
|
||||
"handle": "start-wsserver",
|
||||
"des": "开启WS服务"
|
||||
},
|
||||
"StopWsServer": {
|
||||
"key": "",
|
||||
"handle": "stop-wsserver",
|
||||
"des": "停止WS服务"
|
||||
},
|
||||
"HotUpdate": {
|
||||
"key": "",
|
||||
"handle": "hot-update",
|
||||
"des": "增量更新"
|
||||
},
|
||||
"HotUpdateTest": {
|
||||
"key": "",
|
||||
"handle": "hot-update-test",
|
||||
"des": "测试增量更新"
|
||||
},
|
||||
"StartDownload": {
|
||||
"key": "",
|
||||
"handle": "start-download",
|
||||
"des": "下载东西"
|
||||
},
|
||||
"OpenWin": {
|
||||
"key": "",
|
||||
"handle": "open-win",
|
||||
"des": "打开新的弹窗"
|
||||
},
|
||||
"GetPrinters": {
|
||||
"key": "",
|
||||
"handle": "getPrinters",
|
||||
"des": "获取打印机信息"
|
||||
},
|
||||
"PrintHandlePrint": {
|
||||
"key": "",
|
||||
"handle": "printHandlePrint",
|
||||
"des": "打印"
|
||||
},
|
||||
"OpenPrintDemoWindow": {
|
||||
"key": "",
|
||||
"handle": "openPrintDemoWindow",
|
||||
"des": "打开测试打印页面"
|
||||
},
|
||||
"DownloadProgress": {
|
||||
"key": "",
|
||||
"handle": "download-progress",
|
||||
"des": "下载进度回调"
|
||||
},
|
||||
"DownloadError": {
|
||||
"key": "",
|
||||
"handle": "download-error",
|
||||
"des": "下载错误回调"
|
||||
},
|
||||
"DownloadPaused": {
|
||||
"key": "",
|
||||
"handle": "download-paused",
|
||||
"des": "下载暂停回调"
|
||||
},
|
||||
"DownloadDone": {
|
||||
"key": "",
|
||||
"handle": "download-done",
|
||||
"des": "下载完成回调"
|
||||
},
|
||||
"UpdateMsg": {
|
||||
"key": "",
|
||||
"handle": "UpdateMsg",
|
||||
"des": "更新回调消息"
|
||||
},
|
||||
"HotUpdateStatus": {
|
||||
"key": "",
|
||||
"handle": "hot-update-status",
|
||||
"des": "热更新状态回调"
|
||||
},
|
||||
"SendDataTest": {
|
||||
"key": "",
|
||||
"handle": "send-data-test",
|
||||
"des": "数据测试回调"
|
||||
},
|
||||
"AddDefaultBrowserView": {
|
||||
"key": "",
|
||||
"handle": "add-default-browser-view",
|
||||
"des": "添加新的默认页面"
|
||||
},
|
||||
"SelectBrowserDemoTab": {
|
||||
"key": "",
|
||||
"handle": "select-browser-demo-tab",
|
||||
"des": "选择tab"
|
||||
},
|
||||
"DestroyBrowserDemoTab": {
|
||||
"key": "",
|
||||
"handle": "destroy-browser-demo-tab",
|
||||
"des": "关闭tab"
|
||||
},
|
||||
"BrowserDemoTabJumpToUrl": {
|
||||
"key": "",
|
||||
"handle": "browser-demo-tab-jump-to-url",
|
||||
"des": "tab 跳转"
|
||||
},
|
||||
"OpenBrowserDemoWindow": {
|
||||
"key": "",
|
||||
"handle": "open-browser-demo",
|
||||
"des": "打开浏览器demo"
|
||||
},
|
||||
"BrowserTabMousedown": {
|
||||
"key": "",
|
||||
"handle": "browser-tab-mousedown",
|
||||
"des": "Browser tab 鼠标按下"
|
||||
},
|
||||
"BrowserTabMousemove": {
|
||||
"key": "",
|
||||
"handle": "browser-tab-mousemove",
|
||||
"des": "Browser tab 鼠标拖动"
|
||||
},
|
||||
"BrowserTabMouseup": {
|
||||
"key": "",
|
||||
"handle": "browser-tab-mouseup",
|
||||
"des": "Browser tab 鼠标松开"
|
||||
},
|
||||
"GetLastBrowserDemoTabData": {
|
||||
"key": "",
|
||||
"handle": "get-last-browser-demo-tab-data",
|
||||
"des": "获取最后一次拖动的 tab 信息"
|
||||
},
|
||||
"BrowserViewTabDataUpdate": {
|
||||
"key": "",
|
||||
"handle": "browser-view-tab-data-updated",
|
||||
"des": "更新 tab 信息"
|
||||
},
|
||||
"BrowserViewTabPositionXUpdate": {
|
||||
"key": "",
|
||||
"handle": "browser-view-tab-position-x-updated",
|
||||
"des": "更新 tab 坐标"
|
||||
},
|
||||
"SetShowOnMyComputer": {
|
||||
"key": "",
|
||||
"handle": "set-show-on-my-computer",
|
||||
"des": "设置在我的电脑显示"
|
||||
},
|
||||
"CheckShowOnMyComputer": {
|
||||
"key": "",
|
||||
"handle": "check-show-on-my-computer",
|
||||
"des": "查询当前是否显示在我的电脑"
|
||||
},
|
||||
"Mocker": {
|
||||
"key": "",
|
||||
"handle": "get-mock-data",
|
||||
"des": "自定义Mock模拟网络请求-By fm453"
|
||||
}
|
||||
}
|
33
src/main/config/const.ts
Normal file
@ -0,0 +1,33 @@
|
||||
export const UseStartupChart: boolean = true;
|
||||
export const IsUseSysTitle: boolean = true;
|
||||
export const BuiltInServerPort: number = 25565;
|
||||
export const hotPublishUrl: string =
|
||||
"https://gitea.hiluker.com/fm453/hi-sass-frame";
|
||||
export const hotPublishConfigName: string = "update-config";
|
||||
export const openDevTools: boolean = false;
|
||||
export const DisableF12: boolean = true;
|
||||
export const HotUpdateFolder: string = "hot-update";
|
||||
export const GwLink: string = "http://www.hiluker.cn";
|
||||
export const HilukerLink: string = "http://www.hiluker.cn";
|
||||
export const BrowserDemoUrl: string = "https://www.hiluker.cn";
|
||||
// 本地数组缓存KEY,根据自己的项目另行设置,封装localStorage方法的时候用到
|
||||
export const StorageKey: string = "Hi_sass_tester_453";
|
||||
|
||||
// # 远程服务
|
||||
const RemotePort = 3000;
|
||||
const RemoteApi = "/api/v1/";
|
||||
// # 线上接口地址
|
||||
const RemoteApiUrl = "http://api.frame.sass.hiluker.cn";
|
||||
// # 开发接口地址
|
||||
// const REMOTE_API_URL = "http://127.0.0.1:8989";
|
||||
const RemoteServer = false;
|
||||
|
||||
export default {
|
||||
GwLink,
|
||||
UseStartupChart,
|
||||
IsUseSysTitle,
|
||||
DisableF12,
|
||||
HilukerLink,
|
||||
StorageKey,
|
||||
RemoteApi
|
||||
}
|
5
src/main/config/domains.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 域名白名单
|
||||
*/
|
||||
const domains = ["http://127.0.0.1", "https://127.0.0.1", "http://localhost"];
|
||||
export default domains;
|
11
src/main/config/hotPublish.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {hotPublishUrl, hotPublishConfigName} from "./const";
|
||||
|
||||
interface hotPublish {
|
||||
url: string;
|
||||
configName: string;
|
||||
}
|
||||
|
||||
export const hotPublishConfig: hotPublish = {
|
||||
url: hotPublishUrl,
|
||||
configName: hotPublishConfigName,
|
||||
};
|
164
src/main/config/staticPath.ts
Normal file
@ -0,0 +1,164 @@
|
||||
// 这里定义了静态文件路径的位置
|
||||
import {join} from "path";
|
||||
import {HotUpdateFolder} from "./const";
|
||||
import {app} from "electron";
|
||||
import {URL} from "url";
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
|
||||
/**
|
||||
* 目录名: 生产环境 | 开发环境
|
||||
* 基础目录: APP目录 | src下
|
||||
* __static: 基础目录/renderer | 基础目录/renderer/public
|
||||
* __lib: 基础目录 | 基础目录/build/lib/*
|
||||
* __common: 基础目录 | 基础目录/build/lib/common
|
||||
*/
|
||||
class StaticPath {
|
||||
/**
|
||||
* 静态文件路径 渲染进程目录下
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StaticPath
|
||||
*/
|
||||
__static: string;
|
||||
/**
|
||||
* dll文件夹及其他os平台相关的文件路径
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StaticPath
|
||||
*/
|
||||
__lib: string;
|
||||
/**
|
||||
* 与os无关的资源
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StaticPath
|
||||
*/
|
||||
__common: string;
|
||||
/**
|
||||
* 增量更新文件夹
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof StaticPath
|
||||
*/
|
||||
__updateFolder: string;
|
||||
|
||||
constructor() {
|
||||
const basePath = isDev
|
||||
? join(__dirname, "..", "..", "..")
|
||||
: join(app.getAppPath(), "..", "..");
|
||||
this.__updateFolder = join(basePath, `${HotUpdateFolder}`);
|
||||
if (isDev) {
|
||||
this.__static = join(basePath, "src", "renderer", "public");
|
||||
this.__lib = join(
|
||||
basePath,
|
||||
`build`,
|
||||
`lib`,
|
||||
`${process.platform}`,
|
||||
`${process.arch}`
|
||||
);
|
||||
this.__common = join(basePath, "build", "lib", "common");
|
||||
} else {
|
||||
this.__static = join(__dirname, "..", "renderer");
|
||||
this.__lib = basePath;
|
||||
this.__common = basePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const staticPath = new StaticPath();
|
||||
|
||||
/**
|
||||
* 获取真正的地址
|
||||
*
|
||||
* @param {string} devPath 开发环境路径
|
||||
* @param {string} proPath 生产环境路径
|
||||
* @param {string} [hash=""] hash值
|
||||
* @param {string} [search=""] search值
|
||||
* @return {*} {string} 地址
|
||||
*/
|
||||
function getUrl(
|
||||
devPath: string,
|
||||
proPath: string,
|
||||
hash: string = "",
|
||||
search: string = ""
|
||||
): string {
|
||||
const url = isDev
|
||||
? new URL(`http://localhost:${process.env.PORT}`)
|
||||
: new URL("file://");
|
||||
url.pathname = isDev ? devPath : proPath;
|
||||
url.hash = hash;
|
||||
url.search = search;
|
||||
return url.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口入口文件
|
||||
* 开发环境:""
|
||||
* 产品环境:src/renderer/index.html
|
||||
*/
|
||||
export const winURL = getUrl(
|
||||
"",
|
||||
join(__dirname, "..", "renderer", "index.html")
|
||||
);
|
||||
/**
|
||||
* 窗口入口文件
|
||||
* 开发环境:"/loader.html"
|
||||
* 产品环境:src/render/public/loader.html
|
||||
*/
|
||||
export const loadingURL = getUrl(
|
||||
"/loader.html",
|
||||
`${staticPath.__static}/loader.html`
|
||||
);
|
||||
|
||||
export const preloadURL = getUrl(
|
||||
"/preload.html",
|
||||
`${staticPath.__static}/preload.html`
|
||||
);
|
||||
export const printURL = getUrl(
|
||||
"",
|
||||
join(__dirname, "..", "renderer", "index.html"),
|
||||
"#/Print"
|
||||
);
|
||||
export const browserDemoURL = getUrl(
|
||||
"",
|
||||
join(__dirname, "..", "renderer", "index.html"),
|
||||
"#/Browser"
|
||||
);
|
||||
/**
|
||||
* 预加载文件
|
||||
* 开发环境:src/main/preload/index.ts
|
||||
* 产品环境:dist/electron/preload/main/preload.js
|
||||
*/
|
||||
export const preloadPath = isDev
|
||||
? join(__dirname, "..", "..", "..", "dist", "electron", "main", "preload.js")
|
||||
: join(app.getAppPath(), "dist", "electron", "main", "preload.js");
|
||||
|
||||
/**
|
||||
* 托盘设置文件
|
||||
* 开发环境:/tray.html
|
||||
* 产品环境:src/renderer/public/tray.html
|
||||
*/
|
||||
export const trayURL = getUrl(
|
||||
"/tray.html",
|
||||
`${staticPath.__static}/tray.html`
|
||||
);
|
||||
/**
|
||||
* 托盘图标设置
|
||||
*/
|
||||
export const trayIconPath = isDev
|
||||
? join(staticPath.__static, "trayIcon", "trayIcon.png")
|
||||
: join(app.getAppPath(), "dist", "electron", "renderer", "trayIcon", "trayIcon.png");
|
||||
export const trayTransparentIconPath = isDev
|
||||
? join(staticPath.__static, "trayIcon", "transparent.png")
|
||||
: join(app.getAppPath(), "dist", "electron", "renderer", "trayIcon", "transparent.png");
|
||||
|
||||
export const lib = staticPath.__lib;
|
||||
export const common = staticPath.__common;
|
||||
export const updateFolder = staticPath.__updateFolder;
|
||||
export const staticPaths = getUrl("", staticPath.__static);
|
||||
|
||||
// process.env 修改
|
||||
for (const key in staticPath) {
|
||||
process.env[key] = staticPath[key];
|
||||
}
|
44
src/main/config/windowsConfig.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {IsUseSysTitle} from "./const";
|
||||
import {preloadPath} from "./staticPath";
|
||||
import {BrowserWindowConstructorOptions} from "electron";
|
||||
|
||||
const preload = preloadPath;
|
||||
|
||||
export const mainWindowConfig: BrowserWindowConstructorOptions = {
|
||||
height: 800,
|
||||
useContentSize: true,
|
||||
width: 1700,
|
||||
minWidth: 1366,
|
||||
show: false,
|
||||
frame: IsUseSysTitle,
|
||||
webPreferences: {
|
||||
preload,
|
||||
contextIsolation: true, //内容上下文隔离
|
||||
nodeIntegration: false, //是否允许禁用渲染器沙盒,禁用则窗口可使用Node.js模块的系统API
|
||||
webSecurity: true, //开启后将出现域名cors限制;内置服务器的mock请求要相应地调整express配置,在server/server.ts中配置跨域
|
||||
// 如果是开发模式可以使用devTools
|
||||
devTools: process.env.NODE_ENV === "development",
|
||||
// 在macos中启用橡皮动画
|
||||
scrollBounce: process.platform === "darwin",
|
||||
},
|
||||
};
|
||||
|
||||
export const otherWindowConfig: BrowserWindowConstructorOptions = {
|
||||
height: 595,
|
||||
useContentSize: true,
|
||||
width: 1140,
|
||||
autoHideMenuBar: true,
|
||||
minWidth: 842,
|
||||
frame: IsUseSysTitle,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
webSecurity: true,
|
||||
// 如果是开发模式可以使用devTools
|
||||
devTools: process.env.NODE_ENV === "development",
|
||||
// 在macos中启用橡皮动画
|
||||
scrollBounce: process.platform === "darwin",
|
||||
},
|
||||
};
|
323
src/main/handle/browserHandle.ts
Normal file
@ -0,0 +1,323 @@
|
||||
import {BrowserView, BrowserWindow, screen} from "electron";
|
||||
import {IpcChannel, IpcMainHandle} from "../ipc";
|
||||
import {useHookBrowser} from "../hook/browserHook";
|
||||
|
||||
let dragTabOffsetX: number;
|
||||
let lastDragView: BrowserView;
|
||||
let emptyWin: BrowserWindow;
|
||||
let viewFromWin: BrowserWindow;
|
||||
let useNewWindow: BrowserWindow;
|
||||
const winList: BrowserWindow[] = [];
|
||||
const viewList: BrowserView[] = [];
|
||||
let startScreenY: number;
|
||||
|
||||
export function useBrowserHandle(): Pick<
|
||||
IpcMainHandle,
|
||||
| IpcChannel.OpenBrowserDemoWindow
|
||||
| IpcChannel.GetLastBrowserDemoTabData
|
||||
| IpcChannel.AddDefaultBrowserView
|
||||
| IpcChannel.SelectBrowserDemoTab
|
||||
| IpcChannel.DestroyBrowserDemoTab
|
||||
| IpcChannel.BrowserDemoTabJumpToUrl
|
||||
| IpcChannel.BrowserTabMousedown
|
||||
| IpcChannel.BrowserTabMousemove
|
||||
| IpcChannel.BrowserTabMouseup
|
||||
> {
|
||||
return {
|
||||
[IpcChannel.OpenBrowserDemoWindow]: async (event) => {
|
||||
useHookBrowser.openBrowserDemoWindow();
|
||||
},
|
||||
[IpcChannel.GetLastBrowserDemoTabData]: async (event) => {
|
||||
// 拖出tab创建的窗口获取当前tab信息
|
||||
if (lastDragView) {
|
||||
let positionX = -1
|
||||
if (dragTabOffsetX) {
|
||||
const currentWin = BrowserWindow.fromBrowserView(lastDragView)
|
||||
const bound = currentWin.getBounds()
|
||||
const {x, y} = screen.getCursorScreenPoint()
|
||||
positionX = x - bound.x - dragTabOffsetX
|
||||
}
|
||||
return {
|
||||
positionX,
|
||||
bvWebContentsId: lastDragView.webContents.id,
|
||||
title: lastDragView.webContents.getTitle(),
|
||||
url: lastDragView.webContents.getURL(),
|
||||
};
|
||||
}
|
||||
useHookBrowser.openBrowserDemoWindow();
|
||||
},
|
||||
[IpcChannel.AddDefaultBrowserView]: async (event) => {
|
||||
// 添加tab的内容
|
||||
const currentWin = BrowserWindow.fromWebContents(event.sender);
|
||||
let bvWebContentsId = -1;
|
||||
if (currentWin) {
|
||||
const bv = useHookBrowser.createDefaultBrowserView(currentWin);
|
||||
bvWebContentsId = bv.webContents.id;
|
||||
}
|
||||
return {bvWebContentsId};
|
||||
},
|
||||
[IpcChannel.SelectBrowserDemoTab]: async (event, bvWebContentsId) => {
|
||||
// 选择tab为当前tab
|
||||
const currentWin = BrowserWindow.fromWebContents(event.sender);
|
||||
if (currentWin) {
|
||||
const bvList = currentWin.getBrowserViews();
|
||||
for (let i = 0; i < bvList.length; i++) {
|
||||
const bv = bvList[i];
|
||||
if (bv.webContents.id === bvWebContentsId) {
|
||||
currentWin.setTopBrowserView(bv);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[IpcChannel.DestroyBrowserDemoTab]: async (event, bvWebContentsId) => {
|
||||
// 关闭tab
|
||||
const currentWin = BrowserWindow.fromWebContents(event.sender);
|
||||
if (currentWin) {
|
||||
const bvList = currentWin.getBrowserViews();
|
||||
for (let i = 0; i < bvList.length; i++) {
|
||||
const bv = bvList[i];
|
||||
if (bv.webContents.id === bvWebContentsId) {
|
||||
currentWin.removeBrowserView(bv);
|
||||
if (bvList.length === 1) {
|
||||
currentWin.close();
|
||||
}
|
||||
bv.webContents.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[IpcChannel.BrowserDemoTabJumpToUrl]: async (
|
||||
event,
|
||||
{bvWebContentsId, url}
|
||||
) => {
|
||||
// 跳转
|
||||
const currentView = viewList.find(
|
||||
(v) => v.webContents.id === bvWebContentsId
|
||||
);
|
||||
currentView.webContents.loadURL(url);
|
||||
},
|
||||
[IpcChannel.BrowserTabMousedown]: async (event, {
|
||||
offsetX
|
||||
}) => {
|
||||
dragTabOffsetX = offsetX
|
||||
},
|
||||
[IpcChannel.BrowserTabMousemove]: async (
|
||||
event,
|
||||
{
|
||||
screenX, // 鼠标在显示器的x坐标
|
||||
screenY, // 鼠标在显示器的y坐标
|
||||
startX, // 按下鼠标时在窗口的x坐标
|
||||
startY, // 按下鼠标时在窗口的y坐标
|
||||
bvWebContentsId,
|
||||
}
|
||||
) => {
|
||||
if (!startScreenY) {
|
||||
startScreenY = screenY;
|
||||
}
|
||||
if (!viewFromWin) {
|
||||
viewFromWin = BrowserWindow.fromWebContents(event.sender);
|
||||
}
|
||||
let movingWin: BrowserWindow;
|
||||
const currentView = viewList.find(
|
||||
(v) => v.webContents.id === bvWebContentsId
|
||||
);
|
||||
lastDragView = currentView;
|
||||
if (viewFromWin.getBrowserViews().length <= 1) {
|
||||
movingWin = viewFromWin;
|
||||
} else {
|
||||
if (useNewWindow) {
|
||||
movingWin = useNewWindow;
|
||||
viewFromWin = useNewWindow;
|
||||
} else if (Math.abs(startScreenY - screenY) > 40) {
|
||||
// 如果Y差值大于40,则移动到新窗口
|
||||
if (emptyWin) {
|
||||
useNewWindow = emptyWin;
|
||||
useNewWindow.setHasShadow(true);
|
||||
emptyWin = null;
|
||||
} else {
|
||||
useNewWindow = useHookBrowser.openBrowserDemoWindow();
|
||||
}
|
||||
useHookBrowser.removeBrowserView(viewFromWin, currentView);
|
||||
useHookBrowser.addBrowserView(useNewWindow, currentView);
|
||||
viewFromWin = useNewWindow;
|
||||
movingWin = useNewWindow;
|
||||
movingWin.show();
|
||||
startScreenY = screenY;
|
||||
|
||||
// 设置拖拽的 tab 位置
|
||||
const bound = movingWin.getBounds()
|
||||
movingWin.webContents.send(
|
||||
IpcChannel.BrowserViewTabPositionXUpdate,
|
||||
{
|
||||
dragTabOffsetX,
|
||||
positionX: screenX - bound.x,
|
||||
bvWebContentsId: currentView.webContents.id,
|
||||
}
|
||||
);
|
||||
|
||||
} else {
|
||||
// 内部移动 movingWin = null
|
||||
for (let i = 0; i < winList.length; i++) {
|
||||
const existsWin = winList[i];
|
||||
const bound = existsWin.getBounds();
|
||||
if (
|
||||
existsWin !== emptyWin &&
|
||||
bound.x < screenX &&
|
||||
bound.x + bound.width > screenX &&
|
||||
// 在tabbar的范围
|
||||
bound.y + 30 < screenY &&
|
||||
bound.y + 70 > screenY
|
||||
) {
|
||||
existsWin.webContents.send(
|
||||
IpcChannel.BrowserViewTabPositionXUpdate,
|
||||
{
|
||||
dragTabOffsetX,
|
||||
positionX: screenX - bound.x,
|
||||
bvWebContentsId: currentView.webContents.id,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (movingWin) {
|
||||
movingWin.setPosition(screenX - startX, screenY - startY);
|
||||
// 判断是否需要添加进新窗口
|
||||
for (let i = 0; i < winList.length; i++) {
|
||||
const existsWin = winList[i];
|
||||
const bound = existsWin.getBounds();
|
||||
const tabbarCenterY = bound.y + 50; // titlebar 30 tabbar 40 / 2
|
||||
if (
|
||||
existsWin !== emptyWin &&
|
||||
existsWin !== movingWin &&
|
||||
bound.x < screenX &&
|
||||
bound.x + bound.width > screenX &&
|
||||
Math.abs(tabbarCenterY - screenY) < 20
|
||||
) {
|
||||
useHookBrowser.removeBrowserView(movingWin, currentView);
|
||||
if (movingWin.getBrowserViews().length === 0) {
|
||||
emptyWin = movingWin;
|
||||
emptyWin.setHasShadow(false);
|
||||
emptyWin.setAlwaysOnTop(false);
|
||||
emptyWin.setBounds(bound);
|
||||
if (emptyWin === useNewWindow) {
|
||||
useNewWindow = null;
|
||||
}
|
||||
}
|
||||
useHookBrowser.addBrowserView(existsWin, currentView);
|
||||
viewFromWin = existsWin;
|
||||
startScreenY = screenY;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[IpcChannel.BrowserTabMouseup]: async (event) => {
|
||||
winList.map((win) => {
|
||||
if (win?.getBrowserViews().length === 0) {
|
||||
win?.close();
|
||||
} else {
|
||||
win?.setAlwaysOnTop(false);
|
||||
win?.webContents?.send(IpcChannel.BrowserTabMouseup)
|
||||
}
|
||||
});
|
||||
useNewWindow = null;
|
||||
startScreenY = null;
|
||||
emptyWin = null;
|
||||
viewFromWin = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// function openBrowserDemoWindow() {
|
||||
// let win = new BrowserWindow({
|
||||
// titleBarStyle: IsUseSysTitle ? "default" : "hidden",
|
||||
// ...Object.assign(otherWindowConfig, {}),
|
||||
// });
|
||||
// // // 开发模式下自动开启devtools
|
||||
// if (process.env.NODE_ENV === "development") {
|
||||
// openDevTools(win)
|
||||
// }
|
||||
// win.loadURL(browserDemoURL);
|
||||
// win.on("ready-to-show", () => {
|
||||
// win.show();
|
||||
// });
|
||||
// win.on("closed", () => {
|
||||
// const findIndex = winList.findIndex((v) => win === v);
|
||||
// if (findIndex !== -1) {
|
||||
// winList.splice(findIndex, 1);
|
||||
// }
|
||||
// });
|
||||
// winList.push(win);
|
||||
// return win;
|
||||
// }
|
||||
//
|
||||
// function createDefaultBrowserView(
|
||||
// win: BrowserWindow,
|
||||
// defaultUrl = BrowserDemoUrl
|
||||
// ) {
|
||||
// const [winWidth, winHeight] = win.getSize();
|
||||
// const bv = new BrowserView();
|
||||
// win.addBrowserView(bv);
|
||||
// // title-bar 30px tabbar 40px searchbar 40px
|
||||
// bv.setBounds({x: 0, y: 110, width: winWidth, height: winHeight - 110});
|
||||
// bv.setAutoResize({
|
||||
// width: true,
|
||||
// height: true,
|
||||
// });
|
||||
// bv.webContents.on('did-finish-load', () => {
|
||||
// console.log(bv.webContents.getURL())
|
||||
// })
|
||||
// bv.webContents.loadURL(defaultUrl);
|
||||
// bv.webContents.on("page-title-updated", (event, title) => {
|
||||
// const parentBw = BrowserWindow.fromBrowserView(bv);
|
||||
// if (parentBw) {
|
||||
// freshTabData(parentBw, bv, 1)
|
||||
// }
|
||||
// });
|
||||
// bv.webContents.on("destroyed", () => {
|
||||
// const findIndex = viewList.findIndex((v) => v === bv);
|
||||
// if (findIndex !== -1) {
|
||||
// viewList.splice(findIndex, 1);
|
||||
// }
|
||||
// });
|
||||
// bv.webContents.setWindowOpenHandler((details) => {
|
||||
// const parentBw = BrowserWindow.fromBrowserView(bv);
|
||||
// createDefaultBrowserView(parentBw, details.url);
|
||||
// return {action: "deny"};
|
||||
// });
|
||||
// freshTabData(win, bv, 1)
|
||||
// viewList.push(bv);
|
||||
// return bv;
|
||||
// }
|
||||
//
|
||||
// function addBrowserView(win: BrowserWindow, view: BrowserView) {
|
||||
// if (BrowserWindow.fromBrowserView(view) !== win) {
|
||||
// win.addBrowserView(view);
|
||||
// win.show();
|
||||
// win.setAlwaysOnTop(true);
|
||||
// }
|
||||
// freshTabData(win, view, 1)
|
||||
// }
|
||||
//
|
||||
// function removeBrowserView(win: BrowserWindow, view: BrowserView) {
|
||||
// if (BrowserWindow.fromBrowserView(view) === win) {
|
||||
// win.removeBrowserView(view);
|
||||
// }
|
||||
// freshTabData(win, view, -1)
|
||||
// }
|
||||
//
|
||||
// function freshTabData(win: BrowserWindow, bv: BrowserView, status: -1 | 1) {
|
||||
// win.webContents.send(IpcChannel.BrowserViewTabDataUpdate, {
|
||||
// bvWebContentsId: bv.webContents.id,
|
||||
// title: bv.webContents.getTitle(),
|
||||
// url: bv.webContents.getURL(),
|
||||
// status: status,
|
||||
// });
|
||||
// }
|
104
src/main/handle/mainHandle.ts
Normal file
@ -0,0 +1,104 @@
|
||||
import {dialog, BrowserWindow, app} from "electron";
|
||||
import {IsUseSysTitle} from "../config/const";
|
||||
import {winURL, staticPaths} from "../config/staticPath";
|
||||
import DownloadFile from "../services/downloadFile";
|
||||
import {otherWindowConfig} from "../config/windowsConfig";
|
||||
import {IpcChannel} from "../ipc";
|
||||
import {showOnMyComputer, hideOnMyComputer, checkIsShowOnMyComputer} from "../services/regeditUtils"
|
||||
import {openDevTools} from "../hook/devToolHook";
|
||||
|
||||
export function useMainHandle() {
|
||||
return {
|
||||
[IpcChannel.IsUseSysTitle]: () => {
|
||||
return IsUseSysTitle;
|
||||
},
|
||||
|
||||
[IpcChannel.WindowMini]: (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
||||
},
|
||||
[IpcChannel.WindowMax]: (event) => {
|
||||
if (BrowserWindow.fromWebContents(event.sender)?.isMaximized()) {
|
||||
BrowserWindow.fromWebContents(event.sender)?.restore();
|
||||
return {status: false};
|
||||
} else {
|
||||
BrowserWindow.fromWebContents(event.sender)?.maximize();
|
||||
return {status: true};
|
||||
}
|
||||
},
|
||||
[IpcChannel.WindowClose]: (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.close();
|
||||
},
|
||||
[IpcChannel.AppClose]: () => {
|
||||
//关闭APP前先停止内置服务器 By fm453
|
||||
// Server.StopServer();
|
||||
//
|
||||
app.quit();
|
||||
},
|
||||
[IpcChannel.GetStaticPath]: () => {
|
||||
return staticPaths;
|
||||
},
|
||||
[IpcChannel.OpenMessagebox]: async (event, arg) => {
|
||||
return dialog.showMessageBox(
|
||||
BrowserWindow.fromWebContents(event.sender),
|
||||
{
|
||||
type: arg.type || "info",
|
||||
title: arg.title || "",
|
||||
buttons: arg.buttons || [],
|
||||
message: arg.message || "",
|
||||
noLink: arg.noLink || true,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
[IpcChannel.OpenErrorbox]: (_event, arg) => {
|
||||
dialog.showErrorBox(arg.title, arg.message)
|
||||
},
|
||||
|
||||
[IpcChannel.StartDownload]: (event, downloadUrl) => {
|
||||
new DownloadFile(
|
||||
BrowserWindow.fromWebContents(event.sender),
|
||||
downloadUrl
|
||||
).start();
|
||||
},
|
||||
[IpcChannel.OpenWin]: (_event, arg) => {
|
||||
const ChildWin = new BrowserWindow({
|
||||
titleBarStyle: IsUseSysTitle ? "default" : "hidden",
|
||||
...Object.assign(otherWindowConfig, {}),
|
||||
});
|
||||
// 开发模式下自动开启devtools
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
openDevTools(ChildWin)
|
||||
}
|
||||
ChildWin.loadURL(winURL + `#${arg.url}`);
|
||||
ChildWin.once("ready-to-show", () => {
|
||||
ChildWin.show();
|
||||
if (arg.IsPay) {
|
||||
// 检查支付时候自动关闭小窗口
|
||||
const testUrl = setInterval(() => {
|
||||
const Url = ChildWin.webContents.getURL();
|
||||
if (Url.includes(arg.PayUrl)) {
|
||||
ChildWin.close();
|
||||
}
|
||||
}, 1200);
|
||||
ChildWin.on("close", () => {
|
||||
clearInterval(testUrl);
|
||||
});
|
||||
}
|
||||
});
|
||||
// 渲染进程显示时触发
|
||||
ChildWin.once("show", () => {
|
||||
ChildWin.webContents.send("send-data-test", arg.sendData);
|
||||
});
|
||||
},
|
||||
[IpcChannel.CheckShowOnMyComputer]: async () => {
|
||||
return await checkIsShowOnMyComputer()
|
||||
},
|
||||
[IpcChannel.SetShowOnMyComputer]: async (event, bool) => {
|
||||
if (bool) {
|
||||
return await showOnMyComputer()
|
||||
} else {
|
||||
return await hideOnMyComputer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/main/handle/printHandle.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {WebContentsPrintOptions} from "electron";
|
||||
import {IpcChannel, IpcMainHandle} from "../ipc";
|
||||
|
||||
import {openPrintDemoWindow} from "../hook/printHook";
|
||||
|
||||
export function usePrintHandle(): Pick<IpcMainHandle, IpcChannel.GetPrinters | IpcChannel.PrintHandlePrint | IpcChannel.OpenPrintDemoWindow> {
|
||||
return {
|
||||
[IpcChannel.GetPrinters]: async (event) => {
|
||||
return await event.sender.getPrintersAsync();
|
||||
},
|
||||
[IpcChannel.PrintHandlePrint]: async (event, options: WebContentsPrintOptions) => {
|
||||
return new Promise((resolve) => {
|
||||
event.sender.print(
|
||||
options,
|
||||
(success: boolean, failureReason: string) => {
|
||||
resolve({success, failureReason});
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
[IpcChannel.OpenPrintDemoWindow]: () => {
|
||||
openPrintDemoWindow();
|
||||
}
|
||||
}
|
||||
}
|
45
src/main/handle/serverHandle.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {dialog} from "electron";
|
||||
import Server from "../server";
|
||||
import WsServer from "../server/wsServer";
|
||||
import {IpcMainHandle, IpcChannel} from "../ipc";
|
||||
|
||||
export function useServerHandle(): Pick<IpcMainHandle, IpcChannel.StartServer | IpcChannel.StopServer | IpcChannel.StartWsServer | IpcChannel.StopWsServer> {
|
||||
return {
|
||||
[IpcChannel.StartServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await Server.StartServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
[IpcChannel.StopServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await Server.StopServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
[IpcChannel.StartWsServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await WsServer.StartServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
[IpcChannel.StopWsServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await WsServer.StopServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
40
src/main/handle/updateHandle.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {BrowserWindow, app} from "electron";
|
||||
import {updater} from "../services/HotUpdater";
|
||||
import {updater as updaterTest} from "../services/HotUpdaterTest";
|
||||
import Update from "../services/checkUpdate";
|
||||
import {UpdateStatus} from "electron_updater_node_core";
|
||||
import {IpcMainHandle, IpcChannel} from "../ipc";
|
||||
|
||||
const ALL_UPDATER = new Update();
|
||||
|
||||
export function useUpdateHandle(): Pick<IpcMainHandle, IpcChannel.CheckUpdate | IpcChannel.ConfirmUpdate | IpcChannel.HotUpdate | IpcChannel.HotUpdateTest> {
|
||||
return {
|
||||
[IpcChannel.CheckUpdate]: (event) => {
|
||||
ALL_UPDATER.checkUpdate(BrowserWindow.fromWebContents(event.sender));
|
||||
},
|
||||
[IpcChannel.ConfirmUpdate]: () => {
|
||||
ALL_UPDATER.quitAndInstall();
|
||||
},
|
||||
[IpcChannel.HotUpdate]: (event) => {
|
||||
updater(BrowserWindow.fromWebContents(event.sender))
|
||||
},
|
||||
[IpcChannel.HotUpdateTest]: async (event, arg) => {
|
||||
console.log("hot-update-test");
|
||||
try {
|
||||
let updateInfo = await updaterTest(
|
||||
BrowserWindow.fromWebContents(event.sender)
|
||||
);
|
||||
if (updateInfo === UpdateStatus.Success) {
|
||||
app.quit();
|
||||
} else if (updateInfo === UpdateStatus.HaveNothingUpdate) {
|
||||
console.log("不需要更新");
|
||||
} else if (updateInfo === UpdateStatus.Failed) {
|
||||
console.error("更新出错");
|
||||
}
|
||||
} catch (error) {
|
||||
// 更新出错
|
||||
console.error("更新出错");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
src/main/hook/browserHook.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import {BrowserView, BrowserWindow} from "electron";
|
||||
import {IpcChannel} from "../ipc";
|
||||
import {IsUseSysTitle, BrowserDemoUrl} from "../config/const";
|
||||
import {otherWindowConfig} from "../config/windowsConfig";
|
||||
import {browserDemoURL} from "../config/staticPath";
|
||||
import {openDevTools} from "./devToolHook";
|
||||
|
||||
let dragTabOffsetX: number;
|
||||
let lastDragView: BrowserView;
|
||||
let emptyWin: BrowserWindow;
|
||||
let viewFromWin: BrowserWindow;
|
||||
let useNewWindow: BrowserWindow;
|
||||
const winList: BrowserWindow[] = [];
|
||||
const viewList: BrowserView[] = [];
|
||||
let startScreenY: number;
|
||||
|
||||
export function openBrowserDemoWindow() {
|
||||
let win = new BrowserWindow({
|
||||
titleBarStyle: IsUseSysTitle ? "default" : "hidden",
|
||||
...Object.assign(otherWindowConfig, {}),
|
||||
});
|
||||
// // 开发模式下自动开启devtools
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
openDevTools(win)
|
||||
}
|
||||
win.loadURL(browserDemoURL);
|
||||
win.on("ready-to-show", () => {
|
||||
win.show();
|
||||
});
|
||||
win.on("closed", () => {
|
||||
const findIndex = winList.findIndex((v) => win === v);
|
||||
if (findIndex !== -1) {
|
||||
winList.splice(findIndex, 1);
|
||||
}
|
||||
});
|
||||
winList.push(win);
|
||||
return win;
|
||||
}
|
||||
|
||||
export function createDefaultBrowserView(
|
||||
win: BrowserWindow,
|
||||
defaultUrl = BrowserDemoUrl
|
||||
) {
|
||||
const [winWidth, winHeight] = win.getSize();
|
||||
const bv = new BrowserView();
|
||||
win.addBrowserView(bv);
|
||||
// title-bar 30px tabbar 40px searchbar 40px
|
||||
bv.setBounds({x: 0, y: 110, width: winWidth, height: winHeight - 110});
|
||||
bv.setAutoResize({
|
||||
width: true,
|
||||
height: true,
|
||||
});
|
||||
bv.webContents.on('did-finish-load', () => {
|
||||
console.log(bv.webContents.getURL())
|
||||
})
|
||||
bv.webContents.loadURL(defaultUrl);
|
||||
bv.webContents.on("page-title-updated", (event, title) => {
|
||||
const parentBw = BrowserWindow.fromBrowserView(bv);
|
||||
if (parentBw) {
|
||||
freshTabData(parentBw, bv, 1)
|
||||
}
|
||||
});
|
||||
bv.webContents.on("destroyed", () => {
|
||||
const findIndex = viewList.findIndex((v) => v === bv);
|
||||
if (findIndex !== -1) {
|
||||
viewList.splice(findIndex, 1);
|
||||
}
|
||||
});
|
||||
bv.webContents.setWindowOpenHandler((details) => {
|
||||
const parentBw = BrowserWindow.fromBrowserView(bv);
|
||||
createDefaultBrowserView(parentBw, details.url);
|
||||
return {action: "deny"};
|
||||
});
|
||||
freshTabData(win, bv, 1)
|
||||
viewList.push(bv);
|
||||
return bv;
|
||||
}
|
||||
|
||||
export function addBrowserView(win: BrowserWindow, view: BrowserView) {
|
||||
if (BrowserWindow.fromBrowserView(view) !== win) {
|
||||
win.addBrowserView(view);
|
||||
win.show();
|
||||
win.setAlwaysOnTop(true);
|
||||
}
|
||||
freshTabData(win, view, 1)
|
||||
}
|
||||
|
||||
export function removeBrowserView(win: BrowserWindow, view: BrowserView) {
|
||||
if (BrowserWindow.fromBrowserView(view) === win) {
|
||||
win.removeBrowserView(view);
|
||||
}
|
||||
freshTabData(win, view, -1)
|
||||
}
|
||||
|
||||
export function freshTabData(win: BrowserWindow, bv: BrowserView, status: -1 | 1) {
|
||||
win.webContents.send(IpcChannel.BrowserViewTabDataUpdate, {
|
||||
bvWebContentsId: bv.webContents.id,
|
||||
title: bv.webContents.getTitle(),
|
||||
url: bv.webContents.getURL(),
|
||||
status: status,
|
||||
});
|
||||
}
|
||||
|
||||
export const useHookBrowser = {
|
||||
openBrowserDemoWindow,
|
||||
createDefaultBrowserView,
|
||||
addBrowserView,
|
||||
removeBrowserView,
|
||||
freshTabData,
|
||||
}
|
17
src/main/hook/devToolHook.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {BrowserWindow} from "electron";
|
||||
|
||||
export function openDevTools(win: BrowserWindow) {
|
||||
let devtools = new BrowserWindow();
|
||||
devtools.setMenu(null)
|
||||
devtools.webContents.on('did-finish-load', () => devtools.setTitle(win.webContents.getTitle()))
|
||||
win.webContents.setDevToolsWebContents(devtools.webContents);
|
||||
win.webContents.openDevTools({
|
||||
mode: "detach",
|
||||
});
|
||||
win.on("closed", () => {
|
||||
devtools?.close();
|
||||
});
|
||||
devtools.on("closed", () => {
|
||||
devtools = null;
|
||||
});
|
||||
}
|
17
src/main/hook/disableButtonHook.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {globalShortcut} from 'electron'
|
||||
import {DisableF12} from "../config/const"
|
||||
|
||||
const disableF12 = () => {
|
||||
if (process.env.NODE_ENV === 'production' || DisableF12) {
|
||||
// 在生产环境下,或者设置禁用了F12键的情况下,禁止响应F12键
|
||||
globalShortcut.register('f12', () => {
|
||||
console.log('OS用户试图启动控制台')
|
||||
})
|
||||
globalShortcut.register('CmdOrCtrl+I', () => {
|
||||
console.log('MAC用户试图启动控制台')
|
||||
})
|
||||
}
|
||||
}
|
||||
export default {
|
||||
disableF12
|
||||
}
|
147
src/main/hook/exceptionHook.ts
Normal file
@ -0,0 +1,147 @@
|
||||
import {WebContents, app, dialog} from "electron";
|
||||
import type {
|
||||
Details,
|
||||
RenderProcessGoneDetails,
|
||||
Event,
|
||||
BrowserWindow,
|
||||
} from "electron";
|
||||
|
||||
export interface UseProcessExceptionReturn {
|
||||
/**
|
||||
* Emitted when the renderer process unexpectedly disappears. This is normally because it was crashed or killed.
|
||||
* If a listener is not passed in, it will default to following the crash prompt
|
||||
*
|
||||
* @see https://www.electronjs.org/docs/latest/api/app#event-render-process-gone
|
||||
*/
|
||||
renderProcessGone: (
|
||||
listener?: (
|
||||
event: Event,
|
||||
webContents: WebContents,
|
||||
details: RenderProcessGoneDetails
|
||||
) => void
|
||||
) => void;
|
||||
/**
|
||||
* Emitted when the child process unexpectedly disappears. This is normally because it was crashed or killed. It does not include renderer processes.
|
||||
* If a listener is not passed in, it will default to following the crash prompt
|
||||
*
|
||||
* @see https://www.electronjs.org/docs/latest/api/app#event-child-process-gone
|
||||
*/
|
||||
childProcessGone: (
|
||||
window: BrowserWindow,
|
||||
listener?: (event: Event, details: Details) => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const useProcessException = (): UseProcessExceptionReturn => {
|
||||
const renderProcessGone = (
|
||||
listener?: (
|
||||
event: Event,
|
||||
webContents: WebContents,
|
||||
details: RenderProcessGoneDetails
|
||||
) => void
|
||||
) => {
|
||||
app.on("render-process-gone", (event, webContents, details) => {
|
||||
if (listener) {
|
||||
listener(event, webContents, details);
|
||||
return;
|
||||
}
|
||||
const message = {
|
||||
title: "",
|
||||
buttons: [],
|
||||
message: "",
|
||||
};
|
||||
switch (details.reason) {
|
||||
case "crashed":
|
||||
message.title = "警告";
|
||||
message.buttons = ["确定", "退出"];
|
||||
message.message = "图形化进程崩溃,是否进行软重启操作?";
|
||||
break;
|
||||
case "killed":
|
||||
message.title = "警告";
|
||||
message.buttons = ["确定", "退出"];
|
||||
message.message =
|
||||
"由于未知原因导致图形化进程被终止,是否进行软重启操作?";
|
||||
break;
|
||||
case "oom":
|
||||
message.title = "警告";
|
||||
message.buttons = ["确定", "退出"];
|
||||
message.message = "内存不足,是否软重启释放内存?";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dialog
|
||||
.showMessageBox({
|
||||
type: "warning",
|
||||
title: message.title,
|
||||
buttons: message.buttons,
|
||||
message: message.message,
|
||||
noLink: true,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.response === 0) webContents.reload();
|
||||
else webContents.close();
|
||||
});
|
||||
});
|
||||
};
|
||||
const childProcessGone = (
|
||||
window: BrowserWindow,
|
||||
listener?: (event: Event, details: Details) => void
|
||||
) => {
|
||||
app.on("child-process-gone", (event, details) => {
|
||||
if (listener) {
|
||||
listener(event, details);
|
||||
return;
|
||||
}
|
||||
const message = {
|
||||
title: "",
|
||||
buttons: [],
|
||||
message: "",
|
||||
};
|
||||
switch (details.type) {
|
||||
case "GPU":
|
||||
switch (details.reason) {
|
||||
case "crashed":
|
||||
message.title = "警告";
|
||||
message.buttons = ["确定", "退出"];
|
||||
message.message = "硬件加速进程已崩溃,是否关闭硬件加速并重启?";
|
||||
break;
|
||||
case "killed":
|
||||
message.title = "警告";
|
||||
message.buttons = ["确定", "退出"];
|
||||
message.message =
|
||||
"硬件加速进程被意外终止,是否关闭硬件加速并重启?";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
dialog
|
||||
.showMessageBox(window, {
|
||||
type: "warning",
|
||||
title: message.title,
|
||||
buttons: message.buttons,
|
||||
message: message.message,
|
||||
noLink: true,
|
||||
})
|
||||
.then((res) => {
|
||||
// 当显卡出现崩溃现象时使用该设置禁用显卡加速模式。
|
||||
if (res.response === 0) {
|
||||
if (details.type === "GPU") app.disableHardwareAcceleration();
|
||||
window.reload();
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return {
|
||||
renderProcessGone,
|
||||
childProcessGone,
|
||||
};
|
||||
};
|
30
src/main/hook/hotkeysHook.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {app, globalShortcut} from "electron";
|
||||
|
||||
const hotkeysHook = function (mainWindow) {
|
||||
mainWindow.on('focus', () => {
|
||||
// mac下快捷键失效的问题
|
||||
if (process.platform === 'darwin') {
|
||||
let contents = mainWindow.webContents
|
||||
globalShortcut.register('CmdOrCtrl+C', () => {
|
||||
console.log('注册复制快捷键成功')
|
||||
contents.copy()
|
||||
})
|
||||
globalShortcut.register('CommandOrControl+V', () => {
|
||||
console.log('注册粘贴快捷键成功')
|
||||
contents.paste()
|
||||
})
|
||||
globalShortcut.register('CommandOrControl+X', () => {
|
||||
console.log('注册剪切快捷键成功')
|
||||
contents.cut()
|
||||
})
|
||||
globalShortcut.register('CommandOrControl+A', () => {
|
||||
console.log('注册全选快捷键成功')
|
||||
contents.selectAll()
|
||||
})
|
||||
}
|
||||
})
|
||||
mainWindow.on('blur', () => {
|
||||
globalShortcut.unregisterAll() // 注销键盘事件
|
||||
})
|
||||
}
|
||||
export default hotkeysHook;
|
92
src/main/hook/menuHook.ts
Normal file
@ -0,0 +1,92 @@
|
||||
// 这里是定义菜单的地方,详情请查看 https://electronjs.org/zh/docs/api/menu
|
||||
import {dialog, BrowserWindow} from "electron";
|
||||
import type {MenuItemConstructorOptions, MenuItem} from "electron"
|
||||
import packageInfo from '../../../package.json';
|
||||
import * as diyConst from "../config/const";
|
||||
|
||||
function platform() {
|
||||
if (process.platform === "darwin") return "Mac OS";
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
const type = platform();
|
||||
const release = process.getSystemVersion();
|
||||
const arch = process.arch;
|
||||
|
||||
function info() {
|
||||
dialog.showMessageBox({
|
||||
title: '关于',
|
||||
type: 'info',
|
||||
message: `${packageInfo.title}`,
|
||||
detail: `版本信息:${packageInfo.version}\n引擎版本:${process.versions.v8}\n当前系统:${type} ${arch} ${release}`,
|
||||
noLink: true,
|
||||
buttons: ["官网", '确定']
|
||||
}).then(r => {
|
||||
console.log(r);
|
||||
if (r.response === 0) {
|
||||
const win = new BrowserWindow({width: 800, height: 600})
|
||||
// Load a remote URL
|
||||
win.loadURL(diyConst.GwLink);
|
||||
// Or load a local HTML file
|
||||
// win.loadFile('index.html')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
|
||||
{
|
||||
label: "设置",
|
||||
submenu: [
|
||||
{
|
||||
label: "快速重启",
|
||||
accelerator: "F5",
|
||||
role: "reload",
|
||||
},
|
||||
{
|
||||
label: "退出",
|
||||
accelerator: "CmdOrCtrl+F4",
|
||||
role: "close",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [{
|
||||
label: '撤销',
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
label: '重做',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
label: '剪切',
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
label: '复制',
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
label: '粘贴',
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
role: 'paste'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "帮助",
|
||||
submenu: [
|
||||
{
|
||||
label: "关于",
|
||||
click: info
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default menu
|
29
src/main/hook/printHook.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {BrowserWindow} from "electron";
|
||||
import {IsUseSysTitle} from "../config/const";
|
||||
import {otherWindowConfig} from "../config/windowsConfig";
|
||||
import {printURL} from "../config/staticPath";
|
||||
import {openDevTools} from "../hook/devToolHook";
|
||||
|
||||
let win: BrowserWindow;
|
||||
|
||||
export function openPrintDemoWindow() {
|
||||
if (win) {
|
||||
win.show();
|
||||
return;
|
||||
}
|
||||
win = new BrowserWindow({
|
||||
titleBarStyle: IsUseSysTitle ? "default" : "hidden",
|
||||
...Object.assign(otherWindowConfig, {}),
|
||||
});
|
||||
// 开发模式下自动开启devtools
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
openDevTools(win)
|
||||
}
|
||||
win.loadURL(printURL);
|
||||
win.on("ready-to-show", () => {
|
||||
win.show();
|
||||
});
|
||||
win.on("closed", () => {
|
||||
win = null;
|
||||
});
|
||||
}
|
128
src/main/hook/trayHook.ts
Normal file
@ -0,0 +1,128 @@
|
||||
//定义系统托盘
|
||||
import {dialog, BrowserWindow, Menu} from "electron";
|
||||
import type {MenuItemConstructorOptions, MenuItem} from "electron"
|
||||
import packageInfo from '../../../package.json';
|
||||
import * as diyConst from "../config/const";
|
||||
import {trayURL} from "../config/staticPath";
|
||||
|
||||
function platform() {
|
||||
if (process.platform === "darwin") return "Mac OS";
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
const type = platform();
|
||||
const release = process.getSystemVersion();
|
||||
const arch = process.arch;
|
||||
|
||||
function info() {
|
||||
dialog.showMessageBox({
|
||||
title: '关于',
|
||||
type: 'info',
|
||||
message: `${packageInfo.title}`,
|
||||
detail: `版本信息:${packageInfo.version}\n引擎版本:${process.versions.v8}\n当前系统:${type} ${arch} ${release}`,
|
||||
noLink: true,
|
||||
buttons: ["官网", '确定']
|
||||
}).then(r => {
|
||||
console.log(r);
|
||||
if (r.response === 0) {
|
||||
const win = new BrowserWindow({width: 800, height: 600})
|
||||
// Load a remote URL
|
||||
win.loadURL(diyConst.GwLink);
|
||||
// Or load a local HTML file
|
||||
// win.loadFile('index.html')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
export function createTrayWindow() {
|
||||
const win = new BrowserWindow({
|
||||
width: 300,
|
||||
height: 160,
|
||||
show: false,
|
||||
opacity: 0,
|
||||
resizable: false,
|
||||
movable: false,
|
||||
frame: false,
|
||||
hiddenInMissionControl: true,
|
||||
skipTaskbar: true,
|
||||
visualEffectState: 'active',
|
||||
vibrancy: 'menu'
|
||||
})
|
||||
win.loadURL(trayURL)
|
||||
|
||||
win.on('blur', async () => {
|
||||
let opacity = 1
|
||||
while (opacity > 0) {
|
||||
await sleep(10)
|
||||
opacity -= 0.1
|
||||
win.setOpacity(opacity)
|
||||
}
|
||||
win.hide()
|
||||
})
|
||||
|
||||
win.on('show', async () => {
|
||||
let opacity = 0
|
||||
while (opacity < 1) {
|
||||
await sleep(10)
|
||||
opacity += 0.2
|
||||
win.setOpacity(opacity)
|
||||
}
|
||||
win.focus()
|
||||
})
|
||||
return win
|
||||
}
|
||||
|
||||
const menu: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
|
||||
{
|
||||
label: "快速重启",
|
||||
accelerator: "F5",
|
||||
role: "reload",
|
||||
},
|
||||
|
||||
{
|
||||
label: '编辑',
|
||||
submenu: [{
|
||||
label: '撤销',
|
||||
accelerator: 'CmdOrCtrl+Z',
|
||||
role: 'undo'
|
||||
},
|
||||
{
|
||||
label: '重做',
|
||||
accelerator: 'Shift+CmdOrCtrl+Z',
|
||||
role: 'redo'
|
||||
},
|
||||
{
|
||||
label: '剪切',
|
||||
accelerator: 'CmdOrCtrl+X',
|
||||
role: 'cut'
|
||||
},
|
||||
{
|
||||
label: '复制',
|
||||
accelerator: 'CmdOrCtrl+C',
|
||||
role: 'copy'
|
||||
},
|
||||
{
|
||||
label: '粘贴',
|
||||
accelerator: 'CmdOrCtrl+V',
|
||||
role: 'paste'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "关于我们",
|
||||
click: info
|
||||
},
|
||||
{
|
||||
label: "退出",
|
||||
accelerator: "CmdOrCtrl+F4",
|
||||
role: "close",
|
||||
}
|
||||
];
|
||||
|
||||
export default Menu.buildFromTemplate(menu)
|
78
src/main/index.ts
Normal file
@ -0,0 +1,78 @@
|
||||
'use strict'
|
||||
|
||||
import {app, session} from 'electron'
|
||||
import InitWindow from './services/windowManager'
|
||||
import disableButton from './hook/disableButtonHook'
|
||||
import {initTray} from './services/trayManager'
|
||||
import domains from "./config/domains";
|
||||
import server from "./server";
|
||||
|
||||
function onAppReady() {
|
||||
new InitWindow().initWindow()
|
||||
initTray()
|
||||
disableButton.disableF12()
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
const {VUEJS_DEVTOOLS} = require("electron-devtools-vendor");
|
||||
session.defaultSession.loadExtension(VUEJS_DEVTOOLS, {
|
||||
allowFileAccess: true,
|
||||
}).then(r => {
|
||||
console.log('已安装: vue-devtools');
|
||||
});
|
||||
|
||||
//添加启动内置服务器
|
||||
server.StartServer().then((r) => {
|
||||
console.log('内置服务器已启动');
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(onAppReady)
|
||||
|
||||
//禁止程序多开;需要单例锁的时候用
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
if (!gotTheLock) {
|
||||
console.log("检测到应用单例锁")
|
||||
app.quit()
|
||||
}
|
||||
|
||||
// 由于9.x版本问题,需要加入该配置关闭跨域问题
|
||||
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
// 所有平台均为所有窗口关闭就退出软件
|
||||
app.quit()
|
||||
})
|
||||
app.on('browser-window-created', () => {
|
||||
console.log('window-created')
|
||||
})
|
||||
/**
|
||||
* 安全检查
|
||||
* 创建WebView前先检查内容来源
|
||||
*/
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-attach-webview', (event, webPreferences, params) => {
|
||||
let isSafe = 0;
|
||||
domains.some((d) => {
|
||||
// 验证正在加载的 URL
|
||||
if (!params.src.startsWith(d)) {
|
||||
isSafe -= 1;
|
||||
}
|
||||
})
|
||||
if (isSafe < 0) {
|
||||
// 禁用 Node.js 集成
|
||||
// webPreferences.nodeIntegration = false;
|
||||
// event.preventDefault()
|
||||
// 如果未使用,则删除预加载脚本或验证其位置是否合法
|
||||
// delete webPreferences.preload
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (process.defaultApp) {
|
||||
if (process.argv.length >= 2) {
|
||||
app.removeAsDefaultProtocolClient('Hi-sass-frame')
|
||||
console.log('由于框架特殊性,开发环境下无法使用')
|
||||
}
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient('Hi-sass-frame')
|
||||
}
|
419
src/main/ipc.ts
Normal file
@ -0,0 +1,419 @@
|
||||
import type {
|
||||
IpcMainInvokeEvent,
|
||||
IpcRendererEvent,
|
||||
MessageBoxOptions,
|
||||
MessageBoxReturnValue,
|
||||
PrinterInfo,
|
||||
WebContents,
|
||||
WebContentsPrintOptions,
|
||||
} from "electron";
|
||||
import {ProgressInfo} from "electron-updater";
|
||||
// import _channels_ from "./config/channels";
|
||||
|
||||
/**
|
||||
* 定义主进程监听类型
|
||||
* send:消息发送事件
|
||||
* receive:消息接收事件
|
||||
*/
|
||||
type IpcMainEventListener<Send = void, Receive = void> = {
|
||||
ipcMainHandle: (
|
||||
event: IpcMainInvokeEvent,
|
||||
args: Send
|
||||
) => Receive | Promise<Receive>;
|
||||
ipcRendererInvoke: (args: Send) => Promise<Receive>;
|
||||
};
|
||||
/**
|
||||
* 定义渲染进程监听类型
|
||||
*/
|
||||
type IpcRendererEventListener<Send = void> = {
|
||||
ipcRendererOn: (event: IpcRendererEvent, args?: Send) => void;
|
||||
webContentSend: (args: Send) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* 枚举进程名称定义
|
||||
*/
|
||||
export const enum IpcChannel {
|
||||
/**
|
||||
* 是否使用无边框
|
||||
*/
|
||||
IsUseSysTitle = "IsUseSysTitle",
|
||||
|
||||
/**
|
||||
* 窗口最小化
|
||||
*/
|
||||
WindowMini = "windows-mini",
|
||||
|
||||
/**
|
||||
* 窗口最大化
|
||||
*/
|
||||
WindowMax = "window-max",
|
||||
|
||||
/**
|
||||
* 窗口关闭
|
||||
*/
|
||||
WindowClose = "window-close",
|
||||
|
||||
/**
|
||||
* 检查更新
|
||||
*/
|
||||
CheckUpdate = "check-update",
|
||||
|
||||
/**
|
||||
* 确认更新
|
||||
*/
|
||||
ConfirmUpdate = "confirm-update",
|
||||
|
||||
/**
|
||||
* app退出
|
||||
*/
|
||||
AppClose = "app-close",
|
||||
|
||||
/**
|
||||
* 获取静态资源路径
|
||||
*/
|
||||
GetStaticPath = "get-static-path",
|
||||
|
||||
/**
|
||||
* 打开系统弹窗信息
|
||||
*/
|
||||
OpenMessagebox = "open-messagebox",
|
||||
|
||||
/**
|
||||
* 打开系统错误弹窗信息
|
||||
*/
|
||||
OpenErrorbox = "open-errorbox",
|
||||
|
||||
/**
|
||||
* 开启http服务
|
||||
*/
|
||||
StartServer = "start-server",
|
||||
|
||||
/**
|
||||
* 停止http服务
|
||||
*/
|
||||
StopServer = "stop-server",
|
||||
|
||||
/**
|
||||
* 开启WS服务
|
||||
*/
|
||||
StartWsServer = "start-wsserver",
|
||||
|
||||
/**
|
||||
* 停止WS服务
|
||||
*/
|
||||
StopWsServer = "stop-wsserver",
|
||||
|
||||
/**
|
||||
* 增量更新
|
||||
*/
|
||||
HotUpdate = "hot-update",
|
||||
|
||||
/**
|
||||
* 增量更新2
|
||||
*/
|
||||
HotUpdateTest = "hot-update-test",
|
||||
|
||||
/**
|
||||
* 下载东西
|
||||
*/
|
||||
StartDownload = "start-download",
|
||||
|
||||
/**
|
||||
* 打开新的弹窗
|
||||
*/
|
||||
OpenWin = "open-win",
|
||||
|
||||
/**
|
||||
* 获取打印机信息
|
||||
*/
|
||||
GetPrinters = "getPrinters",
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*/
|
||||
PrintHandlePrint = "printHandlePrint",
|
||||
|
||||
/**
|
||||
* 打开测试打印页面
|
||||
*/
|
||||
OpenPrintDemoWindow = "openPrintDemoWindow",
|
||||
|
||||
/**
|
||||
* 下载进度回调
|
||||
*/
|
||||
DownloadProgress = "download-progress",
|
||||
|
||||
/**
|
||||
* 下载错误回调
|
||||
*/
|
||||
DownloadError = "download-error",
|
||||
|
||||
/**
|
||||
* 下载暂停回调
|
||||
*/
|
||||
DownloadPaused = "download-paused",
|
||||
|
||||
/**
|
||||
* 下载完成回调
|
||||
*/
|
||||
DownloadDone = "download-done",
|
||||
|
||||
UpdateMsg = "UpdateMsg",
|
||||
|
||||
/**
|
||||
* 热更新状态回调
|
||||
*/
|
||||
HotUpdateStatus = "hot-update-status",
|
||||
|
||||
/**
|
||||
* 数据测试回调
|
||||
*/
|
||||
SendDataTest = "send-data-test",
|
||||
|
||||
/**
|
||||
* 添加新的默认页面
|
||||
*/
|
||||
AddDefaultBrowserView = "add-default-browser-view",
|
||||
|
||||
/**
|
||||
* 选择tab
|
||||
*/
|
||||
SelectBrowserDemoTab = "select-browser-demo-tab",
|
||||
|
||||
/**
|
||||
* 关闭tab
|
||||
*/
|
||||
DestroyBrowserDemoTab = "destroy-browser-demo-tab",
|
||||
|
||||
/**
|
||||
* tab 跳转
|
||||
*/
|
||||
BrowserDemoTabJumpToUrl = "browser-demo-tab-jump-to-url",
|
||||
|
||||
/**
|
||||
* 打开浏览器demo
|
||||
*/
|
||||
OpenBrowserDemoWindow = "open-browser-demo",
|
||||
|
||||
/**
|
||||
* Browser tab 鼠标按下
|
||||
*/
|
||||
BrowserTabMousedown = "browser-tab-mousedown",
|
||||
|
||||
/**
|
||||
* Browser tab 鼠标拖动
|
||||
*/
|
||||
BrowserTabMousemove = "browser-tab-mousemove",
|
||||
|
||||
/**
|
||||
* Browser tab 鼠标松开
|
||||
*/
|
||||
BrowserTabMouseup = "browser-tab-mouseup",
|
||||
|
||||
/**
|
||||
* 获取最后一次拖动的 tab 信息
|
||||
*/
|
||||
GetLastBrowserDemoTabData = "get-last-browser-demo-tab-data",
|
||||
|
||||
/**
|
||||
* 更新 tab 信息
|
||||
*/
|
||||
BrowserViewTabDataUpdate = "browser-view-tab-data-updated",
|
||||
|
||||
/**
|
||||
* 更新 tab 坐标
|
||||
*/
|
||||
BrowserViewTabPositionXUpdate = "browser-view-tab-position-x-updated",
|
||||
|
||||
/**
|
||||
* 设置在我的电脑显示
|
||||
*/
|
||||
SetShowOnMyComputer = "set-show-on-my-computer",
|
||||
/**
|
||||
* 查询当前是否显示在我的电脑
|
||||
*/
|
||||
CheckShowOnMyComputer = "check-show-on-my-computer",
|
||||
/**
|
||||
* 自定义Mock模拟网络请求-By fm453
|
||||
*/
|
||||
Mocker = "get-mock-data",
|
||||
}
|
||||
|
||||
type IpcMainEvent = {
|
||||
[IpcChannel.AppClose]: IpcMainEventListener;
|
||||
[IpcChannel.CheckUpdate]: IpcMainEventListener;
|
||||
[IpcChannel.ConfirmUpdate]: IpcMainEventListener;
|
||||
[IpcChannel.GetStaticPath]: IpcMainEventListener<void, string>;
|
||||
[IpcChannel.HotUpdate]: IpcMainEventListener;
|
||||
[IpcChannel.HotUpdateTest]: IpcMainEventListener;
|
||||
[IpcChannel.IsUseSysTitle]: IpcMainEventListener<void, boolean>;
|
||||
[IpcChannel.OpenErrorbox]: IpcMainEventListener<
|
||||
{ title: string; message: string },
|
||||
void
|
||||
>;
|
||||
[IpcChannel.OpenMessagebox]: IpcMainEventListener<
|
||||
MessageBoxOptions,
|
||||
MessageBoxReturnValue
|
||||
>;
|
||||
[IpcChannel.OpenWin]: IpcMainEventListener<
|
||||
{
|
||||
/**
|
||||
* 新的窗口地址
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
url: string;
|
||||
|
||||
/**
|
||||
* 是否是支付页
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
IsPay?: boolean;
|
||||
|
||||
/**
|
||||
* 支付参数
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
PayUrl?: string;
|
||||
|
||||
/**
|
||||
* 发送的新页面数据
|
||||
*
|
||||
* @type {unknown}
|
||||
*/
|
||||
sendData?: unknown;
|
||||
},
|
||||
void
|
||||
>;
|
||||
[IpcChannel.StartDownload]: IpcMainEventListener<string, void>;
|
||||
[IpcChannel.StartServer]: IpcMainEventListener<void, string>;
|
||||
[IpcChannel.StopServer]: IpcMainEventListener<void, string>;
|
||||
[IpcChannel.StartWsServer]: IpcMainEventListener<void, string>;
|
||||
[IpcChannel.StopWsServer]: IpcMainEventListener<void, string>;
|
||||
[IpcChannel.WindowClose]: IpcMainEventListener;
|
||||
[IpcChannel.WindowMax]: IpcMainEventListener<void, { status: boolean }>;
|
||||
[IpcChannel.WindowMini]: IpcMainEventListener;
|
||||
[IpcChannel.GetPrinters]: IpcMainEventListener<void, PrinterInfo[]>;
|
||||
[IpcChannel.PrintHandlePrint]: IpcMainEventListener<
|
||||
WebContentsPrintOptions,
|
||||
{ success: boolean; failureReason: string }
|
||||
>;
|
||||
[IpcChannel.OpenPrintDemoWindow]: IpcMainEventListener;
|
||||
[IpcChannel.OpenBrowserDemoWindow]: IpcMainEventListener;
|
||||
[IpcChannel.BrowserTabMousedown]: IpcMainEventListener<
|
||||
{
|
||||
offsetX: number;
|
||||
},
|
||||
void
|
||||
>;
|
||||
[IpcChannel.BrowserTabMousemove]: IpcMainEventListener<
|
||||
{
|
||||
screenX: number;
|
||||
screenY: number;
|
||||
startX: number;
|
||||
startY: number;
|
||||
bvWebContentsId: number;
|
||||
},
|
||||
void
|
||||
>;
|
||||
[IpcChannel.BrowserTabMouseup]: IpcMainEventListener;
|
||||
[IpcChannel.AddDefaultBrowserView]: IpcMainEventListener<
|
||||
void,
|
||||
{ bvWebContentsId: number }
|
||||
>;
|
||||
[IpcChannel.SelectBrowserDemoTab]: IpcMainEventListener<number, boolean>;
|
||||
[IpcChannel.DestroyBrowserDemoTab]: IpcMainEventListener<number, void>;
|
||||
[IpcChannel.BrowserDemoTabJumpToUrl]: IpcMainEventListener<
|
||||
{
|
||||
url: string;
|
||||
bvWebContentsId: number;
|
||||
},
|
||||
void
|
||||
>;
|
||||
[IpcChannel.GetLastBrowserDemoTabData]: IpcMainEventListener<
|
||||
void,
|
||||
{
|
||||
positionX: number;
|
||||
bvWebContentsId: number;
|
||||
title: string;
|
||||
url: string;
|
||||
}
|
||||
>;
|
||||
[IpcChannel.CheckShowOnMyComputer]: IpcMainEventListener<void, boolean>;
|
||||
[IpcChannel.SetShowOnMyComputer]: IpcMainEventListener<boolean, boolean>;
|
||||
};
|
||||
|
||||
type IpcRenderderEvent = {
|
||||
[IpcChannel.DownloadProgress]: IpcRendererEventListener<number>;
|
||||
[IpcChannel.DownloadError]: IpcRendererEventListener<boolean>;
|
||||
[IpcChannel.DownloadPaused]: IpcRendererEventListener<boolean>;
|
||||
[IpcChannel.DownloadDone]: IpcRendererEventListener<{
|
||||
/**
|
||||
* 下载的文件路径
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
filePath: string;
|
||||
}>;
|
||||
[IpcChannel.UpdateMsg]: IpcRendererEventListener<{
|
||||
state: number;
|
||||
msg: string | ProgressInfo;
|
||||
}>;
|
||||
[IpcChannel.HotUpdateStatus]: IpcRendererEventListener<{
|
||||
status:
|
||||
| "init"
|
||||
| "downloading"
|
||||
| "moving"
|
||||
| "finished"
|
||||
| "failed"
|
||||
| "download";
|
||||
message: string;
|
||||
}>;
|
||||
|
||||
[IpcChannel.SendDataTest]: IpcRendererEventListener<unknown>;
|
||||
[IpcChannel.BrowserViewTabDataUpdate]: IpcRendererEventListener<{
|
||||
bvWebContentsId: number;
|
||||
title: string;
|
||||
url: string;
|
||||
status: 1 | -1; // 1 添加/更新 -1 删除
|
||||
}>;
|
||||
[IpcChannel.BrowserViewTabPositionXUpdate]: IpcRendererEventListener<{
|
||||
dragTabOffsetX: number;
|
||||
positionX: number;
|
||||
bvWebContentsId: number;
|
||||
}>;
|
||||
[IpcChannel.BrowserTabMouseup]: IpcRendererEventListener;
|
||||
};
|
||||
|
||||
export type IpcMainHandle = {
|
||||
[Key in keyof IpcMainEvent]: IpcMainEvent[Key]["ipcMainHandle"];
|
||||
};
|
||||
|
||||
export type IpcRendererInvoke = {
|
||||
[Key in keyof IpcMainEvent]: IpcMainEvent[Key]["ipcRendererInvoke"];
|
||||
};
|
||||
|
||||
export type IpcRendererOn = {
|
||||
[Key in keyof IpcRenderderEvent]: IpcRenderderEvent[Key]["ipcRendererOn"];
|
||||
};
|
||||
|
||||
export type WebContentSend = {
|
||||
[Key in keyof IpcRenderderEvent]: IpcRenderderEvent[Key]["webContentSend"];
|
||||
};
|
||||
|
||||
type VoidParametersWebContentSendKey = {
|
||||
[K in keyof WebContentSend]: Parameters<WebContentSend[K]>[0] extends void ? K : never
|
||||
}[keyof WebContentSend]
|
||||
|
||||
type NotVoidParametersWebContentSendKey = Exclude<keyof WebContentSend, VoidParametersWebContentSendKey>
|
||||
|
||||
|
||||
export function winContentSend<T extends VoidParametersWebContentSendKey>(win: WebContents, channel: T): void;
|
||||
export function winContentSend<T extends NotVoidParametersWebContentSendKey>(win: WebContents, channel: T, args: Parameters<WebContentSend[T]>[0]): void;
|
||||
|
||||
export function winContentSend<T extends VoidParametersWebContentSendKey | NotVoidParametersWebContentSendKey>(win: WebContents, channel: T, args?: Parameters<WebContentSend[T]>[0]): void {
|
||||
win.send(channel, args);
|
||||
}
|
67
src/main/server/index.ts
Normal file
@ -0,0 +1,67 @@
|
||||
/* eslint-disable prefer-promise-reject-errors */
|
||||
import appExp from "./server";
|
||||
import { BuiltInServerPort } from "../config/const";
|
||||
import { createServer, Server } from "http";
|
||||
const port = BuiltInServerPort;
|
||||
|
||||
class SingleServer {
|
||||
constructor(app: any) {
|
||||
app.set("port", port);
|
||||
this.server = createServer(app);
|
||||
this.server.keepAliveTimeout = 0;
|
||||
this.server.on("connection", (socket) => {
|
||||
// keep-alive 1s后自动关闭
|
||||
socket.setTimeout(1000);
|
||||
});
|
||||
}
|
||||
server: Server;
|
||||
startServer() {
|
||||
return new Promise((resolve: (value: string) => void, reject) => {
|
||||
try {
|
||||
this.server.listen(port);
|
||||
resolve("内置服务端已经启动");
|
||||
} catch (error) {
|
||||
switch (error.code) {
|
||||
case "ERR_SERVER_ALREADY_LISTEN":
|
||||
resolve("服务端已经启动");
|
||||
break;
|
||||
case "EACCES":
|
||||
reject("权限不足内置服务器启动失败,请使用管理员权限运行。");
|
||||
break;
|
||||
case "EADDRINUSE":
|
||||
reject("内置服务器端口已被占用,请检查。");
|
||||
break;
|
||||
default:
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
stopServer() {
|
||||
return new Promise((resolve: (value: string) => void, reject) => {
|
||||
this.server.close((err) => {
|
||||
if (err) {
|
||||
switch ((err as any).code) {
|
||||
case "ERR_SERVER_NOT_RUNNING":
|
||||
resolve("服务端未启动");
|
||||
break;
|
||||
default:
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
resolve("服务端已关闭");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const singleServer = new SingleServer(appExp);
|
||||
export default {
|
||||
StartServer() {
|
||||
return singleServer.startServer();
|
||||
},
|
||||
StopServer() {
|
||||
return singleServer.stopServer();
|
||||
},
|
||||
};
|
81
src/main/server/server.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import express from 'express'
|
||||
|
||||
const app = express()
|
||||
const router = express.Router();
|
||||
import config from "../config/const";
|
||||
|
||||
const BaseApi = process.env.REMOTE_API ?? config.RemoteApi;
|
||||
|
||||
/**
|
||||
* 解决跨域
|
||||
*/
|
||||
const cors = (req, res, next) => {
|
||||
//设置允许跨域的域名,*代表允许任意域名跨域
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
//允许的header类型
|
||||
res.header("Access-Control-Allow-Headers", "*");
|
||||
res.header("Access-Control-Allow-Credentials", true);
|
||||
//跨域允许的请求方式
|
||||
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
|
||||
res.header("Content-Type", "application/json;charset=utf-8")
|
||||
|
||||
if (req.method.toLowerCase() === 'options')
|
||||
res.sendStatus(200); //让options尝试请求快速结束
|
||||
else
|
||||
next();
|
||||
}
|
||||
|
||||
//添加空路由占位符,后期动态添加路由用
|
||||
// const arr = ["GET","POST","DELETE","PUT","HEAD","OPTIONS"];
|
||||
// arr.some((method)=>{
|
||||
// let m = lowerCase(method);
|
||||
// router[m]("/*", (req,res,next)=>{
|
||||
// next();
|
||||
// })
|
||||
// });
|
||||
|
||||
//示范
|
||||
app.get('/message', (req, res) => {
|
||||
res.send('这是来自node服务端的信息')
|
||||
})
|
||||
|
||||
app.post('/message', (req, res) => {
|
||||
if (req) {
|
||||
res.send(req + '--来自node')
|
||||
}
|
||||
})
|
||||
|
||||
// 引入mock
|
||||
import mocks from "../../renderer/mock/index";
|
||||
import {lowerCase} from "lodash";
|
||||
|
||||
for (let i in mocks) {
|
||||
let mocker = mocks[i];
|
||||
mocker.forEach(mock => {
|
||||
// console.log(BaseApi+mock.url);
|
||||
let api = BaseApi + mock.url;
|
||||
let callback = function (req, res) {
|
||||
// console.log(req.params);
|
||||
//{userId:'2'}
|
||||
if (Object.keys(req.params).length > 0) {
|
||||
// console.log(mock.body)
|
||||
let data = mock.body({params: req.params});
|
||||
// console.log(data)
|
||||
res.send(data);
|
||||
} else {
|
||||
let data = mock.body
|
||||
res.send(data);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 请求类型 ["GET","POST","DELETE","PUT","HEAD","OPTIONS",……];
|
||||
*/
|
||||
let methods = mock.method;
|
||||
methods.some((method) => {
|
||||
let m = lowerCase(method);
|
||||
router[m](api, callback)
|
||||
});
|
||||
});
|
||||
}
|
||||
app.use("/", cors, router);
|
||||
export default app
|
84
src/main/server/wsRouters.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import express from 'express';
|
||||
import expressWs from "express-ws";
|
||||
|
||||
const router = express.Router();
|
||||
const app = express();
|
||||
expressWs(app);//混入功能,使router支持ws方法
|
||||
const wsClients = [];
|
||||
|
||||
/**
|
||||
* 解决跨域
|
||||
*/
|
||||
const cors = (req, res, next) => {
|
||||
//设置允许跨域的域名,*代表允许任意域名跨域
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
//允许的header类型
|
||||
res.header("Access-Control-Allow-Headers", "*");
|
||||
res.header("Access-Control-Allow-Credentials", true);
|
||||
//跨域允许的请求方式
|
||||
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
|
||||
res.header("Content-Type", "application/json;charset=utf-8")
|
||||
|
||||
if (req.method.toLowerCase() === 'options')
|
||||
res.sendStatus(200); //让options尝试请求快速结束
|
||||
else
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收client的command请求
|
||||
* @param cmd
|
||||
*/
|
||||
function get(cmd) {
|
||||
console.log(cmd)
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送command到client端
|
||||
* @param msg
|
||||
* @param cb
|
||||
*/
|
||||
function send(msg: string, cb) {
|
||||
cb.send(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置ws的回调方法
|
||||
*/
|
||||
function callback(ws, req, next) {
|
||||
// 有客户端连接时, 打印一条日志
|
||||
console.log("client connect to server successful!", req, next);
|
||||
// 保存客户端标识
|
||||
wsClients.push(ws);
|
||||
ws.on('connect', () => {
|
||||
ws.send('hello, I am wss');
|
||||
});
|
||||
// 创建message监听
|
||||
ws.on('message', (msg) => {
|
||||
// 直接将消息打印出来
|
||||
console.log("receive client msg :", msg);
|
||||
//处理接收的消息
|
||||
get(msg);
|
||||
send("演示自动回复一个消息", ws);
|
||||
});
|
||||
//监听客户端断连
|
||||
ws.on("close", function (msg) {
|
||||
console.log("client is closed", msg);
|
||||
for (var index = 0; index < wsClients.length; index++) {
|
||||
if (wsClients[index] === this) {
|
||||
wsClients.splice(index, 1)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//合并app
|
||||
// import appExp from "./server";
|
||||
// const newApp = {...app,...appExp}
|
||||
/**
|
||||
* 构造路由
|
||||
*/
|
||||
router.ws('/*', callback);
|
||||
|
||||
app.use('/ws', cors, router);
|
||||
export default app;
|
72
src/main/server/wsServer.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 利用express-ws创建wss服务;注意这个只能在主进程(如 src/main/index.ts)中加载
|
||||
* 只是个演示,生产中其实不需要也不建议在本地搭建WSS服务
|
||||
*/
|
||||
import expressWs from "express-ws";
|
||||
/**
|
||||
* 引入http服务器
|
||||
*/
|
||||
import { createServer, Server } from "http";
|
||||
/**
|
||||
* 初始化WS服务,并设置最大传输文件大小
|
||||
*/
|
||||
const port = 8888;
|
||||
class WsServer {
|
||||
constructor(app: any) {
|
||||
app.set("port", port);
|
||||
this.server = createServer(app);
|
||||
expressWs(app, this.server, { wsOptions: { maxPayload: 5 * 1024 * 1024 * 1024, } })
|
||||
}
|
||||
|
||||
server: Server;
|
||||
startServer() {
|
||||
return new Promise((resolve: (value: string) => void, reject) => {
|
||||
try {
|
||||
this.server.listen(port);
|
||||
resolve("内置WS服务端已经启动");
|
||||
} catch (error) {
|
||||
switch (error.code) {
|
||||
case "ERR_SERVER_ALREADY_LISTEN":
|
||||
resolve("WS服务端已经启动");
|
||||
break;
|
||||
case "EACCES":
|
||||
reject("权限不足WS服务器启动失败,请使用管理员权限运行。");
|
||||
break;
|
||||
case "EADDRINUSE":
|
||||
reject("WS服务器端口已被占用,请检查。");
|
||||
break;
|
||||
default:
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
stopServer() {
|
||||
return new Promise((resolve: (value: string) => void, reject) => {
|
||||
this.server.close((err) => {
|
||||
if (err) {
|
||||
switch ((err as any).code) {
|
||||
case "ERR_SERVER_NOT_RUNNING":
|
||||
resolve("WS服务端未启动");
|
||||
break;
|
||||
default:
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
resolve("WS服务端已关闭");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
import app from "./wsRouters";
|
||||
const myServer = new WsServer(app);
|
||||
export default {
|
||||
StartServer() {
|
||||
return myServer.startServer();
|
||||
},
|
||||
StopServer() {
|
||||
return myServer.stopServer();
|
||||
},
|
||||
};
|
101
src/main/services/HotUpdater.ts
Normal file
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* power by biuuu
|
||||
*/
|
||||
|
||||
import {emptyDir, createWriteStream, readFile, copy, remove} from 'fs-extra'
|
||||
import {join, resolve} from 'path'
|
||||
import {promisify} from 'util'
|
||||
import {pipeline} from 'stream'
|
||||
import {app, BrowserWindow} from 'electron'
|
||||
import {gt} from 'semver'
|
||||
import {createHmac} from 'crypto'
|
||||
import extract from 'extract-zip'
|
||||
import {version} from '../../../package.json'
|
||||
import {hotPublishConfig} from '../config/hotPublish'
|
||||
import axios from 'axios'
|
||||
import {IpcChannel, winContentSend} from "../ipc";
|
||||
|
||||
const streamPipeline = promisify(pipeline)
|
||||
const appPath = app.getAppPath()
|
||||
const updatePath = resolve(appPath, '..', '..', 'update')
|
||||
const request = axios.create()
|
||||
|
||||
/**
|
||||
* @param data 文件流
|
||||
* @param type 类型,默认sha256
|
||||
* @param key 密钥,用于匹配计算结果
|
||||
* @returns {string} 计算结果
|
||||
* @author umbrella22
|
||||
* @date 2021-03-05
|
||||
*/
|
||||
function hash(data, type = 'sha256', key = 'Sky') {
|
||||
const hmac = createHmac(type, key)
|
||||
hmac.update(data)
|
||||
return hmac.digest('hex')
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param url 下载地址
|
||||
* @param filePath 文件存放地址
|
||||
* @returns {void}
|
||||
* @author umbrella22
|
||||
* @date 2021-03-05
|
||||
*/
|
||||
async function download(url: string, filePath: string) {
|
||||
const res = await request({url, responseType: "stream"})
|
||||
await streamPipeline(res.data, createWriteStream(filePath))
|
||||
}
|
||||
|
||||
const updateInfo: {
|
||||
status: "init" | "downloading" | "moving" | "finished" | "failed";
|
||||
message: string;
|
||||
} = {
|
||||
status: 'init',
|
||||
message: ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @param windows 指主窗口
|
||||
* @returns {void}
|
||||
* @author umbrella22
|
||||
* @date 2021-03-05
|
||||
*/
|
||||
export const updater = async (windows?: BrowserWindow) => {
|
||||
const statusCallback = (status: {
|
||||
status: "init" | "downloading" | "moving" | "finished" | "failed";
|
||||
message: string;
|
||||
}) => {
|
||||
if (windows) winContentSend(windows.webContents, IpcChannel.HotUpdateStatus, status)
|
||||
}
|
||||
try {
|
||||
const res = await request({url: `${hotPublishConfig.url}/${hotPublishConfig.configName}.json?time=${new Date().getTime()}`,})
|
||||
if (!gt(res.data.version, version)) return
|
||||
await emptyDir(updatePath)
|
||||
const filePath = join(updatePath, res.data.name)
|
||||
updateInfo.status = 'downloading'
|
||||
statusCallback(updateInfo);
|
||||
await download(`${hotPublishConfig.url}/${res.data.name}`, filePath);
|
||||
const buffer = await readFile(filePath)
|
||||
const sha256 = hash(buffer)
|
||||
if (sha256 !== res.data.hash) throw new Error('sha256 error')
|
||||
const appPathTemp = join(updatePath, 'temp')
|
||||
await extract(filePath, {dir: appPathTemp})
|
||||
updateInfo.status = 'moving'
|
||||
statusCallback(updateInfo);
|
||||
await remove(join(`${appPath}`, 'dist'));
|
||||
await remove(join(`${appPath}`, 'package.json'));
|
||||
await copy(appPathTemp, appPath)
|
||||
updateInfo.status = 'finished'
|
||||
statusCallback(updateInfo);
|
||||
resolve('success')
|
||||
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
updateInfo.status = 'failed'
|
||||
updateInfo.message = error.message ? error.message : error
|
||||
statusCallback(updateInfo)
|
||||
}
|
||||
}
|
||||
|
||||
export const getUpdateInfo = () => updateInfo
|
62
src/main/services/HotUpdaterTest.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {app, BrowserWindow} from "electron";
|
||||
import {UpdateInfo, UpdateJson, UpdateStatus, UpdateElectron} from "electron_updater_node_core"
|
||||
import {dirname, join} from "path";
|
||||
import {version} from '../../../package.json'
|
||||
import {Readable} from "stream";
|
||||
import axios from 'axios'
|
||||
|
||||
const request = axios.create()
|
||||
import updateConfig from "../../../updateConfig.json";
|
||||
import {IpcChannel, winContentSend} from "../ipc";
|
||||
|
||||
/**
|
||||
* 增量更新
|
||||
*
|
||||
* @export
|
||||
* @param {BrowserWindow} [windows]
|
||||
*/
|
||||
export async function updater(windows?: BrowserWindow) {
|
||||
const statusCallback = (status: UpdateInfo) => {
|
||||
if (windows) winContentSend(windows.webContents, IpcChannel.HotUpdateStatus, status)
|
||||
}
|
||||
const downloadFn = async (url: string): Promise<Readable> => {
|
||||
const response = await request({
|
||||
method: 'get',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
const dirDirectory = join(app.getAppPath(), '..', '..');
|
||||
const tempDirectory = join(dirDirectory, updateConfig.tempDirectory);
|
||||
try {
|
||||
const res = await request({url: `${updateConfig.url}/${updateConfig.updateJsonName}.json?time=${new Date().getTime()}`,})
|
||||
const updateJson: UpdateJson = res.data;
|
||||
const updateElectron = new UpdateElectron(statusCallback, updateConfig.updaterName || "updater", version, app.getPath('exe'), tempDirectory, updateConfig.updateJsonName, updateJson, `${updateConfig.url}/${updateConfig.target + updateJson.version}`, downloadFn)
|
||||
const needUpdateNumber = await updateElectron.checkForUpdates();
|
||||
// have nothing to update
|
||||
if (needUpdateNumber === 0) {
|
||||
console.log("have nothing to update");
|
||||
return UpdateStatus.HaveNothingUpdate;
|
||||
} else {
|
||||
const download = await updateElectron.downloadUpdate();
|
||||
if (download) {
|
||||
const r = await updateElectron.install(false, true);
|
||||
if (r) {
|
||||
return UpdateStatus.Success;
|
||||
} else {
|
||||
throw new Error("update Fail")
|
||||
}
|
||||
} else {
|
||||
throw new Error("download Fail")
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const updateInfo = new UpdateInfo();
|
||||
updateInfo.status = 'failed'
|
||||
updateInfo.message = error;
|
||||
statusCallback(updateInfo);
|
||||
return UpdateStatus.Failed
|
||||
}
|
||||
}
|
82
src/main/services/checkUpdate.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import {ProgressInfo, autoUpdater} from 'electron-updater'
|
||||
import {BrowserWindow} from 'electron'
|
||||
import {IpcChannel, winContentSend} from "../ipc";
|
||||
|
||||
|
||||
/**
|
||||
* -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成
|
||||
**/
|
||||
class Update {
|
||||
public mainWindow: BrowserWindow
|
||||
|
||||
constructor() {
|
||||
// 设置url
|
||||
autoUpdater.setFeedURL('http://127.0.0.1:25565/')
|
||||
|
||||
// 当更新发生错误的时候触发。
|
||||
autoUpdater.on('error', (err) => {
|
||||
console.log('更新出现错误', err.message)
|
||||
if (err.message.includes('sha512 checksum mismatch')) {
|
||||
this.Message(this.mainWindow, -1, 'sha512校验失败')
|
||||
} else {
|
||||
this.Message(this.mainWindow, -1, '错误信息请看主进程控制台')
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// 当开始检查更新的时候触发
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
console.log('开始检查更新')
|
||||
this.Message(this.mainWindow, 0)
|
||||
})
|
||||
|
||||
// 发现可更新数据时
|
||||
autoUpdater.on('update-available', () => {
|
||||
console.log('有更新')
|
||||
this.Message(this.mainWindow, 1)
|
||||
})
|
||||
|
||||
// 没有可更新数据时
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
console.log('没有更新')
|
||||
this.Message(this.mainWindow, 2)
|
||||
})
|
||||
|
||||
// 下载监听
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
this.Message(this.mainWindow, 3, progressObj)
|
||||
})
|
||||
|
||||
// 下载完成
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
console.log('下载完成')
|
||||
this.Message(this.mainWindow, 4)
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 负责向渲染进程发送信息
|
||||
Message(mainWindow: BrowserWindow, type: number, data: string | ProgressInfo = "") {
|
||||
const senddata = {
|
||||
state: type,
|
||||
msg: data
|
||||
}
|
||||
winContentSend(mainWindow.webContents, IpcChannel.UpdateMsg, senddata)
|
||||
}
|
||||
|
||||
// 执行自动更新检查
|
||||
checkUpdate(mainWindow: BrowserWindow) {
|
||||
this.mainWindow = mainWindow
|
||||
autoUpdater.checkForUpdates().catch(err => {
|
||||
console.log('网络连接问题', err)
|
||||
})
|
||||
}
|
||||
|
||||
// 退出并安装
|
||||
quitAndInstall() {
|
||||
autoUpdater.quitAndInstall()
|
||||
}
|
||||
}
|
||||
|
||||
export default Update
|
135
src/main/services/downloadFile.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import {app, BrowserWindow, dialog} from "electron";
|
||||
|
||||
import {join} from "path";
|
||||
import {arch, platform} from "os";
|
||||
import {stat, remove, appendFileSync} from "fs-extra";
|
||||
import type {IncomingMessage} from "http";
|
||||
import {request as httpRequest} from "http";
|
||||
import {request as httpsRequest} from "https";
|
||||
import packageInfo from "../../../package.json";
|
||||
import {IpcChannel, winContentSend} from "../ipc";
|
||||
|
||||
/**
|
||||
*
|
||||
* @description
|
||||
* @returns {void} 下载类
|
||||
* @param {mainWindow} 主窗口
|
||||
* @param {downloadUrl} 下载地址,当未传入时则会使用预先设置好的baseUrl拼接名称
|
||||
* @author Sky
|
||||
* @date 2020-08-12
|
||||
*/
|
||||
|
||||
class Main {
|
||||
public mainWindow: BrowserWindow = null;
|
||||
public downloadUrl: string = "";
|
||||
public fileName: string = "";
|
||||
public filePath: string = "";
|
||||
public version: string = packageInfo.version;
|
||||
public baseUrl: string = process.env.BASE_API;
|
||||
public Sysarch: string = arch().includes("64") ? "win64" : "win32";
|
||||
public HistoryFilePath = join(
|
||||
app.getPath("downloads"),
|
||||
platform().includes("win32")
|
||||
? `electron_${this.version}_${this.Sysarch}.exe`
|
||||
: `electron_${this.version}_mac.dmg`
|
||||
);
|
||||
|
||||
constructor(mainWindow: BrowserWindow, downloadUrl?: string) {
|
||||
this.mainWindow = mainWindow;
|
||||
if (downloadUrl) {
|
||||
this.downloadUrl = downloadUrl;
|
||||
} else {
|
||||
this.downloadUrl = platform().includes("win32")
|
||||
? this.baseUrl +
|
||||
`electron_${this.version}_${this.Sysarch}.exe?${new Date().getTime()}`
|
||||
: this.baseUrl +
|
||||
`electron_${this.version}_mac.dmg?${new Date().getTime()}`;
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
// 更新时检查有无同名文件,若有就删除,若无就开始下载
|
||||
stat(this.HistoryFilePath, async (err, stats) => {
|
||||
try {
|
||||
if (stats) {
|
||||
await remove(this.HistoryFilePath);
|
||||
}
|
||||
this.download(
|
||||
this.downloadUrl,
|
||||
(chunk: any, size: number, fullSize: number) => {
|
||||
// 保存文件
|
||||
appendFileSync(this.filePath, chunk, {encoding: "binary"});
|
||||
|
||||
//发送进度
|
||||
winContentSend(this.mainWindow.webContents, IpcChannel.DownloadProgress, Number(((size / fullSize) * 100).toFixed(2)));
|
||||
|
||||
//完成后反馈
|
||||
if (size === fullSize) {
|
||||
const data = {
|
||||
filePath: this.filePath,
|
||||
};
|
||||
winContentSend(this.mainWindow.webContents, IpcChannel.DownloadDone, data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
).catch((err) => {
|
||||
winContentSend(this.mainWindow.webContents, IpcChannel.DownloadError, true);
|
||||
console.error(err);
|
||||
dialog.showErrorBox("下载出错", err.message);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
download(
|
||||
url: string,
|
||||
onDown: (chunk: any, size: number, fullSize: number) => void
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let size: number = 0;
|
||||
const ing = (response: IncomingMessage) => {
|
||||
if (response.statusCode && response.statusCode === 301) {
|
||||
this.download(response.headers.location as string, onDown)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
return;
|
||||
}
|
||||
const fullSize = Number(response.headers["content-length"] || 0);
|
||||
this.fileName = response.headers["content-disposition"]
|
||||
.match(/filename=[\"|'](.*?)[\"|']/gi)[0]
|
||||
.match(/["|'](.*)["|']/)[1];
|
||||
this.filePath = join(app.getPath("downloads"), this.fileName);
|
||||
response.on("data", (chunk) => {
|
||||
size += chunk.length;
|
||||
onDown(chunk, size, fullSize);
|
||||
});
|
||||
response.on("end", () => {
|
||||
if (response.statusCode && response.statusCode >= 400) {
|
||||
reject(new Error(response.statusCode + ""));
|
||||
return;
|
||||
}
|
||||
resolve({
|
||||
msg: "downloaded",
|
||||
fullSize,
|
||||
});
|
||||
});
|
||||
};
|
||||
let request;
|
||||
const isHttp = url.startsWith("http://");
|
||||
if (isHttp) request = httpRequest(url, {}, ing);
|
||||
request = httpsRequest(url, {}, ing);
|
||||
request.on("destroyed", () => reject(new Error("destroy")));
|
||||
request.on("error", (err) => reject(err));
|
||||
request.end();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Main;
|
186
src/main/services/ipcMain-bak.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import {ipcMain, dialog, BrowserWindow, app, WebContents} from "electron";
|
||||
import {IsUseSysTitle} from "../config/const";
|
||||
import Server from "../server";
|
||||
import WsServer from "../server/wsServer";
|
||||
import {winURL, preloadURL, staticPaths} from "../config/staticPath";
|
||||
import {updater} from "./HotUpdater";
|
||||
import {updater as updaterTest} from "./HotUpdaterTest";
|
||||
import DownloadFile from "./downloadFile";
|
||||
import Update from "./checkUpdate";
|
||||
import {otherWindowConfig} from "../config/windowsConfig";
|
||||
import {usePrintHandle} from "src/main/handle/printHandle";
|
||||
import {useBrowserHandle} from "src/main/handle/browserHandle";
|
||||
import {useMainHandle} from "src/main/handle/mainHandle";
|
||||
import {UpdateStatus} from "electron_updater_node_core";
|
||||
import {IpcMainHandle, IpcChannel, winContentSend} from "../ipc";
|
||||
import {ProgressInfo} from "electron-updater";
|
||||
import {showOnMyComputer, hideOnMyComputer, checkIsShowOnMyComputer} from "./regeditUtils"
|
||||
import {openDevTools} from "../hook/devToolHook";
|
||||
|
||||
const ALL_UPDATER = new Update();
|
||||
|
||||
const ipcMainHandle: IpcMainHandle = {
|
||||
[IpcChannel.IsUseSysTitle]: () => {
|
||||
return IsUseSysTitle;
|
||||
},
|
||||
|
||||
[IpcChannel.WindowMini]: (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.minimize();
|
||||
},
|
||||
[IpcChannel.WindowMax]: (event) => {
|
||||
if (BrowserWindow.fromWebContents(event.sender)?.isMaximized()) {
|
||||
BrowserWindow.fromWebContents(event.sender)?.restore();
|
||||
return {status: false};
|
||||
} else {
|
||||
BrowserWindow.fromWebContents(event.sender)?.maximize();
|
||||
return {status: true};
|
||||
}
|
||||
},
|
||||
[IpcChannel.WindowClose]: (event) => {
|
||||
BrowserWindow.fromWebContents(event.sender)?.close();
|
||||
},
|
||||
|
||||
[IpcChannel.CheckUpdate]: (event) => {
|
||||
ALL_UPDATER.checkUpdate(BrowserWindow.fromWebContents(event.sender));
|
||||
},
|
||||
[IpcChannel.ConfirmUpdate]: () => {
|
||||
ALL_UPDATER.quitAndInstall();
|
||||
},
|
||||
[IpcChannel.AppClose]: () => {
|
||||
//关闭APP前先停止内置服务器 By fm453
|
||||
// Server.StopServer();
|
||||
//
|
||||
app.quit();
|
||||
},
|
||||
[IpcChannel.GetStaticPath]: () => {
|
||||
return staticPaths;
|
||||
},
|
||||
[IpcChannel.OpenMessagebox]: async (event, arg) => {
|
||||
return dialog.showMessageBox(
|
||||
BrowserWindow.fromWebContents(event.sender),
|
||||
{
|
||||
type: arg.type || "info",
|
||||
title: arg.title || "",
|
||||
buttons: arg.buttons || [],
|
||||
message: arg.message || "",
|
||||
noLink: arg.noLink || true,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
[IpcChannel.OpenErrorbox]: (_event, arg) => {
|
||||
dialog.showErrorBox(arg.title, arg.message)
|
||||
},
|
||||
[IpcChannel.StartServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await Server.StartServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
[IpcChannel.StopServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await Server.StopServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
[IpcChannel.StartWsServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await WsServer.StartServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
[IpcChannel.StopWsServer]: async () => {
|
||||
try {
|
||||
const serverStatus = await WsServer.StopServer();
|
||||
return serverStatus;
|
||||
} catch (error) {
|
||||
dialog.showErrorBox("错误", error);
|
||||
return ""
|
||||
}
|
||||
},
|
||||
|
||||
[IpcChannel.HotUpdate]: (event) => {
|
||||
updater(BrowserWindow.fromWebContents(event.sender))
|
||||
},
|
||||
[IpcChannel.HotUpdateTest]: async (event, arg) => {
|
||||
console.log("hot-update-test");
|
||||
try {
|
||||
let updateInfo = await updaterTest(
|
||||
BrowserWindow.fromWebContents(event.sender)
|
||||
);
|
||||
if (updateInfo === UpdateStatus.Success) {
|
||||
app.quit();
|
||||
} else if (updateInfo === UpdateStatus.HaveNothingUpdate) {
|
||||
console.log("不需要更新");
|
||||
} else if (updateInfo === UpdateStatus.Failed) {
|
||||
console.error("更新出错");
|
||||
}
|
||||
} catch (error) {
|
||||
// 更新出错
|
||||
console.error("更新出错");
|
||||
}
|
||||
},
|
||||
[IpcChannel.StartDownload]: (event, downloadUrl) => {
|
||||
new DownloadFile(
|
||||
BrowserWindow.fromWebContents(event.sender),
|
||||
downloadUrl
|
||||
).start();
|
||||
},
|
||||
[IpcChannel.OpenWin]: (_event, arg) => {
|
||||
const ChildWin = new BrowserWindow({
|
||||
titleBarStyle: IsUseSysTitle ? "default" : "hidden",
|
||||
...Object.assign(otherWindowConfig, {}),
|
||||
});
|
||||
// 开发模式下自动开启devtools
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
openDevTools(ChildWin)
|
||||
}
|
||||
ChildWin.loadURL(winURL + `#${arg.url}`);
|
||||
ChildWin.once("ready-to-show", () => {
|
||||
ChildWin.show();
|
||||
if (arg.IsPay) {
|
||||
// 检查支付时候自动关闭小窗口
|
||||
const testUrl = setInterval(() => {
|
||||
const Url = ChildWin.webContents.getURL();
|
||||
if (Url.includes(arg.PayUrl)) {
|
||||
ChildWin.close();
|
||||
}
|
||||
}, 1200);
|
||||
ChildWin.on("close", () => {
|
||||
clearInterval(testUrl);
|
||||
});
|
||||
}
|
||||
});
|
||||
// 渲染进程显示时触发
|
||||
ChildWin.once("show", () => {
|
||||
ChildWin.webContents.send("send-data-test", arg.sendData);
|
||||
});
|
||||
},
|
||||
[IpcChannel.CheckShowOnMyComputer]: async () => {
|
||||
return await checkIsShowOnMyComputer()
|
||||
},
|
||||
[IpcChannel.SetShowOnMyComputer]: async (event, bool) => {
|
||||
if (bool) {
|
||||
return await showOnMyComputer()
|
||||
} else {
|
||||
return await hideOnMyComputer()
|
||||
}
|
||||
},
|
||||
...usePrintHandle(),
|
||||
...useBrowserHandle(),
|
||||
}
|
||||
|
||||
export function installIpcMain() {
|
||||
Object.entries(ipcMainHandle).forEach(([ipcChannelName, ipcListener]) => {
|
||||
ipcMain.handle(ipcChannelName, ipcListener)
|
||||
})
|
||||
}
|
21
src/main/services/ipcMain.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {ipcMain} from "electron";
|
||||
import {usePrintHandle} from "../handle/printHandle";
|
||||
import {useBrowserHandle} from "../handle/browserHandle";
|
||||
import {useMainHandle} from "../handle/mainHandle";
|
||||
import {useUpdateHandle} from "../handle/updateHandle";
|
||||
import {useServerHandle} from "../handle/serverHandle";
|
||||
import {IpcMainHandle} from "../ipc";
|
||||
|
||||
const ipcMainHandle: IpcMainHandle = {
|
||||
...useMainHandle(),
|
||||
...useBrowserHandle(),
|
||||
...usePrintHandle(),
|
||||
...useServerHandle(),
|
||||
...useUpdateHandle(),
|
||||
}
|
||||
|
||||
export function installIpcMain() {
|
||||
Object.entries(ipcMainHandle).forEach(([ipcChannelName, ipcListener]) => {
|
||||
ipcMain.handle(ipcChannelName, ipcListener)
|
||||
})
|
||||
}
|
165
src/main/services/regeditUtils.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import { app } from "electron";
|
||||
import regedit from "regedit";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import { appId } from "../../../build.json";
|
||||
|
||||
const reg = regedit.promisified;
|
||||
|
||||
const appName = app.getName();
|
||||
const exePath = app.getPath("exe");
|
||||
|
||||
const ELECTRON_BUILDER_NS_UUID = "50e065bc-3134-11e6-9bab-38c9862bdaf3";
|
||||
const GUID = `{${uuidv5(appId, ELECTRON_BUILDER_NS_UUID)}}`;
|
||||
const options = {
|
||||
name: appName, // 显示名
|
||||
infoTip: "双击运行" + appName, // 鼠标移入显示
|
||||
localizedString: appName, // 本地化显示名
|
||||
itemAuthors: "双击运行" + appName, // 显示名下描述
|
||||
iconPath: exePath, // exe / ico
|
||||
exe: exePath, // 双击打开
|
||||
};
|
||||
// 在设备和驱动器占位
|
||||
const nameSpacePath =
|
||||
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace";
|
||||
const appInNameSpacePath = `HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace\\${GUID}`;
|
||||
|
||||
// 配置设备和驱动器信息
|
||||
const clsidPath = `HKCU\\Software\\Classes\\CLSID`;
|
||||
const appInClsidPath = `HKCU\\Software\\Classes\\CLSID\\${GUID}`;
|
||||
const appInClsidShell = `HKCU\\Software\\Classes\\CLSID\\${GUID}\\Shell`;
|
||||
const appInClsidShellOpen = `HKCU\\Software\\Classes\\CLSID\\${GUID}\\Shell\\Open`;
|
||||
const appInClsidShellOpenCommand = `HKCU\\Software\\Classes\\CLSID\\${GUID}\\Shell\\Open\\Command`;
|
||||
|
||||
// 设置图标
|
||||
const appInClsidDefaultIconPath = `HKCU\\Software\\Classes\\CLSID\\${GUID}\\DefaultIcon`;
|
||||
|
||||
export async function showOnMyComputer() {
|
||||
try {
|
||||
if (!(await checkExists(appInNameSpacePath))) {
|
||||
await reg.createKey([appInNameSpacePath]);
|
||||
}
|
||||
// 修改默认值
|
||||
await reg.putValue({
|
||||
[appInNameSpacePath]: {
|
||||
default: {
|
||||
value: options.name,
|
||||
type: "REG_DEFAULT",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!(await checkExists(appInClsidPath))) {
|
||||
await reg.createKey([appInClsidPath]);
|
||||
}
|
||||
// 修改默认值
|
||||
await reg.putValue({
|
||||
[appInClsidPath]: {
|
||||
default: {
|
||||
value: options.name,
|
||||
type: "REG_DEFAULT",
|
||||
},
|
||||
InfoTip: {
|
||||
value: options.infoTip,
|
||||
type: "REG_SZ",
|
||||
},
|
||||
LocalizedString: {
|
||||
value: options.localizedString,
|
||||
type: "REG_SZ",
|
||||
},
|
||||
"System.ItemAuthors": {
|
||||
value: options.itemAuthors,
|
||||
type: "REG_SZ",
|
||||
},
|
||||
TileInfo: {
|
||||
value: "prop:System.ItemAuthors",
|
||||
type: "REG_SZ",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!(await checkExists(appInClsidDefaultIconPath))) {
|
||||
await reg.createKey([appInClsidDefaultIconPath]);
|
||||
}
|
||||
await reg.putValue({
|
||||
[appInClsidDefaultIconPath]: {
|
||||
Icon: {
|
||||
value: options.iconPath,
|
||||
type: "REG_DEFAULT",
|
||||
},
|
||||
},
|
||||
});
|
||||
// // InprocServer32 注册 32 位进程内服务器,并指定服务器可以在其中运行的单元的线程模型。
|
||||
// const appInClsidInprocServer32 = appInClsidPath + '\\InprocServer32'
|
||||
// if (!await checkExists(appInClsidInprocServer32)) {
|
||||
// await reg.createKey([appInClsidInprocServer32])
|
||||
// }
|
||||
// await reg.putValue({
|
||||
// [appInClsidInprocServer32]: {
|
||||
// default: {
|
||||
// value: '%SystemRoot%\system32\shdocvw.dll',
|
||||
// type: 'REG_DEFAULT'
|
||||
// },
|
||||
// ThreadingModel: {
|
||||
// value: 'Apartment',
|
||||
// type: 'REG_SZ'
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
if (!(await checkExists(appInClsidShell))) {
|
||||
await reg.createKey([appInClsidShell]);
|
||||
}
|
||||
if (!(await checkExists(appInClsidShellOpen))) {
|
||||
await reg.createKey([appInClsidShellOpen]);
|
||||
}
|
||||
if (!(await checkExists(appInClsidShellOpenCommand))) {
|
||||
await reg.createKey([appInClsidShellOpenCommand]);
|
||||
}
|
||||
await reg.putValue({
|
||||
[appInClsidShellOpenCommand]: {
|
||||
default: {
|
||||
value: options.exe,
|
||||
type: "REG_DEFAULT",
|
||||
},
|
||||
},
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function hideOnMyComputer() {
|
||||
try {
|
||||
await rmrf(appInNameSpacePath);
|
||||
await rmrf(appInClsidPath);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkIsShowOnMyComputer() {
|
||||
try {
|
||||
return await checkExists(appInNameSpacePath);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkExists(key: string) {
|
||||
return (await reg.list([key]))[key].exists;
|
||||
}
|
||||
|
||||
async function rmrf(key: string) {
|
||||
const data = (await reg.list([key]))[key];
|
||||
if (data.exists) {
|
||||
for (let i = 0; i < data.keys.length; i++) {
|
||||
await rmrf(key + "\\" + data.keys[i]);
|
||||
}
|
||||
await reg.deleteKey([key]);
|
||||
}
|
||||
}
|
38
src/main/services/trayDiyManager.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {BrowserWindow, Menu, Tray, app, shell} from "electron";
|
||||
import {trayURL, trayIconPath, trayTransparentIconPath} from "../config/staticPath";
|
||||
import pkg from "../../../package.json";
|
||||
import menu, {createTrayWindow, sleep} from "../hook/trayHook";
|
||||
|
||||
let tray: Tray
|
||||
|
||||
export function initTray() {
|
||||
// console.log('trayIconPath', trayIconPath)
|
||||
tray = new Tray(trayIconPath);
|
||||
|
||||
tray.setToolTip(app.name)
|
||||
tray.setTitle(pkg.title)
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const trayWin = createTrayWindow()
|
||||
tray.on('click', (e, bounds, position) => {
|
||||
trayWin.setPosition(bounds.x, bounds.y)
|
||||
trayWin.show()
|
||||
})
|
||||
} else {
|
||||
tray.setContextMenu(menu)
|
||||
}
|
||||
|
||||
tray.on('double-click', async () => {
|
||||
let times = 3
|
||||
while (times > 0) {
|
||||
shell.beep()
|
||||
tray.setImage(trayTransparentIconPath)
|
||||
await sleep(500)
|
||||
tray.setImage(trayIconPath)
|
||||
await sleep(500)
|
||||
times--
|
||||
}
|
||||
})
|
||||
|
||||
return tray
|
||||
}
|
30
src/main/services/trayManager.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {BrowserWindow, Menu, Tray, app, shell} from "electron";
|
||||
import {trayURL, trayIconPath, trayTransparentIconPath} from "../config/staticPath";
|
||||
import pkg from "../../../package.json";
|
||||
import menu, {sleep} from "../hook/trayHook";
|
||||
|
||||
let tray: Tray
|
||||
|
||||
export function initTray() {
|
||||
// console.log('trayIconPath', trayIconPath)
|
||||
tray = new Tray(trayIconPath);
|
||||
|
||||
tray.setToolTip(app.name)
|
||||
tray.setTitle(pkg.title)
|
||||
|
||||
tray.setContextMenu(menu)
|
||||
|
||||
tray.on('double-click', async () => {
|
||||
let times = 3
|
||||
while (times > 0) {
|
||||
shell.beep()
|
||||
tray.setImage(trayTransparentIconPath)
|
||||
await sleep(500)
|
||||
tray.setImage(trayIconPath)
|
||||
await sleep(500)
|
||||
times--
|
||||
}
|
||||
})
|
||||
|
||||
return tray
|
||||
}
|
100
src/main/services/windowManager.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import {installIpcMain} from "./ipcMain";
|
||||
import {IsUseSysTitle, openDevTools, UseStartupChart} from "../config/const";
|
||||
import menuconfig from "../hook/menuHook";
|
||||
import {app, BrowserWindow, Menu, dialog} from "electron";
|
||||
import {winURL, loadingURL} from "../config/staticPath";
|
||||
import {mainWindowConfig} from "../config/windowsConfig";
|
||||
import hotkeysHook from "../hook/hotkeysHook";
|
||||
import {useProcessException} from "../hook/exceptionHook";
|
||||
import {openDevTools as openDevToolsFunc} from "../hook/devToolHook";
|
||||
|
||||
class MainInit {
|
||||
public winURL: string = "";
|
||||
public shartURL: string = "";
|
||||
public loadWindow: BrowserWindow = null;
|
||||
public mainWindow: BrowserWindow = null;
|
||||
|
||||
constructor() {
|
||||
this.winURL = winURL;
|
||||
this.shartURL = loadingURL;
|
||||
// 开发环境或者设置了打开开发者模式时
|
||||
if (process.env.NODE_ENV === "development" || openDevTools) {
|
||||
menuconfig.push({
|
||||
label: "开发者设置",
|
||||
submenu: [
|
||||
{
|
||||
label: "切换到开发者模式",
|
||||
accelerator: "CmdOrCtrl+I",
|
||||
click: () => openDevToolsFunc(BrowserWindow.getFocusedWindow()),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
// 启用协议
|
||||
installIpcMain();
|
||||
}
|
||||
|
||||
// 主窗口函数
|
||||
createMainWindow() {
|
||||
this.mainWindow = new BrowserWindow({
|
||||
titleBarStyle: IsUseSysTitle ? "default" : "hidden",
|
||||
...Object.assign(mainWindowConfig, {}),
|
||||
});
|
||||
//注册全局快捷键
|
||||
hotkeysHook(this.mainWindow);
|
||||
// 赋予主菜单模板
|
||||
const menu = Menu.buildFromTemplate(menuconfig as any);
|
||||
// 加载模板
|
||||
Menu.setApplicationMenu(menu);
|
||||
// 加载主窗口
|
||||
this.mainWindow.loadURL(this.winURL);
|
||||
// ready-to-show之后显示界面
|
||||
this.mainWindow.once("ready-to-show", () => {
|
||||
this.mainWindow.show();
|
||||
// 开发模式下自动开启devtools
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
openDevToolsFunc(this.mainWindow);
|
||||
}
|
||||
if (UseStartupChart) this.loadWindow.destroy();
|
||||
});
|
||||
// 对进程进行监控处理(响应假死等状态)
|
||||
useProcessException().renderProcessGone;
|
||||
useProcessException().childProcessGone;
|
||||
|
||||
this.mainWindow.on("closed", () => {
|
||||
this.mainWindow = null;
|
||||
});
|
||||
}
|
||||
|
||||
// 加载窗口函数
|
||||
loadingWindow(loadingURL: string) {
|
||||
this.loadWindow = new BrowserWindow({
|
||||
width: 400,
|
||||
height: 600,
|
||||
frame: false,
|
||||
skipTaskbar: true,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
webPreferences: {experimentalFeatures: true},
|
||||
});
|
||||
|
||||
this.loadWindow.loadURL(loadingURL);
|
||||
this.loadWindow.show();
|
||||
this.loadWindow.setAlwaysOnTop(true);
|
||||
// 延迟两秒可以根据情况后续调快,= =,就相当于个,sleep吧,就那种。 = =。。。
|
||||
setTimeout(() => {
|
||||
this.createMainWindow();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// 初始化窗口函数
|
||||
initWindow() {
|
||||
if (UseStartupChart) {
|
||||
return this.loadingWindow(this.shartURL);
|
||||
} else {
|
||||
return this.createMainWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MainInit;
|
40
src/preload/index.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {contextBridge, ipcRenderer, IpcRendererEvent, shell} from "electron"
|
||||
import pkg from "../../package.json";
|
||||
|
||||
contextBridge.exposeInMainWorld("ipcRenderer", {
|
||||
send: (channel: string, args?: any) => ipcRenderer.send(channel, args),
|
||||
sendSync: (channel: string, args?: any) => ipcRenderer.sendSync(channel, args),
|
||||
on: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) =>
|
||||
ipcRenderer.on(channel, listener),
|
||||
once: (channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void) =>
|
||||
ipcRenderer.once(channel, listener),
|
||||
invoke: (channel: string, args: any) => ipcRenderer.invoke(channel, args),
|
||||
removeAllListeners: (channel: string) => ipcRenderer.removeAllListeners(channel)
|
||||
});
|
||||
|
||||
function platform() {
|
||||
if (process.platform === "darwin") return "Mac OS";
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
const release = process.getSystemVersion();
|
||||
const arch = process.arch;
|
||||
|
||||
contextBridge.exposeInMainWorld("systemInfo", {
|
||||
platform: platform(),
|
||||
release: release,
|
||||
arch: arch,
|
||||
node: () => process.versions.node,
|
||||
chrome: () => process.versions.chrome,
|
||||
electron: () => process.versions.electron
|
||||
})
|
||||
|
||||
contextBridge.exposeInMainWorld("pkgInfo", pkg)
|
||||
|
||||
contextBridge.exposeInMainWorld("shell", shell)
|
||||
|
||||
contextBridge.exposeInMainWorld("crash", {
|
||||
start: () => {
|
||||
process.crash()
|
||||
}
|
||||
})
|
16
src/renderer/App.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<title-bar />
|
||||
<router-view v-slot="{ Component }">
|
||||
<component :is="Component" />
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { i18n } from "./i18n";
|
||||
import TitleBar from "./components/common/TitleBar.vue";
|
||||
|
||||
const i18nt = computed(() => i18n.global.messages.value[i18n.global.locale.value].$el);
|
||||
</script>
|
||||
|
||||
<style></style>
|
72
src/renderer/api/auth.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const AUTH_BASE_URL = "/api/v1/auth";
|
||||
|
||||
class AuthAPI {
|
||||
/** 登录 接口*/
|
||||
static login(data: LoginData) {
|
||||
const formData = new FormData();
|
||||
formData.append("username", data.username);
|
||||
formData.append("password", data.password);
|
||||
formData.append("captchaKey", data.captchaKey);
|
||||
formData.append("captchaCode", data.captchaCode);
|
||||
return request<any, LoginResult>({
|
||||
url: `${AUTH_BASE_URL}/login`,
|
||||
method: "post",
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 注销 接口*/
|
||||
static logout() {
|
||||
return request({
|
||||
url: `${AUTH_BASE_URL}/logout`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取验证码 接口*/
|
||||
static getCaptcha() {
|
||||
return request<any, CaptchaResult>({
|
||||
url: `${AUTH_BASE_URL}/captcha`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthAPI;
|
||||
|
||||
/** 登录请求参数 */
|
||||
export interface LoginData {
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 密码 */
|
||||
password: string;
|
||||
/** 验证码缓存key */
|
||||
captchaKey: string;
|
||||
/** 验证码 */
|
||||
captchaCode: string;
|
||||
}
|
||||
|
||||
/** 登录响应 */
|
||||
export interface LoginResult {
|
||||
/** 访问token */
|
||||
accessToken?: string;
|
||||
/** 过期时间(单位:毫秒) */
|
||||
expires?: number;
|
||||
/** 刷新token */
|
||||
refreshToken?: string;
|
||||
/** token 类型 */
|
||||
tokenType?: string;
|
||||
}
|
||||
|
||||
/** 验证码响应 */
|
||||
export interface CaptchaResult {
|
||||
/** 验证码缓存key */
|
||||
captchaKey: string;
|
||||
/** 验证码图片Base64字符串 */
|
||||
captchaBase64: string;
|
||||
}
|
25
src/renderer/api/demo.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// 仅示例
|
||||
import request from '@/utils/request'
|
||||
|
||||
// export function login (data) {
|
||||
// return request({
|
||||
// url: '/user/login',
|
||||
// method: 'post',
|
||||
// data
|
||||
// })
|
||||
// }
|
||||
|
||||
// export function getInfo (token) {
|
||||
// return request({
|
||||
// url: '/user/info',
|
||||
// method: 'get',
|
||||
// params: { token }
|
||||
// })
|
||||
// }
|
||||
|
||||
export function message() {
|
||||
return request({
|
||||
url: '/message',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
130
src/renderer/api/dept.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const DEPT_BASE_URL = "/api/v1/dept";
|
||||
|
||||
class DeptAPI {
|
||||
/**
|
||||
* 获取部门列表
|
||||
*
|
||||
* @param queryParams 查询参数(可选)
|
||||
* @returns 部门树形表格数据
|
||||
*/
|
||||
static getList(queryParams?: DeptQuery) {
|
||||
return request<any, DeptVO[]>({
|
||||
url: `${DEPT_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取部门下拉列表 */
|
||||
static getOptions() {
|
||||
return request<any, OptionType[]>({
|
||||
url: `${DEPT_BASE_URL}/options`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门详情表单数据
|
||||
*
|
||||
* @param id 部门ID
|
||||
* @returns 部门详情表单数据
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, DeptForm>({
|
||||
url: `${DEPT_BASE_URL}/${id}/form`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
*
|
||||
* @param data 部门表单数据
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static add(data: DeptForm) {
|
||||
return request({
|
||||
url: `${DEPT_BASE_URL}`,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门
|
||||
*
|
||||
* @param id 部门ID
|
||||
* @param data 部门表单数据
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static update(id: number, data: DeptForm) {
|
||||
return request({
|
||||
url: `${DEPT_BASE_URL}/${id}`,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
*
|
||||
* @param ids 部门ID,多个以英文逗号(,)分隔
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: `${DEPT_BASE_URL}/${ids}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DeptAPI;
|
||||
|
||||
/** 部门查询参数 */
|
||||
export interface DeptQuery {
|
||||
/** 搜索关键字 */
|
||||
keywords?: string;
|
||||
/** 状态 */
|
||||
status?: number;
|
||||
}
|
||||
|
||||
/** 部门类型 */
|
||||
export interface DeptVO {
|
||||
/** 子部门 */
|
||||
children?: DeptVO[];
|
||||
/** 创建时间 */
|
||||
createTime?: Date;
|
||||
/** 部门ID */
|
||||
id?: number;
|
||||
/** 部门名称 */
|
||||
name?: string;
|
||||
/** 部门编号 */
|
||||
code?: string;
|
||||
/** 父部门ID */
|
||||
parentId?: number;
|
||||
/** 排序 */
|
||||
sort?: number;
|
||||
/** 状态(1:启用;0:禁用) */
|
||||
status?: number;
|
||||
/** 修改时间 */
|
||||
updateTime?: Date;
|
||||
}
|
||||
|
||||
/** 部门表单类型 */
|
||||
export interface DeptForm {
|
||||
/** 部门ID(新增不填) */
|
||||
id?: number;
|
||||
/** 部门名称 */
|
||||
name?: string;
|
||||
/** 部门编号 */
|
||||
code?: string;
|
||||
/** 父部门ID */
|
||||
parentId: number;
|
||||
/** 排序 */
|
||||
sort?: number;
|
||||
/** 状态(1:启用;0:禁用) */
|
||||
status?: number;
|
||||
}
|
183
src/renderer/api/dict.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const DICT_BASE_URL = "/api/v1/dict";
|
||||
|
||||
class DictAPI {
|
||||
/**
|
||||
* 获取字典分页列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @returns 字典分页结果
|
||||
*/
|
||||
static getPage(queryParams: DictPageQuery) {
|
||||
return request<any, PageResult<DictPageVO[]>>({
|
||||
url: `${DICT_BASE_URL}/page`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典表单数据
|
||||
*
|
||||
* @param id 字典ID
|
||||
* @returns 字典表单数据
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, ResponseData<DictForm>>({
|
||||
url: `${DICT_BASE_URL}/${id}/form`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增字典
|
||||
*
|
||||
* @param data 字典表单数据
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static add(data: DictForm) {
|
||||
return request({
|
||||
url: `${DICT_BASE_URL}`,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改字典
|
||||
*
|
||||
* @param id 字典ID
|
||||
* @param data 字典表单数据
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static update(id: number, data: DictForm) {
|
||||
return request({
|
||||
url: `${DICT_BASE_URL}/${id}`,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典
|
||||
*
|
||||
* @param ids 字典ID,多个以英文逗号(,)分隔
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: `${DICT_BASE_URL}/${ids}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典的数据项
|
||||
*
|
||||
* @param typeCode 字典编码
|
||||
* @returns 字典数据项
|
||||
*/
|
||||
static getOptions(code: string) {
|
||||
return request<any, OptionType[]>({
|
||||
url: `${DICT_BASE_URL}/${code}/options`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default DictAPI;
|
||||
|
||||
/**
|
||||
* 字典查询参数
|
||||
*/
|
||||
export interface DictPageQuery extends PageQuery {
|
||||
/**
|
||||
* 关键字(字典名称/编码)
|
||||
*/
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典分页对象
|
||||
*/
|
||||
export interface DictPageVO {
|
||||
/**
|
||||
* 字典ID
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 字典编码
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* 字典状态(1-启用,0-禁用)
|
||||
*/
|
||||
status: number;
|
||||
/**
|
||||
* 字典项列表
|
||||
*/
|
||||
dictItems: DictItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典项
|
||||
*/
|
||||
export interface DictItem {
|
||||
/**
|
||||
* 字典项ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 字典项名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 字典项值
|
||||
*/
|
||||
value?: string;
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
sort?: number;
|
||||
/**
|
||||
* 状态(1-启用,0-禁用)
|
||||
*/
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// TypeScript 类型声明
|
||||
|
||||
/**
|
||||
* 字典
|
||||
*/
|
||||
export interface DictForm {
|
||||
/**
|
||||
* 字典ID
|
||||
*/
|
||||
id?: number;
|
||||
/**
|
||||
* 字典名称
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 字典编码
|
||||
*/
|
||||
code?: string;
|
||||
/**
|
||||
* 字典状态(1-启用,0-禁用)
|
||||
*/
|
||||
status?: number;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
remark?: string;
|
||||
/**
|
||||
* 字典数据项列表
|
||||
*/
|
||||
dictItems?: DictItem[];
|
||||
}
|
47
src/renderer/api/file.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const DICT_BASE_URL = "/api/v1/files";
|
||||
class FileAPI {
|
||||
/**
|
||||
* 上传文件
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
static upload(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return request<any, FileInfo>({
|
||||
url: `${DICT_BASE_URL}`,
|
||||
method: "post",
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param filePath 文件完整路径
|
||||
*/
|
||||
static deleteByPath(filePath?: string) {
|
||||
return request({
|
||||
url: `${DICT_BASE_URL}`,
|
||||
method: "delete",
|
||||
params: { filePath: filePath },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default FileAPI;
|
||||
|
||||
/**
|
||||
* 文件API类型声明
|
||||
*/
|
||||
export interface FileInfo {
|
||||
/** 文件名 */
|
||||
name: string;
|
||||
/** 文件路径 */
|
||||
url: string;
|
||||
}
|
56
src/renderer/api/log.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const LOG_BASE_URL = "/api/v1/logs";
|
||||
|
||||
class LogAPI {
|
||||
/**
|
||||
* 获取日志分页列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
*/
|
||||
static getPage(queryParams: LogPageQuery) {
|
||||
return request<any, PageResult<LogPageVO[]>>({
|
||||
url: `${LOG_BASE_URL}/page`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default LogAPI;
|
||||
|
||||
/**
|
||||
* 日志分页查询对象
|
||||
*/
|
||||
export interface LogPageQuery extends PageQuery {
|
||||
/** 搜索关键字 */
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统日志分页VO
|
||||
*/
|
||||
export interface LogPageVO {
|
||||
/** 主键 */
|
||||
id: number;
|
||||
/** 日志模块 */
|
||||
module: string;
|
||||
/** 日志内容 */
|
||||
content: string;
|
||||
/** 请求路径 */
|
||||
requestUri: string;
|
||||
/** 请求方法 */
|
||||
method: string;
|
||||
/** IP 地址 */
|
||||
ip: string;
|
||||
/** 地区 */
|
||||
region: string;
|
||||
/** 浏览器 */
|
||||
browser: string;
|
||||
/** 终端系统 */
|
||||
os: string;
|
||||
/** 执行时间(毫秒) */
|
||||
executionTime: number;
|
||||
/** 操作人 */
|
||||
operator: string;
|
||||
}
|
209
src/renderer/api/menu.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import request from "@/utils/request";
|
||||
// 菜单基础URL
|
||||
const MENU_BASE_URL = "/api/v1/menus";
|
||||
|
||||
class MenuAPI {
|
||||
/**
|
||||
* 获取当前用户的路由列表
|
||||
* <p/>
|
||||
* 无需传入角色,后端解析token获取角色自行判断是否拥有路由的权限
|
||||
*
|
||||
* @returns 路由列表
|
||||
*/
|
||||
static getRoutes() {
|
||||
return request<any, RouteVO[]>({
|
||||
url: `${MENU_BASE_URL}/routes`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单树形列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
* @returns 菜单树形列表
|
||||
*/
|
||||
static getList(queryParams: MenuQuery) {
|
||||
return request<any, MenuVO[]>({
|
||||
url: `${MENU_BASE_URL}`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单下拉数据源
|
||||
*
|
||||
* @returns 菜单下拉数据源
|
||||
*/
|
||||
static getOptions() {
|
||||
return request<any, OptionType[]>({
|
||||
url: `${MENU_BASE_URL}/options`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单表单数据
|
||||
*
|
||||
* @param id 菜单ID
|
||||
* @returns 菜单表单数据
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, MenuForm>({
|
||||
url: `${MENU_BASE_URL}/${id}/form`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加菜单
|
||||
*
|
||||
* @param data 菜单表单数据
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static add(data: MenuForm) {
|
||||
return request({
|
||||
url: `${MENU_BASE_URL}`,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改菜单
|
||||
*
|
||||
* @param id 菜单ID
|
||||
* @param data 菜单表单数据
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static update(id: string, data: MenuForm) {
|
||||
return request({
|
||||
url: `${MENU_BASE_URL}/${id}`,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
*
|
||||
* @param id 菜单ID
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static deleteById(id: number) {
|
||||
return request({
|
||||
url: `${MENU_BASE_URL}/${id}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default MenuAPI;
|
||||
|
||||
import { MenuTypeEnum } from "@theme/enums/MenuTypeEnum";
|
||||
|
||||
/** 菜单查询参数 */
|
||||
export interface MenuQuery {
|
||||
/** 搜索关键字 */
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/** 菜单视图对象 */
|
||||
export interface MenuVO {
|
||||
/** 子菜单 */
|
||||
children?: MenuVO[];
|
||||
/** 组件路径 */
|
||||
component?: string;
|
||||
/** ICON */
|
||||
icon?: string;
|
||||
/** 菜单ID */
|
||||
id?: number;
|
||||
/** 菜单名称 */
|
||||
name?: string;
|
||||
/** 父菜单ID */
|
||||
parentId?: number;
|
||||
/** 按钮权限标识 */
|
||||
perm?: string;
|
||||
/** 跳转路径 */
|
||||
redirect?: string;
|
||||
/** 路由名称 */
|
||||
routeName?: string;
|
||||
/** 路由相对路径 */
|
||||
routePath?: string;
|
||||
/** 菜单排序(数字越小排名越靠前) */
|
||||
sort?: number;
|
||||
/** 菜单 */
|
||||
type?: MenuTypeEnum;
|
||||
/** 菜单是否可见(1:显示;0:隐藏) */
|
||||
visible?: number;
|
||||
}
|
||||
|
||||
/** 菜单表单对象 */
|
||||
export interface MenuForm {
|
||||
/** 菜单ID */
|
||||
id?: string;
|
||||
/** 父菜单ID */
|
||||
parentId?: number;
|
||||
/** 菜单名称 */
|
||||
name?: string;
|
||||
/** 菜单是否可见(1-是 0-否) */
|
||||
visible: number;
|
||||
/** ICON */
|
||||
icon?: string;
|
||||
/** 排序 */
|
||||
sort?: number;
|
||||
/** 路由名称 */
|
||||
routeName?: string;
|
||||
/** 路由路径 */
|
||||
routePath?: string;
|
||||
/** 组件路径 */
|
||||
component?: string;
|
||||
/** 跳转路由路径 */
|
||||
redirect?: string;
|
||||
/** 菜单 */
|
||||
type?: MenuTypeEnum;
|
||||
/** 权限标识 */
|
||||
perm?: string;
|
||||
/** 【菜单】是否开启页面缓存 */
|
||||
keepAlive?: number;
|
||||
/** 【目录】只有一个子路由是否始终显示 */
|
||||
alwaysShow?: number;
|
||||
/** 参数 */
|
||||
params?: KeyValue[];
|
||||
}
|
||||
|
||||
interface KeyValue {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** RouteVO,路由对象 */
|
||||
export interface RouteVO {
|
||||
/** 子路由列表 */
|
||||
children: RouteVO[];
|
||||
/** 组件路径 */
|
||||
component?: string;
|
||||
/** 路由属性 */
|
||||
meta?: Meta;
|
||||
/** 路由名称 */
|
||||
name?: string;
|
||||
/** 路由路径 */
|
||||
path?: string;
|
||||
/** 跳转链接 */
|
||||
redirect?: string;
|
||||
}
|
||||
|
||||
/** Meta,路由属性 */
|
||||
export interface Meta {
|
||||
/** 【目录】只有一个子路由是否始终显示 */
|
||||
alwaysShow?: boolean;
|
||||
/** 是否隐藏(true-是 false-否) */
|
||||
hidden?: boolean;
|
||||
/** ICON */
|
||||
icon?: string;
|
||||
/** 【菜单】是否开启页面缓存 */
|
||||
keepAlive?: boolean;
|
||||
/** 路由title */
|
||||
title?: string;
|
||||
}
|
140
src/renderer/api/role.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const ROLE_BASE_URL = "/api/v1/roles";
|
||||
|
||||
class RoleAPI {
|
||||
/** 获取角色分页数据 */
|
||||
static getPage(queryParams?: RolePageQuery) {
|
||||
return request<any, PageResult<RolePageVO[]>>({
|
||||
url: `${ROLE_BASE_URL}/page`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/** 获取角色下拉数据源 */
|
||||
static getOptions() {
|
||||
return request<any, OptionType[]>({
|
||||
url: `${ROLE_BASE_URL}/options`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色的菜单ID集合
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @returns 角色的菜单ID集合
|
||||
*/
|
||||
static getRoleMenuIds(roleId: number) {
|
||||
return request<any, number[]>({
|
||||
url: `${ROLE_BASE_URL}/${roleId}/menuIds`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配菜单权限
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @param data 菜单ID集合
|
||||
* @returns 请求结果
|
||||
*/
|
||||
static updateRoleMenus(roleId: number, data: number[]) {
|
||||
return request({
|
||||
url: `${ROLE_BASE_URL}/${roleId}/menus`,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色表单数据
|
||||
*
|
||||
* @param id 角色ID
|
||||
* @returns 角色表单数据
|
||||
*/
|
||||
static getFormData(id: number) {
|
||||
return request<any, RoleForm>({
|
||||
url: `${ROLE_BASE_URL}/${id}/form`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/** 添加角色 */
|
||||
static add(data: RoleForm) {
|
||||
return request({
|
||||
url: `${ROLE_BASE_URL}`,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*
|
||||
* @param id 角色ID
|
||||
* @param data 角色表单数据
|
||||
*/
|
||||
static update(id: number, data: RoleForm) {
|
||||
return request({
|
||||
url: `${ROLE_BASE_URL}/${id}`,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除角色,多个以英文逗号(,)分割
|
||||
*
|
||||
* @param ids 角色ID字符串,多个以英文逗号(,)分割
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: `${ROLE_BASE_URL}/${ids}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleAPI;
|
||||
|
||||
/** 角色分页查询参数 */
|
||||
export interface RolePageQuery extends PageQuery {
|
||||
/** 搜索关键字 */
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
/** 角色分页对象 */
|
||||
export interface RolePageVO {
|
||||
/** 角色编码 */
|
||||
code?: string;
|
||||
/** 角色ID */
|
||||
id?: number;
|
||||
/** 角色名称 */
|
||||
name?: string;
|
||||
/** 排序 */
|
||||
sort?: number;
|
||||
/** 角色状态 */
|
||||
status?: number;
|
||||
/** 创建时间 */
|
||||
createTime?: Date;
|
||||
/** 修改时间 */
|
||||
updateTime?: Date;
|
||||
}
|
||||
|
||||
/** 角色表单对象 */
|
||||
export interface RoleForm {
|
||||
/** 角色ID */
|
||||
id?: number;
|
||||
/** 角色编码 */
|
||||
code: string;
|
||||
/** 数据权限 */
|
||||
dataScope?: number;
|
||||
/** 角色名称 */
|
||||
name: string;
|
||||
/** 排序 */
|
||||
sort?: number;
|
||||
/** 角色状态(1-正常;0-停用) */
|
||||
status?: number;
|
||||
}
|
35
src/renderer/api/stats.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const STATS_BASE_URL = "/api/v1/stats";
|
||||
|
||||
class StatsAPI {
|
||||
static getVisitTrend(queryParams: VisitTrendQuery) {
|
||||
return request<any, VisitTrendVO>({
|
||||
url: `${STATS_BASE_URL}/visit-trend`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default StatsAPI;
|
||||
|
||||
/** 访问趋势视图对象 */
|
||||
export interface VisitTrendVO {
|
||||
/** 日期列表 */
|
||||
dates: string[];
|
||||
/** 浏览量(PV) */
|
||||
pvList: number[];
|
||||
/** 访客数(UV) */
|
||||
uvList: number[];
|
||||
/** IP数 */
|
||||
ipList: number[];
|
||||
}
|
||||
|
||||
/** 访问趋势查询参数 */
|
||||
export interface VisitTrendQuery {
|
||||
/** 开始日期 */
|
||||
startDate: string;
|
||||
/** 结束日期 */
|
||||
endDate: string;
|
||||
}
|
232
src/renderer/api/user.ts
Normal file
@ -0,0 +1,232 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
const USER_BASE_URL = "/api/v1/users";
|
||||
|
||||
class UserAPI {
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @returns 登录用户昵称、头像信息,包括角色和权限
|
||||
*/
|
||||
static getInfo() {
|
||||
return request<any, UserInfo>({
|
||||
url: `${USER_BASE_URL}/me`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
*/
|
||||
static getPage(queryParams: UserPageQuery) {
|
||||
return request<any, PageResult<UserPageVO[]>>({
|
||||
url: `${USER_BASE_URL}/page`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户表单详情
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @returns 用户表单详情
|
||||
*/
|
||||
static getFormData(userId: number) {
|
||||
return request<any, UserForm>({
|
||||
url: `${USER_BASE_URL}/${userId}/form`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户
|
||||
*
|
||||
* @param data 用户表单数据
|
||||
*/
|
||||
static add(data: UserForm) {
|
||||
return request({
|
||||
url: `${USER_BASE_URL}`,
|
||||
method: "post",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param data 用户表单数据
|
||||
*/
|
||||
static update(id: number, data: UserForm) {
|
||||
return request({
|
||||
url: `${USER_BASE_URL}/${id}`,
|
||||
method: "put",
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户密码
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @param password 新密码
|
||||
*/
|
||||
static updatePassword(id: number, password: string) {
|
||||
return request({
|
||||
url: `${USER_BASE_URL}/${id}/password`,
|
||||
method: "patch",
|
||||
params: { password: password },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除用户,多个以英文逗号(,)分割
|
||||
*
|
||||
* @param ids 用户ID字符串,多个以英文逗号(,)分割
|
||||
*/
|
||||
static deleteByIds(ids: string) {
|
||||
return request({
|
||||
url: `${USER_BASE_URL}/${ids}`,
|
||||
method: "delete",
|
||||
});
|
||||
}
|
||||
|
||||
/** 下载用户导入模板 */
|
||||
static downloadTemplate() {
|
||||
return request({
|
||||
url: `${USER_BASE_URL}/template`,
|
||||
method: "get",
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户
|
||||
*
|
||||
* @param queryParams 查询参数
|
||||
*/
|
||||
static export(queryParams: UserPageQuery) {
|
||||
return request({
|
||||
url: `${USER_BASE_URL}/export`,
|
||||
method: "get",
|
||||
params: queryParams,
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入用户
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @param file 导入文件
|
||||
*/
|
||||
static import(deptId: number, file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return request({
|
||||
url: `${USER_BASE_URL}/import`,
|
||||
method: "post",
|
||||
params: { deptId: deptId },
|
||||
data: formData,
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UserAPI;
|
||||
|
||||
/** 登录用户信息 */
|
||||
export interface UserInfo {
|
||||
/** 用户ID */
|
||||
userId?: number;
|
||||
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
|
||||
/** 昵称 */
|
||||
nickname?: string;
|
||||
|
||||
/** 头像URL */
|
||||
avatar?: string;
|
||||
|
||||
/** 角色 */
|
||||
roles: string[];
|
||||
|
||||
/** 权限 */
|
||||
perms: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户分页查询对象
|
||||
*/
|
||||
export interface UserPageQuery extends PageQuery {
|
||||
/** 搜索关键字 */
|
||||
keywords?: string;
|
||||
|
||||
/** 用户状态 */
|
||||
status?: number;
|
||||
|
||||
/** 部门ID */
|
||||
deptId?: number;
|
||||
|
||||
/** 开始时间 */
|
||||
startTime?: string;
|
||||
|
||||
/** 结束时间 */
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
/** 用户分页对象 */
|
||||
export interface UserPageVO {
|
||||
/** 用户头像URL */
|
||||
avatar?: string;
|
||||
/** 创建时间 */
|
||||
createTime?: Date;
|
||||
/** 部门名称 */
|
||||
deptName?: string;
|
||||
/** 用户邮箱 */
|
||||
email?: string;
|
||||
/** 性别 */
|
||||
genderLabel?: string;
|
||||
/** 用户ID */
|
||||
id?: number;
|
||||
/** 手机号 */
|
||||
mobile?: string;
|
||||
/** 用户昵称 */
|
||||
nickname?: string;
|
||||
/** 角色名称,多个使用英文逗号(,)分割 */
|
||||
roleNames?: string;
|
||||
/** 用户状态(1:启用;0:禁用) */
|
||||
status?: number;
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 用户表单类型 */
|
||||
export interface UserForm {
|
||||
/** 用户头像 */
|
||||
avatar?: string;
|
||||
/** 部门ID */
|
||||
deptId?: number;
|
||||
/** 邮箱 */
|
||||
email?: string;
|
||||
/** 性别 */
|
||||
gender?: number;
|
||||
/** 用户ID */
|
||||
id?: number;
|
||||
/** 手机号 */
|
||||
mobile?: string;
|
||||
/** 昵称 */
|
||||
nickname?: string;
|
||||
/** 角色ID集合 */
|
||||
roleIds?: number[];
|
||||
/** 用户状态(1:正常;0:禁用) */
|
||||
status?: number;
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
}
|
BIN
src/renderer/assets/404_images/404.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
src/renderer/assets/404_images/404_cloud.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
1
src/renderer/assets/icons/svg/close.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width='11' height='11' viewBox='0 0 11 11' class="icon" xmlns='http://www.w3.org/2000/svg'><path d='M6.279 5.5L11 10.221l-.779.779L5.5 6.279.779 11 0 10.221 4.721 5.5 0 .779.779 0 5.5 4.721 10.221 0 11 .779 6.279 5.5z'/></svg>
|
After Width: | Height: | Size: 366 B |
529
src/renderer/assets/icons/svg/electron-logo.svg
Normal file
@ -0,0 +1,529 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve"> <image id="image0" width="256" height="256" x="0" y="0"
|
||||
href="data:image/png;base64,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" />
|
||||
</svg>
|
After Width: | Height: | Size: 40 KiB |
1
src/renderer/assets/icons/svg/mini.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width='11' height='11' viewBox='0 0 11 11' class='icon' xmlns='http://www.w3.org/2000/svg'><path d='M11 4.399V5.5H0V4.399h11z'/></svg>
|
After Width: | Height: | Size: 274 B |
1
src/renderer/assets/icons/svg/mix.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width='11' height='11' viewBox='0 0 11 11' class='icon' xmlns='http://www.w3.org/2000/svg'><path d='M11 0v11H0V0h11zM9.899 1.101H1.1V9.9h8.8V1.1z'/></svg>
|
After Width: | Height: | Size: 294 B |
1
src/renderer/assets/icons/svg/reduction.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width='11' height='11' viewBox='0 0 11 11' class="icon" xmlns='http://www.w3.org/2000/svg'><path d='M11 8.798H8.798V11H0V2.202h2.202V0H11v8.798zm-3.298-5.5h-6.6v6.6h6.6v-6.6zM9.9 1.1H3.298v1.101h5.5v5.5h1.1v-6.6z'/></svg>
|
After Width: | Height: | Size: 361 B |
BIN
src/renderer/assets/logo.png
Normal file
After Width: | Height: | Size: 109 KiB |
80
src/renderer/components/LandingPage/SystemInformation.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="title">{{ t("about.system") }}</div>
|
||||
<div class="items">
|
||||
<div v-for="(item, index) in tips" :key="index" class="item">
|
||||
<div class="name" v-text="item.name"/>
|
||||
<div class="value" v-text="item.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from "vue";
|
||||
import {useRoute} from "vue-router";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {path, name} = useRoute();
|
||||
const {t} = useI18n();
|
||||
const process = window.process;
|
||||
const pkg = window.pkgInfo;
|
||||
const systemInfo = window.systemInfo;
|
||||
|
||||
let tips = ref(
|
||||
computed(() => [
|
||||
{name: t("about.language"), value: t("about.languageValue")},
|
||||
{name: t("about.currentPagePath"), value: path},
|
||||
{name: t("about.currentPageName"), value: name},
|
||||
{
|
||||
name: t("about.vueVersion"),
|
||||
value:
|
||||
process.env.NODE_ENV === "development"
|
||||
? pkg.version
|
||||
: "不可见",
|
||||
},
|
||||
{
|
||||
name: t("about.electronVersion"),
|
||||
value: systemInfo.electron() || "未获取",
|
||||
},
|
||||
{
|
||||
name: t("about.nodeVersion"),
|
||||
value: systemInfo.node() || "未获取",
|
||||
},
|
||||
{name: t("about.systemPlatform"), value: systemInfo.platform},
|
||||
{name: t("about.systemVersion"), value: systemInfo.release},
|
||||
{name: t("about.systemArch"), value: systemInfo.arch + "位"},
|
||||
])
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
color: #888;
|
||||
font-size: 18px;
|
||||
font-weight: initial;
|
||||
letter-spacing: 0.25px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.items {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 6px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.item .name {
|
||||
color: #6a6a6a;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.item .value {
|
||||
color: #35495e;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
161
src/renderer/components/UniverJs/SDK/open-file-csv.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import {
|
||||
CommandType,
|
||||
ICellData,
|
||||
ICommand,
|
||||
ICommandService,
|
||||
IUniverInstanceService,
|
||||
Inject,
|
||||
Injector,
|
||||
Plugin,
|
||||
UniverInstanceType,
|
||||
Workbook,
|
||||
} from "@univerjs/core";
|
||||
|
||||
import { SetRangeValuesCommand } from "@univerjs/sheets";
|
||||
import {
|
||||
ComponentManager,
|
||||
IMenuService,
|
||||
MenuGroup,
|
||||
MenuItemType,
|
||||
MenuPosition,
|
||||
} from "@univerjs/ui";
|
||||
import { FolderSingle } from "@univerjs/icons";
|
||||
|
||||
/**
|
||||
* wait user select csv file
|
||||
*/
|
||||
const waitUserSelectCSVFile = (
|
||||
onSelect: (data: {
|
||||
data: string[][];
|
||||
colsCount: number;
|
||||
rowsCount: number;
|
||||
}) => void
|
||||
) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".csv";
|
||||
input.click();
|
||||
|
||||
input.onchange = () => {
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const text = reader.result;
|
||||
console.log(text);
|
||||
if (typeof text !== "string") return;
|
||||
|
||||
// tip: use npm package to parse csv
|
||||
const rows = text.split(/\r\n|\n/);
|
||||
const data = rows.map((line) => line.split(","));
|
||||
|
||||
const colsCount = data.reduce((max, row) => Math.max(max, row.length), 0);
|
||||
|
||||
onSelect({
|
||||
data,
|
||||
colsCount,
|
||||
rowsCount: data.length,
|
||||
});
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* parse csv to univer data
|
||||
* @param csv
|
||||
* @returns { v: string }[][]
|
||||
*/
|
||||
const parseCSV2UniverData = (csv: string[][]): ICellData[][] => {
|
||||
return csv.map((row) => {
|
||||
return row.map((cell) => {
|
||||
return {
|
||||
v: cell || "",
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Import CSV Button Plugin
|
||||
* A simple Plugin example, show how to write a plugin.
|
||||
*/
|
||||
class ImportCSVButtonPlugin extends Plugin {
|
||||
static override pluginName = "import-csv-plugin";
|
||||
constructor(
|
||||
_config: null,
|
||||
// inject injector, required
|
||||
@Inject(Injector) override readonly _injector: Injector,
|
||||
// inject menu service, to add toolbar button
|
||||
@Inject(IMenuService) private menuService: IMenuService,
|
||||
// inject command service, to register command handler
|
||||
@Inject(ICommandService) private readonly commandService: ICommandService,
|
||||
// inject component manager, to register icon component
|
||||
@Inject(ComponentManager)
|
||||
private readonly componentManager: ComponentManager
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* The first lifecycle of the plugin mounted on the Univer instance,
|
||||
* the Univer business instance has not been created at this time.
|
||||
* The plugin should add its own module to the dependency injection system at this lifecycle.
|
||||
* It is not recommended to initialize the internal module of the plugin outside this lifecycle.
|
||||
*/
|
||||
onStarting() {
|
||||
// register icon component
|
||||
this.componentManager.register("FolderSingle", FolderSingle);
|
||||
|
||||
const buttonId = "import-csv-button";
|
||||
|
||||
const menuItem = {
|
||||
id: buttonId,
|
||||
title: "导入CSV",
|
||||
tooltip: "导入CSV表格数据",
|
||||
icon: "FolderSingle", // icon name
|
||||
type: MenuItemType.BUTTON,
|
||||
group: MenuGroup.CONTEXT_MENU_DATA,
|
||||
positions: [MenuPosition.TOOLBAR_START],
|
||||
};
|
||||
|
||||
this.menuService.addMenuItem(menuItem, {});
|
||||
|
||||
const command: ICommand = {
|
||||
type: CommandType.OPERATION,
|
||||
id: buttonId,
|
||||
handler: (accessor) => {
|
||||
// inject univer instance service
|
||||
const univer = accessor.get(IUniverInstanceService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
// get current sheet
|
||||
const sheet = univer
|
||||
.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!
|
||||
.getActiveSheet();
|
||||
// wait user select csv file
|
||||
waitUserSelectCSVFile(({ data, rowsCount, colsCount }) => {
|
||||
// set sheet size
|
||||
sheet.setColumnCount(colsCount);
|
||||
sheet.setRowCount(rowsCount);
|
||||
|
||||
// set sheet data
|
||||
commandService.executeCommand(SetRangeValuesCommand.id, {
|
||||
range: {
|
||||
startColumn: 0, // start column index
|
||||
startRow: 0, // start row index
|
||||
endColumn: colsCount - 1, // end column index
|
||||
endRow: rowsCount - 1, // end row index
|
||||
},
|
||||
value: parseCSV2UniverData(data),
|
||||
});
|
||||
});
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
this.commandService.registerCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
export default ImportCSVButtonPlugin;
|
91
src/renderer/components/UniverJs/SDK/save-data.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import {
|
||||
CommandType,
|
||||
ICellData,
|
||||
ICommand,
|
||||
ICommandService,
|
||||
IUniverInstanceService,
|
||||
Inject,
|
||||
Injector,
|
||||
Plugin,
|
||||
UniverInstanceType,
|
||||
Workbook,
|
||||
} from "@univerjs/core";
|
||||
|
||||
import {
|
||||
ComponentManager,
|
||||
IMenuService,
|
||||
MenuGroup,
|
||||
MenuItemType,
|
||||
MenuPosition,
|
||||
} from "@univerjs/ui";
|
||||
|
||||
import { SaveSingle } from "@univerjs/icons";
|
||||
|
||||
/**
|
||||
* 实例化按钮插件
|
||||
*/
|
||||
class SaveDataButtonPlugin extends Plugin {
|
||||
static override pluginName = "save-data-plugin";
|
||||
constructor(
|
||||
_config: null,
|
||||
// inject injector, required
|
||||
@Inject(Injector) override readonly _injector: Injector,
|
||||
// inject menu service, to add toolbar button
|
||||
@Inject(IMenuService) private menuService: IMenuService,
|
||||
// inject command service, to register command handler
|
||||
@Inject(ICommandService) private readonly commandService: ICommandService,
|
||||
// inject component manager, to register icon component
|
||||
@Inject(ComponentManager)
|
||||
private readonly componentManager: ComponentManager
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* The first lifecycle of the plugin mounted on the Univer instance,
|
||||
* the Univer business instance has not been created at this time.
|
||||
* The plugin should add its own module to the dependency injection system at this lifecycle.
|
||||
* It is not recommended to initialize the internal module of the plugin outside this lifecycle.
|
||||
*/
|
||||
onStarting() {
|
||||
// register icon component
|
||||
this.componentManager.register("SaveSingle", SaveSingle);
|
||||
|
||||
const buttonId = "save-data-button";
|
||||
|
||||
const menuItem = {
|
||||
id: buttonId,
|
||||
title: "保存数据",
|
||||
tooltip: "获取数据并保存",
|
||||
icon: "SaveSingle", // icon name
|
||||
type: MenuItemType.BUTTON,
|
||||
group: MenuGroup.CONTEXT_MENU_DATA,
|
||||
positions: [MenuPosition.TOOLBAR_START],
|
||||
};
|
||||
|
||||
this.menuService.addMenuItem(menuItem, {});
|
||||
|
||||
const command: ICommand = {
|
||||
type: CommandType.OPERATION,
|
||||
id: buttonId,
|
||||
handler: (accessor) => {
|
||||
// inject univer instance service
|
||||
const univer = accessor.get(IUniverInstanceService);
|
||||
|
||||
// get current sheet
|
||||
const sheet = univer
|
||||
.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!
|
||||
.getActiveSheet();
|
||||
// 保存数据
|
||||
const res = sheet.getSnapshot();
|
||||
const data = res.cellData;
|
||||
console.log("表中数据", data);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
this.commandService.registerCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
export default SaveDataButtonPlugin;
|
307
src/renderer/components/UniverJs/UniverSheet.vue
Normal file
@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<div ref="container" class="univer-container"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 组件传参的data(要初始化到表格中的数据)
|
||||
const props = defineProps({
|
||||
// workbook data
|
||||
data: {
|
||||
type: Object,
|
||||
require: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
//页脚
|
||||
isFooter: {
|
||||
type: Boolean,
|
||||
require: false,
|
||||
default: false,
|
||||
},
|
||||
//测试传入一个函数
|
||||
testFunc: {
|
||||
type: Function,
|
||||
require: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
});
|
||||
|
||||
const data = props.data;
|
||||
import {
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
Workbook,
|
||||
LocaleType,
|
||||
IWorkbookData,
|
||||
} from "@univerjs/core";
|
||||
import { defaultTheme } from "@univerjs/design";
|
||||
import { UniverDocsPlugin } from "@univerjs/docs";
|
||||
import { UniverDocsUIPlugin } from "@univerjs/docs-ui";
|
||||
import { UniverFormulaEnginePlugin } from "@univerjs/engine-formula";
|
||||
import { UniverRenderEnginePlugin } from "@univerjs/engine-render";
|
||||
import { UniverSheetsPlugin } from "@univerjs/sheets";
|
||||
import { UniverSheetsFormulaPlugin } from "@univerjs/sheets-formula";
|
||||
import { UniverSheetsUIPlugin } from "@univerjs/sheets-ui";
|
||||
import { UniverUIPlugin } from "@univerjs/ui";
|
||||
import { FUniver } from "@univerjs/facade";
|
||||
|
||||
// 禅编辑器
|
||||
import "@univerjs/sheets-zen-editor/lib/index.css";
|
||||
import { UniverSheetsZenEditorPlugin } from "@univerjs/sheets-zen-editor";
|
||||
// 超链接
|
||||
import "@univerjs/sheets-hyper-link-ui/lib/index.css";
|
||||
import { UniverSheetsHyperLinkPlugin } from "@univerjs/sheets-hyper-link";
|
||||
import { UniverSheetsHyperLinkUIPlugin } from "@univerjs/sheets-hyper-link-ui";
|
||||
// 浮动图片
|
||||
import "@univerjs/drawing-ui/lib/index.css";
|
||||
import "@univerjs/sheets-drawing-ui/lib/index.css";
|
||||
import { UniverDrawingPlugin } from "@univerjs/drawing";
|
||||
import { UniverDrawingUIPlugin } from "@univerjs/drawing-ui";
|
||||
import { UniverSheetsDrawingPlugin } from "@univerjs/sheets-drawing";
|
||||
import { UniverSheetsDrawingUIPlugin } from "@univerjs/sheets-drawing-ui";
|
||||
// 查找与替换
|
||||
import "@univerjs/find-replace/lib/index.css";
|
||||
import { UniverFindReplacePlugin } from "@univerjs/find-replace";
|
||||
import { UniverSheetsFindReplacePlugin } from "@univerjs/sheets-find-replace";
|
||||
// 筛选
|
||||
import "@univerjs/sheets-filter-ui/lib/index.css";
|
||||
import { UniverSheetsFilterPlugin } from "@univerjs/sheets-filter";
|
||||
import { UniverSheetsFilterUIPlugin } from "@univerjs/sheets-filter-ui";
|
||||
// 排序
|
||||
import "@univerjs/sheets-sort-ui/lib/index.css";
|
||||
import { UniverSheetsSortPlugin } from "@univerjs/sheets-sort";
|
||||
import { UniverSheetsSortUIPlugin } from "@univerjs/sheets-sort-ui";
|
||||
// 数据验证
|
||||
import "@univerjs/sheets-data-validation/lib/index.css";
|
||||
import { UniverDataValidationPlugin } from "@univerjs/data-validation";
|
||||
import { UniverSheetsDataValidationPlugin } from "@univerjs/sheets-data-validation";
|
||||
// 条件格式
|
||||
import "@univerjs/sheets-conditional-formatting-ui/lib/index.css";
|
||||
import { UniverSheetsConditionalFormattingPlugin } from "@univerjs/sheets-conditional-formatting";
|
||||
import { UniverSheetsConditionalFormattingUIPlugin } from "@univerjs/sheets-conditional-formatting-ui";
|
||||
// 数字格式
|
||||
import "@univerjs/sheets-numfmt/lib/index.css";
|
||||
import { UniverSheetsNumfmtPlugin } from "@univerjs/sheets-numfmt";
|
||||
//评论批注
|
||||
import "@univerjs/thread-comment-ui/lib/index.css";
|
||||
import {
|
||||
IThreadCommentMentionDataService,
|
||||
UniverThreadCommentUIPlugin,
|
||||
} from "@univerjs/thread-comment-ui";
|
||||
import { UniverThreadCommentPlugin } from "@univerjs/thread-comment";
|
||||
import { UniverSheetsThreadCommentBasePlugin } from "@univerjs/sheets-thread-comment-base";
|
||||
import { UniverSheetsThreadCommentPlugin } from "@univerjs/sheets-thread-comment";
|
||||
|
||||
// import ImportCSVButtonPlugin from "./SDK/open-file-csv";
|
||||
// import SaveDataButtonPlugin from "./SDK/save-data";
|
||||
// import { onBeforeUnmount, onMounted, ref, toRaw } from "vue";
|
||||
|
||||
/**
|
||||
*
|
||||
* The ability to import locales from virtual modules and automatically import styles is provided by Univer Plugins. For more details, please refer to: https://univer.ai/guides/sheet/advanced/univer-plugins.
|
||||
* If you encounter issues while using the plugin or have difficulty understanding how to use it, please disable Univer Plugins and manually import the language packs and styles.
|
||||
*
|
||||
* 【从虚拟模块导入语言包】以及【自动导入样式】是由 Univer Plugins 提供的能力,详情参考:https://univer.ai/zh-CN/guides/sheet/advanced/univer-plugins
|
||||
* 如果您在使用该插件的时候出现了问题,或者无法理解如何使用,请禁用 Univer Plugins,并手动导入语言包和样式
|
||||
*/
|
||||
import { zhCN, enUS } from "univer:locales";
|
||||
|
||||
const univerRef = ref<Univer | null>(null);
|
||||
const workbook = ref<Workbook | null>(null);
|
||||
const container = ref<HTMLElement | null>(null);
|
||||
let univerAPI: any, _univer: any;
|
||||
onMounted(() => {
|
||||
init(data);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
destroyUniver();
|
||||
});
|
||||
|
||||
/**
|
||||
* Initialize univer instance and workbook instance
|
||||
* @param data {IWorkbookData} document see https://univer.ai/typedoc/@univerjs/core/interfaces/IWorkbookData
|
||||
*/
|
||||
const init = (data?: {}) => {
|
||||
const univer = new Univer({
|
||||
theme: defaultTheme,
|
||||
locale: LocaleType.ZH_CN,
|
||||
locales: {
|
||||
[LocaleType.ZH_CN]: zhCN,
|
||||
[LocaleType.EN_US]: enUS,
|
||||
},
|
||||
});
|
||||
univerRef.value = univer;
|
||||
|
||||
// 注册插件
|
||||
// core plugins
|
||||
const isFooter = props.isFooter;
|
||||
univer.registerPlugin(UniverRenderEnginePlugin);
|
||||
univer.registerPlugin(UniverFormulaEnginePlugin);
|
||||
univer.registerPlugin(UniverUIPlugin, {
|
||||
container: container.value!,
|
||||
header: true,
|
||||
//显示页头
|
||||
footer: isFooter,
|
||||
//显示页脚
|
||||
contextMenu: true,
|
||||
//显示右键
|
||||
});
|
||||
|
||||
// doc plugins
|
||||
univer.registerPlugin(UniverDocsPlugin, {
|
||||
hasScroll: false,
|
||||
});
|
||||
univer.registerPlugin(UniverDocsUIPlugin);
|
||||
|
||||
// sheet plugins
|
||||
univer.registerPlugin(UniverSheetsPlugin);
|
||||
univer.registerPlugin(UniverSheetsUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsFormulaPlugin);
|
||||
|
||||
// 禅编辑器
|
||||
univer.registerPlugin(UniverSheetsZenEditorPlugin);
|
||||
// 超链接
|
||||
univer.registerPlugin(UniverSheetsHyperLinkPlugin);
|
||||
univer.registerPlugin(UniverSheetsHyperLinkUIPlugin);
|
||||
// 浮动图片
|
||||
univer.registerPlugin(UniverDrawingPlugin);
|
||||
univer.registerPlugin(UniverDrawingUIPlugin);
|
||||
univer.registerPlugin(UniverSheetsDrawingPlugin);
|
||||
univer.registerPlugin(UniverSheetsDrawingUIPlugin);
|
||||
// 查找与替换
|
||||
univer.registerPlugin(UniverFindReplacePlugin);
|
||||
univer.registerPlugin(UniverSheetsFindReplacePlugin);
|
||||
// 筛选
|
||||
univer.registerPlugin(UniverSheetsFilterPlugin);
|
||||
univer.registerPlugin(UniverSheetsFilterUIPlugin);
|
||||
// 排序
|
||||
univer.registerPlugin(UniverSheetsSortPlugin);
|
||||
univer.registerPlugin(UniverSheetsSortUIPlugin);
|
||||
// 数据验证
|
||||
univer.registerPlugin(UniverDataValidationPlugin);
|
||||
univer.registerPlugin(UniverSheetsDataValidationPlugin);
|
||||
// 条件格式
|
||||
univer.registerPlugin(UniverSheetsConditionalFormattingPlugin);
|
||||
univer.registerPlugin(UniverSheetsConditionalFormattingUIPlugin);
|
||||
// 数字格式
|
||||
univer.registerPlugin(UniverSheetsNumfmtPlugin);
|
||||
// 评论批注
|
||||
const mockUser = {
|
||||
userID: "mockId",
|
||||
name: "MockUser",
|
||||
avatar:
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAInSURBVHgBtZU9TxtBEIbfWRzFSIdkikhBSqRQkJqkCKTCFkqVInSUSaT0wC8w/gXxD4gU2nRJkXQWhAZowDUUWKIwEgWWbEEB3mVmx3dn4DA2nB/ppNuPeWd29mMIPXDr+RxwtgRHeW6+guNPRxogqnL7Dwz9psJ27S4NShaeZTH3kwXy6I81dlRKcmRui88swdq9AcSFL7Buz1Vmlns64MiLsCjzwnIYHLH57tbfFbs7KRaXyEU8FVZofqccOfA5l7Q8LPIkGrwnb2RPNEXWFVMUF3L+kDCk0btDDAMzOm5YfAHDwp4tG74wnzAsiOYMnJ3GoDybA7IT98/jm5+JNnfiIzAS6LlqHQBN/i6b2t/cV1Hh6BfwYlHnHP4AXi5q/8kmMMpOs8+BixZw/Fd6xUEHEbnkgclvQP2fGp7uShRKnQ3G32rkjV1th8JhIGG7tR/JyjGteSOZELwGMmNqIIigRCLRh2OZIE6BjItdd7pCW6Uhm1zzkUtungSxwEUzNpQ+GQumtH1ej1MqgmNT6vwmhCq5yuwq56EYTbgeQUz3yvrpV1b4ok3nYJ+eYhgYmjRUqErx2EDq0Fr8FhG++iqVGqxlUJI/70Ar0UgJaWHj6hYVHJrfKssAHot1JfqwE9WVWzXZVd5z2Ws/4PnmtEjkXeKJDvxUecLbWOXH/DP6QQ4J72NS0adedp1aseBfXP8odlZFfPvBF7SN/8hky1TYuPOAXAEipMx15u5ToAAAAABJRU5ErkJggg==",
|
||||
anonymous: false,
|
||||
canBindAnonymous: false,
|
||||
};
|
||||
class CustomMentionDataService implements IThreadCommentMentionDataService {
|
||||
dataSource: Nullable<IThreadCommentMentionDataSource>;
|
||||
trigger: string = "@";
|
||||
|
||||
async getMentions(search: string) {
|
||||
return [
|
||||
{
|
||||
id: mockUser.userID,
|
||||
label: mockUser.name,
|
||||
type: "user",
|
||||
icon: mockUser.avatar,
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
label: "User2",
|
||||
type: "user",
|
||||
icon: mockUser.avatar,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(UniverThreadCommentPlugin);
|
||||
univer.registerPlugin(UniverThreadCommentUIPlugin, {
|
||||
overrides: [
|
||||
[
|
||||
IThreadCommentMentionDataService,
|
||||
{ useClass: CustomMentionDataService },
|
||||
],
|
||||
],
|
||||
});
|
||||
univer.registerPlugin(UniverSheetsThreadCommentBasePlugin);
|
||||
univer.registerPlugin(UniverSheetsThreadCommentPlugin);
|
||||
|
||||
//diy plugins
|
||||
// univer.registerPlugin(ImportCSVButtonPlugin);
|
||||
// univer.registerPlugin(SaveDataButtonPlugin);
|
||||
|
||||
// 利用传的预定义数据data创建工作簿实例
|
||||
workbook.value = univer.createUnit<IWorkbookData, Workbook>(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
data
|
||||
);
|
||||
|
||||
// 使用 Facade API;可以通过调用 univerAPI 的方法来使用 Univer,包裹的univer必须完成实例化
|
||||
univerAPI = FUniver.newAPI(univer);
|
||||
_univer = univer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy univer instance and workbook instance
|
||||
*/
|
||||
const destroyUniver = () => {
|
||||
toRaw(univerRef.value)?.dispose();
|
||||
univerRef.value = null;
|
||||
workbook.value = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get workbook data
|
||||
*/
|
||||
const getData = () => {
|
||||
if (!workbook.value) {
|
||||
throw new Error("Workbook is not initialized");
|
||||
}
|
||||
return workbook.value.save();
|
||||
};
|
||||
|
||||
/**
|
||||
* 监听单元格数据变化
|
||||
*/
|
||||
const onChangeData = () => {
|
||||
var res = univerAPI.onCommandExecuted((command: any) => {
|
||||
const { id } = command;
|
||||
if (id === "doc.command.insert-text" || id === "doc.command.delete-text") {
|
||||
const doc = univerAPI.getActiveDocument();
|
||||
if (doc) {
|
||||
const snapshot = doc.getSnapshot();
|
||||
// console.log(snapshot.body?.dataStream);
|
||||
// console.log(snapshot);
|
||||
return snapshot.body.dataStream;
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* 向上层组件返回传参或事件
|
||||
*/
|
||||
const expose = {
|
||||
getData,
|
||||
destroyUniver,
|
||||
onChangeData,
|
||||
};
|
||||
defineExpose(expose);
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.univer-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Also hide the menubar */
|
||||
:global(.univer-menubar) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
132
src/renderer/components/common/TitleBar.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div v-if="!IsUseSysTitle && isNotMac && !IsWeb" class="window-title">
|
||||
<!-- 软件logo预留位置 -->
|
||||
<div class="logo" style="-webkit-app-region: drag">
|
||||
<img
|
||||
class="icon-logo"
|
||||
src="@renderer/assets/icons/svg/electron-logo.svg"
|
||||
/>
|
||||
</div>
|
||||
<!-- 菜单栏位置 -->
|
||||
<div></div>
|
||||
<!-- 中间标题位置 -->
|
||||
<div class="title" style="-webkit-app-region: drag"></div>
|
||||
<div class="controls-container">
|
||||
<div class="windows-icon-bg" @click="Mini">
|
||||
<img class="icon-size" src="@renderer/assets/icons/svg/mini.svg"/>
|
||||
</div>
|
||||
<div class="windows-icon-bg" @click="MixOrReduction">
|
||||
<img
|
||||
v-if="mix"
|
||||
class="icon-size"
|
||||
src="@renderer/assets/icons/svg/reduction.svg"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
class="icon-size"
|
||||
src="@renderer/assets/icons/svg/mix.svg"
|
||||
/>
|
||||
</div>
|
||||
<div class="windows-icon-bg close-icon" @click="Close">
|
||||
<img class="icon-size" src="@renderer/assets/icons/svg/close.svg"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!IsUseSysTitle && !isNotMac" class="window-title">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from "vue";
|
||||
import {IpcChannel, invoke} from "@/utils/ipcRenderer";
|
||||
|
||||
const process = window.process;
|
||||
const IsUseSysTitle = ref(false);
|
||||
const mix = ref(false);
|
||||
const isNotMac = ref(process.platform !== "darwin");
|
||||
const IsWeb = ref(process.env.BUILD_TARGET);
|
||||
invoke(IpcChannel.IsUseSysTitle).then((res) => {
|
||||
IsUseSysTitle.value = res;
|
||||
});
|
||||
|
||||
const Mini = () => {
|
||||
invoke(IpcChannel.WindowMini);
|
||||
};
|
||||
const MixOrReduction = () => {
|
||||
invoke(IpcChannel.WindowMax).then((res) => {
|
||||
mix.value = res.status;
|
||||
});
|
||||
};
|
||||
const Close = () => {
|
||||
invoke(IpcChannel.WindowClose);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.window-title {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
-webkit-app-region: drag;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
|
||||
.icon-logo {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.controls-container {
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 3000;
|
||||
-webkit-app-region: no-drag;
|
||||
height: 100%;
|
||||
width: 138px;
|
||||
margin-left: auto;
|
||||
|
||||
.windows-icon-bg {
|
||||
display: inline-block;
|
||||
-webkit-app-region: no-drag;
|
||||
height: 100%;
|
||||
width: 33.34%;
|
||||
color: rgba(129, 129, 129, 0.6);
|
||||
|
||||
.icon-size {
|
||||
width: 12px;
|
||||
height: 15px;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.windows-icon-bg:hover {
|
||||
background-color: rgba(182, 182, 182, 0.2);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
background-color: rgba(232, 17, 35, 0.9);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
154
src/renderer/components/updataProgress/index.vue
Normal file
@ -0,0 +1,154 @@
|
||||
<!-- -->
|
||||
<template>
|
||||
<div v-if="visible" class="mask-box" @click="closeMask">
|
||||
<div class="title-box">
|
||||
<div class="title">
|
||||
<!-- <ali-svg-icon :icon-class="icon_name" class-name="cuowu"></ali-svg-icon> -->
|
||||
<div class="text">{{ title }}</div>
|
||||
</div>
|
||||
<div class="content">{{ message }}</div>
|
||||
<div class="download-progress">
|
||||
<el-progress :color="colors" :percentage="percentage" :status="progressStatus"></el-progress>
|
||||
</div>
|
||||
<div v-if="winOS" class="progress-content">
|
||||
注:当提示您安全警告时,请点击“运行(R)”,或者点击“更多信息”,选择仍要运行
|
||||
</div>
|
||||
<div class="bom-box">
|
||||
<el-button v-if="progressStatus == 'success'" type="text" @click="openfile">
|
||||
查看文件位置
|
||||
</el-button>
|
||||
<el-button @click="killSys">{{ killButton }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {onUnmounted, ref, watch, Ref} from "vue";
|
||||
import {IpcChannel, invoke, vueListen} from "@/utils/ipcRenderer";
|
||||
|
||||
// const process = window.process;
|
||||
const platform = process.platform;
|
||||
const shell = window.shell;
|
||||
const props = defineProps({
|
||||
modelValue: Boolean,
|
||||
});
|
||||
const emits = defineEmits(["update:modelValue"]);
|
||||
const visible = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
visible.value = props.modelValue;
|
||||
}
|
||||
);
|
||||
const colors: Ref<{ color: string, percentage: number }[] | string> = ref([
|
||||
{color: "#f56c6c", percentage: 20},
|
||||
{color: "#e6a23c", percentage: 40},
|
||||
{color: "#6f7ad3", percentage: 60},
|
||||
{color: "#1989fa", percentage: 80},
|
||||
{color: "#5cb87a", percentage: 100},
|
||||
]);
|
||||
const percentage = ref(0);
|
||||
const progressStatus = ref(null);
|
||||
const winOS = platform.includes("win32");
|
||||
const title = ref("强制更新");
|
||||
const message = ref("由于当前软件版本过低,为了保证您的使用,已激活强制更新");
|
||||
const killButton = ref("退出软件");
|
||||
vueListen(IpcChannel.DownloadProgress, (event, arg) => {
|
||||
percentage.value = Number(arg);
|
||||
});
|
||||
vueListen(IpcChannel.DownloadError, (event, arg) => {
|
||||
if (arg) {
|
||||
progressStatus.value = "exception";
|
||||
percentage.value = 40;
|
||||
colors.value = "#d81e06";
|
||||
}
|
||||
});
|
||||
vueListen(IpcChannel.DownloadDone, (event, age) => {
|
||||
filePath.value = age.filePath;
|
||||
progressStatus.value = "success";
|
||||
message.value = "更新下载完成!";
|
||||
});
|
||||
|
||||
const filePath = ref("");
|
||||
const openfile = () => {
|
||||
shell.showItemInFolder(filePath.value);
|
||||
};
|
||||
const killSys = () => {
|
||||
invoke(IpcChannel.AppClose);
|
||||
};
|
||||
// 当且仅当为开发模式才可以关闭
|
||||
const closeMask = () => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
emits("update:modelValue", false);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.mask-box {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 99999;
|
||||
backdrop-filter: saturate(180%) blur(20px);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
|
||||
.title-box {
|
||||
position: relative;
|
||||
margin: 0 auto 50px;
|
||||
background-color: #ffffff;
|
||||
width: 50%;
|
||||
margin-top: 20vh;
|
||||
color: #333;
|
||||
border-radius: 2px;
|
||||
padding: 48px 0;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
line-height: 25px;
|
||||
justify-content: center;
|
||||
|
||||
.cuowu {
|
||||
color: #999999;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 30px 0px 30px;
|
||||
}
|
||||
|
||||
.bom-box {
|
||||
:deep(.el-button) {
|
||||
padding: 10px 35px;
|
||||
}
|
||||
}
|
||||
|
||||
.download-progress {
|
||||
width: 100%;
|
||||
padding: 20px 50px;
|
||||
}
|
||||
|
||||
.progress-content {
|
||||
color: red;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
20
src/renderer/error.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { App } from 'vue'
|
||||
import { nextTick } from "vue"
|
||||
export const errorHandler = (App: App<Element>) => {
|
||||
App.config.errorHandler = (err, vm, info) => {
|
||||
nextTick(() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.group('%c >>>>>> 错误信息 >>>>>>', 'color:red')
|
||||
console.log(`%c ${info}`, 'color:blue')
|
||||
console.groupEnd()
|
||||
console.group('%c >>>>>> 发生错误的Vue 实例对象 >>>>>>', 'color:green')
|
||||
console.log(vm)
|
||||
console.groupEnd()
|
||||
console.group('%c >>>>>> 发生错误的原因及位置 >>>>>>', 'color:red')
|
||||
console.error(err)
|
||||
console.groupEnd()
|
||||
}
|
||||
}).then(r =>{})
|
||||
}
|
||||
|
||||
}
|
586
src/renderer/fixTypes/auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,586 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
// Generated by unplugin-auto-import
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElForm: typeof import('element-plus/es')['ElForm']
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
|
||||
const ElNotification: typeof import('element-plus/es')['ElNotification']
|
||||
const ElTree: typeof import('element-plus/es')['ElTree']
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedAsync: typeof import('@vueuse/core')['computedAsync']
|
||||
const computedEager: typeof import('@vueuse/core')['computedEager']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const defineStore: typeof import('pinia')['defineStore']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
const mapStores: typeof import('pinia')['mapStores']
|
||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
|
||||
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onLongPress: typeof import('@vueuse/core')['onLongPress']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed']
|
||||
const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refAutoReset: typeof import('@vueuse/core')['refAutoReset']
|
||||
const refDebounced: typeof import('@vueuse/core')['refDebounced']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const refThrottled: typeof import('@vueuse/core')['refThrottled']
|
||||
const refWithControl: typeof import('@vueuse/core')['refWithControl']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const syncRefs: typeof import('@vueuse/core')['syncRefs']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const toValue: typeof import('vue')['toValue']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
|
||||
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
|
||||
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
|
||||
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
|
||||
const useArrayFindLast: typeof import('@vueuse/core')['useArrayFindLast']
|
||||
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
|
||||
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
|
||||
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
|
||||
const useArraySome: typeof import('@vueuse/core')['useArraySome']
|
||||
const useArrayUnique: typeof import('@vueuse/core')['useArrayUnique']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement']
|
||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDateFormat: typeof import('@vueuse/core')['useDateFormat']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useDropZone: typeof import('@vueuse/core')['useDropZone']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGamepad: typeof import('@vueuse/core')['useGamepad']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useImage: typeof import('@vueuse/core')['useImage']
|
||||
const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl']
|
||||
const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSorted: typeof import('@vueuse/core')['useSorted']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStepper: typeof import('@vueuse/core')['useStepper']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
|
||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
||||
const useToString: typeof import('@vueuse/core')['useToString']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchArray: typeof import('@vueuse/core')['watchArray']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchDebounced: typeof import('@vueuse/core')['watchDebounced']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchPausable: typeof import('@vueuse/core')['watchPausable']
|
||||
const watchPostEffect: typeof import('vue')['watchPostEffect']
|
||||
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
|
||||
const watchThrottled: typeof import('@vueuse/core')['watchThrottled']
|
||||
const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
// for type re-export
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
// for vue template auto import
|
||||
import { UnwrapRef } from 'vue'
|
||||
declare module 'vue' {
|
||||
interface GlobalComponents {}
|
||||
interface ComponentCustomProperties {
|
||||
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
|
||||
readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']>
|
||||
readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']>
|
||||
readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
|
||||
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>
|
||||
readonly autoResetRef: UnwrapRef<typeof import('@vueuse/core')['autoResetRef']>
|
||||
readonly computed: UnwrapRef<typeof import('vue')['computed']>
|
||||
readonly computedAsync: UnwrapRef<typeof import('@vueuse/core')['computedAsync']>
|
||||
readonly computedEager: UnwrapRef<typeof import('@vueuse/core')['computedEager']>
|
||||
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
||||
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
||||
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
||||
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
readonly createEventHook: UnwrapRef<typeof import('@vueuse/core')['createEventHook']>
|
||||
readonly createGlobalState: UnwrapRef<typeof import('@vueuse/core')['createGlobalState']>
|
||||
readonly createInjectionState: UnwrapRef<typeof import('@vueuse/core')['createInjectionState']>
|
||||
readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
|
||||
readonly createReactiveFn: UnwrapRef<typeof import('@vueuse/core')['createReactiveFn']>
|
||||
readonly createSharedComposable: UnwrapRef<typeof import('@vueuse/core')['createSharedComposable']>
|
||||
readonly createUnrefFn: UnwrapRef<typeof import('@vueuse/core')['createUnrefFn']>
|
||||
readonly customRef: UnwrapRef<typeof import('vue')['customRef']>
|
||||
readonly debouncedRef: UnwrapRef<typeof import('@vueuse/core')['debouncedRef']>
|
||||
readonly debouncedWatch: UnwrapRef<typeof import('@vueuse/core')['debouncedWatch']>
|
||||
readonly defineAsyncComponent: UnwrapRef<typeof import('vue')['defineAsyncComponent']>
|
||||
readonly defineComponent: UnwrapRef<typeof import('vue')['defineComponent']>
|
||||
readonly defineStore: UnwrapRef<typeof import('pinia')['defineStore']>
|
||||
readonly eagerComputed: UnwrapRef<typeof import('@vueuse/core')['eagerComputed']>
|
||||
readonly effectScope: UnwrapRef<typeof import('vue')['effectScope']>
|
||||
readonly extendRef: UnwrapRef<typeof import('@vueuse/core')['extendRef']>
|
||||
readonly getActivePinia: UnwrapRef<typeof import('pinia')['getActivePinia']>
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
readonly isReadonly: UnwrapRef<typeof import('vue')['isReadonly']>
|
||||
readonly isRef: UnwrapRef<typeof import('vue')['isRef']>
|
||||
readonly makeDestructurable: UnwrapRef<typeof import('@vueuse/core')['makeDestructurable']>
|
||||
readonly mapActions: UnwrapRef<typeof import('pinia')['mapActions']>
|
||||
readonly mapGetters: UnwrapRef<typeof import('pinia')['mapGetters']>
|
||||
readonly mapState: UnwrapRef<typeof import('pinia')['mapState']>
|
||||
readonly mapStores: UnwrapRef<typeof import('pinia')['mapStores']>
|
||||
readonly mapWritableState: UnwrapRef<typeof import('pinia')['mapWritableState']>
|
||||
readonly markRaw: UnwrapRef<typeof import('vue')['markRaw']>
|
||||
readonly nextTick: UnwrapRef<typeof import('vue')['nextTick']>
|
||||
readonly onActivated: UnwrapRef<typeof import('vue')['onActivated']>
|
||||
readonly onBeforeMount: UnwrapRef<typeof import('vue')['onBeforeMount']>
|
||||
readonly onBeforeRouteLeave: UnwrapRef<typeof import('vue-router')['onBeforeRouteLeave']>
|
||||
readonly onBeforeRouteUpdate: UnwrapRef<typeof import('vue-router')['onBeforeRouteUpdate']>
|
||||
readonly onBeforeUnmount: UnwrapRef<typeof import('vue')['onBeforeUnmount']>
|
||||
readonly onBeforeUpdate: UnwrapRef<typeof import('vue')['onBeforeUpdate']>
|
||||
readonly onClickOutside: UnwrapRef<typeof import('@vueuse/core')['onClickOutside']>
|
||||
readonly onDeactivated: UnwrapRef<typeof import('vue')['onDeactivated']>
|
||||
readonly onErrorCaptured: UnwrapRef<typeof import('vue')['onErrorCaptured']>
|
||||
readonly onKeyStroke: UnwrapRef<typeof import('@vueuse/core')['onKeyStroke']>
|
||||
readonly onLongPress: UnwrapRef<typeof import('@vueuse/core')['onLongPress']>
|
||||
readonly onMounted: UnwrapRef<typeof import('vue')['onMounted']>
|
||||
readonly onRenderTracked: UnwrapRef<typeof import('vue')['onRenderTracked']>
|
||||
readonly onRenderTriggered: UnwrapRef<typeof import('vue')['onRenderTriggered']>
|
||||
readonly onScopeDispose: UnwrapRef<typeof import('vue')['onScopeDispose']>
|
||||
readonly onServerPrefetch: UnwrapRef<typeof import('vue')['onServerPrefetch']>
|
||||
readonly onStartTyping: UnwrapRef<typeof import('@vueuse/core')['onStartTyping']>
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
readonly reactive: UnwrapRef<typeof import('vue')['reactive']>
|
||||
readonly reactiveComputed: UnwrapRef<typeof import('@vueuse/core')['reactiveComputed']>
|
||||
readonly reactiveOmit: UnwrapRef<typeof import('@vueuse/core')['reactiveOmit']>
|
||||
readonly reactivePick: UnwrapRef<typeof import('@vueuse/core')['reactivePick']>
|
||||
readonly readonly: UnwrapRef<typeof import('vue')['readonly']>
|
||||
readonly ref: UnwrapRef<typeof import('vue')['ref']>
|
||||
readonly refAutoReset: UnwrapRef<typeof import('@vueuse/core')['refAutoReset']>
|
||||
readonly refDebounced: UnwrapRef<typeof import('@vueuse/core')['refDebounced']>
|
||||
readonly refDefault: UnwrapRef<typeof import('@vueuse/core')['refDefault']>
|
||||
readonly refThrottled: UnwrapRef<typeof import('@vueuse/core')['refThrottled']>
|
||||
readonly refWithControl: UnwrapRef<typeof import('@vueuse/core')['refWithControl']>
|
||||
readonly resolveComponent: UnwrapRef<typeof import('vue')['resolveComponent']>
|
||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
readonly shallowRef: UnwrapRef<typeof import('vue')['shallowRef']>
|
||||
readonly storeToRefs: UnwrapRef<typeof import('pinia')['storeToRefs']>
|
||||
readonly syncRef: UnwrapRef<typeof import('@vueuse/core')['syncRef']>
|
||||
readonly syncRefs: UnwrapRef<typeof import('@vueuse/core')['syncRefs']>
|
||||
readonly templateRef: UnwrapRef<typeof import('@vueuse/core')['templateRef']>
|
||||
readonly throttledRef: UnwrapRef<typeof import('@vueuse/core')['throttledRef']>
|
||||
readonly throttledWatch: UnwrapRef<typeof import('@vueuse/core')['throttledWatch']>
|
||||
readonly toRaw: UnwrapRef<typeof import('vue')['toRaw']>
|
||||
readonly toReactive: UnwrapRef<typeof import('@vueuse/core')['toReactive']>
|
||||
readonly toRef: UnwrapRef<typeof import('vue')['toRef']>
|
||||
readonly toRefs: UnwrapRef<typeof import('vue')['toRefs']>
|
||||
readonly toValue: UnwrapRef<typeof import('vue')['toValue']>
|
||||
readonly triggerRef: UnwrapRef<typeof import('vue')['triggerRef']>
|
||||
readonly tryOnBeforeMount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeMount']>
|
||||
readonly tryOnBeforeUnmount: UnwrapRef<typeof import('@vueuse/core')['tryOnBeforeUnmount']>
|
||||
readonly tryOnMounted: UnwrapRef<typeof import('@vueuse/core')['tryOnMounted']>
|
||||
readonly tryOnScopeDispose: UnwrapRef<typeof import('@vueuse/core')['tryOnScopeDispose']>
|
||||
readonly tryOnUnmounted: UnwrapRef<typeof import('@vueuse/core')['tryOnUnmounted']>
|
||||
readonly unref: UnwrapRef<typeof import('vue')['unref']>
|
||||
readonly unrefElement: UnwrapRef<typeof import('@vueuse/core')['unrefElement']>
|
||||
readonly until: UnwrapRef<typeof import('@vueuse/core')['until']>
|
||||
readonly useActiveElement: UnwrapRef<typeof import('@vueuse/core')['useActiveElement']>
|
||||
readonly useArrayEvery: UnwrapRef<typeof import('@vueuse/core')['useArrayEvery']>
|
||||
readonly useArrayFilter: UnwrapRef<typeof import('@vueuse/core')['useArrayFilter']>
|
||||
readonly useArrayFind: UnwrapRef<typeof import('@vueuse/core')['useArrayFind']>
|
||||
readonly useArrayFindIndex: UnwrapRef<typeof import('@vueuse/core')['useArrayFindIndex']>
|
||||
readonly useArrayFindLast: UnwrapRef<typeof import('@vueuse/core')['useArrayFindLast']>
|
||||
readonly useArrayJoin: UnwrapRef<typeof import('@vueuse/core')['useArrayJoin']>
|
||||
readonly useArrayMap: UnwrapRef<typeof import('@vueuse/core')['useArrayMap']>
|
||||
readonly useArrayReduce: UnwrapRef<typeof import('@vueuse/core')['useArrayReduce']>
|
||||
readonly useArraySome: UnwrapRef<typeof import('@vueuse/core')['useArraySome']>
|
||||
readonly useArrayUnique: UnwrapRef<typeof import('@vueuse/core')['useArrayUnique']>
|
||||
readonly useAsyncQueue: UnwrapRef<typeof import('@vueuse/core')['useAsyncQueue']>
|
||||
readonly useAsyncState: UnwrapRef<typeof import('@vueuse/core')['useAsyncState']>
|
||||
readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
|
||||
readonly useBase64: UnwrapRef<typeof import('@vueuse/core')['useBase64']>
|
||||
readonly useBattery: UnwrapRef<typeof import('@vueuse/core')['useBattery']>
|
||||
readonly useBluetooth: UnwrapRef<typeof import('@vueuse/core')['useBluetooth']>
|
||||
readonly useBreakpoints: UnwrapRef<typeof import('@vueuse/core')['useBreakpoints']>
|
||||
readonly useBroadcastChannel: UnwrapRef<typeof import('@vueuse/core')['useBroadcastChannel']>
|
||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
readonly useCssVar: UnwrapRef<typeof import('@vueuse/core')['useCssVar']>
|
||||
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
|
||||
readonly useCurrentElement: UnwrapRef<typeof import('@vueuse/core')['useCurrentElement']>
|
||||
readonly useCycleList: UnwrapRef<typeof import('@vueuse/core')['useCycleList']>
|
||||
readonly useDark: UnwrapRef<typeof import('@vueuse/core')['useDark']>
|
||||
readonly useDateFormat: UnwrapRef<typeof import('@vueuse/core')['useDateFormat']>
|
||||
readonly useDebounce: UnwrapRef<typeof import('@vueuse/core')['useDebounce']>
|
||||
readonly useDebounceFn: UnwrapRef<typeof import('@vueuse/core')['useDebounceFn']>
|
||||
readonly useDebouncedRefHistory: UnwrapRef<typeof import('@vueuse/core')['useDebouncedRefHistory']>
|
||||
readonly useDeviceMotion: UnwrapRef<typeof import('@vueuse/core')['useDeviceMotion']>
|
||||
readonly useDeviceOrientation: UnwrapRef<typeof import('@vueuse/core')['useDeviceOrientation']>
|
||||
readonly useDevicePixelRatio: UnwrapRef<typeof import('@vueuse/core')['useDevicePixelRatio']>
|
||||
readonly useDevicesList: UnwrapRef<typeof import('@vueuse/core')['useDevicesList']>
|
||||
readonly useDisplayMedia: UnwrapRef<typeof import('@vueuse/core')['useDisplayMedia']>
|
||||
readonly useDocumentVisibility: UnwrapRef<typeof import('@vueuse/core')['useDocumentVisibility']>
|
||||
readonly useDraggable: UnwrapRef<typeof import('@vueuse/core')['useDraggable']>
|
||||
readonly useDropZone: UnwrapRef<typeof import('@vueuse/core')['useDropZone']>
|
||||
readonly useElementBounding: UnwrapRef<typeof import('@vueuse/core')['useElementBounding']>
|
||||
readonly useElementByPoint: UnwrapRef<typeof import('@vueuse/core')['useElementByPoint']>
|
||||
readonly useElementHover: UnwrapRef<typeof import('@vueuse/core')['useElementHover']>
|
||||
readonly useElementSize: UnwrapRef<typeof import('@vueuse/core')['useElementSize']>
|
||||
readonly useElementVisibility: UnwrapRef<typeof import('@vueuse/core')['useElementVisibility']>
|
||||
readonly useEventBus: UnwrapRef<typeof import('@vueuse/core')['useEventBus']>
|
||||
readonly useEventListener: UnwrapRef<typeof import('@vueuse/core')['useEventListener']>
|
||||
readonly useEventSource: UnwrapRef<typeof import('@vueuse/core')['useEventSource']>
|
||||
readonly useEyeDropper: UnwrapRef<typeof import('@vueuse/core')['useEyeDropper']>
|
||||
readonly useFavicon: UnwrapRef<typeof import('@vueuse/core')['useFavicon']>
|
||||
readonly useFetch: UnwrapRef<typeof import('@vueuse/core')['useFetch']>
|
||||
readonly useFileDialog: UnwrapRef<typeof import('@vueuse/core')['useFileDialog']>
|
||||
readonly useFileSystemAccess: UnwrapRef<typeof import('@vueuse/core')['useFileSystemAccess']>
|
||||
readonly useFocus: UnwrapRef<typeof import('@vueuse/core')['useFocus']>
|
||||
readonly useFocusWithin: UnwrapRef<typeof import('@vueuse/core')['useFocusWithin']>
|
||||
readonly useFps: UnwrapRef<typeof import('@vueuse/core')['useFps']>
|
||||
readonly useFullscreen: UnwrapRef<typeof import('@vueuse/core')['useFullscreen']>
|
||||
readonly useGamepad: UnwrapRef<typeof import('@vueuse/core')['useGamepad']>
|
||||
readonly useGeolocation: UnwrapRef<typeof import('@vueuse/core')['useGeolocation']>
|
||||
readonly useI18n: UnwrapRef<typeof import('vue-i18n')['useI18n']>
|
||||
readonly useIdle: UnwrapRef<typeof import('@vueuse/core')['useIdle']>
|
||||
readonly useImage: UnwrapRef<typeof import('@vueuse/core')['useImage']>
|
||||
readonly useInfiniteScroll: UnwrapRef<typeof import('@vueuse/core')['useInfiniteScroll']>
|
||||
readonly useIntersectionObserver: UnwrapRef<typeof import('@vueuse/core')['useIntersectionObserver']>
|
||||
readonly useInterval: UnwrapRef<typeof import('@vueuse/core')['useInterval']>
|
||||
readonly useIntervalFn: UnwrapRef<typeof import('@vueuse/core')['useIntervalFn']>
|
||||
readonly useKeyModifier: UnwrapRef<typeof import('@vueuse/core')['useKeyModifier']>
|
||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
|
||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
||||
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
||||
readonly useMediaQuery: UnwrapRef<typeof import('@vueuse/core')['useMediaQuery']>
|
||||
readonly useMemoize: UnwrapRef<typeof import('@vueuse/core')['useMemoize']>
|
||||
readonly useMemory: UnwrapRef<typeof import('@vueuse/core')['useMemory']>
|
||||
readonly useMounted: UnwrapRef<typeof import('@vueuse/core')['useMounted']>
|
||||
readonly useMouse: UnwrapRef<typeof import('@vueuse/core')['useMouse']>
|
||||
readonly useMouseInElement: UnwrapRef<typeof import('@vueuse/core')['useMouseInElement']>
|
||||
readonly useMousePressed: UnwrapRef<typeof import('@vueuse/core')['useMousePressed']>
|
||||
readonly useMutationObserver: UnwrapRef<typeof import('@vueuse/core')['useMutationObserver']>
|
||||
readonly useNavigatorLanguage: UnwrapRef<typeof import('@vueuse/core')['useNavigatorLanguage']>
|
||||
readonly useNetwork: UnwrapRef<typeof import('@vueuse/core')['useNetwork']>
|
||||
readonly useNow: UnwrapRef<typeof import('@vueuse/core')['useNow']>
|
||||
readonly useObjectUrl: UnwrapRef<typeof import('@vueuse/core')['useObjectUrl']>
|
||||
readonly useOffsetPagination: UnwrapRef<typeof import('@vueuse/core')['useOffsetPagination']>
|
||||
readonly useOnline: UnwrapRef<typeof import('@vueuse/core')['useOnline']>
|
||||
readonly usePageLeave: UnwrapRef<typeof import('@vueuse/core')['usePageLeave']>
|
||||
readonly useParallax: UnwrapRef<typeof import('@vueuse/core')['useParallax']>
|
||||
readonly usePermission: UnwrapRef<typeof import('@vueuse/core')['usePermission']>
|
||||
readonly usePointer: UnwrapRef<typeof import('@vueuse/core')['usePointer']>
|
||||
readonly usePointerLock: UnwrapRef<typeof import('@vueuse/core')['usePointerLock']>
|
||||
readonly usePointerSwipe: UnwrapRef<typeof import('@vueuse/core')['usePointerSwipe']>
|
||||
readonly usePreferredColorScheme: UnwrapRef<typeof import('@vueuse/core')['usePreferredColorScheme']>
|
||||
readonly usePreferredContrast: UnwrapRef<typeof import('@vueuse/core')['usePreferredContrast']>
|
||||
readonly usePreferredDark: UnwrapRef<typeof import('@vueuse/core')['usePreferredDark']>
|
||||
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||
readonly useScreenOrientation: UnwrapRef<typeof import('@vueuse/core')['useScreenOrientation']>
|
||||
readonly useScreenSafeArea: UnwrapRef<typeof import('@vueuse/core')['useScreenSafeArea']>
|
||||
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
||||
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
||||
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
||||
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
||||
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
||||
readonly useSlots: UnwrapRef<typeof import('vue')['useSlots']>
|
||||
readonly useSorted: UnwrapRef<typeof import('@vueuse/core')['useSorted']>
|
||||
readonly useSpeechRecognition: UnwrapRef<typeof import('@vueuse/core')['useSpeechRecognition']>
|
||||
readonly useSpeechSynthesis: UnwrapRef<typeof import('@vueuse/core')['useSpeechSynthesis']>
|
||||
readonly useStepper: UnwrapRef<typeof import('@vueuse/core')['useStepper']>
|
||||
readonly useStorage: UnwrapRef<typeof import('@vueuse/core')['useStorage']>
|
||||
readonly useStorageAsync: UnwrapRef<typeof import('@vueuse/core')['useStorageAsync']>
|
||||
readonly useStyleTag: UnwrapRef<typeof import('@vueuse/core')['useStyleTag']>
|
||||
readonly useSupported: UnwrapRef<typeof import('@vueuse/core')['useSupported']>
|
||||
readonly useSwipe: UnwrapRef<typeof import('@vueuse/core')['useSwipe']>
|
||||
readonly useTemplateRefsList: UnwrapRef<typeof import('@vueuse/core')['useTemplateRefsList']>
|
||||
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
||||
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
||||
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
||||
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
||||
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
||||
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
||||
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
||||
readonly useTimestamp: UnwrapRef<typeof import('@vueuse/core')['useTimestamp']>
|
||||
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
||||
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
||||
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
||||
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
||||
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
||||
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
||||
readonly useUserMedia: UnwrapRef<typeof import('@vueuse/core')['useUserMedia']>
|
||||
readonly useVModel: UnwrapRef<typeof import('@vueuse/core')['useVModel']>
|
||||
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
||||
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
||||
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||
readonly useWebWorker: UnwrapRef<typeof import('@vueuse/core')['useWebWorker']>
|
||||
readonly useWebWorkerFn: UnwrapRef<typeof import('@vueuse/core')['useWebWorkerFn']>
|
||||
readonly useWindowFocus: UnwrapRef<typeof import('@vueuse/core')['useWindowFocus']>
|
||||
readonly useWindowScroll: UnwrapRef<typeof import('@vueuse/core')['useWindowScroll']>
|
||||
readonly useWindowSize: UnwrapRef<typeof import('@vueuse/core')['useWindowSize']>
|
||||
readonly watch: UnwrapRef<typeof import('vue')['watch']>
|
||||
readonly watchArray: UnwrapRef<typeof import('@vueuse/core')['watchArray']>
|
||||
readonly watchAtMost: UnwrapRef<typeof import('@vueuse/core')['watchAtMost']>
|
||||
readonly watchDebounced: UnwrapRef<typeof import('@vueuse/core')['watchDebounced']>
|
||||
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
|
||||
readonly watchIgnorable: UnwrapRef<typeof import('@vueuse/core')['watchIgnorable']>
|
||||
readonly watchOnce: UnwrapRef<typeof import('@vueuse/core')['watchOnce']>
|
||||
readonly watchPausable: UnwrapRef<typeof import('@vueuse/core')['watchPausable']>
|
||||
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>
|
||||
readonly watchSyncEffect: UnwrapRef<typeof import('vue')['watchSyncEffect']>
|
||||
readonly watchThrottled: UnwrapRef<typeof import('@vueuse/core')['watchThrottled']>
|
||||
readonly watchTriggerable: UnwrapRef<typeof import('@vueuse/core')['watchTriggerable']>
|
||||
readonly watchWithFilter: UnwrapRef<typeof import('@vueuse/core')['watchWithFilter']>
|
||||
readonly whenever: UnwrapRef<typeof import('@vueuse/core')['whenever']>
|
||||
}
|
||||
}
|
120
src/renderer/fixTypes/components.d.ts
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AppLink: typeof import('./../themes/default/components/AppLink/index.vue')['default']
|
||||
AppMain: typeof import('./../themes/default/layout/components/AppMain/index.vue')['default']
|
||||
Breadcrumb: typeof import('./../themes/default/components/Breadcrumb/index.vue')['default']
|
||||
CopyButton: typeof import('./../themes/default/components/CopyButton/index.vue')['default']
|
||||
DeptTree: typeof import('./../themes/default/views/system/user/components/dept-tree.vue')['default']
|
||||
Dictionary: typeof import('./../themes/default/components/Dictionary/index.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElBacktop: typeof import('element-plus/es')['ElBacktop']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElProgress: typeof import('element-plus/es')['ElProgress']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElStatistic: typeof import('element-plus/es')['ElStatistic']
|
||||
ElSubmenu: typeof import('element-plus/es')['ElSubmenu']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTable: typeof import('element-plus/es')['ElTable']
|
||||
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElText: typeof import('element-plus/es')['ElText']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
|
||||
ElUpload: typeof import('element-plus/es')['ElUpload']
|
||||
ElWatermark: typeof import('element-plus/es')['ElWatermark']
|
||||
GithubCorner: typeof import('./../themes/default/components/GithubCorner/index.vue')['default']
|
||||
Hamburger: typeof import('./../themes/default/components/Hamburger/index.vue')['default']
|
||||
IconSelect: typeof import('./../themes/default/components/IconSelect/index.vue')['default']
|
||||
IEpArrowDown: typeof import('~icons/ep/arrow-down')['default']
|
||||
IEpArrowUp: typeof import('~icons/ep/arrow-up')['default']
|
||||
IEpBottom: typeof import('~icons/ep/bottom')['default']
|
||||
IEpClose: typeof import('~icons/ep/close')['default']
|
||||
IEpDelete: typeof import('~icons/ep/delete')['default']
|
||||
IEpDownload: typeof import('~icons/ep/download')['default']
|
||||
IEpEdit: typeof import('~icons/ep/edit')['default']
|
||||
IEpPlus: typeof import('~icons/ep/plus')['default']
|
||||
IEpPosition: typeof import('~icons/ep/position')['default']
|
||||
IEpQuestionFilled: typeof import('~icons/ep/question-filled')['default']
|
||||
IEpRefresh: typeof import('~icons/ep/refresh')['default']
|
||||
IEpRefreshLeft: typeof import('~icons/ep/refresh-left')['default']
|
||||
IEpSearch: typeof import('~icons/ep/search')['default']
|
||||
IEpSwitch: typeof import('~icons/ep/switch')['default']
|
||||
IEpTop: typeof import('~icons/ep/top')['default']
|
||||
IEpUpload: typeof import('~icons/ep/upload')['default']
|
||||
LangSelect: typeof import('./../themes/default/components/LangSelect/index.vue')['default']
|
||||
LayoutSelect: typeof import('./../themes/default/layout/components/Settings/components/LayoutSelect.vue')['default']
|
||||
MultiUpload: typeof import('./../themes/default/components/Upload/MultiUpload.vue')['default']
|
||||
NavBar: typeof import('./../themes/default/layout/components/NavBar/index.vue')['default']
|
||||
NavbarLeft: typeof import('./../themes/default/layout/components/NavBar/components/NavbarLeft.vue')['default']
|
||||
NavbarRight: typeof import('./../themes/default/layout/components/NavBar/components/NavbarRight.vue')['default']
|
||||
PageContent: typeof import('./../themes/default/components/CURD/PageContent.vue')['default']
|
||||
PageForm: typeof import('./../themes/default/components/CURD/PageForm.vue')['default']
|
||||
PageModal: typeof import('./../themes/default/components/CURD/PageModal.vue')['default']
|
||||
PageSearch: typeof import('./../themes/default/components/CURD/PageSearch.vue')['default']
|
||||
Pagination: typeof import('./../themes/default/components/Pagination/index.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Settings: typeof import('./../themes/default/layout/components/Settings/index.vue')['default']
|
||||
Sidebar: typeof import('./../themes/default/layout/components/Sidebar/index.vue')['default']
|
||||
SidebarLogo: typeof import('./../themes/default/layout/components/Sidebar/components/SidebarLogo.vue')['default']
|
||||
SidebarMenu: typeof import('./../themes/default/layout/components/Sidebar/components/SidebarMenu.vue')['default']
|
||||
SidebarMenuItem: typeof import('./../themes/default/layout/components/Sidebar/components/SidebarMenuItem.vue')['default']
|
||||
SidebarMenuItemTitle: typeof import('./../themes/default/layout/components/Sidebar/components/SidebarMenuItemTitle.vue')['default']
|
||||
SidebarMixTopMenu: typeof import('./../themes/default/layout/components/Sidebar/components/SidebarMixTopMenu.vue')['default']
|
||||
SingleUpload: typeof import('./../themes/default/components/Upload/SingleUpload.vue')['default']
|
||||
SizeSelect: typeof import('./../themes/default/components/SizeSelect/index.vue')['default']
|
||||
SvgIcon: typeof import('./../themes/default/components/SvgIcon/index.vue')['default']
|
||||
TableSelect: typeof import('./../themes/default/components/TableSelect/index.vue')['default']
|
||||
TagsView: typeof import('./../themes/default/layout/components/TagsView/index.vue')['default']
|
||||
ThemeColorPicker: typeof import('./../themes/default/layout/components/Settings/components/ThemeColorPicker.vue')['default']
|
||||
UserImport: typeof import('./../themes/default/views/system/user/components/user-import.vue')['default']
|
||||
VisitTrend: typeof import('./../themes/default/views/dashboard/components/VisitTrend.vue')['default']
|
||||
WangEditor: typeof import('./../themes/default/components/WangEditor/index.vue')['default']
|
||||
}
|
||||
export interface ComponentCustomProperties {
|
||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||
}
|
||||
}
|
32
src/renderer/i18n/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { createI18n } from "vue-i18n"
|
||||
|
||||
export function loadLanguages() {
|
||||
const context: any = import.meta.glob("./languages/*.ts", { eager: true });
|
||||
|
||||
const languages: any = {};
|
||||
|
||||
let langs = Object.keys(context);
|
||||
for (let key of langs) {
|
||||
if (key === "./index.ts") return;
|
||||
let lang = context[key].lang;
|
||||
let name = key.replace(/(\.\/languages\/|\.ts)/g, '');
|
||||
languages[name] = lang
|
||||
}
|
||||
|
||||
return languages
|
||||
}
|
||||
|
||||
export function i18nt(key: string) {
|
||||
return i18n.global.d(key);
|
||||
}
|
||||
|
||||
export const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'zh-cn',
|
||||
fallbackLocale: 'zh-cn',
|
||||
messages: loadLanguages()
|
||||
})
|
||||
|
||||
export function setLanguage(locale: string) {
|
||||
i18n.global.locale.value = locale
|
||||
}
|
59
src/renderer/i18n/languages/en.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import enLocale from "element-plus/dist/locale/en.min"
|
||||
|
||||
export const lang = {
|
||||
welcome: "Welcome use the framework",
|
||||
buttonTips: "You can click buttons to experience",
|
||||
waitDataLoading: "Wait data loading",
|
||||
about: {
|
||||
system: "About system",
|
||||
language: "language:",
|
||||
languageValue: "English",
|
||||
currentPagePath: "current page path:",
|
||||
currentPageName: "current page name:",
|
||||
vueVersion: "Vue version:",
|
||||
electronVersion: "Electron version:",
|
||||
nodeVersion: "Node version:",
|
||||
systemPlatform: "system platform:",
|
||||
systemVersion: "system version:",
|
||||
systemArch: "system arch:"
|
||||
},
|
||||
buttons: {
|
||||
console: "Console",
|
||||
checkUpdate: "Check update",
|
||||
checkUpdate2: "Check update(plan 2)",
|
||||
checkUpdateInc: "Check update(increment)",
|
||||
startServer: "Start server",
|
||||
stopServer: "Stop server",
|
||||
viewMessage: "view message",
|
||||
openNewWindow: "Open new window",
|
||||
simulatedCrash: "Simulated crash",
|
||||
changeLanguage: "切换语言",
|
||||
ForcedUpdate: "Forced Update Mode",
|
||||
printDemo: "Print demo",
|
||||
incrementalUpdateTest: "Incremental Update test",
|
||||
openPreloadWindow: "preload.js test",
|
||||
browser: "Browser",
|
||||
showOnMyComputer: "Show on my computer",
|
||||
hideOnMyComputer: "hide on my computer",
|
||||
},
|
||||
print: {
|
||||
print: 'Print',
|
||||
silentPrinting: 'Silent printing',
|
||||
backgroundColor: 'Background color',
|
||||
use: 'Use ',
|
||||
unuse: 'Unuse ',
|
||||
notUse: 'Unuse ',
|
||||
tips: 'It is recommended to use printed PDF test (paper saving), and then use the real situation after success',
|
||||
blackAndWhite: 'Black and white',
|
||||
colorful: 'Colorful',
|
||||
margin: 'Margin',
|
||||
top: 'Top ',
|
||||
bottom: 'Bottom ',
|
||||
right: 'Right ',
|
||||
left: 'Left ',
|
||||
},
|
||||
browser: {
|
||||
searchBarPlaceholder: 'Search in Bing or enter a website address',
|
||||
},
|
||||
el: enLocale
|
||||
}
|
66
src/renderer/i18n/languages/zh-cn.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import zhLocale from "element-plus/dist/locale/zh-cn.min";
|
||||
|
||||
export const lang = {
|
||||
welcome: "欢迎进入本框架",
|
||||
buttonTips: "您可以点击的按钮测试功能",
|
||||
waitDataLoading: "等待数据读取",
|
||||
home: {
|
||||
openPreloadWindowError: {
|
||||
title: "提示",
|
||||
content: "请移步项目的strict分支",
|
||||
confirm: "确定",
|
||||
},
|
||||
},
|
||||
about: {
|
||||
system: "关于系统",
|
||||
language: "语言:",
|
||||
languageValue: "中文简体",
|
||||
currentPagePath: "当前页面路径:",
|
||||
currentPageName: "当前页面名称:",
|
||||
vueVersion: "Vue版本:",
|
||||
electronVersion: "Electron版本:",
|
||||
nodeVersion: "Node版本:",
|
||||
systemPlatform: "系统平台:",
|
||||
systemVersion: "系统版本:",
|
||||
systemArch: "系统位数:",
|
||||
},
|
||||
buttons: {
|
||||
console: "控制台打印",
|
||||
checkUpdate: "检查更新",
|
||||
checkUpdate2: "检查更新(第二种方法)",
|
||||
checkUpdateInc: "检查更新(增量更新)",
|
||||
startServer: "启动内置服务端",
|
||||
stopServer: "关闭内置服务端",
|
||||
viewMessage: "查看消息",
|
||||
openNewWindow: "打开新窗口",
|
||||
simulatedCrash: "模拟崩溃",
|
||||
changeLanguage: "Change language",
|
||||
ForcedUpdate: "强制更新模式",
|
||||
printDemo: "打印例子",
|
||||
incrementalUpdateTest: "增量更新测试",
|
||||
openPreloadWindow: "preload.js测试",
|
||||
browser: "浏览器",
|
||||
showOnMyComputer: "在我的电脑显示",
|
||||
hideOnMyComputer: "在我的电脑隐藏",
|
||||
},
|
||||
print: {
|
||||
print: "打印",
|
||||
silentPrinting: "静默打印",
|
||||
backgroundColor: "背景色",
|
||||
use: "使用",
|
||||
unuse: "不使用",
|
||||
notUse: "非",
|
||||
tips: "建议使用打印PDF测试(省纸),成功后再使用真实情况",
|
||||
blackAndWhite: "黑白",
|
||||
colorful: "彩色",
|
||||
margin: "边距",
|
||||
top: "上",
|
||||
bottom: "下",
|
||||
right: "右",
|
||||
left: "左",
|
||||
},
|
||||
browser: {
|
||||
searchBarPlaceholder: "在Bing中搜索,或输入一个网址",
|
||||
},
|
||||
el: zhLocale,
|
||||
};
|
41
src/renderer/index.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link href="/favicon.ico" rel="icon"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Hi-Sass-Frame</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="myApp"></div>
|
||||
<script>
|
||||
localStorage.setItem("_LOCAL_THEME_", '_single')
|
||||
const _LOCAL_THEME_ = localStorage.getItem("_LOCAL_THEME_"); //notheme,default,……
|
||||
let jsFile;
|
||||
switch (_LOCAL_THEME_) {
|
||||
case "notheme":
|
||||
jsFile = "./main-notheme.ts";
|
||||
break;
|
||||
case "_single":
|
||||
jsFile = "./main-single-theme.ts";
|
||||
break;
|
||||
case "element":
|
||||
default:
|
||||
jsFile = "./main-multi-themes.ts";
|
||||
break;
|
||||
}
|
||||
|
||||
function importModule() {
|
||||
var script = document.createElement('script');
|
||||
script.type = 'module';
|
||||
script.src = jsFile; // 模块文件路径
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
|
||||
importModule();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
52
src/renderer/interceptor.ts
Normal file
@ -0,0 +1,52 @@
|
||||
// 页面路由阻断器
|
||||
import router from './router'
|
||||
import Performance from '@/tools/performance'
|
||||
|
||||
import {useStorePermission} from "@store/permission"
|
||||
import {useStoreUser} from "@store/user"
|
||||
|
||||
/**
|
||||
* 路由拦截模式,白名单whiteList 或者 登陆页needLogin 两种模式,后者目录未配置
|
||||
* 建议采用白名单方式,可以更方便地应对多种角色权限的配置
|
||||
*/
|
||||
// const whiteList = ["/", '/login', '/landing', '/dashboard'] // 不重定向白名单
|
||||
const whiteList = ["/", '/login'] // 不重定向白名单
|
||||
// const needLogin = ['/LandingPage']; //需要登陆的页面,未写相关功能,暂不应用
|
||||
|
||||
const useInterceptor = function () {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// console.log("目标路由信息", to, to.meta);//to.meta取自路由设置中的meta信息
|
||||
const {checkRoute} = useStorePermission();
|
||||
const {hasLogin, roles, logout} = useStoreUser();
|
||||
let end = Performance.startExecute(`${from.path} => ${to.path} 路由耗时`); /// 路由性能监控
|
||||
|
||||
if (whiteList.includes(to.path)) {
|
||||
// 在白名单内,直接放行
|
||||
next();
|
||||
} else {
|
||||
// console.log('用户登陆状态:', hasLogin);
|
||||
let _roles = ['guest'];
|
||||
if (hasLogin && to.path === "/login") {
|
||||
// 如果已登录,跳转到首页
|
||||
next({path: "/"});
|
||||
} else {
|
||||
if (hasLogin) {
|
||||
_roles = roles;
|
||||
}
|
||||
console.log('用户角色:', _roles);
|
||||
if (checkRoute(_roles, to)) {
|
||||
next();
|
||||
} else {
|
||||
next({path: '/login'});
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
end();
|
||||
}, 0)
|
||||
})
|
||||
router.afterEach(() => {
|
||||
})
|
||||
}
|
||||
|
||||
export default useInterceptor;
|
24
src/renderer/main-multi-themes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import {createApp} from 'vue'
|
||||
import App from '@theme/App-theme.vue';
|
||||
import setupPlugins from "@theme/plugins";
|
||||
|
||||
// 本地SVG图标
|
||||
import "virtual:svg-icons-register";
|
||||
|
||||
// 样式
|
||||
import "element-plus/theme-chalk/dark/css-vars.css";
|
||||
import "@theme/styles/index.scss";
|
||||
import "uno.css";
|
||||
import "animate.css";
|
||||
|
||||
import {errorHandler} from './error'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(setupPlugins);
|
||||
errorHandler(app)
|
||||
// 执行路由拦截,放在挂载之前的方式(按路由权限标识)
|
||||
import useInterceptor from './interceptor';
|
||||
|
||||
useInterceptor();
|
||||
|
||||
app.mount("#myApp")
|
30
src/renderer/main-notheme.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {createApp} from 'vue'
|
||||
import {createPinia} from 'pinia'
|
||||
|
||||
import './styles/index.scss'
|
||||
import "uno.css";
|
||||
|
||||
import useInterceptor from './interceptor';
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import {errorHandler} from './error'
|
||||
|
||||
import {i18n} from "./i18n"
|
||||
|
||||
import TitleBar from "./components/common/TitleBar.vue"
|
||||
|
||||
const app = createApp(App)
|
||||
const store = createPinia()
|
||||
|
||||
app.use(store)
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
errorHandler(app)
|
||||
|
||||
// 全局引入 TitleBar 组件
|
||||
app.component("TitleBar", TitleBar);
|
||||
|
||||
// 执行路由拦截,放在挂载之前的方式(按路由权限标识)
|
||||
useInterceptor();
|
||||
app.mount("#myApp")
|
35
src/renderer/main-single-theme.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 简化的内建主题模式,适用于主体功能一致、部分界面定制的情况
|
||||
*/
|
||||
|
||||
import {createApp} from 'vue'
|
||||
import {createPinia} from 'pinia'
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import "uno.css";
|
||||
import './styles/index.scss'
|
||||
|
||||
import useInterceptor from './interceptor';
|
||||
|
||||
import App from '@/themes/_single/App.vue';
|
||||
import router from './router'
|
||||
import {errorHandler} from './error'
|
||||
|
||||
import {i18n} from "./i18n"
|
||||
|
||||
import TitleBar from "./components/common/TitleBar.vue"
|
||||
|
||||
const app = createApp(App)
|
||||
const store = createPinia()
|
||||
app.use(ElementPlus)
|
||||
app.use(store)
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
errorHandler(app)
|
||||
|
||||
// 全局引入 TitleBar 组件
|
||||
app.component("TitleBar", TitleBar);
|
||||
|
||||
// 执行路由拦截,放在挂载之前的方式(按路由权限标识)
|
||||
useInterceptor();
|
||||
app.mount("#myApp")
|