Browse Source

管理端项目

hzj18279462576@163.com 3 years ago
commit
31b284522d
64 changed files with 21062 additions and 0 deletions
  1. 18 0
      admin/.babelrc
  2. 9 0
      admin/.editorconfig
  3. 5 0
      admin/.eslintignore
  4. 29 0
      admin/.eslintrc.js
  5. 17 0
      admin/.gitignore
  6. 10 0
      admin/.postcssrc.js
  7. 30 0
      admin/README.md
  8. 41 0
      admin/build/build.js
  9. 54 0
      admin/build/check-versions.js
  10. BIN
      admin/build/logo.png
  11. 101 0
      admin/build/utils.js
  12. 22 0
      admin/build/vue-loader.conf.js
  13. 103 0
      admin/build/webpack.base.conf.js
  14. 95 0
      admin/build/webpack.dev.conf.js
  15. 149 0
      admin/build/webpack.prod.conf.js
  16. 7 0
      admin/config/dev.env.js
  17. 76 0
      admin/config/index.js
  18. 4 0
      admin/config/prod.env.js
  19. 7 0
      admin/config/test.env.js
  20. 12 0
      admin/index.html
  21. 18161 0
      admin/package-lock.json
  22. 93 0
      admin/package.json
  23. 30 0
      admin/src/App.vue
  24. 1 0
      admin/src/assets/icons/svg/account-active.svg
  25. 1 0
      admin/src/assets/icons/svg/account.svg
  26. 1 0
      admin/src/assets/icons/svg/home-active.svg
  27. 1 0
      admin/src/assets/icons/svg/home.svg
  28. 1 0
      admin/src/assets/icons/svg/item-logo.svg
  29. 1 0
      admin/src/assets/icons/svg/order-active.svg
  30. 1 0
      admin/src/assets/icons/svg/order.svg
  31. 1 0
      admin/src/assets/icons/svg/quit.svg
  32. 1 0
      admin/src/assets/icons/svg/sousuo.svg
  33. 1 0
      admin/src/assets/icons/svg/staff-active.svg
  34. 1 0
      admin/src/assets/icons/svg/staff.svg
  35. 1 0
      admin/src/assets/icons/svg/stat-active.svg
  36. 1 0
      admin/src/assets/icons/svg/stat.svg
  37. 1 0
      admin/src/assets/icons/svg/system-active.svg
  38. 1 0
      admin/src/assets/icons/svg/system.svg
  39. 1 0
      admin/src/assets/icons/svg/tuifang.svg
  40. 1 0
      admin/src/assets/icons/svg/xiaoxizhongxin.svg
  41. 0 0
      admin/src/assets/scss/variable.scss
  42. 82 0
      admin/src/components/SvgIcon/index.vue
  43. 113 0
      admin/src/layout/components/Navbar.vue
  44. 150 0
      admin/src/layout/components/NavbarItem.vue
  45. 62 0
      admin/src/layout/index.vue
  46. 19 0
      admin/src/main.js
  47. 54 0
      admin/src/router/index.js
  48. 13 0
      admin/src/views/account/index.vue
  49. 871 0
      admin/src/views/home/index.vue
  50. 359 0
      admin/src/views/inform/index.vue
  51. 12 0
      admin/src/views/order/index.vue
  52. 13 0
      admin/src/views/staff/index.vue
  53. 13 0
      admin/src/views/stat/index.vue
  54. 13 0
      admin/src/views/system/index.vue
  55. 0 0
      admin/static/.gitkeep
  56. 27 0
      admin/test/e2e/custom-assertions/elementCount.js
  57. 46 0
      admin/test/e2e/nightwatch.conf.js
  58. 48 0
      admin/test/e2e/runner.js
  59. 19 0
      admin/test/e2e/specs/test.js
  60. 7 0
      admin/test/unit/.eslintrc
  61. 30 0
      admin/test/unit/jest.conf.js
  62. 3 0
      admin/test/unit/setup.js
  63. 11 0
      admin/test/unit/specs/HelloWorld.spec.js
  64. 7 0
      admin/vue.config.js

+ 18 - 0
admin/.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
admin/.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
admin/.eslintignore

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

+ 29 - 0
admin/.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
admin/.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
admin/.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 - 0
admin/README.md

@@ -0,0 +1,30 @@
+# 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
admin/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
admin/build/check-versions.js

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

BIN
admin/build/logo.png


+ 101 - 0
admin/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
admin/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
admin/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'
+  }
+}

+ 95 - 0
admin/build/webpack.dev.conf.js

