Bladeren bron

feat: 盼达点餐项目

suwenjiang 2 weken geleden
commit
c7dd7a65a0
100 gewijzigde bestanden met toevoegingen van 5409 en 0 verwijderingen
  1. 95 0
      .babelrc
  2. 122 0
      .electron-vue/build.js
  3. 210 0
      .electron-vue/dev-runner.js
  4. 122 0
      .electron-vue/hot-updater.js
  5. 101 0
      .electron-vue/utils.js
  6. 99 0
      .electron-vue/webpack.main.config.js
  7. 219 0
      .electron-vue/webpack.renderer.config.js
  8. 3 0
      .eslintignore
  9. 27 0
      .eslintrc.js
  10. 29 0
      .github/workflows/build-test.yml
  11. 18 0
      .gitignore
  12. 5 0
      .postcssrc.js
  13. 247 0
      CHANGELOG.md
  14. 21 0
      LICENSE
  15. 80 0
      README.md
  16. 36 0
      README_ZH.md
  17. 220 0
      build/builder-debug.yml
  18. BIN
      build/icons/256x256.png
  19. BIN
      build/icons/icon.icns
  20. BIN
      build/icons/icon.ico
  21. 8 0
      build/latest.yml
  22. 12 0
      config/index.js
  23. 3 0
      env/.env
  24. 2 0
      env/sit.env
  25. 0 0
      lib/updater.html
  26. 157 0
      package.json
  27. 12 0
      server/index.js
  28. 32 0
      src/index.ejs
  29. 12 0
      src/main/config/DisableButton.js
  30. 17 0
      src/main/config/StaticPath.js
  31. 7 0
      src/main/config/const.js
  32. 6 0
      src/main/config/hotPublish.js
  33. 68 0
      src/main/config/menu.js
  34. 41 0
      src/main/index.js
  35. 44 0
      src/main/server/index.js
  36. 14 0
      src/main/server/server.js
  37. 91 0
      src/main/services/HotUpdater.js
  38. 76 0
      src/main/services/checkupdate.js
  39. 63 0
      src/main/services/downloadFile.js
  40. 169 0
      src/main/services/ipcMain.js
  41. 105 0
      src/main/services/windowManager.js
  42. 17 0
      src/renderer/App.vue
  43. 58 0
      src/renderer/api/balance.js
  44. 124 0
      src/renderer/api/cashier.js
  45. 75 0
      src/renderer/api/coupon.js
  46. 76 0
      src/renderer/api/goods.js
  47. 51 0
      src/renderer/api/login.js
  48. 65 0
      src/renderer/api/member.js
  49. 83 0
      src/renderer/api/order.js
  50. 58 0
      src/renderer/api/point.js
  51. 44 0
      src/renderer/api/refund.js
  52. 10 0
      src/renderer/api/staff.js
  53. 0 0
      src/renderer/assets/.gitkeep
  54. BIN
      src/renderer/assets/404_images/404.png
  55. BIN
      src/renderer/assets/404_images/404_cloud.png
  56. BIN
      src/renderer/assets/images/avatar.png
  57. BIN
      src/renderer/assets/images/bg.png
  58. BIN
      src/renderer/assets/images/cashier.png
  59. BIN
      src/renderer/assets/images/goods.png
  60. BIN
      src/renderer/assets/images/hot.png
  61. BIN
      src/renderer/assets/images/life.png
  62. BIN
      src/renderer/assets/images/love.png
  63. BIN
      src/renderer/assets/images/noGoods.png
  64. BIN
      src/renderer/assets/images/office.png
  65. BIN
      src/renderer/assets/images/order.png
  66. BIN
      src/renderer/assets/logo.png
  67. BIN
      src/renderer/assets/user.png
  68. 51 0
      src/renderer/components/Breadcrumb/index.vue
  69. 46 0
      src/renderer/components/Hamburger/index.vue
  70. 340 0
      src/renderer/components/LandingPage.vue
  71. 75 0
      src/renderer/components/LandingPage/SystemInformation.vue
  72. 114 0
      src/renderer/components/Pagination/index.vue
  73. 57 0
      src/renderer/components/ScrollBar/index.vue
  74. 42 0
      src/renderer/components/SvgIcon/index.vue
  75. 112 0
      src/renderer/components/Tinymce/components/EditorImage.vue
  76. 60 0
      src/renderer/components/Tinymce/dynamicLoadScript.js
  77. 223 0
      src/renderer/components/Tinymce/index.vue
  78. 7 0
      src/renderer/components/Tinymce/plugins.js
  79. 9 0
      src/renderer/components/Tinymce/toolbar.js
  80. 116 0
      src/renderer/components/title/index.vue
  81. 64 0
      src/renderer/directive/dialog/drag.js
  82. 34 0
      src/renderer/directive/dialog/dragHeight.js
  83. 30 0
      src/renderer/directive/dialog/dragWidth.js
  84. 23 0
      src/renderer/directive/index.js
  85. 54 0
      src/renderer/directive/module/clipboard.js
  86. 23 0
      src/renderer/directive/permission/hasPermi.js
  87. 22 0
      src/renderer/directive/permission/hasRole.js
  88. 17 0
      src/renderer/error.js
  89. 7 0
      src/renderer/hooks/use-router.js
  90. 25 0
      src/renderer/i18n/index.js
  91. 30 0
      src/renderer/i18n/languages/en.js
  92. 30 0
      src/renderer/i18n/languages/zh-CN.js
  93. 9 0
      src/renderer/icons/index.js
  94. 1 0
      src/renderer/icons/svg/close.svg
  95. 529 0
      src/renderer/icons/svg/electron-logo.svg
  96. 1 0
      src/renderer/icons/svg/example.svg
  97. 1 0
      src/renderer/icons/svg/eye-open.svg
  98. 1 0
      src/renderer/icons/svg/eye.svg
  99. 1 0
      src/renderer/icons/svg/form.svg
  100. 1 0
      src/renderer/icons/svg/logo.svg

+ 95 - 0
.babelrc

@@ -0,0 +1,95 @@
+{
+    "comments": false,
+    "env": {
+        "renderer": {
+            "presets": [
+                [
+                    "@babel/preset-env",
+                    {
+                        "modules": false,
+                        "useBuiltIns": "entry",
+                        "corejs": 3
+                    }
+                ],
+                [
+                    "@vue/babel-preset-jsx"
+                ]
+            ],
+            "plugins": [
+                "@babel/plugin-syntax-dynamic-import",
+                "@babel/plugin-syntax-import-meta",
+                "@babel/plugin-proposal-class-properties",
+                "@babel/plugin-proposal-json-strings",
+                [
+                    "@babel/plugin-proposal-decorators",
+                    {
+                        "legacy": true
+                    }
+                ],
+                "@babel/plugin-proposal-function-sent",
+                "@babel/plugin-proposal-export-namespace-from",
+                "@babel/plugin-proposal-numeric-separator",
+                "@babel/plugin-proposal-throw-expressions",
+                "@babel/plugin-proposal-export-default-from",
+                "@babel/plugin-proposal-logical-assignment-operators",
+                "@babel/plugin-proposal-optional-chaining",
+                [
+                    "@babel/plugin-proposal-pipeline-operator",
+                    {
+                        "proposal": "minimal"
+                    }
+                ],
+                "@babel/plugin-proposal-nullish-coalescing-operator",
+                "@babel/plugin-proposal-do-expressions",
+                "@babel/plugin-proposal-function-bind"
+            ]
+        },
+        "web": {
+            "presets": [
+                [
+                    "@babel/preset-env",
+                    {
+                        "modules": false,
+                        "useBuiltIns": "entry",
+                        "corejs": 3
+                    }
+                ],
+                [
+                    "@vue/babel-preset-jsx"
+                ]
+            ],
+            "plugins": [
+                "@babel/plugin-syntax-dynamic-import",
+                "@babel/plugin-syntax-import-meta",
+                "@babel/plugin-proposal-class-properties",
+                "@babel/plugin-proposal-json-strings",
+                [
+                    "@babel/plugin-proposal-decorators",
+                    {
+                        "legacy": true
+                    }
+                ],
+                "@babel/plugin-proposal-function-sent",
+                "@babel/plugin-proposal-export-namespace-from",
+                "@babel/plugin-proposal-numeric-separator",
+                "@babel/plugin-proposal-throw-expressions",
+                "@babel/plugin-proposal-export-default-from",
+                "@babel/plugin-proposal-logical-assignment-operators",
+                "@babel/plugin-proposal-optional-chaining",
+                [
+                    "@babel/plugin-proposal-pipeline-operator",
+                    {
+                        "proposal": "minimal"
+                    }
+                ],
+                "@babel/plugin-proposal-nullish-coalescing-operator",
+                "@babel/plugin-proposal-do-expressions",
+                "@babel/plugin-proposal-function-bind"
+            ]
+        }
+    },
+    "plugins": [
+        "@babel/transform-runtime",
+        "@babel/plugin-syntax-dynamic-import"
+    ]
+}

+ 122 - 0
.electron-vue/build.js

@@ -0,0 +1,122 @@
+'use strict'
+process.env.NODE_ENV = 'production'
+const { say } = require('cfonts')
+const chalk = require('chalk')
+const del = require('del')
+const webpack = require('webpack')
+const { Listr } = require('listr2')
+
+
+const mainConfig = require('./webpack.main.config')
+const rendererConfig = require('./webpack.renderer.config')
+
+const doneLog = chalk.bgGreen.white(' DONE ') + ' '
+const errorLog = chalk.bgRed.white(' ERROR ') + ' '
+const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
+const isCI = process.env.CI || false
+
+if (process.env.BUILD_TARGET === 'web') web()
+else build()
+
+function clean() {
+  del.sync(['dist/electron/*', 'build/*', '!build/icons', '!build/lib', '!build/lib/electron-build.*', '!build/icons/icon.*'])
+  console.log(`\n${doneLog}clear done`)
+  if (process.env.BUILD_TARGET === 'onlyClean') process.exit()
+}
+
+function build() {
+  greeting()
+  if (process.env.BUILD_TARGET === 'clean' || process.env.BUILD_TARGET === 'onlyClean') clean()
+  const tasksLister = new Listr([
+    {
+      title: 'building main process',
+      task: async (_, tasks) => {
+        try {
+          await pack(mainConfig)
+        } catch (error) {
+          console.error(`\n${error}\n`)
+          console.log(`\n  ${errorLog}failed to build main process`)
+          process.exit(1)
+        }
+      }
+    },
+    {
+      title: "building renderer process",
+      task: async (_, tasks) => {
+        try {
+          await pack(rendererConfig)
+          tasks.output = `${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`
+        } catch (error) {
+          console.error(`\n${error}\n`)
+          console.log(`\n  ${errorLog}failed to build renderer process`)
+          process.exit(1)
+        }
+      },
+      options: { persistentOutput: true }
+    }
+  ], {
+    exitOnError: true
+  })
+  tasksLister.run()
+}
+
+function pack(config) {
+  return new Promise((resolve, reject) => {
+    config.mode = 'production'
+    webpack(config, (err, stats) => {
+      if (err) reject(err.stack || err)
+      else if (stats.hasErrors()) {
+        let err = ''
+
+        stats.toString({
+          chunks: false,
+          colors: true
+        })
+          .split(/\r?\n/)
+          .forEach(line => {
+            err += `    ${line}\n`
+          })
+
+        reject(err)
+      } else {
+        resolve(stats.toString({
+          chunks: false,
+          colors: true
+        }))
+      }
+    })
+  })
+}
+
+function web() {
+  del.sync(['dist/web/*', '!.gitkeep'])
+  rendererConfig.mode = 'production'
+  webpack(rendererConfig, (err, stats) => {
+    if (err || stats.hasErrors()) console.log(err)
+
+    console.log(stats.toString({
+      chunks: false,
+      colors: true
+    }))
+
+    process.exit()
+  })
+}
+
+function greeting() {
+  const cols = process.stdout.columns
+  let text = ''
+
+  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()
+}

+ 210 - 0
.electron-vue/dev-runner.js

@@ -0,0 +1,210 @@
+'use strict'
+
+process.env.NODE_ENV = 'development'
+
+const chalk = require('chalk')
+const electron = require('electron')
+const path = require('path')
+const { say } = require('cfonts')
+const { spawn } = require('child_process')
+const config = require('../config')
+const webpack = require('webpack')
+const WebpackDevServer = require('webpack-dev-server')
+const Portfinder = require("portfinder")
+
+const mainConfig = require('./webpack.main.config')
+const rendererConfig = require('./webpack.renderer.config')
+
+let electronProcess = null
+let manualRestart = false
+
+function logStats(proc, data) {
+  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) {
+  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() {
+  return new Promise((resolve, reject) => {
+    rendererConfig.mode = 'development'
+    Portfinder.basePort = config.dev.port || 9080
+    Portfinder.getPort((err, port) => {
+      if (err) {
+        reject("PortError:" + err)
+      } else {
+        const compiler = webpack(rendererConfig)
+
+        compiler.hooks.done.tap('done', stats => {
+          logStats('Renderer', stats)
+        })
+
+        const server = new WebpackDevServer(
+          {
+            port,
+            static: {
+              directory: path.join(__dirname, '..', 'static'),
+              publicPath: '/static/',
+            }
+          },
+          compiler
+        )
+
+        process.env.PORT = port
+        server.start().then(() => {
+          resolve()
+        })
+
+      }
+    })
+
+  })
+}
+
+function startMain() {
+  return new Promise((resolve) => {
+    mainConfig.mode = 'development'
+    const compiler = webpack(mainConfig)
+
+    compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
+      logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, chalk.white.bold(`${config.dev.chineseLog ? '正在处理资源文件...' : 'compiling...'}`))
+      done()
+    })
+
+    compiler.watch({}, (err, stats) => {
+      if (err) {
+        console.log(err)
+        return
+      }
+
+      logStats(`${config.dev.chineseLog ? '主进程' : 'Main'}`, stats)
+
+      if (electronProcess && electronProcess.kill) {
+        manualRestart = true
+        process.kill(electronProcess.pid)
+        electronProcess = null
+        startElectron()
+
+        setTimeout(() => {
+          manualRestart = false
+        }, 5000)
+      }
+
+      resolve()
+    })
+  })
+}
+
+function startElectron() {
+  var args = [
+    '--inspect=5858',
+    path.join(__dirname, '../dist/electron/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, args)
+
+  electronProcess.stdout.on('data', data => {
+    electronLog(removeJunk(data), 'blue')
+  })
+  electronProcess.stderr.on('data', data => {
+    electronLog(removeJunk(data), 'red')
+  })
+
+  electronProcess.on('close', () => {
+    if (!manualRestart) process.exit()
+  })
+}
+
+function electronLog(data, color) {
+  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() {
+  const cols = process.stdout.columns
+  let text = ''
+
+  if (cols > 104) text = 'electron-vue'
+  else if (cols > 76) text = 'electron-|vue'
+  else text = false
+
+  if (text) {
+    say(text, {
+      colors: ['yellow'],
+      font: 'simple3d',
+      space: false
+    })
+  } else console.log(chalk.yellow.bold('\n  electron-vue'))
+  console.log(chalk.blue(`${config.dev.chineseLog ? '  准备启动...' : '  getting ready...'}`) + '\n')
+}
+
+async function init() {
+  greeting()
+
+  try {
+    await startRenderer()
+    await startMain()
+    await startElectron()
+  } catch (error) {
+    console.error(error)
+  }
+
+}
+
+init()

+ 122 - 0
.electron-vue/hot-updater.js

@@ -0,0 +1,122 @@
+/**
+ * power by biuuu
+ */
+
+const chalk = require("chalk");
+const { join } = require('path')
+const crypto = require('crypto')
+const AdmZip = require('adm-zip')
+const packageFile = require('../package.json')
+const { build } = require("../config/index")
+const { platform } = require("os")
+const { ensureDir, emptyDir, copy, outputJSON, remove, stat, readFile } = require("fs-extra");
+
+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 = crypto.createHmac(type, 'Sky')
+    hmac.update(data)
+    return hmac.digest('hex')
+}
+
+const createZip = (filePath, dest) => {
+    const zip = new AdmZip()
+    zip.addLocalFolder(filePath)
+    zip.toBuffer()
+    zip.writeZip(dest)
+}
+
+const start = async () => {
+    console.log(chalk.green.bold(`\n  Start packing`))
+
+    if (packageFile.build.asar) {
+        console.log(
+            "\n" +
+            chalk.bgRed.white(" ERROR ") +
+            "  " +
+            chalk.red("Please make sure the build.asar option in the Package.json file is set to false") +
+            "\n"
+        );
+        return;
+    }
+
+    if (build.hotPublishConfigName === '') {
+        console.log(
+            "\n" +
+            chalk.bgRed.white(" ERROR ") +
+            "  " +
+            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) {
+            console.log(
+                "\n" +
+                chalk.bgRed.white(" ERROR ") +
+                "  " +
+                chalk.red("No resource files were found, please execute this command after the build command") +
+                "\n"
+            );
+            return;
+        }
+
+        try {
+            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);
+            await outputJSON(join(packPackagePath, "package.json"), {
+                name: packageFile.name,
+                productName: packageFile.productName,
+                version: packageFile.version,
+                private: packageFile.private,
+                description: packageFile.description,
+                main: packageFile.main,
+                author: packageFile.author,
+                dependencies: packageFile.dependencies
+            });
+            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 remove(zipPath);
+            await remove(appPath)
+            await outputJSON(join(outputPath, `${build.hotPublishConfigName}.json`),
+                {
+                    version: packageFile.version,
+                    name: `${hashName}.zip`,
+                    hash: sha256
+                }
+            );
+            console.log(
+                "\n" + chalk.bgGreen.white(" DONE ") + "  " + "The resource file is packaged!\n"
+            );
+            console.log("File location: " + chalk.green(outputPath) + "\n");
+        } catch (error) {
+            console.log(
+                "\n" +
+                chalk.bgRed.white(" ERROR ") +
+                "  " +
+                chalk.red(error.message || error) +
+                "\n"
+            );
+            process.exit(1)
+        }
+    });
+}
+
+start()

+ 101 - 0
.electron-vue/utils.js

