ソースを参照

0.1.19版本提交

laiguoran 6 年 前
コミット
9458fe408a
87 ファイル変更22572 行追加0 行削除
  1. 30 0
      .babelrc
  2. 132 0
      .electron-vue/build.js
  3. 40 0
      .electron-vue/dev-client.js
  4. 190 0
      .electron-vue/dev-runner.js
  5. 83 0
      .electron-vue/webpack.main.config.js
  6. 190 0
      .electron-vue/webpack.renderer.config.js
  7. 151 0
      .electron-vue/webpack.web.config.js
  8. 0 0
      .eslintignore
  9. 26 0
      .eslintrc.js
  10. 12 0
      .gitignore
  11. 36 0
      .travis.yml
  12. 27 0
      README.md
  13. 29 0
      appveyor.yml
  14. BIN
      build/icons/256x256.png
  15. BIN
      build/icons/icon.icns
  16. BIN
      build/icons/icon.ico
  17. BIN
      build/icons/setup.ico
  18. BIN
      build/icons/smartcost.ico
  19. BIN
      build/icons/uninst.ico
  20. 8 0
      build/nsisfile/installer.nsh
  21. 11 0
      build/nsisfile/installer.nsi
  22. BIN
      data/fileInfo.dll
  23. 1 0
      data/sc_software.json
  24. BIN
      data/zhlData.dll
  25. 132 0
      package.json
  26. 57 0
      src/database/index.js
  27. 24 0
      src/index.ejs
  28. 24 0
      src/main/index.dev.js
  29. 159 0
      src/main/index.js
  30. 185 0
      src/main/main-process/downloads.js
  31. 159 0
      src/main/main-process/exe-install.js
  32. 275 0
      src/main/main-process/file-select.js
  33. 314 0
      src/main/main-process/updateInstall.js
  34. 86 0
      src/main/main-process/usb-ffi.js
  35. 64 0
      src/main/software-update.js
  36. 18 0
      src/renderer/App.vue
  37. 0 0
      src/renderer/assets/.gitkeep
  38. 7 0
      src/renderer/assets/bootstrap-grid.min.css
  39. 8 0
      src/renderer/assets/bootstrap-reboot.min.css
  40. 7 0
      src/renderer/assets/bootstrap.min.css
  41. BIN
      src/renderer/assets/font-awesome/font/fa-brands-400.eot
  42. 1104 0
      src/renderer/assets/font-awesome/font/fa-brands-400.svg
  43. BIN
      src/renderer/assets/font-awesome/font/fa-brands-400.ttf
  44. BIN
      src/renderer/assets/font-awesome/font/fa-brands-400.woff
  45. BIN
      src/renderer/assets/font-awesome/font/fa-brands-400.woff2
  46. BIN
      src/renderer/assets/font-awesome/font/fa-regular-400.eot
  47. 372 0
      src/renderer/assets/font-awesome/font/fa-regular-400.svg
  48. BIN
      src/renderer/assets/font-awesome/font/fa-regular-400.ttf
  49. BIN
      src/renderer/assets/font-awesome/font/fa-regular-400.woff
  50. BIN
      src/renderer/assets/font-awesome/font/fa-regular-400.woff2
  51. BIN
      src/renderer/assets/font-awesome/font/fa-solid-900.eot
  52. 1896 0
      src/renderer/assets/font-awesome/font/fa-solid-900.svg
  53. BIN
      src/renderer/assets/font-awesome/font/fa-solid-900.ttf
  54. BIN
      src/renderer/assets/font-awesome/font/fa-solid-900.woff
  55. BIN
      src/renderer/assets/font-awesome/font/fa-solid-900.woff2
  56. 4013 0
      src/renderer/assets/font-awesome/fontawesome-all.css
  57. BIN
      src/renderer/assets/font/pingfang.ttf
  58. 668 0
      src/renderer/assets/global.css
  59. BIN
      src/renderer/assets/img/bg.jpg
  60. BIN
      src/renderer/assets/img/bg1.jpg
  61. BIN
      src/renderer/assets/img/bg2.jpg
  62. BIN
      src/renderer/assets/img/bodyBg.jpg
  63. BIN
      src/renderer/assets/img/header-bg.png
  64. BIN
      src/renderer/assets/img/headimg.png
  65. BIN
      src/renderer/assets/img/logo.png
  66. BIN
      src/renderer/assets/img/mainlogo.png
  67. BIN
      src/renderer/assets/logo.png
  68. 128 0
      src/renderer/components/LandingPage.vue
  69. 203 0
      src/renderer/components/StartUpPage.vue
  70. 308 0
      src/renderer/components/StartUpPage/DownloadHeader.vue
  71. 36 0
      src/renderer/components/StartUpPage/FirstOpen.vue
  72. 167 0
      src/renderer/components/StartUpPage/SoftwareDetail.vue
  73. 103 0
      src/renderer/components/StartUpPage/SoftwareList.vue
  74. 181 0
      src/renderer/components/StartUpPage/SoftwareStartup.vue
  75. 244 0
      src/renderer/components/StartUpPage/SoftwareStartupDetail.vue
  76. 67 0
      src/renderer/components/StartUpPage/SoftwareUpdate.vue
  77. 128 0
      src/renderer/components/StartUpPage/UpdateHeader.vue
  78. 195 0
      src/renderer/components/StartUpPage/UsbHeader.vue
  79. 451 0
      src/renderer/components/StartUpPage/UserHeader.vue
  80. 26 0
      src/renderer/components/mixin.js
  81. 27 0
      src/renderer/main.js
  82. 47 0
      src/renderer/router/index.js
  83. 11 0
      src/renderer/store/index.js
  84. 25 0
      src/renderer/store/modules/Counter.js
  85. 14 0
      src/renderer/store/modules/index.js
  86. 0 0
      static/.gitkeep
  87. 9673 0
      yarn.lock

+ 30 - 0
.babelrc

@@ -0,0 +1,30 @@
+{
+  "comments": false,
+  "env": {
+    "main": {
+      "presets": [
+        ["env", {
+          "targets": { "node": 7 }
+        }],
+        "stage-0"
+      ]
+    },
+    "renderer": {
+      "presets": [
+        ["env", {
+          "modules": false
+        }],
+        "stage-0"
+      ]
+    },
+    "web": {
+      "presets": [
+        ["env", {
+          "modules": false
+        }],
+        "stage-0"
+      ]
+    }
+  },
+  "plugins": ["transform-runtime"]
+}

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

@@ -0,0 +1,132 @@
+'use strict'
+
+process.env.NODE_ENV = 'production'
+
+const { say } = require('cfonts')
+const chalk = require('chalk')
+const del = require('del')
+const { spawn } = require('child_process')
+const webpack = require('webpack')
+const Multispinner = require('multispinner')
+
+
+const mainConfig = require('./webpack.main.config')
+const rendererConfig = require('./webpack.renderer.config')
+const webConfig = require('./webpack.web.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 === 'clean') clean()
+else if (process.env.BUILD_TARGET === 'web') web()
+else build()
+
+function clean () {
+  del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
+  console.log(`\n${doneLog}\n`)
+  process.exit()
+}
+
+function build () {
+  greeting()
+
+  del.sync(['dist/electron/*', '!.gitkeep'])
+
+  const tasks = ['main', 'renderer']
+  const m = new Multispinner(tasks, {
+    preText: 'building',
+    postText: 'process'
+  })
+
+  let results = ''
+
+  m.on('success', () => {
+    process.stdout.write('\x1B[2J\x1B[0f')
+    console.log(`\n\n${results}`)
+    console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
+    process.exit()
+  })
+
+  pack(mainConfig).then(result => {
+    results += result + '\n\n'
+    m.success('main')
+  }).catch(err => {
+    m.error('main')
+    console.log(`\n  ${errorLog}failed to build main process`)
+    console.error(`\n${err}\n`)
+    process.exit(1)
+  })
+
+  pack(rendererConfig).then(result => {
+    results += result + '\n\n'
+    m.success('renderer')
+  }).catch(err => {
+    m.error('renderer')
+    console.log(`\n  ${errorLog}failed to build renderer process`)
+    console.error(`\n${err}\n`)
+    process.exit(1)
+  })
+}
+
+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'])
+  webConfig.mode = 'production'
+  webpack(webConfig, (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 = 'lets-build'
+  else if (cols > 60) text = 'lets-|build'
+  else text = false
+
+  if (text && !isCI) {
+    say(text, {
+      colors: ['yellow'],
+      font: 'simple3d',
+      space: false
+    })
+  } else console.log(chalk.yellow.bold('\n  lets-build'))
+  console.log()
+}

+ 40 - 0
.electron-vue/dev-client.js

@@ -0,0 +1,40 @@
+const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
+
+hotClient.subscribe(event => {
+  /**
+   * Reload browser when HTMLWebpackPlugin emits a new index.html
+   *
+   * Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
+   * https://github.com/SimulatedGREG/electron-vue/issues/437
+   * https://github.com/jantimon/html-webpack-plugin/issues/680
+   */
+  // if (event.action === 'reload') {
+  //   window.location.reload()
+  // }
+
+  /**
+   * Notify `mainWindow` when `main` process is compiling,
+   * giving notice for an expected reload of the `electron` process
+   */
+  if (event.action === 'compiling') {
+    document.body.innerHTML += `
+      <style>
+        #dev-client {
+          background: #4fc08d;
+          border-radius: 4px;
+          bottom: 20px;
+          box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
+          color: #fff;
+          font-family: 'Source Sans Pro', sans-serif;
+          left: 20px;
+          padding: 8px 12px;
+          position: absolute;
+        }
+      </style>
+
+      <div id="dev-client">
+        Compiling Main Process...
+      </div>
+    `
+  }
+})

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

@@ -0,0 +1,190 @@
+'use strict'
+
+const chalk = require('chalk')
+const electron = require('electron')
+const path = require('path')
+const { say } = require('cfonts')
+const { spawn } = require('child_process')
+const webpack = require('webpack')
+const WebpackDevServer = require('webpack-dev-server')
+const webpackHotMiddleware = require('webpack-hot-middleware')
+
+const mainConfig = require('./webpack.main.config')
+const rendererConfig = require('./webpack.renderer.config')
+
+let electronProcess = null
+let manualRestart = false
+let hotMiddleware
+
+function logStats (proc, data) {
+  let log = ''
+
+  log += chalk.yellow.bold(`┏ ${proc} 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 startRenderer () {
+  return new Promise((resolve, reject) => {
+    rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)
+    rendererConfig.mode = 'development'
+    const compiler = webpack(rendererConfig)
+    hotMiddleware = webpackHotMiddleware(compiler, {
+      log: false,
+      heartbeat: 2500
+    })
+
+    compiler.hooks.compilation.tap('compilation', compilation => {
+      compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => {
+        hotMiddleware.publish({ action: 'reload' })
+        cb()
+      })
+    })
+
+    compiler.hooks.done.tap('done', stats => {
+      logStats('Renderer', stats)
+    })
+
+    const server = new WebpackDevServer(
+      compiler,
+      {
+        contentBase: path.join(__dirname, '../'),
+        quiet: true,
+        before (app, ctx) {
+          app.use(hotMiddleware)
+          ctx.middleware.waitUntilValid(() => {
+            resolve()
+          })
+        }
+      }
+    )
+
+    server.listen(9080)
+  })
+}
+
+function startMain () {
+  return new Promise((resolve, reject) => {
+    mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)
+    mainConfig.mode = 'development'
+    const compiler = webpack(mainConfig)
+
+    compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => {
+      logStats('Main', chalk.white.bold('compiling...'))
+      hotMiddleware.publish({ action: 'compiling' })
+      done()
+    })
+
+    compiler.watch({}, (err, stats) => {
+      if (err) {
+        console.log(err)
+        return
+      }
+
+      logStats('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(data, 'blue')
+  })
+  electronProcess.stderr.on('data', data => {
+    electronLog(data, 'red')
+  })
+
+  electronProcess.on('close', () => {
+    if (!manualRestart) process.exit()
+  })
+}
+
+function electronLog (data, color) {
+  let log = ''
+  data = data.toString().split(/\r?\n/)
+  data.forEach(line => {
+    log += `  ${line}\n`
+  })
+  if (/[0-9A-z]+/.test(log)) {
+    console.log(
+      chalk[color].bold('┏ 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('  getting ready...') + '\n')
+}
+
+function init () {
+  greeting()
+
+  Promise.all([startRenderer(), startMain()])
+    .then(() => {
+      startElectron()
+    })
+    .catch(err => {
+      console.error(err)
+    })
+}
+
+init()

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

@@ -0,0 +1,83 @@
+'use strict'
+
+process.env.BABEL_ENV = 'main'
+
+const path = require('path')
+const { dependencies } = require('../package.json')
+const webpack = require('webpack')
+
+const BabiliWebpackPlugin = require('babili-webpack-plugin')
+
+let mainConfig = {
+  entry: {
+    main: path.join(__dirname, '../src/main/index.js')
+  },
+  externals: [
+    ...Object.keys(dependencies || {})
+  ],
+  module: {
+    rules: [
+      {
+        test: /\.(js)$/,
+        enforce: 'pre',
+        exclude: /node_modules/,
+        use: {
+          loader: 'eslint-loader',
+          options: {
+            formatter: require('eslint-friendly-formatter')
+          }
+        }
+      },
+      {
+        test: /\.js$/,
+        use: 'babel-loader',
+        exclude: /node_modules/
+      },
+      {
+        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.NoEmitOnErrorsPlugin()
+  ],
+  resolve: {
+    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, '\\\\')}"`
+    })
+  )
+}
+
+/**
+ * Adjust mainConfig for production settings
+ */
+if (process.env.NODE_ENV === 'production') {
+  mainConfig.plugins.push(
+    new BabiliWebpackPlugin(),
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': '"production"'
+    })
+  )
+}
+
+module.exports = mainConfig

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

@@ -0,0 +1,190 @@
+'use strict'
+
+process.env.BABEL_ENV = 'renderer'
+
+const path = require('path')
+const { dependencies } = require('../package.json')
+const webpack = require('webpack')
+
+const BabiliWebpackPlugin = require('babili-webpack-plugin')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const { VueLoaderPlugin } = require('vue-loader')
+
+/**
+ * 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 whiteListedModules = ['vue']
+
+let rendererConfig = {
+  devtool: '#cheap-module-eval-source-map',
+  entry: {
+    renderer: path.join(__dirname, '../src/renderer/main.js')
+  },
+  externals: [
+    ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
+  ],
+  module: {
+    rules: [
+      {
+        test: /\.(js|vue)$/,
+        enforce: 'pre',
+        exclude: /node_modules/,
+        use: {
+          loader: 'eslint-loader',
+          options: {
+            formatter: require('eslint-friendly-formatter')
+          }
+        }
+      },
+      {
+        test: /\.scss$/,
+        use: ['vue-style-loader', 'css-loader', 'sass-loader']
+      },
+      {
+        test: /\.sass$/,
+        use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
+      },
+      {
+        test: /\.less$/,
+        use: ['vue-style-loader', 'css-loader', 'less-loader']
+      },
+      {
+        test: /\.css$/,
+        use: ['vue-style-loader', 'css-loader']
+      },
+      {
+        test: /\.html$/,
+        use: 'vue-html-loader'
+      },
+      {
+        test: /\.js$/,
+        use: 'babel-loader',
+        exclude: /node_modules/
+      },
+      {
+        test: /\.node$/,
+        use: 'node-loader'
+      },
+      {
+        test: /\.vue$/,
+        use: {
+          loader: 'vue-loader',
+          options: {
+            extractCSS: process.env.NODE_ENV === 'production',
+            loaders: {
+              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
+              scss: 'vue-style-loader!css-loader!sass-loader',
+              less: 'vue-style-loader!css-loader!less-loader'
+            }
+          }
+        }
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        use: {
+          loader: 'url-loader',
+          query: {
+            limit: 10000,
+            name: 'imgs/[name]--[folder].[ext]'
+          }
+        }
+      },
+      {
+        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: 'media/[name]--[folder].[ext]'
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        use: {
+          loader: 'url-loader',
+          query: {
+            limit: 10000,
+            name: 'fonts/[name]--[folder].[ext]'
+          }
+        }
+      }
+    ]
+  },
+  node: {
+    __dirname: process.env.NODE_ENV !== 'production',
+    __filename: process.env.NODE_ENV !== 'production'
+  },
+  plugins: [
+    new VueLoaderPlugin(),
+    new MiniCssExtractPlugin({filename: 'styles.css'}),
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: path.resolve(__dirname, '../src/index.ejs'),
+      minify: {
+        collapseWhitespace: true,
+        removeAttributeQuotes: true,
+        removeComments: true
+      },
+      nodeModules: process.env.NODE_ENV !== 'production'
+        ? path.resolve(__dirname, '../node_modules')
+        : false
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NoEmitOnErrorsPlugin()
+  ],
+  output: {
+    filename: '[name].js',
+    libraryTarget: 'commonjs2',
+    path: path.join(__dirname, '../dist/electron')
+  },
+  resolve: {
+    alias: {
+      '@': path.join(__dirname, '../src/renderer'),
+      'vue$': 'vue/dist/vue.esm.js'
+    },
+    extensions: ['.js', '.vue', '.json', '.css', '.node']
+  },
+  target: 'electron-renderer'
+}
+
+/**
+ * Adjust rendererConfig for development settings
+ */
+if (process.env.NODE_ENV !== 'production') {
+  rendererConfig.plugins.push(
+    new webpack.DefinePlugin({
+      '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
+    })
+  )
+}
+
+/**
+ * Adjust rendererConfig for production settings
+ */
+if (process.env.NODE_ENV === 'production') {
+  rendererConfig.devtool = ''
+
+  rendererConfig.plugins.push(
+    new BabiliWebpackPlugin(),
+    new CopyWebpackPlugin([
+      {
+        from: path.join(__dirname, '../static'),
+        to: path.join(__dirname, '../dist/electron/static'),
+        ignore: ['.*']
+      }
+    ]),
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': '"production"'
+    }),
+    new webpack.LoaderOptionsPlugin({
+      minimize: true
+    })
+  )
+}
+
+module.exports = rendererConfig

+ 151 - 0
.electron-vue/webpack.web.config.js

@@ -0,0 +1,151 @@
+'use strict'
+
+process.env.BABEL_ENV = 'web'
+
+const path = require('path')
+const webpack = require('webpack')
+
+const BabiliWebpackPlugin = require('babili-webpack-plugin')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const MiniCssExtractPlugin = require('mini-css-extract-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const { VueLoaderPlugin } = require('vue-loader')
+
+let webConfig = {
+  devtool: '#cheap-module-eval-source-map',
+  entry: {
+    web: path.join(__dirname, '../src/renderer/main.js')
+  },
+  module: {
+    rules: [
+      {
+        test: /\.(js|vue)$/,
+        enforce: 'pre',
+        exclude: /node_modules/,
+        use: {
+          loader: 'eslint-loader',
+          options: {
+            formatter: require('eslint-friendly-formatter')
+          }
+        }
+      },
+      {
+        test: /\.scss$/,
+        use: ['vue-style-loader', 'css-loader', 'sass-loader']
+      },
+      {
+        test: /\.sass$/,
+        use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax']
+      },
+      {
+        test: /\.less$/,
+        use: ['vue-style-loader', 'css-loader', 'less-loader']
+      },
+      {
+        test: /\.css$/,
+        use: ['vue-style-loader', 'css-loader']
+      },
+      {
+        test: /\.html$/,
+        use: 'vue-html-loader'
+      },
+      {
+        test: /\.js$/,
+        use: 'babel-loader',
+        include: [ path.resolve(__dirname, '../src/renderer') ],
+        exclude: /node_modules/
+      },
+      {
+        test: /\.vue$/,
+        use: {
+          loader: 'vue-loader',
+          options: {
+            extractCSS: true,
+            loaders: {
+              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
+              scss: 'vue-style-loader!css-loader!sass-loader',
+              less: 'vue-style-loader!css-loader!less-loader'
+            }
+          }
+        }
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        use: {
+          loader: 'url-loader',
+          query: {
+            limit: 10000,
+            name: 'imgs/[name].[ext]'
+          }
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        use: {
+          loader: 'url-loader',
+          query: {
+            limit: 10000,
+            name: 'fonts/[name].[ext]'
+          }
+        }
+      }
+    ]
+  },
+  plugins: [
+    new VueLoaderPlugin(),
+    new MiniCssExtractPlugin({filename: 'styles.css'}),
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: path.resolve(__dirname, '../src/index.ejs'),
+      minify: {
+        collapseWhitespace: true,
+        removeAttributeQuotes: true,
+        removeComments: true
+      },
+      nodeModules: false
+    }),
+    new webpack.DefinePlugin({
+      'process.env.IS_WEB': 'true'
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NoEmitOnErrorsPlugin()
+  ],
+  output: {
+    filename: '[name].js',
+    path: path.join(__dirname, '../dist/web')
+  },
+  resolve: {
+    alias: {
+      '@': path.join(__dirname, '../src/renderer'),
+      'vue$': 'vue/dist/vue.esm.js'
+    },
+    extensions: ['.js', '.vue', '.json', '.css']
+  },
+  target: 'web'
+}
+
+/**
+ * Adjust webConfig for production settings
+ */
+if (process.env.NODE_ENV === 'production') {
+  webConfig.devtool = ''
+
+  webConfig.plugins.push(
+    new BabiliWebpackPlugin(),
+    new CopyWebpackPlugin([
+      {
+        from: path.join(__dirname, '../static'),
+        to: path.join(__dirname, '../dist/web/static'),
+        ignore: ['.*']
+      }
+    ]),
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': '"production"'
+    }),
+    new webpack.LoaderOptionsPlugin({
+      minimize: true
+    })
+  )
+}
+
+module.exports = webConfig

+ 0 - 0
.eslintignore


+ 26 - 0
.eslintrc.js

