soft5566 пре 3 година
родитељ
комит
1f1f6e9912
100 измењених фајлова са 29372 додато и 1 уклоњено
  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. 110 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. 33 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. 59 0
      src/api/api.js
  28. 17 0
      src/api/doorLock.js
  29. 7 0
      src/api/fileServes.js
  30. 31 0
      src/api/fingerprint.js
  31. 32 0
      src/api/hotelAdmin.js
  32. 71 0
      src/api/hotelOrder.js
  33. 32 0
      src/api/hotelStaff.js
  34. 32 0
      src/api/houseType.js
  35. 31 0
      src/api/icCard.js
  36. 12 0
      src/api/normalUser.js
  37. 27 0
      src/api/room.js
  38. 25 0
      src/api/roomRealTimeStatu.js
  39. 17 0
      src/api/roomThirdSetting.js
  40. 13 0
      src/api/stat.js
  41. 13 0
      src/api/systemSetup.js
  42. 17 0
      src/api/systemnotice.js
  43. 1 0
      src/assets/icons/svg/account-active.svg
  44. 1 0
      src/assets/icons/svg/account.svg
  45. 1 0
      src/assets/icons/svg/edit.svg
  46. 1 0
      src/assets/icons/svg/error.svg
  47. 1 0
      src/assets/icons/svg/home-active.svg
  48. 1 0
      src/assets/icons/svg/home.svg
  49. 1 0
      src/assets/icons/svg/item-logo.svg
  50. 1 0
      src/assets/icons/svg/order-active.svg
  51. 1 0
      src/assets/icons/svg/order.svg
  52. 1 0
      src/assets/icons/svg/quit.svg
  53. 1 0
      src/assets/icons/svg/sousuo.svg
  54. 1 0
      src/assets/icons/svg/staff-active.svg
  55. 1 0
      src/assets/icons/svg/staff.svg
  56. 1 0
      src/assets/icons/svg/stat-active.svg
  57. 1 0
      src/assets/icons/svg/stat.svg
  58. 1 0
      src/assets/icons/svg/system-active.svg
  59. 1 0
      src/assets/icons/svg/system.svg
  60. 1 0
      src/assets/icons/svg/tuifang.svg
  61. 1 0
      src/assets/icons/svg/xiaoxizhongxin.svg
  62. BIN
      src/assets/images/bg.png
  63. BIN
      src/assets/images/eye.png
  64. BIN
      src/assets/images/eye_close.png
  65. BIN
      src/assets/images/password.png
  66. BIN
      src/assets/images/user.png
  67. 0 0
      src/assets/scss/variable.scss
  68. 81 0
      src/components/SvgIcon/index.vue
  69. 144 0
      src/layout/components/Navbar.vue
  70. 158 0
      src/layout/components/NavbarItem.vue
  71. 83 0
      src/layout/index.vue
  72. 33 0
      src/main.js
  73. 152 0
      src/router/index.js
  74. 14 0
      src/store/getters.js
  75. 28 0
      src/store/index.js
  76. 25 0
      src/store/modules/inform.js
  77. 206 0
      src/store/modules/user.js
  78. 15 0
      src/utils/auth.js
  79. 65 0
      src/utils/http.js
  80. 128 0
      src/utils/request.js
  81. 22 0
      src/utils/rsa.js
  82. 591 0
      src/views/account/index.vue
  83. 1347 0
      src/views/fingerprint/index.vue
  84. 2051 0
      src/views/home/index.vue
  85. 1270 0
      src/views/icCard/index.vue
  86. 402 0
      src/views/inform/index.vue
  87. 272 0
      src/views/login/index.vue
  88. 787 0
      src/views/order/index.vue
  89. 701 0
      src/views/staff/index.vue
  90. 360 0
      src/views/stat/index.vue
  91. 281 0
      src/views/system/index.vue
  92. 0 0
      static/.gitkeep
  93. 27 0
      test/e2e/custom-assertions/elementCount.js
  94. 46 0
      test/e2e/nightwatch.conf.js
  95. 48 0
      test/e2e/runner.js
  96. 19 0
      test/e2e/specs/test.js
  97. 7 0
      test/unit/.eslintrc
  98. 30 0
      test/unit/jest.conf.js
  99. 3 0
      test/unit/setup.js
  100. 0 0
      test/unit/specs/HelloWorld.spec.js

+ 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 @@
-#hotelorder_student_manage
+# 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)
+  }
+}


+ 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'
+  }
+}

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