@@ -0,0 +1,101 @@
+'use strict'
+const MiniCssPlugin = require('mini-css-extract-plugin');
+const dotenv = require('dotenv')
+const { join } = require("path")
+const argv = require('minimist')(process.argv.slice(2));
+const rootResolve = (...pathSegments) => join(__dirname, '..', ...pathSegments)
+
+function getEnv() {
+    return argv['m']
+}
+function getEnvPath() {
+    if (String(typeof getEnv()) === 'boolean' || String(typeof getEnv()) === 'undefined') {
+        return rootResolve('env/.env')
+    }
+    return rootResolve(`env/${getEnv()}.env`)
+}
+function getConfig() {
+    return dotenv.config({ path: getEnvPath() }).parsed
+}
+
+// 获取环境
+exports.getEnv = getEnv()
+// 获取配置
+exports.getConfig = getConfig()
+
+exports.cssLoaders = function (options) {
+    options = options || {}
+    const esbuildCss = {
+        loader: 'esbuild-loader',
+        options: {
+            loader: 'css',
+            minify: options.minifyCss
+        }
+    }
+
+    const cssLoader = {
+        loader: 'css-loader',
+        options: {
+            sourceMap: options.sourceMap,
+            esModule: false
+        }
+    }
+
+    const postcssLoader = {
+        loader: 'postcss-loader',
+        options: {
+            sourceMap: options.sourceMap
+        }
+    }
+
+    // 这里就是生成loader和其对应的配置
+    function generateLoaders(loader, loaderOptions) {
+        const loaders = [cssLoader, postcssLoader, esbuildCss]
+
+        if (loader) {
+            loaders.push({
+                loader: loader + '-loader',
+                options: Object.assign({}, loaderOptions, {
+                    sourceMap: options.sourceMap
+                })
+            })
+        }
+
+        // 当配置信息中开启此项时,启用css分离压缩
+        // 这一项在生产环境时,是默认开启的
+        if (options.extract) {
+            return [MiniCssPlugin.loader].concat(loaders)
+        } else {
+            // 如果不开启则让vue-style-loader来处理
+            return ['vue-style-loader'].concat(loaders)
+        }
+    }
+
+    // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+    return {
+        css: generateLoaders(),
+        postcss: generateLoaders(),
+        less: generateLoaders('less'),
+        sass: generateLoaders('sass', { indentedSyntax: true }),
+        scss: generateLoaders('sass'),
+        stylus: generateLoaders('stylus'),
+        styl: generateLoaders('stylus')
+    }
+}
+
+// 根据上面的函数遍历出来的各个css预处理器的loader进行最后的拼装
+exports.styleLoaders = function (options) {
+    const output = []
+    const loaders = exports.cssLoaders(options)
+
+
+    for (const extension in loaders) {
+        const loader = loaders[extension]
+        output.push({
+            test: new RegExp('\\.' + extension + '$'),
+            use: loader
+        })
+    }
+
+    return output
+}

+ 99 - 0
.electron-vue/webpack.main.config.js

@@ -0,0 +1,99 @@
+'use strict'
+
+process.env.BABEL_ENV = 'main'
+
+const path = require('path')
+const { dependencies } = require('../package.json')
+const webpack = require('webpack')
+const TerserPlugin = require('terser-webpack-plugin')
+const config = require('../config')
+const { getConfig } = require("./utils")
+
+function resolve(dir) {
+  return path.join(__dirname, '..', dir)
+}
+
+let mainConfig = {
+  infrastructureLogging: {
+    level: 'warn'
+  },
+  entry: {
+    main: path.join(__dirname, '../src/main/index.js')
+  },
+  externals: [
+    ...Object.keys(dependencies || {})
+  ],
+  module: {
+    rules: [
+      {
+        test: /\.js$/,
+        loader: 'esbuild-loader'
+      },
+      {
+        test: /\.node$/,
+        use: 'node-loader'
+      }
+    ]
+  },
+  node: {
+    __dirname: process.env.NODE_ENV !== 'production',
+    __filename: process.env.NODE_ENV !== 'production'
+  },
+  output: {
+    filename: '[name].js',
+    libraryTarget: 'commonjs2',
+    path: path.join(__dirname, '../dist/electron')
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env.userConfig':JSON.stringify(getConfig)
+    })
+  ],
+  resolve: {
+    alias: {
+      '@config': resolve('config'),
+    },
+    extensions: ['.js', '.json', '.node']
+  },
+  target: 'electron-main',
+}
+
+/**
+ * Adjust mainConfig for development settings
+ */
+if (process.env.NODE_ENV !== 'production') {
+  mainConfig.plugins.push(
+    new webpack.DefinePlugin({
+      '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`,
+      'process.env.libPath': `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"`
+    })
+  )
+}
+
+/**
+ * Adjust mainConfig for production settings
+ */
+if (process.env.NODE_ENV === 'production' && config.build.cleanConsole) {
+  mainConfig.optimization = {
+    minimize: true,
+    minimizer: [
+      new TerserPlugin({
+        terserOptions: {
+          compress: {
+            drop_console: true,
+            drop_debugger: true,
+            pure_funcs: ["console.log", "console.warn"]
+          }
+        }
+
+      })
+    ]
+  }
+  mainConfig.plugins.push(
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': '"production"'
+    })
+  )
+}
+
+module.exports = mainConfig

+ 219 - 0
.electron-vue/webpack.renderer.config.js

@@ -0,0 +1,219 @@
+'use strict'
+
+const IsWeb = process.env.BUILD_TARGET === 'web'
+process.env.BABEL_ENV = IsWeb ? 'web' : 'renderer'
+
+const path = require('path')
+const { dependencies } = require('../package.json')
+const webpack = require('webpack')
+const config = require('../config')
+const { styleLoaders } = require('./utils')
+
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const TerserPlugin = require('terser-webpack-plugin')
+// const ESLintPlugin = require('eslint-webpack-plugin');
+const { VueLoaderPlugin } = require('vue-loader')
+const { getConfig } = require("./utils")
+
+function resolve(dir) {
+  return path.join(__dirname, '..', dir)
+}
+/**
+ * List of node_modules to include in webpack bundle
+ *
+ * Required for specific packages like Vue UI libraries
+ * that provide pure *.vue files that need compiling
+ * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
+ */
+
+let rendererConfig = {
+  entry: IsWeb ? { web: path.join(__dirname, '../src/renderer/main.js') } : { renderer: resolve('src/renderer/main.js') },
+  infrastructureLogging: { level: 'warn' },
+  stats: 'none',
+  module: {
+    rules: [
+      {
+        test: /\.vue$/,
+        loader: "vue-loader",
+        options: {
+          babelParserPlugins: [
+            'jsx',
+            'classProperties',
+            'decorators-legacy'
+          ]
+        }
+      },
+      {
+        test: /\.jsx$/,
+        loader: 'babel-loader',
+      },
+      {
+        test: /\.html$/,
+        use: 'vue-html-loader'
+      },
+      {
+        test: /\.svg$/,
+        loader: 'svg-sprite-loader',
+        include: [resolve('src/renderer/icons')],
+        options: {
+          symbolId: 'icon-[name]'
+        }
+      },
+      {
+        test: /\.(png|jpe?g|gif)(\?.*)?$/,
+        type: "asset/resource",
+        generator: {
+          filename: 'imgs/[name]--[hash].[ext]'
+        }
+      },
+      {
+        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+        type: "asset/resource",
+        generator: {
+          filename: 'media/[name]--[hash].[ext]'
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        type: "asset/resource",
+        generator: {
+          filename: 'fonts/[name]--[hash].[ext]'
+        }
+      }
+    ]
+  },
+  node: {
+    __dirname: process.env.NODE_ENV !== 'production',
+    __filename: process.env.NODE_ENV !== 'production'
+  },
+  plugins: [
+    new VueLoaderPlugin(),
+    new MiniCssExtractPlugin(),
+    new webpack.DefinePlugin({
+      'process.env.userConfig': JSON.stringify(getConfig),
+      'process.env.IS_WEB': IsWeb
+    }),
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: resolve('src/index.ejs'),
+      minify: {
+        collapseWhitespace: true,
+        removeAttributeQuotes: true,
+        removeComments: true,
+        minifyJS: true,
+        minifyCSS: true
+      },
+      templateParameters(compilation, assets, options) {
+        return {
+          compilation: compilation,
+          webpack: compilation.getStats().toJson(),
+          webpackConfig: compilation.options,
+          htmlWebpackPlugin: {
+            files: assets,
+            options: options
+          },
+          process,
+        };
+      },
+      nodeModules: false
+    }),
+  ],
+  output: {
+    filename: '[name].js',
+    path: IsWeb ? path.join(__dirname, '../dist/web') : path.join(__dirname, '../dist/electron')
+  },
+  resolve: {
+    alias: {
+      '@': resolve('src/renderer'),
+      'vue$': 'vue/dist/vue.esm.js'
+    },
+    extensions: ['.js', '.vue', '.json', '.css', '.node']
+  },
+  target: IsWeb ? 'web' : 'electron-renderer'
+}
+// 将css相关得loader抽取出来
+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' }));
+(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', } })
+
+/**
+ * Adjust rendererConfig for development settings
+ */
+if (process.env.NODE_ENV !== 'production' && !IsWeb) {
+  rendererConfig.plugins.push(
+    new webpack.DefinePlugin({
+      __lib: `"${path.join(__dirname, `../${config.DllFolder}`).replace(/\\/g, '\\\\')}"`
+    })
+  )
+}
+
+/**
+ * Adjust rendererConfig for production settings
+ */
+if (process.env.NODE_ENV === 'production') {
+
+  rendererConfig.plugins.push(
+    new CopyWebpackPlugin({
+      patterns: [
+        {
+          from: path.join(__dirname, '../static'),
+          to: path.join(__dirname, '../dist/electron/static'),
+          globOptions: {
+            ignore: ['.*']
+          }
+        }
+      ]
+    }),
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': '"production"',
+    }),
+    new webpack.LoaderOptionsPlugin({
+      minimize: true
+    })
+  )
+  rendererConfig.optimization = {
+    minimize: true,
+    minimizer: [
+      new TerserPlugin({
+        terserOptions: {
+          compress: {
+            drop_console: true,
+            drop_debugger: true,
+            pure_funcs: ["console.log", "console.warn"]
+          }
+        }
+
+      })
+    ]
+  }
+  rendererConfig.optimization.splitChunks = {
+    chunks: "async",
+    cacheGroups: {
+      vendor: { // 将第三方模块提取出来
+        minSize: 30000,
+        minChunks: 1,
+        test: /node_modules/,
+        chunks: 'initial',
+        name: 'vendor',
+        priority: 1
+      },
+      commons: {
+        test: /[\\/]src[\\/]common[\\/]/,
+        name: 'commons',
+        minSize: 30000,
+        minChunks: 3,
+        chunks: 'initial',
+        priority: -1,
+        reuseExistingChunk: true // 这个配置允许我们使用已经存在的代码块
+      }
+    }
+  }
+  rendererConfig.optimization.runtimeChunk = { name: 'runtime' }
+} else {
+  rendererConfig.devtool = 'eval-source-map'
+  // eslint
+  // rendererConfig.plugins.push(new ESLintPlugin(config.dev.ESLintoptions))
+}
+
+module.exports = rendererConfig

+ 3 - 0
.eslintignore