@@ -0,0 +1,26 @@
+module.exports = {
+  root: true,
+  parser: 'babel-eslint',
+  parserOptions: {
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true
+  },
+  extends: 'standard',
+  globals: {
+    __static: 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
+  }
+}

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+.DS_Store
+.idea/
+dist/
+build/*
+!build/icons
+!build/nsisfile
+coverage
+node_modules/
+npm-debug.log
+npm-debug.log.*
+thumbs.db
+!.gitkeep

+ 36 - 0
.travis.yml

@@ -0,0 +1,36 @@
+osx_image: xcode8.3
+sudo: required
+dist: trusty
+language: c
+matrix:
+  include:
+  - os: osx
+  - os: linux
+    env: CC=clang CXX=clang++ npm_config_clang=1
+    compiler: clang
+cache:
+  directories:
+  - node_modules
+  - "$HOME/.electron"
+  - "$HOME/.cache"
+addons:
+  apt:
+    packages:
+    - libgnome-keyring-dev
+    - icnsutils
+before_install:
+- mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([
+  "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz
+  | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull
+- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi
+install:
+- nvm install 7
+- curl -o- -L https://yarnpkg.com/install.sh | bash
+- source ~/.bashrc
+- npm install -g xvfb-maybe
+- yarn
+script:
+- yarn run build
+branches:
+  only:
+  - master

+ 27 - 0
README.md

@@ -0,0 +1,27 @@
+# startup
+
+> 启动器
+
+#### Build Setup
+
+#### 必须使用32位的node才能运行,且只适用于Window 7 以上版本
+
+[安装文档下载](http://d3.smartcost.com.cn/启动器开发环境搭建文档.docx)
+
+``` bash
+# 安装必要的包 --推荐使用yarn代替npm
+yarn
+
+# 具体安装务必参考文档来进行
+
+# 运行测试环境
+yarn run dev
+
+# 打包electron程序到build文件夹内
+yarn run build
+
+```
+
+---
+
+This project was generated with [electron-vue](https://github.com/SimulatedGREG/electron-vue) 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).

+ 29 - 0
appveyor.yml

@@ -0,0 +1,29 @@
+version: 0.1.{build}
+
+branches:
+  only:
+    - master
+
+image: Visual Studio 2017
+platform:
+  - x64
+
+cache:
+  - node_modules
+  - '%APPDATA%\npm-cache'
+  - '%USERPROFILE%\.electron'
+  - '%USERPROFILE%\AppData\Local\Yarn\cache'
+
+init:
+  - git config --global core.autocrlf input
+
+install:
+  - ps: Install-Product node 8 x64
+  - git reset --hard HEAD
+  - yarn
+  - node --version
+
+build_script:
+  - yarn build
+
+test: off

BIN
build/icons/256x256.png


BIN
build/icons/icon.icns


BIN
build/icons/icon.ico


BIN
build/icons/setup.ico


BIN
build/icons/smartcost.ico


BIN
build/icons/uninst.ico


+ 8 - 0
build/nsisfile/installer.nsh

@@ -0,0 +1,8 @@
+!macro preInit
+	SetRegView 64
+	WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES\${PRODUCT_FILENAME}"
+	WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES\${PRODUCT_FILENAME}"
+	SetRegView 32
+	WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES\${PRODUCT_FILENAME}"
+	WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES\${PRODUCT_FILENAME}"
+!macroend

+ 11 - 0
build/nsisfile/installer.nsi

@@ -0,0 +1,11 @@
+# ====================== 自定义宏 产品信息==============================
+!define PRODUCT_NAME           		"纵横Z+造价工作平台"
+!define PRODUCT_PATHNAME           	"纵横Z+造价工作平台"     #安装卸载项用到的KEY
+!define INSTALL_APPEND_PATH         "纵横Z+造价工作平台"     #安装路径追加的名称
+!define INSTALL_DEFALT_SETUPPATH    "纵横Z+造价工作平台"     #默认生成的安装路径
+!define EXE_NAME               		"纵横Z+造价工作平台.exe"
+!define PRODUCT_VERSION        		"0.1.10.0"
+!define PRODUCT_PUBLISHER      		"珠海纵横软件有限公司"
+!define PRODUCT_LEGAL          		"Copyright (C) 2018 珠海纵横软件有限公司"
+!define INSTALL_OUTPUT_NAME    		"纵横Z+造价工作平台_setup_0.1.10.exe"
+

BIN
data/fileInfo.dll


ファイルの差分が大きいため隠しています
+ 1 - 0
data/sc_software.json


BIN
data/zhlData.dll


+ 132 - 0
package.json

@@ -0,0 +1,132 @@
+{
+  "name": "startup",
+  "version": "0.1.19",
+  "author": "珠海纵横软件有限公司",
+  "description": "纵横Z+ 造价工作平台",
+  "license": null,
+  "main": "./dist/electron/main.js",
+  "scripts": {
+    "build": "node .electron-vue/build.js && electron-builder",
+    "build:dir": "node .electron-vue/build.js && electron-builder --dir",
+    "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js",
+    "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js",
+    "dev": "node .electron-vue/dev-runner.js",
+    "lint": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src",
+    "lint:fix": "eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src",
+    "pack": "npm run pack:main && npm run pack:renderer",
+    "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js",
+    "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js",
+    "postinstall": "npm run lint:fix",
+    "rebuild-ffi": "cd ./node_modules/ffi/ && node-gyp rebuild --target=2.0.4 --arch=ia32 --target_arch=ia32 --dist-url=https://atom.io/download/electron && cd ../ref && node-gyp rebuild --target=2.0.4 --arch=ia32 --target_arch=ia32 --dist-url=https://atom.io/download/electron "
+  },
+  "build": {
+    "productName": "纵横Z+造价工作平台",
+    "appId": "org.smartcost.startup",
+    "copyright": "Copyright © 2018 珠海纵横软件有限公司",
+    "directories": {
+      "output": "build"
+    },
+    "asar": false,
+    "files": [
+      "dist/electron/**/*"
+    ],
+    "publish": [
+      {
+        "provider": "generic",
+        "url": "http://d2.smartcost.com.cn/startup/"
+      }
+    ],
+    "win": {
+      "icon": "build/icons/smartcost.ico",
+      "target": [
+        "nsis"
+      ],
+      "artifactName": "${productName}_setup_${version}.${ext}"
+    },
+    "nsis": {
+      "oneClick": false,
+      "perMachine": true,
+      "include": "build/nsisfile/installer.nsh",
+      "allowToChangeInstallationDirectory": true,
+      "createDesktopShortcut": true,
+      "deleteAppDataOnUninstall": true,
+      "installerIcon": "build/icons/setup.ico",
+      "uninstallerIcon": "build/icons/uninst.ico",
+      "guid": "startup"
+    },
+    "extraResources": [
+      {
+        "from": "data",
+        "to": "../data"
+      }
+    ]
+  },
+  "dependencies": {
+    "vue": "^2.5.16",
+    "axios": "^0.18.0",
+    "vue-electron": "^1.0.6",
+    "vue-router": "^3.0.1",
+    "vuex": "^3.0.1",
+    "fs-extra": "^6.0.1",
+    "compressing": "^1.2.4",
+    "electron-dl": "^1.12.0",
+    "electron-updater": "^4.0.0",
+    "element-ui": "^2.4.1",
+    "ffi": "^2.2.0",
+    "glob": "^7.1.2",
+    "lodash-id": "^0.14.0",
+    "lowdb": "^1.0.0",
+    "regedit": "^3.0.0",
+    "usb": "^1.3.2"
+  },
+  "devDependencies": {
+    "ajv": "^6.5.0",
+    "babel-core": "^6.26.3",
+    "babel-loader": "^7.1.4",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "babel-preset-env": "^1.7.0",
+    "babel-preset-stage-0": "^6.24.1",
+    "babel-register": "^6.26.0",
+    "babili-webpack-plugin": "^0.1.2",
+    "cfonts": "^2.1.2",
+    "chalk": "^2.4.1",
+    "copy-webpack-plugin": "^4.5.1",
+    "cross-env": "^5.1.6",
+    "css-loader": "^0.28.11",
+    "del": "^3.0.0",
+    "devtron": "^1.4.0",
+    "electron": "^2.0.4",
+    "electron-debug": "^1.5.0",
+    "electron-devtools-installer": "^2.2.4",
+    "electron-builder": "^20.19.2",
+    "electron-rebuild": "^1.7.3",
+    "babel-eslint": "^8.2.3",
+    "eslint": "^4.19.1",
+    "eslint-friendly-formatter": "^4.0.1",
+    "eslint-loader": "^2.0.0",
+    "eslint-plugin-html": "^4.0.3",
+    "eslint-config-standard": "^11.0.0",
+    "eslint-plugin-import": "^2.12.0",
+    "eslint-plugin-node": "^6.0.1",
+    "eslint-plugin-promise": "^3.8.0",
+    "eslint-plugin-standard": "^3.1.0",
+    "mini-css-extract-plugin": "0.4.0",
+    "file-loader": "^1.1.11",
+    "html-webpack-plugin": "^3.2.0",
+    "multispinner": "^0.2.1",
+    "node-loader": "^0.6.0",
+    "node-sass": "^4.9.2",
+    "sass-loader": "^7.0.3",
+    "style-loader": "^0.21.0",
+    "url-loader": "^1.0.1",
+    "vue-html-loader": "^1.2.4",
+    "vue-loader": "^15.2.4",
+    "vue-style-loader": "^4.1.0",
+    "vue-template-compiler": "^2.5.16",
+    "webpack-cli": "^3.0.8",
+    "webpack": "^4.15.1",
+    "webpack-dev-server": "^3.1.4",
+    "webpack-hot-middleware": "^2.22.2",
+    "webpack-merge": "^4.1.3"
+  }
+}

ファイルの差分が大きいため隠しています
+ 57 - 0
src/database/index.js


+ 24 - 0
src/index.ejs

@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>纵横Z+ 造价工作平台</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, '\\\\')
+      </script>
+    <% } %>
+
+    <!-- webpack builds are automatically injected -->
+  </body>
+</html>

+ 24 - 0
src/main/index.dev.js

@@ -0,0 +1,24 @@
+/**
+ * This file is used specifically and only for development. It installs
+ * `electron-debug` & `vue-devtools`. There shouldn't be any need to
+ *  modify this file, but it can be used to extend your development
+ *  environment.
+ */
+
+/* eslint-disable */
+
+// Install `electron-debug` with `devtron`
+require('electron-debug')({ showDevTools: true })
+
+// Install `vue-devtools`
+require('electron').app.on('ready', () => {
+  let installExtension = require('electron-devtools-installer')
+  installExtension.default(installExtension.VUEJS_DEVTOOLS)
+    .then(() => {})
+    .catch(err => {
+      console.log('Unable to install `vue-devtools`: \n', err)
+    })
+})
+
+// Require `main` process to boot app
+require('./index')

+ 159 - 0
src/main/index.js

@@ -0,0 +1,159 @@
+'use strict'
+
+import pkg from '../../package.json'
+import usbffi from './main-process/usb-ffi'
+import downloads from './main-process/downloads'
+import updateInstall from './main-process/updateInstall'
+import fileselect from './main-process/file-select'
+import exeinstall from './main-process/exe-install'
+import db from '../database'
+const path = require('path')
+// const glob = require('glob')
+const electron = require('electron')
+const app = electron.app
+const BrowserWindow = electron.BrowserWindow
+const Menu = electron.Menu
+const autoUpdater = require('./software-update')
+
+/**
+ * Set `__static` path to static files in production
+ * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html
+ */
+if (process.env.NODE_ENV !== 'development') {
+  global.__static = path.join(__dirname, '/static').replace(/\\/g, '\\\\')
+}
+
+let mainWindow
+let winURL = process.env.NODE_ENV === 'development'
+  ? `http://localhost:9080`
+  : `file://${__dirname}/index.html`
+
+let firsturl = db.read().get('sc_hadInstall.first').value()
+winURL += firsturl ? '#/firstopen' : '#' + db.read().get('sc_hadInstall.url').value()
+
+function initialize () {
+  function createWindow () {
+    Menu.setApplicationMenu(null)
+    /**
+     * Initial window options
+     */
+    const windowOptions = {
+      // width: 1000,
+      width: 1550,
+      height: 604,
+      minWidth: 1000,
+      minHeight: 604,
+      // show: false,
+      frame: false,
+      fullscreenable: false,
+      center: true,
+      transparent: true,
+      titleBarStyle: 'hidden',
+      backgroundColor: '#fff',
+      webPreferences: {
+        // devTools: false,
+        backgroundThrottling: false,
+        webSecurity: false
+      }
+    }
+    mainWindow = new BrowserWindow(windowOptions)
+    mainWindow.loadURL(winURL)
+    // 打包测试弹出devtools
+    // mainWindow.webContents.openDevTools()
+    mainWindow.once('ready-to-show', () => {
+      mainWindow.show()
+    })
+    mainWindow.on('closed', () => {
+      mainWindow = null
+    })
+  }
+
+  let shouldQuit = app.makeSingleInstance(function (commandLine, workingDirectory) {
+    // 当另一个实例运行的时候,这里将会被调用,我们需要激活应用的窗口
+    if (mainWindow) {
+      if (mainWindow.isMinimized()) mainWindow.restore()
+      mainWindow.focus()
+    }
+    return true
+  })
+
+  // 这个实例是多余的实例,需要退出
+  if (shouldQuit) {
+    app.quit()
+    return
+  }
+
+  app.on('ready', function () {
+    createWindow()
+    loadJS(mainWindow)
+    autoUpdater.initialize(mainWindow)
+  })
+
+  app.on('window-all-closed', () => {
+    if (process.platform !== 'darwin') {
+      app.quit()
+    }
+  })
+
+  app.on('activate', () => {
+    if (mainWindow === null) {
+      createWindow()
+    }
+  })
+
+  function loadJS (win) {
+    // 无法使用require调用含异步的方法,只能采用import方式,原因不明,坑!
+    // require('./main-process/usb-ffi').initialize(win)
+    usbffi(win)
+    downloads(win)
+    updateInstall(win)
+    fileselect(win)
+    exeinstall(win)
+    // require('./main-process/downloads').initialize(win)
+    // let files = glob.sync(path.join(__dirname, './main-process/*.js'))
+    // if (files !== []) {
+    //   files.forEach(function (file) {
+    //     require(file)
+    //   })
+    // }
+    // require('./main-process/download')
+  }
+}
+
+initialize()
+
+if (process.platform === 'win32') {
+  app.setAppUserModelId(pkg.build.appId)
+}
+
+const ipc = electron.ipcMain
+let newwin
+ipc.on('openWindow', (event, data) => {
+  console.log(data)
+  newwin = new BrowserWindow({
+    webPreferences: {
+      devTools: false,
+      sandbox: true
+    },
+    minWidth: 1000,
+    minHeight: 604,
+    show: false
+  })
+
+  newwin.loadURL(data)
+  newwin.on('ready-to-show', function () {
+    newwin.maximize()
+    newwin.show()
+    newwin.focus()
+  })
+  newwin.on('closed', () => {
+    newwin = null
+  })
+})
+
+// const globalShortcut = electron.globalShortcut
+// // 令tab键失效
+// app.on('ready', () => {
+//   globalShortcut.register('Tab', () => {
+//   })
+// })

+ 185 - 0
src/main/main-process/downloads.js

@@ -0,0 +1,185 @@
+'use strict'
+
+/**
+ *
+ *
+ * @author EllisRan.
+ * @date 2018/6/22
+ * @version
+ */
+import db from '../../database'
+const path = require('path')
+const electron = require('electron')
+const ipcMain = electron.ipcMain
+const folderpath = 'data/software'
+const download = require('electron-dl').download
+// const BrowserWindow = electron.BrowserWindow
+const app = electron.app
+const fs = require('fs')
+const fse = require('fs-extra')
+const compressing = require('compressing')
+const mkdirp = require('mkdirp')
+const __appPath = app.getPath('userData')
+
+const downloads = function (win) {
+  let item
+  ipcMain.on('cancel', (eve, id) => {
+    // cancelStatus = true
+    item.cancel()
+  })
+  ipcMain.on('pause', (eve, id) => {
+    // pauseStatus = true
+    item.pause()
+  })
+  ipcMain.on('resume', (eve, id) => {
+    // resumeStatus = true
+    // item = db.get('sc_download.item').value()
+    // console.log(db.read().get('sc_download.item').value())
+    if (!item) {
+      // item = db.read().get('sc_download.item').value()
+      console.log(item.canResume())
+      console.log(item)
+    }
+    item.resume()
+  })
+
+  ipcMain.on('download', (event, args) => {
+    let info = db.read().get('sc_download').getById(args.id).value()
+    let downloadpath = info.down_url
+    let options = {
+      saveAs: false,
+      directory: path.join(__appPath, folderpath),
+      filename: info.savepath,
+      errorTitle: '下载失败',
+      onStarted: function (items) {
+        item = items // 必须抽离出来控制item,否则二次使用时会报错
+      },
+      onProgress: function (status) {
+        win.webContents.send('downloadtips', {percent: Math.ceil(status * 100), id: args.id, index: args.index})
+      },
+      onCancel: function () {
+        console.log('cancel download')
+        try {
+          // info.start = true
+          // info.pause = false
+          // info.resume = false
+          // db.read().get('sc_download').getById(args.id).assign(info).write()
+          let delpath = path.join(__appPath, folderpath, info.savepath)
+          if (fs.existsSync(delpath)) {
+            fse.removeSync(delpath)
+          }
+        } catch (err) {
+          console.log(err)
+        }
+      }
+    }
+    download(win, downloadpath, options)
+      .then(function (dl) {
+        try {
+          // 判断下载的是安装包 or 压缩包
+          let zippath = path.join(__appPath, folderpath)
+          let zipname = info.savepath
+          let suffix = zipname.substring(zipname.lastIndexOf('.') + 1, zipname.length)
+          if (suffix === 'exe') {
+            console.log('ok')
+            win.webContents.send('downloadsuccess', {id: args.id, savepath: path.join(zippath, zipname), index: args.index})
+          } else {
+            let exename = ''
+            fs.createReadStream(path.join(zippath, zipname))
+              .on('error', function (error) {
+                throw error
+              })
+              .pipe(new compressing.zip.UncompressStream())
+              .on('error', function (error) {
+                throw error
+              })
+              .on('finish', function () {
+                // 完成后删除zip压缩包
+                fs.unlinkSync(path.join(zippath, zipname))
+                console.log('ok')
+                win.webContents.send('downloadsuccess', {id: args.id, savepath: path.join(zippath, exename), index: args.index})
+              })
+              .on('entry', function (header, stream, next) {
+                stream.on('end', next)
+                // header.type => file | directory
+                // header.name => path name
+                exename = header.name
+                if (header.type === 'file') {
+                  stream.pipe(fs.createWriteStream(path.join(zippath, header.name)))
+                } else { // directory
+                  mkdirp(path.join(zippath, header.name), err => {
+                    if (err) throw err
+                    stream.resume()
+                  })
+                }
+              })
+          }
+        } catch (err) {
+          console.log(err)
+        }
+      })
+      .catch(console.error)
+  })
+}
+
+export default downloads
+
+// exports.initialize = function (win) {
+//   ipcMain.on('download', (event, args) => {
+//     let downloadpath = args
+//     win.webContents.downloadURL(downloadpath)
+//     let $event = event
+//     win.webContents.session.on('will-download', (event, item, webContents) => {
+//       const totalBytes = item.getTotalBytes()
+//       const filename = item.getFilename()
+//       item.setSavePath(path.join(folderpath, `${item.getFilename()}`))
+//       item.on('updated', (event, state) => {
+//         if (state === 'interrupted') {
+//           $event.sender.send('downloadtips', 'Download is interrupted but can be resumed')
+//         } else if (state === 'progressing') {
+//           if (item.isPaused()) {
+//             $event.sender.send('downloadtips', filename + ' 下载暂停')
+//           } else {
+//             console.log(item)
+//             $event.sender.send('downloadtips', Math.ceil(item.getReceivedBytes() / totalBytes * 100))
+//           }
+//         }
+//       })
+//       item.once('done', (event, state) => {
+//         if (state === 'completed') {
+//           $event.sender.send('downloadtips', filename + ' 下载完成 共:' + conver(totalBytes))
+//           $event.sender.send('notification-test', filename + ' 下载完成')
+//         } else {
+//           $event.sender.send('downloadtips', filename + ` 下载失败: ${state}`)
+//           $event.sender.send('notification-test', filename + ` 下载失败: ${state}`)
+//         }
+//       })
+//     })
+//   })
+// }
+//
+// /**
+//  *
+//  * @param 计算内存大小
+//  * @return {string}
+//  */
+// function conver (limit) {
+//   var size = ''
+//   if (limit < 0.1 * 1024) { // 如果小于0.1KB转化成B
+//     size = limit.toFixed(2) + 'B'
+//   } else if (limit < 0.1 * 1024 * 1024) { // 如果小于0.1MB转化成KB
+//     size = (limit / 1024).toFixed(2) + 'KB'
+//   } else if (limit < 0.1 * 1024 * 1024 * 1024) { // 如果小于0.1GB转化成MB
+//     size = (limit / (1024 * 1024)).toFixed(2) + 'MB'
+//   } else { // 其他转化成GB
+//     size = (limit / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
+//   }
+//
+//   var sizestr = size + ''
+//   var len = sizestr.indexOf('.')
+//   var dec = sizestr.substr(len + 1, 2)
+//   if (dec === '00') { // 当小数点后为00时 去掉小数部分
+//     return sizestr.substring(0, len) + sizestr.substr(len + 3, 2)
+//   }
+//   return sizestr
+// }

+ 159 - 0
src/main/main-process/exe-install.js

