Parcourir la source

完成所有的bug修改

hzj18279462576@163.com il y a 3 ans
Parent
commit
243def9bcb
100 fichiers modifiés avec 25168 ajouts et 1 suppressions
  1. 18 0
      .babelrc
  2. 9 0
      .editorconfig
  3. 5 0
      .eslintignore
  4. 29 0
      .eslintrc.js
  5. 17 0
      .gitignore
  6. 10 0
      .postcssrc.js
  7. 30 1
      README.md
  8. 41 0
      build/build.js
  9. 54 0
      build/check-versions.js
  10. BIN
      build/logo.png
  11. 101 0
      build/utils.js
  12. 22 0
      build/vue-loader.conf.js
  13. 103 0
      build/webpack.base.conf.js
  14. 101 0
      build/webpack.dev.conf.js
  15. 149 0
      build/webpack.prod.conf.js
  16. 7 0
      config/dev.env.js
  17. 88 0
      config/index.js
  18. 4 0
      config/prod.env.js
  19. 7 0
      config/test.env.js
  20. 12 0
      index.html
  21. 18283 0
      package-lock.json
  22. 102 0
      package.json
  23. 35 0
      src/App.vue
  24. 72 0
      src/api/acl/permission.js
  25. 83 0
      src/api/acl/role.js
  26. 132 0
      src/api/acl/user.js
  27. 33 0
      src/api/affiche.js
  28. 37 0
      src/api/api.js
  29. 45 0
      src/api/car.js
  30. 36 0
      src/api/role.js
  31. 1 0
      src/assets/icons/svg/account-active.svg
  32. 1 0
      src/assets/icons/svg/account.svg
  33. 1 0
      src/assets/icons/svg/edit.svg
  34. 1 0
      src/assets/icons/svg/error.svg
  35. 1 0
      src/assets/icons/svg/home-active.svg
  36. 1 0
      src/assets/icons/svg/home.svg
  37. 1 0
      src/assets/icons/svg/item-logo.svg
  38. 1 0
      src/assets/icons/svg/order-active.svg
  39. 1 0
      src/assets/icons/svg/order.svg
  40. 1 0
      src/assets/icons/svg/quit.svg
  41. 1 0
      src/assets/icons/svg/sousuo.svg
  42. 1 0
      src/assets/icons/svg/staff-active.svg
  43. 1 0
      src/assets/icons/svg/staff.svg
  44. 1 0
      src/assets/icons/svg/stat-active.svg
  45. 1 0
      src/assets/icons/svg/stat.svg
  46. 1 0
      src/assets/icons/svg/system-active.svg
  47. 1 0
      src/assets/icons/svg/system.svg
  48. 1 0
      src/assets/icons/svg/tuifang.svg
  49. 1 0
      src/assets/icons/svg/xiaoxizhongxin.svg
  50. BIN
      src/assets/images/1ogo.png
  51. BIN
      src/assets/images/1ogo@2x.png
  52. BIN
      src/assets/images/292汽车-线性 拷贝@2x.png
  53. BIN
      src/assets/images/292汽车-线性@2x.png
  54. BIN
      src/assets/images/affiche-active.png
  55. BIN
      src/assets/images/affiche.png
  56. BIN
      src/assets/images/bg-img (2).png
  57. BIN
      src/assets/images/bg.jpg
  58. BIN
      src/assets/images/bg.png
  59. BIN
      src/assets/images/car-active.png
  60. BIN
      src/assets/images/car.png
  61. BIN
      src/assets/images/code.png
  62. BIN
      src/assets/images/eye.png
  63. BIN
      src/assets/images/eye1.png
  64. BIN
      src/assets/images/eye_active.png
  65. BIN
      src/assets/images/eye_close.png
  66. BIN
      src/assets/images/iconbg.png
  67. BIN
      src/assets/images/name.png
  68. BIN
      src/assets/images/out.png
  69. BIN
      src/assets/images/password.png
  70. BIN
      src/assets/images/role-active.png
  71. BIN
      src/assets/images/role.png
  72. BIN
      src/assets/images/user.png
  73. BIN
      src/assets/images/公告02 拷贝@2x.png
  74. BIN
      src/assets/images/公告02@2x.png
  75. BIN
      src/assets/images/图层 6@2x.png
  76. BIN
      src/assets/images/图层 7@2x.png
  77. BIN
      src/assets/images/用户管理 拷贝@2x.png
  78. BIN
      src/assets/images/用户管理@2x.png
  79. BIN
      src/assets/images/退出 (3)@2x.png
  80. 0 0
      src/assets/scss/variable.scss
  81. 561 0
      src/components/CarNumber/index.vue
  82. 81 0
      src/components/SvgIcon/index.vue
  83. 144 0
      src/layout/components/Navbar.vue
  84. 141 0
      src/layout/components/NavbarItem.vue
  85. 63 0
      src/layout/index.vue
  86. 34 0
      src/main.js
  87. 120 0
      src/router/index.js
  88. 13 0
      src/store/getters.js
  89. 28 0
      src/store/index.js
  90. 25 0
      src/store/modules/inform.js
  91. 118 0
      src/store/modules/user.js
  92. 15 0
      src/utils/auth.js
  93. 64 0
      src/utils/http.js
  94. 128 0
      src/utils/request.js
  95. 22 0
      src/utils/rsa.js
  96. 1219 0
      src/views/affiche/index.vue
  97. 1234 0
      src/views/car/index.vue
  98. 403 0
      src/views/login/index.vue
  99. 1071 0
      src/views/role/index.vue
  100. 0 0
      static/.gitkeep

+ 18 - 0
.babelrc

@@ -0,0 +1,18 @@
+{
+  "presets": [
+    ["env", {
+      "modules": false,
+      "targets": {
+        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
+      }
+    }],
+    "stage-2"
+  ],
+  "plugins": ["transform-vue-jsx", "transform-runtime"],
+  "env": {
+    "test": {
+      "presets": ["env", "stage-2"],
+      "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
+    }
+  }
+}

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 5 - 0
.eslintignore