@@ -0,0 +1,110 @@
+"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, //将useLoackIp设置为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: [
+              //下面两个地方,直接复制即可
+              `App runing at: `,
+              ` - Local: http://localhost:${port}`, //配置这里
+              ` - Network: http://${require("ip").address()}:${port}` //配置这里
+            ]
+            // messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${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: {
+    // http://192.168.161.34:8089线下地址
+    // https://chtech.ncjti.edu.cn/hotel/ihotel-api线上地址
+    // Paths
+    assetsSubDirectory: "static",
+    assetsPublicPath: "/hotel/manage/",
+    proxyTable: {
+      "/hotel/ihotel-api": {
+        // target: 'http://192.168.161.34:8089',
+        target: "https://chtech.ncjti.edu.cn/hotel/ihotel-api",
+        secure: false, // 这是签名认证,http和https区分的参数设置
+        changeOrigin: true,
+        pathRewrite: {
+          "^/hotel/ihotel-api": ""
+        }
+      }
+    },
+
+    // Various Dev Server settings
+    host: "0.0.0.0", // can be overwritten by process.env.HOST
+    // host: "localhost",
+    port: 8899, // 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: "/hotel/manage/",
+
+    /**
+     * 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>智慧校园公寓管理</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

Разлика између датотеке није приказан због своје велике величине
+ 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"
+  ]
+}

+ 33 - 0
src/App.vue

@@ -0,0 +1,33 @@
+<template>
+  <div id="app">
+    <router-view></router-view>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "App",
+  mounted() {},
+};
+</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
+  })
+}

+ 59 - 0
src/api/api.js

@@ -0,0 +1,59 @@
+// import http from '../utils/http'
+import * as doorLock from "./doorLock";
+import * as fileServes from "./fileServes";
+import * as hotelOrder from "./hotelOrder";
+import * as hotelAdmin from "./hotelAdmin";
+import * as hotelStaff from "./hotelStaff";
+import * as houseType from "./houseType";
+import * as normalUser from "./normalUser";
+import * as room from "./room";
+import * as systemnotice from "./systemnotice";
+import * as systemSetup from "./systemSetup";
+import * as roomRealTimeStatu from "./roomRealTimeStatu";
+import * as roomThirdSetting from "./roomThirdSetting";
+import * as icCard from "./icCard";
+import * as fingerprint from "./fingerprint";
+import * as stat from "./stat";
+//
+/**
+ *  @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 {
+  doorLock, // 门锁
+  fileServes, // 文件服务
+  hotelOrder, // 酒店订单
+  hotelAdmin, // 酒店管理员
+  hotelStaff, // 酒店员工
+  houseType, // 房型
+  normalUser, // 普通用户
+  room, // 房间
+  systemnotice, // 系统通知
+  systemSetup, // 系统设置
+  roomRealTimeStatu, // 实时房态
+  roomThirdSetting, // 房间第三方平台设置
+  icCard, // IC卡
+  stat, // 统计报表
+  fingerprint // 指纹
+};

+ 17 - 0
src/api/doorLock.js

@@ -0,0 +1,17 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/roomDoorLock"
+
+// 根据房间id查询密码锁
+export function roomDoorLockRoomId(roomId) {
+    return http.get(`${resquest}/room/${roomId}`)
+}
+
+// 根据订单id查询密码锁
+export function roomDoorLockOrderId(orderId) {
+    return http.get(`${resquest}/order/${orderId}`)
+}
+
+// 根据订单id下发密码锁
+export function sendPassword(Id) {
+    return http.get(`${resquest}/sendPassword/${Id}`)
+}

+ 7 - 0
src/api/fileServes.js

@@ -0,0 +1,7 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel"
+
+// 获取上传文件签名
+export function getListAPI(params) {
+    return http.get(`${resquest}/oss/aliyun/policy`, params)
+}

+ 31 - 0
src/api/fingerprint.js

@@ -0,0 +1,31 @@
+import http from "../utils/http";
+let resquest = "/hotel/ihotel-api/ihotel/condition/fingerprint";
+
+// fingerprint查询
+export function queryInfo(params) {
+  return http.get(`${resquest}/queryInfo/${params}`);
+}
+
+// IC卡查询-详情
+export function queryICInfoById(params) {
+  return http.get(`${resquest}/ic/queryICInfoById`, params);
+}
+
+// 指纹新增
+export function add(params) {
+  return http.post(`${resquest}/add`, params);
+}
+//  指纹修改  hotel/ihotel-api/ihotel/condition/fingerprint/update
+export function update(params) {
+  return http.post(`${resquest}/update`, params);
+}
+
+// 指纹解绑  hotel/ihotel-api/ihotel/condition/fingerprint/unbundFingerprint
+export function unbundIC(params) {
+  return http.get(`${resquest}/unbundFingerprint`, params);
+}
+
+// 指纹导出详情
+// export function downLoadICInfo(params) {
+//   return http.get(`${resquest}/downLoadICInfo`, params, "", "blob");
+// }

+ 32 - 0
src/api/hotelAdmin.js

@@ -0,0 +1,32 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/hotelAdmin"
+
+// 管理员登录
+export function hotelStaffLogin(params) {
+    return http.post(`${resquest}/login`, params)
+}
+
+// 管理员列表
+export function hotelStaffList(params) {
+    return http.get(`${resquest}/list`, params)
+}
+
+// 新增管理员
+export function hotelStaffAdd(data) {
+    return http.post(`${resquest}/add`, data)
+}
+
+// 修改管理员
+export function hotelStaffUpdate(data) {
+    return http.put(`${resquest}/update`, data)
+}
+
+// 修改密码
+export function updatePassword(data) {
+    return http.put(`${resquest}/updatePassword`, data)
+}
+
+// 删除管理员
+export function hotelStaffDelete(data) {
+    return http.delete(`${resquest}/delete`, data)
+}

+ 71 - 0
src/api/hotelOrder.js

@@ -0,0 +1,71 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/hotelOrder"
+let contentType = { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' }
+//  获取请求令牌
+export function hotelOrderToken(params) {
+    return http.get(`${resquest}/user/submit/token`, params)
+}
+
+//  确认订单    
+export function hotelOrderConfirm(params) {
+    return http.get(`${resquest}/user/order/confirm`, params)
+}
+
+//  提交订单
+export function hotelOrderSubmit(params) {
+    return http.post(`${resquest}/user/order/submit`, params)
+}
+
+//  订单列表【用户端】  
+export function hotelOrderUserPage(params) {
+    return http.get(`${resquest}/user/order/page`, params)
+}
+
+//  订单列表【管理端】
+export function hotelOrderAdminPage(params) {
+    return http.get(`${resquest}/admin/order/page`, params)
+}
+
+//  订单详情 【用户端】
+export function hotelOrderUserOrderId(orderId, params) {
+    return http.get(`${resquest}/user/order/${orderId}`, params)
+}
+
+//   获取订单支付参数
+export function hotelOrderOrderPay(orderId, params) {
+    return http.get(`${resquest}/user/order/pay/${orderId}`, params)
+}
+
+//   获取订单支付参数
+export function hotelOrderOrderBill(orderId, params) {
+    return http.get(`${resquest}/user/order/bill/${orderId}`, params)
+}
+
+//   导出订单【管理端】
+export function downOrder(params) {
+    return http.get(`${resquest}/admin/order/downOrder`, params, '', 'blob')
+}
+
+
+
+
+//  删除订单【管理端】
+export function hotelOrderAdminDelete(orderId, params) {
+    return http.delete(`${resquest}/admin/order/${orderId}`, params, contentType)
+}
+
+// 取消订单
+export function hotelOrdercancel(orderId, params) {
+    return http.put(`${resquest}/user/order/cancel/${orderId}`, params, contentType)
+}
+
+// 办理入住
+export function hotelOrderHold(orderId, params) {
+    return http.put(`${resquest}/user/order/hold/${orderId}`, params, contentType)
+}
+
+// 办理退房
+export function hotelOrderReturn(orderId, params) {
+    return http.put(`${resquest}/user/order/return/${orderId}`, params, contentType)
+}
+

+ 32 - 0
src/api/hotelStaff.js

@@ -0,0 +1,32 @@
+import http from "../utils/http";
+let resquest = "/hotel/ihotel-api/ihotel/ihotel/hotelStaff";
+
+// 员工列表
+export function hotelStaffList(params) {
+  return http.get(`${resquest}/list`, params);
+}
+
+// 员工信息
+export function hotelStaffInfo(id, params) {
+  return http.get(`${resquest}/info/${id}`, params);
+}
+
+// 新增员工
+export function hotelStaffSave(params) {
+  return http.post(`${resquest}/save`, params);
+}
+
+// 修改管理员
+export function hotelStaffUpdate(data) {
+  return http.put(`${resquest}/update`, data);
+}
+
+// 删除管理员
+export function hotelStaffDelete(data) {
+  return http.delete(`${resquest}/delete`, data);
+}
+
+// 员工类型和员工
+export function departList(params) {
+  return http.get(`${resquest}/departList`, params);
+}

+ 32 - 0
src/api/houseType.js

@@ -0,0 +1,32 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/roomType"
+
+// 房型列表(客户端)
+export function roomTypeList(params) {
+    return http.get(`${resquest}/roomType/list`, params)
+}
+
+// 房型列表(管理端)
+export function roomTypePage(params) {
+    return http.get(`${resquest}/roomType/page`, params)
+}
+
+// 房型详细信息
+export function roomTypeInfo(params) {
+    return http.post(`${resquest}/info/{id}`, params)
+}
+
+// 新增房型
+export function roomTypeSave(params) {
+    return http.post(`${resquest}/save`, params)
+}
+
+// 修改房型
+export function roomTypeUpdate(params) {
+    return http.put(`${resquest}/update`, params)
+}
+
+// 删除房型
+export function roomTypeDelete(params) {
+    return http.delete(`${resquest}/delete`, params)
+}

+ 31 - 0
src/api/icCard.js

@@ -0,0 +1,31 @@
+import http from "../utils/http";
+let resquest = "/hotel/ihotel-api/ihotel/condition";
+
+// IC卡查询
+export function icqueryInfo(params) {
+  return http.get(`${resquest}/ic/queryInfo/${params}`);
+}
+
+// IC卡查询-详情
+export function queryICInfoById(params) {
+  return http.get(`${resquest}/ic/queryICInfoById`, params);
+}
+
+//  IC卡新增
+export function add(params) {
+  return http.post(`${resquest}/ic/add`, params);
+}
+//  IC卡修改
+export function update(params) {
+  return http.post(`${resquest}/ic/update`, params);
+}
+
+// IC卡解绑
+export function unbundIC(params) {
+  return http.get(`${resquest}/ic/unbundIC`, params);
+}
+
+// IC卡导出详情
+export function downLoadICInfo(params) {
+  return http.get(`${resquest}/downLoadICInfo`, params, "", "blob");
+}

+ 12 - 0
src/api/normalUser.js

@@ -0,0 +1,12 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/hotelUser"
+
+// 微校授权回调地址
+export function hotelUserWX(params) {
+    return http.get(`${resquest}/weixiaoAuth`, params)
+}
+
+//  获取用户信息
+export function hotelUserInfo(params) {
+    return http.get(`${resquest}/userInfo`, params)
+}

+ 27 - 0
src/api/room.js

@@ -0,0 +1,27 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/room"
+
+// 房间列表(分页)
+export function roomPage(params) {
+    return http.get(`${resquest}/page`, params)
+}
+
+// 房间列表(分页分组)
+export function roomPageGroup(params) {
+    return http.get(`${resquest}/page/group`, params)
+}
+
+// 新增房间
+export function roomSave(params) {
+    return http.post(`${resquest}/save`, params)
+}
+
+// 修改房间
+export function roomUpdate(params) {
+    return http.put(`${resquest}/update`, params)
+}
+
+// 删除房间
+export function roomDelete(params) {
+    return http.delete(`${resquest}/delete`, params)
+}

+ 25 - 0
src/api/roomRealTimeStatu.js

@@ -0,0 +1,25 @@
+import http from "../utils/http";
+let resquest = "/hotel/ihotel-api/ihotel/roomRealTimeStatu";
+
+//  房态信息
+export function realData(params) {
+  return http.get(`${resquest}/realData/${params}`);
+}
+
+// 脏房修改为空闲房间   /hotel/ihotel-api/ihotel/roomRealTimeStatu/clean/cleanByStatuId
+let ContentType = {
+  "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
+};
+export function clean(params) {
+  return http.post(`${resquest}/clean/cleanByStatuId`, params);
+}
+
+// 锁定房间  /hotel/ihotel-api/ihotel/roomRealTimeStatu/lock
+export function lock(params) {
+  return http.put(`${resquest}/lock`, params);
+}
+
+// 房间转为脏房
+export function toDirty(params) {
+  return http.put(`${resquest}/toDirty`, params);
+}

+ 17 - 0
src/api/roomThirdSetting.js

@@ -0,0 +1,17 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/roomThirdSetting"
+
+//  根据房间id查询
+export function settingId(params) {
+    return http.get(`${resquest}/setting/${params}`)
+}
+
+//  电控制
+export function changeElectric(roomId, operType) {
+    return http.get(`${resquest}/changeElectric/${roomId}/${operType}`)
+}
+
+// 修改房间第三方平台设置
+export function Updata(params) {
+    return http.post(`${resquest}/update`, params)
+}

+ 13 - 0
src/api/stat.js

@@ -0,0 +1,13 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/report"
+
+// 12.1. 查询统计报表数据【管理端】
+export function queryReport(params) {
+    return http.get(`${resquest}/queryReport`, params)
+}
+
+//  导出报表【管理端】
+export function downLoadReport(params) {
+    return http.get(`${resquest}/downLoadReport`, params, '', 'blob')
+}
+

+ 13 - 0
src/api/systemSetup.js

@@ -0,0 +1,13 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/systemSetting"
+
+// 获取系统设置
+export function systemSettingInfo(params) {
+    return http.get(`${resquest}/info`, params)
+}
+
+// 修改系统设置
+export function systemnoticeUpdate(params) {
+    return http.put(`${resquest}/update`, params)
+}
+

+ 17 - 0
src/api/systemnotice.js

@@ -0,0 +1,17 @@
+import http from '../utils/http'
+let resquest = "/hotel/ihotel-api/ihotel/systemNotice"
+
+// 通知列表
+export function systemnoticeList(params) {
+    return http.get(`${resquest}/list`, params)
+}
+
+// 标记为已读
+export function systemnoticeRead(params) {
+    return http.put(`${resquest}/read`, params)
+}
+
+// 删除通知
+export function systemnoticeDelete(params) {
+    return http.delete(`${resquest}/delete`, params)
+}

Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/account-active.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/account.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/edit.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/error.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/home-active.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/home.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/item-logo.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/order-active.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/order.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/quit.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/sousuo.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/staff-active.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/staff.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/stat-active.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/stat.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/system-active.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/system.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/tuifang.svg


Разлика између датотеке није приказан због своје велике величине
+ 1 - 0
src/assets/icons/svg/xiaoxizhongxin.svg


BIN
src/assets/images/bg.png


BIN
src/assets/images/eye.png


BIN
src/assets/images/eye_close.png


BIN
src/assets/images/password.png


BIN
src/assets/images/user.png


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


+ 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>
+      <el-badge :value="$store.state.inform.readFlag" class="item">
+        <!-- <svg-icon icon-class="xiaoxizhongxin" /> -->
+        <div @click="informs">
+          <IconSvg :W="28" :H="31" name="xiaoxizhongxin" />
+        </div>
+      </el-badge>
+      <div class="photo">
+        <el-avatar
+          icon="el-icon-user-solid"
+          style="width: 53px; height: 53px; margin: 0; line-height: 53px"
+        >
+        </el-avatar>
+      </div>
+      <div class="name">{{ $store.state.user.userName }}</div>
+      <div class="out" @click="outLogin">
+        <IconSvg :W="16" :H="16" name="quit" />
+        <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(255, 255, 255, 1);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .nav-title {
+    display: inline-block;
+    width: 324px;
+    height: 53px;
+    line-height: 53px;
+    color: rgba(0, 0, 0, 1);
+    font-size: 36px;
+    font-weight: 500;
+    margin-left: 22px;
+  }
+
+  .right {
+    display: flex;
+    align-items: center;
+
+    .time {
+      width: 241px;
+      height: 27px;
+      line-height: 27px;
+      color: rgba(102, 102, 102, 1);
+      font-size: 18px;
+      font-weight: 400;
+      margin-right: 35px;
+      // float: right;
+    }
+
+    .photo {
+      width: 53px;
+      height: 53px;
+      margin: 0 4px 0 38px;
+    }
+
+    .name {
+      // width: 50px;
+      height: 28px;
+      color: rgba(51, 51, 51, 1);
+      font-size: 14px;
+      font-weight: 500;
+      line-height: 28px;
+      padding: 0 15px 0 5px;
+      margin-right: 13px;
+      border-right: 1px solid rgba(204, 204, 204, 1);
+    }
+
+    .out {
+      display: flex;
+      align-items: center;
+      padding-right: 18px;
+      cursor: pointer;
+
+      .title {
+        margin-left: 6px;
+        font-size: 14px;
+      }
+    }
+  }
+}
+</style>

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

@@ -0,0 +1,158 @@
+<template>
+  <div class="navitem">
+    <el-radio-group class="logo">
+      <IconSvg :W="44" :H="44" name="item-logo" />
+    </el-radio-group>
+    <el-menu
+      text-color="#fff"
+      background-color="rgba(41, 109, 227, 1)"
+      :default-active="$route.path"
+      class="el-menu-vertical-demo"
+      @select="handleOpen"
+      :collapse="isCollapse"
+      router
+    >
+      <el-menu-item v-for="item in roles" :key="item.route" :index="'/'+item.route">
+        <div class="icons">
+          <div v-if="ItemIndex== `/${item.route}`">
+            <IconSvg :W="26" :H="25" :name="`${item.route}-active`" />
+          </div>
+          <div v-else>
+            <IconSvg :W="26" :H="25" :name="item.route" />
+          </div>
+          <!-- <IconSvg
+            :W="26"
+            :H="25"
+            :name="ItemIndex == `/${item.route}`  ? `${item.route}-active` : `${item.route}`"
+          />-->
+        </div>
+        <span slot="title">{{item.title}}</span>
+      </el-menu-item>
+      <!-- <el-menu-item index="/order">
+        <div class="icons">
+          <IconSvg :W="26" :H="25" :name="ItemIndex == '/order' ? 'order-active' : 'order'" />
+        </div>
+        <span slot="title">订单管理</span>
+      </el-menu-item>
+      <el-menu-item index="/staff">
+        <div class="icons">
+          <IconSvg :W="26" :H="25" :name="ItemIndex == '/staff' ? 'staff-active' : 'staff'" />
+        </div>
+        <span slot="title">员工管理</span>
+      </el-menu-item>
+      <el-menu-item index="/account">
+        <div class="icons">
+          <IconSvg :W="26" :H="25" :name="ItemIndex == '/account' ? 'account-active' : 'account'" />
+        </div>
+        <span slot="title">账号管理</span>
+      </el-menu-item>
+      <el-menu-item index="/stat">
+        <div class="icons">
+          <IconSvg :W="26" :H="25" :name="ItemIndex == '/stat' ? 'stat-active' : 'stat'" />
+        </div>
+        <span slot="title">统计报表</span>
+      </el-menu-item>
+      <el-menu-item index="/system">
+        <div class="icons">
+          <IconSvg :W="26" :H="25" :name="ItemIndex == '/system' ? 'system-active' : 'system'" />
+        </div>
+        <span slot="title">系统设置</span>
+      </el-menu-item>-->
+    </el-menu>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "NavbarItem",
+  data() {
+    return {
+      isCollapse: false,
+      roles: []
+    };
+  },
+  created() {
+    this.getData();
+  },
+  computed: {
+    ItemIndex: {
+      get() {
+        return this.$route.path;
+      },
+      set(val) {}
+    }
+  },
+  methods: {
+    handleOpen(index, indexPath) {
+      // console.log(index, indexPath);
+    },
+
+    getData() {
+      // console.log(this.$store.state.user.roles);
+      this.$store.state.user.roles.forEach((item, index) => {
+        if (item == "home") {
+          this.roles[index] = { route: item, title: "房态管理" };
+        } else if (item == "order") {
+          this.roles[index] = { route: item, title: "订单管理" };
+        } else if (item == "staff") {
+          this.roles[index] = { route: item, title: "员工管理" };
+        } else if (item == "account") {
+          this.roles[index] = { route: item, title: "账号管理" };
+        } else if (item == "stat") {
+          this.roles[index] = { route: item, title: "统计报表" };
+        } else if (item == "system") {
+          this.roles[index] = { route: item, title: "系统设置" };
+        }
+      });
+      // console.log(this.roles);
+    }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.navitem {
+  width: 260px;
+
+  .logo {
+    height: 96px;
+    border-bottom: 1px solid rgba(255, 255, 255, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .el-menu {
+    margin-top: 33px;
+
+    .is-active {
+      background-color: #fff !important;
+      color: rgba(41, 109, 227, 1);
+    }
+
+    .el-menu-item {
+      height: 52px;
+      line-height: 0;
+      margin-bottom: 13px;
+      font-size: 18px;
+      display: flex;
+      align-items: center;
+      border: 1px solid transparent;
+      box-sizing: border-box;
+
+      .icons {
+        margin-left: 27px;
+      }
+
+      span {
+        width: 87px;
+        height: 26px;
+        display: flex;
+        align-items: center;
+        padding-left: 16px;
+      }
+    }
+  }
+}
+</style>

+ 83 - 0
src/layout/index.vue

@@ -0,0 +1,83 @@
+<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> -->
+          <keep-alive>
+            <!-- <router-view></router-view> -->
+            <router-view v-if="$route.meta.keepAlive"></router-view>
+          </keep-alive>
+          <router-view v-if="!$route.meta.keepAlive"></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() {
+    this.API.systemnotice.systemnoticeList({ pageSize: 100 }).then(res => {
+      let number = 0;
+      res.data.list.forEach(item => {
+        if (item.readFlag == 0) {
+          number += 1;
+        }
+        item.createTime = item.createTime.replace("T", " ");
+      });
+      this.$store.dispatch("inform/setreadFlag", number);
+      if (number == 0) {
+        this.$store.dispatch("inform/setreadFlag", "");
+      }
+    });
+  },
+  methods: {}
+};
+</script>
+
+
+<style scoped lang="scss">
+.box {
+  width: 100%;
+  height: 100%;
+
+  .el-header {
+    color: #333;
+    text-align: center;
+    width: 1660px;
+    height: 96px !important;
+    background: rgba(255, 255, 255, 1);
+    box-shadow: 0px 3px 10px rgba(41, 109, 227, 0.1);
+  }
+
+  .el-aside {
+    background-color: #d3dce6;
+    color: #333;
+    text-align: center;
+    height: 100%;
+    width: 260px !important;
+    background: rgba(41, 109, 227, 1);
+  }
+
+  .el-main {
+    background: #fff;
+    padding: 0;
+    margin: 14px 0 0 25px;
+  }
+}
+</style>

+ 33 - 0
src/main.js

@@ -0,0 +1,33 @@
+// 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 store from './store';
+import router from './router'
+import axios from "axios";
+
+
+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.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/>'
+})

+ 152 - 0
src/router/index.js

@@ -0,0 +1,152 @@
+import Vue from "vue";
+
+import Router from "vue-router";
+import Layout from "@/layout";
+import store from "@/store";
+import Cookies from "js-cookie";
+Vue.use(Router);
+//不管是超级管理员还是普通用户都可以看到的页面
+export const constantRoutes = [
+  // 登录页
+  {
+    path: "/login",
+    // meta: { isAuth: true },
+    component: () => import("@/views/login/index"),
+    hidden: true
+  },
+  {
+    path: "/",
+    name: "Layout",
+    // redirect: '/login',
+    // alwaysShow: true,
+    component: Layout,
+    children: [
+      {
+        path: "inform",
+        name: "Inform",
+        meta: { isAuth: true, title: "系统通知" },
+        component: () => import("@/views/inform")
+      },
+      {
+        path: "home/ICcard",
+        name: "icCard",
+        meta: { isAuth: true, title: "IC卡管理" },
+        component: () => import("@/views/icCard")
+      },
+      {
+        path: "home/fingerprint",
+        name: "fingerprint",
+        meta: { isAuth: true, title: "指纹管理" },
+        component: () => import("@/views/fingerprint")
+      }
+    ]
+  }
+  // {
+  //     path: '*',
+  //     redirect: '/login',
+  //     // meta: { isAuth: true },
+  //     // component: () => import('@/views/login/index'),
+  //     hidden: true
+  // }
+];
+const router = new Router({
+  // mode: 'history', // require service support
+  scrollBehavior: () => ({ y: 0 }),
+  routes: constantRoutes
+});
+
+export default router;
+router.beforeEach((to, from, next) => {
+  //判断 如果cook是否存在,可以进去
+  if (Cookies.get("cook") == store.state.user.token) {
+    // console.log(1111)
+    next(); //放行
+  } else {
+    if (to.path === "/") {
+      // console.log(3333);
+      next({ path: "/login" });
+    } else {
+      // console.log(4444444);
+      if (
+        Cookies.get("cook") &&
+        store.state.user.statRoutes[0].children.length === 3
+      ) {
+        // console.log(rsaPassWord);
+        store
+          .dispatch("user/login", {
+            username: Cookies.get("user"),
+            password: Cookies.get("password")
+          })
+          .then(() => {
+            // console.log('重新请求token');
+            next({ ...to });
+          })
+          .catch(error => {
+            // console.log('重新请求失败');
+            Cookies.remove("cook");
+          });
+      } else {
+        // console.log(666666);
+        // console.log(to);
+        if (to.path !== "/login") {
+          next({ path: "/login" });
+        } else {
+          next();
+        }
+      }
+    }
+  }
+});
+
+//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
+// router.beforeEach((to, from, next) => {
+//     console.log('000000');
+//     if (to.meta.isAuth) {
+//         //判断 如果cook是否存在,可以进去
+//         if (Cookies.get('cook') == store.state.user.token) {
+//             console.log(1111)
+//             next()  //放行
+//         } else {
+//             // alert('登录身份失效,请重新登录')
+//             // router.addRoute("Layout", {
+//             //     path: item,
+//             //     name: item,
+//             //     meta: { isAuth: true },
+//             //     component: () => import(`@/views/${item}`),
+//             // });
+//             console.log(22222);
+//             next({ path: "/login" })
+//         }
+//     } else {
+//         // 否则,放行
+//         console.log(3333);
+//         if (Cookies.get('cook')) {
+//             if (store.state.user.statRoutes[0].children.length === 3) {
+//                 // console.log(rsaPassWord);
+//                 store.dispatch("user/login", {
+//                     username: Cookies.get('user'),
+//                     password: Cookies.get('password')
+//                 })
+//                     .then(() => {
+//                         console.log(444444);
+//                         next({ ...to })
+//                         // next()
+//                     })
+//                     .catch((error) => {
+//                         console.log(55555);
+//                         next()
+//                     });
+
+//             } else {
+//                 console.log(666666);
+//                 next()
+//             }
+//         } else {
+//             console.log(777777);
+//             next()
+//         }
+
+//         // next()
+
+//     }
+// })

+ 14 - 0
src/store/getters.js

@@ -0,0 +1,14 @@
+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,
+    statRoutes: (state) => state.user.statRoutes,
+    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.users
+            }
+        }
+    })]
+})
+
+
+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
+}

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

@@ -0,0 +1,206 @@
+import { login, logout, getInfo } from "@/api/acl/user";
+import { getToken, setToken, removeToken } from "@/utils/auth";
+import { resetRouter } from "@/router";
+import router from "@/router";
+import api from "@/api/api";
+import Layout from "@/layout";
+const getDefaultState = () => {
+  return {
+    token: "",
+    userName: "",
+    headImage: "",
+    userType: "",
+    telPhone: "",
+    time: new Date(),
+    avatar: "",
+    roles: [],
+    statRoutes: [
+      {
+        path: "/",
+        name: "Layout",
+        redirect: "/login",
+        alwaysShow: true,
+        component: Layout,
+        children: [
+          // {
+          //     path: 'home',
+          //     name: 'Home',
+          //     meta: {
+          //         isAuth: true,
+          //         title: '主页',
+          //         keepAlive: true //缓存组件
+          //     },
+          //     component: () => import('@/views/home'),
+          // },
+          {
+            path: "inform",
+            name: "Inform",
+            meta: { isAuth: true, title: "系统通知" },
+            component: () => import("@/views/inform")
+          },
+          {
+            path: "home/ICcard",
+            name: "icCard",
+            meta: { isAuth: true, title: "IC卡管理" },
+            component: () => import("@/views/icCard")
+          },
+          {
+            path: "home/fingerprint",
+            name: "fingerprint",
+            meta: { isAuth: true, title: "指纹管理" },
+            component: () => import("@/views/fingerprint")
+          }
+        ]
+      }
+    ],
+    buttons: []
+  };
+};
+
+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.userName;
+    state.headImage = userInfo.headImage;
+    state.userType = userInfo.userType;
+    state.telPhone = userInfo.telPhone;
+  },
+
+  SET_ROLES: (state, roles) => {
+    if (typeof roles === "string") {
+      state.roles = roles.split("/");
+      // console.log('roles是字符串');
+    } else {
+      state.roles = roles;
+    }
+    state.roles.forEach(item => {
+      if (item == "home") {
+        state.statRoutes[0].children.push({
+          path: item,
+          name: item,
+          meta: { isAuth: true, keepAlive: true },
+          component: () => import(`@/views/${item}`)
+        });
+      } else {
+        state.statRoutes[0].children.push({
+          path: item,
+          name: item,
+          meta: { isAuth: true },
+          component: () => import(`@/views/${item}`)
+        });
+      }
+      // router.addRoute([...state.statRoutes])
+      if (item == "home") {
+        router.addRoute("Layout", {
+          path: item,
+          name: item,
+          meta: { isAuth: true, keepAlive: true },
+          component: () => import(`@/views/${item}`)
+        });
+      } else {
+        router.addRoute("Layout", {
+          path: item,
+          name: item,
+          meta: { isAuth: true },
+          component: () => import(`@/views/${item}`)
+        });
+      }
+    });
+  },
+  SET_TIME: (state, time) => {
+    state.time = time;
+  }
+};
+
+const actions = {
+  // user login
+  login({ commit }, userInfo) {
+    const { username, password } = userInfo;
+    return new Promise((resolve, reject) => {
+      api.hotelAdmin
+        .hotelStaffLogin({ username: username.trim(), password: password })
+        .then(response => {
+          // console.log(response);
+          const { data } = response;
+          commit("SET_TOKEN", data.token);
+          commit("SET_USERINFO", data);
+          commit("SET_ROLES", data.authority);
+          setToken("cook", data.token);
+          setToken("user", username);
+          setToken("password", password);
+          // console.log("resolve")
+          resolve();
+        })
+        .catch(error => {
+          // console.log("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("cook"); // must remove  token  first
+    // resetRouter()
+    removeToken("user");
+    removeToken("password");
+    commit("RESET_STATE");
+  },
+
+  // remove token
+  resetToken({ commit }) {
+    return new Promise(resolve => {
+      // removeToken() // must remove  token  first
+      commit("RESET_STATE");
+      resolve();
+    });
+  },
+
+  // set User Routes
+  setUserRoutes({ commit }) {
+    commit("SET_ROLES", data);
+  }
+};
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+};

+ 15 - 0
src/utils/auth.js

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

+ 65 - 0
src/utils/http.js

@@ -0,0 +1,65 @@
+/****   http.js   ****/
+// 导入封装好的axios实例
+import request from './request'
+
+const http = {
+    /**
+     * methods: 请求
+     * @param url 请求地址 
+     * @param params 请求参数
+     */
+    get(url, params, ContentType, responseType) {
+        const config = {
+            method: 'get',
+            responseType: responseType ? responseType : "",
+            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: 10 * 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);
+}
+

+ 591 - 0
src/views/account/index.vue

@@ -0,0 +1,591 @@
+<template>
+  <div class="inform">
+    <el-card class="box-card" style="width: 1612px; height: 950px">
+      <!-- 标题区域 -->
+      <div slot="header" class="clearfix">
+        <div class="inform-title">账号管理</div>
+      </div>
+
+      <!-- 主体内容区域 -->
+      <div class="inform-body">
+        <!-- 筛选条件区域 -->
+        <div class="state">
+          <div class="left">
+            <el-col :span="18">
+              <el-input
+                v-model="searchValue"
+                placeholder="请输入账号或手机号"
+                prefix-icon="el-icon-search"
+                clearable
+                @clear="getData(1)"
+              ></el-input>
+            </el-col>
+            <el-col :span="6">
+              <el-button type="primary" @click="getData(1)">查询</el-button>
+            </el-col>
+          </div>
+          <div class="right">
+            <el-button type="primary" @click="handleAdd" v-if="$store.state.user.userType==1">添加账号</el-button>
+          </div>
+        </div>
+        <!-- 表格区域 -->
+        <div class="inform-table">
+          <el-table
+            :data="tableData"
+            max-height="576"
+            height="576"
+            style="width: 1550px"
+            stripe
+            :cell-style="rowbg"
+            :header-cell-style="{
+              color: ' rgba(0, 0, 0, 1)',
+              background: 'rgba(240, 243, 247, 1)',
+            }"
+          >
+            <el-table-column prop="username" align="center" label="员工姓名(账号)"></el-table-column>
+
+            <el-table-column prop="phone" label="手机号" align="center"></el-table-column>
+
+            <el-table-column align="center" label="角色">
+              <template slot-scope="{ row }">
+                <div v-if="row.adminType == 1">超级管理员</div>
+                <div v-else>管理员</div>
+              </template>
+            </el-table-column>
+            <el-table-column align="center" label="操作">
+              <template slot-scope="{ row }">
+                <el-row v-if="row.identity == '超级管理员'">/</el-row>
+                <el-row v-else>
+                  <el-button type="text" @click="handleEdit(row)">编辑</el-button>
+                  <el-button type="text" @click="handleDelete(row.id)">删除</el-button>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页器区域 -->
+        <div class="block">
+          <el-pagination
+            background
+            layout="prev, pager, next, jumper"
+            :current-page.sync="currentPage"
+            :page-size="pageSize"
+            :total="total"
+            @current-change="handleCurrentChange"
+          ></el-pagination>
+        </div>
+      </div>
+
+      <!-- 添加编辑账号弹窗区域 -->
+      <el-dialog
+        :title="popTitle"
+        width="30%"
+        :visible.sync="dialogVisible"
+        :close-on-click-modal="false"
+      >
+        <hr class="pop_title" />
+
+        <el-form
+          :model="ruleForm"
+          :rules="rules"
+          ref="ruleForm"
+          label-position="top"
+          class="demo-ruleForm"
+        >
+          <el-row :gutter="85">
+            <el-col :span="12">
+              <el-form-item label="员工姓名" prop="username">
+                <el-input v-model="ruleForm.username" placeholder="请输入员工姓名"></el-input>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="手机号码" prop="phone">
+                <el-input v-model="ruleForm.phone" placeholder="请输入手机号码"></el-input>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <el-row :gutter="85">
+            <el-col :span="12">
+              <el-form-item label="角色" prop="adminType">
+                <el-select
+                  v-model="ruleForm.adminType"
+                  @change="adminTypeSelect"
+                  placeholder="请选择角色"
+                >
+                  <el-option label="超级管理员" value="1" v-if="$store.state.user.userType==1"></el-option>
+                  <el-option label="管理员" value="2"></el-option>
+                </el-select>
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="密码" prop="password">
+                <el-input v-model="ruleForm.password" placeholder="请输入密码"></el-input>
+                <!-- <el-input
+                  v-model="ruleForm.password"
+                  placeholder="请输入密码"
+                  :disabled="popTitle == '编辑账号'"
+                ></el-input>-->
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-form-item label="权限分配" prop="authority">
+            <el-checkbox-group v-model="ruleForm.authority" :min="1" @change="privilege">
+              <el-checkbox label="房态管理" name="type"></el-checkbox>
+              <el-checkbox label="订单管理" name="type"></el-checkbox>
+              <el-checkbox label="员工管理" name="type"></el-checkbox>
+              <el-checkbox label="账号管理" name="type"></el-checkbox>
+              <el-checkbox label="统计报表" name="type"></el-checkbox>
+              <el-checkbox label="系统设置" name="type"></el-checkbox>
+            </el-checkbox-group>
+          </el-form-item>
+        </el-form>
+
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="dialogVisible = false">取 消</el-button>
+          <el-button type="primary" @click="handleConfirm('ruleForm')">确 定</el-button>
+        </span>
+      </el-dialog>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import { RSAencrypt } from "../../utils/rsa";
+export default {
+  name: "Account",
+  data() {
+    return {
+      // 添加账号弹窗控制
+      dialogVisible: false,
+      // 添加账号弹窗标题
+      popTitle: "添加账号",
+      // 表格数据
+      tableData: [],
+      //  搜索框绑定数据
+      searchValue: "",
+      //   分页器当前页
+      currentPage: 1,
+      //   每页条数
+      pageSize: 8,
+      //   总条数
+      total: null,
+      roles: [],
+      id: "",
+      // 添加账号弹窗表格绑定数据
+      ruleForm: {
+        username: "",
+        phone: "",
+        adminType: "",
+        password: "",
+        authority: []
+      },
+      // 添加账号弹窗表格验证规则
+      rules: {
+        username: [
+          { required: true, message: "请输入员工姓名", trigger: "blur" }
+        ],
+        phone: [
+          { required: true, message: "请输入手机号码", trigger: "blur" },
+          {
+            min: 3,
+            max: 11,
+            message: "长度在 3 到 11 个字符",
+            trigger: "blur"
+          }
+        ],
+        password: [
+          { required: true, message: "请输入密码", trigger: "blur" },
+          {
+            min: 6,
+            // max: 12,
+            message: "长度应大于等于 6 字符",
+            trigger: "blur"
+          }
+        ],
+        adminType: [
+          { required: true, message: "请选择角色", trigger: "change" }
+        ],
+        authority: [
+          {
+            type: "array",
+            required: true,
+            message: "请至少选择一个活动性质",
+            trigger: "change"
+          }
+        ]
+      }
+    };
+  },
+  mounted() {
+    this.getData();
+  },
+  methods: {
+    // 获取账号管理页面列表数据
+    async getData(curPage) {
+      let params = {
+        curPage: curPage || this.currentPage,
+        pageSize: this.pageSize,
+        keyword: this.searchValue,
+        userType: this.$store.state.user.userType.toString(),
+        telPhone: this.$store.state.user.telPhone
+      };
+      let res = await this.API.hotelAdmin.hotelStaffList(params);
+      // console.log(res);
+      if (res.success) {
+        this.tableData = res.data.list;
+        this.total = res.data.totalCount;
+      } else {
+        this.$message.error(res.message);
+      }
+    },
+    // 添加账号按钮回调
+    handleAdd() {
+      this.ruleForm = {
+        phone: "",
+        adminType: "",
+        password: "",
+        username: "",
+        statu: "1",
+        authority: []
+      };
+      this.roles = [];
+      this.dialogVisible = true;
+      this.popTitle = "添加账号";
+      this.$nextTick(() => {
+        this.$refs.ruleForm.resetFields();
+      });
+    },
+    // 编辑按钮回调
+    handleEdit(row) {
+      this.dialogVisible = true;
+      this.popTitle = "编辑账号";
+      // console.log(row);
+      let roles = row.authority.split("/");
+      // console.log(roles);
+      this.$nextTick(() => {
+        this.$refs.ruleForm.resetFields();
+        let data = [];
+        let title = [];
+        row.authority.split("/").forEach(item => {
+          if (item == "home") {
+            data.push("home_1");
+            title.push("房态管理");
+          } else if (item == "order") {
+            data.push("order_2");
+            title.push("订单管理");
+          } else if (item == "staff") {
+            data.push("staff_3");
+            title.push("员工管理");
+          } else if (item == "account") {
+            data.push("account_4");
+            title.push("账号管理");
+          } else if (item == "stat") {
+            data.push("stat_5");
+            title.push("统计报表");
+          } else if (item == "system") {
+            data.push("system_6");
+            title.push("系统设置");
+          }
+        });
+        this.roles = data;
+        this.id = row.id;
+        this.ruleForm = {
+          username: row.username,
+          phone: row.phone,
+          adminType: row.adminType,
+          statu: row.statu,
+          password: row.password,
+          authority: title
+        };
+
+        // console.log(data);
+      });
+    },
+    // 删除按钮回调
+    handleDelete(id) {
+      this.$confirm("确定删除吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(async () => {
+          let res = await this.API.hotelAdmin.hotelStaffDelete([id]);
+          // console.log(res);
+          if (res.success) {
+            this.$message.success("删除成功");
+            let totalPage = Math.ceil((this.total - 1) / this.pageSize);
+            let currentPage =
+              this.currentPage > totalPage ? totalPage : this.currentPage;
+            this.currentPage = currentPage < 1 ? 1 : currentPage;
+            this.getData();
+          } else {
+            this.$message.error(res.message);
+          }
+        })
+        .catch(() => {
+          this.$message.info("已取消删除");
+        });
+    },
+    // 分页器换页回调
+    handleCurrentChange(value) {
+      this.currentPage = value;
+      this.getData();
+    },
+    // 添加账号/编辑账号 弹窗确定按钮回调
+    handleConfirm(formName) {
+      // console.log(formName);
+      this.$refs[formName].validate(async valid => {
+        if (valid) {
+          let rsaPassWord = RSAencrypt(this.ruleForm.password);
+          let data = {
+            phone: this.ruleForm.phone,
+            username: this.ruleForm.username,
+            adminType: this.ruleForm.adminType,
+            password: rsaPassWord,
+            statu: "1",
+            authority: this.roles
+          };
+
+          if (this.ruleForm.password.length > 20) {
+            rsaPassWord = "";
+          }
+          let data1 = {
+            id: this.id,
+            phone: this.ruleForm.phone,
+            username: this.ruleForm.username,
+            adminType: this.ruleForm.adminType,
+            statu: "1",
+            authority: this.roles,
+            password: rsaPassWord
+          };
+          // 添加账号请求
+          if (this.popTitle == "添加账号") {
+            // console.log("添加账号", data);
+            let res = await this.API.hotelAdmin.hotelStaffAdd(data);
+            // console.log(res);
+            if (res.success) {
+              this.$message.success("添加成功");
+              this.getData();
+            } else {
+              this.$message.error(res.message);
+            }
+          }
+          // 编辑账号请求
+          else {
+            // console.log("编辑账号", data1);
+            let res = await this.API.hotelAdmin.hotelStaffUpdate(data1);
+            // console.log(res);
+            if (res.success) {
+              this.$message.success("编辑成功");
+              this.getData();
+            } else {
+              this.$message.error(res.message);
+            }
+          }
+          this.dialogVisible = false;
+        } else {
+          console.log("error submit!!");
+          return false;
+        }
+      });
+    },
+    // 权限分配
+    privilege(value) {
+      // console.log(value);
+      let data = [];
+      value.forEach(item => {
+        // console.log(item);
+        if (item == "房态管理") {
+          data.push("home_1");
+        } else if (item == "订单管理") {
+          data.push("order_2");
+        } else if (item == "员工管理") {
+          data.push("staff_3");
+        } else if (item == "账号管理") {
+          data.push("account_4");
+        } else if (item == "统计报表") {
+          data.push("stat_5");
+        } else if (item == "系统设置") {
+          data.push("system_6");
+        }
+      });
+      this.roles = data;
+    },
+    // 改变管理员状态
+    adminTypeSelect(value) {
+      // console.log(value);
+      // let roles = [];
+      // let role = [];
+      // if (value == 2) {
+      //   role = this.ruleForm.authority.filter(item => {
+      //     return item != "账号管理";
+      //   });
+      //   roles = this.roles.filter(item => {
+      //     return item != "account_4";
+      //   });
+      //   this.ruleForm.authority = role;
+      //   this.roles = roles;
+      // } else if (value == 1) {
+      //   let index = this.ruleForm.authority.indexOf("账号管理");
+      //   let index2 = this.roles.indexOf("account_4");
+      //   if (index < 0) {
+      //     this.ruleForm.authority.push("账号管理");
+      //   }
+      //   if (index2 < 0) {
+      //     this.roles.indexOf("account_4");
+      //   }
+      // }
+    },
+    // 表格样式设置回调
+    rowbg(row) {
+      if (row.rowIndex % 2 != 0) {
+        return { background: "rgba(240, 243, 247, 1)", "border-radius": "5px" };
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.inform /deep/ .el-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+  .clearfix {
+    height: 96px;
+    width: 100%;
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .inform-title {
+      width: 96px;
+      height: 36px;
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-left: 33px;
+    }
+  }
+  .inform-body {
+    .state {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 33px 29px 34px 33px;
+      .left {
+        display: flex;
+        align-items: center;
+        .el-button {
+          margin-left: 10px;
+          background-color: rgba(41, 109, 227, 1);
+        }
+      }
+      .right {
+        .el-button {
+          background-color: rgba(41, 109, 227, 1);
+        }
+      }
+    }
+    .inform-table {
+      .el-table {
+        font-weight: 400;
+        font-size: 16px;
+        color: rgba(0, 0, 0, 1);
+        margin: 0 auto;
+        .el-table__header-wrapper {
+          border-radius: 5px;
+        }
+        tr {
+          height: 64px;
+          td {
+            border: none;
+          }
+        }
+        th {
+          font-weight: 400;
+        }
+      }
+    }
+    .block {
+      height: 36px;
+      margin: 66px 29px 0 0;
+      float: right;
+      .el-pagination {
+        padding: 0;
+        button {
+          width: 36px;
+          background: #fff;
+          color: rgba(0, 0, 0, 1);
+          height: 36px;
+          font-size: 14px;
+          border: 1px solid rgba(112, 112, 112, 1);
+          border-radius: 8px;
+        }
+        ul {
+          .active {
+            background: #fff;
+            color: rgba(0, 97, 255, 1);
+            border: 1px solid rgba(0, 97, 255, 1);
+            box-sizing: border-box;
+          }
+          .el-icon {
+            border: none;
+          }
+          li {
+            background: #fff;
+            color: rgba(0, 0, 0, 1);
+            width: 36px;
+            height: 36px;
+            font-size: 14px;
+            border: 1px solid rgba(112, 112, 112, 1);
+            border-radius: 8px;
+            line-height: 36px;
+          }
+        }
+        .el-pagination__jump {
+          color: rgba(0, 0, 0, 1);
+          font-size: 16px;
+          margin-left: 10px;
+          font-weight: 400;
+          height: 36px;
+          .el-input {
+            width: 65px;
+            height: 36px;
+            margin: 0 10px;
+            input {
+              width: 65px;
+              height: 36px;
+              border: 1px solid rgba(0, 0, 0, 1);
+              border-radius: 8px;
+            }
+          }
+        }
+      }
+    }
+  }
+  .pop_title {
+    color: #ccccccff;
+  }
+  .el-form {
+    padding: 0 30px;
+  }
+}
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+  /deep/ .el-card__body {
+    padding: 0;
+  }
+  /deep/ .el-dialog__header {
+    font-weight: bold;
+  }
+  /deep/ .el-dialog__body {
+    padding: 0;
+  }
+}
+</style>

Разлика између датотеке није приказан због своје велике величине
+ 1347 - 0
src/views/fingerprint/index.vue


Разлика између датотеке није приказан због своје велике величине
+ 2051 - 0
src/views/home/index.vue


Разлика између датотеке није приказан због своје велике величине
+ 1270 - 0
src/views/icCard/index.vue


+ 402 - 0
src/views/inform/index.vue

@@ -0,0 +1,402 @@
+<template>
+  <div class="inform">
+    <el-card class="box-card" style="width: 1612px; height: 950px">
+      <div slot="header" class="clearfix">
+        <div class="inform-title">消息通知</div>
+        <div class="inform-header">
+          <el-input placeholder="请输入时间" v-model="inquireValue">
+            <i slot="prefix" class="el-input__icon el-icon-search"></i>
+          </el-input>
+          <el-button type="primary" @click="inquire">查询</el-button>
+        </div>
+      </div>
+      <div class="inform-body">
+        <div class="state">
+          <div class="left">
+            <el-button
+              size="small"
+              @click="read($event, index)"
+              v-for="(item, index) in reads"
+              :key="item"
+              :class="className[index]"
+            >{{ item }}</el-button>
+          </div>
+          <div class="right">
+            <el-button type="primary" size="small" @click="allRead">一键已读</el-button>
+          </div>
+        </div>
+        <div class="inform-table">
+          <el-table
+            :data="data"
+            max-height="576"
+            height="576"
+            style="width: 1550px"
+            stripe
+            :cell-style="rowbg"
+            :row-click="clickData"
+            :header-cell-style="{
+              color: ' rgba(0, 0, 0, 1)',
+              background: 'rgba(240, 243, 247, 1)',
+            }"
+          >
+            <el-table-column prop="title" label="标题" width="300" align="center">
+              <template slot-scope="scope">
+                <span class="yuan" v-show="scope.row.readFlag == 0 ? true : false"></span>
+                <span style="margin-left: 10px">{{ scope.row.title }}</span>
+              </template>
+            </el-table-column>
+
+            <el-table-column prop="content" align="center" label="消息"></el-table-column>
+
+            <el-table-column prop="createTime" align="center" label="时间" width="400"></el-table-column>
+            <el-table-column prop="address" align="center" label="操作" width="150">
+              <template slot-scope="scope">
+                <span
+                  @click="handleInfo(scope.$index, scope.row)"
+                  class="operate"
+                  style="color: #296de3; cursor: pointer"
+                >查看</span>
+                <el-dialog
+                  title="通知详情"
+                  :visible.sync="infoShow"
+                  :close-on-click-modal="false"
+                  custom-class="info"
+                >
+                  <h3>标题:</h3>
+                  <span>{{ info.title }}</span>
+                  <h3>消息:</h3>
+                  <span style="padding: 10px 20px 20px">
+                    {{
+                    info.content
+                    }}
+                  </span>
+                  <h3>时间:</h3>
+                  <span>{{ info.createTime }}</span>
+                </el-dialog>
+                <span
+                  @click="handleDelete(scope.$index, scope.row)"
+                  class="operate"
+                  style="color: #296de3; cursor: pointer"
+                  v-show="scope.row.readFlag == 0 ? false : true"
+                >删除</span>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <div class="block">
+          <el-pagination
+            background
+            @current-change="handleCurrentChange"
+            current-page.sync="1"
+            :page-size="8"
+            layout="prev, pager, next, jumper"
+            :total="Data.length"
+          ></el-pagination>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Inform",
+  data() {
+    return {
+      reads: ["全部", "未读", "已读"],
+      className: ["active", "", ""],
+      inquireValue: "",
+      data: [], // 当前页面显示的数据
+      Data: [], // 显示的总数据
+      tableData: [], // 请求的总数据
+      currentPage: 1,
+      infoShow: false,
+      info: {
+        title: "",
+        content: "",
+        createTime: ""
+      }
+    };
+  },
+  mounted() {
+    document.getElementsByClassName(
+      "el-pagination__jump"
+    )[0].childNodes[0].nodeValue = "跳转到";
+    this.dataList();
+  },
+  methods: {
+    dataList(currentPage) {
+      this.API.systemnotice
+        .systemnoticeList({
+          pageSize: 100,
+          order: "desc",
+          orderField: "create_time"
+        })
+        .then(res => {
+          // console.log(res);
+          this.tableData = res.data.list;
+          let number = 0;
+          this.tableData.forEach(item => {
+            if (item.readFlag == 0) {
+              number += 1;
+            }
+            item.createTime = item.createTime.replace("T", " ");
+          });
+          this.$store.dispatch("inform/setreadFlag", number);
+          if (number == 0) {
+            this.$store.dispatch("inform/setreadFlag", "");
+          }
+          this.Data = this.tableData;
+          this.handleCurrentChange(currentPage ? currentPage : 1);
+        });
+    },
+    // 查询
+    inquire() {
+      this.Data = this.tableData.filter((item, index) => {
+        return item.createTime.includes(this.inquireValue);
+      });
+      this.handleCurrentChange(1);
+    },
+    // 已读未读
+    read($event, index) {
+      this.className = this.className.map(item => (item = ""));
+      this.className[index] = "active";
+      if ($event.target.innerText == "全部") {
+        this.Data = this.tableData;
+      } else if ($event.target.innerText == "已读") {
+        this.Data = this.tableData.filter(item => {
+          return item.readFlag == 1;
+        });
+      } else if ($event.target.innerText == "未读") {
+        this.Data = this.tableData.filter(item => {
+          return item.readFlag == 0;
+        });
+      }
+      this.handleCurrentChange(1);
+    },
+    // 查看详情
+    handleInfo(index, row) {
+      // console.log(row);
+      this.info = {
+        title: row.title,
+        content: row.content,
+        createTime: row.createTime
+      };
+      if (row.readFlag == 0) {
+        this.API.systemnotice.systemnoticeRead([row.id]).then(res => {
+          // console.log(res);
+          this.dataList(this.currentPage);
+        });
+      }
+      this.infoShow = true;
+    },
+    // 一键已读
+    allRead() {
+      let ids = [];
+      this.tableData.forEach(item => {
+        if (item.readFlag == 0) {
+          ids.push(item.id);
+        }
+      });
+      // console.log(ids);
+      if (ids.length) {
+        this.API.systemnotice.systemnoticeRead(ids).then(res => {
+          // console.log(res);
+          this.dataList();
+        });
+      }
+    },
+    rowbg(row) {
+      if (row.rowIndex % 2 != 0) {
+        return { background: "rgba(240, 243, 247, 1)", "border-radius": "5px" };
+      }
+    },
+
+    handleCurrentChange(val) {
+      // console.log(this.Data);
+      this.currentPage = this.val;
+      this.data = this.Data.slice((val - 1) * 8, val * 8);
+      // console.log(`当前页: ${val}`);
+    },
+
+    clickData(row, column, event) {
+      // console.log(1111);
+      // console.log(row, column, event);
+    },
+    //删除通知
+    handleDelete(index, row) {
+      let ids = [row.id];
+      // console.log(ids);
+      this.$confirm("确认删除通知?")
+        .then(_ => {
+          this.API.systemnotice.systemnoticeDelete(ids).then(res => {
+            // console.log(res);
+          });
+          this.dataList();
+        })
+        .catch(_ => {});
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.inform /deep/ .el-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+  .clearfix {
+    height: 96px;
+    width: 100%;
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .inform-title {
+      width: 96px;
+      height: 36px;
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-left: 33px;
+    }
+    .inform-header {
+      display: flex;
+      margin-right: 29px;
+      .el-button {
+        margin-left: 9px;
+        background-color: rgba(41, 109, 227, 1);
+      }
+    }
+  }
+  .inform-body {
+    .state {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 33px 29px 34px 33px;
+      .active {
+        background-color: rgba(41, 109, 227, 1);
+        color: #fff;
+      }
+      .right {
+        .el-button {
+          background-color: rgba(41, 109, 227, 1);
+        }
+      }
+    }
+    .inform-table {
+      .el-table {
+        font-weight: 400;
+        font-size: 16px;
+        color: rgba(0, 0, 0, 1);
+        margin: 0 auto;
+        .el-table__header-wrapper {
+          border-radius: 5px;
+        }
+        tr {
+          height: 64px;
+          .yuan {
+            background-color: rgba(212, 48, 48, 1);
+            border-radius: 50%;
+            display: inline-block;
+            width: 8px;
+            height: 8px;
+          }
+        }
+        th {
+          font-weight: 400;
+        }
+      }
+    }
+    .block {
+      height: 36px;
+      margin: 66px 29px 0 0;
+      float: right;
+      .el-pagination {
+        padding: 0;
+        button {
+          width: 36px;
+          background: #fff;
+          color: rgba(0, 0, 0, 1);
+          height: 36px;
+          font-size: 14px;
+          border: 1px solid rgba(112, 112, 112, 1);
+          border-radius: 8px;
+        }
+        ul {
+          .active {
+            background: #fff;
+            color: rgba(0, 97, 255, 1);
+            border: 1px solid rgba(0, 97, 255, 1);
+            box-sizing: border-box;
+          }
+          .el-icon {
+            border: none;
+          }
+          li {
+            background: #fff;
+            color: rgba(0, 0, 0, 1);
+            width: 36px;
+            height: 36px;
+            font-size: 14px;
+            border: 1px solid rgba(112, 112, 112, 1);
+            border-radius: 8px;
+            line-height: 36px;
+          }
+        }
+        .el-pagination__jump {
+          color: rgba(0, 0, 0, 1);
+          font-size: 16px;
+          margin-left: 10px;
+          font-weight: 400;
+          height: 36px;
+          .el-input {
+            width: 65px;
+            height: 36px;
+            margin: 0 10px;
+            input {
+              width: 65px;
+              height: 36px;
+              border: 1px solid rgba(0, 0, 0, 1);
+              border-radius: 8px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+  /deep/ .el-card__body {
+    padding: 0;
+  }
+}
+/deep/ .info.el-dialog {
+  box-shadow: none;
+  margin: 200px 0 0 700px !important;
+  width: 700px !important;
+  .el-dialog__header {
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    .el-dialog__title {
+      display: flex;
+    }
+  }
+  .el-dialog__body {
+    display: flex;
+    flex-direction: column;
+    h3 {
+      text-align: left;
+    }
+    span {
+      text-align: left;
+      padding: 10px 20px 20px;
+    }
+  }
+}
+</style>>

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

@@ -0,0 +1,272 @@
+<template>
+  <div class="login-container">
+    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
+      <div class="title-container">
+        <h3 class="title">智慧校园公寓管理端</h3>
+      </div>
+
+      <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>
+</template>
+
+<script>
+import { JSEncrypt } from "jsencrypt";
+import router from "@/router";
+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 == 0) {
+        callback(new Error("请输入密码"));
+      } else {
+        callback();
+      }
+    };
+    return {
+      // 表格绑定数据
+      loginForm: {
+        username: "",
+        password: ""
+      },
+      // 校验规则
+      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) {
+    //     console.log(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;
+          let publicKey =
+            "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMOcPB06u5yKyQsPjfVWiWgbEIrd14kiXNNihciaVKb6HnkQvq7zpQuZ80WEX94spnUMI3iOAl/GmIvHrpGwcbB4hJbznm+PajiwnUSPuCCXA68YJF640cJKb/8KeM7WVz69OFkIEPHhVxOy4FFF5QWe/kt6zOZ19HmE+ak+5x/QIDAQAB";
+          let encryptor = new JSEncrypt(); // 新建JSEncrypt对象
+          encryptor.setPublicKey(publicKey); // 设置公钥
+          let rsaPassWord = encryptor.encrypt(this.loginForm.password); // 对密码进行加密
+          // console.log(rsaPassWord);
+          this.$store
+            .dispatch("user/login", {
+              username: this.loginForm.username,
+              password: rsaPassWord
+            })
+            .then(() => {
+              this.$message({
+                message: "登录成功",
+                type: "success"
+              });
+              this.$router.push({
+                // path: this.redirect || "/home",
+                path: `${this.$store.state.user.roles[0]}`
+              });
+              this.loading = false;
+            })
+            .catch(() => {
+              this.$message.error("登录失败,用户名或密码错误");
+              this.loading = false;
+            });
+        }
+        // 不符合验证规则
+        else {
+          console.log("提交失败!!");
+          return false;
+        }
+      });
+    }
+  }
+};
+</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.png);
+  background-size: 100% 100%;
+  overflow: hidden;
+
+  .login-form {
+    width: 520px;
+    margin-top: 310px;
+    margin-left: 1080px;
+  }
+
+  .svg-container {
+    padding: 6px 5px 6px 15px;
+    color: $bg;
+    vertical-align: middle;
+    width: 30px;
+    display: inline-block;
+    .user {
+      width: 20px;
+      height: 20px;
+      background: url(../../assets/images/user.png);
+      background-size: 100% 100%;
+    }
+    .password {
+      width: 20px;
+      height: 20px;
+      background: url(../../assets/images/password.png);
+      background-size: 100% 100%;
+    }
+  }
+
+  .title-container {
+    position: relative;
+
+    .title {
+      font-size: 28px;
+      margin: 0px auto 40px auto;
+      text-align: center;
+      font-weight: bold;
+    }
+  }
+
+  .show-pwd {
+    position: absolute;
+    right: 10px;
+    top: 7px;
+    font-size: 16px;
+    color: $bg;
+    cursor: pointer;
+    user-select: none;
+    .eye_close {
+      margin-top: 7px;
+      width: 20px;
+      height: 20px;
+      background: url(../../assets/images/eye_close.png);
+      background-size: 100% 100%;
+    }
+    .eye {
+      margin-top: 7px;
+      width: 20px;
+      height: 20px;
+      background: url(../../assets/images/eye.png);
+      background-size: 100% 100%;
+    }
+  }
+}
+</style>