@@ -0,0 +1,159 @@
+'use strict'
+
+/**
+ * 软件安装检测
+ *
+ * @author EllisRan.
+ * @date 2018/8/9
+ * @version
+ */
+import db from '../../database'
+const path = require('path')
+const ffi = require('ffi')
+const fs = require('fs')
+const electron = require('electron')
+const ipcMain = electron.ipcMain
+const regeditPath64 = 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
+const regeditPath32 = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
+const regeditPath = isOSWin64() ? regeditPath64 : regeditPath32
+const regedit = require('regedit')
+
+const exeinstall = function (win) {
+  ipcMain.on('checkExeInstall', (event, args) => {
+    let info = args.info
+    try {
+      // 先检查数据库中是否已存在该注册表软件的信息,有则退出,无则继续
+      let exesqlinfo = db.read().get('sc_exeData').find({ regeditName: info.regedit, product_version: info.version }).value()
+      if (exesqlinfo !== undefined) {
+        event.sender.send('InstallResult', { status: 400, msg: `启动器已存在相同版本的 ${info.fulltitle} 软件`, id: info.id, index: args.index })
+      } else {
+        let regeditpath = path.join(regeditPath, info.regedit)
+        regedit.list(regeditpath, async function (err, result) {
+          if (err === null && result !== undefined) {
+            let values = result[regeditpath].values
+            let regeditMsg = {
+              path: values['Inno Setup: App Path'].value,
+              name: values['DisplayName'].value,
+              regeditName: info.regedit,
+              fileName: values['Inno Setup: Icon Group'].value,
+              simpleName: info.product_title,
+              versionName: info.title,
+              keyNumber: info.key_number,
+              keytype: info.keytype,
+              isshow: true,
+              auto: true,
+              pid: info.pid,
+              product_id: info.product_id,
+              down_id: info.down_id,
+              show_updateVersion: info.version
+            }
+            let data = await checkDirectory(regeditMsg.path)
+            for (let i = 0; i < data.length; i++) {
+              let exeinfo = regeditMsg
+              exeinfo.exeName = data[i].exeName
+              exeinfo.fileVersion = data[i].FileVersion
+              exeinfo.productName = data[i].ProductName
+              exeinfo.productVersion = data[i].ProductVersion
+              exeinfo.fileDescription = data[i].FileDescription
+              exeinfo.addtime = Date.parse(new Date()) / 1000
+              exeinfo.product_version = data[i].FileVersion
+              exeinfo.show_tip = data[i].FileVersion !== info.version && data[i].exeName !== 'BillsEditor.exe'
+              exeinfo.regStart = info.reg_start !== '' && info.reg_start[data[i].FileVersion] !== undefined ? info.reg_start[data[i].FileVersion] : ''
+              exeinfo.id = GetFileNameNoExt(exeinfo.exeName) + '-' + RndNum(10)
+              db.read().get('sc_exeData').insert(exeinfo).write()
+            }
+            // 查找是否存在旧版本的软件,升级后判断旧版本(相同文件夹升级则删除,不同文件夹则保留)
+            let exedatalist = await db.read().get('sc_exeData').filter({ product_id: info.product_id, down_id: info.down_id }).value()
+            for (let j in exedatalist) {
+              if (exedatalist[j].product_version !== info.version && exedatalist[j].path === regeditMsg.path) {
+                await db.read().get('sc_exeData').updateById(exedatalist[j].id, { isshow: false }).write()
+              } else {
+                // 把旧版本的更新提示设为false
+                await db.read().get('sc_exeData').updateById(exedatalist[j].id, { show_tip: false }).write()
+              }
+            }
+            await db.read().get('sc_exeData').removeWhere({ isshow: false }).write()
+
+            await db.read().get('sc_productData').updateById(info.pid, { isshow: true }).write()
+            await db.read().set('sc_hadInstall.first', false).write()
+            await db.read().set('sc_hadInstall.url', '/softwarestartup/' + info.pid).write()
+            event.sender.send('InstallResult', { status: 200, msg: data.length, id: info.id, index: args.index })
+            win.webContents.send('successUpdate', { id: info.pid, num: data.length, delnum: 0 })
+          } else {
+            event.sender.send('InstallResult', { status: 400, msg: `未能成功安装 ${info.fulltitle} 软件`, id: info.id, index: args.index })
+          }
+        })
+      }
+    } catch (err) {
+      event.sender.send('InstallResult', { status: 400, msg: err, id: info.id, index: args.index })
+    }
+  })
+
+  // 取文件名不带后缀
+  function GetFileNameNoExt (filepath) {
+    if (filepath !== '') {
+      let names = filepath.split('\\')
+      let pos = names[names.length - 1].lastIndexOf('.')
+      return names[names.length - 1].substring(0, pos)
+    }
+  }
+
+  // 产生随机数函数
+  function RndNum (n) {
+    let rnd = ''
+    for (let i = 0; i < n; i++) {
+      rnd += Math.floor(Math.random() * 10)
+    }
+    return rnd
+  }
+
+  /**
+   * 判断文件名是否包含纵横软件exe的部分名称
+   * @param item
+   * @return {boolean}
+   */
+  function existSoftwareName (item) {
+    // 纵横软件exe所带包含的文件名
+    const smartcostSoftwareHeader = ['SmartCost', 'Measure', 'DrawingBuilder', 'BillsEditor']
+    let flag = false
+    for (let i = 0; i < smartcostSoftwareHeader.length; i++) {
+      if (item.indexOf(smartcostSoftwareHeader[i]) !== -1) {
+        flag = true
+        break
+      }
+    }
+    return flag
+  }
+
+  /**
+   * 检查文件夹并判断是否包含纵横软件(一个文件可能存在多个纵横软件的)
+   * @param files
+   * @return {Promise.<Array>}
+   */
+  async function checkDirectory (files) {
+    let data = []
+    let dirname = await fs.readdirSync(files)
+    dirname.forEach(async function (item, index) {
+      if (path.extname(item) === '.exe' && existSoftwareName(item)) {
+        let pathstr = path.join('data/fileInfo.dll')
+        let libm = ffi.Library(pathstr, {
+          'GetFileInfo': ['string', ['string']]
+        })
+        let fileinfo = JSON.parse(await libm.GetFileInfo(path.join(files, item)))
+        fileinfo.exeName = item
+        data.push(fileinfo)
+      }
+    })
+    return data
+  }
+}
+
+/**
+ * 判断系统位数
+ * @return {boolean}
+ */
+function isOSWin64 () {
+  return process.arch === 'x64' || process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')
+}
+
+export default exeinstall

+ 275 - 0
src/main/main-process/file-select.js

@@ -0,0 +1,275 @@
+'use strict'
+
+/**
+ * 手动添加纵横软件文件夹
+ *
+ * @author EllisRan.
+ * @date 2018/6/20
+ * @version
+ */
+import db from '../../database'
+const path = require('path')
+const ffi = require('ffi')
+const fs = require('fs')
+const fse = require('fs-extra')
+const electron = require('electron')
+const ipcMain = electron.ipcMain
+const dialog = electron.dialog
+const regeditPath64 = 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
+const regeditPath32 = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
+const regeditPath = isOSWin64() ? regeditPath64 : regeditPath32
+const regedit = require('regedit')
+
+const fileselect = function (win) {
+  ipcMain.on('file-select', function (event) {
+    dialog.showOpenDialog({
+      properties: ['openFile', 'openDirectory']
+    }, async function (files) {
+      if (files) {
+        let data = await checkDirectory(files[0])
+        if (data.length === 0) {
+          event.sender.send('failedDirectory', { msg: '该文件夹不存在纵横软件' })
+        } else {
+          let addnum = await checkExeName(files[0], data)
+          if (addnum !== 0) {
+            let info = await db.read().get('sc_exeData').last().value()
+            await db.read().get('sc_productData').updateById(info.pid, { isshow: true }).write()
+            await db.read().set('sc_hadInstall.url', '/softwarestartup/' + info.pid).write()
+            event.sender.send('successUpdate', { id: info.pid, num: addnum, delnum: 0 })
+          } else {
+            event.sender.send('failedDirectory', { msg: '该文件夹中的纵横软件已存在启动器中' })
+          }
+        }
+      } else {
+        event.sender.send('failedDirectory', { msg: '' })
+      }
+    })
+  })
+}
+
+/**
+ * 检查文件夹并判断是否包含纵横软件(一个文件可能存在2个纵横软件的)
+ * @param files
+ * @return {Promise.<Array>}
+ */
+async function checkDirectory (files) {
+  let data = []
+  let dirname = await fs.readdirSync(files)
+  dirname.forEach(async function (item, index) {
+    if (path.extname(item) === '.exe' && existSoftwareName(item)) {
+      let pathstr = path.join('data/fileInfo.dll')
+      let libm = ffi.Library(pathstr, {
+        'GetFileInfo': ['string', ['string']]
+      })
+      let fileinfo = JSON.parse(await libm.GetFileInfo(path.join(files, item)))
+      fileinfo.exeName = item
+      data.push(fileinfo)
+    }
+  })
+  return data
+}
+
+/**
+ * 判断数据库是否已存在该文件信息,不存在则添加到数据库中
+ * @param Directory
+ * @param files
+ * @return {Promise.<number>}
+ */
+async function checkExeName (Directory, files) {
+  let index = Directory.lastIndexOf('\\')
+  let filename = Directory.substring(index + 1, Directory.length)
+  let addnum = 0
+  let addflag = false
+  for (let i = 0; i < files.length; i++) {
+    let exeinfo = await db.read().get('sc_exeData').find({ path: Directory, exeName: files[i].exeName, fileVersion: files[i].FileVersion }).value()
+    if (!exeinfo) {
+      addflag = true
+      break
+    }
+  }
+  if (addflag) {
+    // 再和注册表内容对应,有则添加,无则为未分类
+    let regeditMsg = await checkRegeditbyDirectory(Directory)
+    await sleep(5000)
+    for (let i = 0; i < files.length; i++) {
+      let exeinfo = await db.read().get('sc_exeData').find({ path: Directory, exeName: files[i].exeName, fileVersion: files[i].FileVersion }).value()
+      if (!exeinfo) {
+        let exeData = {
+          path: Directory,
+          name: regeditMsg.name !== '' ? regeditMsg.name : filename,
+          regeditName: regeditMsg.regeditName,
+          fileName: regeditMsg.fileName !== '' ? regeditMsg.fileName : filename,
+          simpleName: regeditMsg.simpleName,
+          versionName: regeditMsg.versionName,
+          exeName: files[i].exeName,
+          fileVersion: files[i].FileVersion,
+          productName: files[i].ProductName,
+          productVersion: files[i].ProductVersion,
+          fileDescription: files[i].FileDescription,
+          pid: regeditMsg.pid,
+          keyNumber: regeditMsg.keyNumber,
+          keytype: regeditMsg.keytype,
+          product_id: regeditMsg.product_id,
+          down_id: regeditMsg.down_id,
+          product_version: files[i].FileVersion,
+          show_tip: regeditMsg.show_updateVersion !== '' && files[i].FileVersion !== regeditMsg.show_updateVersion && files[i].exeName !== 'BillsEditor.exe',
+          show_updateVersion: regeditMsg.show_updateVersion,
+          regStart: regeditMsg.regStart !== '' && regeditMsg.regStart[files[i].FileVersion] !== undefined ? regeditMsg.regStart[files[i].FileVersion] : '',
+          addtime: Date.parse(new Date()) / 1000,
+          isshow: true,
+          auto: regeditMsg.auto,
+          id: GetFileNameNoExt(files[i].exeName) + '-' + RndNum(10)
+        }
+        await db.read().get('sc_exeData').insert(exeData).write()
+        ++addnum
+      }
+    }
+  }
+  return addnum
+}
+
+const sleep = (timeout = 2000) => new Promise(resolve => {
+  setTimeout(resolve, timeout)
+})
+
+// 取文件名不带后缀
+function GetFileNameNoExt (filepath) {
+  if (filepath !== '') {
+    let names = filepath.split('\\')
+    let pos = names[names.length - 1].lastIndexOf('.')
+    return names[names.length - 1].substring(0, pos)
+  }
+}
+
+// 产生随机数函数
+function RndNum (n) {
+  let rnd = ''
+  for (let i = 0; i < n; i++) {
+    rnd += Math.floor(Math.random() * 10)
+  }
+  return rnd
+}
+
+/**
+ * 判断文件名是否包含纵横软件exe的部分名称
+ * @param item
+ * @return {boolean}
+ */
+function existSoftwareName (item) {
+  // 纵横软件exe所带包含的文件名
+  const smartcostSoftwareHeader = ['SmartCost', 'Measure', 'DrawingBuilder', 'BillsEditor']
+  let flag = false
+  for (let i = 0; i < smartcostSoftwareHeader.length; i++) {
+    if (item.indexOf(smartcostSoftwareHeader[i]) !== -1) {
+      flag = true
+      break
+    }
+  }
+  return flag
+}
+
+function inArray (val, arr) {
+  for (let i in arr) {
+    if (arr[i] === val) {
+      return true
+    }
+  }
+  return false
+}
+
+/**
+ * 纵横注册表信息中是否存在该文件夹,存在则返回该注册表信息,并加入到产品数据库中
+ * @param directory
+ * @return {{name: string, regeditName: string, simpleName: string, versionName: string, pid: string, keyNumber: string, keytype: string}}
+ */
+async function checkRegeditbyDirectory (directory) {
+  let regeditMsg = {
+    name: '',
+    fileName: '',
+    regeditName: '',
+    simpleName: '',
+    versionName: '',
+    pid: '1',
+    keyNumber: '',
+    keytype: '',
+    product_id: '',
+    down_id: '',
+    show_updateVersion: '',
+    auto: false,
+    regStart: ''
+  }
+  regedit.list(regeditPath).on('data', async function (result) {
+    try {
+      let softwarelist = result.data.keys
+      for (let i in softwarelist) {
+        if (softwarelist[i].indexOf('SmartCost_') !== -1) {
+          regedit.list(regeditPath + '\\' + softwarelist[i]).on('data', async function (result2) {
+            let values = result2.data.values
+            let directoryPath = values['Inno Setup: App Path'].value
+            if (directoryPath === directory) {
+              regeditMsg.name = values['DisplayName'].value
+              regeditMsg.fileName = values['Inno Setup: Icon Group'].value
+              regeditMsg.regeditName = softwarelist[i]
+              let softwarejson = path.join('data/sc_software.json')
+              let downlist = fse.readJsonSync(softwarejson).sc_down
+              let downinfo = downlist.find(function (item) {
+                return inArray(regeditMsg.regeditName, item.regedits)
+              })
+              // let ptitle = downinfo !== undefined ? downinfo.product_title : ''
+              // 查找数据库是否存在该产品,有则添加,无则根据simplename 新增产品,simplename为空的话,则为未分类
+              // 新方法,先关联注册表名查找数据,然后再关联产品数据库生成
+              if (downinfo !== undefined) {
+                regeditMsg.simpleName = downinfo.product_title
+                regeditMsg.versionName = downinfo.title
+                regeditMsg.auto = true
+                let productInfo = db.read().get('sc_productData').find({ product_id: downinfo.product_id }).value()
+                if (productInfo !== undefined) {
+                  regeditMsg.pid = productInfo.id
+                  db.read().get('sc_productData').updateById(productInfo.id, { isshow: true }).write()
+                } else {
+                  // 查找sc_software.json文件,有则新增产品,无则为未分类(旧的也会归这类)
+                  let scproductlist = fse.readJsonSync(softwarejson).sc_product
+                  let scproductinfo = scproductlist.find(function (item) {
+                    return item.product_id === downinfo.product_id
+                  })
+                  if (scproductinfo !== undefined) {
+                    let addproduct = scproductinfo
+                    addproduct.addtime = Date.parse(new Date()) / 1000
+                    addproduct.isshow = false
+                    let info = db.read().get('sc_productData').insert(addproduct).write()
+                    regeditMsg.pid = info.id
+                  }
+                }
+                // 锁号和锁类型添加
+                // let downlist = fse.readJsonSync(softwarejson).sc_down
+                // let downinfo = downlist.find(function (item) {
+                //   return item.product_title === regeditMsg.simpleName && item.title === regeditMsg.versionName
+                // })
+                regeditMsg.keyNumber = downinfo.key_number
+                regeditMsg.keytype = downinfo.keytype
+                regeditMsg.product_id = downinfo.product_id
+                regeditMsg.down_id = downinfo.down_id
+                regeditMsg.show_updateVersion = downinfo.version
+                regeditMsg.regStart = downinfo.reg_start !== '' ? downinfo.reg_start : ''
+              }
+              return regeditMsg
+            }
+          })
+        }
+      }
+    } catch (err) {
+      console.log(err)
+    }
+  })
+  return regeditMsg
+}
+
+/**
+ * 判断系统位数
+ * @return {boolean}
+ */
+function isOSWin64 () {
+  return process.arch === 'x64' || process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')
+}
+
+export default fileselect

+ 314 - 0
src/main/main-process/updateInstall.js

@@ -0,0 +1,314 @@
+'use strict'
+
+/**
+ * 读取注册表获取纵横软件
+ *
+ * @author EllisRan.
+ * @date 2018/6/20
+ * @version
+ */
+import db from '../../database'
+const fs = require('fs')
+const fse = require('fs-extra')
+const path = require('path')
+const ffi = require('ffi')
+const electron = require('electron')
+const ipcMain = electron.ipcMain
+const regeditPath64 = 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
+const regeditPath32 = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
+const regeditPath = isOSWin64() ? regeditPath64 : regeditPath32
+const regedit = require('regedit')
+let globalNUM = 0
+let globalSoftware = []
+let globalExe = []
+
+const updateInstall = function (win) {
+  ipcMain.on('updateInstall', function () {
+    regedit.list(regeditPath).on('data', async function (result) {
+      try {
+        // let flag = false
+        // 每次自动获取更新软件内容先让数据库isshow都为false,然后再执行getExeDataList方法里把isshow包含的变true,
+        // 最后再检测把还是false的软件进行判断文件夹和软件是否还在,有则保留,否则删除,完成已删除软件功能(手动添加的exe信息除外)
+        await productAndExeDataIsshow()
+        let softwarelist = result.data.keys
+        let promiseArr = []
+        let promiseArr2 = []
+        for (let i in softwarelist) {
+          if (softwarelist[i].indexOf('SmartCost_') !== -1) {
+            promiseArr2.push(getExeDataList(softwarelist[i]))
+          }
+        }
+        await sleep(2000)
+        await Promise.all(promiseArr2)
+          .then(async function (result) {
+            await sleep(5000)
+            if (globalSoftware !== []) {
+              console.log(globalSoftware)
+              for (let i in globalSoftware) {
+                (async function () {
+                  globalSoftware[i].exeName = globalExe[i]
+                  await promiseArr.push(insertData(globalSoftware[i]))
+                })(i)
+              }
+            }
+            let one = globalSoftware.length
+            console.log(one)
+            await sleep(2000)
+            let two = globalSoftware.length
+            console.log(two)
+            if (one !== two) {
+              console.log(globalSoftware)
+            }
+            await Promise.all(promiseArr)
+              .then(async function (result) {
+                // console.log(globalSoftware)
+                let delnum = await delProductAndExeDataAsync()
+                await db.read().set('sc_hadInstall.first', false).write()
+                let exeInfo = await db.read().get('sc_exeData').last().value()
+                let gourl = exeInfo === '{}' || exeInfo.pid === undefined ? 1 : exeInfo.pid
+                await db.read().set('sc_hadInstall.url', '/softwarestartup/' + gourl).write()
+                if (promiseArr !== undefined && promiseArr.length !== 0) {
+                  win.webContents.send('successUpdate', {id: gourl, num: globalNUM, delnum: delnum})
+                  // console.log(globalSoftware)
+                } else {
+                  win.webContents.send('failedUpdate', {id: '1', error: 200})
+                }
+                globalNUM = 0
+                globalSoftware = []
+                globalExe = []
+              })
+              .catch(function (err) {
+                win.webContents.send('failedUpdate', {id: '1', error: err})
+                console.log(err)
+                globalNUM = 0
+                globalSoftware = []
+                globalExe = []
+              })
+          })
+          .catch(function (err) {
+            console.log(err)
+          })
+      } catch (error) {
+        console.log(error)
+      }
+    })
+  })
+
+  /**
+   *  make all data isshow: true=>false
+   */
+  async function productAndExeDataIsshow () {
+    await db.read().get('sc_productData').updateWhere({ isshow: true }, { isshow: false }).write()
+    await db.read().get('sc_exeData').updateWhere({ auto: true, isshow: true }, { isshow: false }).write()
+  }
+
+  /**
+   *  delete data by isshow=false
+   *   @return {int}
+   */
+  async function delProductAndExeDataAsync () {
+    // 这里必须静止1s等Promise.all里的getExeDataList方法所有获取执行完才可以获取正确的num,原因不明......
+    await sleep(1000)
+    let num = await db.read().get('sc_exeData').filter({ auto: true, isshow: false }).size().value()
+    // 先让默认的“未分类” isshow:true,免于被删除,当不存在手动添加的软件或自动检测软件的时候,暂时隐藏“未分类”,
+    // 当启动器不存在任何软件时,默认显示“未分类”
+    // let manualnum = db.read().get('sc_exeData').filter({ auto: false }).size().value()
+    // let flag = db.read().get('sc_productData').getById('1').value().isshow
+    // if (!flag) {
+    //   db.read().get('sc_productData').updateById('1', { isshow: true }).write()
+    // }
+    // db.read().get('sc_productData').removeWhere({ isshow: false }).write()
+    // let allnum = db.read().get('sc_exeData').size().value()
+    // if (manualnum === 0 && !flag && allnum !== 0) {
+    //   db.read().get('sc_productData').updateById('1', { isshow: false }).write()
+    // }
+    // 对还是isshow:false的软件进行文件夹文件查询,如果还在则保留,不存在则删除
+    console.log(num)
+    if (num !== 0) {
+      let deleteExeData = await db.read().get('sc_exeData').filter({ auto: true, isshow: false }).value()
+      for (let exe of deleteExeData) {
+        let exePath = path.join(exe.path, exe.exeName)
+        let existExe = await db.read().get('sc_exeData').find({ path: exe.path, exeName: exe.exeName, isshow: true }).value()
+        if (fse.pathExistsSync(exePath) && !existExe) {
+          db.read().get('sc_exeData').updateById(exe.id, { isshow: true }).write()
+        }
+      }
+    }
+    db.read().get('sc_exeData').removeWhere({ isshow: false }).write()
+    return num
+  }
+
+  const sleep = (timeout = 2000) => new Promise(resolve => {
+    setTimeout(resolve, timeout)
+  })
+
+  // 取文件名不带后缀
+  function GetFileNameNoExt (filepath) {
+    if (filepath !== '') {
+      let names = filepath.split('\\')
+      let pos = names[names.length - 1].lastIndexOf('.')
+      return names[names.length - 1].substring(0, pos)
+    }
+  }
+
+  // 产生随机数函数
+  function RndNum (n) {
+    let rnd = ''
+    for (let i = 0; i < n; i++) {
+      rnd += Math.floor(Math.random() * 10)
+    }
+    return rnd
+  }
+
+  /**
+   * 判断文件名是否包含纵横软件exe的部分名称
+   * @param item
+   * @return {boolean}
+   */
+  function existSoftwareName (item) {
+    // 纵横软件exe所带包含的文件名
+    const smartcostSoftwareHeader = ['SmartCost', 'Measure', 'DrawingBuilder', 'BillsEditor']
+    let flag = false
+    for (let i = 0; i < smartcostSoftwareHeader.length; i++) {
+      if (item.indexOf(smartcostSoftwareHeader[i]) !== -1) {
+        flag = true
+        break
+      }
+    }
+    return flag
+  }
+
+  function inArray (val, arr) {
+    for (let i in arr) {
+      if (arr[i] === val) {
+        return true
+      }
+    }
+    return false
+  }
+
+  /**
+   * 数据入库
+   *
+   */
+  function insertData (exeData) {
+    return new Promise(resolve => {
+      let pathstr = path.join('data/fileInfo.dll')
+      let libm = ffi.Library(pathstr, {
+        'GetFileInfo': ['string', ['string']]
+      })
+      let fileinfo = JSON.parse(libm.GetFileInfo(path.join(exeData.path, exeData.exeName)))
+      exeData.fileVersion = fileinfo.FileVersion
+      exeData.productName = fileinfo.ProductName
+      exeData.productVersion = fileinfo.ProductVersion
+      exeData.fileDescription = fileinfo.FileDescription
+      let dbExeData = db.read().get('sc_exeData').find({ exeName: exeData.exeName, path: exeData.path, fileVersion: exeData.fileVersion }).value()
+      if (dbExeData === undefined || dbExeData === null) {
+        // 先判断是否存在该产品数据库(同时关联到sc_sofeware.json文件数据),再加入安装包数据库
+        // 新方法,先关联注册表名查找数据,然后再关联产品数据库生成
+        let softwarejson = path.join('data/sc_software.json')
+        let downlist = fse.readJsonSync(softwarejson).sc_down
+        let downinfo = downlist.find(function (item) {
+          return inArray(exeData.regeditName, item.regedits)
+        })
+        // let ptitle = downinfo !== undefined ? downinfo.product_title : ''
+        if (downinfo !== undefined) {
+          exeData.simpleName = downinfo.product_title
+          exeData.versionName = downinfo.title
+          let productInfo = db.read().get('sc_productData').find({ product_id: downinfo.product_id }).value()
+          if (productInfo === undefined || productInfo === null) {
+            let scproductlist = fse.readJsonSync(softwarejson).sc_product
+            let scproductInfo = scproductlist.find(function (item) {
+              return item.product_id === downinfo.product_id
+            })
+            let productInfo2 = scproductInfo
+            productInfo2.addtime = Date.parse(new Date()) / 1000
+            productInfo2.isshow = true
+            let insertproudct = db.read().get('sc_productData').insert(productInfo2).write()
+            exeData.pid = insertproudct.id
+            productInfo = insertproudct
+          } else {
+            exeData.pid = productInfo.id
+            db.read().get('sc_productData').updateById(productInfo.id, { isshow: true }).write()
+          }
+          // 获取json文件中的锁号,更新到exe当中
+          for (let j in downlist) {
+            if (productInfo.product_id === downlist[j].product_id && exeData.versionName === downlist[j].title) {
+              exeData.keyNumber = downlist[j].key_number
+              exeData.keytype = downlist[j].keytype
+              exeData.product_id = downlist[j].product_id
+              exeData.down_id = downlist[j].down_id
+              exeData.product_version = fileinfo.FileVersion
+              exeData.show_tip = fileinfo.FileVesrion !== downlist[j].version && exeData.exeName !== 'BillsEditor.exe'
+              exeData.show_updateVersion = downlist[j].version
+              exeData.regStart = downlist[j].reg_start !== '' && downlist[j].reg_start[fileinfo.FileVersion] !== undefined ? downlist[j].reg_start[fileinfo.FileVersion] : ''
+              break
+            }
+          }
+        } else {
+          exeData.pid = '1'
+          exeData.simpleName = ''
+          exeData.versionName = ''
+          exeData.keyNumber = ''
+          exeData.keytype = ''
+          exeData.product_id = ''
+          exeData.down_id = ''
+          exeData.product_version = ''
+          exeData.show_tip = false
+          exeData.show_updateVersion = ''
+          db.read().get('sc_productData').updateById('1', { isshow: true }).write()
+        }
+        exeData.addtime = Date.parse(new Date()) / 1000
+        exeData.isshow = true
+        exeData.auto = true
+        exeData.id = GetFileNameNoExt(exeData.exeName) + '-' + RndNum(10)
+        ++globalNUM
+        db.read().get('sc_exeData').insert(exeData).write()
+      } else {
+        db.read().get('sc_productData').updateById(dbExeData.pid, { isshow: true }).write()
+        db.read().get('sc_exeData').updateById(dbExeData.id, { isshow: true }).write()
+      }
+      resolve(exeData)
+    })
+  }
+
+  /**
+   * node 循环和异步解决方案
+   * @param Info
+   */
+  function getExeDataList (Info) {
+    return new Promise(resolve => {
+    // setTimeout(function () {
+      regedit.list(regeditPath + '\\' + Info).on('data', async function (result) {
+        let values = result.data.values
+        let regeditData = {
+          path: values['Inno Setup: App Path'].value,
+          name: values['DisplayName'].value,
+          regeditName: Info,
+          fileName: values['Inno Setup: Icon Group'].value
+        }
+        let dirname = await fs.readdirSync(regeditData.path)
+        for (let i in dirname) {
+          if (path.extname(dirname[i]) === '.exe' && existSoftwareName(dirname[i])) {
+            let exeData = regeditData
+            // exeData.exeName = diraname[i]
+            // 这里必须分离2个数组记录值并在外面合并,push才不会出错,否则会重复exeName名,未知bug,坑
+            globalSoftware.push(exeData)
+            globalExe.push(dirname[i])
+          }
+        }
+      })
+      resolve('ok')
+    })
+  }
+}
+
+/**
+ * 判断系统位数
+ * @return {boolean}
+ */
+function isOSWin64 () {
+  return process.arch === 'x64' || process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')
+}
+
+export default updateInstall