@@ -0,0 +1,5 @@
+/build/
+/config/
+/dist/
+/*.js
+/test/unit/coverage/

+ 29 - 0
.eslintrc.js

@@ -0,0 +1,29 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint'
+  },
+  env: {
+    browser: true,
+  },
+  extends: [
+    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
+    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
+    'plugin:vue/essential', 
+    // https://github.com/standard/standard/blob/master/docs/RULES-en.md
+    'standard'
+  ],
+  // required to lint *.vue files
+  plugins: [
+    'vue'
+  ],
+  // add your custom rules here
+  rules: {
+    // allow async-await
+    'generator-star-spacing': 'off',
+    // allow debugger during development
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+  }
+}

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+.DS_Store
+node_modules/
+/dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+/test/unit/coverage/
+/test/e2e/reports/
+selenium-debug.log
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 10 - 0
.postcssrc.js

@@ -0,0 +1,10 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  "plugins": {
+    "postcss-import": {},
+    "postcss-url": {},
+    // to edit target browsers: use "browserslist" field in package.json
+    "autoprefixer": {}
+  }
+}

+ 30 - 1
README.md

@@ -1 +1,30 @@
-#car_guanli
+# admin
+
+> A Vue.js project
+
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:8080
+npm run dev
+
+# build for production with minification
+npm run build
+
+# build for production and view the bundle analyzer report
+npm run build --report
+
+# run unit tests
+npm run unit
+
+# run e2e tests
+npm run e2e
+
+# run all tests
+npm test
+```
+
+For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

+ 41 - 0
build/build.js

@@ -0,0 +1,41 @@
+'use strict'
+require('./check-versions')()
+
+process.env.NODE_ENV = 'production'
+
+const ora = require('ora')
+const rm = require('rimraf')
+const path = require('path')
+const chalk = require('chalk')
+const webpack = require('webpack')
+const config = require('../config')
+const webpackConfig = require('./webpack.prod.conf')
+
+const spinner = ora('building for production...')
+spinner.start()
+
+rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
+  if (err) throw err
+  webpack(webpackConfig, (err, stats) => {
+    spinner.stop()
+    if (err) throw err
+    process.stdout.write(stats.toString({
+      colors: true,
+      modules: false,
+      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
+      chunks: false,
+      chunkModules: false
+    }) + '\n\n')
+
+    if (stats.hasErrors()) {
+      console.log(chalk.red('  Build failed with errors.\n'))
+      process.exit(1)
+    }
+
+    console.log(chalk.cyan('  Build complete.\n'))
+    console.log(chalk.yellow(
+      '  Tip: built files are meant to be served over an HTTP server.\n' +
+      '  Opening index.html over file:// won\'t work.\n'
+    ))
+  })
+})

+ 54 - 0
build/check-versions.js

@@ -0,0 +1,54 @@
+'use strict'
+const chalk = require('chalk')
+const semver = require('semver')
+const packageConfig = require('../package.json')
+const shell = require('shelljs')
+
+function exec (cmd) {
+  return require('child_process').execSync(cmd).toString().trim()
+}
+
+const versionRequirements = [
+  {
+    name: 'node',
+    currentVersion: semver.clean(process.version),
+    versionRequirement: packageConfig.engines.node
+  }
+]
+
+if (shell.which('npm')) {
+  versionRequirements.push({
+    name: 'npm',
+    currentVersion: exec('npm --version'),
+    versionRequirement: packageConfig.engines.npm
+  })
+}
+
+module.exports = function () {
+  const warnings = []
+
+  for (let i = 0; i < versionRequirements.length; i++) {
+    const mod = versionRequirements[i]
+
+    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+      warnings.push(mod.name + ': ' +
+        chalk.red(mod.currentVersion) + ' should be ' +
+        chalk.green(mod.versionRequirement)
+      )
+    }
+  }
+
+  if (warnings.length) {
+    console.log('')
+    console.log(chalk.yellow('To use this template, you must update following to modules:'))
+    console.log()
+
+    for (let i = 0; i < warnings.length; i++) {
+      const warning = warnings[i]
+      console.log('  ' + warning)
+    }
+
+    console.log()
+    process.exit(1)
+  }
+}

BIN
build/logo.png


+ 101 - 0
build/utils.js

@@ -0,0 +1,101 @@
+'use strict'
+const path = require('path')
+const config = require('../config')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const packageConfig = require('../package.json')
+
+exports.assetsPath = function (_path) {
+  const assetsSubDirectory = process.env.NODE_ENV === 'production'
+    ? config.build.assetsSubDirectory
+    : config.dev.assetsSubDirectory
+
+  return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+  options = options || {}
+
+  const cssLoader = {
+    loader: 'css-loader',
+    options: {
+      sourceMap: options.sourceMap
+    }
+  }
+
+  const postcssLoader = {
+    loader: 'postcss-loader',
+    options: {
+      sourceMap: options.sourceMap
+    }
+  }
+
+  // generate loader string to be used with extract text plugin
+  function generateLoaders (loader, loaderOptions) {
+    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
+
+    if (loader) {
+      loaders.push({
+        loader: loader + '-loader',
+        options: Object.assign({}, loaderOptions, {
+          sourceMap: options.sourceMap
+        })
+      })
+    }
+
+    // Extract CSS when that option is specified
+    // (which is the case during production build)
+    if (options.extract) {
+      return ExtractTextPlugin.extract({
+        use: loaders,
+        fallback: 'vue-style-loader'
+      })
+    } else {
+      return ['vue-style-loader'].concat(loaders)
+    }
+  }
+
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+  return {
+    css: generateLoaders(),
+    postcss: generateLoaders(),
+    less: generateLoaders('less'),
+    sass: generateLoaders('sass', { indentedSyntax: true }),
+    scss: generateLoaders('sass'),
+    stylus: generateLoaders('stylus'),
+    styl: generateLoaders('stylus')
+  }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+  const output = []
+  const loaders = exports.cssLoaders(options)
+
+  for (const extension in loaders) {
+    const loader = loaders[extension]
+    output.push({
+      test: new RegExp('\\.' + extension + '$'),
+      use: loader
+    })
+  }
+
+  return output
+}
+
+exports.createNotifierCallback = () => {
+  const notifier = require('node-notifier')
+
+  return (severity, errors) => {
+    if (severity !== 'error') return
+
+    const error = errors[0]
+    const filename = error.file && error.file.split('!').pop()
+
+    notifier.notify({
+      title: packageConfig.name,
+      message: severity + ': ' + error.name,
+      subtitle: filename || '',
+      icon: path.join(__dirname, 'logo.png')
+    })
+  }
+}

+ 22 - 0
build/vue-loader.conf.js

@@ -0,0 +1,22 @@
+'use strict'
+const utils = require('./utils')
+const config = require('../config')
+const isProduction = process.env.NODE_ENV === 'production'
+const sourceMapEnabled = isProduction
+  ? config.build.productionSourceMap
+  : config.dev.cssSourceMap
+
+module.exports = {
+  loaders: utils.cssLoaders({
+    sourceMap: sourceMapEnabled,
+    extract: isProduction
+  }),
+  cssSourceMap: sourceMapEnabled,
+  cacheBusting: config.dev.cacheBusting,
+  transformToRequire: {
+    video: ['src', 'poster'],
+    source: 'src',
+    img: 'src',
+    image: 'xlink:href'
+  }
+}

+ 103 - 0
build/webpack.base.conf.js

@@ -0,0 +1,103 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const config = require('../config')
+const vueLoaderConfig = require('./vue-loader.conf')
+
+function resolve (dir) {
+  return path.join(__dirname, '..', dir)
+}
+
+const createLintingRule = () => ({
+  test: /\.(js|vue)$/,
+  loader: 'eslint-loader',
+  enforce: 'pre',
+  include: [resolve('src'), resolve('test')],
+  options: {
+    formatter: require('eslint-friendly-formatter'),
+    emitWarning: !config.dev.showEslintErrorsInOverlay
+  }
+})
+
+module.exports = {
+  context: path.resolve(__dirname, '../'),
+  entry: {
+    app: './src/main.js'
+  },
+  output: {
+    path: config.build.assetsRoot,
+    filename: '[name].js',
+    publicPath: process.env.NODE_ENV === 'production'
+      ? config.build.assetsPublicPath
+      : config.dev.assetsPublicPath
+  },
+  resolve: {
+    extensions: ['.js', '.vue', '.json'],
+    alias: {
+      'vue$': 'vue/dist/vue.esm.js',
+      '@': resolve('src'),
+    }
+  },
+  module: {
+    rules: [
+      ...(config.dev.useEslint ? [] : []),// 开启eslint 将后面改成 [createLintingRule()] : []
+      {
+        test: /(\.svg)(\?.*)?$/,
+        loader: 'svg-sprite-loader',
+        include: [resolve('src/assets/icons/svg')],
+        options: {
+          symbolId: 'icon-[name]'
+        }
+      },
+      
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: vueLoaderConfig
+      },
+      {
+        test: /\.js$/,
+        loader: 'babel-loader',
+        include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        loader: 'url-loader',
+        exclude: [resolve('src/assets/icons/svg')],
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('img/[name].[hash:7].[ext]')
+        }
+      },
+      
+      {
+        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('media/[name].[hash:7].[ext]')
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+        }
+      }
+    ]
+  },
+  node: {
+    // prevent webpack from injecting useless setImmediate polyfill because Vue
+    // source contains it (although only uses it if it's native).
+    setImmediate: false,
+    // prevent webpack from injecting mocks to Node native modules
+    // that does not make sense for the client
+    dgram: 'empty',
+    fs: 'empty',
+    net: 'empty',
+    tls: 'empty',
+    child_process: 'empty'
+  }
+}

+ 101 - 0
build/webpack.dev.conf.js

@@ -0,0 +1,101 @@
+'use strict'
+const utils = require('./utils')
+const webpack = require('webpack')
+const config = require('../config')
+const merge = require('webpack-merge')
+const path = require('path')
+const baseWebpackConfig = require('./webpack.base.conf')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+const portfinder = require('portfinder')
+
+const HOST = process.env.HOST
+const PORT = process.env.PORT && Number(process.env.PORT)
+
+const devWebpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
+  },
+  // cheap-module-eval-source-map is faster for development
+  devtool: config.dev.devtool,
+
+  // these devServer options should be customized in /config/index.js
+  devServer: {
+    clientLogLevel: 'warning',
+    historyApiFallback: {
+      rewrites: [
+        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
+      ],
+    },
+    useLocalIp: true,  //////
+    hot: true,
+    contentBase: false, // since we use CopyWebpackPlugin.
+    compress: true,
+    host: HOST || config.dev.host,
+    port: PORT || config.dev.port,
+    open: config.dev.autoOpenBrowser,
+    overlay: config.dev.errorOverlay
+      ? { warnings: false, errors: true }
+      : false,
+    publicPath: config.dev.assetsPublicPath,
+    proxy: config.dev.proxyTable,
+    quiet: true, // necessary for FriendlyErrorsPlugin
+    watchOptions: {
+      poll: config.dev.poll,
+    }
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': require('../config/dev.env')
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
+    new webpack.NoEmitOnErrorsPlugin(),
+    // https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: 'index.html',
+      inject: true
+    }),
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.dev.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+module.exports = new Promise((resolve, reject) => {
+  portfinder.basePort = process.env.PORT || config.dev.port
+  portfinder.getPort((err, port) => {
+    if (err) {
+      reject(err)
+    } else {
+      // publish the new Port, necessary for e2e tests
+      process.env.PORT = port
+      // add port to devServer config
+      devWebpackConfig.devServer.port = port
+
+      // Add FriendlyErrorsPlugin
+      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
+        compilationSuccessInfo: {
+          // messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
+          messages: [ //下面两个地方,直接复制即可
+            `App runing at: `,
+            ` - Local: http://localhost:${port}`, //配置这里
+            ` - Network: http://${require('ip').address()}:${port}`,//配置这里
+          ],
+        },
+        onErrors: config.dev.notifyOnErrors
+          ? utils.createNotifierCallback()
+          : undefined
+      }))
+
+      resolve(devWebpackConfig)
+    }
+  })
+})

+ 149 - 0
build/webpack.prod.conf.js

@@ -0,0 +1,149 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const webpack = require('webpack')
+const config = require('../config')
+const merge = require('webpack-merge')
+const baseWebpackConfig = require('./webpack.base.conf')
+const CopyWebpackPlugin = require('copy-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const ExtractTextPlugin = require('extract-text-webpack-plugin')
+const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
+
+const env = process.env.NODE_ENV === 'testing'
+  ? require('../config/test.env')
+  : require('../config/prod.env')
+
+const webpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({
+      sourceMap: config.build.productionSourceMap,
+      extract: true,
+      usePostCSS: true
+    })
+  },
+  devtool: config.build.productionSourceMap ? config.build.devtool : false,
+  output: {
+    path: config.build.assetsRoot,
+    filename: utils.assetsPath('js/[name].[chunkhash].js'),
+    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
+  },
+  plugins: [
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
+    new webpack.DefinePlugin({
+      'process.env': env
+    }),
+    new UglifyJsPlugin({
+      uglifyOptions: {
+        compress: {
+          warnings: false
+        }
+      },
+      sourceMap: config.build.productionSourceMap,
+      parallel: true
+    }),
+    // extract css into its own file
+    new ExtractTextPlugin({
+      filename: utils.assetsPath('css/[name].[contenthash].css'),
+      // Setting the following option to `false` will not extract CSS from codesplit chunks.
+      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
+      // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 
+      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
+      allChunks: true,
+    }),
+    // Compress extracted CSS. We are using this plugin so that possible
+    // duplicated CSS from different components can be deduped.
+    new OptimizeCSSPlugin({
+      cssProcessorOptions: config.build.productionSourceMap
+        ? { safe: true, map: { inline: false } }
+        : { safe: true }
+    }),
+    // generate dist index.html with correct asset hash for caching.
+    // you can customize output by editing /index.html
+    // see https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: process.env.NODE_ENV === 'testing'
+        ? 'index.html'
+        : config.build.index,
+      template: 'index.html',
+      inject: true,
+      minify: {
+        removeComments: true,
+        collapseWhitespace: true,
+        removeAttributeQuotes: true
+        // more options:
+        // https://github.com/kangax/html-minifier#options-quick-reference
+      },
+      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+      chunksSortMode: 'dependency'
+    }),
+    // keep module.id stable when vendor modules does not change
+    new webpack.HashedModuleIdsPlugin(),
+    // enable scope hoisting
+    new webpack.optimize.ModuleConcatenationPlugin(),
+    // split vendor js into its own file
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor',
+      minChunks (module) {
+        // any required modules inside node_modules are extracted to vendor
+        return (
+          module.resource &&
+          /\.js$/.test(module.resource) &&
+          module.resource.indexOf(
+            path.join(__dirname, '../node_modules')
+          ) === 0
+        )
+      }
+    }),
+    // extract webpack runtime and module manifest to its own file in order to
+    // prevent vendor hash from being updated whenever app bundle is updated
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'manifest',
+      minChunks: Infinity
+    }),
+    // This instance extracts shared chunks from code splitted chunks and bundles them
+    // in a separate chunk, similar to the vendor chunk
+    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'app',
+      async: 'vendor-async',
+      children: true,
+      minChunks: 3
+    }),
+
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.build.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+if (config.build.productionGzip) {
+  const CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+  webpackConfig.plugins.push(
+    new CompressionWebpackPlugin({
+      asset: '[path].gz[query]',
+      algorithm: 'gzip',
+      test: new RegExp(
+        '\\.(' +
+        config.build.productionGzipExtensions.join('|') +
+        ')$'
+      ),
+      threshold: 10240,
+      minRatio: 0.8
+    })
+  )
+}
+
+if (config.build.bundleAnalyzerReport) {
+  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
+}
+
+module.exports = webpackConfig

+ 7 - 0
config/dev.env.js

@@ -0,0 +1,7 @@
+'use strict'
+const merge = require('webpack-merge')
+const prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+  NODE_ENV: '"development"'
+})

+ 88 - 0
config/index.js

@@ -0,0 +1,88 @@
+'use strict'
+// Template version: 1.3.1
+// see http://vuejs-templates.github.io/webpack for documentation.
+
+const path = require('path')
+
+module.exports = {
+  dev: {
+
+    // https://chtech.ncjti.edu.cn/carstop/carMonitor线上地址
+    // https://chtech.ncjti.edu.cn/carstop/dist4
+    // Paths
+    assetsSubDirectory: 'static',
+    assetsPublicPath: '/carstop/dist4/',
+    proxyTable: {
+      '/carstop/carMonitor': {
+        target: 'https://chtech.ncjti.edu.cn/carstop/carMonitor',
+        secure: false,// 这是签名认证,http和https区分的参数设置
+        changeOrigin: true,
+        pathRewrite: {
+          '^/carstop/carMonitor': ''
+        }
+      }
+    },
+
+    // Various Dev Server settings
+    // host: 'localhost', // can be overwritten by process.env.HOST
+    host: '0.0.0.0',
+    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+    autoOpenBrowser: false,
+    errorOverlay: true,
+    notifyOnErrors: true,
+    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
+
+    // Use Eslint Loader?
+    // If true, your code will be linted during bundling and
+    // linting errors and warnings will be shown in the console.
+    useEslint: true,
+    // If true, eslint errors and warnings will also be shown in the error overlay
+    // in the browser.
+    showEslintErrorsInOverlay: false,
+
+    /**
+     * Source Maps
+     */
+
+    // https://webpack.js.org/configuration/devtool/#development
+    devtool: 'cheap-module-eval-source-map',
+
+    // If you have problems debugging vue-files in devtools,
+    // set this to false - it *may* help
+    // https://vue-loader.vuejs.org/en/options.html#cachebusting
+    cacheBusting: true,
+
+    cssSourceMap: true
+  },
+
+  build: {
+    // Template for index.html
+    index: path.resolve(__dirname, '../dist/index.html'),
+
+    // Paths
+    assetsRoot: path.resolve(__dirname, '../dist'),
+    assetsSubDirectory: 'static',
+    assetsPublicPath: '/carstop/dist4/',
+
+    /**
+     * Source Maps
+     */
+
+    productionSourceMap: true,
+    // https://webpack.js.org/configuration/devtool/#production
+    devtool: '#source-map',
+
+    // Gzip off by default as many popular static hosts such as
+    // Surge or Netlify already gzip all static assets for you.
+    // Before setting to `true`, make sure to:
+    // npm install --save-dev compression-webpack-plugin
+    productionGzip: false,
+    productionGzipExtensions: ['js', 'css'],
+
+    // Run the build command with an extra argument to
+    // View the bundle analyzer report after build finishes:
+    // `npm run build --report`
+    // Set to `true` or `false` to always turn it on or off
+    bundleAnalyzerReport: process.env.npm_config_report
+  }
+}

