基础框架完成

This commit is contained in:
fm453 2024-09-08 23:46:15 +08:00
parent 967cfa561b
commit e91ff65c22
343 changed files with 70230 additions and 30 deletions

14
.editorconfig Normal file
View 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
View 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();
}

View 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();

View 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()

View 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)
}

View 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 });
}
},
};
};

View 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",
],
}

View 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
View 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;

View 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
View File

@ -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
View 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
View 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服务器模拟依赖于expressts中还需要安装@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.jsontypes中添加“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
View 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
View File

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

View File

@ -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/)
![GitHub Repo stars](https://img.shields.io/github/stars/umbrella22/electron-vite-template)
[![vue](https://img.shields.io/badge/vue-3.4.21-brightgreen.svg)](https://github.com/vuejs/vue-next)
[![vite](https://img.shields.io/badge/vite-5.2.7-brightgreen.svg)](https://github.com/vitejs/vite)
[![element-ui](https://img.shields.io/badge/element-plus-brightgreen.svg)](https://www.npmjs.org/package/element-plus)
[![electron](https://img.shields.io/badge/electron-29.1.6-brightgreen.svg)](https://github.com/electron/electron)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](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)
[![Build TEST](https://github.com/umbrella22/electron-vite-template/actions/workflows/Build.yml/badge.svg)](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 codefrom github to synchronizeplease visit github for PR
- **Welcome to Issues and PR**

37
README_ZH.md Normal file
View File

@ -0,0 +1,37 @@
# Electron-Vite-template
![GitHub Repo stars](https://img.shields.io/github/stars/umbrella22/electron-vite-template)
[![vue](https://img.shields.io/badge/vue-3.2.23-brightgreen.svg)](https://github.com/vuejs/vue-next)
[![vite](https://img.shields.io/badge/vite-3.0.3-brightgreen.svg)](https://github.com/vitejs/vite)
[![element-ui](https://img.shields.io/badge/element-plus-brightgreen.svg)](https://www.npmjs.org/package/element-plus)
[![electron](https://img.shields.io/badge/electron-19.0.4-brightgreen.svg)](https://github.com/electron/electron)
[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

23
fixTypes/electron-env.d.ts vendored Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

176
package.json Normal file
View 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

File diff suppressed because it is too large Load Diff

7
src/main/config/alias.ts Normal file
View File

@ -0,0 +1,7 @@
/**
* electron的主进程默认无法使用路径别名 TBD
*/
export const alias = {
"@": "src/renderer",
"@src": "src",
};

213
src/main/config/channels.ts Normal file
View 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
View 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
}

View File

@ -0,0 +1,5 @@
/**
*
*/
const domains = ["http://127.0.0.1", "https://127.0.0.1", "http://localhost"];
export default domains;

View File

@ -0,0 +1,11 @@
import {hotPublishUrl, hotPublishConfigName} from "./const";
interface hotPublish {
url: string;
configName: string;
}
export const hotPublishConfig: hotPublish = {
url: hotPublishUrl,
configName: hotPublishConfigName,
};

View 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];
}

View 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",
},
};

View 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,
// });
// }

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

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

View 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 ""
}
},
}
}

View 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("更新出错");
}
}
}
}

View 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,
}

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

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

View 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,
};
};

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

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

View 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;

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

View 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

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

View 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

View 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;

View 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)
})
}

View 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)
})
}

View 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]);
}
}

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View 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

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

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

View 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;

View 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;

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

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

View 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
View 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
View 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
View 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']
}
}

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

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

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

View 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;

View 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")

View 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")

View 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")

Some files were not shown because too many files have changed in this diff Show More