+ 86 - 0
src/main/main-process/usb-ffi.js

@@ -0,0 +1,86 @@
+'use strict'
+
+/**
+ * 软件锁检测
+ *
+ * @author EllisRan.
+ * @date 2018/6/20
+ * @version
+ */
+import db from '../../database'
+const path = require('path')
+const ffi = require('ffi')
+const electron = require('electron')
+const Notification = electron.Notification
+const ipcMain = electron.ipcMain
+const usb = require('usb')
+let usbFlag = false
+const usbffi = function (win) {
+  ipcMain.on('testUsb', function () {
+    testUsb(win.webContents)
+  })
+  usb.on('attach', function (device) {
+    testUsb(win.webContents, true)
+  })
+  usb.on('detach', async function (device) {
+    if (usbFlag) {
+      try {
+        let pathstr = path.join('data/zhlData.dll')
+        console.log(pathstr)
+        let libm = ffi.Library(pathstr, {
+          'GetProductIDList': ['string', []]
+        })
+        // console.log(await libm.GetProductIDList())
+        let alllist = await libm.GetProductIDList()
+        if (alllist === '') {
+          usbFlag = false
+          win.webContents.send('usbOut')
+          const notification = new Notification({
+            title: '软件锁',
+            body: '你的软件锁已拔出'
+          })
+          notification.show()
+        }
+      } catch (err) {
+        console.error(err)
+      }
+    }
+  })
+}
+/**
+ * 检测锁
+ * @param webContents
+ * @param usbIn  是否为插入u盘
+ * @return {Promise.<void>}
+ */
+async function testUsb (webContents, usbIn = false) {
+  try {
+    let pathstr = path.join('data/zhlData.dll')
+    let libm = ffi.Library(pathstr, {
+      'GetProductIDList': ['string', []]
+    })
+    // console.log(await libm.GetProductIDList())
+    let alllist = await libm.GetProductIDList()
+    if (alllist !== '') {
+      // let productlist = alllist.split('|')
+      // for (let i in productlist) {
+      //   let productNum = productlist[i].split(':')[0]
+      //   let productVerList = productlist[i].split(':')[1].split(';')
+      // }
+      let first = await db.read().get('sc_hadInstall.first').value()
+      webContents.send('usbIn', { lockmsg: alllist, first: first, usbIn: usbIn })
+      if (usbIn) {
+        usbFlag = true
+        const notification = new Notification({
+          title: '软件锁',
+          body: '启动器检测到软件锁'
+        })
+        notification.show()
+      }
+    }
+  } catch (err) {
+    console.error(err)
+  }
+}
+
+export default usbffi

+ 64 - 0
src/main/software-update.js

@@ -0,0 +1,64 @@
+'use strict'
+
+/**
+ *
+ *
+ * @author EllisRan.
+ * @date 2018/6/4
+ * @version
+ */
+const electron = require('electron')
+const ipcMain = electron.ipcMain
+const autoUpdater = require('electron-updater').autoUpdater
+const uploadUrl = 'http://d2.smartcost.com.cn/startup/'
+
+exports.initialize = function (win) {
+  // let message = {
+  //   error: '检查更新出错 1',
+  //   checking: '正在检查更新…… 2',
+  //   updateAva: '检测到新版本,正在下载…… 3',
+  //   updateNotAva: '现在使用的就是最新版本,不用更新 4'
+  // }
+  autoUpdater.setFeedURL(uploadUrl)
+  autoUpdater.autoDownload = false
+  autoUpdater.on('error', function () {
+    // sendUpdateMessage(win, message.error)
+  })
+  autoUpdater.on('checking-for-update', function () {
+    // sendUpdateMessage(win, message.checking)
+  })
+  autoUpdater.on('update-available', function (info) {
+    sendUpdateMessage(win, 2)
+  })
+  autoUpdater.on('update-not-available', function (info) {
+    // sendUpdateMessage(win, message.updateNotAva)
+  })
+
+  // 更新下载进度事件
+  autoUpdater.on('download-progress', function (progressObj) {
+    win.webContents.send('downloadProgress', progressObj)
+    win.setProgressBar(progressObj.percent / 100)
+    // console.log(progressObj)
+  })
+  autoUpdater.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
+    ipcMain.on('isUpdateNow', (e, arg) => {
+      // some code here to handle event
+      autoUpdater.quitAndInstall()
+    })
+
+    win.webContents.send('isUpdateNow')
+  })
+
+  ipcMain.on('checkForUpdate', () => {
+    // 执行自动更新检查
+    autoUpdater.checkForUpdates()
+  })
+}
+ipcMain.on('downloadUpdate', () => {
+  autoUpdater.downloadUpdate()
+})
+
+// 通过main进程发送事件给renderer进程,提示更新信息
+function sendUpdateMessage (win, status = 0) {
+  win.webContents.send('msgBox', status)
+}

+ 18 - 0
src/renderer/App.vue

@@ -0,0 +1,18 @@
+<template>
+  <div id="app">
+    <router-view></router-view>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'startup'
+  }
+</script>
+
+<style>
+  /* CSS */
+  @import "assets/font-awesome/fontawesome-all.css";
+  @import "assets/bootstrap.min.css";
+  @import "assets/global.css";
+</style>

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


ファイルの差分が大きいため隠しています
+ 7 - 0
src/renderer/assets/bootstrap-grid.min.css


ファイルの差分が大きいため隠しています
+ 8 - 0
src/renderer/assets/bootstrap-reboot.min.css


ファイルの差分が大きいため隠しています
+ 7 - 0
src/renderer/assets/bootstrap.min.css


BIN
src/renderer/assets/font-awesome/font/fa-brands-400.eot


ファイルの差分が大きいため隠しています
+ 1104 - 0
src/renderer/assets/font-awesome/font/fa-brands-400.svg


BIN
src/renderer/assets/font-awesome/font/fa-brands-400.ttf


BIN
src/renderer/assets/font-awesome/font/fa-brands-400.woff


BIN
src/renderer/assets/font-awesome/font/fa-brands-400.woff2


BIN
src/renderer/assets/font-awesome/font/fa-regular-400.eot


ファイルの差分が大きいため隠しています
+ 372 - 0
src/renderer/assets/font-awesome/font/fa-regular-400.svg


BIN
src/renderer/assets/font-awesome/font/fa-regular-400.ttf


BIN
src/renderer/assets/font-awesome/font/fa-regular-400.woff


BIN
src/renderer/assets/font-awesome/font/fa-regular-400.woff2


BIN
src/renderer/assets/font-awesome/font/fa-solid-900.eot


ファイルの差分が大きいため隠しています
+ 1896 - 0
src/renderer/assets/font-awesome/font/fa-solid-900.svg


BIN
src/renderer/assets/font-awesome/font/fa-solid-900.ttf


BIN
src/renderer/assets/font-awesome/font/fa-solid-900.woff


BIN
src/renderer/assets/font-awesome/font/fa-solid-900.woff2


ファイルの差分が大きいため隠しています
+ 4013 - 0
src/renderer/assets/font-awesome/fontawesome-all.css


BIN
src/renderer/assets/font/pingfang.ttf


+ 668 - 0
src/renderer/assets/global.css

@@ -0,0 +1,668 @@
+@charset "utf-8";
+/*样式初始化*/
+@font-face {
+    font-family: 'pingfang SC';
+    src:url('font/pingfang.ttf') format('truetype');
+}
+body{
+    font-family:-apple-system, BlinkMacSystemFont, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft Yahei', '微软雅黑', sans-serif;
+    font-size:12px !important;
+}
+#app {
+  overflow: hidden;
+}
+.el-dropdown {
+  color: #212529;
+}
+::-webkit-scrollbar {
+  width: 5px;
+  height: 5px;
+}
+::-webkit-scrollbar-thumb {
+  background-color: #ddd5cf;
+}
+::-webkit-scrollbar-track {
+  background-color: #fff;
+}
+
+.btn-white {
+  color: #333;
+  background-color: #fff;
+  border-color: #ddd;
+}
+
+.btn-orange {
+  color: #fff;
+  background-color: #ff6501;
+  border-color: #ff6501;
+}
+.btn-orange:hover {
+  color: #fff;
+  background-color: #e55a00;
+  border-color: #e55a00;
+}
+
+.btn-orange:focus, .btn-orange.focus {
+  box-shadow: 0 0 0 0.2rem rgba(45, 174, 191, 0.5);
+}
+
+.btn-orange.disabled, .btn-orange:disabled {
+  color: #fff;
+  background-color: #e55a00;
+  border-color: #e55a00;
+}
+
+.btn-orange:not(:disabled):not(.disabled):active, .btn-orange:not(:disabled):not(.disabled).active,
+.show > .btn-orange.dropdown-toggle {
+  color: #fff;
+  background-color: #e55a00;
+  border-color: #e55a00;
+}
+
+.btn-orange:not(:disabled):not(.disabled):active:focus, .btn-orange:not(:disabled):not(.disabled).active:focus,
+.show > .btn-orange.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(45, 174, 191, 0.5);
+}
+
+.btn-blue {
+  color: #fff;
+  background-color: #14a5e8;
+  border-color: #14a5e8;
+}
+
+.btn-blue:hover {
+  color: #fff;
+  background-color: #0e90cc;
+  border-color: #0e90cc;
+}
+
+.btn-blue:focus, .btn-blue.focus {
+  box-shadow: 0 0 0 0.2rem rgba(45, 174, 191, 0.5);
+}
+
+.btn-blue.disabled, .btn-blue:disabled {
+  color: #fff;
+  background-color: #0e90cc;
+  border-color: #0e90cc;
+}
+
+.btn-blue:not(:disabled):not(.disabled):active, .btn-blue:not(:disabled):not(.disabled).active,
+.show > .btn-blue.dropdown-toggle {
+  color: #fff;
+  background-color: #0e90cc;
+  border-color: #0e90cc;
+}
+
+.btn-blue:not(:disabled):not(.disabled):active:focus, .btn-blue:not(:disabled):not(.disabled).active:focus,
+.show > .btn-blue.dropdown-toggle:focus {
+  box-shadow: 0 0 0 0.2rem rgba(45, 174, 191, 0.5);
+}
+/*a {
+  color: #007bff;
+  text-decoration: none;
+  background-color: transparent;
+  -webkit-text-decoration-skip: objects;
+}*/
+.container-width{
+  /*position:relative;*/
+  padding:20px;
+  /*width:900px;
+  height:564px;*/
+  /*overflow: hidden;*/
+  background: -prefix-linear-gradient(left top,#6d5855,#3b2d2a);
+  background: linear-gradient(to bottom right,#6d5855,#3b2d2a);
+  position:absolute;
+  left:0;
+  top:0;
+  width:100%;
+  height:100%;
+}
+
+.el-header {
+  padding: 5px 0 0 0 !important;
+}
+
+.header{
+  /*background-color: rgba(0,153,204,1);*/
+  /*color: #fff;*/
+  -webkit-app-region: drag;
+  margin-top: -20px;
+  padding-top: 5px;
+  height: 60px;
+  color: #fff;
+  background: url(../assets/img/header-bg.png) no-repeat 140px 0;
+}
+
+.header i {
+  color: #fff;
+}
+.webkit-drag {
+  -webkit-app-region: no-drag
+}
+
+.media-body {
+  -ms-flex: 1;
+  flex: 1;
+}
+
+.border {
+  border-color: rgba(0,153,204,0.8) !important
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+}
+
+.el-button--medium {
+  padding: .375rem .75rem;
+  font-size: 1rem;
+  line-height: 1.5;
+  border-radius: .25rem;
+}
+.el-main {
+  padding: 0!important;
+  position: relative;
+  background: #fbf6f3;
+  overflow: hidden;
+}
+.el-loading-mask {
+  position: fixed !important;
+}
+.el-tabs__item.is-active {
+  color: #495057!important;
+  background-color: #fff;
+  border-color: #fff;
+}
+.el-tabs__item {
+  padding: 0!important;
+  height: auto!important;
+  line-height: initial!important;
+  /*border: 1px solid transparent;*/
+  border-left: none!important;
+  font-size: 12px!important;
+  color: #007bff!important;
+  /*font-weight: 500!important;*/
+}
+.el-tabs--card>.el-tabs__header .el-tabs__nav{
+  border: none!important;
+}
+.version-name {
+  margin-left: 15px;
+  color: #ddd;
+  font-size: 12px;
+}
+.el-tabs__header {
+  position: absolute!important;
+  right: 2.5%!important;
+  top: 0px!important;
+  padding-left: 1rem!important;
+  border: 0!important;
+  display: flex!important;
+  -ms-flex-wrap: wrap!important;
+  flex-wrap: wrap!important;
+  margin-bottom: 0!important;
+  list-style: none!important;
+  margin-top: 0!important;
+}
+.el-tabs__item .detail-title{
+  padding: 12px 25px;
+  display: block;
+}
+.el-tabs__item:hover{
+  background-color: #fff;
+  border-color: #fff;
+}
+
+.el-tabs__content{
+  position: absolute!important;
+  top:80px!important;
+  width: 95%;
+  bottom: 15px;
+  margin: 0 2.5%;
+  background-color: #fff;
+}
+
+.downloaditem {
+  background: #fbf6f3;
+  border-radius: 3px;
+  padding: 10px;
+  font-size: 14px;
+  border: 2px solid #fbf6f3;
+  margin: 10px 0 10px 0;
+}
+
+.downloaditem button{
+  font-size: 12px;
+}
+
+.text-danger {
+  color: #dc3545!important;
+}
+.el-progress-bar {
+  vertical-align: -webkit-baseline-middle !important;
+}
+.el-progress.is-success .el-progress-bar__inner {
+  background-color: #28a745 !important;
+}
+.el-progress-bar__inner,.el-progress-bar__outer {
+  border-radius: .25rem !important;
+}
+.download-header, .update-header {
+  cursor: pointer;
+  font-size: 12px !important;
+}
+.progress-content .float-right {
+  font-size: 16px;
+}
+.progress-width {
+  width: 85%;
+}
+.text-secondary {
+  color: #6c757d!important;
+}
+.badge-orange {
+  background: #ff9900;
+}
+.badge-red {
+  background: #f00;
+}
+.badge {
+  font-size: 12px;
+  color: #fff;
+}
+.software-list{
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  width: 100%;
+  overflow-y: auto!important;
+}
+.software-item {
+  background: #fff;
+  border: 2px solid #fff;
+  border-radius: 3px;
+  padding: 12px;
+  margin: 0 15px 15px;
+}
+.software-title {
+  font-size: 16px;
+  font-weight: bold;
+}
+.software-body {
+  width: 50%;
+}
+.software-item .software-body{
+  width:85% !important;
+}
+.software-item a{
+  color:#333;
+}
+.software-item a:hover{
+  text-decoration: none;
+}
+
+.software-version {
+  font-size: 14px;
+  color: #888;
+}
+.software-left {
+  position: absolute;
+  width: 27%;
+  top: 0;
+  bottom: 0;
+  left: 0;
+}
+
+.open-left {
+  position: absolute;
+  top: 45%;
+  left: 100%;
+  z-index: 100;
+}
+.open-left span {
+  display: inline-block;
+  font-size: 16px;
+  width: 12px;
+  text-align: center;
+  padding: 10px 0;
+  background: #fff;
+  color: #574542;
+  cursor: pointer;
+}
+.software-num{
+  position:absolute;
+  top:15px;
+  right:0;
+  background:#e9e9e9;
+  color:#555;
+  display:inline-block;
+  padding:3px 8px;
+}
+.software-left-bottom {
+  z-index: 999;
+  position:absolute;
+  width:100%;
+  bottom:0;
+  left:0;
+  background:#fff;
+  height:55px;
+}
+
+.local-software {
+  position: absolute;
+  width: 100%;
+  top: 0;
+  left: 0;
+  bottom: 47px;
+  background: #fff;
+  overflow-y: auto;
+}
+.local-software a{
+  text-decoration: none;
+}
+.software {
+  position: relative;
+  padding: 12px 0;
+  margin: 0 15px;
+  border-bottom: 1px solid #f5ede7;
+  cursor: pointer;
+}
+.software.active {
+  font-weight: bold;
+  color: #ff6501;
+}
+.software.active a {
+  color: #ff6501;
+}
+.software a {
+  color: #333;
+  text-decoration: none;
+  display: block;
+}
+.software:hover a{
+  color:#ff6501;
+}
+.software.active:before{
+  position:absolute;
+  content: "";
+  top:20px;
+  right:-15px;
+  height: 0px;
+  width: 0px;
+  border-top: 7px solid transparent;
+  border-right: 8px solid #fbf6f3;
+  border-bottom: 8px solid transparent;
+}
+.software.active .software-num,.software:hover .software-num{
+  background:#ff6501;
+  color:#fff;
+}
+.command-menu {
+  padding: .5rem 0 !important;
+  color: #212529 !important;
+  border-radius: .25rem !important;
+  margin: .125rem 0 0 !important;
+  font-size: 1rem !important;
+}
+.command-item {
+  font-size: 1rem !important;
+}
+.el-popper[x-placement^=top] {
+  margin-bottom: 5px!important;
+}
+
+.software-startup-detail {
+  position:absolute;
+  top: 0;
+  left:27%;
+  bottom:0;
+  width:73%;
+  overflow-y:auto;
+}
+
+.iconbg-purple {
+  background: #8a7bfd;
+  color: #fff;
+}
+.iconbg-green {
+  background: #0fb890;
+  color: #fff;
+}
+.iconbg-yellow {
+  background: #bfad6a;
+  color: #fff;
+}
+.iconbg {
+  display: block;
+  width: 30px;
+  padding: 8px 0;
+  margin: 0 0 5px 9px;
+  text-align: center;
+}
+
+.software-content {
+  height: 100%;
+  overflow-y: auto;
+  padding: 15px;
+}
+.details {
+  background: #fbf6f3;
+  padding: 15px;
+  border: 2px solid #fff;
+  border-radius: 3px;
+  margin: 0 0 15px;
+  color: #888;
+  font-size: 14px;
+}
+.software-item:hover,.details:hover{
+  border:2px solid rgba(255,101,5,0.2);
+}
+.software-item:hover .btn-white,.details:hover .btn-white{
+  background:#ff6501;
+  color:#fff;
+  border:1px solid #ff6501;
+}
+.details-title {
+  width: 85%;
+  font-size: 16px;
+  color: #333;
+}
+.software-stitle {
+  font-size: 14px;
+  color: #888;
+}
+.details .details-title .w-25 {
+  width: 62%!important;
+  font-weight: 500;
+}
+.details .w-25 {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+}
+
+.software-content a{
+  cursor: pointer;
+  color: #007bff!important;
+}
+.btn-success {
+  color: #fff !important;
+  background-color: #28a745 !important;
+  border-color: #28a745 !important;
+}
+.modal-body {
+  max-height: 280px;
+  overflow-y: auto;
+}
+.btn-secondary {
+  color: #fff;
+  background-color: #6c757d;
+  border-color: #6c757d;
+}
+.btn-secondary:hover {
+  color: #fff;
+  background-color: #5a6268;
+  border-color: #545b62;
+}
+
+.usb-header, .user-header {
+  cursor: pointer;
+  font-size: 12px !important;
+}
+.downloadlist, .updatelist {
+  min-width: 500px!important;
+  max-height: 450px!important;
+  padding: 10px 15px!important;
+  overflow-y: auto!important;
+}
+.usblist {
+  min-width: 550px!important;
+  max-height: 450px!important;
+  padding: 10px 15px!important;
+  overflow-y: auto!important;
+}
+.userlist {
+  min-width: 260px!important;
+  max-height: 450px!important;
+  padding: 10px 15px!important;
+  overflow-y: auto!important;
+}
+.downloading {
+  font-size: 14px;
+}
+.downloading button{
+  font-size: 12px;
+}
+.text-orange {
+  color: #ff6501;
+  padding: 0 10px;
+}
+.header-return {
+  display: inline-block;
+  font-size: 20px;
+  font-weight: bold;
+  padding: 13px 5px 0 0;
+}
+.el-dialog__body {
+  margin: 10px 0 !important;
+  padding: 0 0 10px 0 !important;
+  border-bottom: 1px solid #e9ecef !important;
+  border-top: 1px solid #e9ecef !important;
+  font-size: 12px !important;
+}
+.freewrap{
+  background:#eee5df;
+  margin:0 15px 15px 15px;
+  border-radius: 3px;
+  padding: 0 0 5px 0;
+}
+.freewrap .col{
+  padding:0;
+}
+.free-title{
+  padding:10px 15px;
+  font-size: 18px;
+  border-radius: 3px 3px 0 0;
+}
+.free-content{
+  position:relative;
+  background:#fff;
+  padding:15px;
+  margin:0 15px 15px;
+  height:90px;
+  border:2px solid #fff;
+  border-radius: 3px;
+}
+.free-content:hover{
+  border:2px solid rgba(255,101,5,0.2);
+}
+.free-content:hover .btn-white{
+  background:#ff6501;
+  color:#fff;
+  border:1px solid #ff6501;
+}
+.freewrap .col:first-child{
+  margin-left: 15px;
+}
+.freewrap .col:last-child{
+  margin-right: 15px;
+}
+.freewrap .col:first-child .free-content{
+  margin-right: 0;
+}
+.free-right{
+  position:absolute;
+  right:15px;
+  bottom:10px;
+}
+
+.header i{
+  cursor:pointer;
+}
+.details-title a{
+  color:#333!important;
+}
+.details-title a:hover{
+  background:#fff;
+  text-decoration: none;
+}
+.vide-title {
+  margin: 0 0 20px 0;
+  font-size: 16px;
+}
+.card-width {
+  width: 189px;
+}
+.head-img {
+  margin-top: -7px;
+  width: 26px;
+  height: 26px;
+  border-radius: 13px;
+  vertical-align: top;
+}
+.alert-sm {
+  padding: .25rem .5rem;
+}
+.user-dropdown-menu {
+  font-size: 14px;
+  /*margin-bottom: 20px;*/
+}
+.nomal-a {
+  cursor: pointer;
+  color: #007bff !important;
+  text-decoration: none;
+  background-color: transparent;
+  -webkit-text-decoration-skip: objects;
+}
+.nomal-a:hover{
+  text-decoration: underline !important;
+}
+.dropdown-item {
+  cursor: pointer;
+  color: #007bff !important;
+}
+.form-set label{
+  font-size: 12px;
+}
+/*.form-set input{*/
+  /*font-size: 12px;*/
+  /*padding: .25rem .5rem;*/
+  /*font-size: .875rem;*/
+  /*line-height: 1.5;*/
+  /*border-radius: .2rem;*/
+  /*height: auto;*/
+/*}*/
+
+.form-set .el-form-item__error {
+  top: 75%;
+}
+.form-set .el-form-item__content .el-form-item__content .el-form-item__error {
+  top: 85%;
+}
+.form-set .el-form-item .el-form-item{
+  margin-bottom: 0;
+}
+.form-set .el-form-item {
+  margin-bottom: 10px;
+}

BIN
src/renderer/assets/img/bg.jpg


BIN
src/renderer/assets/img/bg1.jpg


BIN
src/renderer/assets/img/bg2.jpg


BIN
src/renderer/assets/img/bodyBg.jpg


BIN
src/renderer/assets/img/header-bg.png


BIN
src/renderer/assets/img/headimg.png


BIN
src/renderer/assets/img/logo.png


BIN
src/renderer/assets/img/mainlogo.png


BIN
src/renderer/assets/logo.png


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

@@ -0,0 +1,128 @@
+<template>
+  <div id="wrapper">
+    <img id="logo" src="~@/assets/logo.png" alt="electron-vue">
+    <main>
+      <div class="left-side">
+        <span class="title">
+          Welcome to your new project!
+        </span>
+        <system-information></system-information>
+      </div>
+
+      <div class="right-side">
+        <div class="doc">
+          <div class="title">Getting Started</div>
+          <p>
+            electron-vue comes packed with detailed documentation that covers everything from
+            internal configurations, using the project structure, building your application,
+            and so much more.
+          </p>
+          <button @click="open('https://simulatedgreg.gitbooks.io/electron-vue/content/')">Read the Docs</button><br><br>
+        </div>
+        <div class="doc">
+          <div class="title alt">Other Documentation</div>
+          <button class="alt" @click="open('https://electron.atom.io/docs/')">Electron</button>
+          <button class="alt" @click="open('https://vuejs.org/v2/guide/')">Vue.js</button>
+        </div>
+      </div>
+    </main>
+  </div>
+</template>
+
+<script>
+  import SystemInformation from './LandingPage/SystemInformation'
+
+  export default {
+    name: 'landing-page',
+    components: { SystemInformation },
+    methods: {
+      open (link) {
+        this.$electron.shell.openExternal(link)
+      }
+    }
+  }
+</script>
+
+<style>
+  @import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro');
+
+  * {
+    box-sizing: border-box;
+    margin: 0;
+    padding: 0;
+  }
+
+  body { font-family: 'Source Sans Pro', sans-serif; }
+
+  #wrapper {
+    background:
+      radial-gradient(
+        ellipse at top left,
+        rgba(255, 255, 255, 1) 40%,
+        rgba(229, 229, 229, .9) 100%
+      );
+    height: 100vh;
+    padding: 60px 80px;
+    width: 100vw;
+  }
+
+  #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 p {
+    color: black;
+    margin-bottom: 10px;
+  }
+
+  .doc button {
+    font-size: .8em;
+    cursor: pointer;
+    outline: none;
+    padding: 0.75em 2em;
+    border-radius: 2em;
+    display: inline-block;
+    color: #fff;
+    background-color: #4fc08d;
+    transition: all 0.15s ease;
+    box-sizing: border-box;
+    border: 1px solid #4fc08d;
+  }
+
+  .doc button.alt {
+    color: #42b983;
+    background-color: transparent;
+  }
+</style>