@@ -0,0 +1,3 @@
+test/unit/coverage/**
+test/unit/*.js
+test/e2e/*.js

+ 27 - 0
.eslintrc.js

@@ -0,0 +1,27 @@
+module.exports = {
+  root: true,
+  parser: 'babel-eslint',
+  parserOptions: {
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true
+  },
+  extends: 'standard',
+  globals: {
+    __static: true,
+    __lib: true
+  },
+  plugins: [
+    'html'
+  ],
+  'rules': {
+    // allow paren-less arrow functions
+    'arrow-parens': 0,
+    // allow async-await
+    'generator-star-spacing': 0,
+    // allow debugger during development
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
+  }
+}

+ 29 - 0
.github/workflows/build-test.yml

@@ -0,0 +1,29 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Build TEST
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  build:
+
+    runs-on: windows-latest
+
+    strategy:
+      matrix:
+        node-version: [ 16.x, 18.x ]
+        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - run: yarn --frozen-lockfile
+    - run: yarn build

+ 18 - 0
.gitignore

@@ -0,0 +1,18 @@
+.DS_Store
+node_modules/
+/dist/
+build/win-unpacked/
+build/win-ia32-unpacked/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+server/client/fuintCashier Setup 0.0.1.exe
+build/builder-effective-config.yaml

+ 5 - 0
.postcssrc.js

@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {},
+  }
+}

+ 247 - 0
CHANGELOG.md

@@ -0,0 +1,247 @@
+# 更新日志
+
+# 2023 年 03 月 07 日
+
+- 修复 pinia加载问题
+
+# 2022 年 12 月 29 日
+
+- 清理无用的 config 注入,现在环境变量将不再依赖于 `config` 文件,转而使用 env 文件夹中的 `.env` 在改文件内的所有内容均会注入到 `process.env.userConfig` 主进程和渲染进程同名
+
+# 2022 年 06 月 10 日
+
+- 当需要使用 jsx 时,关闭 esbuild
+
+## 2021 年 08 月 26 日
+
+- 添加对 jsx 的支持
+
+## 2021 年 7 月 31 日
+
+- 将 webpack 更新为 5,安装对应的 babel,在打包成 web 时,使用 babel 进行代码垫片设置。
+
+## 2021 年 06 月 24 日
+
+- `i18n` 新增对 `element-ui` 的支持
+
+> 为保证 `element-ui` 正确国际化,请确保 `src/renderer/i18n/languages` 文件夹下的语言包名与 `element-ui` 的语言包名一致
+
+## 2021 年 06 月 23 日
+
+- 新增 [`i18n`](https://kazupon.github.io/vue-i18n/zh/) 支持
+
+### 如何使用?
+
+语言包存放在 `src/renderer/i18n/languages` 文件夹下,每种语言一个文件,新增语言包也无需手动引入
+
+## 2021 年 06 月 22 日
+
+- 尝试修复 mac 环境下窗口无法拖动问题
+
+## 2021 年 09 月 01 日 (9 月?)
+
+- 双分支添加 TERGET_ENV 标识,用于区分区分当前工作环境,如 test or alpha,beta,gamma;通过在`node .electron-vue/build.js`前添加`cross-env TERGET_ENV=test`,即可启用,相应的您应当在`config文件夹以及其index.js`文件中按照`development`对象写好相同的代码方可在主/渲染进程中使用`process.env.TERGET_ENV`来取得您的设置项。
+
+## 2021 年 04 月 13 日
+
+- **master 剔除了 babel 转使用 esbuild 对 js 和 css 进行压缩操作。**
+
+## 2021 年 03 月 03 日
+
+- 更新 electron 版本到 12(noLayOut 分支)
+
+## 2021 年 02 月 26 日
+
+- 添加 vite 版本,但并不合并到当前库中,请[前往对应库查看](https://gitee.com/Zh-Sky/electron-vite-template)
+
+## 2021 年 02 月 08 日
+
+- noLayOut 分支更新 webpack 版本到 5,并分别对所属依赖进行更新,在稍后的日子里,noLayOut 分支将跟进 vue3,感兴趣的小伙伴可以切换分支
+
+## 2020 年 10 月 12 日
+
+- 例行更新基础依赖,感谢 @jiumengs 贡献代码,修正删除数据库文件时,会发生多次监听。
+
+## 2020 年 09 月 10 日
+
+- 例行更新依赖,感谢 @BelinChung 贡献代码,修正 bug。
+
+## 2020 年 04 月 30 日
+
+- 添加内置服务端关闭方法,进一步简化登录流程;多窗口文档已就绪,服务端说明已补充。
+
+## 2020 年 04 月 29 日
+
+- 添加了路由多窗口示例,修复 web 打包,提升依赖;文档还未就绪
+
+## 2020 年 02 月 09 日
+
+- 添加[中文在线文档](https://umbrella22.github.io/electron-vue-template-doc/),[国内访问地址](https://zh-sky.gitee.io/electron-vue-template-doc/)
+- 剔除 win 打包依赖,因为太大了,将它放到码云的额外仓库中,[地址](https://gitee.com/Zh-Sky/HardToDownloadLib)
+
+## 2020 年 02 月 06 日
+
+- 激进分支更新至 8.0.0.
+
+## 2020 年 01 月 09 日
+
+- 例行更新依赖,在 dev 中加入了端口监听检测,如果 9080 端口被占用就会自动向后开启一个端口号并使用,同时在 config/index.js 的 dev 中加入了端口设置,可以快捷设置端口号而不用去更改 webpack 的配置了。
+
+## 2019 年 12 月 18 日
+
+- 我在 build 文件夹内添加了 windows 的打包依赖,在打包爆错的话,可以尝试使用/build/lib 内的压缩包,记得看使用说明哦~
+
+## 2019 年 11 月 22 日
+
+- 得益于群里老哥的提醒,通过修改系统环境变量得到了通过 yarn 下载 electron 失败的问题,具体操作如下:
+  - 用户环境变量中新增两个个变量,一个是变量名为`ELECTRON_MIRROR`,变量值为`https://npm.taobao.org/mirrors/electron/`,另一个是变量名为`registry`,变量值为`https://registry.npm.taobao.org/`,然后系统变量中同样也加上这两个值,完成之后,删除 node_module 文件夹。然后执行 yarn install,如果还是提示未安装,那就去 electron 文件夹内执行一次 yarn install,就好了。这样的话,不仅仅只是 yarn 更快了,electron 的 rebuild 也会加速很多。所以推荐使用 yarn。
+  - (优先尝试)使用 npm config edit 打开 npm 配置文件,添加上 electron_mirror=https://cdn.npm.taobao.org/dist/electron/ ,然后重启窗口删除 node_module 文件夹,重新安装依赖即可。
+
+## 2019 年 11 月 19 日
+
+- 更新了不使用 updater 进行全量更新的方法,但是该方法不会校验安装包 md5 值,也就是说,包如果被拦截了。。可能就会出问题,这一点我正在想办法处理。
+
+## 2019 年 10 月 31 日
+
+- 升级 electron 版本至 7,但是需要做一些修改,由于淘宝的问题,导致 electron 新的下载器出现故障,故我们需要对 electron 的下载器做一些更改,这非常容易,不用担心:
+  - 首先我们在淘宝代理设置下,安装完成依赖,此时是报错的,现在进入项目的 node_modules 文件夹内找到 electron,点击进入,然后修改其中的 package.json 文件,修改 dependencies 对象中的依赖为:
+
+```json
+  "dependencies": {
+    "@types/node": "^12.0.12",
+    "extract-zip": "^1.0.3",
+    "electron-download": "^4.1.0"
+  },
+```
+
+- - 然后我们需要再修改 install.js 中的代码(实际就是 6 中的 install 代码)
+
+```js
+#!/usr/bin/env node
+
+var version = require("./package").version;
+
+var fs = require("fs");
+var os = require("os");
+var path = require("path");
+var extract = require("extract-zip");
+var download = require("electron-download");
+
+var installedVersion = null;
+try {
+  installedVersion = fs
+    .readFileSync(path.join(__dirname, "dist", "version"), "utf-8")
+    .replace(/^v/, "");
+} catch (ignored) {
+  // do nothing
+}
+
+if (process.env.ELECTRON_SKIP_BINARY_DOWNLOAD) {
+  process.exit(0);
+}
+
+var platformPath = getPlatformPath();
+
+var electronPath =
+  process.env.ELECTRON_OVERRIDE_DIST_PATH ||
+  path.join(__dirname, "dist", platformPath);
+
+if (installedVersion === version && fs.existsSync(electronPath)) {
+  process.exit(0);
+}
+
+// downloads if not cached
+download(
+  {
+    cache: process.env.electron_config_cache,
+    version: version,
+    platform: process.env.npm_config_platform,
+    arch: process.env.npm_config_arch,
+    strictSSL: process.env.npm_config_strict_ssl === "true",
+    force: process.env.force_no_cache === "true",
+    quiet: process.env.npm_config_loglevel === "silent" || process.env.CI,
+  },
+  extractFile
+);
+
+// unzips and makes path.txt point at the correct executable
+function extractFile(err, zipPath) {
+  if (err) return onerror(err);
+  extract(zipPath, { dir: path.join(__dirname, "dist") }, function (err) {
+    if (err) return onerror(err);
+    fs.writeFile(
+      path.join(__dirname, "path.txt"),
+      platformPath,
+      function (err) {
+        if (err) return onerror(err);
+      }
+    );
+  });
+}
+
+function onerror(err) {
+  throw err;
+}
+
+function getPlatformPath() {
+  var platform = process.env.npm_config_platform || os.platform();
+
+  switch (platform) {
+    case "darwin":
+      return "Electron.app/Contents/MacOS/Electron";
+    case "freebsd":
+    case "linux":
+      return "electron";
+    case "win32":
+      return "electron.exe";
+    default:
+      throw new Error(
+        "Electron builds are not available on platform: " + platform
+      );
+  }
+}
+```
+
+- - 然后执行 npm i 即可完成安装,至于打包的话,您可能需要去淘宝镜像手动下载并且放好位置,才能完成打包操作,不然依旧还是报下载错误的信息。
+
+## 2019 年 10 月 18 日
+
+- 不知不觉中倒也过去了一个月,啊哈哈这次更新给大家带来的是 updater 的示例,这依旧是个实验特性,所以在新分支中才可以使用,使用方式则是,安装依赖,
+  - 运行 `npm run update:serve` 来启动这个 node 服务器,然后您如果想在 dev 的时候就看到效果需要先运行 build 拿到 `latest.yml`文件,然后将其更名为 `dev-app-update.yml` 放入`dist/electron`中,和`main.js`同级,然后你需要关闭或者排除 webpack 的自动清除插件(我已经屏蔽了,所以无需大家自己动手),然后点击软件中的检查更新即可,记住当软件正在运行的时候,是无法应用安装的,所以您需要关闭之后方可安装。这并不是一个错误!
+
+## 2019 年 09 月 18 日
+
+- 修正生产环境时,没有正确去除控制台输出的问题,双分支例行更新依赖,修正 ui 部分颜色问题,日后准备使用 element 主题功能
+
+## 2019 年 09 月 16 日
+
+- 去除 easymock,直接粗暴更改登陆验证,如有需要请自行修改,例行更新新分支依赖,修正当自定义头部和系统头部互换时,布局不会做出相应变化的问题。
+
+## 2019 年 09 月 03 日
+
+- 修正了当 nodejs >= 12 时,出现 process 未定义的问题,新分支加入自定义头部,现在我们可以做出更 cooool~~的效果了。
+
+## 2019 年 08 月 20 日
+
+- 添加登录拦击,实现登录功能,在 dev 中加入关闭 ELECTRON 无用控制台输出,新分支例行更新依赖,加入生产环境屏蔽 f12 按键。
+
+## 2019 年 08 月 13 日
+
+- 将新分支的所有依赖均更新至最新(但是我觉得,babel 似乎有些东西不需要,还是保留着吧,日后测试后移除)依赖更新之后通过打包和 dev 测试
+
+## 2019 年 08 月 12 日
+
+- 添加一个新分支,该新分支后续将会持续保持 ELECTRON(包括其对应的辅助组件)的版本处于最新状态,去除了单元测试和一些无用的文件。master 分支中则是为路由添加新参数具体
+  - 用途,详看路由中的注释
+
+## 2019 年 08 月 10 日
+
+- 添加各个平台的 build 脚本,当您直接使用 build 时,则会打包您当前操作系统对应的安装包,mac 需要在 macos 上才能进行打包,而 linux 打包 win 的话,需要 wine 的支持,否则会失败
+
+## 2019 年 08 月 04 日
+
+- 修正原 webpack 配置中没有将 config 注入的小问题,添加了拦截实例,修改了侧栏,侧栏需要底色的请勿更新,此更新可能会导致侧栏底色无法完全覆盖(待修正),添加 axios 接口示例,待测。
+
+## 2019 年 08 月 01 日
+
+- 将 node-sass 版本更新至最新版本,尝试修正由于 nodejs 环境是 12 版导致失败(注意!此次更新可能会导致 32 位系统或者 nodejs 版本低于 10 的用户安装依赖报错)去除路由表中重复路由,解决控制台无端报错问题。

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Modified work Copyright (c) 2019-present umbrella22
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 80 - 0
README.md

@@ -0,0 +1,80 @@
+# Electron-Vue-template
+
+> This is a project based on electron-vue, using the elementUI, vuex, vue-router, axios technology stack. This project is divided into two branches. The master maintains the original project structure and features for dependency updates, and the SynchronizedUpdates branch. It keeps the original file structure and the code part is completely customized by me personally; it includes: a complete background management interface, nedb database package, and two complete update download methods. If you feel redundant, you can delete it yourself.
+
+[中文在线文档](https://umbrella22.github.io/electron-vue-template-doc/)
+[国内访问地址](https://gitee.com/Zh-Sky/electron-vue-template)
+[国内文档访问地址](https://zh-sky.gitee.io/electron-vue-template-doc/)
+[vite 版本](https://github.com/umbrella22/electron-vite-template)
+[vite 版本(码云)](https://gitee.com/Zh-Sky/electron-vite-template)
+
+[Open in Visual Studio Code](https://open.vscode.dev/umbrella22/electron-vue-template)
+![GitHub Repo stars](https://img.shields.io/github/stars/umbrella22/electron-vue-template)
+[![vue](https://img.shields.io/badge/vue-2.7.10-brightgreen.svg)](https://github.com/vuejs/vue)
+[![element-ui](https://img.shields.io/badge/element--ui-2.15.9-brightgreen.svg)](https://github.com/ElemeFE/element)
+[![electron](https://img.shields.io/badge/electron-19.0.17-brightgreen.svg)](https://github.com/electron/electron)
+[![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/umbrella22/electron-vue-template/blob/master/LICENSE)
+
+- electron of react [Electron-react-template](https://github.com/umbrella22/electron-react-template)
+
+### To run a project, you need to have **node version 16** or higher and **use yarn as your dependency management tool**
+
+<p align="center">
+  <a href="https://github.com/umbrella22/electron-vue-template">
+    <img src="https://github.com/umbrella22/electron-vue-template/actions/workflows/build-test.yml/badge.svg">
+  </a>
+</p>
+
+<h3 align="center">Thanks for support.</h3>
+
+<p align="center">
+  <a href="https://www.jetbrains.com/?from=electron-vue-template" target="_blank">
+    <img width="160px" src="https://github.com/umbrella22/MCsever/blob/master/jetbrains.png">
+  </a>
+</p>
+
+#### Build Setup
+
+```bash
+# install dependencies
+yarn or yarn install
+
+# serve with hot reload at localhost:9080
+yarn dev
+
+# build electron application for production
+yarn build
+
+
+```
+
+---
+
+# Function list
+
+- Auto update
+- Incremental update
+- Loading animation before startup
+- i18n
+- Incremental update (wait for test)
+
+# Built-in
+
+- [vue-router](https://router.vuejs.org)
+- [vuex](https://vuex.vuejs.org)
+- [electron](http://www.electronjs.org/docs)
+- electron-updater
+- typescript
+- [element-plus](https://element.eleme.cn/#/en-US)
+
+# Note
+
+- [gitee](https://gitee.com/Zh-Sky/electron-vue-template) is only for domestic users to pull code,from github to synchronize,please visit github for PR
+- **Welcome to Issues and PR**
+
+---
+
+This project was generated with [electron-vue](https://github.com/SimulatedGREG/electron-vue)@[8fae476](https://github.com/SimulatedGREG/electron-vue/tree/8fae4763e9d225d3691b627e83b9e09b56f6c935) using [vue-cli](https://github.com/vuejs/vue-cli). Documentation about the original structure can be found [here](https://simulatedgreg.gitbooks.io/electron-vue/content/index.html).
+Manage interface code address [here](https://github.com/PanJiaChen/electron-vue-admin)
+
+# [CHANGELOG](CHANGELOG.md)

+ 36 - 0
README_ZH.md

@@ -0,0 +1,36 @@
+# Electron-Vue-template
+
+## 介绍
+
+### 这是什么?
+
+- 这是一个基于 webpack5 和 vue2/3 的 electron 快速上手框架,你不需要关注它们是怎么联动的,你只需要关注你当前的界面组合就好
+  剩下的交给我就好
+- 上手极快,几乎没有任何心智负担对于前端而言,特别是当你熟悉 vue 和 js 时,就好像回家了一样
+
+### 有什么功能?
+1. 文件下载
+2. 全量更新
+
+> ### **请确保您的 node 环境是大于或等于 16**
+
+#### 如何安装
+
+```bash
+npm config edit
+# 该命令会打开npm的配置文件,请在空白处添加,此操作是配置淘宝镜像。
+# registry=https://registry.npmmirror.com
+# electron_mirror=https://cdn.npmmirror.com/binaries/electron/
+# electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
+# 然后关闭该窗口,重启命令行.
+# 使用yarn安装
+yarn or yarn install
+
+# 启动之后,会在9080端口监听
+yarn dev
+
+# build命令在不同系统环境中,需要的的不一样,需要自己根据自身环境进行配置
+yarn build
+
+```
+# [更新日志](CHANGELOG.md)

+ 220 - 0
build/builder-debug.yml

@@ -0,0 +1,220 @@
+x64:
+  firstOrDefaultFilePatterns:
+    - '!**/node_modules'
+    - '!build{,/**/*}'
+    - '!build{,/**/*}'
+    - dist/electron/**/*
+    - package.json
+    - '!**/*.{iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts,mk,a,o,forge-meta,pdb}'
+    - '!**/._*'
+    - '!**/electron-builder.{yaml,yml,json,json5,toml,ts}'
+    - '!**/{.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore,.idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci,.yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github,electron-builder.env}'
+    - '!.yarn{,/**/*}'
+    - '!.editorconfig'
+    - '!.yarnrc.yml'
+  nodeModuleFilePatterns:
+    - '**/*'
+    - dist/electron/**/*
+nsis:
+  script: |-
+    !include "C:\Code\fuint\fuint-cashier\node_modules\app-builder-lib\templates\nsis\include\StdUtils.nsh"
+    !addincludedir "C:\Code\fuint\fuint-cashier\node_modules\app-builder-lib\templates\nsis\include"
+    !macro _isUpdated _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "updated"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isUpdated `"" isUpdated ""`
+
+    !macro _isForceRun _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "force-run"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isForceRun `"" isForceRun ""`
+
+    !macro _isKeepShortcuts _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "keep-shortcuts"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isKeepShortcuts `"" isKeepShortcuts ""`
+
+    !macro _isNoDesktopShortcut _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "no-desktop-shortcut"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isNoDesktopShortcut `"" isNoDesktopShortcut ""`
+
+    !macro _isDeleteAppData _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "delete-app-data"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isDeleteAppData `"" isDeleteAppData ""`
+
+    !macro _isForAllUsers _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "allusers"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isForAllUsers `"" isForAllUsers ""`
+
+    !macro _isForCurrentUser _a _b _t _f
+      ${StdUtils.TestParameter} $R9 "currentuser"
+      StrCmp "$R9" "true" `${_t}` `${_f}`
+    !macroend
+    !define isForCurrentUser `"" isForCurrentUser ""`
+
+    !macro addLangs
+      !insertmacro MUI_LANGUAGE "English"
+      !insertmacro MUI_LANGUAGE "German"
+      !insertmacro MUI_LANGUAGE "French"
+      !insertmacro MUI_LANGUAGE "SpanishInternational"
+      !insertmacro MUI_LANGUAGE "SimpChinese"
+      !insertmacro MUI_LANGUAGE "TradChinese"
+      !insertmacro MUI_LANGUAGE "Japanese"
+      !insertmacro MUI_LANGUAGE "Korean"
+      !insertmacro MUI_LANGUAGE "Italian"
+      !insertmacro MUI_LANGUAGE "Dutch"
+      !insertmacro MUI_LANGUAGE "Danish"
+      !insertmacro MUI_LANGUAGE "Swedish"
+      !insertmacro MUI_LANGUAGE "Norwegian"
+      !insertmacro MUI_LANGUAGE "Finnish"
+      !insertmacro MUI_LANGUAGE "Russian"
+      !insertmacro MUI_LANGUAGE "Portuguese"
+      !insertmacro MUI_LANGUAGE "PortugueseBR"
+      !insertmacro MUI_LANGUAGE "Polish"
+      !insertmacro MUI_LANGUAGE "Ukrainian"
+      !insertmacro MUI_LANGUAGE "Czech"
+      !insertmacro MUI_LANGUAGE "Slovak"
+      !insertmacro MUI_LANGUAGE "Hungarian"
+      !insertmacro MUI_LANGUAGE "Arabic"
+      !insertmacro MUI_LANGUAGE "Turkish"
+      !insertmacro MUI_LANGUAGE "Thai"
+      !insertmacro MUI_LANGUAGE "Vietnamese"
+    !macroend
+
+    !addplugindir /x86-unicode "C:\Users\zach\AppData\Local\electron-builder\Cache\nsis\nsis-resources-3.4.1\plugins\x86-unicode"
+    !include "C:\Users\zach\AppData\Local\Temp\t-idwXjr\0-messages.nsh"
+
+    Var newStartMenuLink
+    Var oldStartMenuLink
+    Var newDesktopLink
+    Var oldDesktopLink
+    Var oldShortcutName
+    Var oldMenuDirectory
+
+    !include "common.nsh"
+    !include "MUI2.nsh"
+    !include "multiUser.nsh"
+    !include "allowOnlyOneInstallerInstance.nsh"
+
+    !ifdef INSTALL_MODE_PER_ALL_USERS
+      !ifdef BUILD_UNINSTALLER
+        RequestExecutionLevel user
+      !else
+        RequestExecutionLevel admin
+      !endif
+    !else
+      RequestExecutionLevel user
+    !endif
+
+    !ifdef BUILD_UNINSTALLER
+      SilentInstall silent
+    !else
+      Var appExe
+      Var launchLink
+    !endif
+
+    !ifdef ONE_CLICK
+      !include "oneClick.nsh"
+    !else
+      !include "assistedInstaller.nsh"
+    !endif
+
+    !insertmacro addLangs
+
+    !ifmacrodef customHeader
+      !insertmacro customHeader
+    !endif
+
+    Function .onInit
+      Call setInstallSectionSpaceRequired
+
+      SetOutPath $INSTDIR
+      ${LogSet} on
+
+      !ifmacrodef preInit
+        !insertmacro preInit
+      !endif
+
+      !ifdef DISPLAY_LANG_SELECTOR
+        !insertmacro MUI_LANGDLL_DISPLAY
+      !endif
+
+      !ifdef BUILD_UNINSTALLER
+        WriteUninstaller "${UNINSTALLER_OUT_FILE}"
+        !insertmacro quitSuccess
+      !else
+        !insertmacro check64BitAndSetRegView
+
+        !ifdef ONE_CLICK
+          !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
+        !else
+          ${IfNot} ${UAC_IsInnerInstance}
+            !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTANCE
+          ${EndIf}
+        !endif
+
+        !insertmacro initMultiUser
+
+        !ifmacrodef customInit
+          !insertmacro customInit
+        !endif
+
+        !ifmacrodef addLicenseFiles
+          InitPluginsDir
+          !insertmacro addLicenseFiles
+        !endif
+      !endif
+    FunctionEnd
+
+    !ifndef BUILD_UNINSTALLER
+      !include "installUtil.nsh"
+    !endif
+
+    Section "install" INSTALL_SECTION_ID
+      !ifndef BUILD_UNINSTALLER
+        # If we're running a silent upgrade of a per-machine installation, elevate so extracting the new app will succeed.
+        # For a non-silent install, the elevation will be triggered when the install mode is selected in the UI,
+        # but that won't be executed when silent.
+        !ifndef INSTALL_MODE_PER_ALL_USERS
+          !ifndef ONE_CLICK
+              ${if} $hasPerMachineInstallation == "1" # set in onInit by initMultiUser
+              ${andIf} ${Silent}
+                ${ifNot} ${UAC_IsAdmin}
+                  ShowWindow $HWNDPARENT ${SW_HIDE}
+                  !insertmacro UAC_RunElevated
+                  ${Switch} $0
+                    ${Case} 0
+                      ${Break}
+                    ${Case} 1223 ;user aborted
+                      ${Break}
+                    ${Default}
+                      MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate, error $0"
+                      ${Break}
+                  ${EndSwitch}
+                  Quit
+                ${else}
+                  !insertmacro setInstallModePerAllUsers
+                ${endIf}
+              ${endIf}
+          !endif
+        !endif
+        !include "installSection.nsh"
+      !endif
+    SectionEnd
+
+    Function setInstallSectionSpaceRequired
+      !insertmacro setSpaceRequired ${INSTALL_SECTION_ID}
+    FunctionEnd
+
+    !ifdef BUILD_UNINSTALLER
+      !include "uninstaller.nsh"
+    !endif