+ 4 - 0
config/prod.env.js

@@ -0,0 +1,4 @@
+'use strict'
+module.exports = {
+  NODE_ENV: '"production"'
+}

+ 7 - 0
config/test.env.js

@@ -0,0 +1,7 @@
+'use strict'
+const merge = require('webpack-merge')
+const devEnv = require('./dev.env')
+
+module.exports = merge(devEnv, {
+  NODE_ENV: '"testing"'
+})

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <title>admin</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

Fichier diff supprimé car celui-ci est trop grand
+ 18283 - 0
package-lock.json


+ 102 - 0
package.json

@@ -0,0 +1,102 @@
+{
+  "name": "admin",
+  "version": "1.0.0",
+  "description": "A Vue.js project",
+  "author": "hzj2232462644 <hzj2232462644@163.com>",
+  "private": true,
+  "scripts": {
+    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 0.0.0.0",
+    "start": "npm run dev",
+    "unit": "jest --config test/unit/jest.conf.js --coverage",
+    "e2e": "node test/e2e/runner.js",
+    "test": "npm run unit && npm run e2e",
+    "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
+    "build": "node build/build.js"
+  },
+  "dependencies": {
+    "axios": "^0.27.2",
+    "better-scroll": "^2.4.2",
+    "dayjs": "^1.11.5",
+    "element-ui": "^2.15.2",
+    "file-saver": "^2.0.5",
+    "js-cookie": "^3.0.1",
+    "jsencrypt": "^3.2.1",
+    "lodash": "^4.17.21",
+    "moment": "^2.29.4",
+    "svg-sprite-loader": "^4.3.0",
+    "vue": "^2.5.2",
+    "vue-router": "^3.0.1",
+    "vuex": "^3.6.2",
+    "vuex-persistedstate": "^4.1.0",
+    "xlsx": "^0.18.5"
+  },
+  "devDependencies": {
+    "autoprefixer": "^7.1.2",
+    "babel-core": "^6.22.1",
+    "babel-eslint": "^8.2.1",
+    "babel-helper-vue-jsx-merge-props": "^2.0.3",
+    "babel-jest": "^21.0.2",
+    "babel-loader": "^7.1.1",
+    "babel-plugin-dynamic-import-node": "^1.2.0",
+    "babel-plugin-syntax-jsx": "^6.18.0",
+    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
+    "babel-plugin-transform-runtime": "^6.22.0",
+    "babel-plugin-transform-vue-jsx": "^3.5.0",
+    "babel-preset-env": "^1.3.2",
+    "babel-preset-stage-2": "^6.22.0",
+    "babel-register": "^6.22.0",
+    "chalk": "^2.0.1",
+    "chromedriver": "^2.27.2",
+    "copy-webpack-plugin": "^4.0.1",
+    "cross-spawn": "^5.0.1",
+    "css-loader": "^0.28.0",
+    "eslint": "^4.15.0",
+    "eslint-config-standard": "^10.2.1",
+    "eslint-friendly-formatter": "^3.0.0",
+    "eslint-loader": "^1.7.1",
+    "eslint-plugin-import": "^2.7.0",
+    "eslint-plugin-node": "^5.2.0",
+    "eslint-plugin-promise": "^3.4.0",
+    "eslint-plugin-standard": "^3.0.1",
+    "eslint-plugin-vue": "^4.0.0",
+    "extract-text-webpack-plugin": "^3.0.0",
+    "file-loader": "^1.1.4",
+    "friendly-errors-webpack-plugin": "^1.6.1",
+    "html-webpack-plugin": "^2.30.1",
+    "jest": "^22.0.4",
+    "jest-serializer-vue": "^0.3.0",
+    "nightwatch": "^0.9.12",
+    "node-notifier": "^5.1.2",
+    "node-sass": "^4.14.1",
+    "optimize-css-assets-webpack-plugin": "^3.2.0",
+    "ora": "^1.2.0",
+    "portfinder": "^1.0.13",
+    "postcss-import": "^11.0.0",
+    "postcss-loader": "^2.0.8",
+    "postcss-url": "^7.2.1",
+    "rimraf": "^2.6.0",
+    "sass-loader": "^7.3.1",
+    "selenium-server": "^3.0.1",
+    "semver": "^5.3.0",
+    "shelljs": "^0.7.6",
+    "uglifyjs-webpack-plugin": "^1.1.1",
+    "url-loader": "^0.5.8",
+    "vue-jest": "^1.0.2",
+    "vue-loader": "^13.3.0",
+    "vue-style-loader": "^3.0.1",
+    "vue-template-compiler": "^2.5.2",
+    "webpack": "^3.6.0",
+    "webpack-bundle-analyzer": "^2.9.0",
+    "webpack-dev-server": "^2.9.1",
+    "webpack-merge": "^4.1.0"
+  },
+  "engines": {
+    "node": ">= 6.0.0",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ]
+}

+ 35 - 0
src/App.vue

@@ -0,0 +1,35 @@
+<template>
+  <div id="app">
+    <keep-alive>
+      <router-view v-if="$route.meta.keepAlive"></router-view>
+    </keep-alive>
+    <router-view v-if="!$route.meta.keepAlive"></router-view>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "App",
+};
+</script>
+
+<style lang="scss">
+#app {
+  font-family: "Avenir", Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  width: 100%;
+  height: 100%;
+}
+
+html,
+body {
+  margin: 0;
+  padding: 0;
+  width: 100%;
+  height: 100%;
+  // ::-webkit-scrollbar {
+  //   display: none; /* Chrome Safari */
+  // }
+}
+</style>

+ 72 - 0
src/api/acl/permission.js

@@ -0,0 +1,72 @@
+import request from '@/utils/request'
+
+/* 
+权限管理相关的API请求函数
+*/
+const api_name = '/admin/acl/permission'
+
+export default {
+  
+  /* 
+  获取权限(菜单/功能)列表
+  */
+  getPermissionList() {
+    return request({
+      url: `${api_name}`,
+      method: 'get'
+    })
+  },
+  
+  /* 
+  删除一个权限项
+  */
+  removePermission(id) {
+    return request({
+      url: `${api_name}/remove/${id}`,
+      method: "delete"
+    })
+  },
+  
+  /* 
+  保存一个权限项
+  */
+  addPermission(permission) {
+    return request({
+      url: `${api_name}/save`,
+      method: "post",
+      data: permission
+    })
+  },
+
+  /* 
+  更新一个权限项
+  */
+  updatePermission(permission) {
+    return request({
+      url: `${api_name}/update`,
+      method: "put",
+      data: permission
+    })
+  },
+
+  /* 
+  查看某个角色的权限列表
+  */
+  toAssign(roleId) {
+    return request({
+      url: `${api_name}/toAssign/${roleId}`,
+      method: 'get'
+    })
+  },
+
+  /* 
+  给某个角色授权
+  */
+  doAssign(roleId, permissionId) {
+    return request({
+      url: `${api_name}/doAssign`,
+      method: "post",
+      params: {roleId, permissionId}
+    })
+  }
+}

+ 83 - 0
src/api/acl/role.js

@@ -0,0 +1,83 @@
+/* 
+角色管理相关的API请求函数
+*/
+import request from '@/utils/request'
+
+const api_name = '/admin/acl/role'
+
+export default {
+
+  /* 
+  获取角色分页列表(带搜索)
+  */
+  getPageList(page, limit, searchObj) {
+    return request({
+      url: `${api_name}/${page}/${limit}`,
+      method: 'get',
+      params: searchObj // url查询字符串或表单键值对
+    })
+  },
+
+  /* 
+  获取某个角色
+  */
+  getById(id) {
+    return request({
+      url: `${api_name}/get/${id}`,
+      method: 'get'
+    })
+  },
+
+  /* 
+  保存一个新角色
+  */
+  save(role) {
+    return request({
+      url: `${api_name}/save`,
+      method: 'post',
+      data: role
+    })
+  },
+
+  /* 
+  更新一个角色
+  */
+  updateById(role) {
+    return request({
+      url: `${api_name}/update`,
+      method: 'put',
+      data: role
+    })
+  },
+
+  /* 
+  获取一个角色的所有权限列表
+  */
+  getAssign(roleId) {
+    return request({
+      url: `${api_name}/toAssign/${roleId}`,
+      method: 'get'
+    })
+  },
+
+  /* 
+  删除某个角色
+  */
+  removeById(id) {
+    return request({
+      url: `${api_name}/remove/${id}`,
+      method: 'delete'
+    })
+  },
+
+  /* 
+  批量删除多个角色
+  */
+  removeRoles(ids) {
+    return request({
+      url: `${api_name}/batchRemove`,
+      method: 'delete',
+      data: ids
+    })
+  }
+}