+ 203 - 0
src/renderer/components/StartUpPage.vue

@@ -0,0 +1,203 @@
+<template>
+  <el-container class="container-width rounded">
+    <el-header class="header">
+      <div class="d-flex">
+        <div class="" v-show="iconShow">
+          <span class="header-return webkit-drag">
+            <a @click="goURL">
+            <i class="fas fa-angle-left" style="vertical-align: text-top;cursor: pointer"></i>
+            </a>
+          </span>
+        </div>
+        <div v-if="iconShow" class="p-2"><img src="../assets/img/logo.png" width="30" height="30"></div>
+        <div class="pt-2 pb-2 pr-2" v-else ><img src="../assets/img/logo.png" width="30" height="30"></div>
+        <div class="p-2"><h5 class="mt-1">纵横Z+ 造价工作平台 <span class="version-name">版本:V{{ version }}</span></h5></div>
+        <div class="ml-auto p-2 pt-3 webkit-drag">
+          <div class="d-flex flex-wrap">
+            <div class="">
+              <update-header ref="update" v-on:softwareDownload="relationDownload"></update-header>
+            </div>
+            <div class="">
+              <download-header ref="download"></download-header>
+            </div>
+            <div class="">
+              <usb-header ref="usb" v-on:softwareDownload="relationDownload"></usb-header>
+            </div>
+            <div class="mr-5">
+              <user-header ref="user"></user-header>
+            </div>
+            <div class="">
+              <div class="mr-3"><i class="fas fa-minus" @click="minimizeWindow"></i></div>
+            </div>
+            <div class="">
+              <div class=""><i class="fas fa-times" @click="closeWindow"></i></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-header>
+    <el-main>
+      <router-view v-on:softwareDownload="relationDownload" v-on:softwareUpdate="relationUpdate"></router-view>
+    </el-main>
+    <software-update></software-update>
+  </el-container>
+</template>
+
+<script>
+  import pkg from '../../../package.json'
+  import mixin from './mixin'
+  import SoftwareUpdate from './StartUpPage/SoftwareUpdate'
+  import UsbHeader from './StartUpPage/UsbHeader'
+  import DownloadHeader from './StartUpPage/DownloadHeader'
+  import UpdateHeader from './StartUpPage/UpdateHeader'
+  import UserHeader from './StartUpPage/UserHeader'
+  const electron = require('electron').remote
+  const BrowserWindow = electron.BrowserWindow
+  const fs = require('fs-extra')
+  const path = require('path')
+
+  export default {
+    name: 'element-page',
+    mixins: [mixin],
+    components: { SoftwareUpdate, UsbHeader, DownloadHeader, UpdateHeader, UserHeader },
+    data: () => ({
+      version: pkg.version,
+      iconShow: false
+    }),
+    watch: {
+      // 如果路由有变化,会再次执行该方法
+      '$route': 'iconshow'
+    },
+    created () {
+      //      document.onkeydown = function (e) {
+      //        let ev = window.event
+      //        console.log(ev)
+      //        let key = ev.keyCode
+      //        if (key === 9) {
+      //          console.log('tab')
+      //          window.event.keyCode = 0
+      //          window.event.cancelBubble = true
+      //          window.event.returnValue = false
+      //          return false
+      //        }
+      //      }
+      // 更改为启动前获取最新官网产品数据
+      let softwarejson = path.join('data/sc_software.json')
+      if (this.checkOnline()) {
+        let version = fs.readJsonSync(softwarejson).startup_version
+        let self = this
+        this.$http({
+          url: 'https://smartcost.com.cn/startup/sc_version.json?' + this.RndNum(),
+          method: 'get',
+          timeout: 5000
+        }).then(function (response) {
+          if (version !== response.data.startup_version) {
+            // 加入随机数防止缓存
+            console.log(response.data)
+            console.log(version)
+            self.$http.get('https://smartcost.com.cn/startup/sc_software.json?' + self.RndNum())
+              .then(function (response2) {
+                fs.writeJsonSync(softwarejson, response2.data)
+                // 还要更新本地数据
+                let scproductlist = response2.data.sc_product
+                let productlist = self.$db.read().get('sc_productData').value()
+                for (let i in productlist) {
+                  let scproductinfo = scproductlist.find(function (item) {
+                    return item.product_id === productlist[i].product_id
+                  })
+                  self.$db.read().get('sc_productData').updateById(productlist[i].id, scproductinfo).write()
+
+                  let scdownlist = response2.data.sc_down
+                  let downlist = self.$db.read().get('sc_exeData').value()
+                  for (let j in downlist) {
+                    let scdowninfo = scdownlist.find(function (item) {
+                      return item.dwon_id === downlist[j].down_id
+                    })
+                    if (scdowninfo !== undefined) {
+                      let version = downlist[j].version
+                      let setinfo = {
+                        keyNumber: scdowninfo.key_number,
+                        keytype: scdowninfo.keytype,
+                        simpleName: scdowninfo.product_title,
+                        versionName: scdowninfo.title,
+                        regStart: scdowninfo.reg_start !== '' && scdowninfo.reg_start[version] !== undefined ? scdowninfo.reg_start[version] : ''
+                      }
+                      self.$db.read().get('sc_exeData').updateById(downlist[j].id, setinfo).write()
+                    }
+                  }
+                }
+              })
+              .catch(function (error2) {
+                throw error2
+              })
+          }
+        }).catch(function () {
+        })
+
+        // 启动前检测数据库一些异常情况,恢复到上一个状态,暂时包含下载列,安装列, 删除标记delete的字段
+        this.$db.read().get('sc_download').removeWhere({ delete: true }).write()
+        let downinfo = this.$db.read().get('sc_download').find({ status: 5 }).value()
+        if (downinfo !== undefined) {
+          this.$db.read().get('sc_download').updateById(downinfo.id, { show: true, start: true, pause: false, resume: false }).write()
+        } else {
+          let downinfo2 = this.$db.read().get('sc_download').find({ status: 1 }).value()
+          if (downinfo2 !== undefined) {
+            this.$db.read().get('sc_download').updateById(downinfo2.id, { status: 5, show: true, start: true, pause: false, resume: false }).write()
+          }
+        }
+        this.$db.read().get('sc_download').updateWhere({ status: 3 }, { status: 2 }).write()
+        // 删除残留的软件
+        this.$db.read().get('sc_exeData').removeWhere({ isshow: false }).write()
+      }
+    },
+    methods: {
+      minimizeWindow () {
+        const window = BrowserWindow.getFocusedWindow()
+        window.minimize()
+      },
+      closeWindow () {
+        const window = BrowserWindow.getFocusedWindow()
+        window.close()
+      },
+      goURL () {
+        if (this.$route.path === '/softwarelist') {
+          let first = this.$db.read().get('sc_hadInstall.first').value()
+          let url = first ? { path: '/firstopen' } : { path: this.$db.read().get('sc_hadInstall.url').value() }
+          this.$router.push(url)
+        } else {
+          this.$router.push({ path: '/softwarelist' })
+        }
+      },
+      iconshow () {
+        let path = this.$route.path
+        if (path === '/softwarelist' || path.indexOf('/softwaredetail') !== -1) {
+          this.iconShow = true
+        } else {
+          this.iconShow = false
+        }
+      },
+      relationDownload (downID) {
+        console.log(downID)
+        this.$refs.download.adddownload(downID)
+      },
+      relationUpdate () {
+        this.$refs.update.fetchData()
+      },
+      checkOnline () {
+        if (!navigator.onLine) {
+          return false
+        } else {
+          return true
+        }
+      },
+      // 产生随机数函数
+      RndNum (n = 6) {
+        let rnd = ''
+        for (let i = 0; i < n; i++) {
+          rnd += Math.floor(Math.random() * 10)
+        }
+        return rnd
+      }
+    }
+  }
+</script>

+ 308 - 0
src/renderer/components/StartUpPage/DownloadHeader.vue

@@ -0,0 +1,308 @@
+<template>
+  <el-dropdown class="download-header float-left mr-3" ref="downloadmenu" trigger="click" :hide-on-click=false>
+    <span class="el-dropdown-link">
+       <span :class="[badge, downloadNum !== 0 ? badgeLight : '']">
+        <i class="fas fa-download" :class="downloadNum !== 0 ? textSecondary : ''"></i><span v-show="downloadNum !== 0" class="text-secondary"> {{ downloadNum }}</span>
+      </span>
+    </span>
+    <!--<el-button @click="testbtn">test</el-button>-->
+    <el-dropdown-menu slot="dropdown" class="downloadlist">
+      <div v-if="downloadNum === 0">
+        没有待安装软件
+      </div>
+      <div v-for="(item, index) in downloads" v-show="!item.delete" :key="item.id" class="downloaditem">
+        <div class="pb-2"><span class="float-right">{{ item.size }}</span>{{ item.fulltitle }}</div>
+        <div class="pb-2">
+          <div class="progress-content" v-if="item.isfinish === false">
+            <div class="float-right">
+              <span v-if="item.show">
+                <i v-show="item.pause" class="far fa-pause-circle text-secondary mr-2" @click="operationDownload('pause', item.id, index)"></i>
+                <i v-show="item.resume" class="far fa-play-circle text-secondary mr-2" @click="operationDownload('resume', item.id, index)"></i>
+                <i v-show="item.start" class="far fa-play-circle text-secondary mr-2" @click="operationDownload('start', item.id, index)"></i>
+              </span>
+              <i class="fas fa-times text-danger" @click="deleteDownload(item.id, index)"></i>
+            </div>
+            <div class="progress-width">
+              <el-progress :text-inside="true" :stroke-width="18" :percentage="item.percent" status="success"></el-progress>
+            </div>
+          </div>
+          <div class="progress-content" v-else>
+            <div class="float-right"><i class="fas fa-times text-danger" @click="deleteDownload(item.id, index)"></i></div>
+            <div class="progress-width">
+              <button v-if="item.status === 2" @click="installbtn(item.id, index)" class="btn btn-blue btn-sm btn-lg btn-block">安装软件</button>
+              <button v-else-if="item.status === 3" class="btn btn-secondary btn-sm btn-lg btn-block">正在安装本软件...</button>
+              <button v-else-if="item.status === 4" class="btn btn-success btn-sm btn-lg btn-block">已安装</button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  const fs = require('fs-extra')
+  const path = require('path')
+  const lodashId = require('lodash-id')
+  const ffi = require('ffi')
+  export default {
+    mixins: [mixin],
+    data () {
+      return {
+        downloadNum: 0,
+        downloads: '',
+        badge: 'badge',
+        badgeLight: 'badge-light',
+        textSecondary: 'text-secondary'
+      }
+    },
+    created () {
+      this.$electron.ipcRenderer.on('downloadtips', (event, msg) => {
+        let percent = msg.percent
+        let info = this.$db.read().get('sc_download').getById(msg.id).value()
+        info.percent = percent
+        console.log(info)
+        this.downloads.splice(msg.index, 1, info)
+        if (!this.checkOnline() && info.pause === true) {
+          this.operationDownload('pause', msg.id, msg.index)
+        }
+      })
+      this.$db._.mixin(lodashId)
+      this.downloads = this.$db.read().get('sc_download').value()
+      this.downloadNum = this.$db.read().get('sc_download').filter({delete: false}).size().value()
+
+      this.$electron.ipcRenderer.on('downloadsuccess', (event, msg) => {
+        let info = this.$db.read().get('sc_download').getById(msg.id).value()
+        info.isfinish = true
+        info.show = false
+        info.pause = false
+        info.savepath = msg.savepath
+        info.status = 2
+        this.$db.read().get('sc_download').getById(msg.id).assign(info).write()
+        // 通知下载完成
+        let notification = {
+          title: '启动器',
+          body: info.fulltitle + '下载完成'
+        }
+        let myNotification = new window.Notification(notification.title, notification)
+        this.downloads.splice(msg.index, 1, info)
+        // 下载完成当前后 获取下一个info并启动它,没有则停止
+        let info2 = this.$db.read().get('sc_download').find({ status: 1 }).value()
+        if (info2 !== undefined) {
+          info2.show = true
+          info2.status = 5
+          this.$db.read().get('sc_download').getById(info2.id).assign(info2).write()
+          let index = this.downloads.findIndex(function (item) {
+            return item.id === info2.id
+          })
+          this.downloads.splice(index, 1, info2)
+          this.operationDownload('start', info2.id, index)
+        }
+        myNotification.onclick = () => {
+          return true
+        }
+      })
+
+      this.$electron.ipcRenderer.on('InstallResult', (event, item) => {
+        let info = this.$db.read().get('sc_download').getById(item.id).value()
+        if (item.status === 400) {
+          this.$message.error(item.msg)
+          this.$db.read().get('sc_download').updateById(item.id, { status: 2 }).write()
+          info.status = 2
+          this.downloads.splice(item.index, 1, info)
+        } else {
+          this.$db.read().get('sc_download').updateById(item.id, { status: 4 }).write()
+          info.status = 4
+          this.downloads.splice(item.index, 1, info)
+        }
+      })
+    },
+    beforeDestroy () {
+    },
+    methods: {
+      checkOnline () {
+        if (!navigator.onLine) {
+          this.$message.error('当前网络不可用,无法下载')
+          return false
+        } else {
+          return true
+        }
+      },
+      operationDownload (status, id, index) {
+        let info = this.$db.read().get('sc_download').getById(id).value()
+        if (this.checkOnline()) {
+          if (status === 'start') {
+            this.$electron.ipcRenderer.send('download', {id: id, index: index})
+            info.start = false
+            info.resume = false
+            info.pause = true
+          } else if (status === 'resume') {
+            this.$electron.ipcRenderer.send(status, id)
+            info.pause = true
+            info.resume = false
+            info.start = false
+          }
+        }
+        if (status === 'pause') {
+          this.$electron.ipcRenderer.send(status, id)
+          info.pause = false
+          info.resume = true
+          info.start = false
+        }
+        this.downloads.splice(index, 1, info)
+        this.$db.read().get('sc_download').getById(id).assign(info).write()
+      },
+      deleteDownload (id, index) {
+        try {
+          let info = this.$db.read().get('sc_download').getById(id).value()
+          if (info.status === 5) {
+            if (info.start === false) {
+              this.$electron.ipcRenderer.send('cancel', id)
+              info.show = false
+              info.start = true
+              info.pause = false
+              info.resume = false
+            }
+            // 改变下一个数据的下载状态,没有则结束
+            let info2 = this.$db.read().get('sc_download').find({status: 1}).value()
+            if (info2 !== undefined) {
+              info2.show = true
+              info2.status = 5
+              this.$db.read().get('sc_download').getById(info2.id).assign(info2).write()
+              let index2 = this.downloads.findIndex(function (item) {
+                return item.id === info2.id
+              })
+              this.downloads.splice(index2, 1, info2)
+              // this.operationDownload('start', info2.id, index)
+            }
+          }
+          info.delete = true
+          info.status = 6
+          this.$db.read().get('sc_download').getById(id).assign(info).write()
+          this.downloads.splice(index, 1, info)
+          // this.$db.read().get('sc_download').removeById(id).write()
+          // this.downloads.splice(index, 1)
+          this.downloadNum = this.$db.read().get('sc_download').filter({delete: false}).size().value()
+          // fs.removeSync(info.savepath)
+        } catch (err) {
+          console.log(err)
+        }
+      },
+      adddownload (downID) {
+        if (this.checkOnline()) {
+          this.$refs.downloadmenu.show()
+          let softwarejson = path.join('data/sc_software.json')
+          let downlist = fs.readJsonSync(softwarejson).sc_down
+          let downinfo = downlist.find(function (item) {
+            return item.down_id === downID
+          })
+          delete downinfo.regedits
+          delete downinfo.versionDesc
+
+          downinfo.isfinish = false
+          downinfo.percent = 0
+          downinfo.savepath = downinfo.down_url.substring(downinfo.down_url.lastIndexOf('/') + 1)
+          downinfo.addtime = Date.parse(new Date()) / 1000
+          downinfo.show = false
+          downinfo.pause = false
+          downinfo.resume = false
+          downinfo.start = true
+          downinfo.delete = false
+          downinfo.status = 1
+          // 若产品不存在则插入到数据库sc_productData中,并隐藏
+          let productinfo = this.$db.read().get('sc_productData').find({ product_id: downinfo.product_id }).value()
+          if (productinfo === undefined) {
+            let productlist = fs.readJsonSync(softwarejson).sc_product
+            let scproductinfo = productlist.find(function (item) {
+              return item.product_id === downinfo.product_id
+            })
+            scproductinfo.addtime = Date.parse(new Date()) / 1000
+            scproductinfo.isshow = false
+            productinfo = this.$db.read().get('sc_productData').insert(scproductinfo).write()
+          }
+          downinfo.pid = productinfo.id
+          let info = this.$db.read().get('sc_download').insert(downinfo).write()
+          this.downloads.push(downinfo)
+          this.downloadNum = this.$db.read().get('sc_download').filter({delete: false}).size().value()
+          // 如果downloads中没有未下载的info,就让新增的info进入下载过程
+          let info2 = this.$db.read().get('sc_download').find({status: 5}).value()
+          let info3 = this.$db.read().get('sc_download').find({status: 1}).value()
+          if (info2 === undefined && info.id === info3.id) {
+            info.show = true
+            info.status = 5
+            this.$db.read().get('sc_download').getById(info.id).assign(info).write()
+            let index = this.downloads.findIndex(function (item) {
+              return item.id === info.id
+            })
+            this.operationDownload('start', info.id, index)
+          }
+        }
+      },
+      testbtn () {
+        // console.log('C:\\Program Files (x86)\\纵横软件\\纵横结算决算计量一体化软件(云版)')
+        // this.$electron.shell.openItem('C:\\Program Files (x86)\\纵横软件\\纵横结算决算计量一体化软件(云版)')
+        this.$electron.ipcRenderer.send('openWindow', 'https://yun.smartcost.com.cn/')
+        // this.$electron.shell.openItem('Measure:1184117328@qq.com')
+      },
+      installbtn (id, index) {
+        // 安装exe监测,5s后开始监听安装状态
+        let info = this.$db.read().get('sc_download').getById(id).value()
+        if (info.status === 2) {
+          // 判断启动器是否已安装本软件
+          let exesqlinfo = this.$db.read().get('sc_exeData').find({ regeditName: info.regedit, product_version: info.version }).value()
+          if (exesqlinfo !== undefined) {
+            this.$message({
+              message: `启动器已存在相同版本的 ${info.fulltitle} 软件`,
+              iconClass: '',
+              type: 'error'
+            })
+          } else {
+            this.$electron.shell.openItem(info.savepath)
+            let self = this
+            setTimeout(function () {
+              info.status = 3
+              self.$db.read().get('sc_download').getById(id).assign(info).write()
+              self.downloads.splice(index, 1, info)
+              self.watchExeInstall(index, info)
+            }, 5000)
+          }
+        } else {
+          this.$message({
+            message: info.fulltitle + ' 正在安装中,请稍后......',
+            iconClass: '',
+            type: 'warning'
+          })
+        }
+      },
+      watchExeInstall (index, info) {
+        // 监听安装程序目录,不存在则结束监听,进入判断安装是否完成or取消
+        let self = this
+        let interval = setInterval(function () {
+          let result = self.checkExeExist(info.savepath)
+          if (result === 0) {
+            clearInterval(interval)
+            // 判断是否完成还是取消了
+
+            self.$electron.ipcRenderer.send('checkExeInstall', { index: index, info: info })
+          }
+        }, 3000)
+      },
+      checkExeExist (exepath) {
+        try {
+          let ffifile = path.join('data/fileInfo.dll')
+          let libm = ffi.Library(ffifile, {
+            'FindProcess': ['int', ['string']]
+          })
+          let exerun = libm.FindProcess(exepath)
+          if (exerun === 0) {
+            return 0
+          }
+          return 1
+        } catch (err) {
+          console.error('ffi.Library', err)
+        }
+      }
+    }
+  }
+</script>