BIN
build/icons/256x256.png


BIN
build/icons/icon.icns


BIN
build/icons/icon.ico


+ 8 - 0
build/latest.yml

@@ -0,0 +1,8 @@
+version: 1.0.0
+files:
+  - url: fuint收银系统 Setup 1.0.0.exe
+    sha512: /5Jf84ozx4NWmYJ0ifp4QCXTCXoDCuxT4ECBKBlyysiJgqbb8KZ1z4ShFeQ8W6zYCKzz+IPtj7MzK7fn/bnOAw==
+    size: 75309070
+path: fuint收银系统 Setup 1.0.0.exe
+sha512: /5Jf84ozx4NWmYJ0ifp4QCXTCXoDCuxT4ECBKBlyysiJgqbb8KZ1z4ShFeQ8W6zYCKzz+IPtj7MzK7fn/bnOAw==
+releaseDate: '2024-08-09T11:04:57.235Z'

+ 12 - 0
config/index.js

@@ -0,0 +1,12 @@
+module.exports = {
+  build: {
+    cleanConsole: true,
+  },
+  dev: {
+    removeElectronJunk: true,
+    chineseLog: false,
+    port: 8088
+  },
+  DllFolder: '',
+  UseJsx: true
+}

+ 3 - 0
env/.env

@@ -0,0 +1,3 @@
+API_HOST = 'https://www.fuint.cn/fuint-food/'
+NODE_ENV = 'development'
+SYSTEM_NAME = 'fuint餐饮收银系统'

+ 2 - 0
env/sit.env

@@ -0,0 +1,2 @@
+API_HOST = 'http://127.0.0.1:25565'
+NODE_ENV = 'sit'

+ 0 - 0
lib/updater.html


+ 157 - 0
package.json

@@ -0,0 +1,157 @@
+{
+  "name": "fuintCashier",
+  "version": "1.0.0",
+  "author": "延禾技术",
+  "description": "海南延禾信息技术有限公司旗下收银系统",
+  "license": "MIT",
+  "main": "./dist/electron/main.js",
+  "scripts": {
+    "dev": "cross-env TERGET_ENV=development node .electron-vue/dev-runner.js",
+    "build": "cross-env BUILD_TARGET=clean node .electron-vue/build.js  && electron-builder",
+    "build:win32": "cross-env BUILD_TARGET=clean node .electron-vue/build.js  && electron-builder --win  --ia32",
+    "build:win64": "cross-env BUILD_TARGET=clean node .electron-vue/build.js  && electron-builder --win  --x64",
+    "build:mac": "cross-env BUILD_TARGET=clean node .electron-vue/build.js  && electron-builder --mac",
+    "build:dir": "cross-env BUILD_TARGET=clean node .electron-vue/build.js  && electron-builder --dir",
+    "build:clean": "cross-env BUILD_TARGET=onlyClean node .electron-vue/build.js",
+    "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
+    "pack:resources": "node .electron-vue/hot-updater.js",
+    "update:serve": "node server/index.js",
+    "dep:upgrade": "yarn upgrade-interactive --latest",
+    "postinstall": "electron-builder install-app-deps"
+  },
+  "build": {
+    "asar": false,
+    "extraFiles": [],
+    "publish": [
+      {
+        "provider": "generic",
+        "url": "http://127.0.0.1"
+      }
+    ],
+    "productName": "fuint收银系统",
+    "appId": "cn.fuint.cashier",
+    "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"
+    }
+  },
+  "dependencies": {
+    "axios": "^1.4.0",
+    "clipboard": "2.0.8",
+    "electron-updater": "^5.3.0",
+    "express": "4.18.2",
+    "fs-extra": "^11.1.0",
+    "vue-print-nb": "^1.7.5"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.22.5",
+    "@babel/eslint-parser": "^7.22.5",
+    "@babel/plugin-proposal-class-properties": "^7.18.6",
+    "@babel/plugin-proposal-decorators": "^7.22.5",
+    "@babel/plugin-proposal-do-expressions": "^7.22.5",
+    "@babel/plugin-proposal-export-default-from": "^7.22.5",
+    "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
+    "@babel/plugin-proposal-function-bind": "^7.22.5",
+    "@babel/plugin-proposal-function-sent": "^7.22.5",
+    "@babel/plugin-proposal-json-strings": "^7.18.6",
+    "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7",
+    "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
+    "@babel/plugin-proposal-numeric-separator": "^7.18.6",
+    "@babel/plugin-proposal-optional-chaining": "^7.21.0",
+    "@babel/plugin-proposal-pipeline-operator": "^7.22.5",
+    "@babel/plugin-proposal-throw-expressions": "^7.22.5",
+    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+    "@babel/plugin-syntax-import-meta": "^7.10.4",
+    "@babel/plugin-transform-runtime": "^7.22.5",
+    "@babel/preset-env": "^7.22.5",
+    "@babel/register": "^7.22.5",
+    "@babel/runtime": "^7.22.5",
+    "@types/fs-extra": "^11.0.1",
+    "@types/node": "^18.14.5",
+    "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0",
+    "@vue/babel-preset-jsx": "^1.4.0",
+    "adm-zip": "^0.5.10",
+    "autoprefixer": "^10.4.14",
+    "babel-loader": "^9.1.2",
+    "cfonts": "^2.10.0",
+    "chalk": "^4.1.2",
+    "copy-webpack-plugin": "^11.0.0",
+    "core-js": "^3.31.0",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.8.1",
+    "date-fns": "^2.30.0",
+    "del": "^6.1.1",
+    "dotenv": "^16.1.4",
+    "electron": "22.3.27",
+    "electron-builder": "^24.4.0",
+    "electron-devtools-installer": "^3.2.0",
+    "element-ui": "^2.15.13",
+    "esbuild-loader": "^3.0.1",
+    "eslint": "^7.32.0",
+    "eslint-config-standard": "^14.1.1",
+    "eslint-friendly-formatter": "^4.0.1",
+    "eslint-plugin-html": "^6.2.0",
+    "eslint-plugin-import": "^2.25.4",
+    "eslint-plugin-node": "^11.1.0",
+    "eslint-plugin-promise": "^4.3.1",
+    "eslint-plugin-standard": "^5.0.0",
+    "eslint-webpack-plugin": "^3.2.0",
+    "extract-zip": "^2.0.1",
+    "html-webpack-plugin": "^5.5.3",
+    "listr2": "^5.0.7",
+    "mini-css-extract-plugin": "2.7.6",
+    "minimist": "^1.2.8",
+    "node-loader": "^2.0.0",
+    "nprogress": "^0.2.0",
+    "pinia": "^2.0.33",
+    "portfinder": "^1.0.32",
+    "postcss": "^8.4.24",
+    "postcss-loader": "^7.3.3",
+    "sass": "^1.63.4",
+    "sass-loader": "^13.3.2",
+    "style-loader": "^3.3.3",
+    "svg-sprite-loader": "^6.0.11",
+    "terser-webpack-plugin": "^5.3.9",
+    "vue": "^2.7.14",
+    "vue-devtools": "^5.1.4",
+    "vue-html-loader": "^1.2.4",
+    "vue-i18n": "^8.27.1",
+    "vue-loader": "15.10.1",
+    "vue-router": "^3.6.5",
+    "vue-style-loader": "^4.1.3",
+    "vue-template-compiler": "^2.7.14",
+    "webpack": "^5.87.0",
+    "webpack-cli": "^5.1.4",
+    "webpack-dev-server": "^4.15.1",
+    "webpack-hot-middleware": "^2.25.3",
+    "webpack-merge": "^5.9.0"
+  }
+}

+ 12 - 0
server/index.js

@@ -0,0 +1,12 @@
+const express = require('express')
+const path = require('path')
+const app = express()
+
+app.use(express.static(path.join(__dirname, './client')))
+
+const server = app.listen(25565, function () {
+  const host = server.address().address
+  const port = server.address().port
+
+  console.log('服务启动', host, port)
+})

+ 32 - 0
src/index.ejs

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <title>fuintCashier</title>
+  <% if (htmlWebpackPlugin.options.nodeModules) { %>
+    <!-- Add `node_modules/` to global paths so `require` works properly in development -->
+    <script>
+      require('module').globalPaths.push('<%= htmlWebpackPlugin.options.nodeModules.replace(/\\/g, '\\\\') %>')
+    </script>
+    <% } %>
+</head>
+
+<body>
+  <div id="app"></div>
+  <!-- Set `__static` path to static files in production -->
+  <% if (!process.browser) { %>
+    <script>
+      if (process.env.NODE_ENV !== 'development') {
+        window.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\')
+      } else {
+        window.__static = `http://localhost:${process.env.PORT}/static`
+      }
+      if (process.env.NODE_ENV !== 'development') window.__lib = process.env.libPath
+    </script>
+    <% } %>
+
+      <!-- webpack builds are automatically injected -->
+</body>
+
+</html>

+ 12 - 0
src/main/config/DisableButton.js

@@ -0,0 +1,12 @@
+import { globalShortcut } from 'electron'
+import { DisableF12 } from "./const"
+
+export default {
+  Disablef12() {
+    if (process.env.NODE_ENV === 'production' && DisableF12) {
+      globalShortcut.register('f12', () => {
+        console.log('用户试图启动控制台')
+      })
+    }
+  }
+}

+ 17 - 0
src/main/config/StaticPath.js

@@ -0,0 +1,17 @@
+// 这里定义了静态文件路径的位置
+import path from 'path'
+import { DllFolder } from '@config/index'
+
+/**
+ * Set `__static` path to static files in production
+ * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
+ */
+// 这个瓜皮全局变量只能在单个js中生效,而并不是整个主进程中
+if (process.env.NODE_ENV !== 'development') {
+  global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
+  process.env.libPath = path.join(__dirname, '..', '..', '..', '..', `${DllFolder}`).replace(/\\/g, '\\\\')
+}
+
+export const winURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}` : `file://${__dirname}/index.html`
+export const loadingURL = process.env.NODE_ENV === 'development' ? `http://localhost:${process.env.PORT}/static/loader.html` : `file://${__static}/loader.html`
+export const libPath = process.env.libPath

+ 7 - 0
src/main/config/const.js

@@ -0,0 +1,7 @@
+export const UseStartupChart = true
+export const IsUseSysTitle = false
+export const BuiltInServerPort = 25565
+export const hotPublishUrl = ""
+export const hotPublishConfigName = "update-config"
+export const openDevTools = false
+export const DisableF12 = true

+ 6 - 0
src/main/config/hotPublish.js

@@ -0,0 +1,6 @@
+import { hotPublishUrl, hotPublishConfigName } from './const'
+
+export const hotPublishConfig = {
+    url: hotPublishUrl,
+    configName: hotPublishConfigName
+}

+ 68 - 0
src/main/config/menu.js

@@ -0,0 +1,68 @@
+// 这里是定义菜单的地方,详情请查看 https://electronjs.org/docs/api/menu
+const { dialog } = require('electron')
+const os = require('os')
+const version = require('../../../package.json').version
+const menu = [
+  {
+    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: '关于',
+      role: 'about',
+      click: function () {
+        info()
+      }
+    }]
+  }]
+function info() {
+  dialog.showMessageBox({
+    title: '关于',
+    type: 'info',
+    message: 'fuint收银系统',
+    detail: `版本信息:${version}\n引擎版本:${process.versions.v8}\n当前系统:${os.type()} ${os.arch()} ${os.release()}`,
+    noLink: true,
+    buttons: ['查看官网', '确定']
+  })
+}
+export default menu

+ 41 - 0
src/main/index.js

@@ -0,0 +1,41 @@
+'use strict'
+
+import { app } from 'electron'
+import initWindow from './services/windowManager'
+import DisableButton from './config/DisableButton'
+import electronDevtoolsInstaller, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
+
+function onAppReady () {
+  initWindow()
+  DisableButton.Disablef12()
+  if (process.env.NODE_ENV === 'development') {
+    electronDevtoolsInstaller(VUEJS_DEVTOOLS)
+      .then((name) => console.log(`installed: ${name}`))
+      .catch(err => console.log('Unable to install `vue-devtools`: \n', err))
+  }
+}
+// 禁止程序多开,此处需要单例锁的同学打开注释即可
+const gotTheLock = app.requestSingleInstanceLock()
+if(!gotTheLock){
+   app.quit()
+}
+app.isReady() ? onAppReady() : app.on('ready', onAppReady)
+// 解决9.x跨域异常问题
+app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
+
+app.on('window-all-closed', () => {
+  // 所有平台均为所有窗口关闭就退出软件
+  app.quit()
+})
+app.on('browser-window-created', () => {
+  console.log('window-created')
+})
+
+if (process.defaultApp) {
+  if (process.argv.length >= 2) {
+      app.removeAsDefaultProtocolClient('electron-vue-template')
+      console.log('框架特殊性开发环境下无法使用')
+  }
+} else {
+  app.setAsDefaultProtocolClient('electron-vue-template')
+}

+ 44 - 0
src/main/server/index.js

@@ -0,0 +1,44 @@
+/* eslint-disable prefer-promise-reject-errors */
+import app from './server'
+import http from 'http'
+import config from '@config'
+const port = config.BuiltInServerPort
+var server = null
+app.set('port', port)
+
+export default {
+  StatrServer () {
+    return new Promise((resolve, reject) => {
+      server = http.createServer(app)
+      server.listen(port)
+      server.on('error', (error) => {
+        switch (error.code) {
+          case 'EACCES':
+            reject('权限不足内置服务器启动失败,请使用管理员权限运行。')
+            break
+          case 'EADDRINUSE':
+            reject('内置服务器端口已被占用,请检查。')
+            break
+          default:
+            reject(error)
+        }
+      })
+      server.on('listening', () => {
+        resolve('服务端运行中')
+      })
+    })
+  },
+  StopServer () {
+    return new Promise((resolve, reject) => {
+      if (server) {
+        server.close()
+        server.on('close', () => {
+          server = null
+          resolve(1)
+        })
+      } else {
+        reject('服务端尚未开启')
+      }
+    })
+  }
+}

+ 14 - 0
src/main/server/server.js

@@ -0,0 +1,14 @@
+import express from 'express'
+const app = express()
+
+app.get('/message', (req, res) => {
+  res.send('这是来自node服务端的信息')
+})
+
+app.post('/message', (req, res) => {
+  if (req) {
+    res.send(req + '--来自node')
+  }
+})
+
+export default app

+ 91 - 0
src/main/services/HotUpdater.js

@@ -0,0 +1,91 @@
+/**
+ * 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 } 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'
+
+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, filePath) {
+    const res = await request({ url, responseType: "stream" })
+    await streamPipeline(res.data, createWriteStream(filePath))
+}
+
+const updateInfo = {
+    status: 'init',
+    message: ''
+}
+
+/**
+ * @param windows 指主窗口
+ * @returns {void}
+ * @author umbrella22
+ * @date 2021-03-05
+ */
+export const updater = async (windows) => {
+    try {
+        const res = await request({ url: `${hotPublishConfig.url}/${hotPublishConfig.configName}.json?time=${new Date().getTime()}`, })
+        if (gt(res.data.version, version)) {
+            await emptyDir(updatePath)
+            const filePath = join(updatePath, res.data.name)
+            updateInfo.status = 'downloading'
+            if (windows) windows.webContents.send('hot-update-status', 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'
+            if (windows) windows.webContents.send('hot-update-status', updateInfo);
+            await remove(join(`${appPath}`, 'dist'));
+            await remove(join(`${appPath}`, 'package.json'));
+            await copy(appPathTemp, appPath)
+            updateInfo.status = 'finished'
+            if (windows) windows.webContents.send('hot-update-status', updateInfo);
+        }
+
+
+    } catch (error) {
+        updateInfo.status = 'failed'
+        updateInfo.message = error
+        if (windows) windows.webContents.send('hot-update-status', updateInfo)
+    }
+}
+
+export const getUpdateInfo = () => updateInfo

+ 76 - 0
src/main/services/checkupdate.js

@@ -0,0 +1,76 @@
+import { autoUpdater } from 'electron-updater'
+/**
+ * -1 检查更新失败 0 正在检查更新 1 检测到新版本,准备下载 2 未检测到新版本 3 下载中 4 下载完成
+ **/
+class Update {
+  mainWindow
+  constructor() {
+    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', (event, arg) => {
+      console.log('开始检查更新')
+      this.Message(this.mainWindow, 0)
+    })
+
+    // 发现可更新数据时
+    autoUpdater.on('update-available', (event, arg) => {
+      console.log('有更新')
+      this.Message(this.mainWindow, 1)
+    })
+
+    // 没有可更新数据时
+    autoUpdater.on('update-not-available', (event, arg) => {
+      console.log('没有更新')
+      this.Message(this.mainWindow, 2)
+    })
+
+    // 下载监听
+    autoUpdater.on('download-progress', (progressObj) => {
+      this.Message(this.mainWindow, 3, progressObj)
+    })
+
+    // 下载完成
+    autoUpdater.on('update-downloaded', () => {
+      console.log('done')
+      this.Message(this.mainWindow, 4)
+    })
+  }
+  // 负责向渲染进程发送信息
+  Message(mainWindow, type, data) {
+    console.log('发送消息')
+    const senddata = {
+      state: type,
+      msg: data || ''
+    }
+    mainWindow.webContents.send('update-msg', senddata)
+  }
+
+
+
+  // 执行自动更新检查
+  checkUpdate(mainWindow) {
+    this.mainWindow = mainWindow
+    autoUpdater.checkForUpdates().catch(err => {
+      console.log('网络连接问题', err)
+    })
+  }
+
+  // 退出并安装
+  quitInstall() {
+    autoUpdater.quitAndInstall()
+  }
+}
+
+export default Update

