├── .babelrc ├── .electron-vue ├── build.js ├── dev-runner.js ├── hot-updater.js ├── utils.js ├── webpack.main.config.js └── webpack.renderer.config.js ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── build-test.yml ├── .gitignore ├── .postcssrc.js ├── README_ZH.md ├── build ├── builder-debug.yml ├── icons │ ├── 256x256.png │ ├── icon.icns │ └── icon.ico ├── latest.yml └── smy Setup 0.0.1.exe.blockmap ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── lib └── updater.html ├── package-lock.json ├── package.json ├── server └── index.js ├── src ├── index.ejs ├── main │ ├── config │ │ ├── DisableButton.js │ │ ├── StaticPath.js │ │ ├── hotPublish.js │ │ └── menu.js │ ├── index.js │ └── services │ │ ├── HotUpdater.js │ │ ├── checkupdate.js │ │ ├── downloadFile.js │ │ ├── ipcMain.js │ │ ├── mqttUtil.js │ │ ├── preload.js │ │ ├── static │ │ └── icon.png │ │ └── windowManager.js └── renderer │ ├── App.vue │ ├── api │ └── login.js │ ├── assets │ ├── .gitkeep │ ├── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png │ ├── logo.png │ └── user.png │ ├── components │ ├── Breadcrumb │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── LandingPage.vue │ ├── LandingPage │ │ └── SystemInformation.vue │ ├── Pagination │ │ └── index.vue │ ├── ScrollBar │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── Tinymce │ │ ├── components │ │ │ └── EditorImage.vue │ │ ├── dynamicLoadScript.js │ │ ├── index.vue │ │ ├── plugins.js │ │ └── toolbar.js │ └── title │ │ └── index.vue │ ├── error.js │ ├── i18n │ ├── index.js │ └── languages │ │ ├── en.js │ │ └── zh-CN.js │ ├── icons │ ├── index.js │ └── svg │ │ ├── close.svg │ │ ├── electron-logo.svg │ │ ├── example.svg │ │ ├── eye-open.svg │ │ ├── eye.svg │ │ ├── form.svg │ │ ├── logo.svg │ │ ├── mini.svg │ │ ├── mix.svg │ │ ├── nested.svg │ │ ├── password.svg │ │ ├── reduction.svg │ │ ├── table.svg │ │ ├── tree.svg │ │ └── user.svg │ ├── layout │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Sidebar │ │ │ ├── Logo.vue │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ └── index.js │ ├── index.vue │ └── mixin │ │ └── ResizeHandler.js │ ├── main.js │ ├── permission.js │ ├── router │ ├── constantRouterMap.js │ └── index.js │ ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ ├── index.js │ │ ├── permission.js │ │ ├── template.js │ │ └── user.js │ ├── styles │ ├── custom-container.scss │ ├── custom-title.scss │ ├── dark-mode.scss │ ├── element-ui.scss │ ├── index.scss │ ├── mixin.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss │ ├── tools │ ├── notification.js │ ├── performance.js │ └── timer.js │ ├── utils │ ├── request.js │ ├── scroll-to.js │ └── validate.js │ ├── views │ ├── 404.vue │ ├── device │ │ └── index.vue │ ├── form │ │ └── index.vue │ ├── login │ │ └── index.vue │ ├── permission │ │ └── index.vue │ └── table │ │ └── index.vue │ └── vue.config.js ├── static ├── .gitkeep ├── icon.png ├── jquery.js ├── loader.html └── pay.html └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "comments": false, 3 | "env": { 4 | "renderer": { 5 | "presets": [ 6 | [ 7 | "@babel/preset-env", 8 | { 9 | "modules": false, 10 | "useBuiltIns": "entry", 11 | "corejs": 3 12 | } 13 | ], 14 | [ 15 | "@vue/babel-preset-jsx" 16 | ] 17 | ], 18 | "plugins": [ 19 | "@babel/plugin-syntax-dynamic-import", 20 | "@babel/plugin-syntax-import-meta", 21 | "@babel/plugin-proposal-class-properties", 22 | "@babel/plugin-proposal-json-strings", 23 | [ 24 | "@babel/plugin-proposal-decorators", 25 | { 26 | "legacy": true 27 | } 28 | ], 29 | "@babel/plugin-proposal-function-sent", 30 | "@babel/plugin-proposal-export-namespace-from", 31 | "@babel/plugin-proposal-numeric-separator", 32 | "@babel/plugin-proposal-throw-expressions", 33 | "@babel/plugin-proposal-export-default-from", 34 | "@babel/plugin-proposal-logical-assignment-operators", 35 | "@babel/plugin-proposal-optional-chaining", 36 | [ 37 | "@babel/plugin-proposal-pipeline-operator", 38 | { 39 | "proposal": "minimal" 40 | } 41 | ], 42 | "@babel/plugin-proposal-nullish-coalescing-operator", 43 | "@babel/plugin-proposal-do-expressions", 44 | "@babel/plugin-proposal-function-bind" 45 | ] 46 | }, 47 | "web": { 48 | "presets": [ 49 | [ 50 | "@babel/preset-env", 51 | { 52 | "modules": false, 53 | "useBuiltIns": "entry", 54 | "corejs": 3 55 | } 56 | ], 57 | [ 58 | "@vue/babel-preset-jsx" 59 | ] 60 | ], 61 | "plugins": [ 62 | "@babel/plugin-syntax-dynamic-import", 63 | "@babel/plugin-syntax-import-meta", 64 | "@babel/plugin-proposal-class-properties", 65 | "@babel/plugin-proposal-json-strings", 66 | [ 67 | "@babel/plugin-proposal-decorators", 68 | { 69 | "legacy": true 70 | } 71 | ], 72 | "@babel/plugin-proposal-function-sent", 73 | "@babel/plugin-proposal-export-namespace-from", 74 | "@babel/plugin-proposal-numeric-separator", 75 | "@babel/plugin-proposal-throw-expressions", 76 | "@babel/plugin-proposal-export-default-from", 77 | "@babel/plugin-proposal-logical-assignment-operators", 78 | "@babel/plugin-proposal-optional-chaining", 79 | [ 80 | "@babel/plugin-proposal-pipeline-operator", 81 | { 82 | "proposal": "minimal" 83 | } 84 | ], 85 | "@babel/plugin-proposal-nullish-coalescing-operator", 86 | "@babel/plugin-proposal-do-expressions", 87 | "@babel/plugin-proposal-function-bind" 88 | ] 89 | } 90 | }, 91 | "plugins": [ 92 | "@babel/transform-runtime", 93 | "@babel/plugin-syntax-dynamic-import" 94 | ] 95 | } -------------------------------------------------------------------------------- /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | process.env.NODE_ENV = 'production' 3 | const { say } = require('cfonts') 4 | const chalk = require('chalk') 5 | const del = require('del') 6 | const webpack = require('webpack') 7 | const Multispinner = require('multispinner') 8 | 9 | 10 | const mainConfig = require('./webpack.main.config') 11 | const rendererConfig = require('./webpack.renderer.config') 12 | 13 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 14 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 15 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 16 | const isCI = process.env.CI || false 17 | 18 | if (process.env.BUILD_TARGET === 'web') web() 19 | else build() 20 | 21 | function clean() { 22 | del.sync(['dist/electron/*', 'build/*', '!build/icons', '!build/lib', '!build/lib/electron-build.*', '!build/icons/icon.*']) 23 | console.log(`\n${doneLog}clear done`) 24 | if (process.env.BUILD_TARGET === 'onlyClean') process.exit() 25 | } 26 | 27 | function build() { 28 | greeting() 29 | if (process.env.BUILD_TARGET === 'clean' || process.env.BUILD_TARGET === 'onlyClean') clean() 30 | 31 | const tasks = ['main', 'renderer'] 32 | const m = new Multispinner(tasks, { 33 | preText: 'building', 34 | postText: 'process' 35 | }) 36 | 37 | let results = '' 38 | 39 | m.on('success', () => { 40 | process.stdout.write('\x1B[2J\x1B[0f') 41 | console.log(`\n\n${results}`) 42 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 43 | process.exit() 44 | }) 45 | 46 | pack(mainConfig).then(result => { 47 | results += result + '\n\n' 48 | m.success('main') 49 | }).catch(err => { 50 | m.error('main') 51 | console.log(`\n ${errorLog}failed to build main process`) 52 | console.error(`\n${err}\n`) 53 | process.exit(1) 54 | }) 55 | 56 | pack(rendererConfig).then(result => { 57 | results += result + '\n\n' 58 | m.success('renderer') 59 | }).catch(err => { 60 | m.error('renderer') 61 | console.log(`\n ${errorLog}failed to build renderer process`) 62 | console.error(`\n${err}\n`) 63 | process.exit(1) 64 | }) 65 | } 66 | 67 | function pack(config) { 68 | return new Promise((resolve, reject) => { 69 | config.mode = 'production' 70 | webpack(config, (err, stats) => { 71 | if (err) reject(err.stack || err) 72 | else if (stats.hasErrors()) { 73 | let err = '' 74 | 75 | stats.toString({ 76 | chunks: false, 77 | colors: true 78 | }) 79 | .split(/\r?\n/) 80 | .forEach(line => { 81 | err += ` ${line}\n` 82 | }) 83 | 84 | reject(err) 85 | } else { 86 | resolve(stats.toString({ 87 | chunks: false, 88 | colors: true 89 | })) 90 | } 91 | }) 92 | }) 93 | } 94 | 95 | function web() { 96 | del.sync(['dist/web/*', '!.gitkeep']) 97 | rendererConfig.mode = 'production' 98 | webpack(rendererConfig, (err, stats) => { 99 | if (err || stats.hasErrors()) console.log(err) 100 | 101 | console.log(stats.toString({ 102 | chunks: false, 103 | colors: true 104 | })) 105 | 106 | process.exit() 107 | }) 108 | } 109 | 110 | function greeting() { 111 | const cols = process.stdout.columns 112 | let text = '' 113 | 114 | if (cols > 85) text = `let's-build` 115 | else if (cols > 60) text = `let's-|build` 116 | else text = false 117 | 118 | if (text && !isCI) { 119 | say(text, { 120 | colors: ['yellow'], 121 | font: 'simple3d', 122 | space: false 123 | }) 124 | } else console.log(chalk.yellow.bold(`\n let's-build`)) 125 | console.log() 126 | } -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'development' 4 | 5 | const chalk = require('chalk') 6 | const electron = require('electron') 7 | const path = require('path') 8 | const { say } = require('cfonts') 9 | const { spawn } = require('child_process') 10 | const config = require('../config') 11 | const webpack = require('webpack') 12 | const WebpackDevServer = require('webpack-dev-server') 13 | const Portfinder = require("portfinder") 14 | 15 | const mainConfig = require('./webpack.main.config') 16 | const rendererConfig = require('./webpack.renderer.config') 17 | 18 | let electronProcess = null 19 | let manualRestart = false 20 | 21 | function logStats(proc, data) { 22 | let log = '' 23 | 24 | log += chalk.yellow.bold(`┏ ${proc} ${config.dev.chineseLog ? '编译过程' : 'Process'} ${new Array((19 - proc.length) + 1).join('-')}`) 25 | log += '\n\n' 26 | 27 | if (typeof data === 'object') { 28 | data.toString({ 29 | colors: true, 30 | chunks: false 31 | }).split(/\r?\n/).forEach(line => { 32 | log += ' ' + line + '\n' 33 | }) 34 | } else { 35 | log += ` ${data}\n` 36 | } 37 | 38 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 39 | console.log(log) 40 | } 41 | 42 | function removeJunk(chunk) { 43 | if (config.dev.removeElectronJunk) { 44 | // Example: 2018-08-10 22:48:42.866 Electron[90311:4883863] *** WARNING: Textured window 45 | if (/\d+-\d+-\d+ \d+:\d+:\d+\.\d+ Electron(?: Helper)?\[\d+:\d+] /.test(chunk)) { 46 | return false; 47 | } 48 | 49 | // 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) 50 | if (/\[\d+:\d+\/|\d+\.\d+:ERROR:CONSOLE\(\d+\)\]/.test(chunk)) { 51 | return false; 52 | } 53 | 54 | // Example: ALSA lib confmisc.c:767:(parse_card) cannot find card '0' 55 | if (/ALSA lib [a-z]+\.c:\d+:\([a-z_]+\)/.test(chunk)) { 56 | return false; 57 | } 58 | } 59 | 60 | 61 | return chunk; 62 | } 63 | 64 | function startRenderer() { 65 | return new Promise((resolve, reject) => { 66 | rendererConfig.mode = 'development' 67 | Portfinder.basePort = config.dev.port || 9080 68 | Portfinder.getPort((err, port) => { 69 | if (err) { 70 | reject("PortError:" + err) 71 | } else { 72 | const compiler = webpack(rendererConfig) 73 | 74 | compiler.hooks.done.tap('done', stats => { 75 | logStats('Renderer', stats) 76 | }) 77 | 78 | const server = new WebpackDevServer( 79 | { 80 | port, 81 | static: { 82 | directory: path.join(__dirname, '..', 'static'), 83 | publicPath: '/static/', 84 | } 85 | }, 86 | compiler 87 | ) 88 | 89 | process.env.PORT = port 90 | server.start().then(() => { 91 | resolve() 92 | }) 93 | 94 | } 95 | }) 96 | 97 | }) 98 | } 99 | 100 | function startMain() { 101 | return new Promise((resolve) => { 102 | mainConfig.mode = 'development' 103 | const compiler = webpack(mainConfig) 104 | 105 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => { 106 | logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, chalk.white.bold(`${config.dev.chineseLog ? '正在处理资源文件...' : 'compiling...'}`)) 107 | done() 108 | }) 109 | 110 | compiler.watch({}, (err, stats) => { 111 | if (err) { 112 | console.log(err) 113 | return 114 | } 115 | 116 | logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, stats) 117 | 118 | if (electronProcess && electronProcess.kill) { 119 | manualRestart = true 120 | process.kill(electronProcess.pid) 121 | electronProcess = null 122 | startElectron() 123 | 124 | setTimeout(() => { 125 | manualRestart = false 126 | }, 5000) 127 | } 128 | 129 | resolve() 130 | }) 131 | }) 132 | } 133 | 134 | function startElectron() { 135 | var args = [ 136 | '--inspect=5858', 137 | path.join(__dirname, '../dist/electron/main.js') 138 | ] 139 | 140 | // detect yarn or npm and process commandline args accordingly 141 | if (process.env.npm_execpath.endsWith('yarn.js')) { 142 | args = args.concat(process.argv.slice(3)) 143 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { 144 | args = args.concat(process.argv.slice(2)) 145 | } 146 | 147 | electronProcess = spawn(electron, args) 148 | 149 | electronProcess.stdout.on('data', data => { 150 | electronLog(removeJunk(data), 'blue') 151 | }) 152 | electronProcess.stderr.on('data', data => { 153 | electronLog(removeJunk(data), 'red') 154 | }) 155 | 156 | electronProcess.on('close', () => { 157 | if (!manualRestart) process.exit() 158 | }) 159 | } 160 | 161 | function electronLog(data, color) { 162 | if (data) { 163 | let log = '' 164 | data = data.toString().split(/\r?\n/) 165 | data.forEach(line => { 166 | log += ` ${line}\n` 167 | }) 168 | console.log( 169 | chalk[color].bold(`┏ ${config.dev.chineseLog ? '主程序日志' : 'Electron'} -------------------`) + 170 | '\n\n' + 171 | log + 172 | chalk[color].bold('┗ ----------------------------') + 173 | '\n' 174 | ) 175 | } 176 | 177 | } 178 | 179 | function greeting() { 180 | const cols = process.stdout.columns 181 | let text = '' 182 | 183 | if (cols > 104) text = 'electron-vue' 184 | else if (cols > 76) text = 'electron-|vue' 185 | else text = false 186 | 187 | if (text) { 188 | say(text, { 189 | colors: ['yellow'], 190 | font: 'simple3d', 191 | space: false 192 | }) 193 | } else console.log(chalk.yellow.bold('\n electron-vue')) 194 | console.log(chalk.blue(`${config.dev.chineseLog ? ' 准备启动...' : ' getting ready...'}`) + '\n') 195 | } 196 | 197 | async function init() { 198 | greeting() 199 | 200 | try { 201 | await startRenderer() 202 | await startMain() 203 | await startElectron() 204 | } catch (error) { 205 | console.error(error) 206 | } 207 | 208 | } 209 | 210 | init() -------------------------------------------------------------------------------- /.electron-vue/hot-updater.js: -------------------------------------------------------------------------------- 1 | /** 2 | * power by biuuu 3 | */ 4 | 5 | const chalk = require("chalk"); 6 | const { join } = require('path') 7 | const crypto = require('crypto') 8 | const AdmZip = require('adm-zip') 9 | const packageFile = require('../package.json') 10 | const { build } = require("../config/index") 11 | const { platform } = require("os") 12 | const { ensureDir, emptyDir, copy, outputJSON, remove, stat, readFile } = require("fs-extra"); 13 | 14 | const platformName = platform().includes('win32') ? 'win' : platform().includes('darwin') ? 'mac' : 'linux' 15 | const buildPath = join('.', 'build', `${platformName === 'mac' ? 'mac' : platformName + '-unpacked'}`) 16 | 17 | const hash = (data, type = 'sha256') => { 18 | const hmac = crypto.createHmac(type, 'Sky') 19 | hmac.update(data) 20 | return hmac.digest('hex') 21 | } 22 | 23 | const createZip = (filePath, dest) => { 24 | const zip = new AdmZip() 25 | zip.addLocalFolder(filePath) 26 | zip.toBuffer() 27 | zip.writeZip(dest) 28 | } 29 | 30 | const start = async () => { 31 | console.log(chalk.green.bold(`\n Start packing`)) 32 | 33 | if (packageFile.build.asar) { 34 | console.log( 35 | "\n" + 36 | chalk.bgRed.white(" ERROR ") + 37 | " " + 38 | chalk.red("Please make sure the build.asar option in the Package.json file is set to false") + 39 | "\n" 40 | ); 41 | return; 42 | } 43 | 44 | if (build.hotPublishConfigName === '') { 45 | console.log( 46 | "\n" + 47 | chalk.bgRed.white(" ERROR ") + 48 | " " + 49 | chalk.red("HotPublishConfigName is not set, which will cause the update to fail, please set it in the config/index.js \n") 50 | + chalk.red.bold(`\n Packing failed \n`) 51 | ); 52 | process.exit(1) 53 | } 54 | 55 | stat(join(buildPath, 'resources', 'app'), async (err, stats) => { 56 | if (err) { 57 | console.log( 58 | "\n" + 59 | chalk.bgRed.white(" ERROR ") + 60 | " " + 61 | chalk.red("No resource files were found, please execute this command after the build command") + 62 | "\n" 63 | ); 64 | return; 65 | } 66 | 67 | try { 68 | const packResourcesPath = join('.', 'build', 'resources', 'dist'); 69 | const packPackagePath = join('.', 'build', 'resources'); 70 | const resourcesPath = join('.', 'dist'); 71 | const appPath = join('.', 'build', 'resources'); 72 | const name = "app.zip"; 73 | const outputPath = join('.', 'build', 'update'); 74 | const zipPath = join(outputPath, name); 75 | 76 | await ensureDir(packResourcesPath); 77 | await emptyDir(packResourcesPath); 78 | await copy(resourcesPath, packResourcesPath); 79 | await outputJSON(join(packPackagePath, "package.json"), { 80 | name: packageFile.name, 81 | productName: packageFile.productName, 82 | version: packageFile.version, 83 | private: packageFile.private, 84 | description: packageFile.description, 85 | main: packageFile.main, 86 | author: packageFile.author, 87 | dependencies: packageFile.dependencies 88 | }); 89 | await ensureDir(outputPath); 90 | await emptyDir(outputPath); 91 | createZip(appPath, zipPath); 92 | const buffer = await readFile(zipPath); 93 | const sha256 = hash(buffer); 94 | const hashName = sha256.slice(7, 12); 95 | await copy(zipPath, join(outputPath, `${hashName}.zip`)); 96 | await remove(zipPath); 97 | await remove(appPath) 98 | await outputJSON(join(outputPath, `${build.hotPublishConfigName}.json`), 99 | { 100 | version: packageFile.version, 101 | name: `${hashName}.zip`, 102 | hash: sha256 103 | } 104 | ); 105 | console.log( 106 | "\n" + chalk.bgGreen.white(" DONE ") + " " + "The resource file is packaged!\n" 107 | ); 108 | console.log("File location: " + chalk.green(outputPath) + "\n"); 109 | } catch (error) { 110 | console.log( 111 | "\n" + 112 | chalk.bgRed.white(" ERROR ") + 113 | " " + 114 | chalk.red(error.message || error) + 115 | "\n" 116 | ); 117 | process.exit(1) 118 | } 119 | }); 120 | } 121 | 122 | start() -------------------------------------------------------------------------------- /.electron-vue/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const MiniCssPlugin = require('mini-css-extract-plugin'); 3 | 4 | exports.cssLoaders = function (options) { 5 | options = options || {} 6 | const esbuildCss = { 7 | loader: 'esbuild-loader', 8 | options: { 9 | loader: 'css', 10 | minify: options.minifyCss 11 | } 12 | } 13 | 14 | const cssLoader = { 15 | loader: 'css-loader', 16 | options: { 17 | sourceMap: options.sourceMap, 18 | esModule: false 19 | } 20 | } 21 | 22 | const postcssLoader = { 23 | loader: 'postcss-loader', 24 | options: { 25 | sourceMap: options.sourceMap 26 | } 27 | } 28 | 29 | // 这里就是生成loader和其对应的配置 30 | function generateLoaders(loader, loaderOptions) { 31 | const loaders = [cssLoader, postcssLoader, esbuildCss] 32 | 33 | if (loader) { 34 | loaders.push({ 35 | loader: loader + '-loader', 36 | options: Object.assign({}, loaderOptions, { 37 | sourceMap: options.sourceMap 38 | }) 39 | }) 40 | } 41 | 42 | // 当配置信息中开启此项时,启用css分离压缩 43 | // 这一项在生产环境时,是默认开启的 44 | if (options.extract) { 45 | return [MiniCssPlugin.loader].concat(loaders) 46 | } else { 47 | // 如果不开启则让vue-style-loader来处理 48 | return ['vue-style-loader'].concat(loaders) 49 | } 50 | } 51 | 52 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 53 | return { 54 | css: generateLoaders(), 55 | postcss: generateLoaders(), 56 | less: generateLoaders('less'), 57 | sass: generateLoaders('sass', { indentedSyntax: true }), 58 | scss: generateLoaders('sass'), 59 | stylus: generateLoaders('stylus'), 60 | styl: generateLoaders('stylus') 61 | } 62 | } 63 | 64 | // 根据上面的函数遍历出来的各个css预处理器的loader进行最后的拼装 65 | exports.styleLoaders = function (options) { 66 | const output = [] 67 | const loaders = exports.cssLoaders(options) 68 | 69 | 70 | for (const extension in loaders) { 71 | const loader = loaders[extension] 72 | output.push({ 73 | test: new RegExp('\\.' + extension + '$'), 74 | use: loader 75 | }) 76 | } 77 | 78 | return output 79 | } 80 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | const TerserPlugin = require('terser-webpack-plugin') 9 | const config = require('../config') 10 | 11 | function resolve(dir) { 12 | return path.join(__dirname, '..', dir) 13 | } 14 | 15 | let mainConfig = { 16 | infrastructureLogging: { 17 | level: 'warn' 18 | }, 19 | entry: { 20 | main: path.join(__dirname, '../src/main/index.js') 21 | }, 22 | externals: [ 23 | ...Object.keys(dependencies || {}) 24 | ], 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | loader: 'esbuild-loader' 30 | }, 31 | { 32 | test: /\.node$/, 33 | use: 'node-loader' 34 | } 35 | ] 36 | }, 37 | node: { 38 | __dirname: process.env.NODE_ENV !== 'production', 39 | __filename: process.env.NODE_ENV !== 'production' 40 | }, 41 | output: { 42 | filename: '[name].js', 43 | libraryTarget: 'commonjs2', 44 | path: path.join(__dirname, '../dist/electron') 45 | }, 46 | plugins: [ 47 | new webpack.DefinePlugin({ 48 | 'process.env.TERGET_ENV': JSON.stringify(config[process.env.TERGET_ENV]), 49 | }) 50 | ], 51 | resolve: { 52 | alias: { 53 | '@config': resolve('config'), 54 | }, 55 | extensions: ['.js', '.json', '.node'] 56 | }, 57 | target: 'electron-main', 58 | } 59 | 60 | /** 61 | * Adjust mainConfig for development settings 62 | */ 63 | if (process.env.NODE_ENV !== 'production') { 64 | mainConfig.plugins.push( 65 | new webpack.DefinePlugin({ 66 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`, 67 | 'process.env.libPath': `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"` 68 | }) 69 | ) 70 | } 71 | 72 | /** 73 | * Adjust mainConfig for production settings 74 | */ 75 | if (process.env.NODE_ENV === 'production' && config.build.cleanConsole) { 76 | mainConfig.optimization = { 77 | minimize: true, 78 | minimizer: [ 79 | new TerserPlugin({ 80 | terserOptions: { 81 | compress: { 82 | drop_console: true, 83 | drop_debugger: true, 84 | pure_funcs: ["console.log", "console.warn"] 85 | } 86 | } 87 | 88 | }) 89 | ] 90 | } 91 | mainConfig.plugins.push( 92 | new webpack.DefinePlugin({ 93 | 'process.env.NODE_ENV': '"production"' 94 | }) 95 | ) 96 | } 97 | 98 | module.exports = mainConfig 99 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const IsWeb = process.env.BUILD_TARGET === 'web' 4 | process.env.BABEL_ENV = IsWeb ? 'web' : 'renderer' 5 | 6 | const path = require('path') 7 | const { dependencies } = require('../package.json') 8 | const webpack = require('webpack') 9 | const config = require('../config') 10 | const { styleLoaders } = require('./utils') 11 | 12 | const CopyWebpackPlugin = require('copy-webpack-plugin') 13 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 14 | const HtmlWebpackPlugin = require('html-webpack-plugin') 15 | const TerserPlugin = require('terser-webpack-plugin') 16 | // const ESLintPlugin = require('eslint-webpack-plugin'); 17 | const { VueLoaderPlugin } = require('vue-loader') 18 | 19 | function resolve(dir) { 20 | return path.join(__dirname, '..', dir) 21 | } 22 | /** 23 | * List of node_modules to include in webpack bundle 24 | * 25 | * Required for specific packages like Vue UI libraries 26 | * that provide pure *.vue files that need compiling 27 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 28 | */ 29 | 30 | let rendererConfig = { 31 | entry: IsWeb ? { web: path.join(__dirname, '../src/renderer/main.js') } : { renderer: resolve('src/renderer/main.js') }, 32 | infrastructureLogging: { level: 'warn' }, 33 | stats: 'none', 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.vue$/, 38 | loader: "vue-loader", 39 | options: { 40 | babelParserPlugins: [ 41 | 'jsx', 42 | 'classProperties', 43 | 'decorators-legacy' 44 | ] 45 | } 46 | }, 47 | { 48 | test: /\.jsx$/, 49 | loader: 'babel-loader', 50 | }, 51 | { 52 | test: /\.html$/, 53 | use: 'vue-html-loader' 54 | }, 55 | { 56 | test: /\.svg$/, 57 | loader: 'svg-sprite-loader', 58 | include: [resolve('src/renderer/icons')], 59 | options: { 60 | symbolId: 'icon-[name]' 61 | } 62 | }, 63 | { 64 | test: /\.(png|jpe?g|gif)(\?.*)?$/, 65 | type: "asset/resource", 66 | generator: { 67 | filename: 'imgs/[name]--[hash].[ext]' 68 | } 69 | }, 70 | { 71 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 72 | type: "asset/resource", 73 | generator: { 74 | filename: 'media/[name]--[hash].[ext]' 75 | } 76 | }, 77 | { 78 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 79 | type: "asset/resource", 80 | generator: { 81 | filename: 'fonts/[name]--[hash].[ext]' 82 | } 83 | } 84 | ] 85 | }, 86 | node: { 87 | __dirname: process.env.NODE_ENV !== 'production', 88 | __filename: process.env.NODE_ENV !== 'production' 89 | }, 90 | plugins: [ 91 | new VueLoaderPlugin(), 92 | new MiniCssExtractPlugin(), 93 | new webpack.DefinePlugin({ 94 | 'process.env.TERGET_ENV': JSON.stringify(config[process.env.TERGET_ENV]), 95 | 'process.env': process.env.NODE_ENV === 'production' ? JSON.stringify(config.build.env) : JSON.stringify(config.dev.env), 96 | 'process.env.IS_WEB': IsWeb 97 | }), 98 | new HtmlWebpackPlugin({ 99 | filename: 'index.html', 100 | template: resolve('src/index.ejs'), 101 | minify: { 102 | collapseWhitespace: true, 103 | removeAttributeQuotes: true, 104 | removeComments: true, 105 | minifyJS: true, 106 | minifyCSS: true 107 | }, 108 | templateParameters(compilation, assets, options) { 109 | return { 110 | compilation: compilation, 111 | webpack: compilation.getStats().toJson(), 112 | webpackConfig: compilation.options, 113 | htmlWebpackPlugin: { 114 | files: assets, 115 | options: options 116 | }, 117 | process, 118 | }; 119 | }, 120 | nodeModules: false 121 | }), 122 | ], 123 | output: { 124 | filename: '[name].js', 125 | path: IsWeb ? path.join(__dirname, '../dist/web') : path.join(__dirname, '../dist/electron') 126 | }, 127 | resolve: { 128 | alias: { 129 | '@': resolve('src/renderer'), 130 | 'vue$': 'vue/dist/vue.esm.js' 131 | }, 132 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 133 | }, 134 | target: IsWeb ? 'web' : 'electron-renderer' 135 | } 136 | // 将css相关得loader抽取出来 137 | rendererConfig.module.rules = rendererConfig.module.rules.concat(styleLoaders({ sourceMap: process.env.NODE_ENV !== 'production' ? config.dev.cssSourceMap : false, extract: IsWeb, minifyCss: process.env.NODE_ENV === 'production' })); 138 | (IsWeb || config.UseJsx) ? rendererConfig.module.rules.push({ test: /\.m?[jt]sx$/, use: [{ loader: 'babel-loader', options: { cacheDirectory: true } }] }) : rendererConfig.module.rules.push({ test: /\.m?[jt]s$/, loader: 'esbuild-loader', options: { loader: 'ts', } }) 139 | 140 | /** 141 | * Adjust rendererConfig for development settings 142 | */ 143 | if (process.env.NODE_ENV !== 'production' && !IsWeb) { 144 | rendererConfig.plugins.push( 145 | new webpack.DefinePlugin({ 146 | __lib: `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"` 147 | }) 148 | ) 149 | } 150 | 151 | /** 152 | * Adjust rendererConfig for production settings 153 | */ 154 | if (process.env.NODE_ENV === 'production') { 155 | 156 | rendererConfig.plugins.push( 157 | new CopyWebpackPlugin({ 158 | patterns: [ 159 | { 160 | from: path.join(__dirname, '../static'), 161 | to: path.join(__dirname, '../dist/electron/static'), 162 | globOptions: { 163 | ignore: ['.*'] 164 | } 165 | } 166 | ] 167 | }), 168 | new webpack.DefinePlugin({ 169 | 'process.env.NODE_ENV': '"production"', 170 | }), 171 | new webpack.LoaderOptionsPlugin({ 172 | minimize: true 173 | }) 174 | ) 175 | rendererConfig.optimization = { 176 | minimize: true, 177 | minimizer: [ 178 | new TerserPlugin({ 179 | terserOptions: { 180 | compress: { 181 | drop_console: true, 182 | drop_debugger: true, 183 | pure_funcs: ["console.log", "console.warn"] 184 | } 185 | } 186 | 187 | }) 188 | ] 189 | } 190 | rendererConfig.optimization.splitChunks = { 191 | chunks: "async", 192 | cacheGroups: { 193 | vendor: { // 将第三方模块提取出来 194 | minSize: 30000, 195 | minChunks: 1, 196 | test: /node_modules/, 197 | chunks: 'initial', 198 | name: 'vendor', 199 | priority: 1 200 | }, 201 | commons: { 202 | test: /[\\/]src[\\/]common[\\/]/, 203 | name: 'commons', 204 | minSize: 30000, 205 | minChunks: 3, 206 | chunks: 'initial', 207 | priority: -1, 208 | reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块 209 | } 210 | } 211 | } 212 | rendererConfig.optimization.runtimeChunk = { name: 'runtime' } 213 | } else { 214 | rendererConfig.devtool = 'eval-source-map' 215 | // eslint 216 | // rendererConfig.plugins.push(new ESLintPlugin(config.dev.ESLintoptions)) 217 | } 218 | 219 | module.exports = rendererConfig 220 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/unit/coverage/** 2 | test/unit/*.js 3 | test/e2e/*.js 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true 10 | }, 11 | extends: 'standard', 12 | globals: { 13 | __static: true, 14 | __lib: true 15 | }, 16 | plugins: [ 17 | 'html' 18 | ], 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Build TEST 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [ 16.x, 17.x ] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: yarn --frozen-lockfile 29 | - run: yarn build 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | build/win-unpacked/ 5 | build/win-ia32-unpacked/ 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Editor directories and files 11 | .idea 12 | .vscode 13 | *.suo 14 | *.ntvs* 15 | *.njsproj 16 | *.sln 17 | electron-vue-template-ts.7z 18 | server/client/electron-vue-admin Setup 0.0.1.exe 19 | build/builder-effective-config.yaml 20 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | } 5 | } -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # Electron-Vue-template 2 | 3 | > 这是一个基于 electron 的 vue 最基本的模板,其中前端技术栈则用到 vue 全家桶,axios 作为 http 请求,而本地数据库则是 nedb。现在合并了花裤衩的 vue-admin 中的东西侧栏样式是在`src/renderer/layout/components/sidebar`文件夹中,大家可以根据需求进行个性化更改. 4 | 5 | - 因为花裤衩大大的 ELECTRON 版本已经一年没有更新了,而且 ELECTRON,vue,elementui,都已经迭代太多,刚好我司有这方面的需求,我就在 vue-electron 脚手架生成的项目基础上,将花裤衩大大的项目核心组件提取出来合并到这个项目中,在我简单的封装了 axios 和 db.以及 electron 常用的信息弹窗,错误弹窗,具体文档地址:[中文在线文档](https://umbrella22.github.io/electron-vue-template-doc/),[国内访问地址](https://zh-sky.gitee.io/electron-vue-template-doc/)。 6 | 7 | > **请注意,在 2021 年 4 月 13 日的更新之后,将使用 esbuild 替换 babel,如对 babel 有强需求的小伙伴请勿更新。** 8 | 9 | - vite 版本 [electron-vite-template](https://github.com/umbrella22/electron-vite-template) 10 | 11 | - [electron-vite-template(码云)](https://gitee.com/Zh-Sky/electron-vite-template) 12 | 13 | - react 版 [Electron-react-template](https://github.com/umbrella22/electron-react-template) 14 | 15 |
16 | 测试打包状态: 17 | 18 | 19 | 20 |
21 | 22 | > ### **请确保您的 node 环境是大于或等于 16** 23 | 24 | #### 如何安装 25 | 26 | ```bash 27 | npm config edit 28 | # 该命令会打开npm的配置文件,请在空白处添加,此操作是配置淘宝镜像。 29 | # registry=https://registry.npmmirror.com 30 | # electron_mirror=https://cdn.npmmirror.com/binaries/electron/ 31 | # electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ 32 | # 然后关闭该窗口,重启命令行. 33 | # 使用yarn安装 34 | yarn or yarn install 35 | 36 | # 启动之后,会在9080端口监听 37 | yarn dev 38 | 39 | # build命令在不同系统环境中,需要的的不一样,需要自己根据自身环境进行配置 40 | yarn build 41 | 42 | ``` 43 | 44 | --- 45 | 46 | ## 启动逻辑图 47 | 48 | 49 | 50 | # [更新日志](CHANGELOG.md) 51 | -------------------------------------------------------------------------------- /build/builder-debug.yml: -------------------------------------------------------------------------------- 1 | x64: 2 | firstOrDefaultFilePatterns: 3 | - '!**/node_modules' 4 | - '!build{,/**/*}' 5 | - '!build{,/**/*}' 6 | - dist/electron/**/* 7 | - package.json 8 | - '!**/*.{iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts,pdb}' 9 | - '!**/._*' 10 | - '!**/electron-builder.{yaml,yml,json,json5,toml}' 11 | - '!**/{.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore,.idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci,.yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,appveyor.yml,.travis.yml,circle.yml,.nyc_output}' 12 | - '!.yarn{,/**/*}' 13 | - '!.editorconfig' 14 | - '!.yarnrc.yml' 15 | nodeModuleFilePatterns: 16 | - '**/*' 17 | - dist/electron/**/* 18 | nsis: 19 | script: |- 20 | !include "C:\Users\Administrator\Desktop\Smya\smya-client\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh" 21 | !addincludedir "C:\Users\Administrator\Desktop\Smya\smya-client\node_modules\app-builder-lib\templates\nsis\include" 22 | !macro _isUpdated _a _b _t _f 23 | ${StdUtils.TestParameter} $R9 "updated" 24 | StrCmp "$R9" "true" `${_t}` `${_f}` 25 | !macroend 26 | !define isUpdated `"" isUpdated ""` 27 | 28 | !macro _isForceRun _a _b _t _f 29 | ${StdUtils.TestParameter} $R9 "force-run" 30 | StrCmp "$R9" "true" `${_t}` `${_f}` 31 | !macroend 32 | !define isForceRun `"" isForceRun ""` 33 | 34 | !macro _isKeepShortcuts _a _b _t _f 35 | ${StdUtils.TestParameter} $R9 "keep-shortcuts" 36 | StrCmp "$R9" "true" `${_t}` `${_f}` 37 | !macroend 38 | !define isKeepShortcuts `"" isKeepShortcuts ""` 39 | 40 | !macro _isNoDesktopShortcut _a _b _t _f 41 | ${StdUtils.TestParameter} $R9 "no-desktop-shortcut" 42 | StrCmp "$R9" "true" `${_t}` `${_f}` 43 | !macroend 44 | !define isNoDesktopShortcut `"" isNoDesktopShortcut ""` 45 | 46 | !macro _isDeleteAppData _a _b _t _f 47 | ${StdUtils.TestParameter} $R9 "delete-app-data" 48 | StrCmp "$R9" "true" `${_t}` `${_f}` 49 | !macroend 50 | !define isDeleteAppData `"" isDeleteAppData ""` 51 | 52 | !macro _isForAllUsers _a _b _t _f 53 | ${StdUtils.TestParameter} $R9 "allusers" 54 | StrCmp "$R9" "true" `${_t}` `${_f}` 55 | !macroend 56 | !define isForAllUsers `"" isForAllUsers ""` 57 | 58 | !macro _isForCurrentUser _a _b _t _f 59 | ${StdUtils.TestParameter} $R9 "currentuser" 60 | StrCmp "$R9" "true" `${_t}` `${_f}` 61 | !macroend 62 | !define isForCurrentUser `"" isForCurrentUser ""` 63 | 64 | !macro addLangs 65 | !insertmacro MUI_LANGUAGE "English" 66 | !insertmacro MUI_LANGUAGE "German" 67 | !insertmacro MUI_LANGUAGE "French" 68 | !insertmacro MUI_LANGUAGE "SpanishInternational" 69 | !insertmacro MUI_LANGUAGE "SimpChinese" 70 | !insertmacro MUI_LANGUAGE "TradChinese" 71 | !insertmacro MUI_LANGUAGE "Japanese" 72 | !insertmacro MUI_LANGUAGE "Korean" 73 | !insertmacro MUI_LANGUAGE "Italian" 74 | !insertmacro MUI_LANGUAGE "Dutch" 75 | !insertmacro MUI_LANGUAGE "Danish" 76 | !insertmacro MUI_LANGUAGE "Swedish" 77 | !insertmacro MUI_LANGUAGE "Norwegian" 78 | !insertmacro MUI_LANGUAGE "Finnish" 79 | !insertmacro MUI_LANGUAGE "Russian" 80 | !insertmacro MUI_LANGUAGE "Portuguese" 81 | !insertmacro MUI_LANGUAGE "PortugueseBR" 82 | !insertmacro MUI_LANGUAGE "Polish" 83 | !insertmacro MUI_LANGUAGE "Ukrainian" 84 | !insertmacro MUI_LANGUAGE "Czech" 85 | !insertmacro MUI_LANGUAGE "Slovak" 86 | !insertmacro MUI_LANGUAGE "Hungarian" 87 | !insertmacro MUI_LANGUAGE "Arabic" 88 | !insertmacro MUI_LANGUAGE "Turkish" 89 | !insertmacro MUI_LANGUAGE "Thai" 90 | !insertmacro MUI_LANGUAGE "Vietnamese" 91 | !macroend 92 | 93 | !addplugindir /x86-unicode "C:\Users\Administrator\AppData\Local\electron-builder\Cache\nsis\nsis-resources-3.4.1\plugins\x86-unicode" 94 | !include "C:\Users\ADMINI~1\AppData\Local\Temp\t-DXJZuN\0-messages.nsh" 95 | 96 | Var newStartMenuLink 97 | Var oldStartMenuLink 98 | Var newDesktopLink 99 | Var oldDesktopLink 100 | Var oldShortcutName 101 | Var oldMenuDirectory 102 | 103 | !include "common.nsh" 104 | !include "MUI2.nsh" 105 | !include "multiUser.nsh" 106 | !include "allowOnlyOneInstallerInstance.nsh" 107 | 108 | !ifdef INSTALL_MODE_PER_ALL_USERS 109 | !ifdef BUILD_UNINSTALLER 110 | RequestExecutionLevel user 111 | !else 112 | RequestExecutionLevel admin 113 | !endif 114 | !else 115 | RequestExecutionLevel user 116 | !endif 117 | 118 | !ifdef BUILD_UNINSTALLER 119 | SilentInstall silent 120 | !else 121 | Var appExe 122 | Var launchLink 123 | !endif 124 | 125 | !ifdef ONE_CLICK 126 | !include "oneClick.nsh" 127 | !else 128 | !include "assistedInstaller.nsh" 129 | !endif 130 | 131 | !insertmacro addLangs 132 | 133 | !ifmacrodef customHeader 134 | !insertmacro customHeader 135 | !endif 136 | 137 | Function .onInit 138 | SetOutPath $INSTDIR 139 | ${LogSet} on 140 | 141 | !ifmacrodef preInit 142 | !insertmacro preInit 143 | !endif 144 | 145 | !ifdef DISPLAY_LANG_SELECTOR 146 | !insertmacro MUI_LANGDLL_DISPLAY 147 | !endif 148 | 149 | !ifdef BUILD_UNINSTALLER 150 | WriteUninstaller "${UNINSTALLER_OUT_FILE}" 151 | !insertmacro quitSuccess 152 | !else 153 | !insertmacro check64BitAndSetRegView 154 | 155 | !ifdef ONE_CLICK 156 | !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE 157 | !else 158 | ${IfNot} ${UAC_IsInnerInstance} 159 | !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE 160 | ${EndIf} 161 | !endif 162 | 163 | !insertmacro initMultiUser 164 | 165 | !ifmacrodef customInit 166 | !insertmacro customInit 167 | !endif 168 | 169 | !ifmacrodef addLicenseFiles 170 | InitPluginsDir 171 | !insertmacro addLicenseFiles 172 | !endif 173 | !endif 174 | FunctionEnd 175 | 176 | !ifndef BUILD_UNINSTALLER 177 | !include "installUtil.nsh" 178 | !endif 179 | 180 | Section "install" 181 | !ifndef BUILD_UNINSTALLER 182 | # If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed. 183 | # For a non-silent install, the elevation will be triggered when the install mode is selected in the UI, 184 | # but that won't be executed when silent. 185 | !ifndef INSTALL_MODE_PER_ALL_USERS 186 | !ifndef ONE_CLICK 187 | ${if} $hasPerMachineInstallation == "1" # set in onInit by initMultiUser 188 | ${andIf} ${Silent} 189 | ${ifNot} ${UAC_IsAdmin} 190 | ShowWindow $HWNDPARENT ${SW_HIDE} 191 | !insertmacro UAC_RunElevated 192 | ${Switch} $0 193 | ${Case} 0 194 | ${Break} 195 | ${Case} 1223 ;user aborted 196 | ${Break} 197 | ${Default} 198 | MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0" 199 | ${Break} 200 | ${EndSwitch} 201 | Quit 202 | ${else} 203 | !insertmacro setInstallModePerAllUsers 204 | ${endIf} 205 | ${endIf} 206 | !endif 207 | !endif 208 | !include "installSection.nsh" 209 | !endif 210 | SectionEnd 211 | 212 | !ifdef BUILD_UNINSTALLER 213 | !include "uninstaller.nsh" 214 | !endif 215 | -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/build/icons/icon.ico -------------------------------------------------------------------------------- /build/latest.yml: -------------------------------------------------------------------------------- 1 | version: 0.0.1 2 | files: 3 | - url: smy Setup 0.0.1.exe 4 | sha512: Ka2M1noNFENnCt2KRwdZfbqZ9aE2xL8NDB/cuQxHvk8O7ensHoYFlQum1juuSouF5vSNGHvwjcSf1EULKFtvMA== 5 | size: 70089697 6 | path: smy Setup 0.0.1.exe 7 | sha512: Ka2M1noNFENnCt2KRwdZfbqZ9aE2xL8NDB/cuQxHvk8O7ensHoYFlQum1juuSouF5vSNGHvwjcSf1EULKFtvMA== 8 | releaseDate: '2022-08-10T15:03:17.193Z' 9 | -------------------------------------------------------------------------------- /build/smy Setup 0.0.1.exe.blockmap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/build/smy Setup 0.0.1.exe.blockmap -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"development"', 3 | BASE_API: 'https://gateway.smya.cn/smya-service/api' 4 | } 5 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | build: { 3 | env: require('./prod.env'), 4 | DisableF12: true, 5 | cleanConsole: true, 6 | openDevTools: false, 7 | hotPublishUrl: "", 8 | hotPublishConfigName: "update-config" 9 | }, 10 | dev: { 11 | env: require('./dev.env'), 12 | removeElectronJunk: true, 13 | chineseLog: false, 14 | port: 9080 15 | }, 16 | development: require('./dev.env'), 17 | production: require('./prod.env'), 18 | UseStartupChart: true, 19 | IsUseSysTitle: true, 20 | DllFolder: '', 21 | BuiltInServerPort: 25565, 22 | UseJsx: true 23 | } 24 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | BASE_API: 'https://gateway.smya.cn/smya-service/api' 4 | } 5 | -------------------------------------------------------------------------------- /lib/updater.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/lib/updater.html -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smya", 3 | "version": "0.0.1", 4 | "author": "sky ", 5 | "description": "An electron-vue project", 6 | "license": "MIT", 7 | "main": "./dist/electron/main.js", 8 | "scripts": { 9 | "dev": "cross-env TERGET_ENV=development node .electron-vue/dev-runner.js", 10 | "build": "cross-env TERGET_ENV=development BUILD_TARGET=clean node .electron-vue/build.js && electron-builder", 11 | "build:win32": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --win --ia32", 12 | "build:win64": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --win --x64", 13 | "build:mac": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --mac", 14 | "build:dir": "cross-env BUILD_TARGET=clean node .electron-vue/build.js && electron-builder --dir", 15 | "build:clean": "cross-env BUILD_TARGET=onlyClean node .electron-vue/build.js", 16 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 17 | "pack:resources": "node .electron-vue/hot-updater.js", 18 | "update:serve": "node server/index.js", 19 | "dep:upgrade": "yarn upgrade-interactive --latest", 20 | "postinstall": "electron-builder install-app-deps" 21 | }, 22 | "build": { 23 | "asar": false, 24 | "extraFiles": [], 25 | "publish": [ 26 | { 27 | "provider": "generic", 28 | "url": "http://127.0.0.1" 29 | } 30 | ], 31 | "productName": "smy", 32 | "appId": "smy.orzlab.com", 33 | "directories": { 34 | "output": "build" 35 | }, 36 | "files": [ 37 | "dist/electron/**/*" 38 | ], 39 | "dmg": { 40 | "contents": [ 41 | { 42 | "x": 410, 43 | "y": 150, 44 | "type": "link", 45 | "path": "/Applications" 46 | }, 47 | { 48 | "x": 130, 49 | "y": 150, 50 | "type": "file" 51 | } 52 | ] 53 | }, 54 | "mac": { 55 | "icon": "build/icons/icon.icns" 56 | }, 57 | "win": { 58 | "icon": "build/icons/icon.ico", 59 | "requestedExecutionLevel": "requireAdministrator", 60 | "target": "nsis" 61 | }, 62 | "linux": { 63 | "target": "deb", 64 | "icon": "build/icons" 65 | } 66 | }, 67 | "dependencies": { 68 | "axios": "^0.27.2", 69 | "electron-baidu-tongji": "^1.0.5", 70 | "electron-log": "^4.4.8", 71 | "electron-store": "^8.0.2", 72 | "electron-updater": "^5.0.1", 73 | "fs-extra": "^10.1.0", 74 | "getmac": "^5.20.0", 75 | "mqtt": "^4.3.7", 76 | "node-cmd": "^5.0.0" 77 | }, 78 | "devDependencies": { 79 | "@babel/core": "^7.18.2", 80 | "@babel/eslint-parser": "^7.18.2", 81 | "@babel/plugin-proposal-class-properties": "^7.17.12", 82 | "@babel/plugin-proposal-decorators": "^7.18.2", 83 | "@babel/plugin-proposal-do-expressions": "^7.16.7", 84 | "@babel/plugin-proposal-export-default-from": "^7.17.12", 85 | "@babel/plugin-proposal-export-namespace-from": "^7.17.12", 86 | "@babel/plugin-proposal-function-bind": "^7.16.7", 87 | "@babel/plugin-proposal-function-sent": "^7.18.2", 88 | "@babel/plugin-proposal-json-strings": "^7.17.12", 89 | "@babel/plugin-proposal-logical-assignment-operators": "^7.17.12", 90 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.17.12", 91 | "@babel/plugin-proposal-numeric-separator": "^7.16.7", 92 | "@babel/plugin-proposal-optional-chaining": "^7.17.12", 93 | "@babel/plugin-proposal-pipeline-operator": "^7.18.2", 94 | "@babel/plugin-proposal-throw-expressions": "^7.16.7", 95 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 96 | "@babel/plugin-syntax-import-meta": "^7.10.4", 97 | "@babel/plugin-transform-runtime": "^7.18.2", 98 | "@babel/preset-env": "^7.18.2", 99 | "@babel/register": "^7.17.7", 100 | "@babel/runtime": "^7.18.3", 101 | "@types/fs-extra": "^9.0.13", 102 | "@types/node": "^14.14.41", 103 | "@vue/babel-helper-vue-jsx-merge-props": "^1.2.1", 104 | "@vue/babel-preset-jsx": "^1.2.4", 105 | "adm-zip": "^0.5.9", 106 | "autoprefixer": "^10.4.7", 107 | "babel-loader": "^8.2.5", 108 | "cfonts": "^2.10.0", 109 | "chalk": "^4.1.2", 110 | "copy-webpack-plugin": "^10.2.4", 111 | "core-js": "^3.22.8", 112 | "cross-env": "^7.0.3", 113 | "css-loader": "^6.7.1", 114 | "date-fns": "^2.28.0", 115 | "del": "^6.1.1", 116 | "electron": "20.0.0", 117 | "electron-builder": "^23.0.3", 118 | "electron-devtools-installer": "^3.2.0", 119 | "element-ui": "^2.15.9", 120 | "esbuild-loader": "^2.19.0", 121 | "eslint": "^7.32.0", 122 | "eslint-config-standard": "^14.1.1", 123 | "eslint-friendly-formatter": "^4.0.1", 124 | "eslint-plugin-html": "^6.2.0", 125 | "eslint-plugin-import": "^2.25.4", 126 | "eslint-plugin-node": "^11.1.0", 127 | "eslint-plugin-promise": "^4.3.1", 128 | "eslint-plugin-standard": "^5.0.0", 129 | "eslint-webpack-plugin": "^3.1.1", 130 | "extract-zip": "^2.0.1", 131 | "html-webpack-plugin": "^5.5.0", 132 | "mini-css-extract-plugin": "2.6.0", 133 | "multispinner": "^0.2.1", 134 | "node-loader": "^2.0.0", 135 | "nprogress": "^0.2.0", 136 | "portfinder": "^1.0.28", 137 | "postcss": "^8.4.14", 138 | "postcss-loader": "^7.0.0", 139 | "sass": "^1.52.3", 140 | "sass-loader": "^13.0.0", 141 | "style-loader": "^3.3.1", 142 | "svg-sprite-loader": "^6.0.11", 143 | "terser-webpack-plugin": "^5.3.3", 144 | "vue": "^2.6.14", 145 | "vue-html-loader": "^1.2.4", 146 | "vue-i18n": "^8.27.1", 147 | "vue-loader": "^15.9.8", 148 | "vue-router": "^3.5.3", 149 | "vue-style-loader": "^4.1.3", 150 | "vue-template-compiler": "^2.6.14", 151 | "vuex": "^3.6.2", 152 | "webpack": "^5.73.0", 153 | "webpack-cli": "^4.9.2", 154 | "webpack-dev-server": "^4.9.2", 155 | "webpack-hot-middleware": "^2.25.1", 156 | "webpack-merge": "^5.8.0" 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const app = express() 4 | 5 | app.use(express.static(path.join(__dirname, './client'))) 6 | 7 | const server = app.listen(25565, function () { 8 | const host = server.address().address 9 | const port = server.address().port 10 | 11 | console.log('服务启动', host, port) 12 | }) 13 | -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 神秘鸭 - ORZ实验室 7 | 12 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 13 | 14 | 17 | <% } %> 18 | 19 | 20 | 21 |
22 | 23 | <% if (!process.browser) { %> 24 | 32 | <% } %> 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/config/DisableButton.js: -------------------------------------------------------------------------------- 1 | import { globalShortcut } from 'electron' 2 | import config from '@config' 3 | 4 | export default { 5 | Disablef12 () { 6 | if (process.env.NODE_ENV === 'production' && config.DisableF12) { 7 | globalShortcut.register('f12', () => { 8 | console.log('用户试图启动控制台') 9 | }) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/config/StaticPath.js: -------------------------------------------------------------------------------- 1 | // 这里定义了静态文件路径的位置 2 | import path from 'path' 3 | import { DllFolder } from '@config/index' 4 | 5 | /** 6 | * Set `__static` path to static files in production 7 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 8 | */ 9 | // 这个瓜皮全局变量只能在单个js中生效,而并不是整个主进程中 10 | if (process.env.NODE_ENV !== 'development') { 11 | global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\') 12 | process.env.libPath = path.join(__dirname, '..', '..', '..', '..', `${DllFolder}`).replace(/\\/g, '\\\\') 13 | } 14 | 15 | export const winURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}` : `file://${__dirname}/index.html` 16 | export const loadingURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}/static/loader.html` : `file://${__static}/loader.html` 17 | export const libPath = process.env.libPath 18 | -------------------------------------------------------------------------------- /src/main/config/hotPublish.js: -------------------------------------------------------------------------------- 1 | import { build } from '@config/index.js' 2 | 3 | export const hotPublishConfig = { 4 | url: build.hotPublishUrl, 5 | configName: build.hotPublishConfigName 6 | } -------------------------------------------------------------------------------- /src/main/config/menu.js: -------------------------------------------------------------------------------- 1 | // 这里是定义菜单的地方,详情请查看 https://electronjs.org/docs/api/menu 2 | const { dialog } = require('electron') 3 | const os = require('os') 4 | const version = require('../../../package.json').version 5 | const menu = [ 6 | { 7 | label: '设置', 8 | submenu: [{ 9 | label: '快速重启', 10 | accelerator: 'F5', 11 | role: 'reload' 12 | }, { 13 | label: '退出', 14 | accelerator: 'CmdOrCtrl+F4', 15 | role: 'close' 16 | }] 17 | }, { 18 | label: '帮助', 19 | submenu: [{ 20 | label: '关于', 21 | role: 'about', 22 | click: function () { 23 | info() 24 | } 25 | }] 26 | }] 27 | function info () { 28 | dialog.showMessageBox({ 29 | title: '关于', 30 | type: 'info', 31 | message: 'electron-Vue框架', 32 | detail: `版本信息:${version}\n引擎版本:${process.versions.v8}\n当前系统:${os.type()} ${os.arch()} ${os.release()}`, 33 | noLink: true, 34 | buttons: ['查看github', '确定'] 35 | }) 36 | } 37 | export default menu 38 | -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { app } from 'electron' 4 | import path from "path"; 5 | import initWindow from './services/windowManager' 6 | import DisableButton from './config/DisableButton' 7 | import electronDevtoolsInstaller, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' 8 | import Store from 'electron-store' 9 | Store.initRenderer(); 10 | // console.log(" 111+ " + app.getPath('userData')) 11 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; 12 | 13 | function onAppReady () { 14 | initWindow() 15 | DisableButton.Disablef12() 16 | if (process.env.NODE_ENV === 'development') { 17 | electronDevtoolsInstaller(VUEJS_DEVTOOLS) 18 | .then((name) => console.log(`installed: ${name}`)) 19 | .catch(err => console.log('Unable to install `vue-devtools`: \n', err)) 20 | } 21 | } 22 | //禁止程序多开,此处需要单例锁的同学打开注释即可 23 | const gotTheLock = app.requestSingleInstanceLock() 24 | if(!gotTheLock){ 25 | app.quit() 26 | } 27 | app.isReady() ? onAppReady() : app.on('ready', onAppReady) 28 | // 解决9.x跨域异常问题 29 | app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors') 30 | 31 | app.on('window-all-closed', () => { 32 | // 所有平台均为所有窗口关闭就退出软件 33 | app.quit() 34 | }) 35 | app.on('browser-window-created', () => { 36 | console.log('window-created') 37 | }) 38 | 39 | if (process.defaultApp) { 40 | if (process.argv.length >= 2) { 41 | app.removeAsDefaultProtocolClient('smy-orzlab') 42 | console.log('有于框架特殊性开发环境下无法使用') 43 | } 44 | } else { 45 | app.setAsDefaultProtocolClient('smy-orzlab') 46 | } 47 | -------------------------------------------------------------------------------- /src/main/services/HotUpdater.js: -------------------------------------------------------------------------------- 1 | /** 2 | * power by biuuu 3 | */ 4 | 5 | import { emptyDir, createWriteStream, readFile, copy, remove } from 'fs-extra' 6 | import { join, resolve } from 'path' 7 | import { promisify } from 'util' 8 | import { pipeline } from 'stream' 9 | import { app } from 'electron' 10 | import { gt } from 'semver' 11 | import { createHmac } from 'crypto' 12 | import extract from 'extract-zip' 13 | import { version } from '../../../package.json' 14 | import { hotPublishConfig } from '../config/hotPublish' 15 | import axios from 'axios' 16 | 17 | const streamPipeline = promisify(pipeline) 18 | const appPath = app.getAppPath() 19 | const updatePath = resolve(appPath, '..', '..', 'update') 20 | const request = axios.create() 21 | 22 | /** 23 | * @param data 文件流 24 | * @param type 类型,默认sha256 25 | * @param key 密钥,用于匹配计算结果 26 | * @returns {string} 计算结果 27 | * @author umbrella22 28 | * @date 2021-03-05 29 | */ 30 | function hash(data, type = 'sha256', key = 'Sky') { 31 | const hmac = createHmac(type, key) 32 | hmac.update(data) 33 | return hmac.digest('hex') 34 | } 35 | 36 | 37 | /** 38 | * @param url 下载地址 39 | * @param filePath 文件存放地址 40 | * @returns {void} 41 | * @author umbrella22 42 | * @date 2021-03-05 43 | */ 44 | async function download(url, filePath) { 45 | const res = await request({ url, responseType: "stream" }) 46 | await streamPipeline(res.data, createWriteStream(filePath)) 47 | } 48 | 49 | const updateInfo = { 50 | status: 'init', 51 | message: '' 52 | } 53 | 54 | /** 55 | * @param windows 指主窗口 56 | * @returns {void} 57 | * @author umbrella22 58 | * @date 2021-03-05 59 | */ 60 | export const updater = async (windows) => { 61 | try { 62 | const res = await request({ url: `${hotPublishConfig.url}/${hotPublishConfig.configName}.json?time=${new Date().getTime()}`, }) 63 | if (gt(res.data.version, version)) { 64 | await emptyDir(updatePath) 65 | const filePath = join(updatePath, res.data.name) 66 | updateInfo.status = 'downloading' 67 | if (windows) windows.webContents.send('hot-update-status', updateInfo); 68 | await download(`${hotPublishConfig.url}/${res.data.name}`, filePath); 69 | const buffer = await readFile(filePath) 70 | const sha256 = hash(buffer) 71 | if (sha256 !== res.data.hash) throw new Error('sha256 error') 72 | const appPathTemp = join(updatePath, 'temp') 73 | await extract(filePath, { dir: appPathTemp }) 74 | updateInfo.status = 'moving' 75 | if (windows) windows.webContents.send('hot-update-status', updateInfo); 76 | await remove(join(`${appPath}`, 'dist')); 77 | await remove(join(`${appPath}`, 'package.json')); 78 | await copy(appPathTemp, appPath) 79 | updateInfo.status = 'finished' 80 | if (windows) windows.webContents.send('hot-update-status', updateInfo); 81 | } 82 | 83 | 84 | } catch (error) { 85 | updateInfo.status = 'failed' 86 | updateInfo.message = error 87 | if (windows) windows.webContents.send('hot-update-status', updateInfo) 88 | } 89 | } 90 | 91 | export const getUpdateInfo = () => updateInfo -------------------------------------------------------------------------------- /src/main/services/checkupdate.js: -------------------------------------------------------------------------------- 1 | import { autoUpdater } from 'electron-updater' 2 | /** 3 | * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成 4 | **/ 5 | class Update { 6 | mainWindow 7 | constructor() { 8 | autoUpdater.setFeedURL('http://127.0.0.1:25565/') 9 | 10 | // 当更新发生错误的时候触发。 11 | autoUpdater.on('error', (err) => { 12 | console.log('更新出现错误', err.message) 13 | if (err.message.includes('sha512 checksum mismatch')) { 14 | this.Message(this.mainWindow, -1, 'sha512校验失败') 15 | } else { 16 | this.Message(this.mainWindow, -1, '错误信息请看主进程控制台') 17 | 18 | } 19 | }) 20 | 21 | // 当开始检查更新的时候触发 22 | autoUpdater.on('checking-for-update', (event, arg) => { 23 | console.log('开始检查更新') 24 | this.Message(this.mainWindow, 0) 25 | }) 26 | 27 | // 发现可更新数据时 28 | autoUpdater.on('update-available', (event, arg) => { 29 | console.log('有更新') 30 | this.Message(this.mainWindow, 1) 31 | }) 32 | 33 | // 没有可更新数据时 34 | autoUpdater.on('update-not-available', (event, arg) => { 35 | console.log('没有更新') 36 | this.Message(this.mainWindow, 2) 37 | }) 38 | 39 | // 下载监听 40 | autoUpdater.on('download-progress', (progressObj) => { 41 | this.Message(this.mainWindow, 3, progressObj) 42 | }) 43 | 44 | // 下载完成 45 | autoUpdater.on('update-downloaded', () => { 46 | console.log('done') 47 | this.Message(this.mainWindow, 4) 48 | }) 49 | } 50 | // 负责向渲染进程发送信息 51 | Message(mainWindow, type, data) { 52 | console.log('发送消息') 53 | const senddata = { 54 | state: type, 55 | msg: data || '' 56 | } 57 | mainWindow.webContents.send('update-msg', senddata) 58 | } 59 | 60 | 61 | 62 | // 执行自动更新检查 63 | checkUpdate(mainWindow) { 64 | this.mainWindow = mainWindow 65 | autoUpdater.checkForUpdates().catch(err => { 66 | console.log('网络连接问题', err) 67 | }) 68 | } 69 | 70 | // 退出并安装 71 | quitInstall() { 72 | autoUpdater.quitAndInstall() 73 | } 74 | } 75 | 76 | export default Update 77 | -------------------------------------------------------------------------------- /src/main/services/downloadFile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-case-declarations */ 2 | import { app, dialog } from 'electron' 3 | import path from 'path' 4 | import os from 'os' 5 | // 版本以package.json为基准。 6 | const version = require('../../../package.json').version 7 | // 您的下载地址 8 | const baseUrl = 'http://127.0.0.1:25565/' 9 | var Sysarch = null 10 | var defaultDownloadUrL = null 11 | // 识别操作系统位数D 12 | os.arch().includes('64') ? Sysarch = 'win64' : Sysarch = 'win32' 13 | // 识别操作系统 14 | // linux自己修改后缀名哦,我没有linux就没有测试了 15 | if (os.platform().includes('win32')) { 16 | defaultDownloadUrL = baseUrl + `electron_${version}_${Sysarch}.exe?${new Date().getTime()}` 17 | } else if (os.platform().includes('linux')) { 18 | defaultDownloadUrL = baseUrl + `electron_${version}_${Sysarch}?${new Date().getTime()}` 19 | } else { 20 | defaultDownloadUrL = baseUrl + `electron_${version}_mac.dmg?${new Date().getTime()}` 21 | } 22 | export default { 23 | download(mainWindow, downloadUrL) { 24 | mainWindow.webContents.downloadURL(downloadUrL || defaultDownloadUrL) 25 | mainWindow.webContents.session.on('will-download', (event, item, webContents) => { 26 | // 将文件保存在系统的下载目录 27 | const filePath = path.join(app.getPath('downloads'), item.getFilename()) 28 | // 自动保存 29 | item.setSavePath(filePath) 30 | // 下载进度 31 | item.on('updated', (event, state) => { 32 | switch (state) { 33 | case 'progressing': 34 | mainWindow.webContents.send('download-progress', (item.getReceivedBytes() / item.getTotalBytes() * 100).toFixed(0)) 35 | break 36 | case 'interrupted ': 37 | mainWindow.webContents.send('download-paused', true) 38 | break 39 | default: 40 | 41 | break 42 | } 43 | }) 44 | // 下载完成或失败 45 | item.once('done', (event, state) => { 46 | switch (state) { 47 | case 'completed': 48 | const data = { 49 | filePath 50 | } 51 | mainWindow.webContents.send('download-done', data) 52 | break 53 | case 'interrupted': 54 | mainWindow.webContents.send('download-error', true) 55 | dialog.showErrorBox('下载出错', '由于网络或其他未知原因导致下载出错.') 56 | break 57 | default: 58 | break 59 | } 60 | }) 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/services/ipcMain.js: -------------------------------------------------------------------------------- 1 | import { 2 | ipcMain, 3 | dialog, 4 | BrowserWindow, 5 | } from 'electron' 6 | import { 7 | winURL 8 | } from '../config/StaticPath' 9 | import downloadFile from './downloadFile' 10 | import Update from './checkupdate' 11 | import MqttUtil from './mqttUtil' 12 | import { 13 | updater 14 | } from './HotUpdater' 15 | import { 16 | ebtMain 17 | } from 'electron-baidu-tongji' 18 | const isDevelopment = process.env.NODE_ENV !== 'production' 19 | ebtMain(ipcMain, isDevelopment) 20 | 21 | export default { 22 | Mainfunc(IsUseSysTitle) { 23 | const mq = new MqttUtil() 24 | const allUpdater = new Update(); 25 | ipcMain.handle('mq-online', (event, arg) => { 26 | let info = JSON.parse(arg) 27 | const options = { 28 | keepalive: 30, 29 | clientId: "smy_" + Math.random().toString(16).substr(2, 8), 30 | clean: true, 31 | protocolId: "MQTT", 32 | protocolVersion: 4, 33 | username: info.username, 34 | password: info.password, 35 | reconnectPeriod: 1000, 36 | connectTimeout: 30 * 1000, 37 | rejectUnauthorized: false, 38 | will: { 39 | topic: "smy-topic/" + info.password, 40 | payload: "1", 41 | qos: 0, 42 | retain: false, 43 | } 44 | }; 45 | mq.onilne(event, options, info.password, info.host) 46 | return "ok" 47 | }) 48 | 49 | ipcMain.handle('mq-offline', (event, arg) => { 50 | mq.offline() 51 | return "ok" 52 | }) 53 | 54 | ipcMain.handle('IsUseSysTitle', async () => { 55 | return IsUseSysTitle 56 | }) 57 | ipcMain.handle('windows-mini', (event, args) => { 58 | BrowserWindow.fromWebContents(event.sender)?.minimize() 59 | }) 60 | ipcMain.handle('window-max', async (event, args) => { 61 | if (BrowserWindow.fromWebContents(event.sender)?.isMaximized()) { 62 | BrowserWindow.fromWebContents(event.sender)?.unmaximize() 63 | return { 64 | status: false 65 | } 66 | } else { 67 | BrowserWindow.fromWebContents(event.sender)?.maximize() 68 | return { 69 | status: true 70 | } 71 | } 72 | }) 73 | ipcMain.handle('window-close', (event, args) => { 74 | BrowserWindow.fromWebContents(event.sender)?.close() 75 | }) 76 | ipcMain.handle('start-download', (event, msg) => { 77 | downloadFile.download(BrowserWindow.fromWebContents(event.sender), msg.downloadUrL) 78 | }) 79 | ipcMain.handle('check-update', (event, args) => { 80 | allUpdater.checkUpdate(BrowserWindow.fromWebContents(event.sender)) 81 | }) 82 | ipcMain.handle('confirm-update', () => { 83 | allUpdater.quitInstall() 84 | }) 85 | ipcMain.handle('hot-update', (event, arg) => { 86 | updater(BrowserWindow.fromWebContents(event.sender)) 87 | }) 88 | ipcMain.handle('open-messagebox', async (event, arg) => { 89 | const res = await dialog.showMessageBox(BrowserWindow.fromWebContents(event.sender), { 90 | type: arg.type || 'info', 91 | title: arg.title || '', 92 | buttons: arg.buttons || [], 93 | message: arg.message || '', 94 | noLink: arg.noLink || true 95 | }) 96 | return res 97 | }) 98 | ipcMain.handle('open-errorbox', (event, arg) => { 99 | dialog.showErrorBox( 100 | arg.title, 101 | arg.message 102 | ) 103 | }) 104 | let childWin = null; 105 | let cidArray = []; 106 | ipcMain.handle('open-win', (event, arg) => { 107 | let cidJson = { 108 | id: null, 109 | url: '' 110 | } 111 | let data = cidArray.filter((currentValue) => { 112 | if (currentValue.url === arg.url) { 113 | return currentValue 114 | } 115 | }) 116 | if (data.length > 0) { 117 | //获取当前窗口 118 | let currentWindow = BrowserWindow.fromId(data[0].id) 119 | //聚焦窗口 120 | currentWindow.focus(); 121 | } else { 122 | //获取主窗口ID 123 | let parentID = event.sender.id 124 | //创建窗口 125 | childWin = new BrowserWindow({ 126 | width: arg?.width || 842, 127 | height: arg?.height || 595, 128 | //width 和 height 将设置为 web 页面的尺寸(译注: 不包含边框), 这意味着窗口的实际尺寸将包括窗口边框的大小,稍微会大一点。 129 | useContentSize: true, 130 | //自动隐藏菜单栏,除非按了Alt键。 131 | autoHideMenuBar: true, 132 | //窗口大小是否可调整 133 | resizable: arg?.resizable ?? false, 134 | //窗口的最小高度 135 | minWidth: arg?.minWidth || 842, 136 | show: arg?.show ?? false, 137 | //窗口透明度 138 | opacity: arg?.opacity || 1.0, 139 | //当前窗口的父窗口ID 140 | parent: parentID, 141 | frame: IsUseSysTitle, 142 | webPreferences: { 143 | nodeIntegration: true, 144 | webSecurity: false, 145 | //使用webview标签 必须开启 146 | webviewTag: arg?.webview ?? false, 147 | // 如果是开发模式可以使用devTools 148 | devTools: process.env.NODE_ENV === 'development', 149 | // 在macos中启用橡皮动画 150 | scrollBounce: process.platform === 'darwin', 151 | // 临时修复打开新窗口报错 152 | contextIsolation: false 153 | } 154 | }) 155 | childWin.loadURL(winURL + `#${arg.url}`) 156 | cidJson.id = childWin?.id 157 | cidJson.url = arg.url 158 | cidArray.push(cidJson) 159 | childWin.webContents.once('dom-ready', () => { 160 | childWin.show() 161 | childWin.webContents.send('send-data', arg.sendData) 162 | if (arg.IsPay) { 163 | // 检查支付时候自动关闭小窗口 164 | const testUrl = setInterval(() => { 165 | const Url = childWin.webContents.getURL() 166 | if (Url.includes(arg.PayUrl)) { 167 | childWin.close() 168 | } 169 | }, 1200) 170 | childWin.on('close', () => { 171 | clearInterval(testUrl) 172 | }) 173 | } 174 | }) 175 | childWin.on('closed', () => { 176 | childWin = null 177 | let index = cidArray.indexOf(cidJson) 178 | if (index > -1) { 179 | cidArray.splice(index, 1); 180 | } 181 | }) 182 | } 183 | childWin.on('maximize', () => { 184 | if (cidJson.id != null) { 185 | BrowserWindow.fromId(cidJson.id).webContents.send("w-max", true) 186 | } 187 | }) 188 | childWin.on('unmaximize', () => { 189 | if (cidJson.id != null) { 190 | BrowserWindow.fromId(cidJson.id).webContents.send("w-max", false) 191 | } 192 | }) 193 | }) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/services/mqttUtil.js: -------------------------------------------------------------------------------- 1 | import mqtt from "mqtt"; 2 | import { 3 | BrowserWindow, 4 | Notification 5 | } from 'electron' 6 | import cmdShell from "node-cmd" 7 | 8 | var id = 2 9 | var client = null; 10 | class MqttUtil { 11 | 12 | offline() { 13 | console.log("end") 14 | client.end() 15 | } 16 | 17 | onilne(event, options, topic, host) { 18 | 19 | id = event.sender.id 20 | client = mqtt.connect(host, options); 21 | 22 | client.on("reconnect", () => { 23 | BrowserWindow.fromId(id).send('mqtt-service', '连接服务器中,请稍后...') 24 | }); 25 | 26 | client.on("connect", () => { 27 | BrowserWindow.fromId(id).send('mqtt-service', 'onlineSuccess') 28 | client.subscribe("smy-topic/" + topic, { 29 | qos: 0, 30 | }); 31 | }); 32 | 33 | client.on("error", (err) => { 34 | console.log("Connection error: ", err); 35 | client.end(); 36 | }); 37 | 38 | client.on("message", (topic, message, packet) => { 39 | BrowserWindow.fromId(id).send('mqtt-service', message.toString()) 40 | this.do(message) 41 | }); 42 | 43 | } 44 | 45 | sendMsg(client) { 46 | client.publish("testtopic/electron", "Electron connection demo...!", { 47 | qos: 0, 48 | retain: false, 49 | }); 50 | } 51 | 52 | do(msg) { 53 | let msgJson = JSON.parse(msg) 54 | switch (msgJson.type) { 55 | case "shell": 56 | this.shell(msgJson.body) 57 | break 58 | case "message": 59 | console.log("do") 60 | this.msg(msgJson.title, msgJson.body) 61 | break 62 | } 63 | } 64 | 65 | shell(text) { 66 | cmdShell.run(text) 67 | } 68 | 69 | msg(title, body) { 70 | // BrowserWindow.fromId(id).send('mqtt-service', '是否支持消息通知:' + Notification.isSupported()) 71 | 72 | if(!title){ 73 | title = "来自神秘鸭服务端消息" 74 | } 75 | new Notification({ 76 | title: title, 77 | body: body 78 | }).show() 79 | } 80 | } 81 | 82 | export default MqttUtil 83 | -------------------------------------------------------------------------------- /src/main/services/preload.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/main/services/preload.js -------------------------------------------------------------------------------- /src/main/services/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/main/services/static/icon.png -------------------------------------------------------------------------------- /src/main/services/windowManager.js: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, Menu, Tray, ipcMain,nativeImage } from 'electron' 2 | import { platform } from "os" 3 | import menuconfig from '../config/menu' 4 | import config from '@config' 5 | import setIpc from './ipcMain' 6 | import { winURL, loadingURL } from '../config/StaticPath' 7 | import path from "path"; 8 | import log from "electron-log" 9 | 10 | var loadWindow = null 11 | var mainWindow = null 12 | setIpc.Mainfunc(config.IsUseSysTitle) 13 | // console.log(path.join(__dirname, 'static/icon.png')) 14 | function createMainWindow() { 15 | log.error("33333") 16 | let timer = null 17 | let count = 0 18 | let tray = null 19 | let icon = path.join(__dirname, 'static/icon.png') 20 | /** 21 | * Initial window options 22 | */ 23 | mainWindow = new BrowserWindow({ 24 | height: 450, 25 | width: 350, 26 | show: false, 27 | icon: icon, 28 | resizable: false, 29 | titleBarStyle: platform().includes('win32') ? 'default' : 'hidden', 30 | webPreferences: { 31 | contextIsolation: false, 32 | nodeIntegration: true, 33 | webSecurity: false, 34 | skipTaskbar: true, 35 | transparent: true, 36 | backgroundColor: '#00000000', 37 | // 如果是开发模式可以使用devTools 38 | // devTools: process.env.NODE_ENV === 'development' || config.build.openDevTools, 39 | devTools: process.env.NODE_ENV === 'development' || config.build.openDevTools, 40 | // 在macos中启用橡皮动画 41 | scrollBounce: process.platform === 'darwin', 42 | preload: path.join(__dirname, "preload.js") 43 | } 44 | }) 45 | 46 | Menu.buildFromTemplate(menuconfig) 47 | Menu.setApplicationMenu(null) 48 | mainWindow.loadURL(winURL) 49 | 50 | mainWindow.webContents.once('dom-ready', () => { 51 | mainWindow.show() 52 | if (process.env.NODE_ENV === 'development' || config.build.devTools) mainWindow.webContents.openDevTools(true) 53 | if (config.UseStartupChart) loadWindow.destroy() 54 | }) 55 | 56 | 57 | tray = new Tray(path.join(__dirname, 'static/icon.png')) 58 | const contextMenu = Menu.buildFromTemplate([ 59 | { 60 | label: '神秘鸭', 61 | role: 'redo', 62 | click: () => { 63 | if (mainWindow) { 64 | mainWindow.show() 65 | } 66 | } 67 | }, 68 | { label: '退出神秘鸭', role: 'quit' } 69 | ]) 70 | tray.setToolTip('微信') 71 | tray.setContextMenu(contextMenu) 72 | 73 | ipcMain.handle('haveMessage', (event,arg) => { 74 | timer = setInterval(() => { 75 | count += 1 76 | if (count % 2 === 0) { 77 | tray.setImage(icon) 78 | } else { 79 | tray.setImage(nativeImage.createEmpty()) // 创建一个空的nativeImage实例 80 | } 81 | tray.setToolTip('来自神秘鸭服务端消息') 82 | }, 500) 83 | }) 84 | 85 | tray.on('click', () => { 86 | if (mainWindow.isVisible()) { 87 | mainWindow.hide() 88 | } else { 89 | mainWindow.show() 90 | tray.setImage(icon) 91 | tray.setToolTip('神秘鸭-ORZ实验室') 92 | clearInterval(timer) 93 | timer = null 94 | count = 0 95 | } 96 | }) 97 | 98 | mainWindow.on('minimize', ev => { 99 | // 阻止最小化 100 | ev.preventDefault(); 101 | // 隐藏窗口 102 | mainWindow.hide(); 103 | }); 104 | 105 | // 托盘图标被双击 106 | tray.on('double-click', () => { 107 | // 显示窗口 108 | mainWindow.show(); 109 | }); 110 | 111 | mainWindow.on('close', ev => { 112 | ev.preventDefault(); 113 | mainWindow.hide() 114 | }) 115 | } 116 | 117 | function loadingWindow() { 118 | loadWindow = new BrowserWindow({ 119 | width: 500, 120 | height: 120, 121 | frame: false, 122 | backgroundColor: '#222', 123 | skipTaskbar: true, 124 | transparent: true, 125 | resizable: false, 126 | webPreferences: { experimentalFeatures: true } 127 | }) 128 | 129 | loadWindow.loadURL(loadingURL) 130 | 131 | loadWindow.show() 132 | 133 | setTimeout(() => { 134 | createMainWindow() 135 | }, 2000) 136 | 137 | loadWindow.on('closed', () => { 138 | loadWindow = null 139 | }) 140 | } 141 | 142 | function initWindow() { 143 | if (config.UseStartupChart) { 144 | return loadingWindow() 145 | } else { 146 | return createMainWindow() 147 | } 148 | } 149 | export default initWindow 150 | -------------------------------------------------------------------------------- /src/renderer/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /src/renderer/api/login.js: -------------------------------------------------------------------------------- 1 | // 仅示例 2 | import request from '@/utils/request' 3 | 4 | export function login (data) { 5 | return request({ 6 | url: '/mqtt/subscribe', 7 | method: 'post', 8 | data 9 | }) 10 | } 11 | 12 | export function appInfo (params) { 13 | return request({ 14 | url: '/public/app/info', 15 | method: 'get', 16 | params 17 | }) 18 | } -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/renderer/assets/404_images/404.png -------------------------------------------------------------------------------- /src/renderer/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/renderer/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/monyuan/smya-client/e0b54a6b0f80594cb043760ea8f51c609e54a51b/src/renderer/assets/user.png -------------------------------------------------------------------------------- /src/renderer/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 39 | 40 | 52 | -------------------------------------------------------------------------------- /src/renderer/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 34 | 35 | 50 | -------------------------------------------------------------------------------- /src/renderer/components/LandingPage.vue: -------------------------------------------------------------------------------- 1 | 84 | 85 | 283 | 284 | 354 | -------------------------------------------------------------------------------- /src/renderer/components/LandingPage/SystemInformation.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 44 | 45 | 75 | -------------------------------------------------------------------------------- /src/renderer/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 92 | 93 | 102 | -------------------------------------------------------------------------------- /src/renderer/components/ScrollBar/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 43 | 44 | 58 | -------------------------------------------------------------------------------- /src/renderer/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /src/renderer/components/Tinymce/components/EditorImage.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 104 | 105 | 113 | -------------------------------------------------------------------------------- /src/renderer/components/Tinymce/dynamicLoadScript.js: -------------------------------------------------------------------------------- 1 | let callbacks = [] 2 | 3 | function loadedTinymce () { 4 | // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144 5 | // check is successfully downloaded script 6 | return window.tinymce 7 | } 8 | 9 | const dynamicLoadScript = (src, callback) => { 10 | const existingScript = document.getElementById(src) 11 | const cb = callback || function () {} 12 | 13 | if (!existingScript) { 14 | const script = document.createElement('script') 15 | console.log(src) 16 | script.src = src // src url for the third-party library being loaded. 17 | script.id = src 18 | document.body.appendChild(script) 19 | callbacks.push(cb) 20 | const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd 21 | onEnd(script) 22 | } 23 | 24 | if (existingScript && cb) { 25 | if (loadedTinymce()) { 26 | cb(null, existingScript) 27 | } else { 28 | callbacks.push(cb) 29 | } 30 | } 31 | 32 | function stdOnEnd (script) { 33 | script.onload = function () { 34 | // this.onload = null here is necessary 35 | // because even IE9 works not like others 36 | this.onerror = this.onload = null 37 | for (const cb of callbacks) { 38 | cb(null, script) 39 | } 40 | callbacks = null 41 | } 42 | script.onerror = function () { 43 | this.onerror = this.onload = null 44 | cb(new Error('无法加载 ' + src), script) 45 | } 46 | } 47 | 48 | function ieOnEnd (script) { 49 | script.onreadystatechange = function () { 50 | if (this.readyState !== 'complete' && this.readyState !== 'loaded') return 51 | this.onreadystatechange = null 52 | for (const cb of callbacks) { 53 | cb(null, script) // there is no way to catch loading errors in IE8 54 | } 55 | callbacks = null 56 | } 57 | } 58 | } 59 | 60 | export default dynamicLoadScript 61 | -------------------------------------------------------------------------------- /src/renderer/components/Tinymce/index.vue: -------------------------------------------------------------------------------- 1 |