+ 36 - 0
src/renderer/components/StartUpPage/FirstOpen.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="first-open software-wrap p-3">
+    <div style="margin-top: 80px;">
+      <el-row type="flex" :gutter="30">
+        <el-col :span="5"></el-col>
+        <el-col :span="7">
+          <button size="medium" class="btn btn-orange btn-block" @click="openlist('software-list')"><i class="fas fa-download mr-2"></i>下载软件</button>
+        </el-col>
+        <el-col :span="7">
+          <button size="medium" class="btn btn-blue btn-block" @click="updateInstalledSoftware"><i class="fas fa-plus mr-2"></i>添加已安装软件</button>
+        </el-col>
+        <el-col :span="5"></el-col>
+      </el-row>
+    </div>
+  </div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  export default {
+    mixins: [mixin],
+    methods: {
+      openlist (index) {
+        this.$router.push({
+          name: index
+        })
+      },
+      updateInstalledSoftware () {
+        this.$router.push({
+          name: 'software-startup-detail',
+          params: {productid: '1'}
+        })
+      }
+    }
+  }
+</script>

+ 167 - 0
src/renderer/components/StartUpPage/SoftwareDetail.vue

@@ -0,0 +1,167 @@
+<template>
+  <div class="software-detail" v-loading="loading">
+    <div class="pt-3" style="padding-left: 2.5%">
+      <div class="media">
+        <img class="mr-3" :src="items.src" width="50" height="50" :alt="items.title">
+        <div class="media-body">
+          <h5 class="mt-0 mb-1">{{ items.title }}</h5>
+          {{ items.productName }}
+        </div>
+      </div>
+    </div>
+    <div class="pt-3">
+      <el-tabs v-model="activeName" type="card" @tab-click="checkOnline">
+        <el-tab-pane class="pl-3 software-content" name="first">
+          <span slot="label" class="detail-title"><span class="iconbg iconbg-purple rounded"><i class="fas fa-download"></i></span>下载软件</span>
+          <div class="details" v-for="down in downlist">
+            <div class="float-right">
+              <button @click="downloadSoftware(down.down_id, down.version)" class="btn btn-white btn-sm"><i class="fas fa-download mr-2"></i>下载</button>
+            </div>
+            <h5 class="w-25 d-inline-block">{{ down.title }}</h5>
+            <div class="w-50 d-inline-block"><span v-if="down.key_number !== ''">锁号:{{ down.key_number }}</span></div>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane class="pl-3 software-content" name="second" v-html="items.content">
+          <span slot="label" class="detail-title"><span class="iconbg iconbg-green rounded"><i class="fas fa-info"></i></span>软件详情</span>
+        </el-tab-pane>
+        <el-tab-pane class="pl-3 software-content" name="third">
+          <span slot="label" class="detail-title"><span class="iconbg iconbg-yellow rounded"><i class="fas fa-video"></i></span>动画教程</span>
+          <h1 class="vide-title" v-if="video1.length > 0">专属教程</h1>
+          <div class="row" v-if="video1.length > 0">
+            <div class="col-auto mb-4" v-for="video in video1" :key="video.aid">
+              <div class="card card-width">
+                <img class="card-img-top" :src="SmartCostUrl+video.img_url" :alt="video.title">
+                <div class="card-body p-3">
+                  <button class="btn btn-success btn-block btn-sm" @click="showVideo(video.aid)"><i class="fas fa-play-circle pr-1"></i>播放</button>
+                </div>
+              </div>
+            </div>
+          </div>
+          <h1 class="vide-title">通用教程</h1>
+          <div class="row">
+            <div class="col-auto mb-4" v-for="video in video2" :key="video.aid">
+              <div class="card card-width">
+                <img class="card-img-top" :src="SmartCostUrl+video.img_url" :alt="video.title">
+                <div class="card-body p-3">
+                  <button class="btn btn-success btn-block btn-sm" @click="showVideo(video.aid)"><i class="fas fa-play-circle pr-1"></i>播放</button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  const fs = require('fs-extra')
+  const path = require('path')
+  export default {
+    mixins: [mixin],
+    data () {
+      return {
+        activeName: 'first',
+        loading: false,
+        items: '',
+        downlist: '',
+        SmartCostUrl: 'https://smartcost.com.cn',
+        video1: '',
+        video2: ''
+      }
+    },
+    created () {
+      this.fetchData()
+    },
+    watch: {
+      'activeName': function (val) {
+        if (val === 'second') {
+          if (!this.checkOnline()) {
+            this.$message.error('当前网络不可用,图片信息将无法加载')
+          }
+        } else if (val === 'third') {
+          if (!this.checkOnline()) {
+            this.$message.error('当前网络不可用,无法获取视频信息')
+          } else {
+            let productid = this.$route.params.pid
+            let self2 = this
+            this.$http({
+              url: this.SmartCostUrl + '/animation/list?pid=' + productid,
+              method: 'get',
+              timeout: 5000
+            }).then(function (response) {
+              if (response.data.err === 0) {
+                self2.video1 = response.data.info.list1
+                self2.video2 = response.data.info.list2
+              } else {
+                this.$message.error(response.data.msg)
+              }
+            }).catch(function (error2) {
+              throw error2
+            })
+          }
+        }
+      }
+    },
+    methods: {
+      fetchData () {
+        this.loading = true
+        let softwarejson = path.join('data/sc_software.json')
+        let softwarelist = fs.readJsonSync(softwarejson).sc_product
+        let pid = this.$route.params.pid
+        let loadings = true
+        let items = softwarelist.find(function (item) {
+          if (item.product_id === pid) {
+            loadings = false
+          }
+          return item.product_id === pid
+        })
+        this.items = items
+        let downlist = fs.readJsonSync(softwarejson).sc_down
+        let newdownlist = downlist.filter(function (item) {
+          return item.product_id === pid
+        })
+        this.downlist = newdownlist
+        this.loading = loadings
+      },
+      downloadSoftware (downID, version) {
+        // 先判断是否已在下载列表中,再加入列表中
+        let downloaditem = this.$db.read().get('sc_download').find({ down_id: downID, version: version, delete: false }).value()
+        if (!downloaditem) {
+          this.$emit('softwareDownload', downID)
+        } else {
+          let info = this.downlist.find(function (item) {
+            return item.down_id === downID
+          })
+          this.$message({
+            showClose: true,
+            message: info.fulltitle + '已存在下载列中',
+            iconClass: '',
+            type: 'warning'
+          })
+        }
+      },
+      checkOnline () {
+        if (!navigator.onLine) {
+          return false
+        } else {
+          return true
+        }
+      },
+      showVideo (id) {
+        let url = this.SmartCostUrl + '/video/' + id
+        this.$electron.shell.openItem(url)
+      }
+    }
+  }
+
+  /**
+   * 原生方法打开v-html引入a标签的点击事件
+   * @type {Electron}
+   */
+  const electron = require('electron')
+  window.openURL = function (url) {
+    electron.shell.openExternal(url)
+  }
+</script>

+ 103 - 0
src/renderer/components/StartUpPage/SoftwareList.vue

@@ -0,0 +1,103 @@
+<template>
+  <div class="software-list pt-3">
+    <div class="freewrap">
+      <div class="free-title"><img src="../../assets/img/mainlogo.png" class="mr-2">纵横造价免费网络版</div>
+      <div class="">
+        <div class="media free-content">
+          <img class="mr-3" :src="getProductImg(7)" width="60" height="60" alt="公路造价(增值税)">
+          <div class="media-body clearfix">
+            <div class="free-right">
+              <button @click="downloadSoftware(9)" class="btn btn-white btn-sm"><i class="fas fa-download mr-2"></i>立即下载</button>
+            </div>
+            <div class="software-body">
+              <div class="software-bodyleft">
+                <h5 class="software-title mb-1">纵横公路工程造价管理系统</h5>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="media free-content">
+          <img class="mr-3" :src="getProductImg(8)" width="60" height="60" alt="公路造价(增值税)">
+          <div class="media-body clearfix">
+            <div class="free-right">
+              <button @click="downloadSoftware(313)" class="btn btn-white btn-sm"><i class="fas fa-download mr-2"></i>立即下载</button>
+            </div>
+            <div class="software-body">
+              <div class="software-bodyleft">
+                <h5 class="software-title mb-1">广东公路造价编审系统</h5>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <el-row type="flex" justify="center" v-for="software in softwarelist" :key="software.product_id">
+      <el-col :span="24" class="software-item">
+        <router-link :to="{ name: 'software-detail', params: { pid: software.product_id }}">
+          <div class="media">
+            <img class="mr-3" :src="software.src" width="40" height="40" :alt="software.title">
+            <div class="media-body clearfix">
+              <div class="float-right">
+                <button class="btn btn-white btn-sm mt-2"><i class="fas fa-align-left mr-2"></i>详情</button>
+              </div>
+              <div class="software-body">
+                <div class="float-right">
+                  <div class="software-version">更新时间:{{ software.updateTime }}</div>
+                </div>
+                <div class="software-bodyleft">
+                  <h5 class="software-title mb-1">{{ software.title }}</h5>
+                  <div class="software-stitle">{{ software.productName }}</div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </router-link>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  const fs = require('fs-extra')
+  const path = require('path')
+  export default {
+    mixins: [mixin],
+    data () {
+      return {
+        softwarelist: ''
+      }
+    },
+    created () {
+      let softwarejson = path.join('data/sc_software.json')
+      this.softwarelist = fs.readJsonSync(softwarejson).sc_product
+    },
+    methods: {
+      getProductImg (pid) {
+        let pinfo = this.softwarelist.find(function (item) {
+          return item.product_id === pid
+        })
+        return pinfo.src
+      },
+      downloadSoftware (downID) {
+        // 先判断是否已在下载列表中,再加入列表中
+        let softwarejson = path.join('data/sc_software.json')
+        let downlist = fs.readJsonSync(softwarejson).sc_down
+        let info = downlist.find(function (item) {
+          return item.down_id === downID
+        })
+        let downloaditem = this.$db.read().get('sc_download').find({ down_id: downID, version: info.version, delete: false }).value()
+        if (!downloaditem) {
+          this.$emit('softwareDownload', downID)
+        } else {
+          this.$message({
+            showClose: true,
+            message: info.fulltitle + '已存在下载列中',
+            iconClass: '',
+            type: 'warning'
+          })
+        }
+      }
+    }
+  }
+</script>

+ 181 - 0
src/renderer/components/StartUpPage/SoftwareStartup.vue

@@ -0,0 +1,181 @@
+<template>
+  <div ref="startup" class="software-startup software-wrap" v-loading="loading" element-loading-text="正在获取本地数据中,请稍候...">
+    <div class="software-left">
+      <!--<div class="open-left">-->
+        <!--<span><i class="fas fa-chevron-left"></i></span>-->
+      <!--</div>-->
+      <div class="local-software">
+        <router-link v-for="product in productlist" :key="product.id" :to="{ name: 'software-startup-detail', params: { productid: product.id } }">
+        <div class="software media" :class="[products.id === product.id ? 'active' : '']">
+          <img class="mr-2" :src="product.src" width="25" height="25" :alt="product.title">
+          <div class="media-body left-media-body">
+            <div class="mt-1 mb-1"><router-link :to="{ name: 'software-startup-detail', params: { productid: product.id } }">{{ product.title }}</router-link></div>
+            <span class="software-num rounded">{{ product.pnum }}</span>
+          </div>
+        </div>
+        </router-link>
+      </div>
+      <div class="software-left-bottom">
+        <div class="d-flex">
+          <div class="p-2 flex-fill">
+            <button style="width:100%;" class="btn btn-orange btn-sm" @click="openlist('software-list')">下载中心</button>
+          </div>
+          <div class="p-2 flex-fill dropup">
+            <el-dropdown placement="top" trigger="click" style="width:100%;" @command="clickbtn">
+              <button style="width:100%;" class="btn btn-blue btn-sm">
+                添加软件<i class="el-icon-arrow-up el-icon--right"></i>
+              </button>
+              <el-dropdown-menu class="command-menu" slot="dropdown">
+                <el-dropdown-item class="command-item" command="auto">自动获取</el-dropdown-item>
+                <el-dropdown-item class="command-item" command="manual">手动添加</el-dropdown-item>
+              </el-dropdown-menu>
+            </el-dropdown>
+          </div>
+        </div>
+      </div>
+    </div>
+    <software-startup-detail ref="startupDetail"></software-startup-detail>
+  </div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  import SoftwareStartupDetail from './SoftwareStartupDetail'
+  export default {
+    mixins: [mixin],
+    components: { SoftwareStartupDetail },
+    data () {
+      return {
+        activeName: 'first',
+        productlist: '',
+        products: '',
+        loading: false
+      }
+    },
+    created () {
+      this.checkfirst()
+      this.fetchData()
+      let self = this
+      this.$electron.ipcRenderer.on('successUpdate', (event, msg) => {
+        try {
+          self.loading = false
+          let num = msg.num
+          let delnum = msg.delnum
+          this.$electron.ipcRenderer.send('testUsb')
+          if (num !== 0 && delnum !== 0) {
+            self.$message({
+              message: `新增了 ${num} 个纵横软件`,
+              type: 'success'
+            })
+            setTimeout(function () {
+              self.$message({
+                message: `移除了 ${delnum} 个纵横软件`,
+                type: 'error'
+              })
+            }, 2000)
+          } else {
+            if (delnum !== 0) {
+              self.$message({
+                message: `移除了 ${delnum} 个纵横软件`,
+                type: 'error'
+              })
+            }
+
+            if (num !== 0) {
+              self.$message({
+                message: `新增了 ${num} 个纵横软件`,
+                type: 'success'
+              })
+            }
+            if (num === 0 && delnum === 0) {
+              self.$message.info('更新没有获取到新的纵横软件')
+            }
+          }
+          let id = msg.id
+          // 如果不跳转出本产品,则让downlist 每次都执行更新一遍
+          if (self.$route.params.productid === id) {
+            self.fetchData()
+            if (self.$refs.startupDetail !== undefined) {
+              self.$refs.startupDetail.fetchData()
+            }
+            self.$emit('softwareUpdate')
+          } else {
+            self.$router.push({
+              name: 'software-startup-detail',
+              params: {productid: id}
+            })
+          }
+        } catch (err) {
+          console.log(err)
+        }
+      })
+
+      this.$electron.ipcRenderer.on('failedUpdate', (event, msg) => {
+        try {
+          self.loading = false
+          self.$message.info('你的电脑还没安装过纵横软件')
+          console.log(msg.error)
+          let id = msg.id
+          this.$electron.ipcRenderer.send('testUsb')
+          if (self.$route.params.productid === id && msg.error !== 200) {
+            self.fetchData()
+            if (self.$refs.startupDetail !== undefined) {
+              self.$refs.startupDetail.fetchData()
+            }
+          } else {
+            self.$router.push({
+              name: 'software-startup-detail',
+              params: { productid: id }
+            })
+          }
+        } catch (err) {
+          console.log(err)
+        }
+      })
+
+      this.$electron.ipcRenderer.on('failedDirectory', (event, result) => {
+        self.loading = false
+        if (result.msg !== '') {
+          self.$message({
+            message: result.msg,
+            type: 'error'
+          })
+        }
+      })
+    },
+    watch: {
+      // 如果路由有变化,会再次执行该方法
+      '$route': 'fetchData'
+    },
+    methods: {
+      fetchData () {
+        let pid = this.$route.params.productid === undefined ? this.$db.read().get('sc_productData').last().value().id : this.$route.params.productid
+        this.products = this.$db.read().get('sc_productData').getById(pid).value()
+        let plist = this.$db.read().get('sc_productData').filter({ isshow: true }).orderBy(['orders', 'product_id'], ['asc', 'asc']).value()
+        for (let i in plist) {
+          plist[i]['pnum'] = this.$db.read().get('sc_exeData').filter({ pid: plist[i].id }).size().value()
+        }
+        this.productlist = plist
+      },
+      checkfirst () {
+        let first = this.$db.read().get('sc_hadInstall.first').value()
+        if (first) {
+          this.clickbtn('auto')
+        }
+      },
+      openlist (index) {
+        this.$router.push({
+          name: index
+        })
+      },
+      clickbtn (command) {
+        this.loading = true
+        if (command === 'auto') {
+          this.$electron.ipcRenderer.send('updateInstall')
+        } else {
+          this.$electron.ipcRenderer.send('file-select')
+        }
+      }
+    }
+  }
+</script>

+ 244 - 0
src/renderer/components/StartUpPage/SoftwareStartupDetail.vue