+ 63 - 0
src/main/services/downloadFile.js

@@ -0,0 +1,63 @@
+/* eslint-disable no-case-declarations */
+import { app, dialog } from 'electron'
+import path from 'path'
+import os from 'os'
+// 版本以package.json为基准。
+const version = require('../../../package.json').version
+// 您的下载地址
+const baseUrl = 'http://127.0.0.1:25565/'
+var Sysarch = null
+var defaultDownloadUrL = null
+// 识别操作系统位数D
+os.arch().includes('64') ? Sysarch = 'win64' : Sysarch = 'win32'
+// 识别操作系统
+// linux自己修改后缀名哦,我没有linux就没有测试了
+if (os.platform().includes('win32')) {
+  defaultDownloadUrL = baseUrl + `electron_${version}_${Sysarch}.exe?${new Date().getTime()}`
+} else if (os.platform().includes('linux')) {
+  defaultDownloadUrL = baseUrl + `electron_${version}_${Sysarch}?${new Date().getTime()}`
+} else {
+  defaultDownloadUrL = baseUrl + `electron_${version}_mac.dmg?${new Date().getTime()}`
+}
+export default {
+  download(mainWindow, downloadUrL) {
+    mainWindow.webContents.downloadURL(downloadUrL || defaultDownloadUrL)
+    mainWindow.webContents.session.on('will-download', (event, item, webContents) => {
+      //   将文件保存在系统的下载目录
+      const filePath = path.join(app.getPath('downloads'), item.getFilename())
+      // 自动保存
+      item.setSavePath(filePath)
+      // 下载进度
+      item.on('updated', (event, state) => {
+        switch (state) {
+          case 'progressing':
+            mainWindow.webContents.send('download-progress', (item.getReceivedBytes() / item.getTotalBytes() * 100).toFixed(0))
+            break
+          case 'interrupted ':
+            mainWindow.webContents.send('download-paused', true)
+            break
+          default:
+
+            break
+        }
+      })
+      // 下载完成或失败
+      item.once('done', (event, state) => {
+        switch (state) {
+          case 'completed':
+            const data = {
+              filePath
+            }
+            mainWindow.webContents.send('download-done', data)
+            break
+          case 'interrupted':
+            mainWindow.webContents.send('download-error', true)
+            dialog.showErrorBox('下载出错', '由于网络或其他未知原因导致下载出错.')
+            break
+          default:
+            break
+        }
+      })
+    })
+  }
+}

+ 169 - 0
src/main/services/ipcMain.js