+ 787 - 0
src/views/order/index.vue

@@ -0,0 +1,787 @@
+<template>
+  <div class="inform">
+    <el-card class="box-card" style="width: 1612px; height: 950px">
+      <!-- 标题区域 -->
+      <div slot="header" class="clearfix">
+        <div class="inform-title">订单管理</div>
+      </div>
+      <!-- 主体内容区域 -->
+      <div class="inform-body">
+        <!-- 筛选条件区域 -->
+        <div class="order-top">
+          <!-- 订单号筛选 -->
+          <el-input
+            placeholder="请输入订单号或房间号"
+            style="width: 261px"
+            clearable
+            v-model="value"
+            @clear="getData(1)"
+          >
+            <i slot="prefix" class="el-input__icon el-icon-search"></i>
+          </el-input>
+          <el-button @click="getData(1)">查询</el-button>
+
+          <!-- 创建时间筛选 -->
+          <div class="paydate">
+            <span class="demonstration">创建时间:</span>
+            <el-date-picker
+              v-model="createTime"
+              type="datetimerange"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              range-separator="至"
+              start-placeholder="开始时间"
+              end-placeholder="结束时间"
+              @change="getData(1)"
+            ></el-date-picker>
+          </div>
+
+          <!-- 支付时间筛选 -->
+          <div class="paydate">
+            <span class="demonstration">支付时间:</span>
+            <el-date-picker
+              v-model="payTime"
+              type="datetimerange"
+              value-format="yyyy-MM-dd HH:mm:ss"
+              range-separator="至"
+              start-placeholder="开始时间"
+              end-placeholder="结束时间"
+              @change="getData(1)"
+            ></el-date-picker>
+          </div>
+
+          <!-- 状态筛选 -->
+          <div class="state">
+            <span>状态:</span>
+            <el-select v-model="state" clearable placeholder="请选择状态" @change="getData(1)">
+              <el-option
+                v-for="item in options"
+                :key="item.value"
+                :value="item.value"
+                :label="item.label"
+              ></el-option>
+            </el-select>
+          </div>
+
+          <div class="export" @click="exportExcel">导出订单</div>
+        </div>
+        <!-- 表格区域 -->
+        <div class="inform-table">
+          <!-- 表格区域 -->
+          <el-table
+            :data="tableData"
+            @selection-change="handleSelectionChange"
+            max-height="576"
+            height="576"
+            style="width: 1550px"
+            stripe
+            class="Devicetable"
+            :cell-style="rowbg"
+            :header-cell-style="{
+              color: ' rgba(0, 0, 0, 1)',
+              background: 'rgba(240, 243, 247, 1)',
+            }"
+          >
+            <!-- <el-table-column type="selection" width="60" align="center"></el-table-column> -->
+            <el-table-column
+              prop="order.orderNo"
+              width="180"
+              label="订单号"
+              align="center"
+              show-overflow-tooltip
+            ></el-table-column>
+
+            <el-table-column width="100" align="center" label="状态">
+              <template slot-scope="{ row }">{{ options[row.order.orderStatu].label }}</template>
+            </el-table-column>
+
+            <el-table-column prop="order.roomNo" width="160" align="center" label="房间"></el-table-column>
+            <el-table-column align="center" width="130" label="电费(元)">
+              <template slot-scope="{row}">{{row.allOfElectricPayment}}</template>
+            </el-table-column>
+            <el-table-column width="130" align="center" label="水费(元)">
+              <template slot-scope="{ row }">{{ row.allOfWaterAmount }}</template>
+            </el-table-column>
+            <el-table-column prop="allOfReceiveAmount" align="center" width="150" label="收预付款(元)"></el-table-column>
+            <el-table-column prop="refundPayment" width="150" align="center" label="退预付款(元)"></el-table-column>
+            <el-table-column prop="order.createTime" width="180" align="center" label="创建时间"></el-table-column>
+            <el-table-column prop="order.payTime" width="180" align="center" label="支付时间"></el-table-column>
+            <el-table-column prop="address" align="center" label="操作">
+              <template slot-scope="{ row }">
+                <!-- 详情按钮 -->
+                <span @click="handleEdit(row)" class="operate">详情</span>
+
+                <!-- 详情弹窗区域 -->
+                <el-dialog title="订单详情" :visible.sync="dialogVisible" width="30%">
+                  <div class="userinfo">用户信息</div>
+                  <div class="info">
+                    <div class="user">
+                      <div class="name">姓名</div>
+                      <el-input :value="order.userName"></el-input>
+                    </div>
+                    <div class="user">
+                      <div class="name">联系方式</div>
+                      <el-input :value="order.userPhone"></el-input>
+                    </div>
+                  </div>
+                  <div class="info">
+                    <div class="user">
+                      <div class="name">订单号</div>
+                      <el-input :value="order.orderNo"></el-input>
+                    </div>
+                    <div class="user">
+                      <div class="name">预付款金额(元)</div>
+                      <el-input value="20.00"></el-input>
+                    </div>
+                  </div>
+                  <div class="info">
+                    <div class="user">
+                      <div class="name">入住时间</div>
+                      <el-input :value="order.realStartTime"></el-input>
+                    </div>
+                    <div class="user">
+                      <div class="name">离住时间</div>
+                      <el-input :value="order.realEndTime"></el-input>
+                    </div>
+                  </div>
+                  <div class="info">
+                    <div class="user">
+                      <div class="name">入住天数</div>
+                      <el-input value="1"></el-input>
+                    </div>
+                    <div class="user">
+                      <div class="name">支付时间</div>
+                      <el-input :value="order.payTime"></el-input>
+                    </div>
+                  </div>
+
+                  <!-- 水费区域 -->
+                  <div class="rate">水费明细</div>
+                  <div class="water">
+                    表计:101 楼层:{{ order.roomNo }} 抄表时间:2022-07-27
+                    15:15:15
+                  </div>
+                  <el-table
+                    :data="waterTable"
+                    max-height="93"
+                    style="width: 832px"
+                    stripe
+                    :cell-style="rowbg"
+                    :header-cell-style="{
+                      color: ' rgba(0, 0, 0, 1)',
+                      background: 'rgba(240, 243, 247, 1)',
+                    }"
+                  >
+                    <el-table-column prop="startOfWater" width="100" label="始码" align="center"></el-table-column>
+
+                    <el-table-column prop="endOfWater" width="100" align="center" label="终码"></el-table-column>
+
+                    <el-table-column prop="waterVolume" align="center" label="水量(吨)"></el-table-column>
+                    <el-table-column prop="priceOfWater" align="center" label="水价(元)"></el-table-column>
+                    <el-table-column prop="allowance" align="center" label="补助量(吨)"></el-table-column>
+                    <el-table-column prop="cost" align="center" label="产生水费(元)"></el-table-column>
+                  </el-table>
+
+                  <!-- 电费区域 -->
+                  <div class="rate">电费明细</div>
+                  <div class="water">
+                    表计:101 楼层:{{ order.roomNo }} 抄表时间:2022-07-27
+                    15:15:15
+                  </div>
+                  <el-table
+                    :data="electricTable"
+                    max-height="93"
+                    style="width: 832px"
+                    stripe
+                    :cell-style="rowbg"
+                    :header-cell-style="{
+                      color: ' rgba(0, 0, 0, 1)',
+                      background: 'rgba(240, 243, 247, 1)',
+                    }"
+                  >
+                    <el-table-column prop="startOfElectric" width="100" label="始码" align="center"></el-table-column>
+
+                    <el-table-column prop="endOfElectric" width="100" align="center" label="终码"></el-table-column>
+
+                    <el-table-column prop="electricVolume" align="center" label="电量(度)"></el-table-column>
+                    <el-table-column prop="priceOfElectric" align="center" label="电价(元)"></el-table-column>
+                    <el-table-column prop="allowance" align="center" label="补助量(度)"></el-table-column>
+                    <el-table-column prop="cost" align="center" label="产生水费(元)"></el-table-column>
+                  </el-table>
+                </el-dialog>
+
+                <!-- 删除按钮 -->
+                <span v-if="row.order.orderStatu == 0" @click="handleDelete(row)" class="operate">删除</span>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页器区域 -->
+        <div class="block">
+          <el-pagination
+            background
+            @current-change="handleCurrentChange"
+            :page-size="pageSize"
+            :current-page="curPage"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          ></el-pagination>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import FileSaver from "file-saver";
+import * as XLSX from "xlsx";
+import dayjs from "dayjs";
+
+export default {
+  name: "Inform",
+  data() {
+    return {
+      // 显示的总数据
+      tableData: [],
+      // 多选框按钮勾选的数据
+      selectionTable: [],
+      // 状态数组
+      options: [
+        {
+          value: 0,
+          label: "已取消"
+        },
+        {
+          value: 1,
+          label: "待支付"
+        },
+        {
+          value: 2,
+          label: "待入住"
+        },
+        {
+          value: 3,
+          label: "已入住"
+        },
+        {
+          value: 4,
+          label: "待结账"
+        },
+        {
+          value: 5,
+          label: "已完成"
+        }
+      ],
+      // 筛选状态绑定数据
+      state: "",
+      // 筛选订单号绑定数据
+      value: "",
+      // 筛选支付时间绑定数据
+      payTime: [],
+      // 筛选创建时间绑定数据
+      createTime: [],
+      // 弹窗控制属性
+      dialogVisible: false,
+      waterTable: [],
+      electricTable: [],
+      order: [], // 用户信息
+      // 当前页
+      curPage: 1,
+      // 每页条数
+      pageSize: 8,
+      // 总条数
+      total: null
+    };
+  },
+  mounted() {
+    this.getData();
+  },
+  methods: {
+    // 获取表格数据
+    async getData(curPage) {
+      if (curPage) {
+        this.curPage = curPage;
+      }
+      if (!this.createTime) {
+        this.createTime = [];
+      }
+      if (!this.payTime) {
+        this.payTime = [];
+      }
+      let params = {
+        curPage: this.curPage,
+        pageSize: this.pageSize,
+        statu: this.state,
+        keyWord: this.value,
+        payTime1: this.payTime[0],
+        payTime2: this.payTime[1],
+        createTime1: this.createTime[0],
+        createTime2: this.createTime[1]
+      };
+      let res = await this.API.hotelOrder.hotelOrderAdminPage(params);
+      // console.log(res);
+      if (res.success) {
+        this.tableData = res.data.list;
+        this.total = res.data.totalCount;
+        res.data.list.forEach(item => {
+          // 水费电费计算后的价格
+          if (item.orderBill) {
+            // item.order.waterMoney =
+            //   item.orderBill.priceOfWater *
+            //   (item.orderBill.endOfWater - item.orderBill.startOfWater);
+            // item.order.electricMoney =
+            //   item.orderBill.priceOfElectric *
+            //   (item.orderBill.endOfElectric - item.orderBill.startOfElectric);
+            // console.log(item.orderBill.finishTime);
+            // 支付时间转换
+            item.orderBill.finishTime = dayjs(item.orderBill.finishTime).format(
+              "YYYY-MM-DD HH:mm:ss"
+            );
+          }
+        });
+      } else {
+        this.$message.error(res.message);
+      }
+    },
+
+    // 导出订单按钮回调
+    exportExcel() {
+      let params = {
+        keyWord: this.value,
+        statu: this.state,
+        payTime1: this.payTime[0],
+        payTime2: this.payTime[1],
+        createTime1: this.createTime[0],
+        createTime2: this.createTime[1]
+      };
+      this.API.hotelOrder.downOrder(params).then(res => {
+        // console.log(res);
+        let name = `订单数据`;
+        var content = res;
+        // var data = new Blob([content],{type:"application/octet-stream;charset=utf-8"});
+        var data = new Blob([content], {
+          type: "application/vnd.ms-excel;charset=utf-8"
+        });
+        var downloadUrl = window.URL.createObjectURL(data);
+        var anchor = document.createElement("a");
+        anchor.href = downloadUrl;
+        anchor.download = name + ".xls";
+        anchor.click();
+        window.URL.revokeObjectURL(data);
+        if (res) {
+          this.$message.success("导出成功");
+        } else {
+          this.$message.error("导出失败");
+        }
+      });
+    },
+
+    // 导出表格
+    // excel(id) {
+    //   let xlsxParam = { raw: true };
+    //   /* generate workbook object from table */
+    //   let wb = XLSX.utils.table_to_book(document.querySelector(id));
+    //   /* get binary string as output */
+    //   let wbout = XLSX.write(wb, {
+    //     bookType: "xlsx",
+    //     bookSST: true,
+    //     type: "array"
+    //   });
+    //   try {
+    //     FileSaver.saveAs(
+    //       new Blob([wbout], { type: "application/octet-stream" }),
+    //       "订单管理.xlsx"
+    //     );
+    //   } catch (e) {
+    //     if (typeof console !== "undefined") console.log(e, wbout);
+    //   }
+    //   return wbout;
+    // },
+
+    //详情按钮回调
+    handleEdit(row) {
+      // console.log(row);
+      this.order = row.order;
+      this.waterTable = [];
+      this.electricTable = [];
+      if (row.orderBill) {
+        let waterTemList = [];
+        let electricTemList = [];
+        if (row.orderBill.priceOfWater == "0E-8") {
+          row.orderBill.priceOfWater = 0;
+          // console.log(row.orderBill.priceOfWater);
+        }
+        if (row.orderBill.priceOfElectric == "0E-8") {
+          row.orderBill.priceOfElectric = 0;
+        }
+        waterTemList.push({
+          startOfWater: row.orderBill.startOfWater,
+          endOfWater: row.orderBill.endOfWater,
+          priceOfWater: row.orderBill.priceOfWater,
+          waterVolume: row.orderBill.endOfWater - row.orderBill.startOfWater,
+          allowance: null,
+          cost:
+            row.orderBill.priceOfWater *
+            (row.orderBill.endOfWater - row.orderBill.startOfWater)
+        });
+
+        electricTemList.push({
+          startOfElectric: row.orderBill.startOfElectric,
+          endOfElectric: row.orderBill.endOfElectric,
+          priceOfElectric: row.orderBill.priceOfElectric,
+          electricVolume:
+            row.orderBill.endOfElectric - row.orderBill.startOfElectric,
+          allowance: null,
+          cost:
+            row.orderBill.priceOfElectric *
+            (row.orderBill.endOfElectric - row.orderBill.startOfElectric)
+        });
+        this.waterTable = waterTemList;
+        this.electricTable = electricTemList;
+      }
+      this.dialogVisible = true;
+    },
+
+    //删除按钮回调
+    handleDelete(row) {
+      // console.log(row.order.id);
+      this.$confirm("确认删除?")
+        .then(async () => {
+          let res = await this.API.hotelOrder.hotelOrderAdminDelete(
+            row.order.id
+          );
+          // console.log(res);
+          if (res.success) {
+            this.$message.success("删除成功");
+            let totalPage = Math.ceil((this.total - 1) / this.pageSize);
+            let currentPage =
+              this.curPage > totalPage ? totalPage : this.curPage;
+            this.curPage = currentPage < 1 ? 1 : currentPage;
+            this.getData();
+          } else {
+            this.$message.error(res.message);
+          }
+        })
+        .catch(() => {
+          this.$message.info("已取消");
+        });
+    },
+
+    // 翻页逻辑回调
+    handleCurrentChange(val) {
+      this.curPage = val;
+      this.getData();
+    },
+
+    // 多选框按钮操作
+    handleSelectionChange(list) {
+      // console.log(list);
+      this.selectionTable = list;
+    },
+
+    // 表格样式设置
+    rowbg(row) {
+      if (row.rowIndex % 2 != 0) {
+        return { background: "rgba(240, 243, 247, 1)", "border-radius": "5px" };
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.inform /deep/ .el-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+  .clearfix {
+    height: 96px;
+    width: 100%;
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .inform-title {
+      width: 96px;
+      height: 36px;
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-left: 33px;
+    }
+    .inform-header {
+      display: flex;
+      margin-right: 29px;
+      .el-button {
+        margin-left: 9px;
+        background-color: rgba(41, 109, 227, 1);
+      }
+    }
+  }
+  .inform-body {
+    .order-top {
+      display: flex;
+      align-items: center;
+      padding: 33px 29px 34px 33px;
+      font-size: 16px;
+      font-weight: 400;
+      color: #000;
+      .el-button {
+        background-color: rgba(41, 109, 227, 1);
+        color: #fff;
+        margin-left: 9px;
+      }
+      .paydate {
+        margin: 0 0 0 44px;
+        .el-date-editor {
+          margin-left: 7px;
+          width: 215px;
+          .el-range__icon {
+            display: none;
+          }
+        }
+      }
+      .state {
+        margin-left: 30px;
+        .el-input {
+          width: 150px;
+        }
+      }
+      .export {
+        width: 94px;
+        height: 32px;
+        opacity: 1;
+        color: #fff;
+        text-align: center;
+        line-height: 32px;
+        background: rgba(41, 109, 227, 1);
+        cursor: pointer;
+        border-radius: 2px;
+        margin-left: 170px;
+      }
+    }
+    .inform-table {
+      .execlTable,
+      .selectTable {
+        display: none;
+      }
+      .el-table {
+        font-weight: 400;
+        font-size: 16px;
+        color: rgba(0, 0, 0, 1);
+        margin: 0 auto;
+        .el-table__header-wrapper {
+          border-radius: 5px;
+          .el-checkbox.is-checked {
+            .el-checkbox__inner {
+              background-color: rgba(41, 109, 227, 1);
+            }
+          }
+
+          .el-checkbox__input.is-checked,
+          .el-checkbox__input.is-indeterminate {
+            .el-checkbox__inner {
+              background-color: rgba(41, 109, 227, 1);
+            }
+          }
+        }
+        tr {
+          height: 64px;
+          td {
+            .cell {
+              .el-checkbox.is-checked {
+                .el-checkbox__inner {
+                  background-color: rgba(41, 109, 227, 1);
+                }
+              }
+              .is-indeterminate {
+                .el-checkbox__inner {
+                  background-color: rgba(41, 109, 227, 1);
+                }
+              }
+              .operate {
+                color: rgba(41, 109, 227, 1);
+                cursor: pointer;
+                font-size: 16px;
+                font-weight: 400;
+                padding: 0 5px;
+              }
+              .el-dialog__wrapper {
+                overflow: hidden;
+                .el-dialog {
+                  width: 968px !important;
+                  margin-top: 80px !important;
+                  height: 880px;
+                  opacity: 1;
+                  background: rgba(255, 255, 255, 1);
+                  box-shadow: none;
+                  .el-dialog__header {
+                    height: 83px;
+                    border-bottom: 1px solid rgba(230, 230, 230, 1);
+
+                    span {
+                      float: left;
+                      font-size: 20px;
+                      font-weight: 500;
+                      color: #000;
+                    }
+
+                    .el-dialog__close {
+                      width: 35px;
+                      height: 35px;
+                      &::before {
+                        font-size: 24px;
+                        color: #000;
+                      }
+                    }
+                  }
+                  .el-dialog__body {
+                    padding: 0 38px;
+                    .userinfo {
+                      font-size: 20px;
+                      font-weight: 500;
+                      color: rgba(0, 0, 0, 1);
+                      text-align: left;
+                      margin-top: 15px;
+                    }
+                    .info {
+                      display: flex;
+                      .user {
+                        .name {
+                          text-align: left;
+                          padding: 12px 0;
+                        }
+                        .el-input {
+                          width: 283px;
+                          height: 38px;
+                          opacity: 1;
+                          margin-right: 81px;
+                          .el-input__inner {
+                            color: #000;
+                          }
+                        }
+                      }
+                    }
+                    .rate {
+                      font-size: 20px;
+                      font-weight: 500;
+                      color: rgba(0, 0, 0, 1);
+                      text-align: left;
+                      margin: 15px 0;
+                    }
+                    .water {
+                      text-align: left;
+                      font-size: 16px;
+                      font-weight: 400;
+                      color: rgba(0, 0, 0, 1);
+                    }
+                    .el-table {
+                      margin: 10px 0;
+                      tr {
+                        height: 48px;
+                      }
+                      .el-table__body-wrapper {
+                        overflow: visible;
+                      }
+                    }
+                  }
+                }
+              }
+
+              .el-dialog__title {
+                color: rgba(41, 109, 227, 1);
+                cursor: pointer;
+                padding: 10px 18px 0 18px;
+              }
+            }
+          }
+          .el-checkbox__inner {
+            width: 18px;
+            height: 18px;
+            &::after {
+              transform: rotate(45deg) scaleY(1.5);
+              top: 3px;
+              left: 5px;
+            }
+          }
+        }
+        th {
+          font-weight: 400;
+        }
+      }
+    }
+    .block {
+      height: 36px;
+      margin: 66px 29px 0 0;
+      float: right;
+      .el-pagination {
+        padding: 0;
+        button {
+          width: 36px;
+          background: #fff;
+          color: rgba(0, 0, 0, 1);
+          height: 36px;
+          font-size: 14px;
+          border: 1px solid rgba(112, 112, 112, 1);
+          border-radius: 8px;
+        }
+        ul {
+          .active {
+            background: #fff;
+            color: rgba(0, 97, 255, 1);
+            border: 1px solid rgba(0, 97, 255, 1);
+            box-sizing: border-box;
+          }
+          .el-icon {
+            border: none;
+          }
+          li {
+            background: #fff;
+            color: rgba(0, 0, 0, 1);
+            width: 36px;
+            height: 36px;
+            font-size: 14px;
+            border: 1px solid rgba(112, 112, 112, 1);
+            border-radius: 8px;
+            line-height: 36px;
+          }
+        }
+        .el-pagination__jump {
+          color: rgba(0, 0, 0, 1);
+          font-size: 16px;
+          margin-left: 10px;
+          font-weight: 400;
+          height: 36px;
+          .el-input {
+            width: 65px;
+            height: 36px;
+            margin: 0 10px;
+            input {
+              width: 65px;
+              height: 36px;
+              border: 1px solid rgba(0, 0, 0, 1);
+              border-radius: 8px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+  /deep/ .el-card__body {
+    padding: 0;
+  }
+}
+</style>
+<style lang="scss">
+.el-message-box__wrapper {
+  .el-button--primary {
+    background-color: #296de3;
+  }
+}
+</style>

+ 701 - 0
src/views/staff/index.vue

@@ -0,0 +1,701 @@
+<template>
+  <div class="inform">
+    <el-card class="box-card" style="width: 1612px; height: 950px">
+      <!-- 标题区域 -->
+      <div slot="header" class="clearfix">
+        <div class="inform-title">员工管理</div>
+      </div>
+      <!-- 主体内容区域 -->
+      <div class="inform-body">
+        <!-- 筛选条件区域 -->
+        <div class="order-top">
+          <div class="username-inquire">
+            <el-input
+              placeholder="请输入手机号码或员工姓名"
+              style="width: 261px"
+              v-model="value"
+              clearable
+              @clear="getData(1)"
+            >
+              <i slot="prefix" class="el-input__icon el-icon-search"></i>
+            </el-input>
+            <el-button @click="getData(1)">查询</el-button>
+          </div>
+          <div class="export" @click="addStaff">添加员工</div>
+
+          <!-- 添加员工弹窗 -->
+          <el-dialog
+            title="添加员工"
+            :visible.sync="addStaffShow"
+            :close-on-click-modal="false"
+          >
+            <div class="info">
+              <div class="user">
+                <div class="name">员工姓名</div>
+                <el-input v-model="staffValue"></el-input>
+              </div>
+              <div class="user">
+                <div class="name">手机号码</div>
+                <el-input v-model="phoneValue"></el-input>
+              </div>
+            </div>
+            <div class="info">
+              <div class="user">
+                <div class="name">部门</div>
+                <el-select v-model="departid" placeholder="请选择">
+                  <el-option
+                    v-for="(item, index) in options"
+                    :key="index"
+                    :label="item.department"
+                    :value="item.department"
+                  >
+                  </el-option>
+                </el-select>
+              </div>
+              <div class="user">
+                <div class="name">职位</div>
+                <el-input v-model="position"></el-input>
+              </div>
+            </div>
+            <span slot="footer" class="dialog-footer">
+              <el-button @click.stop="addStaffShow = false" class="cancel"
+                >取消</el-button
+              >
+              <el-button @click.stop="addSuccess">完成</el-button>
+            </span>
+          </el-dialog>
+        </div>
+        <!-- 表格区域 -->
+        <div class="inform-table">
+          <el-table
+            :data="tableData"
+            max-height="576"
+            height="576"
+            style="width: 1550px"
+            stripe
+            :cell-style="rowbg"
+            :header-cell-style="{
+              color: ' rgba(0, 0, 0, 1)',
+              background: 'rgba(240, 243, 247, 1)'
+            }"
+          >
+            <el-table-column prop="username" align="center" label="员工姓名">
+            </el-table-column>
+
+            <el-table-column
+              prop="phone"
+              width="300"
+              label="手机号码"
+              align="center"
+            >
+            </el-table-column>
+            <el-table-column prop="departid" align="center" label="部门">
+            </el-table-column>
+            <el-table-column prop="position" align="center" label="职位">
+            </el-table-column>
+            <el-table-column prop="address" align="center" label="操作">
+              <template slot-scope="scope">
+                <!-- 编辑按钮 -->
+                <span @click="handleEdit(scope.row)" class="operate">
+                  编辑
+                </span>
+
+                <!-- 编辑员工弹窗 -->
+                <el-dialog
+                  title="编辑员工"
+                  :visible.sync="editShow"
+                  :close-on-click-modal="false"
+                >
+                  <div class="info">
+                    <div class="user">
+                      <div class="name">员工姓名</div>
+                      <el-input v-model="staffValue"></el-input>
+                    </div>
+                    <div class="user">
+                      <div class="name">手机号码</div>
+                      <el-input v-model="phoneValue"></el-input>
+                    </div>
+                  </div>
+                  <div class="info">
+                    <div class="user">
+                      <div class="name">部门</div>
+                      <el-select v-model="departid" placeholder="请选择">
+                        <el-option
+                          v-for="(item, index) in options"
+                          :key="index"
+                          :label="item.department"
+                          :value="item.department"
+                        >
+                        </el-option>
+                      </el-select>
+                    </div>
+                    <div class="user">
+                      <div class="name">职位</div>
+                      <el-input v-model="position"></el-input>
+                    </div>
+                  </div>
+                  <span slot="footer" class="dialog-footer">
+                    <el-button @click.stop="editShow = false" class="cancel"
+                      >取消</el-button
+                    >
+                    <el-button @click.stop="editSuccess()">完成</el-button>
+                  </span>
+                </el-dialog>
+
+                <!-- 删除按钮 -->
+                <span @click="handleDelete(scope.row)" class="operate">
+                  删除
+                </span>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <!-- 分页器区域 -->
+        <div class="block">
+          <el-pagination
+            background
+            @current-change="handleCurrentChange"
+            :current-page="curPage"
+            :page-size="pageSize"
+            layout="prev, pager, next, jumper"
+            :total="total"
+          >
+          </el-pagination>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+import Cookies from "js-cookie";
+export default {
+  name: "Inform",
+  data() {
+    return {
+      // 当前页显示的数据
+      tableData: [],
+      // 职位类型列表
+      options: [],
+      // 搜索框绑定数据
+      value: "",
+      // 员工姓名
+      staffValue: "",
+      // 员工手机号码
+      phoneValue: "",
+      // 员工部门
+      departid: "",
+      // 员工职位
+      position: "",
+      // 添加员工弹窗控制
+      addStaffShow: false,
+      // 编辑员工弹窗控制
+      editShow: false,
+      // 总条数
+      total: null,
+      // 当前页
+      curPage: 1,
+      // 每页条数
+      pageSize: 8,
+      // 修改数据的id
+      id: ""
+    };
+  },
+  mounted() {
+    this.getData();
+    this.API.hotelStaff
+      .departList()
+      .then(res => {
+        // console.log(res, "员工类型");
+        this.options = res.data;
+      })
+      .catch(err => {
+        console.log(err);
+      });
+  },
+  methods: {
+    // 获取员工管理页面表格数据
+    async getData(curPage) {
+      if (curPage) {
+        this.curPage = curPage;
+      }
+      let params = {
+        curPage: this.curPage,
+        pageSize: this.pageSize,
+        keyword: this.value
+      };
+      let res = await this.API.hotelStaff.hotelStaffList(params);
+      // console.log(res);
+      if (res.success) {
+        this.tableData = res.data.list;
+        this.total = res.data.totalCount;
+      } else {
+        this.$message.error(res.message);
+      }
+    },
+
+    // 删除按钮回调
+    handleDelete(row) {
+      this.$confirm("确定删除吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      })
+        .then(async () => {
+          let res = await this.API.hotelStaff.hotelStaffDelete([row.id]);
+          // console.log(res);
+          if (res.success) {
+            this.$message.success("删除成功");
+            let totalPage = Math.ceil((this.total - 1) / this.pageSize);
+            let currentPage =
+              this.curPage > totalPage ? totalPage : this.curPage;
+            this.curPage = currentPage < 1 ? 1 : currentPage;
+            this.getData();
+          } else {
+            this.$message.error(res.message);
+          }
+        })
+        .catch(() => {
+          this.$message.info("已取消删除");
+        });
+    },
+
+    // 添加员工按钮回调
+    addStaff() {
+      this.addStaffShow = true;
+      this.phoneValue = "";
+      this.staffValue = "";
+      this.position = "";
+      this.departid = "";
+    },
+
+    // 添加员工弹窗完成按钮回调
+    async addSuccess() {
+      let reg_tel = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+      if (reg_tel.test(this.phoneValue)) {
+        if (this.staffValue.trim() == "") {
+          this.$message({ message: "输入的姓名不能为空", type: "error" });
+        } else if (this.position == "") {
+          this.$message({ message: "职位不能为空", type: "error" });
+        } else {
+          let params = {
+            phone: this.phoneValue,
+            username: this.staffValue,
+            position: this.position,
+            departid: this.departid,
+            cardNumber: 666,
+            statu: 1
+          };
+          let res = await this.API.hotelStaff.hotelStaffSave(params);
+          // console.log(res);
+          if (res.success) {
+            this.$message.success("添加成功");
+            this.getData();
+          } else {
+            this.$message.error(res.message);
+          }
+          this.addStaffShow = false;
+        }
+      } else {
+        this.$message({ message: "请输入正确的电话号码", type: "error" });
+      }
+    },
+
+    // 编辑按钮回调
+    handleEdit(row) {
+      // console.log(row);
+      this.editShow = true;
+      this.id = row.id;
+      this.phoneValue = row.phone;
+      this.staffValue = row.username;
+      this.position = row.position;
+      this.departid = row.departid;
+    },
+
+    // 编辑员工弹窗完成按钮回调
+    async editSuccess() {
+      let reg_tel = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+      if (reg_tel.test(this.phoneValue)) {
+        if (this.staffValue.trim() == "") {
+          this.$message({ message: "输入的姓名不能为空", type: "error" });
+        } else if (this.position == "") {
+          this.$message({ message: "职位不能为空", type: "error" });
+        } else {
+          let data = {
+            id: this.id,
+            phone: this.phoneValue,
+            username: this.staffValue,
+            position: this.position,
+            departid: this.departid,
+            cardNumber: 666,
+            statu: 1
+          };
+          let res = await this.API.hotelStaff.hotelStaffUpdate(data);
+          // console.log(res);
+          if (res.success) {
+            this.$message.success("编辑成功");
+            this.getData();
+          } else {
+            this.$message.error(res.message);
+          }
+          this.editShow = false;
+        }
+      } else {
+        this.$message({ message: "请输入正确的电话号码", type: "error" });
+      }
+    },
+
+    // 翻页回调函数
+    handleCurrentChange(val) {
+      this.curPage = val;
+      this.getData();
+    },
+
+    // 表格样式设置
+    rowbg(row) {
+      if (row.rowIndex % 2 != 0) {
+        return { background: "rgba(240, 243, 247, 1)", "border-radius": "5px" };
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.inform /deep/ .el-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+  .clearfix {
+    height: 96px;
+    width: 100%;
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .inform-title {
+      width: 96px;
+      height: 36px;
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-left: 33px;
+    }
+    .inform-header {
+      display: flex;
+      margin-right: 29px;
+      .el-button {
+        margin-left: 9px;
+        background-color: rgba(41, 109, 227, 1);
+      }
+    }
+  }
+  .inform-body {
+    .order-top {
+      display: flex;
+      align-items: center;
+      padding: 33px 29px 34px 33px;
+      font-size: 16px;
+      justify-content: space-between;
+      font-weight: 400;
+      color: #000;
+
+      .el-button {
+        background-color: rgba(41, 109, 227, 1);
+        color: #fff;
+        margin-left: 9px;
+      }
+
+      .state {
+        margin-left: 30px;
+      }
+      .export {
+        width: 94px;
+        height: 32px;
+        opacity: 1;
+        color: #fff;
+        text-align: center;
+        line-height: 32px;
+        background: rgba(41, 109, 227, 1);
+        cursor: pointer;
+        border-radius: 3px;
+      }
+      .el-dialog__wrapper {
+        .el-dialog {
+          width: 555px !important;
+          height: 377px !important;
+          margin-top: 352px !important;
+          .el-dialog__header {
+            height: 80px;
+            padding: 0;
+            display: flex;
+            align-items: center;
+            border-bottom: 1px solid rgba(230, 230, 230, 1);
+            .el-dialog__title {
+              font-size: 20px;
+              font-weight: 500;
+              padding-left: 30px;
+              color: rgba(0, 0, 0, 1);
+            }
+            .el-dialog__headerbtn {
+              width: 35px;
+              height: 35px;
+              .el-dialog__close {
+                font-size: 28px;
+                font-weight: 500;
+                color: #000;
+              }
+            }
+          }
+          .el-dialog__body {
+            padding: 0 30px;
+            .info {
+              display: flex;
+              .user {
+                .name {
+                  text-align: left;
+                  padding: 12px 0;
+                }
+                .el-input {
+                  width: 205px;
+                  height: 38px;
+                  opacity: 1;
+                  margin-right: 81px;
+                  .el-input__inner {
+                    color: #000;
+                  }
+                }
+              }
+            }
+          }
+          .el-dialog__footer {
+            padding: 52px 30px 0 0;
+            .el-button {
+              width: 67px;
+              height: 37px;
+              padding: 0;
+            }
+            .cancel {
+              background-color: #fff;
+              color: rgba(56, 56, 56, 1);
+            }
+          }
+        }
+      }
+    }
+    .inform-table {
+      .el-table {
+        font-weight: 400;
+        font-size: 16px;
+        color: rgba(0, 0, 0, 1);
+        margin: 0 auto;
+        .el-table__header-wrapper {
+          border-radius: 5px;
+        }
+        tr {
+          height: 64px;
+          td {
+            .cell {
+              .operate {
+                color: rgba(41, 109, 227, 1);
+                cursor: pointer;
+                font-size: 16px;
+                font-weight: 400;
+                padding: 0 5px;
+              }
+              .el-dialog__wrapper {
+                overflow: hidden;
+                .el-dialog {
+                  width: 555px !important;
+                  height: 377px !important;
+                  margin-top: 352px !important;
+                  opacity: 1;
+                  background: rgba(255, 255, 255, 1);
+                  box-shadow: none;
+                  .el-dialog__header {
+                    height: 83px;
+                    border-bottom: 1px solid rgba(230, 230, 230, 1);
+                    .el-dialog__headerbtn {
+                      top: 29px;
+                    }
+                    span {
+                      float: left;
+                      font-size: 20px;
+                      font-weight: 500;
+                      color: #000;
+                    }
+
+                    .el-dialog__close {
+                      width: 35px;
+                      height: 35px;
+                      &::before {
+                        font-size: 24px;
+                        color: #000;
+                      }
+                    }
+                  }
+                  .el-dialog__body {
+                    padding: 0 38px;
+                    .userinfo {
+                      font-size: 20px;
+                      font-weight: 500;
+                      color: rgba(0, 0, 0, 1);
+                      text-align: left;
+                      margin-top: 15px;
+                    }
+                    .info {
+                      display: flex;
+                      .user {
+                        .name {
+                          text-align: left;
+                          padding: 12px 0;
+                        }
+                        .el-input {
+                          width: 205px;
+                          height: 38px;
+                          opacity: 1;
+                          margin-right: 81px;
+                          .el-input__inner {
+                            color: #000;
+                          }
+                        }
+                      }
+                    }
+                    .rate {
+                      font-size: 20px;
+                      font-weight: 500;
+                      color: rgba(0, 0, 0, 1);
+                      text-align: left;
+                      margin: 15px 0;
+                    }
+                    .water {
+                      text-align: left;
+                      font-size: 16px;
+                      font-weight: 400;
+                      color: rgba(0, 0, 0, 1);
+                    }
+                    .el-table {
+                      margin: 10px 0;
+                      tr {
+                        height: 48px;
+                      }
+                      .el-table__body-wrapper {
+                        overflow: visible;
+                      }
+                    }
+                  }
+                  .el-dialog__footer {
+                    padding: 52px 30px 0 0;
+                    .el-button {
+                      width: 67px;
+                      height: 37px;
+                      padding: 0;
+                      color: #fff;
+                      background-color: rgba(41, 109, 227, 1);
+                    }
+                    .cancel {
+                      background-color: #fff;
+                      color: rgba(56, 56, 56, 1);
+                    }
+                  }
+                }
+              }
+
+              .el-dialog__title {
+                color: rgba(41, 109, 227, 1);
+                cursor: pointer;
+                padding: 10px 18px 0 18px;
+              }
+            }
+          }
+          .el-checkbox__inner {
+            width: 18px;
+            height: 18px;
+            &::after {
+              transform: rotate(45deg) scaleY(1.5);
+              top: 3px;
+              left: 5px;
+            }
+          }
+        }
+        th {
+          font-weight: 400;
+        }
+      }
+    }
+    .block {
+      height: 36px;
+      margin: 66px 29px 0 0;
+      float: right;
+      .el-pagination {
+        padding: 0;
+        button {
+          width: 36px;
+          background: #fff;
+          color: rgba(0, 0, 0, 1);
+          height: 36px;
+          font-size: 14px;
+          border: 1px solid rgba(112, 112, 112, 1);
+          border-radius: 8px;
+        }
+        ul {
+          .active {
+            background: #fff;
+            color: rgba(0, 97, 255, 1);
+            border: 1px solid rgba(0, 97, 255, 1);
+            box-sizing: border-box;
+          }
+          .el-icon {
+            border: none;
+          }
+          li {
+            background: #fff;
+            color: rgba(0, 0, 0, 1);
+            width: 36px;
+            height: 36px;
+            font-size: 14px;
+            border: 1px solid rgba(112, 112, 112, 1);
+            border-radius: 8px;
+            line-height: 36px;
+          }
+        }
+        .el-pagination__jump {
+          color: rgba(0, 0, 0, 1);
+          font-size: 16px;
+          margin-left: 10px;
+          font-weight: 400;
+          height: 36px;
+          .el-input {
+            width: 65px;
+            height: 36px;
+            margin: 0 10px;
+            input {
+              width: 65px;
+              height: 36px;
+              border: 1px solid rgba(0, 0, 0, 1);
+              border-radius: 8px;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+  /deep/ .el-card__body {
+    padding: 0;
+  }
+}
+</style>
+<style lang="scss">
+.el-message-box__wrapper {
+  .el-button--primary {
+    background-color: #296de3 !important;
+  }
+}
+</style>

+ 360 - 0
src/views/stat/index.vue

@@ -0,0 +1,360 @@
+<template>
+  <div class="stat">
+    <el-card class="box-card" style="width: 1612px; height: 950px">
+      <!-- 标题区域 -->
+      <div slot="header" class="clearfix">
+        <div class="stat-title">统计报表</div>
+      </div>
+
+      <!-- 主体内容区域 -->
+      <div class="stat-body">
+        <!-- 筛选条件区域 -->
+        <div class="state">
+          <div class="left">
+            <el-col :span="4" style="margin-right:10px">
+              <!-- <el-button type="primary" @click="getData">查询</el-button> -->
+              <span>筛选时间:</span>
+            </el-col>
+            <el-col :span="20">
+              <el-date-picker
+                v-model="searchValue"
+                type="monthrange"
+                range-separator="至"
+                start-placeholder="开始月份"
+                end-placeholder="结束月份"
+                style="width: 350px"
+                value-format="yyyy-MM"
+                @change="getData"
+              ></el-date-picker>
+            </el-col>
+          </div>
+          <div class="right">
+            <el-button type="primary" @click="handleExport">导出明细</el-button>
+          </div>
+        </div>
+        <!-- 表格区域 -->
+        <div class="stat-table">
+          <el-table
+            :data="tableData"
+            style="width: 1550px"
+            max-height="512"
+            stripe
+            :cell-style="rowbg"
+            :header-cell-style="{
+              color: ' rgba(0, 0, 0, 1)',
+              background: 'rgba(240, 243, 247, 1)',
+            }"
+          >
+            <el-table-column prop="time" align="center" label="时间"></el-table-column>
+
+            <el-table-column prop="waterPayment" align="center" label="水费(元)"></el-table-column>
+
+            <el-table-column prop="electricPayment" align="center" label="电费(元)"></el-table-column>
+
+            <el-table-column prop="refundPayment" align="center" label="退预付款(元)"></el-table-column>
+
+            <el-table-column prop="totalPayment" align="center" label="合计(元)"></el-table-column>
+          </el-table>
+          <div class="total">
+            <ul>
+              <li>合计/元</li>
+              <li>{{totalNum.waterTotalPayment}}</li>
+              <li>{{totalNum.electricTotalPayment}}</li>
+              <li>{{totalNum.refundTotalPayment}}</li>
+              <li>{{totalNum.tatolPayment}}</li>
+            </ul>
+          </div>
+        </div>
+
+        <!-- 分页器区域 -->
+      </div>
+      <div class="block">
+        <el-pagination
+          background
+          layout="prev, pager, next, jumper"
+          :current-page.sync="currentPage"
+          :page-size="pageSize"
+          :total="total"
+          @current-change="handleCurrentChange"
+        ></el-pagination>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "stat",
+  data() {
+    return {
+      totalNum: [],
+      // 表格数据
+      tableData: [],
+      //  时间搜索框绑定数据
+      searchValue: [],
+      //   分页器当前页
+      currentPage: 1,
+      //   每页条数
+      pageSize: 7,
+      //   总条数
+      total: 1
+    };
+  },
+  mounted() {
+    this.getData();
+  },
+  // watch: {
+  //   searchValue(newVal, oldVal) {
+  //     console.log(newVal);
+  //     if (!newVal) {
+  //       this.getData();
+  //     }
+  //   }
+  // },
+  methods: {
+    async getData() {
+      if (!this.searchValue) {
+        this.searchValue = ["", ""];
+      }
+      let params = {
+        curPage: this.currentPage,
+        pageSize: this.pageSize,
+        startTime: this.searchValue[0],
+        endTime: this.searchValue[1]
+      };
+
+      let res = await this.API.stat.queryReport(params);
+      // console.log(res);
+      this.totalNum = res.data;
+      if (res.success) {
+        this.tableData = res.data.page.list;
+        this.total = res.data.page.totalCount;
+      } else {
+        this.$message.error(res.message);
+      }
+    },
+    // 时间搜索框数值改变触发回调
+    // changeTime(val) {
+    //   console.log(val);
+    //   this.searchValue = val;
+    // },
+    // 导出明细按钮回调
+    handleExport() {
+      let params = {
+        startTime: this.searchValue[0],
+        endTime: this.searchValue[1]
+      };
+      this.API.stat.downLoadReport(params).then(res => {
+        // console.log(res);
+        let name = `统计报表数据`;
+        var content = res;
+        // var data = new Blob([content],{type:"application/octet-stream;charset=utf-8"});
+        var data = new Blob([content], {
+          type: "application/vnd.ms-excel;charset=utf-8"
+        });
+        var downloadUrl = window.URL.createObjectURL(data);
+        var anchor = document.createElement("a");
+        anchor.href = downloadUrl;
+        anchor.download = name + ".xlsx";
+        anchor.click();
+        window.URL.revokeObjectURL(data);
+        if (res) {
+          this.$message.success("导出成功");
+        } else {
+          this.$message.error("导出失败");
+        }
+      });
+      // this.$confirm("确定导出吗?", "提示", {
+      //   confirmButtonText: "确定",
+      //   cancelButtonText: "取消",
+      //   type: "warning"
+      // })
+      //   .then(() => {
+      //     let params = {
+      //       curPage: this.currentPage,
+      //       pageSize: this.pageSize,
+      //       startTime: this.searchValue[0],
+      //       endTime: this.searchValue[1]
+      //     };
+      //     this.API.stat.downLoadReport(params).then(res => {
+      //       console.log(res);
+      //       if (res) {
+      //         this.$message.success("导出成功");
+      //       }
+      //     });
+      //   })
+      //   .catch(() => {
+      //     this.$message.info("已取消");
+      //   });
+    },
+    // 分页器换页回调
+    handleCurrentChange(value) {
+      // console.log(value);
+      this.currentPage = value;
+      this.getData();
+    },
+    // 表格样式设置回调
+    rowbg(row) {
+      if (row.rowIndex % 2 != 0) {
+        return { background: "rgba(240, 243, 247, 1)", "border-radius": "5px" };
+      }
+      // if (row.rowIndex == this.tableData.length - 1) {
+      //   return { fontWeight: "bold" };
+      // }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.stat /deep/ .el-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+  .clearfix {
+    height: 96px;
+    width: 100%;
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .stat-title {
+      width: 96px;
+      height: 36px;
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-left: 33px;
+    }
+  }
+  .stat-body {
+    height: 82%;
+    .state {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 33px 29px 34px 33px;
+      .left {
+        display: flex;
+        align-items: center;
+        .el-button {
+          margin-left: 10px;
+          background-color: rgba(41, 109, 227, 1);
+        }
+      }
+      .right {
+        .el-button {
+          background-color: rgba(41, 109, 227, 1);
+        }
+      }
+    }
+    .stat-table {
+      .el-table {
+        font-weight: 400;
+        font-size: 16px;
+        color: rgba(0, 0, 0, 1);
+        margin: 0 auto;
+        .el-table__header-wrapper {
+          border-radius: 5px;
+        }
+        tr {
+          height: 64px;
+        }
+        th {
+          font-weight: 400;
+        }
+      }
+      .total {
+        width: 1550px;
+        height: 64px;
+
+        margin: 0 auto;
+        ul {
+          display: flex;
+          // justify-content: center;
+          list-style: none;
+          // align-items: center;
+          padding: 0;
+          li {
+            flex: 1;
+            height: 64px;
+            line-height: 64px;
+            text-align: center;
+            font-weight: bold;
+          }
+        }
+      }
+    }
+  }
+  .block {
+    height: 36px;
+    margin: 0 29px 0px 0;
+    float: right;
+    // position: absolute;
+    // bottom: 60px;
+    // right: 29px;
+    .el-pagination {
+      padding: 0;
+      button {
+        width: 36px;
+        background: #fff;
+        color: rgba(0, 0, 0, 1);
+        height: 36px;
+        font-size: 14px;
+        border: 1px solid rgba(112, 112, 112, 1);
+        border-radius: 8px;
+      }
+      ul {
+        .active {
+          background: #fff;
+          color: rgba(0, 97, 255, 1);
+          border: 1px solid rgba(0, 97, 255, 1);
+          box-sizing: border-box;
+        }
+        .el-icon {
+          border: none;
+        }
+        li {
+          background: #fff;
+          color: rgba(0, 0, 0, 1);
+          width: 36px;
+          height: 36px;
+          font-size: 14px;
+          border: 1px solid rgba(112, 112, 112, 1);
+          border-radius: 8px;
+          line-height: 36px;
+        }
+      }
+      .el-pagination__jump {
+        color: rgba(0, 0, 0, 1);
+        font-size: 16px;
+        margin-left: 10px;
+        font-weight: 400;
+        height: 36px;
+        .el-input {
+          width: 65px;
+          height: 36px;
+          margin: 0 10px;
+          input {
+            width: 65px;
+            height: 36px;
+            border: 1px solid rgba(0, 0, 0, 1);
+            border-radius: 8px;
+          }
+        }
+      }
+    }
+  }
+}
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+  /deep/ .el-card__body {
+    padding: 0;
+    height: 100%;
+  }
+}
+</style>

+ 281 - 0
src/views/system/index.vue

@@ -0,0 +1,281 @@
+<template>
+  <div class="stat">
+    <el-card class="box-card" style="width: 1612px; height: 950px">
+      <!-- 标题区域 -->
+      <div slot="header" class="clearfix">
+        <div class="stat-title">系统设置</div>
+      </div>
+
+      <!-- 主体内容区域 -->
+      <div class="stat-body">
+        <!-- 预付款区域 -->
+        <div>
+          预付款每晚不得少于
+          <el-input
+            class="input-new-tag"
+            v-if="imprestShow"
+            v-model="inputValue_imprest"
+            v-fo
+            size="small"
+            @blur="handleInputConfirm('imprestShow', 'inputValue_imprest')"
+          ></el-input>
+          <el-button
+            v-else
+            class="button-new-tag"
+            size="small"
+            @click="showInput('imprestShow', 'inputValue_imprest')"
+          >{{ inputValue_imprest }}</el-button>元
+        </div>
+
+        <!-- 预定天数区域 -->
+        <div>
+          设置预定/续住天数提前
+          <el-input
+            class="input-new-tag"
+            v-if="reserveShow"
+            v-model="inputValue_reserve"
+            size="small"
+            @blur="handleInputConfirm('reserveShow', 'inputValue_reserve')"
+          ></el-input>
+          <el-button
+            v-else
+            class="button-new-tag"
+            size="small"
+            @click="showInput('reserveShow', 'inputValue_reserve')"
+          >{{ inputValue_reserve }}</el-button>天
+        </div>
+
+        <!-- 补助区域 -->
+        <div>
+          每人补助水量为
+          <el-input
+            class="input-new-tag"
+            v-if="subsidyWaterShow"
+            v-model="inputValue_subsidyWater"
+            v-fo
+            size="small"
+            @blur="
+              handleInputConfirm('subsidyWaterShow', 'inputValue_subsidyWater')
+            "
+          ></el-input>
+          <el-button
+            v-else
+            class="button-new-tag"
+            size="small"
+            @click="showInput('subsidyWaterShow', 'inputValue_subsidyWater')"
+          >{{ inputValue_subsidyWater }}</el-button>吨,每人补助电量为
+          <el-input
+            class="input-new-tag"
+            v-if="subsidyElectricityShow"
+            v-model="inputValue_subsidyElectricity"
+            v-fo
+            size="small"
+            @blur="
+              handleInputConfirm(
+                'subsidyElectricityShow',
+                'inputValue_subsidyElectricity'
+              )
+            "
+          ></el-input>
+          <el-button
+            v-else
+            class="button-new-tag"
+            size="small"
+            @click="
+              showInput(
+                'subsidyElectricityShow',
+                'inputValue_subsidyElectricity'
+              )
+            "
+          >{{ inputValue_subsidyElectricity }}</el-button>度
+        </div>
+
+        <!-- 通知人区域 -->
+        <!-- <div>
+          设置清扫/查房通知人<el-input
+            class="input-new-tag"
+            v-if="informShow"
+            v-model="inputValue_inform"
+            v-fo
+            size="small"
+            @blur="handleInputConfirm('informShow', 'inputValue_inform')"
+          >
+          </el-input>
+          <el-button
+            v-else
+            class="button-new-tag"
+            size="small"
+            @click="showInput('informShow', 'inputValue_inform')"
+          >
+            {{ inputValue_inform }}
+          </el-button>
+        </div>-->
+
+        <!-- 退房区域 -->
+        <div>
+          设置退房后
+          <el-input
+            class="input-new-tag"
+            v-if="refundShow"
+            v-model="inputValue_refund"
+            v-fo
+            size="small"
+            @blur="handleInputConfirm('refundShow', 'inputValue_refund')"
+          ></el-input>
+          <el-button
+            v-else
+            class="button-new-tag"
+            size="small"
+            @click="showInput('refundShow', 'inputValue_refund')"
+          >{{ inputValue_refund }}</el-button>分钟,断水断电
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "system",
+  data() {
+    return {
+      // 预付款输入框按钮框切换控制
+      imprestShow: false,
+      // 预付款绑定数据
+      inputValue_imprest: 20,
+
+      // 预定天数输入框按钮框切换控制
+      reserveShow: false,
+      // 预定天数绑定数据
+      inputValue_reserve: 10,
+
+      // 补助区域水量输入框按钮框切换控制
+      subsidyWaterShow: false,
+      // 补助区域水量绑定数据
+      inputValue_subsidyWater: 5,
+
+      // 补助区域电量输入框按钮框切换控制
+      subsidyElectricityShow: false,
+      // 补助区域电量绑定数据
+      inputValue_subsidyElectricity: 9,
+
+      // 通知人输入框按钮框切换控制
+      // informShow: false,
+      // // 通知人绑定数据
+      // inputValue_inform: 7,
+      priceOfWater: 5, // 水费单价
+      priceOfElectric: 5, // 电费单价
+      // 退房输入框按钮框切换控制
+      refundShow: false,
+      // 退房绑定数据
+      inputValue_refund: 8,
+      id: ""
+    };
+  },
+  mounted() {
+    this.getData();
+  },
+  methods: {
+    // 获取系统设置页面数据
+    async getData() {
+      let res = await this.API.systemSetup.systemSettingInfo();
+      // console.log(res);
+      if (res.success) {
+        this.inputValue_imprest = res.data.deposit;
+        this.inputValue_reserve = res.data.preDay;
+        this.inputValue_subsidyWater = res.data.freeQuotaOfWater;
+        this.inputValue_subsidyElectricity = res.data.freeQuotaOfElectric;
+        // this.inputValue_inform = res.data.deposit;
+        // console.log(res.data.priceOfWater);
+        this.priceOfWater = res.data.priceOfWater;
+        this.priceOfElectric = res.data.priceOfElectric;
+        this.inputValue_refund = res.data.turnOffTime;
+        this.id = res.data.id;
+      } else {
+        this.$message.error(res.message);
+      }
+    },
+    // 点击按钮 切换输入框 并聚焦
+    showInput(show, value) {
+      this[show] = true;
+      this[value] = null;
+    },
+    // 失去焦点 切换回按钮 并更新数据
+    async handleInputConfirm(show) {
+      this[show] = false;
+      let data = {
+        id: this.id,
+        deposit: this.inputValue_imprest,
+        preDay: this.inputValue_reserve,
+        priceOfWater: this.priceOfWater,
+        priceOfElectric: this.priceOfElectric,
+        freeQuotaOfWater: this.inputValue_subsidyWater,
+        freeQuotaOfElectric: this.inputValue_subsidyElectricity,
+        freeTotal:
+          this.priceOfWater * this.inputValue_subsidyWater +
+          this.priceOfElectric * this.inputValue_subsidyElectricity,
+        turnOffTime: this.inputValue_refund
+      };
+      let res = await this.API.systemSetup.systemnoticeUpdate(data);
+      // console.log(res);
+      if (res.success) {
+        this.$message.success("修改成功");
+        this.getData();
+      } else {
+        this.$message.error(res.message);
+      }
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.stat /deep/ .el-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+  .clearfix {
+    height: 96px;
+    width: 100%;
+    border-bottom: 1px solid rgba(204, 204, 204, 1);
+    box-sizing: border-box;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    .stat-title {
+      width: 96px;
+      height: 36px;
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-left: 33px;
+    }
+  }
+  .stat-body {
+    padding: 30px 0 0 30px;
+    div {
+      height: 40px;
+      margin-bottom: 28px;
+      .input-new-tag {
+        margin: 0 10px;
+        width: 90px;
+      }
+      .button-new-tag {
+        margin: 0 10px;
+        width: 90px;
+        height: 32px;
+        vertical-align: middle;
+      }
+    }
+  }
+}
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+  /deep/ .el-card__body {
+    padding: 0;
+  }
+}
+</style>

+ 0 - 0
static/.gitkeep


+ 27 - 0
test/e2e/custom-assertions/elementCount.js

@@ -0,0 +1,27 @@
+// A custom Nightwatch assertion.
+// The assertion name is the filename.
+// Example usage:
+//
+//   browser.assert.elementCount(selector, count)
+//
+// For more information on custom assertions see:
+// http://nightwatchjs.org/guide#writing-custom-assertions
+
+exports.assertion = function (selector, count) {
+  this.message = 'Testing if element <' + selector + '> has count: ' + count
+  this.expected = count
+  this.pass = function (val) {
+    return val === this.expected
+  }
+  this.value = function (res) {
+    return res.value
+  }
+  this.command = function (cb) {
+    var self = this
+    return this.api.execute(function (selector) {
+      return document.querySelectorAll(selector).length
+    }, [selector], function (res) {
+      cb.call(self, res)
+    })
+  }
+}

+ 46 - 0
test/e2e/nightwatch.conf.js

@@ -0,0 +1,46 @@
+require('babel-register')
+var config = require('../../config')
+
+// http://nightwatchjs.org/gettingstarted#settings-file
+module.exports = {
+  src_folders: ['test/e2e/specs'],
+  output_folder: 'test/e2e/reports',
+  custom_assertions_path: ['test/e2e/custom-assertions'],
+
+  selenium: {
+    start_process: true,
+    server_path: require('selenium-server').path,
+    host: '127.0.0.1',
+    port: 4444,
+    cli_args: {
+      'webdriver.chrome.driver': require('chromedriver').path
+    }
+  },
+
+  test_settings: {
+    default: {
+      selenium_port: 4444,
+      selenium_host: 'localhost',
+      silent: true,
+      globals: {
+        devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
+      }
+    },
+
+    chrome: {
+      desiredCapabilities: {
+        browserName: 'chrome',
+        javascriptEnabled: true,
+        acceptSslCerts: true
+      }
+    },
+
+    firefox: {
+      desiredCapabilities: {
+        browserName: 'firefox',
+        javascriptEnabled: true,
+        acceptSslCerts: true
+      }
+    }
+  }
+}

+ 48 - 0
test/e2e/runner.js

@@ -0,0 +1,48 @@
+// 1. start the dev server using production config
+process.env.NODE_ENV = 'testing'
+
+const webpack = require('webpack')
+const DevServer = require('webpack-dev-server')
+
+const webpackConfig = require('../../build/webpack.prod.conf')
+const devConfigPromise = require('../../build/webpack.dev.conf')
+
+let server
+
+devConfigPromise.then(devConfig => {
+  const devServerOptions = devConfig.devServer
+  const compiler = webpack(webpackConfig)
+  server = new DevServer(compiler, devServerOptions)
+  const port = devServerOptions.port
+  const host = devServerOptions.host
+  return server.listen(port, host)
+})
+.then(() => {
+  // 2. run the nightwatch test suite against it
+  // to run in additional browsers:
+  //    1. add an entry in test/e2e/nightwatch.conf.js under "test_settings"
+  //    2. add it to the --env flag below
+  // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
+  // For more information on Nightwatch's config file, see
+  // http://nightwatchjs.org/guide#settings-file
+  let opts = process.argv.slice(2)
+  if (opts.indexOf('--config') === -1) {
+    opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
+  }
+  if (opts.indexOf('--env') === -1) {
+    opts = opts.concat(['--env', 'chrome'])
+  }
+
+  const spawn = require('cross-spawn')
+  const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
+
+  runner.on('exit', function (code) {
+    server.close()
+    process.exit(code)
+  })
+
+  runner.on('error', function (err) {
+    server.close()
+    throw err
+  })
+})

+ 19 - 0
test/e2e/specs/test.js

@@ -0,0 +1,19 @@
+// For authoring Nightwatch tests, see
+// http://nightwatchjs.org/guide#usage
+
+module.exports = {
+  'default e2e tests': function (browser) {
+    // automatically uses dev Server port from /config.index.js
+    // default: http://localhost:8080
+    // see nightwatch.conf.js
+    const devServer = browser.globals.devServerURL
+
+    browser
+      .url(devServer)
+      .waitForElementVisible('#app', 5000)
+      .assert.elementPresent('.hello')
+      .assert.containsText('h1', 'Welcome to Your Vue.js App')
+      .assert.elementCount('img', 1)
+      .end()
+  }
+}

+ 7 - 0
test/unit/.eslintrc

@@ -0,0 +1,7 @@
+{
+  "env": { 
+    "jest": true
+  },
+  "globals": { 
+  }
+}

+ 30 - 0
test/unit/jest.conf.js

@@ -0,0 +1,30 @@
+const path = require('path')
+
+module.exports = {
+  rootDir: path.resolve(__dirname, '../../'),
+  moduleFileExtensions: [
+    'js',
+    'json',
+    'vue'
+  ],
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  transform: {
+    '^.+\\.js$': '<rootDir>/node_modules/babel-jest',
+    '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest'
+  },
+  testPathIgnorePatterns: [
+    '<rootDir>/test/e2e'
+  ],
+  snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
+  setupFiles: ['<rootDir>/test/unit/setup'],
+  mapCoverage: true,
+  coverageDirectory: '<rootDir>/test/unit/coverage',
+  collectCoverageFrom: [
+    'src/**/*.{js,vue}',
+    '!src/main.js',
+    '!src/router/index.js',
+    '!**/node_modules/**'
+  ]
+}

+ 3 - 0
test/unit/setup.js

@@ -0,0 +1,3 @@
+import Vue from 'vue'
+
+Vue.config.productionTip = false

+ 0 - 0
test/unit/specs/HelloWorld.spec.js


Неке датотеке нису приказане због велике количине промена