@@ -0,0 +1,95 @@
+'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') },
+      ],
+    },
+    hot: true,
+    contentBase: false, // since we use CopyWebpackPlugin.
+    compress: true,
+    host: HOST || config.dev.host,
+    port: PORT || config.dev.port,
+    open: config.dev.autoOpenBrowser,
+    overlay: config.dev.errorOverlay
+      ? { warnings: false, errors: true }
+      : false,
+    publicPath: config.dev.assetsPublicPath,
+    proxy: config.dev.proxyTable,
+    quiet: true, // necessary for FriendlyErrorsPlugin
+    watchOptions: {
+      poll: config.dev.poll,
+    }
+  },
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': require('../config/dev.env')
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
+    new webpack.NoEmitOnErrorsPlugin(),
+    // https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: 'index.html',
+      inject: true
+    }),
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.dev.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+module.exports = new Promise((resolve, reject) => {
+  portfinder.basePort = process.env.PORT || config.dev.port
+  portfinder.getPort((err, port) => {
+    if (err) {
+      reject(err)
+    } else {
+      // publish the new Port, necessary for e2e tests
+      process.env.PORT = port
+      // add port to devServer config
+      devWebpackConfig.devServer.port = port
+
+      // Add FriendlyErrorsPlugin
+      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
+        compilationSuccessInfo: {
+          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
+        },
+        onErrors: config.dev.notifyOnErrors
+        ? utils.createNotifierCallback()
+        : undefined
+      }))
+
+      resolve(devWebpackConfig)
+    }
+  })
+})

+ 149 - 0
admin/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
admin/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"'
+})

+ 76 - 0
admin/config/index.js