@@ -0,0 +1,169 @@
+import { ipcMain, dialog, BrowserWindow } from 'electron'
+import Server from '../server/index'
+import { winURL } from '../config/StaticPath'
+import downloadFile from './downloadFile'
+import Update from './checkupdate'
+import { updater } from './HotUpdater'
+import {platform} from "os";
+
+export default {
+  Mainfunc(IsUseSysTitle) {
+    const allUpdater = new Update();
+    ipcMain.handle('IsUseSysTitle', async () => {
+      return IsUseSysTitle
+    })
+    ipcMain.handle('windows-mini', (event, args) => {
+      BrowserWindow.fromWebContents(event.sender)?.minimize()
+    })
+    ipcMain.handle('window-max', async (event, args) => {
+      if (BrowserWindow.fromWebContents(event.sender)?.isMaximized()) {
+        BrowserWindow.fromWebContents(event.sender)?.unmaximize()
+        return { status: false }
+      } else {
+        BrowserWindow.fromWebContents(event.sender)?.maximize()
+        return { status: true }
+      }
+    })
+    ipcMain.handle('window-close', (event, args) => {
+      BrowserWindow.fromWebContents(event.sender)?.close()
+    })
+    ipcMain.handle('start-download', (event, msg) => {
+      downloadFile.download(BrowserWindow.fromWebContents(event.sender), msg.downloadUrL)
+    })
+    ipcMain.handle('check-update', (event, args) => {
+      allUpdater.checkUpdate(BrowserWindow.fromWebContents(event.sender))
+    })
+    ipcMain.handle('confirm-update', () => {
+      allUpdater.quitInstall()
+    })
+    ipcMain.handle('hot-update', (event, arg) => {
+      updater(BrowserWindow.fromWebContents(event.sender))
+    })
+    ipcMain.handle('open-messagebox', async (event, arg) => {
+      const res = await dialog.showMessageBox(BrowserWindow.fromWebContents(event.sender), {
+        type: arg.type || 'info',
+        title: arg.title || '',
+        buttons: arg.buttons || [],
+        message: arg.message || '',
+        noLink: arg.noLink || true
+      })
+      return res
+    })
+    ipcMain.handle('open-errorbox', (event, arg) => {
+      dialog.showErrorBox(
+        arg.title,
+        arg.message
+      )
+    })
+    ipcMain.handle('statr-server', async () => {
+      try {
+        const serveStatus = await Server.StatrServer()
+        return serveStatus
+      } catch (error) {
+        dialog.showErrorBox(
+          '错误',
+          error
+        )
+      }
+    })
+    ipcMain.handle('stop-server', async (event, arg) => {
+      try {
+        const serveStatus = await Server.StopServer()
+        return serveStatus
+      } catch (error) {
+        dialog.showErrorBox(
+          '错误',
+          error
+        )
+      }
+    })
+    let childWin = null;
+    let cidArray = [];
+    ipcMain.handle('open-win', (event, arg) => {
+      let cidJson = { id: null, url: '' }
+      let data = cidArray.filter((currentValue) => {
+        if (currentValue.url === arg.url) {
+          return currentValue
+        }
+      })
+      if (data.length > 0) {
+        //获取当前窗口
+        let currentWindow = BrowserWindow.fromId(data[0].id)
+        //聚焦窗口
+        currentWindow.focus();
+      } else {
+        //获取主窗口ID
+        let parentID = event.sender.id
+        //创建窗口
+        childWin = new BrowserWindow({
+          width: arg?.width || 842,
+          height: arg?.height || 595,
+          //width 和 height 将设置为 web 页面的尺寸(译注: 不包含边框), 这意味着窗口的实际尺寸将包括窗口边框的大小,稍微会大一点。
+          useContentSize: true,
+          //自动隐藏菜单栏,除非按了Alt键。
+          autoHideMenuBar: true,
+          //窗口大小是否可调整
+          resizable: arg?.resizable ?? false,
+          //窗口的最小高度
+          minWidth: arg?.minWidth || 842,
+          show: arg?.show ?? false,
+          //窗口透明度
+          opacity: arg?.opacity || 1.0,
+          //当前窗口的父窗口ID
+          parent: parentID,
+          frame: IsUseSysTitle,
+          icon: '',
+          webPreferences: {
+            nodeIntegration: true,
+            webSecurity: false,
+            //使用webview标签 必须开启
+            webviewTag: arg?.webview ?? false,
+            // 如果是开发模式可以使用devTools
+            devTools: process.env.NODE_ENV === 'development',
+            // 在macos中启用橡皮动画
+            scrollBounce: process.platform === 'darwin',
+            // 临时修复打开新窗口报错
+            contextIsolation: false
+          }
+        })
+        childWin.loadURL(winURL + `#${arg.url}`)
+        cidJson.id = childWin?.id
+        cidJson.url = arg.url
+        cidArray.push(cidJson)
+        childWin.webContents.once('dom-ready', () => {
+          childWin.show()
+          childWin.webContents.send('send-data', arg.sendData)
+          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.on('closed', () => {
+          childWin = null
+          let index = cidArray.indexOf(cidJson)
+          if (index > -1) {
+            cidArray.splice(index, 1);
+          }
+        })
+      }
+      childWin.on('maximize', () => {
+        if (cidJson.id != null) {
+          BrowserWindow.fromId(cidJson.id).webContents.send("w-max", true)
+        }
+      })
+      childWin.on('unmaximize', () => {
+        if (cidJson.id != null) {
+          BrowserWindow.fromId(cidJson.id).webContents.send("w-max", false)
+        }
+      })
+    })
+  }
+}

+ 105 - 0
src/main/services/windowManager.js

@@ -0,0 +1,105 @@
+import { BrowserWindow, Menu, app } from 'electron'
+import { platform } from "os"
+import menuconfig from '../config/menu'
+import { openDevTools, IsUseSysTitle, UseStartupChart } from '../config/const'
+import setIpc from './ipcMain'
+import { winURL, loadingURL } from '../config/StaticPath'
+
+var loadWindow = null
+var mainWindow = null
+setIpc.Mainfunc(IsUseSysTitle)
+
+function createMainWindow() {
+  /**
+   * Initial window options
+   */
+  mainWindow = new BrowserWindow({
+    height: 800,
+    useContentSize: true,
+    width: 1700,
+    color: "#ffffff",
+    backgroundColor: "#ffffff",
+    minWidth: 1000,
+    show: false,
+    frame: IsUseSysTitle,
+    icon: '',
+    titleBarStyle: platform().includes('win32') ? 'default' : 'hidden',
+    webPreferences: {
+      contextIsolation: false,
+      nodeIntegration: true,
+      webSecurity: false,
+      // 如果是开发模式可以使用devTools
+      devTools: process.env.NODE_ENV === 'development' || openDevTools,
+      // devTools: true,
+      // 在macos中启用橡皮动画
+      scrollBounce: process.platform === 'darwin'
+    }
+  })
+  // 这里设置只有开发环境才注入显示开发者模式
+  if (process.env.NODE_ENV === 'development' || openDevTools) {
+    menuconfig.push({
+      label: '设置',
+      submenu: [{
+        label: '开发者模式',
+        accelerator: 'CmdOrCtrl+I',
+        role: 'toggledevtools'
+      }]
+    })
+  }
+  // 载入菜单
+  const menu = Menu.buildFromTemplate(menuconfig)
+  Menu.setApplicationMenu(menu)
+  mainWindow.loadURL(winURL)
+
+  mainWindow.webContents.once('dom-ready', () => {
+    mainWindow.show()
+    if (process.env.NODE_ENV === 'development' || openDevTools) mainWindow.webContents.openDevTools(true)
+    if (UseStartupChart) loadWindow.destroy()
+  })
+  mainWindow.on('maximize', () => {
+    mainWindow.webContents.send("w-max", true)
+  })
+  mainWindow.on('unmaximize', () => {
+    mainWindow.webContents.send("w-max", false)
+  })
+  mainWindow.on('closed', () => {
+    mainWindow = null
+    app.quit();
+  })
+}
+
+function loadingWindow() {
+  loadWindow = new BrowserWindow({
+    width: 400,
+    height: 600,
+    frame: false,
+    backgroundColor: '#fff',
+    color: '#fff',
+    skipTaskbar: true,
+    transparent: true,
+    resizable: false,
+    icon: '',
+    webPreferences: { experimentalFeatures: true }
+  })
+
+  loadWindow.loadURL(loadingURL)
+
+  loadWindow.show()
+
+  setTimeout(() => {
+    createMainWindow()
+  }, 2000)
+
+  loadWindow.on('closed', () => {
+    loadWindow = null
+  })
+}
+
+function initWindow() {
+  if (UseStartupChart) {
+    return loadingWindow()
+  } else {
+    return createMainWindow()
+  }
+}
+export default initWindow

+ 17 - 0
src/renderer/App.vue

@@ -0,0 +1,17 @@
+<template>
+  <div id="app">
+    <c-header></c-header>
+    <transition name="fade" mode="out-in">
+      <router-view></router-view>
+    </transition>
+  </div>
+</template>
+
+<script setup>
+import CHeader from "./components/title";
+
+</script>
+
+<style>
+/* CSS */
+</style>

+ 58 - 0
src/renderer/api/balance.js

@@ -0,0 +1,58 @@
+import request from '@/utils/request'
+
+// 分页查询余额明细列表
+export function getBalanceList(query) {
+  return request({
+      url: 'backendApi/balance/list',
+      method: 'get',
+      params: query
+  })
+}
+
+// 查询明细详情
+export function getBalanceInfo(memberId) {
+  return request({
+    url: 'backendApi/balance/info/' + memberId,
+    method: 'get'
+  })
+}
+
+// 更新状态
+export function updateBalanceStatus(id, status) {
+  const data = {
+    id,
+    status
+  }
+  return request({
+      url: 'backendApi/balance/updateStatus',
+      method: 'post',
+      data: data
+  })
+}
+
+// 获取配置信息
+export function getSettingInfo() {
+  return request({
+    url: 'backendApi/balance/setting',
+    method: 'get'
+  })
+}
+
+// 保存配置
+export function saveSetting(data) {
+  return request({
+    url: 'backendApi/balance/saveSetting',
+    method: 'post',
+    data: data
+  })
+}
+
+// 确定充值
+export function doRecharge(data) {
+  return request({
+    url: 'backendApi/balance/doRecharge',
+    method: 'post',
+    data: data
+  })
+}
+

+ 124 - 0
src/renderer/api/cashier.js

@@ -0,0 +1,124 @@
+import request from '@/utils/request'
+
+// 初始化数据
+export function init(userId, cateId, page, pageSize) {
+  return request({
+      url: 'backendApi/cashier/init/' + userId,
+      method: 'get',
+      params: { cateId: cateId, page: page, pageSize: pageSize }
+  })
+}
+
+// 查询商品详情
+export function getGoodsInfo(goodsId) {
+  return request({
+    url: 'backendApi/cashier/getGoodsInfo/' + goodsId,
+    method: 'get'
+  })
+}
+
+// 查询商品
+export function searchGoods(data) {
+  return request({
+    url: 'backendApi/cashier/searchGoods',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询会员信息
+export function getMemberInfo(data) {
+  return request({
+    url: 'backendApi/cashier/getMemberInfo',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询会员信息
+export function getMemberInfoById(userId) {
+  return request({
+    url: 'backendApi/cashier/getMemberInfoById/' + userId,
+    method: 'get'
+  })
+}
+
+// 获取购物车列表
+export function getCartList(data) {
+  return request({
+    url: 'clientApi/cart/list',
+    method: 'post',
+    data: data
+  })
+}
+
+// 保存购物车
+export function saveCart(data) {
+  return request({
+    url: 'clientApi/cart/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 删除购物车
+export function removeFromCart(data) {
+  return request({
+    url: 'clientApi/cart/clear',
+    method: 'post',
+    data: data
+  })
+}
+
+// 提交结算
+export function submitSettlement(data) {
+  return request({
+    url: 'clientApi/settlement/submit',
+    method: 'post',
+    data: data
+  })
+}
+
+// 发起支付
+export function doPay(params) {
+  return request({
+    url: 'clientApi/pay/doPay',
+    method: 'get',
+    params: params
+  })
+}
+
+// 获取订单列表
+export function getOrderList(data) {
+  return request({
+    url: 'backendApi/order/latest',
+    method: 'post',
+    data: data
+  })
+}
+
+// 执行挂单
+export function doHangUp(data) {
+  return request({
+    url: 'backendApi/cashier/doHangUp',
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取挂单
+export function getHangUpList() {
+  return request({
+    url: 'backendApi/cashier/getHangUpList',
+    method: 'get'
+  })
+}
+
+// 删除挂单
+export function removeHangUp(data) {
+  return request({
+    url: 'clientApi/cart/clear',
+    method: 'post',
+    data: data
+  })
+}

+ 75 - 0
src/renderer/api/coupon.js

@@ -0,0 +1,75 @@
+import request from '@/utils/request'
+
+// 分页查询卡券列表
+export function getCouponList(query) {
+  return request({
+      url: 'backendApi/coupon/list',
+      method: 'get',
+      params: query
+  })
+}
+
+// 查询卡券信息
+export function getCouponInfo(id) {
+  return request({
+    url: 'backendApi/coupon/info/' + id,
+    method: 'get'
+  })
+}
+
+// 更新状态
+export function updateCouponStatus(id, status) {
+  const data = {
+    id,
+    status
+  }
+  return request({
+      url: 'backendApi/coupon/updateStatus',
+      method: 'post',
+      data: data
+  })
+}
+
+// 删除卡券
+export function deleteCoupon(id) {
+  return request({
+    url: 'backendApi/coupon/delete/' + id,
+    method: 'get'
+  })
+}
+
+// 保存卡券
+export function saveCoupon(data) {
+  return request({
+    url: 'backendApi/coupon/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询卡券核销信息
+export function getConfirmInfo(data) {
+  return request({
+    url: 'backendApi/doConfirm/info',
+    method: 'post',
+    data: data
+  })
+}
+
+// 执行核销
+export function doConfirm(data) {
+  return request({
+    url: 'backendApi/doConfirm/doConfirm',
+    method: 'post',
+    data: data
+  })
+}
+
+// 发放卡券
+export function sendCoupon(params) {
+  return request({
+    url: 'backendApi/coupon/sendCoupon',
+    method: 'get',
+    params: params
+  })
+}

+ 76 - 0
src/renderer/api/goods.js

@@ -0,0 +1,76 @@
+import request from '@/utils/request'
+
+// 分页查询商品列表
+export function getGoodsList(query) {
+  return request({
+      url: 'backendApi/goods/goods/list',
+      method: 'get',
+      params: query
+  })
+}
+
+// 查询商品详情
+export function getGoodsInfo(goodsId) {
+  return request({
+    url: 'backendApi/goods/goods/info/' + goodsId,
+    method: 'get'
+  })
+}
+
+// 更新状态
+export function updateGoodsStatus(id, status) {
+  const data = {
+    id,
+    status
+  }
+  return request({
+      url: 'backendApi/goods/goods/updateStatus',
+      method: 'post',
+      data: data
+  })
+}
+
+// 保存分类数据
+export function saveGoods(data) {
+  return request({
+      url: 'backendApi/goods/goods/save',
+      method: 'post',
+      data: data
+  })
+}
+
+// 保存商品规格名称
+export function saveSpecName(data) {
+  return request({
+      url: 'backendApi/goods/goods/saveSpecName',
+      method: 'post',
+      data: data
+  })
+}
+
+// 保存商品规格值
+export function saveSpecValue(data) {
+  return request({
+      url: 'backendApi/goods/goods/saveSpecValue',
+      method: 'post',
+      data: data
+  })
+}
+
+// 删除商品规格
+export function deleteSpec(query) {
+  return request({
+      url: 'backendApi/goods/goods/deleteSpec',
+      method: 'get',
+      params: query
+  })
+}
+
+// 删除商品规格值
+export function deleteSpecValue(query) {
+  return request({
+      url: 'backendApi/goods/goods/deleteSpecValue',
+      method: 'get',
+      params: query
+  })
+}

+ 51 - 0
src/renderer/api/login.js

@@ -0,0 +1,51 @@
+import request from '@/utils/request'
+
+// 登录方法
+export function login(username, password, captchaCode, uuid) {
+  const data = {
+    username,
+    password,
+    captchaCode,
+    uuid
+  }
+  return request({
+    url: 'backendApi/login/doLogin',
+    headers: {
+      isToken: false
+    },
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取用户详细信息
+export function getInfo() {
+  return request({
+    url: 'backendApi/login/getInfo',
+    method: 'get'
+  })
+}
+
+// 退出方法
+export function logout() {
+  return request({
+    url: 'backendApi/login/logout',
+    method: 'post'
+  })
+}
+
+// 获取验证码
+export function getCodeImg() {
+  return request({
+    url: 'clientApi/captcha/getCode?v='+Math.random(),
+    method: 'get'
+  })
+}
+
+// 系统消息
+export function message () {
+  return request({
+    url: 'clientApi/captcha/message',
+    method: 'get'
+  })
+}

+ 65 - 0
src/renderer/api/member.js

@@ -0,0 +1,65 @@
+import request from '@/utils/request'
+
+// 分页查询会员列表
+export function getMemberList(query) {
+  return request({
+      url: 'backendApi/member/list',
+      method: 'get',
+      params: query
+  })
+}
+
+// 查询会员信息
+export function getMemberInfo(memberId) {
+  return request({
+    url: 'backendApi/member/info/' + memberId,
+    method: 'get'
+  })
+}
+
+// 查询会员设置
+export function getMemberSetting() {
+  return request({
+    url: 'backendApi/member/setting',
+    method: 'get'
+  })
+}
+
+// 保存会员设置
+export function saveSetting(data) {
+  return request({
+    url: 'backendApi/member/saveSetting',
+    method: 'post',
+    data: data
+  })
+}
+
+// 更新会员状态
+export function updateMemberStatus(userId, status) {
+  const data = {
+    userId,
+    status
+  }
+  return request({
+      url: 'backendApi/member/updateStatus',
+      method: 'post',
+      data: data
+  })
+}
+
+// 删除会员信息
+export function deleteMember(memberId) {
+  return request({
+    url: 'backendApi/member/delete/' + memberId,
+    method: 'get'
+  })
+}
+
+// 保存数据
+export function saveMember(data) {
+  return request({
+    url: 'backendApi/member/save',
+    method: 'post',
+    data: data
+  })
+}

+ 83 - 0
src/renderer/api/order.js

@@ -0,0 +1,83 @@
+import request from '@/utils/request'
+
+// 分页查询订单列表
+export function getOrderList(data) {
+  return request({
+      url: 'backendApi/order/list',
+      method: 'post',
+      data: data
+  })
+}
+
+// 查询订单信息
+export function getOrderInfo(orderId) {
+  return request({
+    url: 'backendApi/order/info/' + orderId,
+    method: 'get'
+  })
+}
+
+// 更新订单状态
+export function updateOrderStatus(orderId, status) {
+  const data = {
+    orderId,
+    status
+  }
+  return request({
+      url: 'backendApi/order/updateStatus',
+      method: 'post',
+      data: data
+  })
+}
+
+// 删除订单
+export function deleteOrder(orderId) {
+  return request({
+    url: 'backendApi/order/delete/' + orderId,
+    method: 'get'
+  })
+}
+
+// 保存订单数据
+export function saveOrder(data) {
+  return request({
+    url: 'backendApi/order/save',
+    method: 'post',
+    data: data
+  })
+}
+
+// 验证核销订单
+export function verifyOrder(data) {
+  return request({
+    url: 'backendApi/order/verify',
+    method: 'post',
+    data: data
+  })
+}
+
+// 提交发货信息
+export function delivered(data) {
+  return request({
+    url: 'backendApi/order/delivered',
+    method: 'post',
+    data: data
+  })
+}
+
+// 获取配置信息
+export function getSettingInfo() {
+  return request({
+    url: 'backendApi/order/setting',
+    method: 'get'
+  })
+}
+
+// 保存配置
+export function saveSetting(data) {
+  return request({
+    url: 'backendApi/order/saveSetting',
+    method: 'post',
+    data: data
+  })
+}

+ 58 - 0
src/renderer/api/point.js

@@ -0,0 +1,58 @@
+import request from '@/utils/request'
+
+// 分页查询积分明细列表
+export function getPointList(query) {
+  return request({
+      url: 'backendApi/point/list',
+      method: 'get',
+      params: query
+  })
+}
+
+// 查询明细详情
+export function getPointInfo(memberId) {
+  return request({
+    url: 'backendApi/point/info/' + memberId,
+    method: 'get'
+  })
+}
+
+// 更新状态
+export function updatePointStatus(id, status) {
+  const data = {
+    id,
+    status
+  }
+  return request({
+      url: 'backendApi/point/updateStatus',
+      method: 'post',
+      data: data
+  })
+}
+
+// 获取配置信息
+export function getSettingInfo() {
+  return request({
+    url: 'backendApi/point/setting',
+    method: 'get'
+  })
+}
+
+// 保存配置
+export function saveSetting(data) {
+  return request({
+    url: 'backendApi/point/saveSetting',
+    method: 'post',
+    data: data
+  })
+}
+
+// 确定充值
+export function doRecharge(data) {
+  return request({
+    url: 'backendApi/point/doRecharge',
+    method: 'post',
+    data: data
+  })
+}
+

+ 44 - 0
src/renderer/api/refund.js

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 分页查询退款订单列表
+export function getRefundList(query) {
+  return request({
+      url: 'backendApi/refund/list',
+      method: 'get',
+      params: query
+  })
+}
+
+// 查询订单信息
+export function getRefundInfo(refundId) {
+  return request({
+    url: 'backendApi/refund/info/' + refundId,
+    method: 'get'
+  })
+}
+
+// 订单退款
+export function doRefund(data) {
+  return request({
+      url: 'backendApi/refund/doRefund',
+      method: 'post',
+      data: data
+  })
+}
+
+// 删除退款订单
+export function deleteRefund(refundId) {
+  return request({
+    url: 'backendApi/refund/delete/' + refundId,
+    method: 'get'
+  })
+}
+
+// 保存退款订单
+export function saveRefund(data) {
+  return request({
+    url: 'backendApi/refund/save',
+    method: 'post',
+    data: data
+  })
+}

+ 10 - 0
src/renderer/api/staff.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 分页查询员工列表
+export function getStaffList(query) {
+  return request({
+      url: 'backendApi/staff/list',
+      method: 'get',
+      params: query
+  })
+}

+ 0 - 0
src/renderer/assets/.gitkeep


BIN
src/renderer/assets/404_images/404.png


BIN
src/renderer/assets/404_images/404_cloud.png


BIN
src/renderer/assets/images/avatar.png


BIN
src/renderer/assets/images/bg.png


BIN
src/renderer/assets/images/cashier.png


BIN
src/renderer/assets/images/goods.png


BIN
src/renderer/assets/images/hot.png


BIN
src/renderer/assets/images/life.png


BIN
src/renderer/assets/images/love.png


BIN
src/renderer/assets/images/noGoods.png


BIN
src/renderer/assets/images/office.png


BIN
src/renderer/assets/images/order.png


BIN
src/renderer/assets/logo.png


BIN
src/renderer/assets/user.png


+ 51 - 0
src/renderer/components/Breadcrumb/index.vue

@@ -0,0 +1,51 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index)  in levelList" :key="item.path" v-if="item.meta.title">
+        <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
+        <router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+export default {
+  created() {
+    this.getBreadcrumb()
+  },
+  data() {
+    return {
+      levelList: null
+    }
+  },
+  watch: {
+    $route() {
+      this.getBreadcrumb()
+    }
+  },
+  methods: {
+    getBreadcrumb() {
+      let matched = this.$route.matched.filter(item => item.name)
+      const first = matched[0]
+      if (first && first.name !== 'dashboard') {
+          matched = [{ path: '/dashboard', meta: { title: '总览' }}].concat(matched)
+      }
+      this.levelList = matched
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+  .app-breadcrumb.el-breadcrumb {
+    display: inline-block;
+    font-size: 14px;
+    line-height: 50px;
+    margin-left: 10px;
+    .no-redirect {
+      color: #97a8be;
+      cursor: text;
+    }
+  }
+</style>

+ 46 - 0
src/renderer/components/Hamburger/index.vue

@@ -0,0 +1,46 @@
+<template>
+  <div>
+    <svg t="1492500959545" @click="toggleClick" class="hamburger" :class="{ 'is-active': isActive }" style=""
+      viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691"
+      xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
+      <path
+        d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
+        p-id="1692"></path>
+      <path
+        d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
+        p-id="1693"></path>
+      <path
+        d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
+        p-id="1694"></path>
+    </svg>
+  </div>
+</template>
+
+<script setup>
+defineProps({
+  isActive: {
+    type: Boolean
+  }
+})
+const emits = defineEmits(['toggle-click'])
+const toggleClick = () => {
+  emits('toggle-click')
+}
+
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  cursor: pointer;
+  width: 20px;
+  height: 20px;
+  transform: rotate(90deg);
+  transition: .38s;
+  transform-origin: 50% 50%;
+}
+
+.hamburger.is-active {
+  transform: rotate(0deg);
+}
+</style>

+ 340 - 0
src/renderer/components/LandingPage.vue

@@ -0,0 +1,340 @@
+<template>
+  <div id="wrapper">
+    <img id="logo" :src="logo" alt="electron-vue" style="display: none"/>
+    <main style="display: none">
+      <div class="left-side">
+        <span class="title">{{ $t("welcome") }}</span>
+        <system-information></system-information>
+        <div v-if="textarray.length === 0">
+          <span>{{ text }}</span>
+        </div>
+        <div v-for="(itme, index) in textarray" :key="index" v-else>
+          <span>{{ itme._id }}</span>
+          <span>{{ itme.name }}</span>
+          <span>{{ itme.age }}</span>
+        </div>
+      </div>
+
+      <div class="right-side">
+        <div class="doc">
+          <div class="title alt">{{ $t("buttonTips") }}</div>
+          <el-button type="primary" round @click="open()">{{
+    $t("buttons.console")
+}}</el-button>
+          <el-button type="primary" round @click="CheckUpdate('one')">{{
+    $t("buttons.checkUpdate")
+}}</el-button>
+        </div>
+        <div class="doc">
+          <el-button type="primary" round @click="CheckUpdate('two')">{{
+    $t("buttons.checkUpdate2")
+}}</el-button>
+          <el-button type="primary" round @click="StartServer">{{
+    $t("buttons.startServer")
+}}</el-button>
+          <el-button type="primary" round @click="StopServer">{{
+    $t("buttons.stopServer")
+}}</el-button>
+          <el-button type="primary" round @click="getMessage">{{
+    $t("buttons.viewMessage")
+}}</el-button>
+        </div>
+        <div class="doc">
+          <el-button type="primary" round @click="openNewWin">{{
+    $t("buttons.openNewWindow")
+}}</el-button>
+          <el-button type="primary" round @click="openDocument">{{
+    $t("buttons.openDocument")
+}}</el-button>
+          <el-button type="primary" round @click="changeLanguage">{{
+    $t("buttons.changeLanguage")
+}}</el-button>
+        </div>
+        <div class="doc">
+          <el-pagination :current-page="1" :page-sizes="[100, 200, 300, 400]" :page-size="100"
+            layout="total, sizes, prev, pager, next, jumper" :total="400">
+          </el-pagination>
+        </div>
+      </div>
+    </main>
+    <el-dialog title="进度" :visible.sync="dialogVisible" :before-close="handleClose" center width="14%" top="45vh">
+      <div class="conten">
+        <el-progress type="dashboard" :percentage="percentage" :color="colors" :status="progressStaus"></el-progress>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import SystemInformation from "./LandingPage/SystemInformation";
+import { message } from "@/api/login";
+import { ipcRenderer, shell } from "electron";
+export default {
+  name: "landing-page",
+  components: { SystemInformation },
+  data: () => ({
+    newdata: {
+      name: "yyy",
+      age: "12",
+    },
+    logo: require("@/assets/logo.png"),
+    textarray: [],
+    percentage: 0,
+    colors: [
+      { color: "#f56c6c", percentage: 20 },
+      { color: "#e6a23c", percentage: 40 },
+      { color: "#6f7ad3", percentage: 60 },
+      { color: "#1989fa", percentage: 80 },
+      { color: "#5cb87a", percentage: 100 },
+    ],
+    dialogVisible: false,
+    progressStaus: null,
+    filePath: "",
+  }),
+  created() {
+    console.log("环境打印示例");
+    console.log("__lib路径", __lib);
+    console.log("环境变量", process.env.userConfig);
+    ipcRenderer.on("download-progress", (event, arg) => {
+      this.percentage = Number(arg);
+    });
+    ipcRenderer.on("download-error", (event, arg) => {
+      if (arg) {
+        this.progressStaus = "exception";
+        this.percentage = 40;
+        this.colors = "#d81e06";
+      }
+    });
+    ipcRenderer.on("download-paused", (event, arg) => {
+      if (arg) {
+        this.progressStaus = "warning";
+        this.$alert("下载由于未知原因被中断!", "提示", {
+          confirmButtonText: "重试",
+          callback: (action) => {
+            ipcRenderer.invoke("satrt-download");
+          },
+        });
+      }
+    });
+    ipcRenderer.on("download-done", (event, age) => {
+      this.filePath = age.filePath;
+      this.progressStaus = "success";
+      console.log("下载完成啦");
+      this.$alert("更新下载完成!", "提示", {
+        confirmButtonText: "确定",
+        callback: (action) => {
+          shell.openPath(this.filePath);
+        },
+      });
+    });
+    ipcRenderer.on("update-msg", (event, age) => {
+      console.log("update-msg", age);
+      switch (age.state) {
+        case -1:
+          const msgdata = {
+            title: "发生错误",
+            message: age.msg,
+          };
+          this.dialogVisible = false;
+          ipcRenderer.invoke("open-errorbox", msgdata);
+          break;
+        case 0:
+          this.$message("正在检查更新");
+          break;
+        case 1:
+          this.$message({
+            type: "success",
+            message: "已检查到新版本,开始下载",
+          });
+          this.dialogVisible = true;
+          break;
+        case 2:
+          this.$message({ type: "success", message: "无新版本" });
+          break;
+        case 3:
+          this.percentage = age.msg.percent.toFixed(1);
+          break;
+        case 4:
+          this.progressStaus = "success";
+          this.$alert("更新下载完成!", "提示", {
+            confirmButtonText: "确定",
+            callback: (action) => {
+              ipcRenderer.invoke("confirm-update");
+            },
+          });
+          break;
+
+        default:
+          break;
+      }
+    });
+    ipcRenderer.on('hot-update-status', (event, arg) => {
+      console.log(arg);
+      if (arg.status === 'finished') {
+        this.$message({
+          type: 'success',
+          message: '热更新成功'
+        });
+      }
+    })
+  },
+  methods: {
+    openNewWin() {
+      let data = {
+        url: "/form/index",
+        resizable: true,
+      };
+      ipcRenderer.invoke("open-win", data);
+    },
+    openDocument() {
+      shell.openExternal("https://zh-sky.gitee.io/electron-vue-template-doc/Overview/#%E5%8A%9F%E8%83%BD")
+    },
+    getMessage() {
+      message().then((res) => {
+        this.$alert(res.data, "提示", {
+          confirmButtonText: "确定",
+        });
+      });
+    },
+    StopServer() {
+      ipcRenderer.invoke("stop-server").then((res) => {
+        this.$message({
+          type: "success",
+          message: "已关闭",
+        });
+      });
+    },
+    StartServer() {
+      ipcRenderer.invoke("statr-server").then((res) => {
+        if (res) {
+          this.$message({
+            type: "success",
+            message: res,
+          });
+        }
+      });
+    },
+    // 获取electron方法
+    open() { },
+    CheckUpdate(data) {
+      switch (data) {
+        case "one":
+          ipcRenderer.invoke("check-update").then((res) => {
+            console.log("启动检查");
+          });
+
+          break;
+        case "two":
+          ipcRenderer.invoke("start-download").then(() => {
+            this.dialogVisible = true;
+          });
+
+          break;
+
+        default:
+          break;
+      }
+    },
+    handleClose() {
+      this.dialogVisible = false;
+    },
+    changeLanguage() {
+      let lang = this.$i18n.locale === "zh-CN" ? "en" : "zh-CN";
+      this.$i18n.locale = lang;
+    },
+  },
+  destroyed() {
+    console.log("销毁了哦");
+    ipcRenderer.removeAllListeners("confirm-message");
+    ipcRenderer.removeAllListeners("download-done");
+    ipcRenderer.removeAllListeners("download-paused");
+    ipcRenderer.removeAllListeners("confirm-stop");
+    ipcRenderer.removeAllListeners("confirm-start");
+    ipcRenderer.removeAllListeners("confirm-download");
+    ipcRenderer.removeAllListeners("download-progress");
+    ipcRenderer.removeAllListeners("download-error");
+    ipcRenderer.removeAllListeners("update-msg");
+  },
+  computed: {
+    text() {
+      return this.$i18n.t("waitDataLoading");
+    },
+  },
+};
+</script>
+
+<style>
+* {
+  box-sizing: border-box;
+  margin: 0;
+  padding: 0;
+}
+
+body {
+  font-family: "Source Sans Pro", sans-serif;
+}
+
+#wrapper {
+  padding: 60px 80px;
+}
+
+#logo {
+  height: auto;
+  margin-bottom: 20px;
+  width: 420px;
+}
+
+main {
+  display: flex;
+  justify-content: space-between;
+}
+
+main>div {
+  flex-basis: 50%;
+}
+
+.left-side {
+  display: flex;
+  flex-direction: column;
+}
+
+.welcome {
+  color: #555;
+  font-size: 23px;
+  margin-bottom: 10px;
+}
+
+.title {
+  color: #2c3e50;
+  font-size: 20px;
+  font-weight: bold;
+  margin-bottom: 6px;
+}
+
+.title.alt {
+  font-size: 18px;
+  margin-bottom: 10px;
+}
+
+.doc {
+  margin-bottom: 10px;
+}
+
+.doc p {
+  color: black;
+  margin-bottom: 10px;
+}
+
+.doc .el-button {
+  margin-top: 10px;
+  margin-right: 10px;
+}
+
+.doc .el-button+.el-button {
+  margin-left: 0;
+}
+
+.conten {
+  text-align: center;
+}
+</style>

+ 75 - 0
src/renderer/components/LandingPage/SystemInformation.vue

@@ -0,0 +1,75 @@
+<template>
+  <div>
+    <div class="title">{{ $t("about.system") }}</div>
+    <div class="items">
+      <div class="item" v-for="(item, index) in tips" :key="index">
+        <div class="name" v-text="item.name" />
+        <div class="value" v-text="item.value" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { platform, release, arch } from "os";
+export default {
+  data() {
+    return {
+
+    };
+  },
+  computed: {
+    tips() {
+      return [
+        { name: this.$i18n.t("about.language"), value: this.$i18n.t("about.languageValue") },
+        { name: this.$i18n.t("about.currentPagePath"), value: this.$route.path },
+        { name: this.$i18n.t("about.currentPageName"), value: this.$route.name },
+        { name: this.$i18n.t("about.vueVersion"), value: require("vue/package.json").version },
+        {
+          name: this.$i18n.t("about.electronVersion"),
+          value: process.versions.electron || "浏览器环境",
+        },
+        { name: this.$i18n.t("about.nodeVersion"), value: process.versions.node || "浏览器环境" },
+        { name: this.$i18n.t("about.systemPlatform"), value: platform() },
+        { name: this.$i18n.t("about.systemVersion"), value: release() },
+        { name: this.$i18n.t("about.systemArch"), value: arch() + "位" },
+        { name: this.$i18n.t("about.currentEnvironment"), value: process.env?.NODE_ENV }
+      ]
+    }
+  },
+  mounted() {
+    console.log(this.$route);
+  }
+};
+</script>
+
+<style 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>

+ 114 - 0
src/renderer/components/Pagination/index.vue

@@ -0,0 +1,114 @@
+<template>
+  <div :class="{'hidden':hidden}" class="pagination-container">
+    <el-pagination
+      :background="background"
+      :current-page.sync="currentPage"
+      :page-size.sync="pageSize"
+      :layout="layout"
+      :page-sizes="pageSizes"
+      :pager-count="pagerCount"
+      :total="total"
+      v-bind="$attrs"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script>
+import { scrollTo } from '@/utils/scroll-to'
+
+export default {
+  name: 'Pagination',
+  props: {
+    total: {
+      required: true,
+      type: Number
+    },
+    page: {
+      type: Number,
+      default: 1
+    },
+    limit: {
+      type: Number,
+      default: 20
+    },
+    pageSizes: {
+      type: Array,
+      default() {
+        return [10, 20, 30, 50]
+      }
+    },
+    // 移动端页码按钮的数量端默认值5
+    pagerCount: {
+      type: Number,
+      default: document.body.clientWidth < 992 ? 5 : 7
+    },
+    layout: {
+      type: String,
+      default: 'total, sizes, prev, pager, next, jumper'
+    },
+    background: {
+      type: Boolean,
+      default: true
+    },
+    autoScroll: {
+      type: Boolean,
+      default: true
+    },
+    hidden: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+    };
+  },
+  computed: {
+    currentPage: {
+      get() {
+        return this.page
+      },
+      set(val) {
+        this.$emit('update:page', val)
+      }
+    },
+    pageSize: {
+      get() {
+        return this.limit
+      },
+      set(val) {
+        this.$emit('update:limit', val)
+      }
+    }
+  },
+  methods: {
+    handleSizeChange(val) {
+      if (this.currentPage * val > this.total) {
+        this.currentPage = 1
+      }
+      this.$emit('pagination', { page: this.currentPage, limit: val })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    },
+    handleCurrentChange(val) {
+      this.$emit('pagination', { page: val, limit: this.pageSize })
+      if (this.autoScroll) {
+        scrollTo(0, 800)
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.pagination-container {
+  background: #fff;
+  padding: 32px 16px;
+}
+.pagination-container.hidden {
+  display: none;
+}
+</style>

+ 57 - 0
src/renderer/components/ScrollBar/index.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="scroll-container" ref="scrollContainer" @wheel.prevent="handleScroll" >
+    <div class="scroll-wrapper" ref="scrollWrapper" :style="{top: top + 'px'}">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+const delta = 15
+
+export default {
+  name: 'scrollBar',
+  data() {
+    return {
+      top: 0
+    }
+  },
+  methods: {
+    handleScroll(e) {
+      const eventDelta = e.wheelDelta || -e.deltaY * 3
+      const $container = this.$refs.scrollContainer
+      const $containerHeight = $container.offsetHeight
+      const $wrapper = this.$refs.scrollWrapper
+      const $wrapperHeight = $wrapper.offsetHeight
+      if (eventDelta > 0) {
+        this.top = Math.min(0, this.top + eventDelta)
+      } else {
+        if ($containerHeight - delta < $wrapperHeight) {
+          if (this.top < -($wrapperHeight - $containerHeight + delta)) {
+            this.top = this.top
+          } else {
+            this.top = Math.max(this.top + eventDelta, $containerHeight - $wrapperHeight - delta)
+          }
+        } else {
+          this.top = 0
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+@import '../../styles/variables.scss';
+
+.scroll-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  background-color: $menuBg;
+  .scroll-wrapper {
+    position: absolute;
+     width: 100%!important;
+  }
+}
+</style>

+ 42 - 0
src/renderer/components/SvgIcon/index.vue

@@ -0,0 +1,42 @@
+<template>
+  <svg :class="svgClass" aria-hidden="true">
+    <use :xlink:href="iconName"></use>
+  </svg>
+</template>
+
+<script>
+export default {
+  name: 'svg-icon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String
+    }
+  },
+  computed: {
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>

+ 112 - 0
src/renderer/components/Tinymce/components/EditorImage.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="upload-container">
+    <el-button
+      :style="{ background: color, borderColor: color }"
+      icon="el-icon-upload"
+      size="mini"
+      type="primary"
+      @click="dialogVisible = true"
+      >上传图片</el-button
+    >
+    <el-dialog :visible.sync="dialogVisible">
+      <el-upload
+        ref="upload"
+        :multiple="true"
+        :file-list="fileList"
+        :show-file-list="true"
+        :on-remove="handleRemove"
+        :on-success="handleSuccess"
+        :on-error="handleError"
+        :data="picPostData"
+        class="editor-slide-upload"
+        action="https://jsonplaceholder.typicode.com/post/"
+        list-type="picture-card"
+        :limit="5"
+      >
+        <el-button size="small" type="primary">点击上传</el-button>
+      </el-upload>
+      <el-button @click="dialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">确定</el-button>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "EditorSlideUpload",
+  props: {
+    color: {
+      type: String,
+      default: "#1890ff",
+    },
+  },
+  data() {
+    return {
+      dialogVisible: false,
+      listObj: {},
+      fileList: [],
+      picPostData: {},
+    };
+  },
+  methods: {
+    checkAllSuccess() {
+      return Object.keys(this.listObj).every(
+        (item) => this.listObj[item].hasSuccess
+      );
+    },
+    handleSubmit() {
+      const arr = Object.keys(this.listObj).map((v) => this.listObj[v]);
+      console.log(arr);
+      if (!this.checkAllSuccess()) {
+        this.$message(
+          "请等待所有图片上传完成,如果存在网络问题请刷新当前页重新上传"
+        );
+        return;
+      }
+      this.$emit("successCBK", arr);
+      this.listObj = {};
+      this.fileList = [];
+      this.dialogVisible = false;
+    },
+    handleSuccess(response, file) {
+      console.log("file", file);
+      console.log("handleSuccess", response);
+      const uid = file.uid;
+      const objKeyArr = Object.keys(this.listObj);
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          this.listObj[objKeyArr[i]].url = this.config.qiniuHost + response.key;
+          this.listObj[objKeyArr[i]].hasSuccess = true;
+          return;
+        }
+      }
+    },
+    handleRemove(file) {
+      const uid = file.uid;
+      const objKeyArr = Object.keys(this.listObj);
+      for (let i = 0, len = objKeyArr.length; i < len; i++) {
+        if (this.listObj[objKeyArr[i]].uid === uid) {
+          delete this.listObj[objKeyArr[i]];
+          return;
+        }
+      }
+    },
+    handleError(err) {
+      console.log(err);
+      this.$alert(err, "发生错误", {
+        confirmButtonText: "确定",
+        callback: (action) => {},
+      });
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.editor-slide-upload {
+  margin-bottom: 20px;
+  ::v-deep .el-upload--picture-card {
+    width: 100%;
+  }
+}
+</style>

+ 60 - 0
src/renderer/components/Tinymce/dynamicLoadScript.js

@@ -0,0 +1,60 @@
+let callbacks = []
+
+function loadedTinymce () {
+  // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
+  // check is successfully downloaded script
+  return window.tinymce
+}
+
+const dynamicLoadScript = (src, callback) => {
+  const existingScript = document.getElementById(src)
+  const cb = callback || function () {}
+
+  if (!existingScript) {
+    const script = document.createElement('script')
+    console.log(src)
+    script.src = src // src url for the third-party library being loaded.
+    script.id = src
+    document.body.appendChild(script)
+    callbacks.push(cb)
+    const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
+    onEnd(script)
+  }
+
+  if (existingScript && cb) {
+    if (loadedTinymce()) {
+      cb(null, existingScript)
+    } else {
+      callbacks.push(cb)
+    }
+  }
+
+  function stdOnEnd (script) {
+    script.onload = function () {
+      // this.onload = null here is necessary
+      // because even IE9 works not like others
+      this.onerror = this.onload = null
+      for (const cb of callbacks) {
+        cb(null, script)
+      }
+      callbacks = null
+    }
+    script.onerror = function () {
+      this.onerror = this.onload = null
+      cb(new Error('无法加载 ' + src), script)
+    }
+  }
+
+  function ieOnEnd (script) {
+    script.onreadystatechange = function () {
+      if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
+      this.onreadystatechange = null
+      for (const cb of callbacks) {
+        cb(null, script) // there is no way to catch loading errors in IE8
+      }
+      callbacks = null
+    }
+  }
+}
+
+export default dynamicLoadScript

+ 223 - 0
src/renderer/components/Tinymce/index.vue

@@ -0,0 +1,223 @@
+<template>
+  <div
+    :class="{ fullscreen: fullscreen }"
+    class="tinymce-container"
+    :style="{ width: containerWidth }"
+  >
+    <textarea :id="tinymceId" class="tinymce-textarea" />
+    <div class="editor-custom-btn-container">
+      <editorImage
+        color="#1890ff"
+        class="editor-upload-btn"
+        @successCBK="imageSuccessCBK"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+/**
+ * docs:
+ * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
+ */
+import editorImage from "./components/EditorImage";
+import plugins from "./plugins";
+import toolbar from "./toolbar";
+import load from "./dynamicLoadScript";
+
+const tinymceFile = `${
+  process.browser ? "static" : __static
+}/tinymce/tinymce.min.js`;
+
+export default {
+  name: "Tinymce",
+  components: { editorImage },
+  props: {
+    id: {
+      type: String,
+      default: function () {
+        return (
+          "vue-tinymce-" +
+          +new Date() +
+          ((Math.random() * 1000).toFixed(0) + "")
+        );
+      },
+    },
+    value: {
+      type: String,
+      default: "",
+    },
+    toolbar: {
+      type: Array,
+      required: false,
+      default() {
+        return [];
+      },
+    },
+    menubar: {
+      type: String,
+      default: "",
+    },
+    height: {
+      type: [Number, String],
+      required: false,
+      default: 360,
+    },
+    width: {
+      type: [Number, String],
+      required: false,
+      default: "auto",
+    },
+  },
+  data() {
+    return {
+      hasChange: false,
+      hasInit: false,
+      tinymceId: this.id,
+      fullscreen: false,
+      languageTypeList: {
+        zh: "zh_CN",
+      },
+    };
+  },
+  computed: {
+    containerWidth() {
+      const width = this.width;
+      if (/^[\d]+(\.[\d]+)?$/.test(width)) {
+        // matches `100`, `'100'`
+        return `${width}px`;
+      }
+      return width;
+    },
+  },
+  watch: {
+    value(val) {
+      if (!this.hasChange && this.hasInit) {
+        this.$nextTick(() =>
+          window.tinymce.get(this.tinymceId).setContent(val || "")
+        );
+      }
+    },
+  },
+  mounted() {
+    this.init();
+  },
+  activated() {
+    if (window.tinymce) {
+      this.initTinymce();
+    }
+  },
+  deactivated() {
+    this.destroyTinymce();
+  },
+  destroyed() {
+    this.destroyTinymce();
+  },
+  methods: {
+    init() {
+      // dynamic load tinymce from cdn
+      load(tinymceFile, (err) => {
+        if (err) {
+          this.$message.error(err.message);
+          return;
+        }
+        this.initTinymce();
+      });
+    },
+    initTinymce() {
+      const _this = this;
+      window.tinymce.init({
+        selector: `#${this.tinymceId}`,
+        language: this.languageTypeList["zh"],
+        min_height: this.height,
+        body_class: "panel-body ",
+        draggable_modal: true,
+        object_resizing: false,
+        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
+        menubar: this.menubar,
+        plugins: plugins,
+        fontsize_formats: "12px 14px 16px 18px 24px 36px 48px 56px 72px",
+        font_formats:
+          "微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;",
+        end_container_on_empty_block: true,
+        powerpaste_word_import: "clean",
+        code_dialog_height: 450,
+        code_dialog_width: 1000,
+        custom_undo_redo_levels: 50,
+        advlist_bullet_styles: "square",
+        advlist_number_styles: "default",
+        imagetools_cors_hosts: ["www.tinymce.com", "codepen.io"],
+        default_link_target: "_blank",
+        link_title: false,
+        branding: false,
+        nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
+        init_instance_callback: (editor) => {
+          if (_this.value) {
+            editor.setContent(_this.value);
+          }
+          _this.hasInit = true;
+          editor.on("NodeChange Change KeyUp SetContent", () => {
+            this.hasChange = true;
+            this.$emit("input", editor.getContent());
+          });
+        },
+        setup(editor) {
+          editor.on("FullscreenStateChanged", (e) => {
+            _this.fullscreen = e.state;
+          });
+        },
+      });
+    },
+    destroyTinymce() {
+      const tinymce = window.tinymce.get(this.tinymceId);
+      if (this.fullscreen) {
+        tinymce.execCommand("mceFullScreen");
+      }
+
+      if (tinymce) {
+        tinymce.destroy();
+      }
+    },
+    setContent(value) {
+      window.tinymce.get(this.tinymceId).setContent(value);
+    },
+    getContent() {
+      window.tinymce.get(this.tinymceId).getContent();
+    },
+    imageSuccessCBK(arr) {
+      const _this = this;
+      arr.forEach((v) => {
+        window.tinymce
+          .get(_this.tinymceId)
+          .insertContent(`<img class="wscnph" src="${v.url}" >`);
+      });
+    },
+  },
+};
+</script>
+
+<style scoped>
+.tinymce-container {
+  position: relative;
+  line-height: normal;
+}
+.tinymce-container >>> .mce-fullscreen {
+  z-index: 10000;
+}
+.tinymce-textarea {
+  visibility: hidden;
+  z-index: -1;
+}
+.editor-custom-btn-container {
+  position: absolute;
+  right: 4px;
+  top: 4px;
+}
+.fullscreen .editor-custom-btn-container {
+  z-index: 10000;
+  position: fixed;
+}
+.editor-upload-btn {
+  display: inline-block;
+}
+</style>

+ 7 - 0
src/renderer/components/Tinymce/plugins.js

@@ -0,0 +1,7 @@
+// Any plugins you want to use has to be imported
+// Detail plugins list see https://www.tinymce.com/docs/plugins/
+// Custom builds see https://www.tinymce.com/download/custom-builds/
+
+const plugins = ['print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools help emoticons autoresize']
+
+export default plugins

+ 9 - 0
src/renderer/components/Tinymce/toolbar.js

@@ -0,0 +1,9 @@
+// Here is a list of the toolbar
+// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
+
+// eslint-disable-next-line no-multi-str
+const toolbar = ['code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
+styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
+table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight formatpainter axupimgs']
+
+export default toolbar

+ 116 - 0
src/renderer/components/title/index.vue

@@ -0,0 +1,116 @@
+<!--  -->
+<template>
+  <div class="window-title" v-if="!IsUseSysTitle&&!IsWeb">
+    <!-- 软件logo预留位置 -->
+    <div style="-webkit-app-region: drag;" class="logo" v-if="isNotMac">
+      <svg-icon icon-class="electron-logo"></svg-icon>
+    </div>
+    <!-- 菜单栏位置 -->
+    <div></div>
+    <!-- 中间标题位置 -->
+    <div style="-webkit-app-region: drag;" class="title"></div>
+    <div class="controls-container" v-if="isNotMac">
+      <div class="windows-icon-bg" @click="Mini">
+        <svg-icon icon-class="mini" class-name="icon-size"></svg-icon>
+      </div>
+      <div class="windows-icon-bg" @click="MixOrReduction">
+        <svg-icon v-if="mix" icon-class="reduction" class-name="icon-size"></svg-icon>
+        <svg-icon v-else icon-class="mix" class-name="icon-size"></svg-icon>
+      </div>
+      <div class="windows-icon-bg close-icon" @click="Close">
+        <svg-icon icon-class="close" class-name="icon-size"></svg-icon>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { ipcRenderer } from "electron";
+export default {
+  data: () => ({
+    mix: false,
+    IsUseSysTitle: false,
+    isNotMac: process.platform !== "darwin",
+    IsWeb: process.env.IS_WEB
+  }),
+
+  components: {},
+  created() {
+    ipcRenderer.invoke("IsUseSysTitle").then(res => {
+      this.IsUseSysTitle = res;
+    });
+  },
+
+  mounted() {
+      ipcRenderer.on("w-max",(event,state)=>{
+        this.mix = state
+      })
+  },
+
+  methods: {
+    Mini() {
+      ipcRenderer.invoke("windows-mini");
+    },
+    MixOrReduction() {
+      ipcRenderer.invoke("window-max").then(res=>{
+        this.mix = res.status
+      })
+    },
+    Close() {
+      ipcRenderer.invoke("window-close");
+    }
+  },
+  destroyed() {
+    ipcRenderer.removeAllListeners("w-max");
+  }
+};
+</script>
+<style rel='stylesheet/scss' lang='scss' scoped>
+.window-title {
+  width: 100%;
+  height: 30px;
+  line-height: 30px;
+  display: flex;
+  -webkit-app-region: drag;
+  position: fixed;
+  top: 0;
+  z-index: 99999;
+  .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;
+      }
+    }
+    .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>

+ 64 - 0
src/renderer/directive/dialog/drag.js

@@ -0,0 +1,64 @@
+/**
+* v-dialogDrag 弹窗拖拽
+* Copyright (c) 2022 fuint
+*/
+
+export default {
+  bind(el, binding, vnode, oldVnode) {
+    const value = binding.value
+    if (value == false) return
+    // 获取拖拽内容头部
+    const dialogHeaderEl = el.querySelector('.el-dialog__header');
+    const dragDom = el.querySelector('.el-dialog');
+    dialogHeaderEl.style.cursor = 'move';
+    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+    dragDom.style.position = 'absolute';
+    dragDom.style.marginTop = 0;
+    let width = dragDom.style.width;
+    if (width.includes('%')) {
+      width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100);
+    } else {
+      width = +width.replace(/\px/g, '');
+    }
+    dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;
+    // 鼠标按下事件
+    dialogHeaderEl.onmousedown = (e) => {
+      // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
+      const disX = e.clientX - dialogHeaderEl.offsetLeft;
+      const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+      // 获取到的值带px 正则匹配替换
+      let styL, styT;
+
+      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+      if (sty.left.includes('%')) {
+        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+      } else {
+        styL = +sty.left.replace(/\px/g, '');
+        styT = +sty.top.replace(/\px/g, '');
+      };
+
+      // 鼠标拖拽事件
+      document.onmousemove = function (e) {
+        // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
+        const l = e.clientX - disX;
+        const t = e.clientY - disY;
+
+        let finallyL = l + styL
+        let finallyT = t + styT
+
+        // 移动当前元素
+        dragDom.style.left = `${finallyL}px`;
+        dragDom.style.top = `${finallyT}px`;
+
+      };
+
+      document.onmouseup = function (e) {
+        document.onmousemove = null;
+        document.onmouseup = null;
+      };
+    }
+  }
+};

+ 34 - 0
src/renderer/directive/dialog/dragHeight.js

@@ -0,0 +1,34 @@
+/**
+* v-dialogDragWidth 可拖动弹窗高度(右下角)
+* Copyright (c) 2022 fuint
+*/
+
+export default {
+    bind(el) {
+        const dragDom = el.querySelector('.el-dialog');
+        const lineEl = document.createElement('div');
+        lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;';
+        lineEl.addEventListener('mousedown',
+            function(e) {
+                // 鼠标按下,计算当前元素距离可视区的距离
+                const disX = e.clientX - el.offsetLeft;
+                const disY = e.clientY - el.offsetTop;
+                // 当前宽度 高度
+                const curWidth = dragDom.offsetWidth;
+                const curHeight = dragDom.offsetHeight;
+                document.onmousemove = function(e) {
+                    e.preventDefault(); // 移动时禁用默认事件
+                    // 通过事件委托,计算移动的距离
+                    const xl = e.clientX - disX;
+                    const yl = e.clientY - disY
+                    dragDom.style.width = `${curWidth + xl}px`;
+                    dragDom.style.height = `${curHeight + yl}px`;
+                };
+                document.onmouseup = function(e) {
+                    document.onmousemove = null;
+                    document.onmouseup = null;
+                };
+            }, false);
+        dragDom.appendChild(lineEl);
+    }
+}

+ 30 - 0
src/renderer/directive/dialog/dragWidth.js

@@ -0,0 +1,30 @@
+/**
+* v-dialogDragWidth 可拖动弹窗宽度(右侧边)
+* Copyright (c) 2022 fuint
+*/
+
+export default {
+    bind(el) {
+        const dragDom = el.querySelector('.el-dialog');
+        const lineEl = document.createElement('div');
+        lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;';
+        lineEl.addEventListener('mousedown',
+            function (e) {
+                // 鼠标按下,计算当前元素距离可视区的距离
+                const disX = e.clientX - el.offsetLeft;
+                // 当前宽度
+                const curWidth = dragDom.offsetWidth;
+                document.onmousemove = function (e) {
+                    e.preventDefault(); // 移动时禁用默认事件
+                    // 通过事件委托,计算移动的距离
+                    const l = e.clientX - disX;
+                    dragDom.style.width = `${curWidth + l}px`;
+                };
+                document.onmouseup = function (e) {
+                    document.onmousemove = null;
+                    document.onmouseup = null;
+                };
+            }, false);
+        dragDom.appendChild(lineEl);
+    }
+}

+ 23 - 0
src/renderer/directive/index.js

@@ -0,0 +1,23 @@
+import hasRole from './permission/hasRole'
+import hasPermi from './permission/hasPermi'
+import dialogDrag from './dialog/drag'
+import dialogDragWidth from './dialog/dragWidth'
+import dialogDragHeight from './dialog/dragHeight'
+import clipboard from './module/clipboard'
+
+const install = function(Vue) {
+  Vue.directive('hasRole', hasRole)
+  Vue.directive('hasPermi', hasPermi)
+  Vue.directive('clipboard', clipboard)
+  Vue.directive('dialogDrag', dialogDrag)
+  Vue.directive('dialogDragWidth', dialogDragWidth)
+  Vue.directive('dialogDragHeight', dialogDragHeight)
+}
+
+if (window.Vue) {
+  window['hasRole'] = hasRole
+  window['hasPermi'] = hasPermi
+  Vue.use(install); // eslint-disable-line
+}
+
+export default install

+ 54 - 0
src/renderer/directive/module/clipboard.js

@@ -0,0 +1,54 @@
+/**
+* v-clipboard 文字复制剪贴
+* Copyright (c) 2022 fuint
+*/
+
+import Clipboard from 'clipboard'
+export default {
+  bind(el, binding, vnode) {
+    switch (binding.arg) {
+      case 'success':
+        el._vClipBoard_success = binding.value;
+        break;
+      case 'error':
+        el._vClipBoard_error = binding.value;
+        break;
+      default: {
+        const clipboard = new Clipboard(el, {
+          text: () => binding.value,
+          action: () => binding.arg === 'cut' ? 'cut' : 'copy'
+        });
+        clipboard.on('success', e => {
+          const callback = el._vClipBoard_success;
+          callback && callback(e);
+        });
+        clipboard.on('error', e => {
+          const callback = el._vClipBoard_error;
+          callback && callback(e);
+        });
+        el._vClipBoard = clipboard;
+      }
+    }
+  },
+  update(el, binding) {
+    if (binding.arg === 'success') {
+      el._vClipBoard_success = binding.value;
+    } else if (binding.arg === 'error') {
+      el._vClipBoard_error = binding.value;
+    } else {
+      el._vClipBoard.text = function () { return binding.value; };
+      el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy';
+    }
+  },
+  unbind(el, binding) {
+    if (!el._vClipboard) return
+    if (binding.arg === 'success') {
+      delete el._vClipBoard_success;
+    } else if (binding.arg === 'error') {
+      delete el._vClipBoard_error;
+    } else {
+      el._vClipBoard.destroy();
+      delete el._vClipBoard;
+    }
+  }
+}

+ 23 - 0
src/renderer/directive/permission/hasPermi.js

@@ -0,0 +1,23 @@
+ /**
+  * v-hasPermi 操作权限处理
+  * Copyright https://www.fuint.cn
+  */
+export default {
+  inserted(el, binding, vnode) {
+    const { value } = binding
+    const all_permission = "*:*:*";
+    const permissions = localStorage.getItem("permissions") ? JSON.parse(localStorage.getItem("permissions")) : [];
+
+    if (permissions.length > 0 && value && value instanceof Array && value.length > 0) {
+        const permissionFlag = value
+        const hasPermissions = permissions.some(permission => {
+            return all_permission === permission || permissionFlag.includes(permission)
+        })
+        if (!hasPermissions) {
+            el.parentNode && el.parentNode.removeChild(el)
+        }
+    } else {
+         throw new Error(`请设置操作权限标签值`)
+    }
+  }
+}

+ 22 - 0
src/renderer/directive/permission/hasRole.js

@@ -0,0 +1,22 @@
+ /**
+ * v-hasRole 角色权限处理
+ * Copyright (c) 2023 https://www.fuint.cn
+ */
+export default {
+  inserted(el, binding, vnode) {
+    const { value } = binding
+    const super_admin = "admin";
+    const roles = localStorage.getItem("roles") ? localStorage.getItem("roles") : [];
+    if (roles.length > 0 && value && value instanceof Array && value.length > 0) {
+        const roleFlag = value
+        const hasRole = roles.some(role => {
+            return super_admin === role || roleFlag.includes(role)
+        })
+        if (!hasRole) {
+            el.parentNode && el.parentNode.removeChild(el)
+        }
+    } else {
+      throw new Error(`请设置角色权限标签值"`)
+    }
+  }
+}

+ 17 - 0
src/renderer/error.js

@@ -0,0 +1,17 @@
+import Vue from 'vue'
+
+Vue.config.errorHandler = function (err, vm, info) {
+  Vue.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()
+    }
+  })
+}

+ 7 - 0
src/renderer/hooks/use-router.js

@@ -0,0 +1,7 @@
+import { getCurrentInstance } from 'vue'
+export const useRoute = () => {
+    return getCurrentInstance()?.proxy.$route
+}
+export const useRouter = () => {
+    return getCurrentInstance()?.proxy.$router
+}

+ 25 - 0
src/renderer/i18n/index.js

@@ -0,0 +1,25 @@
+export default function loadLanguage() {
+    const context = require.context("./languages", false, /([a-z_]+)\.js$/i)
+
+    const languages = context
+        .keys()
+        .map((key) => ({ key, name: key.match(/([a-z_-]+)\.js$/i)[1] }))
+        .reduce(
+            (languages, {key, name}) => {
+                let lang;
+                try {
+                    // 引入 element-ui 语言包
+                    lang = Object.assign(context(key).lang, require(`element-ui/lib/locale/lang/${name}`).default);
+                } catch(err) {
+                    lang = context(key).lang
+                }
+                return {
+                    ...languages,
+                    [name]: lang
+                }
+            },
+            {}
+        )
+
+    return languages
+}

+ 30 - 0
src/renderer/i18n/languages/en.js

@@ -0,0 +1,30 @@
+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:",
+        currentEnvironment:"current environment:"
+    },
+    buttons: {
+        console: "Console",
+        checkUpdate: "Check update",
+        checkUpdate2: "Check update(plan 2)",
+        startServer: "Start server",
+        stopServer: "Stop server",
+        viewMessage: "view message",
+        openNewWindow: "Open new window",
+        openDocument: "Open document",
+        changeLanguage: "Change language"
+    }
+}

+ 30 - 0
src/renderer/i18n/languages/zh-CN.js

@@ -0,0 +1,30 @@
+export const lang = {
+    welcome: "欢迎进入本框架",
+    buttonTips: "您可以点击的按钮测试功能",
+    waitDataLoading: "等待数据读取",
+    about: {
+        system: "关于系统",
+        language: "语言:",
+        languageValue: "中文简体",
+        currentPagePath: "当前页面路径:",
+        currentPageName: "当前页面名称:",
+        vueVersion: "Vue版本:",
+        electronVersion: "Electron版本:",
+        nodeVersion: "Node版本:",
+        systemPlatform: "系统平台:",
+        systemVersion: "系统版本:",
+        systemArch: "系统位数:",
+        currentEnvironment:'当前环境:'
+    },
+    buttons: {
+        console: "控制台打印",
+        checkUpdate: "检查更新",
+        checkUpdate2: "检查更新(第二种方法)",
+        startServer: "启动内置服务端",
+        stopServer: "关闭内置服务端",
+        viewMessage: "查看消息",
+        openNewWindow: "打开新窗口",
+        openDocument: "打开文档",
+        changeLanguage: "切换语言"
+    }
+}

+ 9 - 0
src/renderer/icons/index.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg组件
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+const req = require.context('./svg', false, /\.svg$/)
+requireAll(req)

+ 1 - 0
src/renderer/icons/svg/close.svg

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

+ 529 - 0
src/renderer/icons/svg/electron-logo.svg

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

+ 1 - 0
src/renderer/icons/svg/example.svg

@@ -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 t="1511504199105" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1815" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M770.56 460.8h250.88C998.4 220.16 803.84 25.6 563.2 2.56v250.88c104.96 20.48 186.88 102.4 207.36 207.36z m0 0M460.8 253.44V2.56C220.16 25.6 25.6 220.16 2.56 460.8h250.88c20.48-104.96 102.4-186.88 207.36-207.36z m0 0M563.2 770.56v250.88c243.2-23.04 435.2-217.6 460.8-460.8H773.12C750.08 668.16 668.16 750.08 563.2 770.56z m0 0M253.44 563.2H2.56c23.04 243.2 217.6 435.2 460.8 460.8V773.12C355.84 750.08 273.92 668.16 253.44 563.2z m0 0" fill="" p-id="1816"></path></svg>

+ 1 - 0
src/renderer/icons/svg/eye-open.svg

@@ -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 t="1577518173588" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6318" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085z m0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334z m0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333z m0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z" p-id="6319"></path></svg>

+ 1 - 0
src/renderer/icons/svg/eye.svg

@@ -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 t="1503993826520" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7878" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M941.677063 391.710356c9.337669-14.005992 6.224772-32.68133-6.224772-43.575447-14.005992-10.894118-32.68133-7.78122-43.575447 6.224771-1.556449 1.556449-174.300768 205.426673-379.727441 205.426673-199.200878 0-379.727441-205.426673-381.28389-206.982098-10.894118-12.450567-31.124881-14.005992-43.575448-3.112898-12.450567 10.894118-14.005992 31.124881-3.112897 43.575448 3.112897 4.668323 40.46255 46.687322 99.600439 93.375667l-79.369676 82.48155c-12.450567 12.450567-10.894118 32.68133 1.556449 43.575448 3.112897 6.224772 10.894118 9.337669 18.675338 9.337669 7.78122 0 15.562441-3.112897 21.787213-9.337669l85.594447-88.706321c40.46255 28.013007 88.706321 54.469566 141.619438 73.14388L340.959485 707.631586c-4.668323 17.118889 4.669346 34.237779 21.787213 38.906101h9.337669c14.005992 0 26.456558-9.337669 29.568432-23.343661l32.68133-110.494556c24.90011 4.668323 51.356668 7.78122 77.813227 7.78122s52.913117-3.112897 77.813227-7.78122l32.68133 108.938108c3.112897 14.005992 17.118889 23.343661 29.569456 23.343661 3.112897 0 6.224772 0 7.78122-1.556449 17.118889-4.669346 26.456558-21.787212 21.788236-38.906102l-32.68133-108.938108c52.913117-18.675338 101.156888-45.131897 141.619438-73.14388l84.037998 87.150896c6.224772 6.224772 14.005992 9.337669 21.787212 9.337669 7.78122 0 15.562441-3.112897 21.787212-9.337669 12.450567-12.450567 12.450567-31.124881 1.556449-43.575448l-79.369675-82.48155c63.808258-46.688345 101.158934-91.820242 101.158934-91.820242z" p-id="7879"></path></svg>

+ 1 - 0
src/renderer/icons/svg/form.svg

@@ -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 t="1511504319223" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3230" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M942.827259 80.3367c-11.42419-11.406794-26.41051-17.117866-41.377386-17.117866-14.985296 0-29.952172 5.711072-41.358967 17.117866L719.392444 221.014696l-19.441794 19.441794L681.577187 258.832 569.516971 370.909611 375.99749 564.411697l0 0.019443 0 84.372619 81.145112 0 0.010233 0 95.418186-95.435583 213.398228-213.400275 3.14155-3.14155-0.019443 0 9.979282-9.977235 0 0L942.827259 163.073052C965.697129 140.259464 965.697129 103.186104 942.827259 80.3367z" p-id="3231"></path><path d="M793.542234 367.521444 580.14196 580.939115 484.72582 676.376745 473.299583 687.800935 457.152834 687.800935 375.99749 687.800935 337.000314 687.800935 337.000314 648.803759 337.000314 564.411697 337.000314 548.264948 348.424504 536.838711 541.943986 343.338672 654.004201 231.259014 665.428392 219.834824 64.020082 219.834824 64.020082 960.781166 804.966425 960.781166 804.966425 356.116697 796.607036 364.475062Z" p-id="3232"></path></svg>

+ 1 - 0
src/renderer/icons/svg/logo.svg

@@ -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 t="1564902100209" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="840" xmlns:xlink="http://www.w3.org/1999/xlink" width="300" height="300"><defs><style type="text/css"></style></defs><path d="M615.6 123.6h165.5L512 589.7 242.9 123.6H63.5L512 900.4l448.5-776.9z" fill="#41B883" p-id="841"></path><path d="M781.1 123.6H615.6L512 303 408.4 123.6H242.9L512 589.7z" fill="#34495E" p-id="842"></path></svg>

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