@@ -0,0 +1,244 @@
+<template>
+  <div class="software-startup-detail">
+    <div class="software-detail-top pt-3" style="padding-left: 2.5%">
+      <div class="media">
+        <img class="mr-3" :src="products.src" width="50" height="50" :alt="products.title">
+        <div class="media-body">
+          <h5 class="mt-0 mb-1">{{ products.title }}</h5>
+          {{ products.productName }}&nbsp;
+        </div>
+      </div>
+    </div>
+    <div class="pt-3">
+      <el-tabs v-model="activeName" type="card">
+        <el-tab-pane class="software-content" name="first">
+          <span slot="label" class="detail-title"><span class="iconbg iconbg-purple rounded"><i class="fas fa-download"></i></span>启动软件</span>
+          <div class="details" v-for="exe in exelist" :key="exe.id">
+            <div class="float-right">
+              <button class="btn btn-white btn-sm" @click="openExebtn(exe.id)"><i class="fas fa-play mr-2"></i>启动</button>
+            </div>
+            <div class="details-title">
+              <div class="w-25 d-inline-block" v-if="exe.versionName !== ''"><a @click="openFolder(exe.path)" :title="exe.path">{{ exe.versionName }}</a></div>
+              <div v-else><a @click="openFolder(exe.path)" :title="exe.path">{{ exe.fileName }}</a></div>
+              <div class="d-inline-block" v-if="exe.keyNumber !== ''">锁号:{{ exe.keyNumber }}</div>
+            </div>
+            <div class="w-25 d-inline-block">{{ exe.fileVersion }}</div>
+            <div class="inline-block">{{ exe.fileDescription }}</div>
+          </div>
+        </el-tab-pane>
+        <el-tab-pane v-if="products.id != 1" class="software-content" name="second" v-html="products.content">
+          <span slot="label" class="detail-title"><span class="iconbg iconbg-green rounded"><i class="fas fa-info"></i></span>软件详情</span>
+        </el-tab-pane>
+        <el-tab-pane v-if="products.id != 1" class="software-content" name="third">
+          <span slot="label" class="detail-title"><span class="iconbg iconbg-yellow rounded"><i class="fas fa-video"></i></span>动画教程</span>
+          <h1 class="vide-title" v-if="video1.length > 0">专属教程</h1>
+          <div class="row" v-if="video1.length > 0">
+            <div class="col-auto mb-4" v-for="video in video1" :key="video.aid">
+              <div class="card card-width">
+                <img class="card-img-top" :src="SmartCostUrl+video.img_url" :alt="video.title">
+                <div class="card-body p-3">
+                  <button class="btn btn-success btn-block btn-sm" @click="showVideo(video.aid)"><i class="fas fa-play-circle pr-1"></i>播放</button>
+                </div>
+              </div>
+            </div>
+          </div>
+          <h1 class="vide-title">通用教程</h1>
+          <div class="row">
+            <div class="col-auto mb-4" v-for="video in video2" :key="video.aid">
+              <div class="card card-width">
+                <img class="card-img-top" :src="SmartCostUrl+video.img_url" :alt="video.title">
+                <div class="card-body p-3">
+                  <button class="btn btn-success btn-block btn-sm" @click="showVideo(video.aid)"><i class="fas fa-play-circle pr-1"></i>播放</button>
+                </div>
+              </div>
+            </div>
+          </div>
+          <!--<div class="row">-->
+            <!--&lt;!&ndash;<iframe class="col-12" scrolling=auto src="http://ol.smartcost.com.cn/loginbanner.html" frameborder="0"></iframe>&ndash;&gt;-->
+            <!--<webview id="banner" style="width:100%; height:360px" src="http://ol.smartcost.com.cn/loginbanner.html"></webview>-->
+          <!--</div>-->
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  const path = require('path')
+  const fs = require('fs')
+  const ffi = require('ffi')
+  export default {
+    mixins: [mixin],
+    data () {
+      return {
+        activeName: 'first',
+        products: '',
+        exelist: '',
+        SmartCostUrl: 'https://smartcost.com.cn',
+        video1: '',
+        video2: ''
+      }
+    },
+    created () {
+      this.fetchData()
+    },
+    //  mounted () {
+    //    this.$nextTick(() => {
+    //      document.getElementById('banner').addEventListener('new-window', (e) => {
+    //        const protocol = require('url').parse(e.url).protocol
+    //        if (protocol === 'http:' || protocol === 'https:') {
+    //          //      electron.shell.openExternal(e.url)
+    //          this.$electron.ipcRenderer.send('openWindow', e.url)
+    //          //      window.open(e.url)
+    //        }
+    //      })
+    //    })
+    //  },
+    watch: {
+      // 如果路由有变化,会再次执行该方法
+      '$route': 'fetchData',
+      'activeName': function (val) {
+        if (val === 'second') {
+          if (!this.checkOnline()) {
+            this.$message.error('当前网络不可用,图片信息将无法加载')
+          }
+        } else if (val === 'third') {
+          if (!this.checkOnline()) {
+            this.$message.error('当前网络不可用,无法获取视频信息')
+          } else {
+            let productid = this.$route.params.productid === undefined ? this.$db.read().get('sc_productData').last().value().id : this.$route.params.productid
+            let productInfo = this.$db.read().get('sc_productData').getById(productid).value()
+            let self2 = this
+            this.$http({
+              url: this.SmartCostUrl + '/animation/list?pid=' + productInfo.product_id,
+              method: 'get',
+              timeout: 5000
+            }).then(function (response) {
+              if (response.data.err === 0) {
+                self2.video1 = response.data.info.list1
+                self2.video2 = response.data.info.list2
+              } else {
+                this.$message.error(response.data.msg)
+              }
+            }).catch(function (error2) {
+              throw error2
+            })
+          }
+        }
+      }
+    },
+    methods: {
+      fetchData () {
+        let productid = this.$route.params.productid === undefined ? this.$db.read().get('sc_productData').last().value().id : this.$route.params.productid
+        this.$db.read().set('sc_hadInstall.url', '/softwarestartup/' + productid).write()
+        this.products = this.$db.read().get('sc_productData').getById(productid).value()
+        this.exelist = this.$db.read().get('sc_exeData').filter({ pid: productid }).orderBy('addtime', 'desc').value()
+        this.activeName = 'first'
+      },
+      openExebtn (id) {
+        let info = this.$db.read().get('sc_exeData').getById(id).value()
+        let file = fs.existsSync(path.join(info.path, info.exeName))
+        let self = this
+        if (file) {
+          // 还要检测软件是否升级了,升级了则要替换exeData
+          let exeVersion = self.getExeVersion(path.join(info.path, info.exeName))
+          if (exeVersion !== info.fileVersion) {
+            self.$confirm('该软件版本已升级,是否更新启动器数据?', '提示', {
+              confirmButtonText: '确定',
+              cancelButtonText: '取消',
+              type: 'error'
+            }).then(() => {
+              self.$parent.clickbtn('auto')
+            }).catch(() => {
+            })
+          } else {
+            // 判断是否是网络版注册表方式登录版本,传值登录
+            let openFlag = true
+            let openLink = ''
+            if (info.regStart !== undefined && info.regStart) {
+              let userMsg = self.$db.read().get('sc_SsoUser').value()
+              if (userMsg === undefined || userMsg === '') {
+                self.$message.info('请登录通行账号再打开软件')
+                openFlag = false
+                self.$parent.$parent.$parent.$children[0].$children[3].$refs.usermenu.show()
+              } else if (userMsg !== undefined && userMsg.onlineFlag !== 1) {
+                self.$message.info('请先开通网络版账号')
+                openFlag = false
+                self.$parent.$parent.$parent.$children[0].$children[3].$refs.usermenu.show()
+              } else {
+                // console.log(info.regStart + ':' + userMsg.token)
+                openLink = info.regStart + ':' + userMsg.token
+                // openLink = info.regStart + ':' + '1184117328@qq.com'
+              }
+            }
+            if (openFlag) {
+              self.$message.success('正在启动软件...')
+              setTimeout(function () {
+                if (openLink !== '') {
+                  self.$electron.shell.openItem(openLink)
+                } else {
+                  self.$electron.shell.openItem(path.join(info.path, info.exeName))
+                }
+              }, 500)
+            }
+          }
+        } else {
+          self.$confirm('该软件已卸载或路径不对了,是否移除?', '提示', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: 'error'
+          }).then(() => {
+            self.$db.read().get('sc_exeData').removeById(id).write()
+            // 查找是否还存在其他产品软件,否则隐藏左边产品栏产品
+            let info2 = self.$db.read().get('sc_exeData').find({ pid: info.pid }).value()
+            if (info2 === undefined) {
+              self.$db.read().get('sc_productData').updateById(info.pid, { isshow: false }).write()
+            }
+            self.fetchData()
+            self.$message({
+              type: 'success',
+              message: '删除成功!'
+            })
+          }).catch(() => {
+          })
+        }
+      },
+      checkOnline () {
+        if (!navigator.onLine) {
+          return false
+        } else {
+          return true
+        }
+      },
+      getExeVersion (exepath) {
+        try {
+          let ffifile = path.join('data/fileInfo.dll')
+          let libm = ffi.Library(ffifile, {
+            'GetFileInfo': ['string', ['string']]
+          })
+          let fileinfo = JSON.parse(libm.GetFileInfo(exepath))
+          return fileinfo.FileVersion
+        } catch (err) {
+          console.error('ffi.Library', err)
+        }
+      },
+      openFolder (path) {
+        this.$electron.shell.openItem(path)
+      },
+      showVideo (id) {
+        let url = this.SmartCostUrl + '/video/' + id
+        this.$electron.shell.openItem(url)
+      }
+    }
+  }
+
+  /**
+   * 原生方法打开v-html引入a标签的点击事件
+   * @type {Electron}
+   */
+  const electron = require('electron')
+  window.openURL = function (url) {
+    electron.shell.openExternal(url)
+  }
+</script>

+ 67 - 0
src/renderer/components/StartUpPage/SoftwareUpdate.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="software-update">
+    <el-dialog
+            title="正在下载新版本,请稍候......"
+            :visible.sync="centerDialogVisible"
+            width="50%"
+            :show-close=false
+            :close-on-click-modal=false
+            :close-on-press-escape=false
+            >
+      <div class="modal-body" style="text-align: center;">
+        <el-progress :percentage="uploadPercent"></el-progress>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  export default {
+    mixins: [mixin],
+    created () {
+      this.$electron.ipcRenderer.send('checkForUpdate')
+      this.$electron.ipcRenderer.on('msgBox', (event, status) => {
+        if (status === 2) {
+          this.openUpdateDialog()
+        }
+      })
+
+      this.$electron.ipcRenderer.on('isUpdateNow', function (event, index) {
+        event.sender.send('isUpdateNow')
+      })
+    },
+    data () {
+      return {
+        uploadPercent: 0,
+        centerDialogVisible: false
+      }
+    },
+    methods: {
+      openUpdateDialog () {
+        this.$confirm('1.添加版本更新功能<br>' +
+          '2:修复部分已知bug<br>' +
+          '3:内容页样式更新', '版本更新提示', {
+          confirmButtonText: '马上更新',
+          cancelButtonText: '稍后再说',
+          center: true,
+          showClose: false,
+          closeOnClickModal: false,
+          dangerouslyUseHTMLString: true
+        }).then(() => {
+          setTimeout(() => {
+            this.ProcessDialog()
+          }, 200)
+        }).catch(() => {
+        })
+      },
+      ProcessDialog () {
+        this.$electron.ipcRenderer.send('downloadUpdate')
+        this.centerDialogVisible = true
+        this.$electron.ipcRenderer.on('downloadProgress', (event, progressObj) => {
+          this.uploadPercent = parseInt(progressObj.percent)
+        })
+      }
+    }
+  }
+</script>

+ 128 - 0
src/renderer/components/StartUpPage/UpdateHeader.vue

@@ -0,0 +1,128 @@
+<template>
+  <el-dropdown ref="updatemenu" class="update-header float-left mr-3" trigger="click" :hide-on-click=false>
+    <span class="el-dropdown-link">
+      <span :class="[badge, updateNum !== 0 ? badgeLight : '']">
+        <i class="fas fa-arrow-up" :class="updateNum !== 0 ? textSecondary : ''"></i><span v-show="updateNum !== 0" class="text-secondary"> {{ updateNum }}</span></span>
+    </span>
+    <el-dropdown-menu slot="dropdown" class="updatelist">
+      <div v-if="updateNum === 0">
+        没有可更新软件
+      </div>
+      <div class="downloaditem" v-for="item in updates" :key="item.id">
+        <div class="pb-2">
+          <div class="float-right"><button class="btn btn-success btn-sm ml-2" @click="downloadbtn(item.down_id, item.show_updateVersion)">更新</button></div>
+          <div>{{ item.simpleName }} {{ item.versionName }}</div>
+          <div class="text-secondary">当前版本:{{ item.product_version }}&nbsp;&nbsp;  |  &nbsp;&nbsp;升级版本:{{ item.show_updateVersion }}</div>
+        </div>
+      </div>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  const path = require('path')
+  const fse = require('fs-extra')
+  export default {
+    mixins: [mixin],
+    data: () => ({
+      updates: '',
+      updateNum: 0,
+      badge: 'badge',
+      badgeLight: 'badge-light',
+      textSecondary: 'text-secondary'
+    }),
+    created () {
+      this.fetchData()
+    },
+    methods: {
+      fetchData () {
+        let exelist = this.$db.read().get('sc_exeData').orderBy('addtime', 'desc').value()
+        let softwarejson = path.join('data/sc_software.json')
+        let scdownlist = fse.readJsonSync(softwarejson).sc_down
+        let notUpdateDownIDList = [] // 获取不需要更新的down_id
+        let updateExeList = [] // 获取需要更新的down_id
+        this.$db.read().get('sc_exeData').updateWhere({ show_tip: true }, { show_tip: false }).write()
+        for (let i = 0; i < exelist.length; i++) {
+          let scdownInfo = scdownlist.find(function (item) {
+            return item.down_id === exelist[i].down_id
+          })
+          // 先过滤大部分不用更新或已经检测的down_id
+          if (!this.inArray(exelist[i].down_id, notUpdateDownIDList)) {
+            if (scdownInfo !== undefined && scdownInfo.version === exelist[i].product_version) {
+              notUpdateDownIDList.push(exelist[i].down_id)
+            } else if (scdownInfo !== undefined && scdownInfo.version !== exelist[i].product_version && exelist[i].exeName !== 'BillsEditor.exe') {
+              exelist[i].show_updateVersion = scdownInfo.version
+              updateExeList.push(exelist[i])
+            }
+          }
+          // updateExeList 找出down_id相同的一个最高版本提示更新
+          let updateExeMsgList = []
+          for (let exe of updateExeList) {
+            if (!this.inArray(exe.down_id, notUpdateDownIDList) && exe.exeName !== 'BillsEditor.exe') {
+              let version = exe.product_version.split('.').join('')
+              let exeMsgIndex = updateExeMsgList.findIndex(function (item) {
+                return item.down_id === exe.down_id
+              })
+              if (!exeMsgIndex || exeMsgIndex === -1) {
+                let updateInfo = {
+                  down_id: exe.down_id,
+                  version: version,
+                  id: exe.id,
+                  updateVersion: exe.show_updateVersion
+                }
+                updateExeMsgList.push(updateInfo)
+              } else if (version > updateExeMsgList[exeMsgIndex].version.split('.').join('')) {
+                let updateInfo = {
+                  down_id: exe.down_id,
+                  version: version,
+                  id: exe.id,
+                  updateVersion: exe.show_updateVersion
+                }
+                updateExeMsgList.splice(exeMsgIndex, 1, updateInfo)
+              }
+            }
+          }
+          for (let exemsg of updateExeMsgList) {
+            this.$db.read().get('sc_exeData').updateById(exemsg.id, { show_updateVersion: exemsg.updateVersion, show_tip: true }).write()
+          }
+          // 再把所有notUpdateDownIDList 里down_id相同的软件进行更新提示消除
+          for (let notUpdateDownID of notUpdateDownIDList) {
+            this.$db.read().get('sc_exeData').updateWhere({ down_id: notUpdateDownID }, { show_tip: false }).write()
+            this.$db.read().get('sc_exeData').updateWhere({ exeName: 'BillsEditor.exe' }, { show_tip: false }).write()
+          }
+        }
+        this.updateNum = this.$db.read().get('sc_exeData').filter({ show_tip: true }).size().value()
+        this.updates = this.$db.read().get('sc_exeData').filter({ show_tip: true }).value()
+      },
+      downloadbtn (id, version) {
+        let downloaditem = this.$db.read().get('sc_download').find({ down_id: id, version: version, delete: false }).value()
+        if (!downloaditem) {
+          this.$refs.updatemenu.hide()
+          console.log(id)
+          this.$emit('softwareDownload', id)
+        } else {
+          let softwarejson = path.join('data/sc_software.json')
+          let scdownlist = fse.readJsonSync(softwarejson).sc_down
+          let scdowninfo = scdownlist.find(function (item) {
+            return item.down_id === id
+          })
+          this.$message({
+            showClose: true,
+            message: scdowninfo.fulltitle + '已存在下载列中',
+            iconClass: '',
+            type: 'warning'
+          })
+        }
+      },
+      inArray (val, arr) {
+        for (let i in arr) {
+          if (arr[i] === val) {
+            return true
+          }
+        }
+        return false
+      }
+    }
+  }
+</script>

+ 195 - 0
src/renderer/components/StartUpPage/UsbHeader.vue

@@ -0,0 +1,195 @@
+<template>
+  <el-dropdown ref="usbmenu" v-if="usbshow && !first2" class="usb-header float-left mr-3" trigger="click" :hide-on-click=false>
+    <span class="el-dropdown-link">
+      <span :class="[badge, usbNum !== 0 ? badgeLight : '']">
+        <i class="fab fa-usb" :class="usbNum !== 0 ? textSecondary : ''"></i><span v-show="usbNum !== 0" class="text-secondary"> {{ usbNum }}</span>
+      </span>
+    </span>
+    <el-dropdown-menu slot="dropdown" class="usblist">
+      <div class="downloading" v-for="lock in lockList">
+        <div class="border-bottom">
+          <span class="float-right" v-if="lock.date !== ''">期限: {{ lock.date }}</span>
+          <h5>{{ lock.name }}</h5>
+        </div>
+        <div class="pt-3 pb-2" v-for="product in lock.prolist">
+          <div class="float-right">
+            <button class="btn btn-success btn-sm" v-if="product.status === 1" @click="openProduct(product.startpath)"><i class="fas fa-play mr-2"></i>启动</button>
+            <button class="btn btn-orange btn-sm" v-else-if="product.status === 2" @click="downloadProduct(product.down_id, product.version)"><i class="fas fa-download mr-2"></i>下载</button>
+          </div>
+          {{ product.title }}&nbsp;<span class="text-orange">{{ product.version1 }}</span>&nbsp;<span class="software-stitle">{{ product.version2 }}</span>
+        </div>
+      </div>
+    </el-dropdown-menu>
+  </el-dropdown>
+  <div class="usb-header float-left mr-3" v-else-if="usbshow && first2" @click="openconfirm()"><i class="fab fa-usb"></i></div>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  const fs = require('fs-extra')
+  const path = require('path')
+  export default {
+    mixins: [mixin],
+    data: () => ({
+      usbNum: 0,
+      usbshow: false,
+      lockList: '',
+      first2: false,
+      badge: 'badge',
+      badgeLight: 'badge-light',
+      textSecondary: 'text-secondary'
+    }),
+    created () {
+      this.$electron.ipcRenderer.send('testUsb')
+      this.$electron.ipcRenderer.on('usbIn', async (event, result) => {
+        this.usbNum = 0
+        this.usbshow = true
+        if (result.first === false) {
+          this.first2 = false
+          let alllist = result.lockmsg
+          let productlist = alllist.split('|')
+          let locklist = []
+          let softwarejson = path.join('data/sc_software.json')
+          let downlist = fs.readJsonSync(softwarejson).sc_down
+          let exelist = await this.$db.read().get('sc_exeData').value()
+          // 锁号先关联已安装软件列表提供启动,若不存在则再关联官网软件列表提供下载
+          for (let i in productlist) {
+            let productNum = productlist[i].split(':')[0]
+            let productVerList = productlist[i].split(':')[1].split(';')
+            let productMsg = await this.getkeynum(productNum)
+            let data = {
+              name: productMsg[0] !== undefined ? productMsg[0] : productNum,
+              date: productMsg[1] !== undefined && productMsg[1] !== '' ? productMsg[1] : ''
+            }
+            let prolist = []
+            for (let j in productVerList) {
+              let pdata
+              let flag = true
+              for (let z in exelist) {
+                if (exelist[z].keytype !== '' && this.in_array(productVerList[j], exelist[z].keytype.split(','))) {
+                  pdata = {
+                    title: exelist[z].simpleName,
+                    version1: exelist[z].versionName,
+                    version2: exelist[z].fileDescription,
+                    status: 1,
+                    startpath: path.join(exelist[z].path, exelist[z].exeName)
+                  }
+                  flag = false
+                  prolist.push(pdata)
+                  this.usbNum++
+                }
+              }
+              if (flag) {
+                for (let z in downlist) {
+                  if (this.in_array(productVerList[j], downlist[z].keytype.split(','))) {
+                    pdata = {
+                      title: downlist[z].product_title,
+                      version1: downlist[z].title,
+                      version2: '',
+                      status: 2,
+                      down_id: downlist[z].down_id,
+                      version: downlist[z].version
+                    }
+                    flag = false
+                    prolist.push(pdata)
+                    this.usbNum++
+                    break
+                  }
+                }
+              }
+            }
+            data.prolist = prolist
+            locklist.push(data)
+          }
+          this.lockList = locklist
+          if (this.usbNum > 99) {
+            this.usbNum = '99+'
+          }
+          if (this.$refs.usbmenu !== undefined && result.usbIn === true) {
+            this.$refs.usbmenu.show()
+          }
+        } else {
+          this.first2 = true
+          this.openconfirm()
+        }
+      })
+      this.$electron.ipcRenderer.on('usbOut', (event) => {
+        this.usbshow = false
+      })
+    },
+    methods: {
+      in_array (val, arr) {
+        for (let i in arr) {
+          if (arr[i] === val) {
+            return true
+          }
+        }
+        return false
+      },
+      openProduct (item) {
+        this.$electron.shell.openItem(item)
+      },
+      downloadProduct (downID, version) {
+        // 先判断是否已在下载列表中,再加入列表中
+        let downloaditem = this.$db.read().get('sc_download').find({ down_id: downID, version: version, delete: false }).value()
+        if (!downloaditem) {
+          this.$refs.usbmenu.hide()
+          this.$emit('softwareDownload', downID)
+        } else {
+          this.$message({
+            showClose: true,
+            message: downloaditem.fulltitle + '已存在下载列中',
+            iconClass: '',
+            type: 'warning'
+          })
+        }
+      },
+      openconfirm () {
+        if (this.first2) {
+          this.$confirm('先检测本地安装软件才可以读取软件锁信息,是否添加本地已安装软件的信息?', '软件锁', {
+            confirmButtonText: '确定',
+            cancelButtonText: '取消',
+            type: ''
+          }).then(() => {
+            this.$router.push({
+              name: 'software-startup-detail',
+              params: { productid: '1' }
+            })
+          }).catch(() => {
+          })
+        }
+      },
+      getkeynum (num) {
+        if (this.checkOnline()) {
+          return new Promise((resolve, reject) => {
+            this.$http({
+              url: 'http://cld.smartcost.com.cn/api/startup/keynum?num=' + num,
+              method: 'get',
+              timeout: 2000,
+              headers: {
+                'Access-Control-Allow-Origin': '*'
+              }
+            }).then(function (response) {
+              if (response.data.error === 0) {
+                resolve([response.data.info, response.data.alloted_time])
+              } else {
+                resolve([num, ''])
+              }
+            }).catch(function () {
+              reject(num)
+            })
+          })
+        } else {
+          return num
+        }
+      },
+      checkOnline () {
+        if (!navigator.onLine) {
+          return false
+        } else {
+          return true
+        }
+      }
+    }
+  }
+</script>