+ 132 - 0
src/api/acl/user.js

@@ -0,0 +1,132 @@
+import request from '@/utils/request'
+
+const api_name = '/api/ihotel/hotelStaff'
+
+/*
+登陆
+*/
+export function login({ username, password }) {
+  return request({
+    url: '/admin/acl/index/login',
+    method: 'post',
+    data: { username, password }
+  })
+}
+
+/*
+获取用户信息(根据token)
+*/
+export function getInfo() {
+  return request({
+    url: '/admin/acl/index/info',
+    method: 'get'
+  })
+}
+
+/*
+登出
+*/
+export function logout() {
+  return request({
+    url: '/admin/acl/index/logout',
+    method: 'post'
+  })
+}
+
+/* 
+获取当前用户的菜单权限列表
+*/
+export function getMenu() {
+  return request('/admin/acl/index/menu')
+}
+
+
+/* 
+获取后台用户分页列表(带搜索)
+*/
+export function getPageList(page, limit, searchObj) {
+  return request({
+    url: `${api_name}/${page}/${limit}`,
+    method: 'get',
+    params: searchObj
+  })
+}
+
+/* 
+根据ID获取某个后台用户
+*/
+export function getById(id) {
+  return request({
+    url: `${api_name}/get/${id}`,
+    method: 'get'
+  })
+}
+
+/* 
+保存一个新的后台用户
+*/
+export function add(user) {
+  return request({
+    url: `${api_name}/save`,
+    method: 'post',
+    data: user
+  })
+}
+
+/* 
+更新一个后台用户
+*/
+export function update(user) {
+  return request({
+    url: `${api_name}/update`,
+    method: 'put',
+    data: user
+  })
+}
+
+/* 
+获取某个用户的所有角色
+*/
+export function getRoles(userId) {
+  return request({
+    url: `${api_name}/toAssign/${userId}`,
+    method: 'get'
+  })
+}
+
+/* 
+给某个用户分配角色
+roleId的结构: 字符串, 'rId1,rId2,rId3'
+*/
+export function assignRoles(userId, roleId) {
+  return request({
+    url: `${api_name}/doAssign`,
+    method: 'post',
+    params: {
+      userId,
+      roleId
+    }
+  })
+}
+
+/* 
+删除某个用户
+*/
+export function removeById(id) {
+  return request({
+    url: `${api_name}/remove/${id}`,
+    method: 'delete'
+  })
+}
+
+/* 
+批量删除多个用户
+ids的结构: ids是包含n个id的数组
+*/
+export function removeUsers(ids) {
+  return request({
+    url: `${api_name}/batchRemove`,
+    method: 'delete',
+    data: ids
+  })
+}

+ 33 - 0
src/api/affiche.js

@@ -0,0 +1,33 @@
+import http from '../utils/http'
+let resquest = "/carstop/carMonitor"
+
+
+
+// 查看广告
+export function adverquery(params) {
+    return http.get(`${resquest}/adverquery.action`, params)
+}
+// 新增广告
+export function adveradd(params) {
+    return http.post(`${resquest}/adveradd.action`, params)
+}
+
+// 修改广告
+export function adverupdate(params) {
+    return http.put(`${resquest}/adverupdate.action`, params)
+}
+
+// 删除广告
+export function adverdel(params) {
+    return http.get(`${resquest}/adverdel.action`, params)
+}
+// 导出广告
+export function advertoExcel(params) {
+    return http.get(`${resquest}/advertoExcel.action`, params)
+}
+// 选择当前使用的广告
+export function adverupdateState(params) {
+    return http.get(`${resquest}/adverupdateState.action`, params)
+}
+
+

+ 37 - 0
src/api/api.js

@@ -0,0 +1,37 @@
+// import http from '../utils/http'
+import * as car from './car'
+import * as role from './role'
+import * as affiche from './affiche'
+// 
+/**
+ *  @parms resquest 请求地址 例如:http://197.82.15.15:8088/request/...
+ *  @param '/testIp'代表vue-cil中config,index.js中配置的代理
+ */
+// let resquest = "/api/ihotel"
+
+// get请求
+export function getListAPI(params) {
+    return http.get(`${resquest}/getList.json`, params)
+}
+
+// post请求
+export function postFormAPI(params) {
+    return http.post(`${resquest}/postForm.json`, params)
+}
+
+// put 请求
+export function putSomeAPI(params) {
+    return http.put(`${resquest}/putSome.json`, params)
+}
+// delete 请求
+export function deleteListAPI(params) {
+    return http.delete(`${resquest}/deleteList.json`, params)
+}
+
+export default {
+    car,      // 车辆管理
+    role,    // 角色管理
+    affiche,    // 公告管理
+};
+
+

+ 45 - 0
src/api/car.js

@@ -0,0 +1,45 @@
+import http from '../utils/http'
+let resquest = "/carstop/carMonitor"
+
+// 车辆管理
+export function carList(params) {
+    return http.get(`${resquest}/carqueryCar.action`, params)
+}
+
+// 新增车辆
+export function caradd(params) {
+    return http.post(`${resquest}/caradd.action`, params)
+}
+
+// 更新车辆
+export function carupdate(data) {
+    return http.put(`${resquest}/carupdate.action`, data)
+}
+
+// 删除车辆
+export function cardel(data) {
+    return http.get(`${resquest}/cardel.action`, data)
+}
+
+// 导出车辆信息
+export function cartoExcel(data) {
+    return http.get(`${resquest}/cartoExcel.action`, data)
+}
+
+
+// 车辆日统计
+export function carquery(params) {
+    return http.post(`${resquest}/carMonitor/carqueryTj.action`, params)
+}
+
+// 查看车辆类型
+
+export function ctypequeryAll(data) {
+    return http.get(`${resquest}/carMonitor/ctypequeryAll.action`, data)
+}
+
+// 新增车辆类型
+
+export function ctypeinsert(params) {
+    return http.get(`${resquest}/carMonitor/ctypeinsert.action`, params)
+}

+ 36 - 0
src/api/role.js

@@ -0,0 +1,36 @@
+import http from '../utils/http'
+let resquest = "/carstop/carMonitor"
+
+// 查看管理员
+export function adminquery(params) {
+    return http.get(`${resquest}/adminquery.action`, params)
+}
+
+// 新增管理员
+export function adminadd(params) {
+    return http.post(`${resquest}/adminadd.action`, params)
+}
+
+// 更新管理员
+export function adminupdate(params) {
+    return http.post(`${resquest}/adminupdate.action`, params)
+}
+// 根据账户查询管理员
+export function adminqueryByNum(params) {
+    return http.get(`${resquest}/adminqueryByNum.action`, params)
+}
+// 删除管理员
+export function admindel(params) {
+    return http.get(`${resquest}/admindel.action`, params)
+}
+
+// 导出管理员
+export function admintoExcel(params) {
+    return http.get(`${resquest}/admintoExcel.action`, params)
+}
+
+// 登录multipart/form-data
+export function adminlogin(params, ContentType) {
+    return http.get(`${resquest}/carMonitor/adminlogin.action`, params
+    )
+}

Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/account-active.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/account.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/edit.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/error.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/home-active.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/home.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/item-logo.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/order-active.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/order.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/quit.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/sousuo.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/staff-active.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/staff.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/stat-active.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/stat.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/system-active.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/system.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/tuifang.svg


Fichier diff supprimé car celui-ci est trop grand
+ 1 - 0
src/assets/icons/svg/xiaoxizhongxin.svg


BIN
src/assets/images/1ogo.png


BIN
src/assets/images/1ogo@2x.png


BIN
src/assets/images/292汽车-线性 拷贝@2x.png


BIN
src/assets/images/292汽车-线性@2x.png


BIN
src/assets/images/affiche-active.png


BIN
src/assets/images/affiche.png


BIN
src/assets/images/bg-img (2).png


BIN
src/assets/images/bg.jpg


BIN
src/assets/images/bg.png


BIN
src/assets/images/car-active.png


BIN
src/assets/images/car.png


BIN
src/assets/images/code.png


BIN
src/assets/images/eye.png


BIN
src/assets/images/eye1.png


BIN
src/assets/images/eye_active.png


BIN
src/assets/images/eye_close.png


BIN
src/assets/images/iconbg.png


BIN
src/assets/images/name.png


BIN
src/assets/images/out.png


BIN
src/assets/images/password.png


BIN
src/assets/images/role-active.png


BIN
src/assets/images/role.png


BIN
src/assets/images/user.png


BIN
src/assets/images/公告02 拷贝@2x.png


BIN
src/assets/images/公告02@2x.png


BIN
src/assets/images/图层 6@2x.png


BIN
src/assets/images/图层 7@2x.png


BIN
src/assets/images/用户管理 拷贝@2x.png


BIN
src/assets/images/用户管理@2x.png


BIN
src/assets/images/退出 (3)@2x.png


+ 0 - 0
src/assets/scss/variable.scss


+ 561 - 0
src/components/CarNumber/index.vue