@@ -0,0 +1,76 @@
+'use strict'
+// Template version: 1.3.1
+// see http://vuejs-templates.github.io/webpack for documentation.
+
+const path = require('path')
+
+module.exports = {
+  dev: {
+
+    // Paths
+    assetsSubDirectory: 'static',
+    assetsPublicPath: '/',
+    proxyTable: {},
+
+    // Various Dev Server settings
+    host: 'localhost', // can be overwritten by process.env.HOST
+    port: 8081, // 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: '/',
+
+    /**
+     * 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
admin/config/prod.env.js

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

+ 7 - 0
admin/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
admin/index.html

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

File diff suppressed because it is too large
+ 18161 - 0
admin/package-lock.json


+ 93 - 0
admin/package.json

@@ -0,0 +1,93 @@
+{
+  "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",
+    "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": {
+    "better-scroll": "^2.4.2",
+    "element-ui": "^2.15.2",
+    "moment": "^2.29.4",
+    "svg-sprite-loader": "^4.3.0",
+    "vue": "^2.5.2",
+    "vue-router": "^3.0.1"
+  },
+  "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"
+  ]
+}

+ 30 - 0
admin/src/App.vue

@@ -0,0 +1,30 @@
+<template>
+    <div id="app">
+        <router-view />
+    </div>
+</template>
+
+<script>
+export default {
+    name: 'App'
+}
+</script>
+
+<style>
+#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%;
+
+}
+</style>

File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/account-active.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/account.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/home-active.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/home.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/item-logo.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/order-active.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/order.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/quit.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/sousuo.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/staff-active.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/staff.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/stat-active.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/stat.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/system-active.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/system.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/tuifang.svg


File diff suppressed because it is too large
+ 1 - 0
admin/src/assets/icons/svg/xiaoxizhongxin.svg


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


+ 82 - 0
admin/src/components/SvgIcon/index.vue

@@ -0,0 +1,82 @@
+<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>

+ 113 - 0
admin/src/layout/components/Navbar.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="navbar">
+    <div class="nav-title">智慧校园公寓管理端</div>
+    <div class="right">
+      <p class="time">{{ time }}</p>
+      <el-badge :value="3" 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">名字</div>
+      <div class="out">
+        <IconSvg :W="16" :H="16" name="quit" />
+        <div class="title">退出</div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+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"),
+    };
+  },
+  methods: {
+    informs() {
+      this.$router.replace({ name: "Inform" }).catch((err) => {
+        console.log();
+      });
+    },
+  },
+};
+</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;
+
+      .title {
+        margin-left: 6px;
+        font-size: 14px;
+      }
+    }
+  }
+}
+</style>

+ 150 - 0
admin/src/layout/components/NavbarItem.vue

@@ -0,0 +1,150 @@
+<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="ItemIndex"
+      class="el-menu-vertical-demo"
+      @open="handleOpen"
+      @close="handleClose"
+      :collapse="isCollapse"
+      @select="select"
+      router
+    >
+      <el-menu-item index="/home">
+        <div class="icons">
+          <IconSvg
+            :W="28"
+            :H="26"
+            :name="ItemIndex == '/home' ? 'home-active' : 'home'"
+          />
+        </div>
+        <span slot="title">房态管理</span>
+      </el-menu-item>
+      <el-menu-item index="/order">
+        <div class="icons">
+          <IconSvg
+            :W="21"
+            :H="26"
+            :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="24"
+            :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="20"
+            :name="ItemIndex == '/account' ? 'account-active' : 'account'"
+          />
+        </div>
+        <span slot="title">账号管理</span>
+      </el-menu-item>
+      <el-menu-item index="/stat">
+        <div class="icons">
+          <IconSvg
+            :W="22"
+            :H="22"
+            :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,
+      ItemIndex: "/home",
+    };
+  },
+  mounted() {
+    console.log(this.ItemIndex);
+  },
+  methods: {
+    select(index, indexPath) {
+      this.ItemIndex = index;
+    },
+    handleOpen(key, keyPath) {
+      console.log(key, keyPath);
+    },
+    handleClose(key, keyPath) {
+      console.log(key, keyPath);
+    },
+  },
+};
+</script>
+
+<style scoped lang="scss">
+.navitem {
+  width: 260px;
+
+  .logo {
+    height: 96px;
+    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;
+
+      .icons {
+        margin-left: 27px;
+      }
+
+      span {
+        width: 87px;
+        height: 26px;
+        display: flex;
+        align-items: center;
+        padding-left: 16px;
+      }
+    }
+  }
+}
+</style>

+ 62 - 0
admin/src/layout/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="box">
+    <el-container style="height: 100%">
+      <el-aside>
+        <NavbarItem></NavbarItem>
+      </el-aside>
+      <el-container>
+        <el-header>
+          <Navbar></Navbar>
+        </el-header>
+        <el-main>
+          <router-view></router-view>
+        </el-main>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script>
+import NavbarItem from "@/layout/components/NavbarItem";
+import Navbar from "@/layout/components/Navbar";
+export default {
+  name: "Layout",
+  components: { NavbarItem, Navbar },
+  data() {
+    return {};
+  },
+  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, 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>

+ 19 - 0
admin/src/main.js

@@ -0,0 +1,19 @@
+// The Vue build version to load with the `import` command
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
+import Vue from 'vue'
+import App from './App'
+import router from './router'
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+
+Vue.component('IconSvg', require('./components/SvgIcon').default)
+
+Vue.config.productionTip = false
+Vue.use(ElementUI);
+/* eslint-disable no-new */
+new Vue({
+  el: '#app',
+  router,
+  components: { App },
+  template: '<App/>'
+})

+ 54 - 0
admin/src/router/index.js

@@ -0,0 +1,54 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+// import HelloWorld from '@/components/HelloWorld'
+import Layout from '@/layout'
+
+Vue.use(Router)
+
+export default new Router({
+    routes: [
+        {
+            path: '/',
+            name: 'Layout',
+            redirect: '/home',
+            component: Layout,
+            children: [
+                {
+                    path: 'home',
+                    name: 'Home',
+                    component: () => import('@/views/home')
+                },
+                {
+                    path: 'order',
+                    name: 'Order',
+                    component: () => import('@/views/order')
+                },
+                {
+                    path: 'staff',
+                    name: 'Staff',
+                    component: () => import('@/views/staff')
+                },
+                {
+                    path: 'account',
+                    name: 'Account',
+                    component: () => import('@/views/account')
+                },
+                {
+                    path: 'stat',
+                    name: 'Stat',
+                    component: () => import('@/views/stat')
+                },
+                {
+                    path: 'system',
+                    name: 'System',
+                    component: () => import('@/views/system')
+                },
+                {
+                    path: 'inform',
+                    name: 'Inform',
+                    component: () => import('@/views/inform')
+                }
+            ]
+        }
+    ]
+})

+ 13 - 0
admin/src/views/account/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div>Account</div>
+</template>
+
+<script>
+export default {
+    name: 'Account'
+
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 871 - 0
admin/src/views/home/index.vue

@@ -0,0 +1,871 @@
+<template>
+  <el-card class="box-card">
+    <div slot="header" class="clearfix">
+      <div class="header-left">
+        <span class="title">房态管理 </span>
+        <span> / 共182间</span>
+      </div>
+      <div class="header-right">
+        <el-input
+          placeholder="请输入姓名或者房间号"
+          prefix-icon="el-icon-search"
+          class="inquire-input"
+        >
+        </el-input>
+        <el-button class="inquire-button">查询</el-button>
+        <div class="control">
+          <div class="title" @click="water = !water" style="cursor: pointer">
+            批量控制水电
+          </div>
+          <ul v-show="water">
+            <li
+              @click="dialogShow"
+              v-for="(item, index) in titles"
+              :key="index"
+            >
+              {{ item }}
+            </li>
+            <el-dialog
+              :title="title"
+              :visible.sync="dialogVisible"
+              width="692px"
+              append-to-body
+            >
+              <el-tree
+                :data="data"
+                show-checkbox
+                node-key="id"
+                :default-expanded-keys="[2, 3]"
+                :default-checked-keys="[5]"
+                :props="defaultProps"
+              >
+              </el-tree>
+              <span slot="footer" class="dialog-footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="dialogVisible = false"
+                  >确 定</el-button
+                >
+              </span>
+            </el-dialog>
+          </ul>
+        </div>
+        <div class="control" style="margin-left: 17px">
+          <div class="title" @click="room = !room" style="cursor: pointer">
+            批量开关房
+          </div>
+          <ul v-show="room">
+            <li
+              @click="dialogShow"
+              v-for="(item, index) in titles2"
+              :key="index"
+            >
+              {{ item }}
+            </li>
+            <el-dialog
+              :title="title"
+              :visible.sync="dialogVisible"
+              width="692px"
+              append-to-body
+            >
+              <div class="tree">
+                <div class="tree-left">
+                  <el-checkbox v-model="checked" style="margin-left: 15px"
+                    >全选</el-checkbox
+                  >
+                  <span style="margin-left: 165px">100 / 200</span>
+                </div>
+                <el-tree
+                  :data="data"
+                  show-checkbox
+                  node-key="id"
+                  :default-expanded-keys="[2, 3]"
+                  :default-checked-keys="[5]"
+                  :props="defaultProps"
+                >
+                </el-tree>
+              </div>
+              <div class="tree">
+                <div class="tree-left">
+                  <p style="margin-left: 15px">已选 ( 100 )</p>
+                </div>
+                <ul
+                  class="infinite-list"
+                  v-infinite-scroll="load"
+                  style="overflow: auto"
+                  infinite-scroll-immediate="false"
+                >
+                  <li
+                    v-for="(i, index) in count"
+                    :key="index"
+                    class="infinite-list-item"
+                  >
+                    <span style="margin-left: 19px">{{ i }}</span
+                    ><i class="el-icon-close"></i>
+                  </li>
+                </ul>
+              </div>
+              <span slot="footer" class="dialog-footer">
+                <el-button @click="dialogVisible = false">取 消</el-button>
+                <el-button type="primary" @click="dialogVisible = false"
+                  >确 定</el-button
+                >
+              </span>
+            </el-dialog>
+          </ul>
+        </div>
+      </div>
+    </div>
+    <div class="home-body">
+      <div class="room-floor">
+        <div class="floor">
+          <div class="header-title">
+            <div class="tier">2层</div>
+            <span>/&nbsp; 共10间</span>
+          </div>
+          <div class="main-floor">
+            <div class="room-num" v-for="i in 8" :key="i">
+              <el-popover
+                width="70"
+                placement="right"
+                trigger="click"
+                popper-class="popperOptions"
+              >
+                <div class="control-room">
+                  <div>关灯</div>
+                  <div>关灯</div>
+                  <div>关灯</div>
+                  <div>关灯</div>
+                  <div>关灯</div>
+                  <div>关灯</div>
+                  <div>关灯</div>
+                </div>
+                <div class="card" slot="reference">
+                  <div class="title">
+                    <span>17栋1单元2层01房</span>
+                    <div class="state">空闲</div>
+                  </div>
+                  <div class="teacher">张老师</div>
+                  <div class="num">
+                    <span>水:25吨</span>&nbsp;&nbsp;
+                    <span>电:25度</span>
+                  </div>
+                  <div class="count-down">
+                    <span>退房倒计时:00:13:26</span>
+                    <div class="tuifang">
+                      <IconSvg :W="21" :H="21" name="tuifang" />
+                    </div>
+                  </div>
+                </div>
+              </el-popover>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="query-condition">
+        <div class="title">查询条件</div>
+        <div class="calendar">
+          <p>日期查询</p>
+          <div class="date">
+            <el-date-picker
+              size="mini"
+              v-model="date"
+              align="right"
+              type="date"
+              placeholder="请选择日期"
+              :picker-options="pickerOptions"
+              :clearable="false"
+            >
+            </el-date-picker>
+            <el-calendar v-model="date" :first-day-of-week="7"> </el-calendar>
+          </div>
+        </div>
+        <div class="filtrate">
+          <p>房态筛选</p>
+          <div class="room-filtrate">
+            <div style="border-bottom: 1px solid rgba(225, 228, 232, 1)">
+              <el-checkbox
+                size="mini"
+                :indeterminate="isIndeterminate"
+                v-model="checkAll"
+                @change="handleCheckAllChange"
+                >全选</el-checkbox
+              >
+              <span class="all">150间</span>
+            </div>
+            <div style="margin: 15px 0"></div>
+            <el-checkbox-group
+              size="mini"
+              v-model="checkedCities"
+              @change="handleCheckedCitiesChange"
+            >
+              <el-checkbox
+                size="mini"
+                v-for="city in cities"
+                :label="city"
+                :key="city"
+                >{{ city }} <span>50间</span></el-checkbox
+              >
+            </el-checkbox-group>
+          </div>
+        </div>
+        <div class="reset">
+          <el-button @click="reset" type="primary" size="mini"
+            >重置筛选</el-button
+          >
+        </div>
+      </div>
+    </div>
+  </el-card>
+</template>
+
+<script>
+export default {
+  name: "Home",
+  data() {
+    return {
+      // 批量数据
+      dialogVisible: false,
+      count: 20,
+      title: "",
+      checked: true,
+      titles: ["批量开电", "批量开水", "批量关电", "批量关水"],
+      titles2: ["批量开房", "批量关房"],
+      data: [
+        {
+          id: 1,
+          label: "墨轩湖酒店",
+          children: [
+            {
+              id: 2,
+              label: "17栋1单元02层01",
+              children: [
+                {
+                  id: 9,
+                  label: "三级 1-1-1",
+                },
+                {
+                  id: 10,
+                  label: "三级 1-1-2",
+                },
+              ],
+            },
+            {
+              id: 3,
+              label: "17栋1单元02层01",
+            },
+            {
+              id: 4,
+              label: "17栋1单元02层01",
+            },
+            {
+              id: 5,
+              label: "17栋1单元02层01",
+            },
+            {
+              id: 6,
+              label: "17栋1单元02层01",
+            },
+
+            {
+              id: 7,
+              label: "17栋1单元02层01",
+            },
+            {
+              id: 8,
+              label: "17栋1单元02层01",
+            },
+            {
+              id: 9,
+              label: "17栋1单元02层01",
+            },
+          ],
+        },
+      ],
+      defaultProps: {
+        children: "children",
+        label: "label",
+      },
+      water: false,
+      room: false,
+
+      //日历
+      pickerOptions: {
+        disabledDate(time) {
+          return time.getTime() > Date.now();
+        },
+        shortcuts: [
+          {
+            text: "今天",
+            onClick(picker) {
+              picker.$emit("pick", new Date());
+            },
+          },
+          {
+            text: "昨天",
+            onClick(picker) {
+              const date = new Date();
+              date.setTime(date.getTime() - 3600 * 1000 * 24);
+              picker.$emit("pick", date);
+            },
+          },
+          {
+            text: "一周前",
+            onClick(picker) {
+              const date = new Date();
+              date.setTime(date.getTime() - 3600 * 1000 * 24 * 7);
+              picker.$emit("pick", date);
+            },
+          },
+        ],
+      },
+      date: new Date(),
+
+      // 房态筛选数据
+      checkAll: false,
+      checkedCities: ["已入住", "空间"],
+      cities: ["已入住", "空间", "已约定", "脏房", "保留房"],
+      isIndeterminate: true,
+    };
+  },
+  mounted() {
+    document.getElementsByClassName(
+      "el-button--plain"
+    )[0].children[0].innerText = "<";
+    document.getElementsByClassName(
+      "el-button--plain"
+    )[1].children[0].innerText = "o";
+    document.getElementsByClassName(
+      "el-button--plain"
+    )[2].children[0].innerText = ">";
+  },
+  methods: {
+    handleCheckChange(data, checked, indeterminate) {
+      console.log(data, checked, indeterminate);
+    },
+    loadNode(node, resolve) {
+      if (node.level === 0) {
+        return resolve([{ name: "region1" }, { name: "region2" }]);
+      }
+      if (node.level > 3) return resolve([]);
+
+      var hasChild;
+      if (node.data.name === "region1") {
+        hasChild = true;
+      } else if (node.data.name === "region2") {
+        hasChild = false;
+      } else {
+        hasChild = Math.random() > 0.5;
+      }
+
+      setTimeout(() => {
+        var data;
+        if (hasChild) {
+          data = [
+            {
+              name: "zone" + this.count++,
+            },
+            {
+              name: "zone" + this.count++,
+            },
+          ];
+        } else {
+          data = [];
+        }
+
+        resolve(data);
+      }, 500);
+    },
+    dialogShow(e) {
+      this.dialogVisible = true;
+      this.title = e.target.innerHTML;
+    },
+    handleClose(key, keyPath) {
+      console.log(key, keyPath);
+    },
+    load() {
+      this.count += 0;
+      if (this.count < 50) {
+        this.count += 4;
+      }
+    },
+
+    // 房态筛查
+    handleCheckAllChange(val) {
+      this.checkedCities = val ? this.cities : [];
+      this.isIndeterminate = false;
+      console.log(val);
+    },
+    handleCheckedCitiesChange(value) {
+      let checkedCount = value.length;
+      this.checkAll = checkedCount === this.cities.length;
+      this.isIndeterminate =
+        checkedCount > 0 && checkedCount < this.cities.length;
+    },
+    reset() {
+      this.checkedCities = [];
+      this.isIndeterminate = false;
+      this.checkAll = false;
+    },
+
+    handleCommand(command) {
+      this.$message("click on item " + command);
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.box-card {
+  /deep/ .el-card__header {
+    padding: 0;
+  }
+}
+
+.clearfix {
+  height: 96px;
+  display: flex;
+  align-items: center;
+
+  .el-card__header {
+    padding: 0;
+  }
+
+  .header-left {
+    margin-left: 33px;
+    display: flex;
+    align-items: center;
+
+    .title {
+      color: rgba(0, 0, 0, 1);
+      font-size: 24px;
+      font-weight: 500;
+      margin-right: 15px;
+    }
+  }
+
+  .header-right {
+    display: flex;
+
+    .inquire-input {
+      width: 261px;
+      height: 37px;
+      background: rgba(240, 243, 247, 1);
+      border-radius: 5px;
+      margin-left: 715px;
+
+      /deep/ .el-input__inner {
+        height: 37px !important;
+      }
+    }
+
+    .inquire-button {
+      width: 73px;
+      height: 37px;
+      font-size: 16px;
+      margin: 0 58px 0 9px;
+      padding: 0;
+      background: rgba(41, 109, 227, 1);
+      color: #fff;
+    }
+
+    .control {
+      // width: 121px;
+      height: 37px;
+      position: relative;
+
+      .title {
+        background: rgba(41, 109, 227, 1);
+        color: #fff;
+        text-align: center;
+        line-height: 37px;
+        padding: 0 11px;
+      }
+
+      ul {
+        position: absolute;
+        list-style: none;
+        width: 100%;
+        padding: 0;
+        box-sizing: border-box;
+        margin: 0;
+
+        background: rgba(255, 255, 255, 1);
+        box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
+        border-radius: 3px;
+
+        li {
+          height: 26px;
+          font-size: 12px;
+          color: #000;
+          line-height: 26px;
+          text-align: center;
+          &:hover {
+            background-color: rgba(41, 109, 227, 1);
+            color: #fff;
+            cursor: pointer;
+          }
+        }
+      }
+    }
+  }
+}
+/deep/ .el-card__body {
+  height: 854px;
+  width: 100%;
+  padding: 0;
+  .home-body {
+    display: flex;
+    height: 100%;
+
+    .room-floor {
+      height: 2000px;
+      flex: 1;
+      .floor {
+        height: 394px;
+        margin: 0 0 0 33px;
+        .header-title {
+          display: flex;
+          align-items: center;
+          height: 66px;
+          .tier {
+            color: rgba(0, 0, 0, 1);
+            font-size: 20px;
+            font-weight: 500;
+            display: inline-block;
+          }
+          span {
+            color: rgba(102, 102, 102, 1);
+            font-size: 14px;
+            font-weight: 400;
+            margin-left: 10px;
+          }
+        }
+        .main-floor {
+          display: flex;
+          flex-wrap: wrap;
+          .room-num {
+            margin-bottom: 30px;
+
+            .card {
+              width: 228px;
+              height: 140px;
+              background: rgba(240, 243, 247, 1);
+              border-radius: 4px;
+              margin-right: 20px;
+              font-size: 12px;
+              font-weight: 400;
+              color: #000;
+              display: flex;
+              flex-direction: column;
+              // div {
+              //   margin-left: 15px;
+              //   margin-bottom: 7px;
+              // }
+              .title {
+                display: flex;
+                align-items: center;
+                margin: 15px;
+                span {
+                  display: inline-block;
+                  height: 24px;
+                  font-size: 16px;
+                  font-weight: 500;
+                  letter-spacing: 0px;
+                  color: rgba(51, 51, 51, 1);
+                }
+                .state {
+                  width: 38px;
+                  height: 17px;
+                  opacity: 1;
+                  /** 文本1 */
+                  font-size: 12px;
+                  font-weight: 400;
+                  letter-spacing: 0px;
+                  color: #fff;
+                  text-align: center;
+                  line-height: 17px;
+                  margin-left: 20px;
+                  background: rgba(41, 109, 227, 1);
+                }
+              }
+              .teacher {
+                margin: 5px 15px;
+              }
+              .num {
+                margin: 5px 15px;
+              }
+              .count-down {
+                margin-left: 15px;
+                display: flex;
+                span {
+                  margin-right: 60px;
+                }
+                .tuifang {
+                  width: 21px;
+                  height: 21px;
+                }
+              }
+            }
+            .el-dropdown-link {
+              cursor: pointer;
+              color: #409eff;
+            }
+            .el-icon-arrow-down {
+              font-size: 12px;
+            }
+          }
+        }
+      }
+    }
+    .query-condition {
+      width: 302px;
+      height: 752px;
+      background: rgba(240, 243, 247, 1);
+      border-radius: 4px;
+      margin: 72px 29px 0 0;
+      .title {
+        color: rgba(0, 0, 0, 1);
+        font-size: 20px;
+        font-weight: 400;
+        height: 55px;
+        border-bottom: 1px solid rgba(217, 217, 217, 1);
+        line-height: 55px;
+        text-align: center;
+      }
+      .calendar {
+        margin: 0 auto;
+        width: 266px;
+        height: 227px;
+        .date {
+          box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
+          .el-input {
+            width: 266px !important;
+            .el-input__inner {
+              padding-left: 15px;
+            }
+            .el-input__prefix {
+              left: 233px !important;
+            }
+          }
+        }
+        .el-calendar {
+          width: 266px;
+          height: 227px;
+
+          .el-calendar__header {
+            height: 37px;
+            padding: 0;
+            font-size: 10px;
+            padding: 0 20px;
+            align-items: center;
+            .el-button {
+              width: 30px;
+              border: none;
+              padding: 5px 10px;
+            }
+          }
+          .el-calendar__body {
+            height: 190px !important;
+            padding: 0 20px;
+            table {
+              width: 100%;
+              height: 100% !important;
+              thead {
+                height: 19px;
+                th {
+                  font-size: 10.7px;
+                  padding: 15px 0 10px 0;
+                }
+              }
+              .el-calendar-table__row {
+                .next,
+                .prev {
+                  .el-calendar-day {
+                    display: none;
+                  }
+                }
+                height: 19px !important;
+                .is-selected {
+                  background: transparent;
+                  .el-calendar-day {
+                    border-radius: 50%;
+                    background-color: rgba(51, 150, 251, 1);
+                    span {
+                      color: #fff;
+                    }
+                  }
+                }
+
+                td {
+                  height: 19px !important;
+                  border-left: none;
+                  border-top: none;
+                  border-bottom: none;
+                  border-right: none;
+
+                  .el-calendar-day {
+                    display: inline-block;
+                    padding: 0;
+                    height: 19px;
+                    border: none !important;
+                    margin: 4px 5px;
+                    span {
+                      display: inline-block;
+                      font-size: 10.7px;
+                      width: 19px;
+                      height: 19px;
+                      border-radius: 50%;
+                      color: #000;
+                      text-align: center;
+                      line-height: 19px;
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+      .filtrate {
+        margin-top: 85px;
+        padding: 0 20px;
+        p {
+          color: rgba(0, 0, 0, 1);
+          font-size: 16px;
+          font-weight: 400;
+          padding: 10px 0;
+        }
+        .room-filtrate {
+          border-bottom: 1px solid rgba(225, 228, 232, 1);
+        }
+        .all {
+          float: right;
+          font-size: 12px;
+        }
+        .el-checkbox {
+          width: 50%;
+          margin: 0;
+
+          .el-checkbox__label {
+            font-size: 12px;
+            margin-bottom: 10px;
+            span {
+              margin-left: 25px;
+              font-size: 12px;
+            }
+          }
+        }
+        .el-checkbox__inner {
+          width: 12px;
+          height: 12px;
+          &:after {
+            left: 3px;
+            top: 0;
+          }
+        }
+      }
+      .reset {
+        margin: 15px 20px;
+        .el-button {
+          width: 69px;
+          height: 28px;
+          background: rgba(41, 109, 227, 1);
+          border-radius: 4px;
+          padding: 0;
+        }
+      }
+    }
+  }
+}
+
+.box-card {
+  width: 1612px;
+  height: 950px;
+  box-shadow: 0px 3px 10px rgba(0, 97, 255, 0.2);
+  border-radius: 8px;
+}
+
+/deep/ .el-dialog {
+  height: 565px !important;
+
+  .el-dialog__header {
+    height: 81px;
+    margin: 0;
+    padding: 0;
+    border-bottom: 1px solid rgba(230, 230, 230, 1);
+    display: flex;
+    align-items: center;
+    color: rgba(0, 0, 0, 1);
+
+    span {
+      font-size: 20px;
+      font-weight: 500;
+      margin-left: 30px;
+    }
+
+    button {
+      font-size: 20px;
+      width: 35px;
+      height: 35px;
+      top: 23px !important;
+    }
+  }
+
+  .el-dialog__body {
+    display: flex;
+    justify-content: center;
+    padding: 21px 0 0 0;
+
+    .tree {
+      .tree-left {
+        width: 316px;
+        height: 44px;
+        background: rgba(240, 243, 247, 1);
+        border: 1px solid rgba(230, 230, 230, 1);
+        box-sizing: border-box;
+        display: flex;
+        align-items: center;
+      }
+    }
+
+    .el-tree {
+      width: 316px;
+      height: 324px;
+      border: 1px solid rgba(230, 230, 230, 1);
+      box-sizing: border-box;
+      padding-left: 9px;
+    }
+
+    ul {
+      width: 316px;
+      height: 324px;
+      list-style: none;
+      padding: 0;
+      margin: 0;
+      border: 1px solid rgba(230, 230, 230, 1);
+      box-sizing: border-box;
+      li {
+        display: flex;
+        justify-content: space-between;
+        padding-right: 17px;
+      }
+    }
+  }
+
+  .el-dialog__footer {
+    padding: 24px 30px 0 0;
+  }
+}
+
+.popperOptions {
+  min-width: 69.99px;
+  height: 167px;
+  .control-room {
+    width: 69.99px;
+    height: 167px;
+    opacity: 1;
+    background: rgba(240, 243, 247, 1);
+    box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.25);
+  }
+}
+</style>

+ 359 - 0
admin/src/views/inform/index.vue

@@ -0,0 +1,359 @@
+<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="请输入时间">
+            <i slot="prefix" class="el-input__icon el-icon-search"></i>
+          </el-input>
+          <el-button type="primary">查询</el-button>
+        </div>
+      </div>
+      <div class="inform-body">
+        <div class="state">
+          <div class="left">
+            <el-button
+              size="small"
+              @click="allRead($event, index)"
+              v-for="(item, index) in read"
+              :key="item"
+              :class="className[index]"
+              >{{ item }}</el-button
+            >
+          </div>
+          <div class="right">
+            <el-button type="primary" size="small">一键已读</el-button>
+          </div>
+        </div>
+        <div class="inform-table">
+          <el-table
+            :data="data"
+            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="date"
+              label="标题"
+              width="300"
+              align="center"
+            >
+              <template slot-scope="scope">
+                <span class="yuan"></span>
+                <span style="margin-left: 10px">{{ scope.row.date }}</span>
+              </template>
+            </el-table-column>
+
+            <el-table-column prop="name" align="center" label="消息">
+            </el-table-column>
+
+            <el-table-column
+              prop="address"
+              align="center"
+              label="时间"
+              width="400"
+            >
+            </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="tableData.length"
+          >
+          </el-pagination>
+        </div>
+      </div>
+    </el-card>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "Inform",
+  data() {
+    return {
+      read: ["全部", "未读", "已读"],
+      className: ["active", "", ""],
+      data: [],
+      tableData: [
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.07.03 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.07.02 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.07.01 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 09:50:50",
+        },
+        ,
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 10:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:55:50",
+        },
+        ,
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 18:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.29 08:50:50",
+        },
+
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "上海市普陀区金沙江路 1518 弄",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.28 18:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+        {
+          date: "续住通知",
+          name: "王小虎",
+          address: "2022.06.30 08:50:50",
+        },
+      ],
+    };
+  },
+  mounted() {
+    document.getElementsByClassName(
+      "el-pagination__jump"
+    )[0].childNodes[0].nodeValue = "跳转到";
+    this.handleCurrentChange(1);
+  },
+  methods: {
+    allRead($event, index) {
+      this.className = this.className.map((item) => (item = ""));
+      this.className[index] = "active";
+    },
+    rowbg(row) {
+      if (row.rowIndex % 2 != 0) {
+        return { background: "rgba(240, 243, 247, 1)", "border-radius": "5px" };
+      }
+    },
+
+    handleCurrentChange(val) {
+      this.data = this.tableData.slice((val - 1) * 9, val * 9);
+      // console.log(`当前页: ${val}`);
+    },
+  },
+};
+</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;
+  }
+}
+</style>>

+ 12 - 0
admin/src/views/order/index.vue

@@ -0,0 +1,12 @@
+<template>
+  <div>order</div>
+</template>
+
+<script>
+export default {
+  name: "Order",
+};
+</script>
+
+<style>
+</style>

+ 13 - 0
admin/src/views/staff/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div>System</div>
+</template>
+
+<script>
+export default {
+    name: 'System'
+
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 13 - 0
admin/src/views/stat/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div>home</div>
+</template>
+
+<script>
+export default {
+    name: 'Home'
+
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 13 - 0
admin/src/views/system/index.vue

@@ -0,0 +1,13 @@
+<template>
+    <div>home</div>
+</template>
+
+<script>
+export default {
+    name: 'Home'
+
+}
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 0 - 0
admin/static/.gitkeep


+ 27 - 0
admin/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
admin/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
admin/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
admin/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
admin/test/unit/.eslintrc

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

+ 30 - 0
admin/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
admin/test/unit/setup.js

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

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

@@ -0,0 +1,11 @@
+import Vue from 'vue'
+import HelloWorld from '@/components/HelloWorld'
+
+describe('HelloWorld.vue', () => {
+  it('should render correct contents', () => {
+    const Constructor = Vue.extend(HelloWorld)
+    const vm = new Constructor().$mount()
+    expect(vm.$el.querySelector('.hello h1').textContent)
+      .toEqual('Welcome to Your Vue.js App')
+  })
+})

+ 7 - 0
admin/vue.config.js

@@ -0,0 +1,7 @@
+css: {
+    loaderOptions: {
+      sass: {
+        prependData: `@import "@/assets/scss/variable.scss";` //引入全局变量   
+      }
+    }
+  }