+ 451 - 0
src/renderer/components/StartUpPage/UserHeader.vue

@@ -0,0 +1,451 @@
+<template>
+  <el-dropdown ref="usermenu" class="user-header float-left mr-3" trigger="click" :hide-on-click=false>
+    <span class="el-dropdown-link">
+      <span class="badge" v-if="loginPage">
+        <img class="head-img" src="../../assets/img/headimg.png"> 登录
+      </span>
+      <span class="badge" v-else>
+        Hi <img class="head-img" :src="userHead">
+      </span>
+    </span>
+    <el-dropdown-menu slot="dropdown" class="userlist">
+      <div class="user-dropdown-menu" v-if="loginPage">
+        <div class="form-group">
+          <!-- <label for="tel">邮箱/手机</label> -->
+          <el-input type="text" v-model="username" id="username" placeholder="通行账号 邮箱 或 手机" clearable size="small" v-on:keyup="checkKeyCode()" v-on:keydown="checkKeyCode()"></el-input>
+        </div>
+        <div class="form-group">
+          <!-- <label for="password">密码</label> -->
+          <el-input type="password" v-model="password" id="password" placeholder="密码" clearable size="small" v-on:keyup="checkKeyCode()" v-on:keydown="checkKeyCode()"></el-input>
+        </div>
+        <button class="btn btn-blue btn-sm mr-4 btn-block" @click="loginButton()">登录</button>
+        <div class="alert alert-danger alert-sm mt-3" role="alert" v-show="fail">{{ failMsg }}</div>
+        <div class="row mt-2 mb-3">
+          <div class="col text-left"><a @click="goSsoUrl('/getpasswd')" class="nomal-a">忘记密码</a></div>
+          <div class="col text-right"><a @click="goSsoUrl('/reg')" class="nomal-a">注册通行帐号</a></div>
+        </div>
+      </div>
+      <div class="user-dropdown-menu" v-else>
+        <div class="">
+          <button class="btn btn-primary btn-sm btn-block" v-if="onlinePage" @click="openDialog(onlinePage)">免费开通纵横网络版</button>
+          <a class="dropdown-item" v-else @click="openDialog(onlinePage)">已开通纵横网络版</a>
+          <el-dialog
+                  title="免费网络版"
+                  :visible.sync="dialogVisible"
+                  width="43%"
+                  top="4vh"
+                  :close-on-click-modal="false"
+                  :close-on-press-escape="false"
+                  append-to-body>
+            <div class="modal-body" style="max-height: 378px">
+              <div class="text-right" v-if="!onlinePage && !editPage"><a class="nomal-a" @click="changeEdit()">修改信息</a></div>
+              <el-form ref="form" :rules="rules" :model="form" label-width="110px" label-position="left" class="form-set" v-if="editPage || onlinePage">
+                <el-form-item label="真实姓名" prop="name">
+                  <el-input v-model="form.name" clearable size="mini"></el-input>
+                </el-form-item>
+                <el-form-item label="手机号码" prop="mobile">
+                  <el-input type="age" v-model.number="form.mobile" clearable size="mini"></el-input>
+                </el-form-item>
+                <el-form-item label="单位(公司)名称" prop="company">
+                  <el-input v-model="form.company" clearable size="mini"></el-input>
+                </el-form-item>
+                <el-form-item label="联系地址" required>
+                  <el-col :span="6">
+                    <el-form-item prop="province">
+                      <el-select v-model="form.province" placeholder="省份" size="mini">
+                        <el-option label="北京" value="北京"></el-option>
+                        <el-option label="天津" value="天津"></el-option>
+                        <el-option label="河北" value="河北"></el-option>
+                        <el-option label="山西" value="山西"></el-option>
+                        <el-option label="辽宁" value="辽宁"></el-option>
+                        <el-option label="吉林" value="吉林"></el-option>
+                        <el-option label="黑龙江" value="黑龙江"></el-option>
+                        <el-option label="上海" value="上海"></el-option>
+                        <el-option label="江苏" value="江苏"></el-option>
+                        <el-option label="浙江" value="浙江"></el-option>
+                        <el-option label="安徽" value="安徽"></el-option>
+                        <el-option label="福建" value="福建"></el-option>
+                        <el-option label="江西" value="江西"></el-option>
+                        <el-option label="山东" value="山东"></el-option>
+                        <el-option label="河南" value="河南"></el-option>
+                        <el-option label="湖北" value="湖北"></el-option>
+                        <el-option label="湖南" value="湖南"></el-option>
+                        <el-option label="广东" value="广东"></el-option>
+                        <el-option label="海南" value="海南"></el-option>
+                        <el-option label="重庆" value="重庆"></el-option>
+                        <el-option label="四川" value="四川"></el-option>
+                        <el-option label="贵州" value="贵州"></el-option>
+                        <el-option label="云南" value="云南"></el-option>
+                        <el-option label="陕西" value="陕西"></el-option>
+                        <el-option label="甘肃" value="甘肃"></el-option>
+                        <el-option label="青海" value="青海"></el-option>
+                        <el-option label="西藏" value="西藏"></el-option>
+                        <el-option label="宁夏" value="宁夏"></el-option>
+                        <el-option label="新疆" value="新疆"></el-option>
+                        <el-option label="广西" value="广西"></el-option>
+                        <el-option label="内蒙古" value="内蒙古"></el-option>
+                        <el-option label="其他" value="其他"></el-option>
+                      </el-select>
+                    </el-form-item>
+                  </el-col>
+                  <el-col :span="2" class="line">&nbsp;</el-col>
+                  <el-col :span="16">
+                    <el-form-item prop="address">
+                      <el-input v-model="form.address" clearable size="mini"></el-input>
+                    </el-form-item>
+                  </el-col>
+                </el-form-item>
+                <el-form-item label="电话号码" required>
+                  <el-col :span="6">
+                    <el-form-item prop="ex_tel">
+                      <el-input type="text" v-model="form.ex_tel" clearable size="mini"></el-input>
+                    </el-form-item>
+                  </el-col>
+                  <el-col :span="2" class="line">&nbsp;&nbsp;-</el-col>
+                  <el-col :span="16">
+                    <el-form-item prop="tel">
+                      <el-input type="age" v-model.number="form.tel" clearable size="mini"></el-input>
+                    </el-form-item>
+                  </el-col>
+                </el-form-item>
+                <el-form-item label="QQ号码" prop="qq">
+                  <el-input type="age" v-model.number="form.qq" clearable size="mini"></el-input>
+                </el-form-item>
+                <p class="text-danger" v-if="onlinePage">需要完善您的信息,才能完成开通。</p>
+              </el-form>
+              <el-form v-else>
+                <div class="form-group row">
+                  <label for="" class="col-sm-3 col-form-label">真实姓名</label>
+                  <div class="col-sm-9">
+                    <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.name">
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="" class="col-sm-3 col-form-label">手机号码</label>
+                  <div class="col-sm-9">
+                    <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.mobile">
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="" class="col-sm-3 col-form-label">单位(公司)名称</label>
+                  <div class="col-sm-9">
+                    <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.company">
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="" class="col-sm-3 col-form-label">联系地址</label>
+                  <div class="col-sm-9 row">
+                    <div class="form-group col-sm-3">
+                      <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.province">
+                    </div>
+                    <div class="form-group col-sm-9">
+                      <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.address">
+                    </div>
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="" class="col-sm-3 col-form-label">电话号码</label>
+                  <div class="col-sm-9 row">
+                    <div class="form-group col-sm-3">
+                      <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.phoneNum[0]">
+                    </div>
+                    <div class="form-group col-sm-9">
+                      <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.phoneNum[1]">
+                    </div>
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="" class="col-sm-3 col-form-label">QQ号码</label>
+                  <div class="col-sm-9">
+                    <input type="text" readonly class="form-control-plaintext" :value="onlineInfo.qq">
+                  </div>
+                </div>
+              </el-form>
+            </div>
+            <span slot="footer" class="dialog-footer">
+              <button class="btn btn-sm btn-secondary" @click="dialogVisible = false">关闭</button>&nbsp;&nbsp;
+              <button class="btn btn-sm btn-primary btn-blue" v-show="onlinePage" @click="editOnlineMsg('form', 'open')">立即开通</button>
+              <button class="btn btn-sm btn-primary btn-blue" v-show="editButton" @click="editOnlineMsg('form', 'edit')">确认修改</button>
+            </span>
+          </el-dialog>
+          <div class="dropdown-divider"></div>
+          <a class="dropdown-item" @click="goSsoUrl()">通行账号</a>
+          <a class="dropdown-item" @click="goHelpUrl()">帮助中心</a>
+          <a class="dropdown-item" @click="exitSso()">退出</a>
+        </div>
+      </div>
+    </el-dropdown-menu>
+  </el-dropdown>
+</template>
+
+<script>
+  import mixin from '../mixin'
+  import ElForm from '../../../../node_modules/element-ui/packages/form/src/form'
+  export default {
+    components: {ElForm},
+    mixins: [mixin],
+    data: () => ({
+      userHead: '',
+      loginPage: true, // 是否已登录
+      username: '',
+      password: '',
+      SSOUrl: 'https://sso.smartcost.com.cn',
+      HelpUrl: 'http://h.zhzdwd.com',
+      OnlineUrl: 'http://ol.smartcost.com.cn',
+      fail: false,
+      failMsg: '',
+      onlinePage: false, // 是否已开通网络版
+      dialogVisible: false, // 是否弹出窗
+      onlineInfo: '',
+      editPage: false, // 是否为编辑页面
+      editButton: false,
+      form: {
+        name: '',
+        mobile: '',
+        company: '',
+        province: '',
+        address: '',
+        ex_tel: '',
+        tel: '',
+        qq: ''
+      },
+      rules: {
+        name: [
+          {required: true, message: '请输入真实姓名', trigger: 'blur'}
+        ],
+        mobile: [
+          {required: true, message: '请输入手机号码', trigger: 'blur'},
+          {type: 'number', message: '手机号码必须为数字值', trigger: 'blur'}
+        ],
+        company: [
+          {required: true, message: '请输入单位(公司)名称', trigger: 'blur'}
+        ],
+        province: [
+          {required: true, message: '请输入联系地址', trigger: 'blur,change'}
+        ],
+        address: [
+          {required: true, message: '请输入联系地址', trigger: 'blur'}
+        ],
+        ex_tel: [
+          {required: true, message: '请输入区号', trigger: 'blur'}
+        ],
+        tel: [
+          {required: true, message: '请输入电话号码', trigger: 'blur'},
+          {type: 'number', message: '电话号码必须为数字值', trigger: 'blur'}
+        ],
+        qq: [
+          {required: true, message: '请输入qq号码', trigger: 'blur'},
+          {type: 'number', message: 'qq号码必须为数字值', trigger: 'blur'}
+        ]
+      }
+    }),
+    created () {
+      this.fetchData()
+    },
+    methods: {
+      fetchData () {
+        // 查询账号是否可登录和状态
+        if (this.checkOnline()) {
+          let userMsg = this.$db.read().get('sc_SsoUser').value()
+          if (userMsg !== undefined && userMsg !== '') {
+            let self = this
+            this.$http({
+              method: 'post',
+              url: self.SSOUrl + '/startup/auth',
+              data: {
+                auth: 'checkssologin',
+                token: userMsg.token,
+                ssoID: userMsg.id
+              },
+              transformRequest: [function (data) {
+                let ret = ''
+                for (let it in data) {
+                  ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
+                }
+                return ret
+              }],
+              headers: {
+                'Content-Type': 'application/x-www-form-urlencoded'
+              },
+              timeout: 5000
+            }).then(function (response) {
+              // 更新ssouser数据库
+              if (response.data.err === 0) {
+                let info = response.data.info
+                self.loginPage = false
+                self.onlinePage = info.onlineFlag !== 1
+                self.onlineInfo = info.onlineMsg
+                self.userHead = info.avatar
+                self.$db.read().set('sc_SsoUser', info).write()
+              } else {
+                self.loginPage = true
+                self.showFailMsg(response.data.msg)
+                self.$refs.usermenu.show()
+                self.$db.read().set('sc_SsoUser', '').write()
+              }
+            }).catch(function () {
+              self.showFailMsg('网络有误')
+            })
+          }
+        }
+      },
+      loginButton () {
+        if (!this.checkOnline()) {
+          this.$message.error('当前网络不可用,请检查网络问题')
+        } else {
+          if (this.username === '' || this.password === '') {
+            this.showFailMsg('请输入账号或密码!')
+          } else {
+            let self = this
+            this.$http({
+              method: 'post',
+              url: self.SSOUrl + '/startup/login',
+              data: {
+                username: this.username,
+                userpasswd: this.password
+              },
+              transformRequest: [function (data) {
+                let ret = ''
+                for (let it in data) {
+                  ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
+                }
+                return ret
+              }],
+              headers: {
+                'Content-Type': 'application/x-www-form-urlencoded'
+              },
+              timeout: 5000
+            }).then(function (response) {
+              // 更新ssouser数据库
+              if (response.data.err === 0) {
+                let info = response.data.info
+                self.loginPage = false
+                self.onlinePage = info.onlineFlag !== 1
+                self.$message.success('登录成功')
+                self.onlineInfo = info.onlineMsg
+                self.userHead = info.avatar
+                self.$db.read().set('sc_SsoUser', info).write()
+              } else {
+                self.showFailMsg(response.data.msg)
+              }
+            }).catch(function () {
+              self.showFailMsg('网络有误')
+            })
+          }
+        }
+      },
+      showFailMsg (msg) {
+        this.fail = true
+        this.failMsg = msg
+        let self = this
+        setTimeout(function () {
+          self.fail = false
+        }, 5000)
+      },
+      goSsoUrl () {
+        let userMsg = this.$db.read().get('sc_SsoUser').value()
+        let param = userMsg !== undefined && userMsg !== '' ? '?ssoID=' + userMsg.id + '&token=' + userMsg.token : ''
+        let url = this.SSOUrl + param
+        this.$electron.shell.openItem(url)
+      },
+      goHelpUrl () {
+        let url = this.HelpUrl
+        this.$electron.shell.openItem(url)
+      },
+      exitSso () {
+        this.loginPage = true
+        this.$message.info(`已退出当前账号`)
+        // 删除本地token
+        this.$db.read().set('sc_SsoUser', '').write()
+      },
+      openDialog (open = true) {
+        this.$refs.usermenu.hide()
+        this.dialogVisible = true
+        this.editButton = false
+        // 未开通网络版
+        if (open === false) {
+          this.editPage = false
+        }
+      },
+      changeEdit () {
+        this.editPage = true
+        this.editButton = true
+        this.form.name = this.onlineInfo.name
+        this.form.mobile = parseInt(this.onlineInfo.mobile)
+        this.form.company = this.onlineInfo.company
+        this.form.province = this.onlineInfo.province
+        this.form.address = this.onlineInfo.address
+        this.form.ex_tel = this.onlineInfo.phoneNum[0]
+        this.form.tel = parseInt(this.onlineInfo.phoneNum[1])
+        this.form.qq = parseInt(this.onlineInfo.qq)
+      },
+      editOnlineMsg (form, status) {
+        this.$refs[form].validate((valid) => {
+          if (valid) {
+            let userMsg = this.$db.read().get('sc_SsoUser').value()
+            let self = this
+            this.$http({
+              method: 'post',
+              url: self.OnlineUrl + '/api/user/edit',
+              data: {
+                auth: 'editOnline',
+                formData: JSON.stringify(this.form),
+                account: userMsg.account
+              },
+              transformRequest: [function (data) {
+                let ret = ''
+                for (let it in data) {
+                  ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
+                }
+                return ret
+              }],
+              headers: {
+                'Content-Type': 'application/x-www-form-urlencoded'
+              },
+              timeout: 5000
+            }).then(function (response) {
+              // 更新ssouser数据库
+              self.editPage = false
+              if (response.data.err === 0) {
+                let info = response.data.info
+                self.onlineInfo = info
+                userMsg.onlineMsg = info
+                self.editPage = false
+                if (status === 'open') {
+                  self.onlinePage = false
+                  userMsg.onlineFlag = 1
+                  self.$message.success('成功开通网络版信息!')
+                } else {
+                  self.$message.success('成功修改网络版信息!')
+                }
+                self.$db.read().set('sc_SsoUser', userMsg).write()
+              } else {
+                if (status === 'edit') {
+                  self.editPage = false
+                }
+              }
+            }).catch(function () {
+              self.showFailMsg('网络有误')
+            })
+          } else {
+            return false
+          }
+        })
+      },
+      checkOnline () {
+        if (!navigator.onLine) {
+          return false
+        } else {
+          return true
+        }
+      },
+      checkKeyCode () {
+        let ev = window.event
+        if (ev.keyCode === 9) {
+          console.log('tab')
+          return false
+        }
+      }
+    }
+  }
+</script>

+ 26 - 0
src/renderer/components/mixin.js

@@ -0,0 +1,26 @@
+export default {
+  mounted () {
+    this.disableDragEvent()
+  },
+  methods: {
+    disableDragEvent () {
+      window.addEventListener('dragenter', this.disableDrag, false)
+      window.addEventListener('dragover', this.disableDrag)
+      window.addEventListener('drop', this.disableDrag)
+    },
+    disableDrag (e) {
+      // const dropzone = document.getElementById('upload-area') // 这个是可拖拽的上传区
+      // if (dropzone === null || !dropzone.contains(e.target)) {
+      e.preventDefault()
+      return false
+      //   e.dataTransfer.effectAllowed = 'none'
+      //   e.dataTransfer.dropEffect = 'none'
+      // }
+    }
+  },
+  beforeDestroy () {
+    window.removeEventListener('dragenter', this.disableDrag, false)
+    window.removeEventListener('dragover', this.disableDrag)
+    window.removeEventListener('drop', this.disableDrag)
+  }
+}

+ 27 - 0
src/renderer/main.js

@@ -0,0 +1,27 @@
+import Vue from 'vue'
+import axios from 'axios'
+
+import App from './App'
+import router from './router'
+import store from './store'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import db from '../database'
+
+if (!process.env.IS_WEB) Vue.use(require('vue-electron'))
+
+Vue.http = Vue.prototype.$http = axios
+
+Vue.config.productionTip = false
+
+Vue.use(ElementUI)
+
+Vue.prototype.$db = db
+
+/* eslint-disable no-new */
+new Vue({
+  components: { App },
+  router,
+  store,
+  template: '<App/>'
+}).$mount('#app')

+ 47 - 0
src/renderer/router/index.js

@@ -0,0 +1,47 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+export default new Router({
+  routes: [
+    {
+      path: '/',
+      name: 'startup-page',
+      component: require('@/components/StartUpPage').default,
+      children: [
+        {
+          path: 'firstopen',
+          component: require('@/components/StartUpPage/FirstOpen').default,
+          name: 'first-open'
+        },
+        {
+          path: 'softwarelist',
+          component: require('@/components/StartUpPage/SoftwareList').default,
+          name: 'software-list'
+        },
+        {
+          path: 'softwaredetail/:pid',
+          component: require('@/components/StartUpPage/SoftwareDetail').default,
+          name: 'software-detail'
+        },
+        {
+          path: 'softwarestartup',
+          component: require('@/components/StartUpPage/SoftwareStartup').default,
+          name: 'software-startup',
+          children: [
+            {
+              path: ':productid',
+              component: require('@/components/StartUpPage/SoftwareStartupDetail').default,
+              name: 'software-startup-detail'
+            }
+          ]
+        }
+      ]
+    },
+    {
+      path: '*',
+      redirect: '/'
+    }
+  ]
+})

+ 11 - 0
src/renderer/store/index.js

@@ -0,0 +1,11 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+import modules from './modules'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  modules,
+  strict: process.env.NODE_ENV !== 'production'
+})

+ 25 - 0
src/renderer/store/modules/Counter.js

@@ -0,0 +1,25 @@
+const state = {
+  main: 0
+}
+
+const mutations = {
+  DECREMENT_MAIN_COUNTER (state) {
+    state.main--
+  },
+  INCREMENT_MAIN_COUNTER (state) {
+    state.main++
+  }
+}
+
+const actions = {
+  someAsyncTask ({ commit }) {
+    // do something async
+    commit('INCREMENT_MAIN_COUNTER')
+  }
+}
+
+export default {
+  state,
+  mutations,
+  actions
+}

+ 14 - 0
src/renderer/store/modules/index.js

@@ -0,0 +1,14 @@
+/**
+ * The file enables `@/store/index.js` to import all vuex modules
+ * in a one-shot manner. There should not be any reason to edit this file.
+ */
+
+const files = require.context('.', false, /\.js$/)
+const modules = {}
+
+files.keys().forEach(key => {
+  if (key === './index.js') return
+  modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
+})
+
+export default modules

+ 0 - 0
static/.gitkeep


ファイルの差分が大きいため隠しています
+ 9673 - 0
yarn.lock