@@ -0,0 +1,561 @@
+<template>
+  <div>
+    <div>
+      <div
+        v-for="(item,index) in chinaShortName"
+        :class="chinaShortName[index].isClick?'item-box1':'item-box'"
+        @click="clickShortName(index)"
+      >
+        <a>{{ item.value }}</a>
+      </div>
+    </div>
+    <div>
+      <div
+        :class="letters[index].isClick?'item-box2':'item-box'"
+        :style="bgcolor"
+        v-for="(item,index) in letters"
+        @click="clickLetters(index)"
+      >
+        <a>{{ item.value }}</a>
+      </div>
+    </div>
+    <div>
+      <div
+        :class="number[index].isClick?'item-box2':'item-box'"
+        :style="bgcolor"
+        v-for="(item,index) in number"
+        @click="clickNumber(index)"
+      >
+        <a>{{ item.value }}</a>
+      </div>
+    </div>
+    <div style="float: right"></div>
+    <div class="car-number" style="display: inline-block;margin-top:10px">
+      车牌号:
+      <div style="display: inline" v-for="(item,index) in firstName">{{ item.value }}·</div>
+      <div
+        style="display: inline;letter-spacing:2px;"
+        v-for="(item,index) in otherName"
+      >{{ item.value }}</div>
+    </div>
+    <div class="backspace" @click="backSpace">
+      <a>退格</a>
+    </div>
+    <!-- <div class="backspace" @click="comfirmCarNum">
+      <a>确认</a>
+    </div>-->
+  </div>
+</template>
+ 
+<script>
+export default {
+  name: "carnumber",
+  data() {
+    return {
+      bgcolor: {
+        backgroundColor: "gray"
+      },
+      isAllowClickNumber: false,
+      isActive: false,
+      firstName: [],
+      //
+      otherName: [],
+      //车牌地区简称
+      chinaShortName: [
+        {
+          value: "京",
+          label: "京",
+          isClick: false
+        },
+        {
+          value: "津",
+          label: "津",
+          isClick: false
+        },
+        {
+          value: "沪",
+          label: "沪",
+          isClick: false
+        },
+        {
+          value: "渝",
+          label: "渝",
+          isClick: false
+        },
+        {
+          value: "冀",
+          label: "冀",
+          isClick: false
+        },
+        {
+          value: "晋",
+          label: "晋",
+          isClick: false
+        },
+        {
+          value: "蒙",
+          label: "蒙",
+          isClick: false
+        },
+        {
+          value: "辽",
+          label: "辽",
+          isClick: false
+        },
+        {
+          value: "吉",
+          label: "吉",
+          isClick: false
+        },
+        {
+          value: "黑",
+          label: "黑",
+          isClick: false
+        },
+        {
+          value: "苏",
+          label: "苏",
+          isClick: false
+        },
+        {
+          value: "浙",
+          label: "浙",
+          isClick: false
+        },
+        {
+          value: "皖",
+          label: "皖",
+          isClick: false
+        },
+        {
+          value: "闽",
+          label: "闽",
+          isClick: false
+        },
+        {
+          value: "赣",
+          label: "赣",
+          isClick: false
+        },
+        {
+          value: "鲁",
+          label: "鲁",
+          isClick: false
+        },
+        {
+          value: "豫",
+          label: "豫",
+          isClick: false
+        },
+        {
+          value: "鄂",
+          label: "鄂",
+          isClick: false
+        },
+        {
+          value: "湘",
+          label: "湘",
+          isClick: false
+        },
+        {
+          value: "粤",
+          label: "粤",
+          isClick: false
+        },
+        {
+          value: "桂",
+          label: "桂",
+          isClick: false
+        },
+        {
+          value: "新",
+          label: "新",
+          isClick: false
+        },
+        {
+          value: "黔",
+          label: "黔",
+          isClick: false
+        },
+        {
+          value: "云",
+          label: "云",
+          isClick: false
+        }
+      ],
+      //车牌号码
+      letters: [
+        {
+          label: "A",
+          value: "A",
+          isClick: false
+        },
+        {
+          label: "B",
+          value: "B",
+          isClick: false
+        },
+        {
+          label: "C",
+          value: "C",
+          isClick: false
+        },
+        {
+          label: "D",
+          value: "D",
+          isClick: false
+        },
+        {
+          label: "E",
+          value: "E",
+          isClick: false
+        },
+        {
+          label: "F",
+          value: "F",
+          isClick: false
+        },
+        {
+          label: "G",
+          value: "G",
+          isClick: false
+        },
+        {
+          label: "H",
+          value: "H",
+          isClick: false
+        },
+        {
+          label: "J",
+          value: "J",
+          isClick: false
+        },
+        {
+          label: "K",
+          value: "K",
+          isClick: false
+        },
+        {
+          label: "L",
+          value: "L",
+          isClick: false
+        },
+        {
+          label: "M",
+          value: "M",
+          isClick: false
+        },
+        {
+          label: "N",
+          value: "N",
+          isClick: false
+        },
+        {
+          label: "P",
+          value: "P",
+          isClick: false
+        },
+        {
+          label: "Q",
+          value: "Q",
+          isClick: false
+        },
+        {
+          label: "R",
+          value: "R",
+          isClick: false
+        },
+        {
+          label: "S",
+          value: "S",
+          isClick: false
+        },
+        {
+          label: "T",
+          value: "T",
+          isClick: false
+        },
+        {
+          label: "U",
+          value: "U",
+          isClick: false
+        },
+        {
+          label: "V",
+          value: "V",
+          isClick: false
+        },
+        {
+          label: "W",
+          value: "W",
+          isClick: false
+        },
+        {
+          label: "X",
+          value: "X",
+          isClick: false
+        },
+        {
+          label: "Y",
+          value: "Y",
+          isClick: false
+        },
+        {
+          label: "Z",
+          value: "Z",
+          isClick: false
+        }
+      ],
+      number: [
+        {
+          label: "0",
+          value: "0",
+          isClick: false
+        },
+        {
+          label: "1",
+          value: "1",
+          isClick: false
+        },
+        {
+          label: "2",
+          value: "2",
+          isClick: false
+        },
+        {
+          label: "3",
+          value: "3",
+          isClick: false
+        },
+        {
+          label: "4",
+          value: "4",
+          isClick: false
+        },
+        {
+          label: "5",
+          value: "5",
+          isClick: false
+        },
+        {
+          label: "6",
+          value: "6",
+          isClick: false
+        },
+        {
+          label: "7",
+          value: "7",
+          isClick: false
+        },
+        {
+          label: "8",
+          value: "8",
+          isClick: false
+        },
+        {
+          label: "9",
+          value: "9",
+          isClick: false
+        }
+      ]
+    };
+  },
+  watch: {
+    otherName(val, value) {
+      console.log(val);
+      console.log(value);
+    }
+  },
+  methods: {
+    clickShortName: function(index) {
+      for (let i = 0; i < this.chinaShortName.length; i++) {
+        if (index === i) {
+          this.chinaShortName[i].isClick = true;
+          if (this.firstName.length == 0) {
+            this.firstName.push({
+              type: "firstName",
+              index: index,
+              value: this.chinaShortName[i].value
+            });
+          } else {
+            this.firstName.pop();
+            this.firstName.push({
+              type: "firstName",
+              index: index,
+              value: this.chinaShortName[i].value
+            });
+          }
+        } else {
+          this.chinaShortName[i].isClick = false;
+        }
+      }
+      (this.isAllowClickNumber = true), (this.bgcolor.backgroundColor = "");
+    },
+    clickLetters: function(index) {
+      //   this.limitSize();
+      if (!this.isAllowClickNumber) {
+        //提示不可以点击
+        this.$message("请点击省份简称");
+        return;
+      }
+      if (this.otherName.length <= 5) {
+        for (let i = 0; i < this.letters.length; i++) {
+          if (index === i) {
+            this.letters[i].isClick = true;
+            this.otherName.push({
+              type: "letters",
+              index: index,
+              value: this.letters[i].value
+            });
+          }
+        }
+      } else {
+        this.$message.warning("输入的车牌号码过长");
+        //确认车牌号
+        let arr = [];
+        this.firstName.forEach(item => {
+          arr.push(item.value);
+        });
+        this.otherName.forEach(item => {
+          arr.push(item.value);
+        });
+        console.log(arr);
+        this.$store.state.user.carNumber = arr.join("");
+      }
+    },
+    clickNumber: function(index) {
+      //   this.limitSize();
+      if (!this.isAllowClickNumber) {
+        //提示不可以点击
+        this.$message("请点击省份简称");
+        return;
+      }
+      if (this.otherName.length <= 5) {
+        for (let i = 0; i < this.number.length; i++) {
+          if (index === i) {
+            this.number[i].isClick = true;
+            this.otherName.push({
+              type: "number",
+              index: index,
+              value: this.number[i].value
+            });
+          }
+        }
+      } else {
+        this.$message.warning("输入的车牌号码过长");
+        let arr = [];
+        this.firstName.forEach(item => {
+          arr.push(item.value);
+        });
+        this.otherName.forEach(item => {
+          arr.push(item.value);
+        });
+      }
+    },
+    backSpace: function() {
+      if (this.firstName.length == 0) {
+        this.isAllowClickNumber = false;
+        this.$message("已经清空数据");
+        return;
+      }
+      let pop = this.otherName.pop();
+      if (this.otherName.length == 0) {
+        let pop1 = this.firstName.pop();
+        this.chinaShortName[pop1.index].isClick = false;
+        this.isAllowClickNumber = false;
+      }
+      if (pop) {
+        if (pop.type == "number") {
+          this.number[pop.index].isClick = false;
+        } else if (pop.type == "letters") {
+          this.letters[pop.index].isClick = false;
+        }
+      }
+    },
+    comfirmCarNum: function() {
+      //确认车牌号
+      let arr = [];
+      this.firstName.forEach(item => {
+        arr.push(item.value);
+      });
+      this.otherName.forEach(item => {
+        arr.push(item.value);
+      });
+      this.$emit("carNum", arr);
+    }
+  }
+};
+</script>
+ 
+<style scoped>
+.item-box {
+  display: inline-block;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+  font-size: 20px;
+  line-height: 30px;
+  background-color: black;
+  color: white;
+  text-align: center;
+  -webkit-user-select: none;
+}
+
+.item-box1 {
+  display: inline-block;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+  font-size: 20px;
+  line-height: 30px;
+  background-color: white;
+  color: black;
+  border: 0.5px solid black;
+  text-align: center;
+  -webkit-user-select: none;
+}
+
+.item-box2 {
+  display: inline-block;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+  font-size: 20px;
+  line-height: 30px;
+  background-color: white;
+  border: 0.5px solid black;
+  color: black;
+  text-align: center;
+  -webkit-user-select: none;
+}
+
+.car-number {
+  font-size: 20px;
+  font-weight: bolder;
+  width: 230px;
+  height: 40px;
+  margin: 0 auto;
+  background-color: #fff;
+  border: 1px solid #dcdfe6;
+  color: #000;
+  line-height: 40px;
+  padding-left: 20px;
+  border-radius: 5px;
+  -webkit-user-select: none;
+}
+
+.backspace {
+  display: inline-block;
+  font-size: 20px;
+  font-weight: bolder;
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  margin-left: 10px;
+  padding-left: 10px;
+  padding-right: 10px;
+  border-radius: 5px;
+  border: 0.5px solid #dcdfe6;
+  -webkit-user-select: none;
+}
+</style>

+ 81 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="icon-wrapper" :style="wd">
+    <svg class="icon" aria-hidden="true" :style="wd">
+      <use :xlink:href="iconName"></use>
+    </svg>
+  </div>
+</template>
+ 
+<script>
+// 引入从iconfont 下载的symbox文件
+// import '@/assets/icons/iconfont-svg.js'
+
+// 引入本地的svg文件
+// 定义一个加载目录的函数
+const requireAll = (requireContext) =>
+  requireContext.keys().map(requireContext);
+const req = require.context("@/assets/icons/svg", false, /\.svg$/);
+// 加载目录下的所有的 svg 文件
+requireAll(req);
+// console.log('I: 加载svg文件:', req.keys())
+export default {
+  name: "IconSvg",
+  props: {
+    name: String,
+    prefix: {
+      type: String,
+      default: "icon-",
+    },
+    W: {
+      type: Number,
+      default: 28,
+    },
+    H: {
+      type: Number,
+      default: 28,
+    },
+  },
+  mounted() {},
+  computed: {
+    iconName() {
+      let name = `#${this.prefix}${this.name}`;
+      return name;
+    },
+    wd() {
+      const style = { width: this.W + "px", height: this.H + "px" };
+      return style;
+    },
+  },
+};
+</script>
+ 
+<style scoped>
+/*.icon-wrapper {
+  display: inline-block;
+}
+.icon {
+  width: 100%;
+  height: 100%;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}*/
+
+.icon-wrapper {
+  /* Using currentColor above lets
+  us use `color` for changing the color
+  of our icons: */
+  color: red;
+
+  /* The width and height of the SVG
+  was previously set to 1em.
+  This allows us to use `font-size`
+  to change the size of our icon: */
+}
+
+.icon {
+  display: inline-block;
+  color: #444444;
+  fill: currentColor;
+}
+</style>

+ 144 - 0
src/layout/components/Navbar.vue

@@ -0,0 +1,144 @@
+<template>
+  <div class="navbar">
+    <div class="nav-title">车辆大屏管理系统</div>
+    <div class="right">
+      <p class="time">{{ time }}</p>
+
+      <div class="photo">
+        <div>
+          <el-avatar icon="el-icon-user-solid" style="width: 38px; height: 38px"></el-avatar>
+        </div>
+        <div
+          style="font-size: 14px;line-height;line-height:30px;padding-left:10px
+        "
+        >{{$store.state.user.username}}</div>
+      </div>
+      <div class="name">{{ $store.state.user.userName }}</div>
+      <div class="out" @click="outLogin">
+        <img src="../../assets/images/out.png" />
+        <div class="title">退出</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { removeToken } from "@/utils/auth";
+import moment from "moment";
+import "moment/locale/zh-cn";
+export default {
+  name: "Navbar",
+  data() {
+    return {
+      time: moment().format("YYYY-M-D  dddd  HH:mm:ss"),
+      timer: null
+    };
+  },
+  mounted() {
+    this.time = moment().format("YYYY-M-D  dddd  HH:mm:ss");
+    this.timer = setInterval(() => {
+      this.time = moment().format("YYYY-M-D  dddd  HH:mm:ss");
+    }, 1000);
+  },
+  beforeDestroy() {
+    if (this.timer) {
+      clearInterval(this.timer);
+      this.timer = null;
+    }
+  },
+  methods: {
+    informs() {
+      this.$router
+        .replace({ name: "Inform" })
+        .then(res => {})
+        .catch(err => {
+          console.log();
+        });
+    },
+    outLogin() {
+      this.$confirm("确认退出登录?")
+        .then(_ => {
+          this.$store.dispatch("user/logout").then(() => {
+            this.$message({
+              message: "退出成功",
+              type: "success"
+            });
+            this.$router.replace({ path: "/login" });
+          });
+        })
+        .catch(_ => {});
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  height: 96px;
+  overflow: hidden;
+  background: rgba(19, 41, 73, 1);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .nav-title {
+    width: 324px;
+    height: 53px;
+    line-height: 53px;
+    color: transparent;
+    font-size: 36px;
+    font-weight: 500;
+    margin-left: 22px;
+  }
+
+  .right {
+    display: flex;
+    align-items: center;
+
+    .time {
+      width: 241px;
+      height: 27px;
+      line-height: 27px;
+      color: #fff;
+      font-size: 18px;
+      font-weight: 400;
+      margin-right: 5px;
+      // float: right;
+    }
+
+    .photo {
+      height: 28px;
+      padding: 0 5px 0 15px;
+      border-left: 2px solid #fff;
+      border-right: 2px solid #fff;
+      display: flex;
+      align-items: center;
+      color: #fff;
+    }
+
+    .name {
+      // width: 50px;
+      height: 28px;
+      color: #fff;
+      font-size: 14px;
+      font-weight: 500;
+      line-height: 28px;
+      padding: 0 15px 0 5px;
+      margin-right: 13px;
+    }
+
+    .out {
+      display: flex;
+      align-items: center;
+      padding-right: 18px;
+      cursor: pointer;
+
+      .title {
+        margin-left: 6px;
+        font-size: 14px;
+        color: #fff;
+      }
+    }
+  }
+}
+</style>

+ 141 - 0
src/layout/components/NavbarItem.vue

@@ -0,0 +1,141 @@
+<template>
+  <div class="navitem">
+    <div class="logo">
+      <div class="nav-title">车辆大屏管理系统</div>
+    </div>
+    <!-- <el-radio-group class="logo">
+      <IconSvg :W="44" :H="44" name="item-logo" />
+    </el-radio-group> -->
+    <el-menu
+      text-color="#000"
+      :default-active="$route.path"
+      class="el-menu-vertical-demo"
+      @open="handleOpen"
+      @close="handleClose"
+      :collapse="isCollapse"
+      router
+    >
+      <el-menu-item index="/car">
+        <div class="icons">
+          <img
+            v-if="ItemIndex == '/car'"
+            src="../../assets/images/car-active.png"
+          />
+          <img v-else src="../../assets/images/car.png" />
+        </div>
+        <span slot="title">车辆管理</span>
+      </el-menu-item>
+      <el-menu-item index="/role">
+        <div class="icons">
+          <img
+            v-if="ItemIndex == '/role'"
+            src="../../assets/images/role-active.png"
+          />
+          <img v-else src="../../assets/images/role.png" />
+        </div>
+        <span slot="title">账号管理</span>
+      </el-menu-item>
+      <el-menu-item index="/affiche">
+        <div class="icons">
+          <img
+            v-if="ItemIndex == '/affiche'"
+            src="../../assets/images/affiche-active.png"
+          />
+          <img v-else src="../../assets/images/affiche.png" />
+        </div>
+        <span slot="title">公告管理</span>
+      </el-menu-item>
+    </el-menu>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "NavbarItem",
+  data() {
+    return {
+      isCollapse: false,
+    };
+  },
+  mounted() {},
+  computed: {
+    ItemIndex: {
+      get() {
+        return this.$route.path;
+      },
+      set(val) {},
+    },
+  },
+  methods: {
+    handleOpen(key, keyPath) {
+      console.log(key, keyPath);
+    },
+    handleClose(key, keyPath) {
+      console.log(key, keyPath);
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.navitem {
+  width: 260px;
+
+  .logo {
+    height: 96px;
+    background-color: rgba(19, 41, 73, 1);
+    box-sizing: border-box;
+    display: flex;
+    align-items: center;
+    .nav-title {
+      position: absolute;
+      display: inline-block;
+      height: 53px;
+      line-height: 53px;
+      color: #fff;
+      font-size: 36px;
+      font-weight: 500;
+      margin-left: 35px;
+    }
+  }
+
+  .el-menu {
+    margin-top: 33px;
+    border: none;
+    .is-active {
+      background-color: rgba(43, 76, 254, 1) !important;
+      color: #fff;
+      border-top-left-radius: 27px;
+      border-bottom-left-radius: 27px;
+      margin-left: 20px;
+    }
+
+    .el-menu-item {
+      margin-left: 20px;
+      height: 52px;
+      line-height: 0;
+      margin-bottom: 13px;
+      font-size: 18px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border: 1px solid transparent;
+      box-sizing: border-box;
+      border-top-left-radius: 27px;
+      border-bottom-left-radius: 27px;
+
+      .icons {
+        margin-left: 27px;
+      }
+
+      span {
+        width: 87px;
+        height: 26px;
+        display: flex;
+        align-items: center;
+        padding-left: 16px;
+      }
+    }
+  }
+}
+</style>

+ 63 - 0
src/layout/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <div class="box">
+    <el-container style="height: 100%">
+      <el-aside>
+        <NavbarItem></NavbarItem>
+      </el-aside>
+      <el-container>
+        <el-header>
+          <Navbar></Navbar>
+        </el-header>
+        <el-main>
+          <router-view></router-view>
+        </el-main>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import NavbarItem from "@/layout/components/NavbarItem";
+import Navbar from "@/layout/components/Navbar";
+import Cookies from "js-cookie";
+export default {
+  name: "Layout",
+  components: { NavbarItem, Navbar },
+  data() {
+    return {};
+  },
+  mounted() {},
+  methods: {},
+};
+</script>
+
+
+<style scoped lang="scss">
+.box {
+  width: 100%;
+  height: 100%;
+
+  .el-header {
+    color: #333;
+    text-align: center;
+    width: 1660px;
+    padding: 0;
+    height: 96px !important;
+    background: rgba(255, 255, 255, 1);
+  }
+
+  .el-aside {
+    background-color: #d3dce6;
+    color: #333;
+    text-align: center;
+    height: 100%;
+    width: 260px !important;
+    background: #fff;
+  }
+
+  .el-main {
+    background: RGBA(235, 238, 246, 1);
+    padding: 32px 0 0 25px;
+  }
+}
+</style>

+ 34 - 0
src/main.js

@@ -0,0 +1,34 @@
+// The Vue build version to load with the `import` command
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
+import Vue from 'vue'
+import App from './App'
+
+import router from './router'
+import axios from "axios";
+import store from './store';
+
+
+import API from './api/api'
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+Vue.prototype.API = API
+Vue.component('IconSvg', require('./components/SvgIcon').default)
+Vue.component('CarNumber', require('./components/CarNumber').default)
+
+Vue.config.productionTip = false
+Vue.use(ElementUI);
+Vue.directive('fo', {
+  inserted(el, binding, vnode) {
+    // 聚焦元素
+    el.querySelector('input').focus()
+  }
+})
+
+/* eslint-disable no-new */
+new Vue({
+  el: '#app',
+  router,
+  store,
+  components: { App },
+  template: '<App/>'
+})

+ 120 - 0
src/router/index.js

@@ -0,0 +1,120 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import Layout from '@/layout'
+import store from '@/store'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+Vue.use(Router)
+//不管是超级管理员还是普通用户都可以看到的页面 
+export const constantRoutes = [
+    // 登录页
+    {
+        path: '/login',
+        component: () => import('@/views/login/index'),
+        hidden: true
+    },
+]
+
+/**
+ * 异步路由
+ * asyncRoutes
+ */
+export const asyncRoutes = [
+    {
+        path: '/',
+        name: 'Layout',
+        redirect: '/login',
+        alwaysShow: true,
+        component: Layout,
+        children: [
+            {
+                path: 'car',
+                name: 'car',
+                meta: {
+                    isAuth: true,
+                    title: '主页',
+                    keepAlive: true, //缓存组件 
+                    requireAuth: true
+                },
+                component: () => import('@/views/car'),
+            },
+            {
+                path: 'affiche',
+                name: 'affiche',
+                meta: {
+                    isAuth: true, title: '公告管理',
+                    requireAuth: true
+                },
+                component: () => import('@/views/affiche')
+            },
+            {
+                path: 'role',
+                name: 'role',
+                meta: { isAuth: true, title: '角色管理', requireAuth: true },
+                component: () => import('@/views/role')
+            }
+
+        ]
+    },
+]
+
+export const anyRoute = { path: '*', redirect: '/404', hidden: true }
+const createRouter = () => new Router({
+    // mode: 'history', // require service support
+    scrollBehavior: () => ({ y: 0 }),
+    routes: [...constantRoutes, ...asyncRoutes]
+})
+
+const router = createRouter()
+
+export function resetRouter() {
+    const newRouter = createRouter()
+    router.matcher = newRouter.matcher // reset router
+}
+
+
+//这里设置路由拦截
+router.beforeEach((to, from, next) => {
+    if (to.meta.requireAuth) { //判断是否需要登录验证
+        const token = getToken()
+        if (token && store.state.user.token) {
+            if (store.state.user.token == token) { //判断token等于时候就跳转路由
+                next();//这个跳转是上面路由跳转
+            }
+            else { //token不一样时候就返回登录页面
+                next({
+                    path: '/login',//返回登录界面
+                    // query: {redirect: to.fullPath}  
+                })
+            }
+        } else {
+            next({
+                path: '/login',//返回登录界面
+                // query: {redirect: to.fullPath}  
+            })
+        }
+    }
+    else {
+        //如果不需要登录权限就直接跳转到该路由
+        next();
+    }
+})
+
+
+export default router
+
+//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
+// router.beforeEach((to, from, next) => {
+//     //如果路由需要跳转
+//     // if (to.meta.isAuth) {
+//     //     //判断 如果cook是否存在,可以进去
+//     //     if (Cookies.get('cook')) {
+//     //         next()  //放行
+//     //     } else {
+//     //         // alert('登录身份失效,请重新登录')
+//     //         next({ path: "/login" });
+//     //     }
+//     // } else {
+//     //     // 否则,放行
+//     //     next()
+//     // }
+// })

+ 13 - 0
src/store/getters.js

@@ -0,0 +1,13 @@
+const getters = {
+    sidebar: (state) => state.app.sidebar,
+    device: (state) => state.app.device,
+    token: (state) => state.user.token,
+    time: (state) => state.user.time,
+    avatar: (state) => state.user.avatar,
+    userName: (state) => state.user.userName,
+    headImage: (state) => state.user.headImage,
+    roles: (state) => state.user.roles,
+    routes: (state) => state.permission.routes,
+    buttons: (state) => state.user.buttons
+}
+export default getters

+ 28 - 0
src/store/index.js

@@ -0,0 +1,28 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import user from './modules/user'
+import inform from './modules/inform'
+import createPersistedState from "vuex-persistedstate"
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+    modules: {
+        user,
+        inform
+    },
+    getters,
+    plugins: [createPersistedState({
+        storage: window.sessionStorage,
+        reducer(val) {
+            // console.log(val);
+            return { // 只储存state中的user 
+                inform: val.inform,
+                user: val.user
+            }
+        }
+    })]
+})
+
+
+export default store

+ 25 - 0
src/store/modules/inform.js

@@ -0,0 +1,25 @@
+
+const state = {
+    readFlag: ""
+}
+
+const mutations = {
+
+
+    SET_readFlag: (state, number) => {
+        state.readFlag = number
+    },
+}
+
+const actions = {
+    setreadFlag({ commit }, number) {
+        commit('SET_readFlag', number)
+    }
+}
+
+export default {
+    namespaced: true,
+    state,
+    mutations,
+    actions
+}

+ 118 - 0
src/store/modules/user.js

@@ -0,0 +1,118 @@
+import { login, logout, getInfo } from '@/api/acl/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { resetRouter } from '@/router'
+import api from '@/api/api'
+const getDefaultState = () => {
+    return {
+        token: '',
+        userName: '',
+        headImage: '',
+        time: new Date(),
+        avatar: '',
+        roles: [],
+        buttons: [],
+        carNumber: ''
+    }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+    RESET_STATE: (state) => {
+        Object.assign(state, getDefaultState())
+    },
+    SET_TOKEN: (state, token) => {
+        state.token = token
+    },
+
+    SET_USERINFO: (state, userInfo) => {
+        state.userName = userInfo.admin_account
+        // state.headImage = userInfo.headImage
+        // state.buttons = userInfo.buttons
+    },
+
+    SET_ROLES: (state, roles) => {
+        state.roles = roles
+    },
+    SET_TIME: (state, time) => {
+        state.time = time
+    }
+}
+
+const actions = {
+    // user login
+    login({ commit }, userInfo) {
+        // console.log(userInfo);
+        const { username, password, token } = userInfo
+        return new Promise((resolve, reject) => {
+            api.role.adminlogin({ admin_account: username.trim(), admin_password: password })
+                .then((response) => {
+                    if (response.code === 200) {
+                        commit('SET_TOKEN', token)
+                        commit('SET_USERINFO', response.data)
+                        setToken(token)
+                        resolve()
+                    } else {
+                        commit('SET_TOKEN', "")
+                        setToken("")
+                        reject(error)
+                    }
+                })
+                .catch((error) => {
+                    reject(error)
+                })
+        })
+    },
+
+    // get user info
+    getInfo({ commit, state }) {
+        return new Promise((resolve, reject) => {
+            getInfo(state.token)
+                .then((response) => {
+                    const { data } = response
+
+                    if (!data) {
+                        reject('Verification failed, please Login again.')
+                    }
+
+                    const { roles } = data
+
+                    // roles must be a non-empty array
+                    if (!roles || roles.length <= 0) {
+                        reject('getInfo: roles must be a non-null array!')
+                    }
+
+                    commit('SET_ROLES', roles)
+                    commit('SET_USERINFO', data)
+                    resolve(data)
+                })
+                .catch((error) => {
+                    reject(error)
+                })
+        })
+    },
+
+    // user logout
+    logout({ commit, state }) {
+        removeToken() // must remove  token  first
+        // resetRouter()
+        commit('RESET_STATE')
+
+    },
+
+    // remove token
+    resetToken({ commit }) {
+        return new Promise((resolve) => {
+            // removeToken() // must remove  token  first
+            commit('RESET_STATE')
+            resolve()
+        })
+    }
+}
+
+export default {
+    namespaced: true,
+    state,
+    mutations,
+    actions
+}

+ 15 - 0
src/utils/auth.js

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}

+ 64 - 0
src/utils/http.js

@@ -0,0 +1,64 @@
+/****   http.js   ****/
+// 导入封装好的axios实例
+import request from './request'
+
+const http = {
+    /**
+     * methods: 请求
+     * @param url 请求地址 
+     * @param params 请求参数
+     */
+    get(url, params, ContentType) {
+        const config = {
+            method: 'get',
+            url: url,
+            headers: ContentType ? ContentType : { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }
+        }
+        if (params) {
+            config.params = params
+        }
+
+        return request(config)
+    },
+    post(url, params, ContentType) {
+        const config = {
+            method: 'post',
+            url: url,
+            headers: ContentType ? ContentType : {
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        }
+        if (params) config.data = params
+        return request(config)
+    },
+    put(url, params, ContentType) {
+        const config = {
+            method: 'put',
+            url: url,
+            headers: ContentType ? ContentType : {
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        }
+        if (params) {
+            // config.params = params
+            config.data = params
+        }
+        return request(config)
+    },
+    delete(url, params, ContentType) {
+        const config = {
+            method: 'delete',
+            url: url,
+            headers: ContentType ? ContentType : {
+                'Content-Type': 'application/json; charset=utf-8'
+            }
+        }
+        if (params) {
+            // config.params = params
+            config.data = params
+        }
+        return request(config)
+    }
+}
+//导出
+export default http

+ 128 - 0
src/utils/request.js

@@ -0,0 +1,128 @@
+/****   request.js   ****/
+// 导入axios
+import axios from 'axios'
+// 使用element-ui Message做消息提醒
+import { Message } from 'element-ui';
+import Cookies from 'js-cookie'
+//1. 创建新的axios实例,
+const service = axios.create({
+    // 公共接口--这里注意后面会讲
+    baseURL: process.env.BASE_API,
+    // 超时时间 单位是ms,这里设置了3s的超时时间
+    timeout: 5 * 1000
+})
+// 2.请求拦截器
+service.interceptors.request.use(config => {
+    //发请求前做的一些处理,数据转化,配置请求头,设置token,设置loading等,根据需求去添加
+    config.data = JSON.stringify(config.data); //数据转化,也可以使用qs转换
+    // config.headers = {
+    //     'Content-Type': 'application/json' //配置请求头
+    // }
+    // config.headers = { 'Content-Type': 'application/x-www-form-urlencoded' } //配置请求头
+    //如有需要:注意使用token的时候需要引入cookie方法或者用本地localStorage等方法,推荐js-cookie
+    // const token = Cookies.get('cook');//这里取token之前,你肯定需要先拿到token,存一下
+    // if (token) {
+    //     // config.params = { 'admin_token': token } //如果要求携带在参数中
+    //     // config.headers['admin_token'] = token; //如果要求携带在请求头中
+    //     // config.headers['admin-token'] = token; //如果要求携带在请求头中
+    // }
+
+
+    return config
+}, error => {
+    Promise.reject(error)
+})
+
+
+
+// 3.响应拦截器
+service.interceptors.response.use(response => {
+    //接收到响应数据并成功后的一些共有的处理,关闭loading等
+
+    return response.data
+}, error => {
+    /***** 接收到异常响应的处理开始 *****/
+    if (error && error.response) {
+        // 1.公共错误处理
+        // 2.根据响应码具体处理
+        switch (error.response.status) {
+            case 10000:
+                error.message = '系统未知异常'
+                break;
+            case 10001:
+                error.message = '请求方法不支持'
+                break;
+            case 10002:
+                error.message = '缺少必要的请求头'
+                break;
+            case 10003:
+                error.message = '无效的token'
+                // window.location.href = "/NotFound"
+                break;
+            case 10004:
+                error.message = 'token为空'
+                break;
+            case 10005:
+                error.message = '管理员登录失败'
+                break;
+            case 10006:
+                error.message = '权限不足'
+                break;
+            case 11001:
+                error.message = '参数格式校验失败'
+                break;
+            case 11002:
+                error.message = 'body为空'
+                break;
+            case 11003:
+                error.message = '参数异常'
+                break;
+            case 11004:
+                error.message = '重复提交表单'
+                break;
+            case 11005:
+                error.message = '订单提交失败'
+                break;
+            case 11006:
+                error.message = '消费者消费异常'
+                break;
+            case 11007:
+                error.message = '第三方服务调用失败'
+                break;
+            case 11008:
+                error.message = '农商行订单状态查询失败'
+                break;
+            case 16000:
+                error.message = '数据已存在'
+                break;
+            case 16000:
+                error.message = '数据不存在'
+                break;
+            case 16006:
+                error.message = '临时文件不存在'
+                break;
+            case 16007:
+                error.message = '文件上传失败'
+                break;
+            case 16008:
+                error.message = '文件太大'
+                break;
+
+            default:
+                error.message = `连接错误${error.response.status}`
+        }
+    } else {
+        // 超时处理
+        if (JSON.stringify(error).includes('timeout')) {
+            Message.error('服务器响应超时,请刷新当前页')
+        }
+        error.message = '连接服务器失败'
+    }
+
+    Message.error(error.message)
+    /***** 处理结束 *****/
+    //如果不需要错误处理,以上的处理过程都可省略
+    return Promise.resolve(error.response)
+})
+//4.导入文件
+export default service

+ 22 - 0
src/utils/rsa.js

@@ -0,0 +1,22 @@
+import { JSEncrypt } from 'jsencrypt';
+let publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMOcPB06u5yKyQsPjfVWiWgbEIrd14kiXNNihciaVKb6HnkQvq7zpQuZ80WEX94spnUMI3iOAl/GmIvHrpGwcbB4hJbznm+PajiwnUSPuCCXA68YJF640cJKb/8KeM7WVz69OFkIEPHhVxOy4FFF5QWe/kt6zOZ19HmE+ak+5x/QIDAQAB'
+let privateKey = '这里是封装的私钥'
+//加密方法
+export function RSAencrypt(pas) {
+    //实例化jsEncrypt对象
+    let jse = new JSEncrypt();
+    //设置公钥
+    jse.setPublicKey(publicKey);
+    console.log('加密:' + jse.encrypt(pas))
+    return jse.encrypt(pas);
+}
+
+//解密方法
+export function RSAdecrypt(pas) {
+    let jse = new JSEncrypt();
+    // 私钥
+    jse.setPrivateKey(privateKey)
+    console.log('解密:' + jse.decrypt(pas))
+    return jse.decrypt(pas);
+}
+

Fichier diff supprimé car celui-ci est trop grand
+ 1219 - 0
src/views/affiche/index.vue


Fichier diff supprimé car celui-ci est trop grand
+ 1234 - 0
src/views/car/index.vue


+ 403 - 0
src/views/login/index.vue

@@ -0,0 +1,403 @@
+<template>
+  <div class="login-container">
+    <div class="shadow">
+      <div class="img">
+        <img src="../../assets/images/iconbg.png" alt="" />
+      </div>
+      <div class="form">
+        <img src="../../assets/images/1ogo.png" alt="" />
+        <div class="title">车辆大屏管理系统</div>
+        <div class="login">登录</div>
+        <div class="name">
+          <el-form
+            ref="loginForm"
+            :model="loginForm"
+            :rules="loginRules"
+            class="login-form"
+          >
+            <el-form-item prop="username">
+              <span class="svg-container">
+                <div class="user"></div>
+              </span>
+              <el-input
+                v-model="loginForm.username"
+                placeholder="请输入账号"
+                type="text"
+              />
+            </el-form-item>
+
+            <el-form-item prop="password">
+              <span class="svg-container">
+                <div class="password"></div>
+              </span>
+              <el-input
+                ref="password"
+                v-model="loginForm.password"
+                :type="passwordType"
+                placeholder="请输入密码"
+                @keyup.enter.native="handleLogin"
+              />
+              <span class="show-pwd" @click="showPwd">
+                <div
+                  :class="passwordType === 'password' ? 'eye_close' : 'eye'"
+                ></div>
+              </span>
+            </el-form-item>
+
+            <el-button
+              :loading="loading"
+              type="primary"
+              style="width: 100%; margin-bottom: 30px"
+              @click="handleLogin"
+              >登录</el-button
+            >
+          </el-form>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { JSEncrypt } from "jsencrypt";
+export default {
+  name: "Login",
+  data() {
+    // 自定义校验规则
+    const validateUsername = (rule, value, callback) => {
+      if (!value) {
+        callback(new Error("请输入用户名"));
+      } else {
+        callback();
+      }
+    };
+    const validatePassword = (rule, value, callback) => {
+      if (value.length < 1) {
+        callback(new Error("密码不少于1位"));
+      } else {
+        callback();
+      }
+    };
+    return {
+      // 表格绑定数据
+      loginForm: {
+        username: "",
+        password: "",
+        token: "",
+      },
+      // 校验规则
+      loginRules: {
+        username: [
+          { required: true, trigger: "blur", validator: validateUsername },
+        ],
+        password: [
+          { required: true, trigger: "blur", validator: validatePassword },
+        ],
+      },
+      // 加载中效果
+      loading: false,
+      // 密码显示隐藏控制
+      passwordType: "password",
+      redirect: undefined,
+    };
+  },
+  watch: {
+    // 监听路由地址变化
+    $route: {
+      handler: function (route) {
+        this.redirect = route.query && route.query.redirect;
+      },
+      immediate: true,
+    },
+  },
+  methods: {
+    // 密码显示隐藏控制函数
+    showPwd() {
+      if (this.passwordType === "password") {
+        this.passwordType = "";
+      } else {
+        this.passwordType = "password";
+      }
+      // 重新渲染后使密码框聚焦
+      this.$nextTick(() => {
+        this.$refs.password.focus();
+      });
+    },
+    // 登录函数
+    handleLogin() {
+      // 校验用户名密码是否符合验证规则
+      this.$refs.loginForm.validate((valid) => {
+        // 符合验证规则
+        if (valid) {
+          this.loading = true;
+          this.loginForm.token = this.getPassword(8);
+          // this.API.role
+          //   .adminlogin({
+          //     admin_account: this.loginForm.username,
+          //     admin_password: this.loginForm.password,
+          //   })
+          //   .then((res) => {
+          //     console.log(res);
+          //   });
+          this.$store
+            .dispatch("user/login", this.loginForm)
+            .then(() => {
+              this.$message({
+                message: "登录成功",
+                type: "success",
+              });
+              this.$router.push({ path: this.redirect || "/car" });
+              this.loading = false;
+            })
+            .catch(() => {
+              this.$message.error("登录失败,用户名或密码错误");
+              this.loading = false;
+            });
+        }
+        // 不符合验证规则
+        else {
+          console.log("提交失败!!");
+          return false;
+        }
+      });
+    },
+    getPassword(num) {
+      // 定义一个空数组保存我们的密码
+      let passArrItem = [];
+
+      // 定义获取密码成员的方法
+      const getNumber = () => Math.floor(Math.random() * 10); // 0~9的数字
+      const getUpLetter = () =>
+        String.fromCharCode(Math.floor(Math.random() * 26) + 65); // A-Z
+      const getLowLetter = () =>
+        String.fromCharCode(Math.floor(Math.random() * 26) + 97); // a-z
+
+      // 将获取成员的方法保存在一个数组中方便用后面生成的随机index取用
+      const passMethodArr = [getNumber, getUpLetter, getLowLetter];
+
+      // 随机index
+      const getIndex = () => Math.floor(Math.random() * 3);
+
+      // 从0-9,a-z,A-Z中随机获取一项
+      const getPassItem = () => passMethodArr[getIndex()]();
+
+      // 不多解释
+      Array(num - 3)
+        .fill("")
+        .forEach(() => {
+          passArrItem.push(getPassItem());
+        });
+
+      const confirmItem = [getNumber(), getUpLetter(), getLowLetter()];
+
+      // 加上我们确认的三项,从而使生成的密码,大写字母、小写字母和数字至少各包含一个
+      passArrItem.push(...confirmItem);
+
+      // 转为字符串返回
+      return passArrItem.join("");
+    },
+  },
+};
+</script>
+
+<style lang="scss">
+/* 修复input 背景不协调 和光标变色 */
+
+$bg: #283443;
+$light_gray: #fff;
+$cursor: #fff;
+
+@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
+  .login-container .el-input input {
+    color: $bg;
+  }
+}
+
+/* 重置 element-ui css */
+.login-container {
+  .el-input {
+    display: inline-block;
+    height: 47px;
+    width: 85%;
+
+    input {
+      background: transparent;
+      border: 0px;
+      -webkit-appearance: none;
+      border-radius: 0px;
+      padding: 12px 5px 12px 15px;
+      color: $bg;
+      height: 47px;
+      caret-color: $bg;
+
+      &:-webkit-autofill {
+        box-shadow: 0 0 0px 1000px #e5e5e5 inset !important;
+        -webkit-text-fill-color: $bg !important;
+      }
+    }
+  }
+
+  .el-form-item {
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    background: rgba(0, 0, 0, 0.1);
+    border-radius: 5px;
+    color: #454545;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+$bg: #2d3a4b;
+$dark_gray: #889aa4;
+
+.login-container {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1280px;
+  min-height: 800px;
+  background: url(../../assets/images/bg.jpg);
+  background-size: 100% 100%;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  .shadow {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    box-shadow: 0px 10px 30px 0px rgba(60, 108, 254, 0.36);
+
+    .img {
+      width: 666px;
+      height: 848px;
+      background: linear-gradient(
+        -40deg,
+        rgba(83, 94, 240, 0.5),
+        rgba(67, 147, 248, 0.5)
+      );
+      border-radius: 20px 0px 0px 20px;
+      img {
+        width: 586px;
+        height: 531px;
+        margin: 136px 0 0 37px;
+      }
+    }
+    .form {
+      width: 740px;
+      height: 848px;
+      background: #fff;
+      border-radius: 0 20px 20px 0;
+      position: relative;
+      img {
+        position: absolute;
+        top: 61px;
+        left: 53px;
+        // margin: 61px 0 0 53px;
+      }
+      .title {
+        position: absolute;
+        top: 217px;
+        left: 141px;
+        font-size: 50px;
+        font-family: Microsoft YaHei;
+        font-weight: bold;
+        color: #132949;
+      }
+    }
+    .login {
+      height: 36px;
+      font-size: 30px;
+      font-family: Adobe Heiti Std;
+      font-weight: normal;
+      color: #132949;
+      position: absolute;
+      top: 334px;
+      left: 345px;
+      border-bottom: 4px solid rgba(0, 118, 254, 0.3);
+    }
+    .name {
+      position: absolute;
+      top: 406px;
+      left: 139px;
+      // z-index: 99;
+
+      .login-form {
+        width: 480px;
+        position: absolute;
+        .el-input {
+          font-size: 20px;
+        }
+        .el-form-item {
+          margin-bottom: 29px;
+        }
+        /deep/ .el-form-item__content {
+          border: 1px solid #d9d9d9;
+          background-color: #fff;
+          width: 481px;
+          height: 64px;
+          border-radius: 6px;
+          display: flex;
+          align-items: center;
+          .el-form-item__error {
+            font-size: 18px;
+          }
+        }
+        /deep/ .el-button {
+          width: 485px !important;
+          height: 74px;
+          background: #2b4cfe;
+          box-shadow: 0px 3px 16px 0px rgba(43, 71, 224, 0.58);
+          border-radius: 6px;
+          font-size: 22px;
+          font-family: Microsoft YaHei-3970(82674968);
+          font-weight: 400;
+          color: #ffffff;
+        }
+      }
+
+      .svg-container {
+        padding: 6px 5px 6px 15px;
+        color: $bg;
+        vertical-align: middle;
+        width: 30px;
+        display: inline-block;
+
+        .user {
+          width: 25px;
+          height: 25px;
+          background: url(../../assets/images/name.png);
+          background-size: 100% 100%;
+        }
+        .password {
+          width: 25px;
+          height: 25px;
+          background: url(../../assets/images/code.png);
+          background-size: 100% 100%;
+        }
+      }
+
+      .show-pwd {
+        right: 10px;
+        font-size: 16px;
+        color: $bg;
+        cursor: pointer;
+        user-select: none;
+        .eye_close {
+          margin-right: 15px;
+          width: 25px;
+          height: 25px;
+          background: url(../../assets/images/eye_close.png);
+          background-size: 100% 100%;
+        }
+        .eye {
+          margin-right: 15px;
+          width: 25px;
+          height: 25px;
+          background: url(../../assets/images/eye.png);
+          background-size: 100% 100%;
+        }
+      }
+    }
+  }
+}
+</style>

Fichier diff supprimé car celui-ci est trop grand
+ 1071 - 0
src/views/role/index.vue


+ 0 - 0
static/.gitkeep


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff