Переглянути джерело

Signed-off-by: ljx <809268652@qq.com>

ljx 10 місяців тому
коміт
7b8ab6bc0e
88 змінених файлів з 6165 додано та 0 видалено
  1. 9 0
      .editorconfig
  2. 4 0
      .eslintignore
  3. 30 0
      .eslintrc.js
  4. 3 0
      .gitignore
  5. 10 0
      .postcssrc.js
  6. 123 0
      Jenkinsfile
  7. 14 0
      README.md
  8. 41 0
      build/build.js
  9. 54 0
      build/check-versions.js
  10. BIN
      build/logo.png
  11. 102 0
      build/utils.js
  12. 22 0
      build/vue-loader.conf.js
  13. 94 0
      build/webpack.base.conf.js
  14. 95 0
      build/webpack.dev.conf.js
  15. 145 0
      build/webpack.prod.conf.js
  16. 12 0
      config/dev.env.js
  17. 76 0
      config/index.js
  18. 13 0
      config/prod.env.js
  19. 108 0
      index.html
  20. 79 0
      package.json
  21. 38 0
      src/App.vue
  22. 79 0
      src/api/3dModelApi.js
  23. 24 0
      src/api/RbacApi.js
  24. 24 0
      src/api/bannerControlApi.js
  25. 51 0
      src/api/bannerDetailApi.js
  26. 79 0
      src/api/courseIntroduceApi.js
  27. 13 0
      src/api/crawlerApi.js
  28. 13 0
      src/api/logoutApi.js
  29. 18 0
      src/api/materialApi.js
  30. 84 0
      src/api/newsApi.js
  31. 79 0
      src/api/vrPanoramaApi.js
  32. BIN
      src/assets/3Dmoxing.png
  33. BIN
      src/assets/3Dmoxing1.png
  34. BIN
      src/assets/403bg.png
  35. BIN
      src/assets/BANNERguanli.png
  36. BIN
      src/assets/BANNERguanli1.png
  37. BIN
      src/assets/VRquanjingtu.png
  38. BIN
      src/assets/VRquanjingtu1.png
  39. BIN
      src/assets/hangye.png
  40. BIN
      src/assets/hangye2.png
  41. BIN
      src/assets/head-logo.png
  42. BIN
      src/assets/kechengjieshao.png
  43. BIN
      src/assets/kechengjieshao1.png
  44. BIN
      src/assets/logo.png
  45. BIN
      src/assets/news.png
  46. BIN
      src/assets/news2.png
  47. BIN
      src/assets/sucaiguanli.png
  48. BIN
      src/assets/sucaiguanli1.png
  49. BIN
      src/assets/tianjia.png
  50. BIN
      src/assets/xinwenzixun.png
  51. BIN
      src/assets/xinwenzixun1.png
  52. 70 0
      src/components/layout/aside-menu.vue
  53. 10 0
      src/components/layout/index.js
  54. 49 0
      src/components/layout/search-white-board.vue
  55. 15 0
      src/components/layout/white-board.vue
  56. 2 0
      src/constants/constants.js
  57. 24 0
      src/filters/bytes.js
  58. 27 0
      src/filters/date-format.js
  59. 9 0
      src/filters/index.js
  60. 67 0
      src/filters/str-format.js
  61. 9 0
      src/linghang-menhu-front-end.iml
  62. 44 0
      src/main.js
  63. 5 0
      src/plugins/element-ui.js
  64. 3 0
      src/plugins/index.js
  65. 4 0
      src/plugins/vue-clipboard2.js
  66. 9 0
      src/plugins/vue-quill-editor.js
  67. 144 0
      src/router/index.js
  68. 319 0
      src/utils/date-kit.js
  69. 120 0
      src/utils/http-kit.js
  70. 20 0
      src/utils/str-kit.js
  71. 268 0
      src/views/3Dmodel/edit.vue
  72. 437 0
      src/views/3Dmodel/list.vue
  73. 124 0
      src/views/RBAC/list.vue
  74. 141 0
      src/views/banner/edit.vue
  75. 280 0
      src/views/banner/list.vue
  76. 184 0
      src/views/courseIntroduce/edit.vue
  77. 346 0
      src/views/courseIntroduce/list.vue
  78. 85 0
      src/views/exception/403.vue
  79. 20 0
      src/views/login.vue
  80. 20 0
      src/views/logout.vue
  81. 134 0
      src/views/main.vue
  82. 63 0
      src/views/material/dialog.vue
  83. 283 0
      src/views/material/list.vue
  84. 243 0
      src/views/news/edit.vue
  85. 366 0
      src/views/news/list.vue
  86. 249 0
      src/views/vr-panorama/edit.vue
  87. 436 0
      src/views/vr-panorama/list.vue
  88. 0 0
      static/.gitkeep

+ 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

+ 4 - 0
.eslintignore

@@ -0,0 +1,4 @@
+/build/
+/config/
+/dist/
+/*.js

+ 30 - 0
.eslintrc.js

@@ -0,0 +1,30 @@
+// 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: {
+        'indent': ['off', 2],
+        // allow async-await
+        'generator-star-spacing': 'off',
+        // allow debugger during development
+        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+    }
+}

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.idea
+node_modules
+package-lock.json

+ 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': {browsers: 'last 5 version'}
+  }
+}

+ 123 - 0
Jenkinsfile

@@ -0,0 +1,123 @@
+pipeline {
+  agent {
+    node {
+      label '112.74.105.17'
+    }
+  }
+//   tools { node '' }
+//   parameters {}
+  environment {
+    _productFileName = buildProductFileName() // 产物文件名
+    _remote = "root@47.97.230.53"
+    _productBackupPath = '/app/csair/backup' // 产物备份目录
+  }
+  triggers {
+//     cron('0 0 * * *') // 周期任务
+//     pollSCM('H/1 * * * *') // 轮询代码仓库(每分钟判断一次代码是否有变化)
+    gitlab(
+      triggerOnPush: true,
+      triggerOnMergeRequest: true,
+      triggerOnNoteRequest: true,
+      branchFilterType: 'All',
+      secretToken: 'asdfghjkl'
+    )
+  }
+  options {
+    buildDiscarder(logRotator(numToKeepStr: '10')) // 保存最近历史构建记录的数量
+    disableConcurrentBuilds() // 同一个pipeline,Jenkins默认是可以同时执行多次的,此选项为了禁止pipeline同时执行
+    // checkoutToSubdirectory('sub') // Jenkins默认拉取源码至工作空间的根目录中,此选项可以指定检出到工作空间的子目录中
+    retry(1) // 当发生失败时进行重试(包括第1次失败)
+    timestamps() // 添加日志打印时间
+    timeout(time: 15, unit: 'MINUTES') // 如果pipeline执行时间过长,超出了设置的timeout时间,Jenkins将中止pipeline(SECONDS、MINUTES、HOURS)
+    gitLabConnection('gitlab') // 连接gitlab服务(需要在Jenkins中设置Jenkins -> Configure System)
+  }
+  post {
+    always { // 不论当前完成状态是什么,都执行
+      cleanWs() // 清理工作空间插件[Workspace Cleanup Plugin](https://plugins.jenkins.io/ws-cleanup)
+    }
+    failure {
+      updateGitlabCommitStatus name: 'build', state: 'failed'
+    }
+    success {
+      updateGitlabCommitStatus name: 'build', state: 'success'
+    }
+  }
+  stages {
+    stage('Env & Param') {
+      parallel {
+        stage('Env') {
+          steps {
+            sh 'printenv'
+            echo "系统当前用户    [${env.USER}]"
+            echo "WORKSPACE     [${env.WORKSPACE}]"
+            echo "JENKINS_URL   [${env.JENKINS_URL}]"
+            echo "${_productFileName}"
+          }
+        }
+        stage('Job') {
+          steps {
+            echo "Running [${env.BUILD_NUMBER}] on [${env.BUILD_URL}]"
+            echo "BRANCH_NAME [${env.BRANCH_NAME}] GIT_BRANCH [${env.GIT_BRANCH}]"
+          }
+        }
+      }
+    }
+    stage('NPM Install') {
+      steps {
+        sh 'npm install'
+      }
+    }
+    stage('Static check') {
+      parallel {
+        stage('eslint') {
+          steps {
+            echo 'eslint'
+          }
+        }
+      }
+    }
+    stage('NPM build') {
+      steps {
+        sh 'npm run build'
+      }
+    }
+    stage('Product') {
+      steps {
+        dir("${env.WORKSPACE}/dist") {
+          sh "tar -zcvf ${env.WORKSPACE}/${_productFileName} ./"
+        }
+
+        archiveArtifacts(artifacts: '*.tar.gz', caseSensitive: true, fingerprint: true)
+      }
+    }
+    stage('Release') {
+      parallel {
+        stage('Master') {
+          when {
+            branch 'master'
+          }
+          steps {
+            script {
+              sh "scp -r ${WORKSPACE}/${_productFileName} ${_remote}:${_productBackupPath}"
+              sh """
+                ssh ${_remote} "
+                  source /etc/profile
+                  rm -rf /app/csair/admin/*
+                  tar -zxvf ${_productBackupPath}/${_productFileName} -C /app/csair/admin
+                "
+              """
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// 生成产物的文件名
+def buildProductFileName() {
+  String projectName = "${env.JOB_NAME}".tokenize('//')[0]
+  String branchName = "${env.JOB_NAME}".tokenize('//')[1]
+  String date = new Date().format('yyyyMMddHHmmss')
+  return "${projectName}-${branchName}-${date}.tar.gz"
+}

+ 14 - 0
README.md

@@ -0,0 +1,14 @@
+# csair-vr-portal-ui-admin
+南方航空虚拟现实航空实训平台主页管理后台
+
+## Build Setup
+```bash
+# Install dependencies
+npm install
+
+# Serve with hot reload at localhost:9528
+npm run dev
+
+# Build for production with minification
+npm run build
+```

+ 41 - 0
build/build.js

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

+ 54 - 0
build/check-versions.js

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

BIN
build/logo.png


+ 102 - 0
build/utils.js

@@ -0,0 +1,102 @@
+'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',
+        publicPath: '../../'
+      })
+    } 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'
+  }
+}

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

@@ -0,0 +1,94 @@
+'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 ? [createLintingRule()] : []),
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: vueLoaderConfig
+      },
+      {
+        test: /\.js$/,
+        loader: 'babel-loader',
+        options:{
+          plugins:['syntax-dynamic-import']
+        }
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        loader: 'url-loader',
+        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
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)
+    }
+  })
+})

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

@@ -0,0 +1,145 @@
+'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 = 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: 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

+ 12 - 0
config/dev.env.js

@@ -0,0 +1,12 @@
+'use strict'
+const merge = require('webpack-merge')
+const prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+  NODE_ENV: '"development"',
+  PORTAL_URL: '"http://localhost:9191/linghang/portal"',
+  ADMIN_URL: '"http://localhost:9191/linghang/admin"',
+  HOME_URL: '"http://localhost:9191/linghang"',
+  OSS_URL: '"http://localhost:9193/oss"',
+  YUN_URL: '"https://xr.csair.com/720yun"'
+})

+ 76 - 0
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: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
+    autoOpenBrowser: false,
+    errorOverlay: true,
+    notifyOnErrors: true,
+    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
+
+    // Use Eslint Loader?
+    // If true, your code will be linted during bundling and
+    // linting errors and warnings will be shown in the console.
+    useEslint: true,
+    // If true, eslint errors and warnings will also be shown in the error overlay
+    // in the browser.
+    showEslintErrorsInOverlay: false,
+
+    /**
+     * Source Maps
+     */
+
+    // https://webpack.js.org/configuration/devtool/#development
+    devtool: 'cheap-module-eval-source-map',
+
+    // If you have problems debugging vue-files in devtools,
+    // set this to false - it *may* help
+    // https://vue-loader.vuejs.org/en/options.html#cachebusting
+    cacheBusting: true,
+
+    cssSourceMap: true
+  },
+
+  build: {
+    // Template for index.html
+    index: path.resolve(__dirname, '../dist/index.html'),
+
+    // Paths
+    assetsRoot: path.resolve(__dirname, '../dist'),
+    assetsSubDirectory: 'static',
+    assetsPublicPath: './',
+
+    /**
+     * 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
+  }
+}

+ 13 - 0
config/prod.env.js

@@ -0,0 +1,13 @@
+'use strict'
+module.exports = {
+  NODE_ENV: '"production"',
+  PORTAL_URL: '"https://430jy.uutime.cn/csair/portal"',
+  ADMIN_URL: '"https://430jy.uutime.cn/csair/admin"',
+  HOME_URL: '"https://430jy.uutime.cn/linghang"',
+  OSS_URL: '"https://430jy.uutime.cn/oss"',
+//  PORTAL_URL: '"/back-end-ui"',
+//  ADMIN_URL: '"/admin"',
+//  HOME_URL: '"https://10.79.11.52"',
+//  OSS_URL: '"http://localhost:9193/oss"',
+  YUN_URL: '"https://xr.csair.com/720yun"'
+}

+ 108 - 0
index.html

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width,initial-scale=1.0">
+  <title>南航门户后台管理系统</title>
+
+  <style>
+    html, body, h1, h2, h3, h4, h5, h6, div, dl, dt, dd, ul, ol, li, p, blockquote, pre, hr, figure, table, caption, th, td, form, fieldset, legend, input, button, textarea, menu {
+      margin: 0;
+      padding: 0;
+    }
+
+    header, footer, section, article, aside, nav, hgroup, address, figure, figcaption, menu, details {
+      display: block;
+    }
+
+    table {
+      border-collapse: collapse;
+      border-spacing: 0;
+    }
+
+    caption, th {
+      text-align: left;
+      font-weight: normal;
+    }
+
+    html, body, fieldset, img, iframe, abbr {
+      border: 0;
+    }
+
+    i, cite, em, var, address, dfn {
+      font-style: normal;
+    }
+
+    [hidefocus], summary {
+      outline: 0;
+    }
+
+    li {
+      list-style: none;
+    }
+
+    h1, h2, h3, h4, h5, h6, small {
+      font-size: 100%;
+    }
+
+    sup, sub {
+      font-size: 83%;
+    }
+
+    pre, code, kbd, samp {
+      font-family: inherit;
+    }
+
+    q:before, q:after {
+      content: none;
+    }
+
+    textarea {
+      overflow: auto;
+      resize: none;
+    }
+
+    label, summary {
+      cursor: default;
+    }
+
+    a, button {
+      cursor: pointer;
+    }
+
+    h1, h2, h3, h4, h5, h6, em, strong, b {
+      font-weight: normal;
+    }
+
+    del, ins, u, s, a, a:hover {
+      text-decoration: none;
+    }
+
+    body, textarea, input, button, select, keygen, legend {
+      font: 14px/1em 'microsoft yahei', arial;
+      color: #333;
+      outline: 0;
+    }
+
+    body {
+      background: #fff;
+    }
+
+    a {
+      line-height: 24px;
+    }
+
+    a {
+      color: #ffffff;
+    }
+
+    a:hover {
+    }
+  </style>
+</head>
+
+<body style="overflow-y: hidden;">
+<div id="app"></div>
+</body>
+
+</html>

+ 79 - 0
package.json

@@ -0,0 +1,79 @@
+{
+  "name": "vue",
+  "version": "1.0.0",
+  "description": "A Vue.js project",
+  "author": "",
+  "private": true,
+  "scripts": {
+    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
+    "start": "npm run dev",
+    "lint": "eslint --ext .js,.vue src",
+    "build": "node build/build.js"
+  },
+  "dependencies": {
+    "axios": "^0.19.0",
+    "element-ui": "^2.10.1",
+    "nprogress": "^0.2.0",
+    "vue": "^2.5.2",
+    "vue-clipboard2": "^0.3.1",
+    "vue-quill-editor": "^3.0.6",
+    "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-loader": "^7.1.1",
+    "babel-plugin-syntax-dynamic-import": "^6.18.0",
+    "babel-plugin-syntax-jsx": "^6.18.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",
+    "chalk": "^2.0.1",
+    "copy-webpack-plugin": "^4.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",
+    "node-notifier": "^5.1.2",
+    "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",
+    "semver": "^5.3.0",
+    "shelljs": "^0.7.6",
+    "uglifyjs-webpack-plugin": "^1.1.1",
+    "url-loader": "^0.5.8",
+    "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"
+  ]
+}

+ 38 - 0
src/App.vue

@@ -0,0 +1,38 @@
+<template>
+  <div id="app">
+    <router-view/>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'App',
+    components: {},
+    data () {
+      return {}
+    },
+    mounted () {},
+    methods: {}
+  }
+</script>
+
+<style>
+  #app {
+    font-family: 'Avenir', Helvetica, Arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+    text-align: center;
+    color: #2c3e50;
+  }
+
+  .search-label {
+    color: #606266;
+    line-height: 28px;
+    padding-right: 10px;
+  }
+
+  .el-form .el-form-item .el-form-item__label{
+    font-size: 17px;
+    font-weight: bold;
+  }
+</style>

+ 79 - 0
src/api/3dModelApi.js

@@ -0,0 +1,79 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/model?crux=${form.searchKey}&page=${form.page}&pageSize=${form.size}`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:上移
+   * @param form
+   */
+  forward (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}/forward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:下移
+   * @param form
+   */
+  backward (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}/backward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:发布
+   * @param form
+   */
+  checkboxrelease (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}/publish`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:取消发布
+   * @param form
+   */
+  checkboxnorelease (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}/un-publish`).then(
+      res => res.data
+    )
+  },
+  changePushIndex (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}/push-index`).then(
+      res => res.data
+    )
+  },
+  changeSecondTop (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}/second-top`).then(
+      res => res.data
+    )
+  },
+  findById (modelId) {
+    return HttpKit.get(`/admin/model/${modelId}`).then(
+      res => res.data
+    )
+  },
+  save (form) {
+    return HttpKit.post(`/admin/model/theModelInsert`, form).then(
+      res => res.data
+    )
+  },
+  delete (idList) {
+    return HttpKit.post(`/admin/model/delete`, idList).then(
+      res => res.data
+    )
+  },
+  changeSeq (id, seqencing) {
+    return HttpKit.get(`/admin/model/${id}/changeSeqencing?seqencing=${seqencing}`).then(
+      res => res.data
+    )
+  }
+}

+ 24 - 0
src/api/RbacApi.js

@@ -0,0 +1,24 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/RBAC?page=${form.page}&pageSize=${form.size}&crux=${form.searchKey}`).then(
+      res => res.data
+    )
+  },
+  ChangeRBAC (id, type) {
+    return HttpKit.get(`/admin/RBAC/${id}/ChangeRBAC?type=${type}`).then(
+      res => res.data
+    )
+  },
+
+  ChangeActivation (id) {
+    return HttpKit.get(`/admin/RBAC/${id}/ChangeActivation`).then(
+      res => res.data
+    )
+  }
+}

+ 24 - 0
src/api/bannerControlApi.js

@@ -0,0 +1,24 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  findAll () {
+    return HttpKit.post(`/admin/bannerControl/searchBannerControl`).then(
+      res => res.data
+    )
+  },
+  ChangeSpace (value) {
+    return HttpKit.get(`/admin/bannerControl/ChangeSpace?value=${value}`).then(
+      res => res.data
+    )
+  },
+
+  moveDirection () {
+    return HttpKit.post(`/admin/bannerControl/move`).then(
+      res => res.data
+    )
+  }
+}

+ 51 - 0
src/api/bannerDetailApi.js

@@ -0,0 +1,51 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/banner?page=${form.page}&pageSize=${form.size}&crux=${form.searchKey}`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:上移
+   * @param form
+   */
+  forward (bannerDetailsId) {
+    return HttpKit.get(`/admin/banner/${bannerDetailsId}/forward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:下移
+   * @param form
+   */
+  backward (bannerDetailsId) {
+    return HttpKit.get(`/admin/banner/${bannerDetailsId}/backward`).then(
+      res => res.data
+    )
+  },
+  changeBannerOn (bannerDetailsId) {
+    return HttpKit.get(`/admin/banner/${bannerDetailsId}/banner-on`).then(
+      res => res.data
+    )
+  },
+  findById (bannerId) {
+    return HttpKit.get(`/admin/banner/${bannerId}/findbyid`).then(
+      res => res.data
+    )
+  },
+  save (form) {
+    return HttpKit.post(`/admin/banner/bannerInsert`, form).then(
+      res => res.data
+    )
+  },
+  delete (idList) {
+    return HttpKit.post(`/admin/banner/delete`, idList).then(
+      res => res.data
+    )
+  }
+}

+ 79 - 0
src/api/courseIntroduceApi.js

@@ -0,0 +1,79 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/course?page=${form.page}&pageSize=${form.size}&crux=${form.searchKey}`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:上移
+   * @param form
+   */
+  forward (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}/forward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:下移
+   * @param form
+   */
+  backward (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}/backward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:发布
+   * @param form
+   */
+  checkboxrelease (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}/publish`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:取消发布
+   * @param form
+   */
+  checkboxnorelease (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}/un-publish`).then(
+      res => res.data
+    )
+  },
+  changePushIndex (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}/push-index`).then(
+      res => res.data
+    )
+  },
+  changeSecondTop (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}/second-top`).then(
+      res => res.data
+    )
+  },
+  findById (courseId) {
+    return HttpKit.get(`/admin/course/${courseId}`).then(
+      res => res.data
+    )
+  },
+  save (form) {
+    return HttpKit.post(`/admin/course/CourseIntroduceInsert`, form).then(
+      res => res.data
+    )
+  },
+  delete (idList) {
+    return HttpKit.post(`/admin/course/delete`, idList).then(
+      res => res.data
+    )
+  },
+  changeSeq (id, seqencing) {
+    return HttpKit.get(`/admin/course/${id}/changeSeqencing?seqencing=${seqencing}`).then(
+      res => res.data
+    )
+  }
+}

+ 13 - 0
src/api/crawlerApi.js

@@ -0,0 +1,13 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  crawler (url) {
+    return HttpKit.post(`/admin/crawler/grab?url=${url}`).then(
+      res => res.data
+    )
+  }
+}

+ 13 - 0
src/api/logoutApi.js

@@ -0,0 +1,13 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  logout () {
+    return HttpKit.get(`/apiv1/web/user/logout`).then(
+      res => res.data
+    )
+  }
+}

+ 18 - 0
src/api/materialApi.js

@@ -0,0 +1,18 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/image?page=${form.page}&pageSize=${form.size}&sourceType=${form.sourceType}&crux=${form.searchKey}`).then(
+      res => res.data
+    )
+  },
+  delete (idList) {
+    return HttpKit.post(`/admin/image/delete`, idList).then(
+      res => res.data
+    )
+  }
+}

+ 84 - 0
src/api/newsApi.js

@@ -0,0 +1,84 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/news-paper?page=${form.page}&pageSize=${form.size}&crux=${form.searchKey}`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:上移
+   * @param form
+   */
+  forward (newsId) {
+    return HttpKit.get(`/admin/news-paper/${newsId}/forward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:下移
+   * @param form
+   */
+  backward (newsId) {
+    return HttpKit.get(`/admin/news-paper/${newsId}/backward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:发布
+   * @param form
+   */
+  checkboxrelease (newsId) {
+    return HttpKit.get(`/admin/news-paper/${newsId}/publish`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:取消发布
+   * @param form
+   */
+  checkboxnorelease (newsId) {
+    return HttpKit.get(`/admin/news-paper/${newsId}/un-publish`).then(
+      res => res.data
+    )
+  },
+  changePushIndex (newsPaperId) {
+    return HttpKit.get(`/admin/news-paper/${newsPaperId}/push-index`).then(
+      res => res.data
+    )
+  },
+  changeSecondTop (newsPaperId) {
+    return HttpKit.get(`/admin/news-paper/${newsPaperId}/second-top`).then(
+      res => res.data
+    )
+  },
+  changeClasstype (newsPaperId) {
+    return HttpKit.get(`/admin/news-paper/${newsPaperId}/classtype`).then(
+      res => res.data
+    )
+  },
+  findById (newsPaperId) {
+    return HttpKit.get(`/admin/news-paper/${newsPaperId}`).then(
+      res => res.data
+    )
+  },
+  save (form) {
+    return HttpKit.post(`/admin/news-paper/NewsPaperInsert`, form).then(
+      res => res.data
+    )
+  },
+  delete (idList) {
+    return HttpKit.post(`/admin/news-paper/delete`, idList).then(
+      res => res.data
+    )
+  },
+  changeSeq (id, seqencing) {
+    return HttpKit.get(`/admin/news-paper/${id}/changeSeqencing?seqencing=${seqencing}`).then(
+      res => res.data
+    )
+  }
+}

+ 79 - 0
src/api/vrPanoramaApi.js

@@ -0,0 +1,79 @@
+import HttpKit from '@/utils/http-kit'
+
+export default {
+  /**
+   * 功能描述:关键字查询所有[课程介绍]
+   * @param name
+   */
+  page (form) {
+    return HttpKit.post(`/admin/vrPanoram?page=${form.page}&pageSize=${form.size}&crux=${form.searchKey}`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:上移
+   * @param form
+   */
+  forward (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}/forward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:下移
+   * @param form
+   */
+  backward (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}/backward`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:发布
+   * @param form
+   */
+  checkboxrelease (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}/publish`).then(
+      res => res.data
+    )
+  },
+  /**
+   * 功能描述:取消发布
+   * @param form
+   */
+  checkboxnorelease (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}/un-publish`).then(
+      res => res.data
+    )
+  },
+  changePushIndex (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}/push-index`).then(
+      res => res.data
+    )
+  },
+  changeSecondTop (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}/second-top`).then(
+      res => res.data
+    )
+  },
+  findById (vrPanoramId) {
+    return HttpKit.get(`/admin/vrPanoram/${vrPanoramId}`).then(
+      res => res.data
+    )
+  },
+  save (form) {
+    return HttpKit.post(`/admin/vrPanoram/VrDetailsInsert`, form).then(
+      res => res.data
+    )
+  },
+  delete (idList) {
+    return HttpKit.post(`/admin/vrPanoram/delete`, idList).then(
+      res => res.data
+    )
+  },
+  changeSeq (id, seqencing) {
+    return HttpKit.get(`/admin/vrPanoram/${id}/changeSeqencing?seqencing=${seqencing}`).then(
+      res => res.data
+    )
+  }
+}

BIN
src/assets/3Dmoxing.png


BIN
src/assets/3Dmoxing1.png


BIN
src/assets/403bg.png


BIN
src/assets/BANNERguanli.png


BIN
src/assets/BANNERguanli1.png


BIN
src/assets/VRquanjingtu.png


BIN
src/assets/VRquanjingtu1.png


BIN
src/assets/hangye.png


BIN
src/assets/hangye2.png


BIN
src/assets/head-logo.png


BIN
src/assets/kechengjieshao.png


BIN
src/assets/kechengjieshao1.png


BIN
src/assets/logo.png


BIN
src/assets/news.png


BIN
src/assets/news2.png


BIN
src/assets/sucaiguanli.png


BIN
src/assets/sucaiguanli1.png


BIN
src/assets/tianjia.png


BIN
src/assets/xinwenzixun.png


BIN
src/assets/xinwenzixun1.png


Різницю між файлами не показано, бо вона завелика
+ 70 - 0
src/components/layout/aside-menu.vue


+ 10 - 0
src/components/layout/index.js

@@ -0,0 +1,10 @@
+import WhiteBoard from '@/components/layout/white-board'
+import SearchWhiteBoard from '@/components/layout/search-white-board'
+
+/* 全局安装通用组件 */
+export default {
+  install (Vue) {
+    Vue.component('white-board', WhiteBoard)
+    Vue.component('search-white-board', SearchWhiteBoard)
+  }
+}

+ 49 - 0
src/components/layout/search-white-board.vue

@@ -0,0 +1,49 @@
+<template>
+  <el-row style="background-color: #fff; padding: 10px 10px; margin: 0px 0px 10px 0px; box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);">
+    <el-col :span="20">
+      <el-row>
+        <slot name="base"></slot>
+      </el-row>
+
+      <el-row>
+        <el-collapse-transition>
+          <div v-show="isCollapse">
+            <slot name="more"></slot>
+          </div>
+        </el-collapse-transition>
+      </el-row>
+    </el-col>
+
+    <el-col :span="2" v-if="showMore && isCollapse" style="line-height: 40px;"><el-button @click="changeCollapse" icon="el-icon-arrow-up">收起</el-button></el-col>
+    <el-col :span="2" v-if="showMore && !isCollapse" style="line-height: 40px;"><el-button @click="changeCollapse" icon="el-icon-arrow-down">更多</el-button></el-col>
+    <el-col :span="2" style="line-height: 40px; float: right;"><el-button @click="search" icon="el-icon-search">搜索</el-button></el-col>
+  </el-row>
+</template>
+
+<script>
+  export default {
+    name: 'LayoutSearchWhiteBoard',
+    props: {
+      showMore: {
+        type: Boolean,
+        default: true
+      }
+    },
+    data () {
+      return {
+        isCollapse: false // 是否展开更多
+      }
+    },
+    methods: {
+      changeCollapse () {
+        this.isCollapse = !this.isCollapse
+      },
+      search () {
+        this.$emit('search')
+      }
+    }
+  }
+</script>
+
+<style scoped>
+</style>

+ 15 - 0
src/components/layout/white-board.vue

@@ -0,0 +1,15 @@
+<template>
+  <el-row style="background-color: #fff; padding: 10px 10px; margin-bottom: 10px; box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);">
+    <slot></slot>
+  </el-row>
+</template>
+
+<script>
+  export default {
+    name: 'LayoutWhiteBoard'
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 2 - 0
src/constants/constants.js

@@ -0,0 +1,2 @@
+export default {
+}

+ 24 - 0
src/filters/bytes.js

@@ -0,0 +1,24 @@
+const bytesFilter = {
+  // 日期格式化过滤器
+  format (bytes, precision) {
+    if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) {
+      return '-'
+    }
+
+    if (typeof precision === 'undefined') {
+      precision = 1
+    }
+
+    let units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
+    let number = Math.floor(Math.log(bytes) / Math.log(1024))
+
+    return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]
+  },
+
+  // 全局安装器
+  install (Vue) {
+    Vue.filter('bytes', this.format)
+  }
+}
+
+export default bytesFilter

+ 27 - 0
src/filters/date-format.js

@@ -0,0 +1,27 @@
+import DateKit from '@/utils/date-kit'
+
+const dateFilter = {
+  /**
+   * 功能描述:格式化时间字段
+   * 使用方式:
+   * {{item.date | fmtDate}}
+   * {{row.createAt | fmtDate('yyyy-MM-dd')}}
+   * @param value
+   * @param format
+   * @returns {*}
+   */
+  formatDate (value, format = 'yyyy-MM-dd HH:mm:ss') {
+    if (!value) {
+      return ''
+    }
+
+    return DateKit.format(new Date(Date.parse(value.replace(/-/g, '/'))), format)
+  },
+
+  // 全局安装器
+  install (Vue) {
+    Vue.filter('fmtDate', this.formatDate)
+  }
+}
+
+export default dateFilter

+ 9 - 0
src/filters/index.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import DateFormat from './date-format'
+import StrFormat from './str-format'
+import BytesFilter from './bytes'
+
+// 全局注册过滤器
+Vue.use(DateFormat)
+Vue.use(StrFormat)
+Vue.use(BytesFilter)

+ 67 - 0
src/filters/str-format.js

@@ -0,0 +1,67 @@
+import StrKit from '@/utils/str-kit'
+
+const strFilter = {
+  /**
+   * 功能描述:剪切字符串过滤器
+   * 使用方式:
+   * {{item.deviceName | cutStr(14)}}
+   * @param value
+   * @param len
+   * @param placeholder
+   * @returns {*}
+   */
+  cutStr (value, len, placeholder) {
+    if (!value) {
+      return ''
+    }
+    len = len || 18
+    placeholder = placeholder || '...'
+
+    let count = 0
+    let titleLenOld = StrKit.getByteLen(value)
+    let titleLenNew = 0 // 初始化重新构造的title的长度
+
+    for (var i = 0; i < value.length; i++) {
+      if (titleLenNew < len) {
+        ++count
+        if (value[i].match(/[^x00-xff]/ig) !== null) { // 全角
+          titleLenNew += 2
+        } else {
+          titleLenNew += 1
+        }
+      } else {
+        break
+      }
+    }
+    value = value.substring(0, count) + (titleLenOld <= len ? '' : placeholder)
+    return value
+  },
+
+  /**
+   * 功能描述:格式化手机号码
+   * 使用方式:
+   * {{item.deviceName | fmtPhoneNumber}}
+   * @param val
+   * @returns {string|string | *}
+   */
+  fmtPhoneNumber (val) {
+    val = val.replace(/[^\d]/g, '').substr(0, 11)
+    if (val.length <= 3) {
+      return val
+    } else if (val.length <= 7) {
+      val = val.replace(/(\d{3})(\d{0,4})/, '$1-$2')
+    } else {
+      val = val.replace(/(\d{3})(\d{0,4})(\d{0,4})/, '$1-$2-$3')
+    }
+
+    return val
+  },
+
+  // 全局安装器
+  install (Vue) {
+    Vue.filter('cutStr', this.cutStr)
+    Vue.filter('fmtPhoneNumber', this.fmtPhoneNumber)
+  }
+}
+
+export default strFilter

+ 9 - 0
src/linghang-menhu-front-end.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 44 - 0
src/main.js

@@ -0,0 +1,44 @@
+// 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 Layout from '@/components/layout'
+
+import Constants from '@/constants/constants'
+/* 全局安装[过滤器] */
+import './filters'
+
+import './plugins'
+
+/* 进度条(网络请求、页面路由切换) */
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+NProgress.configure({
+  easing: 'ease', // 动画方式
+  speed: 500, // 递增进度条的速度
+  showSpinner: false, // 是否显示加载ico
+  trickleSpeed: 200, // 自动递增间隔
+  minimum: 0.3 // 初始化时的最小百分比
+})
+
+Vue.config.productionTip = false
+Vue.prototype.constants = Constants
+Vue.prototype.PORTAL_URL = process.env.PORTAL_URL
+Vue.prototype.ADMIN_URL = process.env.ADMIN_URL
+Vue.prototype.LOGIN_URL = process.env.LOGIN_URL
+Vue.prototype.HOME_URL = process.env.HOME_URL
+Vue.prototype.YUN_URL = process.env.YUN_URL
+Vue.prototype.OSS_URL = process.env.OSS_URL
+
+Vue.use(Layout)
+
+/* eslint-disable no-new */
+new Vue({
+  el: '#app',
+  router,
+  components: { App },
+  template: '<App/>'
+})

+ 5 - 0
src/plugins/element-ui.js

@@ -0,0 +1,5 @@
+import Vue from 'vue'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+
+Vue.use(ElementUI, { size: 'mini', zIndex: 3000 })

+ 3 - 0
src/plugins/index.js

@@ -0,0 +1,3 @@
+import './element-ui'
+import './vue-quill-editor'
+import './vue-clipboard2'

+ 4 - 0
src/plugins/vue-clipboard2.js

@@ -0,0 +1,4 @@
+import Vue from 'vue'
+import VueClipboard from 'vue-clipboard2'
+
+Vue.use(VueClipboard)

+ 9 - 0
src/plugins/vue-quill-editor.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import VueQuillEditor from 'vue-quill-editor'
+
+// require styles
+import 'quill/dist/quill.core.css'
+import 'quill/dist/quill.snow.css'
+import 'quill/dist/quill.bubble.css'
+
+Vue.use(VueQuillEditor)

+ 144 - 0
src/router/index.js

@@ -0,0 +1,144 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import NProgress from 'nprogress'
+
+Vue.use(Router)
+
+const router = new Router({
+  routes: [{
+    path: '/login',
+    name: 'Login',
+    component: () => import(`@/views/login`),
+    meta: {title: '登录', skipAuth: true}
+  }, {
+    path: '/logout',
+    name: 'Logout',
+    component: () => import(`@/views/logout`),
+    meta: {title: '退出登录'}
+  }, {
+    path: '/403',
+    name: '403',
+    component: () => import(`@/views/exception/403`),
+    meta: {title: '403', skipAuth: true}
+  }, {
+    path: '/',
+    redirect: '/news'
+  }, {
+    path: '/news',
+    component: () => import(`@/views/main`),
+    children: [{
+      path: '/news',
+      name: 'News',
+      component: () => import(`@/views/news/list`),
+      meta: {title: '新闻列表'}
+    }, {
+      path: '/news/edit',
+      name: 'NewsEdit',
+      component: () => import(`@/views/news/edit`),
+      meta: {title: '修改新闻列表'}
+    }]
+  }, {
+    path: '/courseIntroduce',
+    component: () => import(`@/views/main`),
+    children: [{
+      path: '/courseIntroduce',
+      name: 'CourseIntroduce',
+      component: () => import(`@/views/courseIntroduce/list`),
+      meta: {title: '课程介绍'}
+    }, {
+      path: '/courseIntroduce/edit',
+      name: 'CourseIntroduceEdit',
+      component: () => import(`@/views/courseIntroduce/edit`),
+      meta: {title: '修改课程介绍'}
+    }]
+  }, {
+    path: '/vrPanorama',
+    component: () => import(`@/views/main`),
+    children: [{
+      path: '/vrPanorama',
+      name: 'vrPanorama',
+      component: () => import(`@/views/vr-panorama/list`),
+      meta: {title: 'VR全景图'}
+    }, {
+      path: '/vrPanorama/edit',
+      name: 'vrPanoramaEdit',
+      component: () => import(`@/views/vr-panorama/edit`),
+      meta: {title: '修改VR全景图'}
+    }]
+  }, {
+    path: '/banner',
+    component: () => import(`@/views/main`),
+    children: [{
+      path: '/banner',
+      name: 'Banner',
+      component: () => import(`@/views/banner/list`),
+      meta: {title: 'Banner管理'}
+    }, {
+      path: '/banner/edit',
+      name: 'BannerEdit',
+      component: () => import(`@/views/banner/edit`),
+      meta: {title: 'Banner管理-修改'}
+    }, {
+      path: '/3Dmodel',
+      name: '3Dmodel',
+      component: () => import(`@/views/3Dmodel/list`),
+      meta: {title: '在线3D模型'}
+    }, {
+      path: '/3Dmodel/edit',
+      name: '3DmodelEdit',
+      component: () => import(`@/views/3Dmodel/edit`),
+      meta: {title: '在线3D模型-修改'}
+    }]
+  }, {
+      path: '/material',
+      component: () => import(`@/views/main`),
+      children: [{
+        path: '/material',
+        name: 'Material',
+        component: () => import(`@/views/material/list`),
+        meta: {title: '素材管理'}
+      }]
+  }
+  ]
+})
+
+router.beforeEach((to, from, next) => {
+  if (to.meta.skipAuth) {
+    next()
+  } else {
+    if (to.query.token === null | to.query.token === '' | to.query.token === undefined) {
+      if (localStorage.getItem('token') === null | localStorage.getItem('token') === '') {
+        var cookies = document.cookie.split('; ')
+        for (var arr of cookies) {
+          var _cookies = arr.split('=')
+          if (_cookies[0] === 'token') {
+            if (_cookies[1] !== null && _cookies[1] !== '' && _cookies[1] !== undefined) {
+              next()
+            }
+          }
+        }
+        next({name: '403', query: {redirect_url: window.location.href}})
+      } else {
+        if (to.matched.length === 0) { // 匹配前往的路由不存在
+          from.name ? next({name: from.name}) : next('/error') // 判断此跳转路由的来源路由是否存在,存在的情况跳转到来源路由,否则跳转到404页面
+        } else {
+          next()
+        }
+      }
+    } else {
+      localStorage.setItem('token', to.query.token)
+      if (to.matched.length === 0) { // 匹配前往的路由不存在
+        from.name ? next({name: from.name}) : next('/error') // 判断此跳转路由的来源路由是否存在,存在的情况跳转到来源路由,否则跳转到404页面
+      } else {
+        next()
+      }
+    }
+  }
+  NProgress.start() // 每次切换页面时,调用进度条
+})
+
+router.afterEach(() => {
+  NProgress.done() // 在即将进入新的页面组件前,关闭掉进度条
+})
+
+export default router

+ 319 - 0
src/utils/date-kit.js

@@ -0,0 +1,319 @@
+/*eslint-disable*/
+// 把 YYYY-MM-DD 改成了 yyyy-MM-dd
+(function (main) {
+    'use strict';
+
+    /**
+     * Parse or format dates
+     * @class fecha
+     */
+    var fecha = {};
+    var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g;
+    var twoDigits = /\d\d?/;
+    var threeDigits = /\d{3}/;
+    var fourDigits = /\d{4}/;
+    var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
+    var noop = function () {
+    };
+
+    function shorten(arr, sLen) {
+        var newArr = [];
+        for (var i = 0, len = arr.length; i < len; i++) {
+            newArr.push(arr[i].substr(0, sLen));
+        }
+        return newArr;
+    }
+
+    function monthUpdate(arrName) {
+        return function (d, v, i18n) {
+            var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase());
+            if (~index) {
+                d.month = index;
+            }
+        };
+    }
+
+    function pad(val, len) {
+        val = String(val);
+        len = len || 2;
+        while (val.length < len) {
+            val = '0' + val;
+        }
+        return val;
+    }
+
+    var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+    var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+    var monthNamesShort = shorten(monthNames, 3);
+    var dayNamesShort = shorten(dayNames, 3);
+    fecha.i18n = {
+        dayNamesShort: dayNamesShort,
+        dayNames: dayNames,
+        monthNamesShort: monthNamesShort,
+        monthNames: monthNames,
+        amPm: ['am', 'pm'],
+        DoFn: function DoFn(D) {
+            return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10];
+        }
+    };
+
+    var formatFlags = {
+        D: function (dateObj) {
+            return dateObj.getDay();
+        },
+        DD: function (dateObj) {
+            return pad(dateObj.getDay());
+        },
+        Do: function (dateObj, i18n) {
+            return i18n.DoFn(dateObj.getDate());
+        },
+        d: function (dateObj) {
+            return dateObj.getDate();
+        },
+        dd: function (dateObj) {
+            return pad(dateObj.getDate());
+        },
+        ddd: function (dateObj, i18n) {
+            return i18n.dayNamesShort[dateObj.getDay()];
+        },
+        dddd: function (dateObj, i18n) {
+            return i18n.dayNames[dateObj.getDay()];
+        },
+        M: function (dateObj) {
+            return dateObj.getMonth() + 1;
+        },
+        MM: function (dateObj) {
+            return pad(dateObj.getMonth() + 1);
+        },
+        MMM: function (dateObj, i18n) {
+            return i18n.monthNamesShort[dateObj.getMonth()];
+        },
+        MMMM: function (dateObj, i18n) {
+            return i18n.monthNames[dateObj.getMonth()];
+        },
+        yy: function (dateObj) {
+            return String(dateObj.getFullYear()).substr(2);
+        },
+        yyyy: function (dateObj) {
+            return dateObj.getFullYear();
+        },
+        h: function (dateObj) {
+            return dateObj.getHours() % 12 || 12;
+        },
+        hh: function (dateObj) {
+            return pad(dateObj.getHours() % 12 || 12);
+        },
+        H: function (dateObj) {
+            return dateObj.getHours();
+        },
+        HH: function (dateObj) {
+            return pad(dateObj.getHours());
+        },
+        m: function (dateObj) {
+            return dateObj.getMinutes();
+        },
+        mm: function (dateObj) {
+            return pad(dateObj.getMinutes());
+        },
+        s: function (dateObj) {
+            return dateObj.getSeconds();
+        },
+        ss: function (dateObj) {
+            return pad(dateObj.getSeconds());
+        },
+        S: function (dateObj) {
+            return Math.round(dateObj.getMilliseconds() / 100);
+        },
+        SS: function (dateObj) {
+            return pad(Math.round(dateObj.getMilliseconds() / 10), 2);
+        },
+        SSS: function (dateObj) {
+            return pad(dateObj.getMilliseconds(), 3);
+        },
+        a: function (dateObj, i18n) {
+            return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1];
+        },
+        A: function (dateObj, i18n) {
+            return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase();
+        },
+        ZZ: function (dateObj) {
+            var o = dateObj.getTimezoneOffset();
+            return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4);
+        }
+    };
+
+    var parseFlags = {
+        d: [twoDigits, function (d, v) {
+            d.day = v;
+        }],
+        M: [twoDigits, function (d, v) {
+            d.month = v - 1;
+        }],
+        yy: [twoDigits, function (d, v) {
+            var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2);
+            d.year = '' + (v > 68 ? cent - 1 : cent) + v;
+        }],
+        h: [twoDigits, function (d, v) {
+            d.hour = v;
+        }],
+        m: [twoDigits, function (d, v) {
+            d.minute = v;
+        }],
+        s: [twoDigits, function (d, v) {
+            d.second = v;
+        }],
+        yyyy: [fourDigits, function (d, v) {
+            d.year = v;
+        }],
+        S: [/\d/, function (d, v) {
+            d.millisecond = v * 100;
+        }],
+        SS: [/\d{2}/, function (d, v) {
+            d.millisecond = v * 10;
+        }],
+        SSS: [threeDigits, function (d, v) {
+            d.millisecond = v;
+        }],
+        D: [twoDigits, noop],
+        ddd: [word, noop],
+        MMM: [word, monthUpdate('monthNamesShort')],
+        MMMM: [word, monthUpdate('monthNames')],
+        a: [word, function (d, v, i18n) {
+            var val = v.toLowerCase();
+            if (val === i18n.amPm[0]) {
+                d.isPm = false;
+            } else if (val === i18n.amPm[1]) {
+                d.isPm = true;
+            }
+        }],
+        ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) {
+            var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes;
+
+            if (parts) {
+                minutes = +(parts[1] * 60) + parseInt(parts[2], 10);
+                d.timezoneOffset = parts[0] === '+' ? minutes : -minutes;
+            }
+        }]
+    };
+    parseFlags.DD = parseFlags.DD;
+    parseFlags.dddd = parseFlags.ddd;
+    parseFlags.Do = parseFlags.dd = parseFlags.d;
+    parseFlags.mm = parseFlags.m;
+    parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h;
+    parseFlags.MM = parseFlags.M;
+    parseFlags.ss = parseFlags.s;
+    parseFlags.A = parseFlags.a;
+
+
+    // Some common format strings
+    fecha.masks = {
+        'default': 'ddd MMM dd yyyy HH:mm:ss',
+        shortDate: 'M/D/yy',
+        mediumDate: 'MMM d, yyyy',
+        longDate: 'MMMM d, yyyy',
+        fullDate: 'dddd, MMMM d, yyyy',
+        shortTime: 'HH:mm',
+        mediumTime: 'HH:mm:ss',
+        longTime: 'HH:mm:ss.SSS'
+    };
+
+    /***
+     * Format a date
+     * @method format
+     * @param {Date|number} dateObj
+     * @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate'
+     */
+    fecha.format = function (dateObj, mask, i18nSettings) {
+        var i18n = i18nSettings || fecha.i18n;
+
+        if (typeof dateObj === 'number') {
+            dateObj = new Date(dateObj);
+        }
+
+        if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) {
+            throw new Error('Invalid Date in fecha.format');
+        }
+
+        mask = fecha.masks[mask] || mask || fecha.masks['default'];
+
+        return mask.replace(token, function ($0) {
+            return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1);
+        });
+    };
+
+    /**
+     * Parse a date string into an object, changes - into /
+     * @method parse
+     * @param {string} dateStr Date string
+     * @param {string} format Date parse format
+     * @returns {Date|boolean}
+     */
+    fecha.parse = function (dateStr, format, i18nSettings) {
+        var i18n = i18nSettings || fecha.i18n;
+
+        if (typeof format !== 'string') {
+            throw new Error('Invalid format in fecha.parse');
+        }
+
+        format = fecha.masks[format] || format;
+
+        // Avoid regular expression denial of service, fail early for really long strings
+        // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS
+        if (dateStr.length > 1000) {
+            return false;
+        }
+
+        var isValid = true;
+        var dateInfo = {};
+        format.replace(token, function ($0) {
+            if (parseFlags[$0]) {
+                var info = parseFlags[$0];
+                var index = dateStr.search(info[0]);
+                if (!~index) {
+                    isValid = false;
+                } else {
+                    dateStr.replace(info[0], function (result) {
+                        info[1](dateInfo, result, i18n);
+                        dateStr = dateStr.substr(index + result.length);
+                        return result;
+                    });
+                }
+            }
+
+            return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1);
+        });
+
+        if (!isValid) {
+            return false;
+        }
+
+        var today = new Date();
+        if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) {
+            dateInfo.hour = +dateInfo.hour + 12;
+        } else if (dateInfo.isPm === false && +dateInfo.hour === 12) {
+            dateInfo.hour = 0;
+        }
+
+        var date;
+        if (dateInfo.timezoneOffset != null) {
+            dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset;
+            date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
+                dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0));
+        } else {
+            date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1,
+                dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0);
+        }
+        return date;
+    };
+
+    /* istanbul ignore next */
+    if (typeof module !== 'undefined' && module.exports) {
+        module.exports = fecha;
+    } else if (typeof define === 'function' && define.amd) {
+        define(function () {
+            return fecha;
+        });
+    } else {
+        main.fecha = fecha;
+    }
+})(this);

+ 120 - 0
src/utils/http-kit.js

@@ -0,0 +1,120 @@
+import {Message} from 'element-ui'
+import NProgress from 'nprogress'
+import axios from 'axios'
+// import router from '../router'
+
+axios.defaults.baseURL = process.env.HOME_URL
+axios.defaults.timeout = 30 * 1000 // 设置接口响应时间
+// let loginUrl = '/login'
+
+/**
+ * 功能描述:Http Request 拦截器
+ */
+axios.interceptors.request.use((config) => {
+  NProgress.start() // 展示进度条
+
+  config.headers.common['authorization'] = 'Bearer ' + localStorage.getItem('token') // todo 设置真实token
+  config.headers.common['Content-Type'] = 'application/json;charset=UTF-8'
+
+  return config
+}, (error) => { // 请求错误时做些事(接口错误、超时等)
+  NProgress.done() // 隐藏进度条
+  Message.error(`请求参数错误`, 10)
+
+  /* 1.判断请求超时 */
+  if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
+    // return service.request(originalRequest);//例如再重复请求一次
+  }
+
+  /* 2.需要重定向到错误页面 */
+  if (error.response) {
+    // error =errorInfo.data//页面那边catch的时候就能拿到详细的错误信息,看最下边的Promise.reject
+    // router.push({
+    //   path: `/error/${errorInfo.status}`// 404 403 500 ... 等
+    // })
+  }
+
+  return Promise.reject(error) // 在调用的那边可以拿到(catch)你想返回的错误信息
+})
+
+/**
+ * 功能描述:Http Response 拦截器
+ */
+axios.interceptors.response.use((res) => {
+  NProgress.done() // 隐藏进度条
+  let isTips = res.config.showMessage === undefined || res.config.showMessage === true
+  let data = res.data
+  /* 根据返回的code值来做不同的处理(和后端约定) */
+  switch (data.errorCode) {
+    case 0:
+    case 200:
+      return Promise.resolve(data)
+    default:
+      isTips && Message.error(`服务器返回异常:${data.errorMessage}`, 10)
+      return Promise.reject(res)
+  }
+}, (error) => {
+  Message.error(`服务器返回异常:${error}`, 10)
+
+  if (error && error.response) {
+    switch (error.response.status) {
+      case 400:
+        break
+      case 403:
+        window.location.href = `#/403`
+        break
+      default:
+        break
+    }
+  }
+
+  return Promise.reject(error)
+})
+
+export default {
+  get (url, data = {}) {
+    return new Promise((resolve, reject) => {
+      axios.get(url, data).then(response => {
+        resolve(response)
+      }).catch(err => {
+        reject(err)
+      })
+    })
+  },
+  delete (url, data = {}) {
+    return new Promise((resolve, reject) => {
+      axios.delete(url, data).then(response => {
+        resolve(response)
+      }).catch(err => {
+        reject(err)
+      })
+    })
+  },
+  post (url, data = {}) {
+    return new Promise((resolve, reject) => {
+      axios.post(url, data).then(response => {
+        resolve(response)
+      }, err => {
+        reject(err)
+      })
+    })
+  },
+  put (url, data = {}) {
+    return new Promise((resolve, reject) => {
+      axios.put(url, data).then(response => {
+        resolve(response)
+      }, err => {
+        reject(err)
+      })
+    })
+  },
+  patch (url, data = {}) {
+    return new Promise((resolve, reject) => {
+      axios.patch(url, data).then(response => {
+        resolve(response)
+      }, err => {
+        reject(err)
+      })
+    })
+  }
+}

+ 20 - 0
src/utils/str-kit.js

@@ -0,0 +1,20 @@
+export default {
+  /**
+   * 功能描述:计算文本的长度
+   * @param strKit
+   * @returns {number}
+   */
+  getByteLen (strKit) {
+    let len = 0
+    if (typeof strKit === 'string' && strKit.constructor === String) {
+      for (let i = 0; i < strKit.length; i++) {
+        if (strKit[i].match(/[^x00-xff]/ig)) { // 全角
+          len += 2
+        } else {
+          len += 1
+        }
+      }
+    }
+    return len
+  }
+}

+ 268 - 0
src/views/3Dmodel/edit.vue

@@ -0,0 +1,268 @@
+<template>
+  <el-row>
+    <el-row style="padding-top: 50px;width: 1500px;padding-left: 30px">
+      <el-form ref="form" :model="form" :rules="formRules" label-width="120px">
+        <el-row>
+          <el-col :span="12">
+            <el-row>
+              <el-form-item label="标题" prop="thdTitle">
+                <el-input v-model="form.thdTitle" placeholder="在这里输入标题" show-word-limit maxlength="40"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row>
+              <el-form-item label="简介" prop="intro">
+                <el-input v-model="form.intro" type="textarea" :rows="7" placeholder="在这里输入简介" show-word-limit maxlength="300"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row style="width: 232px;margin-top: 10px">
+              <el-form-item label="二维码" prop="qrcodeUrl">
+                <el-upload
+                  drag
+                  class="avatar-qrcode-uploader"
+                  :action="action"
+                  :data="fileform"
+                  :before-upload="beforeqrcodeUpload"
+                  :on-success="qrcodeSuccess"
+                  :show-file-list="false"
+                  :headers="{'authorization' : this.token}"
+                >
+                  <img v-if="form.qrcodeUrl !== ''" width="100%" :src="imgQrcodeUrl" alt="">
+                  <el-row>
+                    <img v-if="form.qrcodeUrl === ''" src="@/assets/tianjia.png" style="width: 33px;height: 33px;padding-top: 80px">
+                  </el-row>
+                  <el-row>
+                   <label v-if="form.qrcodeUrl === ''" style="width: 106px;height: 14px;font-family: MicrosoftYaHei;font-size: 14px;font-weight: normal;font-stretch: normal;line-height: 24px;letter-spacing: 1px;color: #858585;">点击添加二维码</label>
+                  </el-row>
+                </el-upload>
+              </el-form-item>
+            </el-row>
+          </el-col>
+          <el-col :span="12">
+            <el-row>
+              <el-col  :span="12">
+                <el-form-item label="缩略图" prop="imageUrl">
+                  <material-dialog v-model="form.imageUrl" sourceType="全景图/3D模型"></material-dialog>
+                </el-form-item>
+              </el-col>
+              <el-col  :span="12">
+                <div style="height: 131px"><img :src="OSS_URL + form.imageUrl" style="height: 131px;width: 248px;border-radius: 4px"></div>
+              </el-col>
+            </el-row>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-row>
+    <el-row style="padding: 0px 150px;text-align: left;">
+      <el-button type="primary" @click="handleSave">保存</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toOpenZip">预览</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="checkboxrelease">发布</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toBack">取消</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import materialDialog from '@/views/material/dialog'
+  import dModelApi from '@/api/3dModelApi'
+
+  export default {
+    name: 'dModeld',
+    components: {
+      'material-dialog': materialDialog
+    },
+    data () {
+      return {
+        form: {
+          id: '',
+          thdTitle: '',
+          saveMark: 0,
+          intro: '',
+          imageUrl: '',
+          releaseStatue: 0,
+          pushIndex: 0,
+          secondTop: 0,
+          qrcodeUrl: '',
+          zipUrl: ''
+        },
+        token: '',
+        imgQrcodeUrl: '',
+        action: `${this.OSS_URL}/file/upload`,
+        fileform: {
+          sourceType: ''
+        },
+        formRules: {
+          thdTitle: { required: true, message: '请输入标题', trigger: 'blur' },
+          intro: { required: true, message: '请输入简介', trigger: 'blur' },
+          imageUrl: { required: true, message: '请选择缩略图', trigger: 'blur' }
+        }
+      }
+    },
+    mounted () {
+      this.token = 'Bearer ' + localStorage.getItem('token')
+      this.form['id'] = this.$route.params.msgKey
+      this.findById()
+    },
+    methods: {
+      toOpenZip (index, row) {
+        if (this.form['id'] !== undefined) {
+          window.open(`${this.YUN_URL}${this.form['zipUrl']}`, '_blank')
+        } else {
+          this.$message.error('请保存后再进行预览')
+        }
+      },
+      checkboxrelease () {
+        this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+          if (this.form['id'] !== undefined | this.saveMark === 1) {
+            dModelApi.checkboxrelease(this.form['id']).then(data => {
+              this.list()
+            })
+          } else {
+            this.$message.error('请保存后再进行发布')
+          }
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+      },
+      toBack () {
+        this.$confirm('是否取消创建/修改文章?,并退出编辑页面', {
+          confirmButtonText: '继续退出',
+          cancelButtonText: '留下',
+          type: 'primary'
+        }).then(() => {
+          this.$router.push({name: '3Dmodel'})
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已退出编辑页面'
+          })
+        })
+      },
+      handleSave () {
+        this.$refs['form'].validate((valid) => {
+          if (!valid) {
+            return false
+          }
+        this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+            dModelApi.save(this.form).then(data => {
+              this.saveMark = 1
+          })
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+        })
+      },
+      findById () {
+        if (this.form['id']) {
+          dModelApi.findById(this.form['id']).then(data => {
+            this.form['id'] = data.id
+            this.form['thdTitle'] = data.thdTitle
+            this.form['intro'] = data.intro
+            this.form['imageUrl'] = data.imageUrl
+            this.form['releaseStatue'] = data.releaseStatue
+            this.form['pushIndex'] = data.pushIndex
+            this.form['secondTop'] = data.secondTop
+            this.form['qrcodeUrl'] = data.qrcodeUrl
+            this.form['zipUrl'] = data.zipUrl
+            this.imgQrcodeUrl = `${this.OSS_URL}` + data.qrcodeUrl
+          })
+        }
+      },
+      beforeqrcodeUpload (file) {
+        var i = 0
+        const type = file.type
+        if (type === 'image/png') {
+          i = 1
+        }
+        if (type === 'image/jpeg') {
+          i = 1
+        }
+        let isJPG = true // todo 临时增加
+        const isLt2M = file.size / 1024 / 1024 < 10
+        if (i === 0) {
+          this.$message.error('上传头像图片格式非法')
+          return false
+        }
+        if (!isLt2M) {
+          this.$message.error('上传头像图片大小不能超过 10MB!')
+          return false
+        }
+        this.fileform.sourceType = 'vr3d'
+        return isJPG && isLt2M
+      },
+      beforezipUpload (file) {
+        const type = file.type
+        let isJPG = true // todo 临时增加
+        const isLt2M = file.size / 1024 / 1024 < 1024
+        if (type !== 'application/x-zip-compressed') {
+          this.$message.error('上传文件格式非法')
+          return false
+        }
+        if (!isLt2M) {
+          this.$message.error('上传大小不能超过 1G!')
+          return false
+        }
+        this.fileform.sourceType = 'zip'
+        return isJPG && isLt2M
+      },
+      qrcodeSuccess (data) {
+        this.form['qrcodeUrl'] = data.data
+        this.imgQrcodeUrl = `${this.OSS_URL}` + data.data
+      },
+      zipSuccess (data) {
+        this.form['zipUrl'] = data.data
+        this.$message.success('上传成功')
+      }
+    }
+  }
+</script>
+
+<style>
+  .el-icon-circle-plus {
+    font-size: 50px;
+  }
+
+  .avatar-qrcode-uploader .el-upload-dragger {
+    height: 150px;
+    width: 150px;
+  }
+
+  .avatar-zip-uploader .el-upload-dragger {
+    height: 50px;
+    width: 150px;
+    background-color: #eeeeee;
+    border: 0px;
+  }
+</style>
+<style scoped>
+  /deep/ .el-dialog {
+    margin-top: 10vh
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .avatar-qrcode-uploader .el-upload-dragger{
+    width: 232px;
+    height: 232px;
+  }
+</style>

+ 437 - 0
src/views/3Dmodel/list.vue

@@ -0,0 +1,437 @@
+<template>
+  <el-row style="padding-top: 50px;padding-left: 40px">
+    <el-row >
+      <el-form>
+        <el-col style="text-align: left"><el-input v-model.trim="searchForm.searchKey" style="width: 184px;height: 32px" placeholder="关键字"></el-input>
+          <el-button type="primary" @click="list()" style="margin-left: 15px">搜索</el-button>
+          <el-button type="primary" @click="canelSearch()" style="margin-left: 15px">清除搜索</el-button></el-col>
+      </el-form>
+    </el-row>
+
+    <el-row style="margin-top: 20px">
+      <white-board style="margin-right: 50px;padding: 0px 0px 0px 0px;height: 714px;border-radius: 6px;">
+        <el-row style="padding-top: 30px">
+          <el-col  style="margin-left: 29px;text-align: left">
+            <el-button type="primary" @click="toEdit" style="float: left;">新建</el-button>
+            <el-upload
+              drag
+              class="avatar-zip-uploader"
+              ref="upload"
+              :action="action"
+              :before-upload="beforezipUpload"
+              :data="fileform"
+              :on-success="zipSuccess"
+              :show-file-list="false"
+              :headers="{'authorization' : this.token}"
+              style="float: left;margin-left: 15px"
+            >
+              <el-button  type="primary">上传</el-button>
+            </el-upload>
+            <el-button type="primary" @click="handleDel" style="margin-left: 1300px">删除选中</el-button>
+          </el-col>
+        </el-row>
+        <el-row style="padding-top: 20px;padding-left: 3px">
+          <el-table  :data="tableData" row-key="id" ref="table" style="width: 100%;" @selection-change="handleSelectionChange" highlight-current-row stripe>
+            <el-table-column type="selection" width="55" align="center"></el-table-column>
+            <el-table-column prop="seqencing" label="序号" width="100" align="center"></el-table-column>
+            <el-table-column prop="thdTitle" label="标题" align="center" width="300" show-overflow-tooltip></el-table-column>
+            <el-table-column label="缩略图" width="200" align="center">
+              <template slot-scope="scope">
+                <div style="height: 88px"><img :src="OSS_URL + scope.row.imageUrl" v-if="scope.row.imageUrl" style="height: 88px;width: 147px;"></div>
+              </template>
+            </el-table-column>
+            <el-table-column label="发布" width="150" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.releaseStatue" style="text-align: left;">
+                  <el-row>
+                  <el-radio :label="1" @change="checkboxrelease(scope.row)">是</el-radio>
+                  </el-row>
+                  <el-row>
+                  <el-radio :label="0" style="margin-top: 15px" @change="checkboxnorelease(scope.row)">否</el-radio>
+                  </el-row>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column label="强推首页" width="150" align="center">
+              <template slot-scope="scope">
+                <el-row>
+                  <el-col :span="5">
+                    <el-checkbox v-model="scope.row.pushIndex" @change="pushChange(scope.row)" :true-label="1" style="padding-top: 3px"></el-checkbox>
+                  </el-col>
+                  <el-col :span="19" v-if="scope.row.pushIndex === 1">
+                    <el-select v-model="scope.row.seqencing" placeholder="请选择" @change="changeSeqencing(scope.row)">
+                      <el-option
+                        v-for="item in options"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value">
+                      </el-option>
+                    </el-select>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+            <el-table-column label="二级置顶" width="180" align="center">
+              <template slot-scope="scope"><el-checkbox v-model="scope.row.secondTop" @change="changSecondTop(scope.row)" :true-label="1"></el-checkbox></template>
+            </el-table-column>
+            <el-table-column label="是否上传" width="100" align="center">
+              <template slot-scope="scope"><el-checkbox v-model="scope.row.zipUrl"  :checked="scope.row.zipUrl === ''?  false : scope.row.zipUrl === null ? false : true "></el-checkbox></template>
+            </el-table-column>
+            <el-table-column prop="gmtCreated" label="创建日期" width="200" align="center"></el-table-column>
+            <el-table-column width="150" align="center">
+              <template slot-scope="scope">
+                <el-row style="text-align: left;">
+                  <el-col :span="12">
+                  <el-button type="text" style="color: red;" plain @click="toOpenZip(scope.$index, scope.row)">预览</el-button>
+                  </el-col>
+                  <el-col :span="12">
+                  <el-button type="text" style="color: red;" plain @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-bottom: 20px">
+          <el-pagination  @current-change="changeCurrentChange" :current-page="searchForm.page"  :page-size="5" layout="prev, pager, next, jumper, total" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import dModelApi from '@/api/3dModelApi'
+
+  export default {
+    name: 'Model',
+    components: {
+    },
+    data () {
+      return {
+        fileform: {
+          sourceType: 'zip'
+        },
+        form: {
+          id: '',
+          saveMark: 0,
+          vrTitle: '',
+          intro: '',
+          imageUrl: '',
+          releaseStatue: 0,
+          pushIndex: 0,
+          secondTop: 0,
+          qrcodeUrl: '',
+          zipUrl: ''
+        },
+        token: '',
+        zipUrl: '',
+        action: `${this.OSS_URL}/file/upload`,
+        lessData: '',
+        searchForm: {
+          searchKey: '',
+          page: 1,
+          size: 5
+        },
+        total: 0,
+        tableData: [],
+        options: [{
+          value: '1',
+          label: '1'
+        }, {
+          value: '2',
+          label: '2'
+        }, {
+          value: '3',
+          label: '3'
+        }, {
+          value: '4',
+          label: '4'
+        }],
+        temp: [],
+        IdList: []
+      }
+    },
+    mounted () {
+      this.token = 'Bearer ' + localStorage.getItem('token')
+      this.list()
+    },
+    methods: {
+      beforezipUpload (file) {
+        if (this.temp.length === 0) {
+          this.$message.error('请先勾选要上传的数据')
+          return false
+        }
+        if (this.temp.length > 1) {
+          this.$message.error('只能同时上传一列')
+          return false
+        }
+        const type = file.type
+        let isJPG = true // todo 临时增加
+        const isLt2M = file.size / 1024 / 1024 < 1024
+        if (type !== 'application/x-zip-compressed') {
+          this.$message.error('上传文件格式非法')
+          return false
+        }
+        if (!isLt2M) {
+          this.$message.error('上传大小不能超过 1G!')
+          return false
+        }
+        return isJPG && isLt2M
+      },
+      zipSuccess (data) {
+        this.form.id = this.temp[0].id
+        this.form['thdTitle'] = this.temp[0].vrTitle
+        this.form['intro'] = this.temp[0].intro
+        this.form['imageUrl'] = this.temp[0].imageUrl
+        this.form['releaseStatue'] = this.temp[0].releaseStatue
+        this.form['pushIndex'] = this.temp[0].pushIndex
+        this.form['secondTop'] = this.temp[0].secondTop
+        this.form['qrcodeUrl'] = this.temp[0].qrcodeUrl
+        this.form.zipUrl = data.data
+        dModelApi.save(this.form).then(data => {
+          this.$message.success('上传成功')
+          this.list()
+        })
+      },
+      onCopy () {
+        this.$message.success('复制成功!')
+      },
+      onError () {
+        this.$message.error('复制失败!')
+      },
+      handleDel () {
+        this.$confirm('是否删除文章', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'primary'
+        }).then(() => {
+          this.IdList = []
+          for (let i = 0; i < this.temp.length; i++) {
+            this.IdList.push(this.temp[i].id)
+          }
+          dModelApi.delete({idList: this.IdList}).then(data => {
+            this.list()
+          })
+        }).catch(() => {
+          this.$message({type: 'info', message: '已取消删除'})
+        })
+      },
+      toEdit () {
+        this.$router.push({name: '3DmodelEdit'})
+      },
+      handleEdit (index, row) {
+        this.$router.push({name: '3DmodelEdit', params: {key: 'modelId', msgKey: row.id}})
+      },
+      handleSelectionChange (val) {
+        this.temp = val
+      },
+      canelSearch () {
+        this.searchForm['searchKey'] = ''
+        this.list()
+      },
+      list () {
+        this.tableData = []
+        dModelApi.page(this.searchForm).then(data => {
+          this.searchForm.page = data.pageIndex
+          this.total = data.totalCount
+          this.tableData = data.list
+        })
+      },
+      changeSeqencing (row) {
+        dModelApi.changeSeq(row.id, row.seqencing).then(data => {
+          this.list()
+        })
+      },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.init()
+        this.searchForm.page = val
+        this.list()
+      },
+      forward (row) {
+        dModelApi.forward(row.id).then(data => {
+          this.list()
+        })
+      },
+      backward (row) {
+        dModelApi.backward(row.id).then(data => {
+          this.list()
+        })
+      },
+      checkboxrelease (row) {
+        dModelApi.checkboxrelease(row.id).then(data => {
+          this.list()
+        })
+      },
+      init () {
+        if (this.searchForm.page === 1) {
+          this.lessData = 0
+          for (var item of this.tableData) {
+            if (item.pushIndex === 1) {
+              this.lessData += 1
+            }
+          }
+        }
+      },
+      checkboxnorelease (row) {
+        if (row.pushIndex === 1) {
+          this.$message.error('取消发布前必须取消强推首页')
+          this.list()
+        } else {
+        dModelApi.checkboxnorelease(row.id).then(data => {
+          this.list()
+        })
+        }
+      },
+      pushChange (row) {
+        if (row.releaseStatue !== 1) {
+          this.$message.error('强推首页前必须发布')
+          this.list()
+        } else {
+          if (this.searchForm.page === 1) {
+            var localLessData = 0
+            for (var item of this.tableData) {
+              if (item.pushIndex === 1) {
+                localLessData += 1
+              }
+            }
+            if (localLessData < 5) {
+              dModelApi.changePushIndex(row.id).then(data => {
+                this.list()
+              })
+            } else {
+              this.$message.error('强推首页不能有大于4条记录')
+              this.list()
+            }
+          } else {
+            if (this.lessData + 1 < 5) {
+              dModelApi.changePushIndex(row.id).then(data => {
+                this.list()
+              })
+            } else {
+              this.$message.error('强推首页不能有大于4条记录')
+              this.list()
+            }
+          }
+        }
+      },
+      changSecondTop (row) {
+        dModelApi.changeSecondTop(row.id).then(data => {
+          this.list()
+        })
+      },
+      toOpenZip (index, row) {
+        window.open(`${this.YUN_URL}${row.zipUrl}`, '_blank')
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-radio {
+    margin-right: 0px;
+  }
+  /deep/ .el-table th>.cell{
+    font-family: MicrosoftYaHei-Bold;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row el-table__row--striped {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__total {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-upload-dragger{
+    border: 0px;
+    width: 57px;
+    height: 28px;
+    border-radius: 3px;
+
+  }
+  /deep/ .avatar-zip-uploader .el-upload-dragger{
+    background-color: #ffffff;
+  width: 64px;
+  height: 32px;
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .el-table td, .el-table th.is-leaf{
+    border: 0px;
+  }
+  /deep/ .el-pagination__total{
+    margin-left: 17px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump{
+    margin-left: 15px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-radio__inner{
+    width: 16px;
+    height: 16px;
+  }
+  /deep/ .el-radio__inner::after{
+    width: 8px;
+    height: 8px;
+  }
+</style>

+ 124 - 0
src/views/RBAC/list.vue

@@ -0,0 +1,124 @@
+<template>
+  <el-row>
+    <el-row style="padding: 20px 0px;"><span style="float: left;font-size: 20px">权限设置</span></el-row>
+    <el-row>
+      <el-form>
+        <el-col :span="6"><el-input v-model.trim="searchForm.searchKey" placeholder="关键字"></el-input></el-col>
+        <el-col :span="2"><el-button type="primary" @click="list()">搜索</el-button></el-col>
+        <el-col :span="2" :offset="1"><el-button type="primary" @click="canelSearch()">清除搜索</el-button></el-col>
+      </el-form>
+    </el-row>
+    <el-row style="margin-top: 20px">
+      <white-board>
+        <el-row style="padding: 20px 0px;">
+          <el-table :data="tableData" row-key="id" ref="table" style="width: 100%;" @selection-change="handleSelectionChange" highlight-current-row stripe>
+            <el-table-column type="selection" width="20" align="center"></el-table-column>
+            <el-table-column prop="id" label="工号" width="60" align="center"></el-table-column>
+            <el-table-column prop="card" label="姓名" align="center"></el-table-column>
+            <el-table-column prop="area.name" label="部门" align="center"></el-table-column>
+            <el-table-column prop="authorityTemplate" label="权限" width="130" align="center">
+              <template slot-scope="scope">
+                <div style="text-align: left;">
+                  <el-row><el-checkbox :checked="scope.row.qianduan" @change="ChangeRBAC(scope.row,0)" :true-label="1">VR管理后台</el-checkbox></el-row>
+                  <el-row><el-checkbox :checked="scope.row.houduan" @change="ChangeRBAC(scope.row,1)" :true-label="1">门户管理后台</el-checkbox></el-row>
+                </div>
+              </template>
+            </el-table-column>
+            <el-table-column label="状态" width="60" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.activation" style="text-align: left;">
+                  <el-radio :label=true @change="ChangeActivation(scope.row)">是</el-radio>
+                  <el-radio :label=false @change="ChangeActivation(scope.row)">否</el-radio>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column label="创建日期" width="90" align="center">
+              <template slot-scope="scope"><div>{{scope.row.gmtCreated}}</div></template>
+            </el-table-column>
+          </el-table>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-bottom: 20px">
+          <el-pagination @size-change="changeSizeChange" @current-change="changeCurrentChange" :current-page="searchForm.page" :page-sizes="[10, 20, 50, 100]" :page-size="10" layout="total, sizes, prev, pager, next, jumper" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import RbacApi from '@/api/RbacApi'
+
+  export default {
+    name: 'Newsindex',
+    components: {
+    },
+    data () {
+      return {
+        searchForm: {
+          searchKey: '',
+          page: 1,
+          size: 10
+        },
+        total: 0,
+        tableData: [],
+        temp: [],
+        IdList: []
+      }
+    },
+    mounted () {
+      this.list()
+    },
+    methods: {
+      canelSearch () {
+        this.searchForm['searchKey'] = ''
+        this.list()
+      },
+      list () {
+        this.tableData = []
+        RbacApi.page(this.searchForm).then(data => {
+          this.total = data.totalCount
+          this.tableData = data.list
+          data.list.map(item => {
+            item.qianduan = false
+            item.houduan = false
+            for (let index of item.authorityTemplate.split(',')) {
+              if (index === '0') {
+                item.qianduan = true
+              } else if (index === '1') {
+                item.houduan = true
+              }
+            }
+          })
+        })
+      },
+      ChangeRBAC (val, type) {
+        RbacApi.ChangeRBAC(val.id, type).then(data => {
+          this.list()
+        })
+      },
+      ChangeActivation (val) {
+         RbacApi.ChangeActivation(val.id).then(data => {
+         this.list()
+    })
+  },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.searchForm.page = val
+        this.list()
+      },
+      handleSelectionChange (val) {
+        this.temp = val
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-radio {
+    margin-right: 0px;
+  }
+</style>

+ 141 - 0
src/views/banner/edit.vue

@@ -0,0 +1,141 @@
+<template>
+  <el-row>
+    <el-row style="padding-top: 50px;width: 1500px;padding-left: 30px">
+      <el-form ref="form" :model="form" :rules="formRules" label-width="120px">
+        <el-row>
+          <el-col :span="12">
+            <el-row>
+              <el-form-item label="标题" prop="bannerTitle">
+                <el-input v-model="form.bannerTitle" placeholder="在这里输入标题" show-word-limit maxlength="40"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row style="height: 350px;width: 615px;margin-top: 10px">
+              <el-form-item label="缩略图" prop="imageUrl">
+                <material-dialog v-model="form.imageUrl" sourceType="banner" style="height: 350px;width: 615px;border-radius: 4px"></material-dialog>
+              </el-form-item>
+            </el-row>
+            <el-row style="margin-top: 30px">
+              <el-form-item label="网址">
+                <el-input v-model="form.bannerUrl" placeholder="在这里输入URL"></el-input>
+              </el-form-item>
+            </el-row>
+          </el-col>
+
+          <el-col :span="11" :offset="1" style="margin-top: 61px">
+            <el-row>
+              <div style="height: 350px;width: 615px;"><img :src="OSS_URL + form.imageUrl" style="height: 350px;width: 615px;border-radius: 4px"></div>
+            </el-row>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-row>
+    <el-row style="padding: 3px 150px;text-align: left;">
+      <el-button type="primary" @click="toBack">取消</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="handleSave">保存</el-button>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import materialDialog from '@/views/material/dialog'
+  import bannerDetailApi from '@/api/bannerDetailApi'
+
+  export default {
+    name: 'bannerDetail',
+    components: {
+      'material-dialog': materialDialog
+    },
+    data () {
+      return {
+        form: {
+          id: '',
+          bannerTitle: '',
+          imageUrl: '',
+          bannerUrl: '',
+          bannerOn: 0
+        },
+        formRules: {
+          bannerTitle: { required: true, message: '请输入标题', trigger: 'blur' },
+          imageUrl: { required: true, message: '请选择缩略图', trigger: 'blur' }
+        }
+      }
+    },
+    mounted () {
+      this.form['id'] = this.$route.params.msgKey
+      this.findById()
+      var s = document.getElementsByClassName('el-card is-hover-shadow')
+      s[0].style.height = '350px'
+      s[1].style.height = '350px'
+    },
+    methods: {
+      toBack () {
+        this.$confirm('是否取消创建/修改文章?,并退出编辑页面', {
+          confirmButtonText: '继续退出',
+          cancelButtonText: '留下',
+          type: 'primary'
+        }).then(() => {
+          this.$router.push({name: 'Banner'})
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已退出编辑页面'
+          })
+        })
+      },
+      handleSave () {
+        this.$refs['form'].validate((valid) => {
+          if (!valid) {
+            return false
+          }
+          this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+            bannerDetailApi.save(this.form).then(data => {
+              this.$router.push({name: 'Banner'})
+          })
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+        })
+      },
+      findById () {
+        if (this.form['id']) {
+          bannerDetailApi.findById(this.form['id']).then(data => {
+            this.form['id'] = data.id
+            this.form['bannerTitle'] = data.bannerTitle
+            this.form['imageUrl'] = data.imageUrl
+            this.form['bannerUrl'] = data.bannerUrl
+            this.form['bannerOn'] = data.bannerOn
+          })
+        }
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-icon-circle-plus {
+    font-size: 50px;
+  }
+  /deep/ .el-dialog {
+    margin-top: 10vh
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .el-icon-circle-plus-outline{
+    margin-top: 100px;
+  }
+</style>

+ 280 - 0
src/views/banner/list.vue

@@ -0,0 +1,280 @@
+<template>
+  <el-row style="padding-top: 50px;padding-left: 40px">
+    <el-row style="margin-top: 20px">
+      <white-board style="margin-right: 50px;padding: 0px 0px 0px 0px;height: 714px;border-radius: 6px;">
+        <el-row style="line-height: 40px;padding-top: 20px">
+          <el-col :span="4">
+            <el-select v-model="bannerData[0].value" placeholder="请选择" @change="moveDirection">
+              <el-option
+                v-for="item in options"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value">
+              </el-option>
+            </el-select>
+          </el-col>
+          <el-col :span="1" >
+            <span style="font-weight: bold">间隔</span>
+          </el-col>
+          <el-col :span="1">
+            <el-input v-model.trim="bannerData[1].value"></el-input>
+          </el-col>
+          <el-col :span="1">
+            <span style="font-weight: bold">秒</span>
+          </el-col>
+          <el-col :span="1">
+            <el-button type="primary" @click="changeSpace">确认</el-button>
+          </el-col>
+          <el-col :span="1" :offset="13">
+            <el-button type="primary" @click="toEdit">新建</el-button>
+          </el-col>
+          <el-col :span="2">
+            <el-button type="primary" @click="handleDel">删除选中</el-button>
+          </el-col>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-left: 3px">
+          <el-table :data="tableData" row-key="id" ref="table" style="width: 100%;" @selection-change="handleSelectionChange" highlight-current-row stripe>
+            <el-table-column type="selection" width="55" align="center"></el-table-column>
+            <el-table-column prop="seqencing" label="序号" width="100" align="center"></el-table-column>
+            <el-table-column prop="bannerTitle" width="300" label="标题" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column label="缩略图" width="200" align="center">
+              <template slot-scope="scope">
+                <div style="height: 88px"><img :src="OSS_URL + scope.row.imageUrl" v-if="scope.row.imageUrl" style="height: 88px;width: 147px;"></div>
+              </template>
+            </el-table-column>
+            <el-table-column prop="bannerUrl"  width="230" label="网址" show-overflow-tooltip></el-table-column>
+            <el-table-column prop="id" label="Banner顺序" width="200">
+              <template slot-scope="scope">
+                <el-col :span="6">
+                  <el-row><el-button type="primary" @click="forward(scope.row)">前移</el-button></el-row>
+                  <el-row style="margin-top: 3px"><el-button type="primary" @click="backward(scope.row)">后移</el-button></el-row>
+                </el-col>
+              </template>
+            </el-table-column>
+            <el-table-column label="是否启用" width="150" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.bannerOn" style="text-align: left;">
+                  <el-row>
+                  <el-radio :label="1" @change="changeBannerOn(scope.row)">是</el-radio>
+                  </el-row>
+                  <el-row>
+                  <el-radio :label="0" style="margin-top: 15px" @change="changeBannerOn(scope.row)">否</el-radio>
+                  </el-row>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column prop="gmtCreated" label="创建日期" width="200" align="center"></el-table-column>
+            <el-table-column width="150" align="center">
+              <template slot-scope="scope">
+                <el-button type="text" style="color: red" plain @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-bottom: 20px">
+          <el-pagination @size-change="changeSizeChange" @current-change="changeCurrentChange" :current-page="searchForm.page" :page-sizes="[10, 20, 50, 100]" :page-size="10" layout="sizes, prev, pager, next, jumper, total" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import bannerDetailApi from '@/api/bannerDetailApi'
+  import bannerControlApi from '@/api/bannerControlApi'
+
+  export default {
+    name: 'bannerDetails',
+    components: {
+    },
+    data () {
+      return {
+        searchForm: {
+          searchKey: '',
+          page: 1,
+          size: 10
+        },
+        total: 0,
+        bannerData: [{code: 'move', value: '0'}, {code: 'space', value: 3}],
+        tableData: [],
+        options: [{
+          value: '0',
+          label: '从左到右切换'
+        }, {
+          value: '1',
+          label: '从上到下切换'
+        }],
+        temp: [],
+        IdList: []
+      }
+    },
+    mounted () {
+      this.list()
+    },
+    methods: {
+      handleDel () {
+        this.$confirm('是否删除', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.IdList = []
+          for (let i = 0; i < this.temp.length; i++) {
+            this.IdList.push(this.temp[i].id)
+          }
+          bannerDetailApi.delete({idList: this.IdList}).then(data => {
+            this.list()
+          })
+        }).catch(() => {
+          this.$message({type: 'info', message: '已取消删除'})
+        })
+      },
+      toEdit () {
+        this.$router.push({name: 'BannerEdit'})
+      },
+      handleEdit (index, row) {
+        this.$router.push({name: 'BannerEdit', params: {key: 'bannerId', msgKey: row.id}})
+      },
+      handleSelectionChange (val) {
+        this.temp = val
+      },
+      list () {
+        this.tableData = []
+        bannerDetailApi.page(this.searchForm).then(data => {
+          this.total = data.totalCount
+          this.tableData = data.list
+        })
+        bannerControlApi.findAll().then(data => {
+          this.bannerData = data
+        })
+      },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.searchForm.page = val
+        this.list()
+      },
+      forward (row) {
+        bannerDetailApi.forward(row.id).then(data => {
+          this.list()
+        })
+      },
+      backward (row) {
+        bannerDetailApi.backward(row.id).then(data => {
+          this.list()
+        })
+      },
+      changeBannerOn (row) {
+        bannerDetailApi.changeBannerOn(row.id).then(data => {
+          this.list()
+        })
+      },
+      changeSpace () {
+        bannerControlApi.ChangeSpace(this.bannerData[1].value).then(data => {
+          this.list()
+        })
+      },
+      moveDirection () {
+        bannerControlApi.moveDirection().then(data => {
+          this.list()
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-radio {
+    margin-right: 0px;
+  }
+  /deep/ .el-table th>.cell{
+    font-family: MicrosoftYaHei-Bold;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row el-table__row--striped {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__total {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .el-table td, .el-table th.is-leaf{
+    border: 0px;
+  }
+  /deep/ .el-pagination__total{
+    margin-left: 17px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump{
+    margin-left: 15px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-radio__inner{
+    width: 16px;
+    height: 16px;
+  }
+  /deep/ .el-radio__inner::after{
+    width: 8px;
+    height: 8px;
+  }
+</style>

+ 184 - 0
src/views/courseIntroduce/edit.vue

@@ -0,0 +1,184 @@
+<template>
+  <el-row>
+    <el-row style="padding-top: 50px;width: 1500px;padding-left: 30px">
+      <el-form ref="form" :model="form" :rules="formRules" label-width="120px">
+        <el-row>
+          <el-col :span="12">
+            <el-row>
+              <el-form-item label="标题" prop="courseTitle">
+                <el-input v-model="form.courseTitle" placeholder="在这里输入标题" show-word-limit maxlength="40"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row>
+              <el-form-item label="简介" prop="intro" style="margin-top: 5px">
+                <el-input v-model="form.intro" type="textarea" :rows="7" placeholder="在这里输入简介" show-word-limit maxlength="300"></el-input>
+              </el-form-item>
+            </el-row>
+          </el-col>
+          <el-col :span="12">
+            <el-row>
+              <el-col  :span="12">
+              <el-form-item label="缩略图" prop="imageUrl">
+                <material-dialog v-model="form.imageUrl" sourceType="课程介绍"></material-dialog>
+              </el-form-item>
+              </el-col>
+              <el-col  :span="12">
+                <div style="height: 131px"><img :src="OSS_URL + form.imageUrl" style="height: 131px;width: 248px;border-radius: 4px"></div>
+              </el-col>
+            </el-row>
+          </el-col>
+        </el-row>
+        <el-row>
+          <el-form-item label="内容" style="margin-top: 10px">
+            <quill-editor ref="myTextEditor" v-model="form.content" :options="editorOption" style="height: 300px;"></quill-editor>
+          </el-form-item>
+        </el-row>
+      </el-form>
+    </el-row>
+
+    <el-row style="padding: 50px 150px;text-align: left;">
+      <el-button type="primary" @click="handleSave">保存</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toPortalDetail">预览</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="checkboxrelease">发布</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toBack">取消</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import materialDialog from '@/views/material/dialog'
+  import courseIntroduceApi from '@/api/courseIntroduceApi'
+
+  export default {
+    name: 'dModeld',
+    components: {
+      'material-dialog': materialDialog
+    },
+    data () {
+      return {
+        form: {
+          saveMark: 0,
+          id: '',
+          courseTitle: '',
+          content: '',
+          courseUrl: '',
+          intro: '',
+          imageUrl: '',
+          releaseStatue: 0,
+          pushIndex: 0,
+          secondTop: 0
+        },
+        formRules: {
+          courseTitle: { required: true, message: '请输入标题', trigger: 'blur' },
+          intro: { required: true, message: '请输入简介', trigger: 'blur' },
+          imageUrl: { required: true, message: '请选择缩略图', trigger: 'blur' }
+        },
+        editorOption: {
+          placeholder: '请输入内容'
+        }
+      }
+    },
+    mounted () {
+      this.form['id'] = this.$route.params.msgKey
+      this.findById()
+    },
+    methods: {
+      toBack () {
+        this.$confirm('是否取消创建/修改文章?,并退出编辑页面', {
+          confirmButtonText: '继续退出',
+          cancelButtonText: '留下',
+          type: 'primary'
+        }).then(() => {
+          this.$router.push({name: 'CourseIntroduce'})
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已退出编辑页面'
+          })
+        })
+      },
+      handleSave () {
+        this.$refs['form'].validate((valid) => {
+          if (!valid) {
+            return false
+          }
+        this.$confirm('是否马上保存文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+            courseIntroduceApi.save(this.form).then(data => {
+              this.saveMark = 1
+          })
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+        })
+      },
+      checkboxrelease () {
+        this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+          if (this.form['id'] !== undefined | this.saveMark === 1) {
+            courseIntroduceApi.checkboxrelease(this.form['id']).then(data => {
+              this.list()
+            })
+          } else {
+            this.$message.error('请保存后再进行发布')
+          }
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+      },
+      toPortalDetail () {
+        if (this.form['id'] !== undefined) {
+          window.open(`${this.PORTAL_URL}/#/main/course/${this.form['id']}`, '_blank')
+        } else {
+          this.$message.error('请保存后再进行预览')
+        }
+      },
+      findById () {
+        if (this.form['id']) {
+          courseIntroduceApi.findById(this.form['id']).then(data => {
+            this.form['id'] = data.id
+            this.form['courseTitle'] = data.courseTitle
+            this.form['content'] = data.content
+            this.form['courseUrl'] = data.course_url
+            this.form['intro'] = data.intro
+            this.form['imageUrl'] = data.imageUrl
+            this.form['releaseStatue'] = data.releaseStatue
+            this.form['pushIndex'] = data.pushIndex
+            this.form['secondTop'] = data.secondTop
+          })
+        }
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-icon-circle-plus {
+    font-size: 50px;
+  }
+  /deep/ .el-dialog {
+    margin-top: 10vh
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+</style>

+ 346 - 0
src/views/courseIntroduce/list.vue

@@ -0,0 +1,346 @@
+<template>
+  <el-row style="padding-top: 50px;padding-left: 40px">
+    <el-row >
+      <el-form>
+        <el-col style="text-align: left"><el-input v-model.trim="searchForm.searchKey" style="width: 184px;height: 32px" placeholder="关键字"></el-input>
+          <el-button type="primary" @click="list()" style="margin-left: 15px">搜索</el-button>
+          <el-button type="primary" @click="canelSearch()" style="margin-left: 15px">清除搜索</el-button></el-col>
+      </el-form>
+    </el-row>
+    <el-row style="margin-top: 20px">
+      <white-board style="margin-right: 50px;padding: 0px 0px 0px 0px;height: 714px">
+        <el-row style="padding-top: 30px">
+          <el-col  style="margin-left: 29px;text-align: left">
+            <el-button type="primary" @click="toEdit" style="">新建</el-button>
+            <el-button type="primary" @click="handleDel" style="margin-left: 1370px">删除选中</el-button>
+          </el-col>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-left: 3px">
+          <el-table :data="tableData" row-key="id" ref="table" style="width: 100%;" @selection-change="handleSelectionChange" highlight-current-row stripe>
+            <el-table-column type="selection" width="55" align="center"></el-table-column>
+            <el-table-column prop="seqencing" label="序号" width="100" align="center"></el-table-column>
+            <el-table-column width="280" prop="courseTitle" label="标题" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column width="200" label="缩略图"  align="center">
+              <template slot-scope="scope">
+                <div style="height: 88px"><img :src="OSS_URL + scope.row.imageUrl" v-if="scope.row.imageUrl" style="height: 88px;width: 147px;"></div>
+              </template>
+            </el-table-column>
+            <el-table-column label="发布"  style="margin-top: 15px" width="200" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.releaseStatue" style="text-align: left;">
+                  <el-row>
+                  <el-radio :label="1" @change="checkboxrelease(scope.row)">是</el-radio>
+                  </el-row>
+                  <el-row>
+                  <el-radio :label="0" style="margin-top: 15px" @change="checkboxnorelease(scope.row)">否</el-radio>
+                  </el-row>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column label="强推首页" width="150" align="center">
+              <template slot-scope="scope">
+                <el-row>
+                <el-col :span="5">
+                  <el-checkbox v-model="scope.row.pushIndex" @change="pushChange(scope.row)" :true-label="1" style="padding-top: 3px"></el-checkbox>
+                </el-col>
+                <el-col :span="19" v-if="scope.row.pushIndex === 1">
+                  <el-select v-model="scope.row.seqencing" placeholder="请选择" @change="changeSeqencing(scope.row)">
+                    <el-option
+                      v-for="item in options"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value">
+                    </el-option>
+                  </el-select>
+                </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+            <el-table-column label="二级置顶" width="250" align="center">
+              <template slot-scope="scope"><el-checkbox v-model="scope.row.secondTop" @change="changSecondTop(scope.row)" :true-label="1"></el-checkbox></template>
+            </el-table-column>
+            <el-table-column prop="gmtCreated" label="创建日期" width="200" align="center"></el-table-column>
+            <el-table-column width="150" align="center">
+              <template slot-scope="scope">
+                <el-row>
+                  <el-col :span="12">
+                <el-button type="text" style="color: red" @click="toPortalDetail(scope.row)" plain>预览</el-button>
+                  </el-col>
+                  <el-col :span="12">
+                <el-button type="text" style="color: red" plain @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-bottom: 20px">
+          <el-pagination  @current-change="changeCurrentChange" :current-page="searchForm.page"  :page-size="5" layout="prev, pager, next, jumper, total" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import courseIntroduceApi from '@/api/courseIntroduceApi'
+
+  export default {
+    name: 'courseIntroduce',
+    components: {
+    },
+    data () {
+      return {
+        lessData: '',
+        searchForm: {
+          searchKey: '',
+          page: 1,
+          size: 5
+        },
+        total: 0,
+        tableData: [],
+        options: [{
+          value: '1',
+          label: '1'
+        }, {
+          value: '2',
+          label: '2'
+        }, {
+          value: '3',
+          label: '3'
+        }, {
+          value: '4',
+          label: '4'
+        }],
+        temp: [],
+        IdList: []
+      }
+    },
+    mounted () {
+      this.list()
+    },
+    methods: {
+      handleDel () {
+        this.$confirm('是否删除文章', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'primary'
+        }).then(() => {
+          this.IdList = []
+          for (let i = 0; i < this.temp.length; i++) {
+            this.IdList.push(this.temp[i].id)
+          }
+          courseIntroduceApi.delete({idList: this.IdList}).then(data => {
+            this.list()
+          })
+        }).catch(() => {
+          this.$message({type: 'info', message: '已取消删除'})
+        })
+      },
+      toPortalDetail (row) {
+        window.open(`${this.PORTAL_URL}/#/main/course/${row.id}`, '_blank')
+      },
+      toEdit () {
+        this.$router.push({name: 'CourseIntroduceEdit'})
+      },
+      handleEdit (index, row) {
+        this.$router.push({name: 'CourseIntroduceEdit', params: {key: 'crouseId', msgKey: row.id}})
+      },
+      handleSelectionChange (val) {
+        this.temp = val
+      },
+      canelSearch () {
+        this.searchForm['searchKey'] = ''
+        this.list()
+      },
+      list () {
+        this.tableData = []
+        courseIntroduceApi.page(this.searchForm).then(data => {
+          this.searchForm.page = data.pageIndex
+          this.total = data.totalCount
+          this.tableData = data.list
+        })
+      },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.init()
+        this.searchForm.page = val
+        this.list()
+      },
+      forward (row) {
+        courseIntroduceApi.forward(row.id).then(data => {
+          this.list()
+        })
+      },
+      backward (row) {
+        courseIntroduceApi.backward(row.id).then(data => {
+          this.list()
+        })
+      },
+      init () {
+        if (this.searchForm.page === 1) {
+          this.lessData = 0
+          for (var item of this.tableData) {
+            if (item.pushIndex === 1) {
+              this.lessData += 1
+            }
+          }
+        }
+      },
+      changeSeqencing (row) {
+        courseIntroduceApi.changeSeq(row.id, row.seqencing).then(data => {
+          this.list()
+        })
+      },
+      checkboxrelease (row) {
+        courseIntroduceApi.checkboxrelease(row.id).then(data => {
+          this.list()
+        })
+      },
+      checkboxnorelease (row) {
+        if (row.pushIndex === 1) {
+          this.$message.error('取消发布前必须取消强推首页')
+          this.list()
+        } else {
+        courseIntroduceApi.checkboxnorelease(row.id).then(data => {
+          this.list()
+        })
+          }
+      },
+      pushChange (row) {
+        if (row.releaseStatue !== 1) {
+          this.$message.error('强推首页前必须发布')
+          this.list()
+        } else {
+          if (this.searchForm.page === 1) {
+            var localLessData = 0
+            for (var item of this.tableData) {
+              if (item.pushIndex === 1) {
+                localLessData += 1
+              }
+            }
+            if (localLessData < 5) {
+              courseIntroduceApi.changePushIndex(row.id).then(data => {
+                this.list()
+              })
+            } else {
+              this.$message.error('强推首页不能有大于4条记录')
+              this.list()
+            }
+          } else {
+            if (this.lessData + 1 < 5) {
+              courseIntroduceApi.changePushIndex(row.id).then(data => {
+                this.list()
+              })
+            } else {
+              this.$message.error('强推首页不能有大于4条记录')
+              this.list()
+            }
+          }
+        }
+      },
+      changSecondTop (row) {
+        courseIntroduceApi.changeSecondTop(row.id).then(data => {
+          this.list()
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-radio {
+    margin-right: 0px;
+  }
+  /deep/ .el-table th>.cell{
+    font-family: MicrosoftYaHei-Bold;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row el-table__row--striped {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__total {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .el-table td, .el-table th.is-leaf{
+    border: 0px;
+  }
+  /deep/ .el-pagination__total{
+    margin-left: 17px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump{
+    margin-left: 15px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-radio__inner{
+    width: 16px;
+    height: 16px;
+  }
+  /deep/ .el-radio__inner::after{
+    width: 8px;
+    height: 8px;
+  }
+</style>

+ 85 - 0
src/views/exception/403.vue

@@ -0,0 +1,85 @@
+<template>
+  <div>
+      <el-header style="height: 75px;padding-top: 5px">
+        <div style="width: 200px; height: 60px; float: left;margin-left: 300px">
+          <img src="../../assets/logo.png" width="223" height="47" style="display: inline-block; vertical-align: middle; padding-left: 30px;"/>
+        </div>
+      </el-header>
+    <el-row style="min-height:100%;margin-bottom:-70px;">
+      <div :style ="note">
+        <div style=" position: fixed;top: 48%;left: 40%;">
+          <el-row style="padding: 15px 15px 15px 15px;background-color: white;box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);">
+          <el-row>
+          <label style="color: red;font-size: 20px;">对不起,您没有权限进入本管理后台</label>
+          </el-row>
+          <el-row style="margin-top: 8px;color: #848484">
+            <span >页面将在 </span>{{this.total}}<span> 秒后跳转</span>
+          </el-row>
+          </el-row>
+        </div>
+      </div>
+    </el-row>
+
+    <el-footer height="100px" style="padding: 0 0 0 0 ">
+      <el-row style="text-align: center; height: 100px; background-color: #E4EFF5;">
+        <label
+          style="font-family: 'Microsoft Yahei'; font-size: 12px; color: #000; letter-spacing: 1px; line-height: 105px;">Copyright(C)1997-2019 中国南方航空股份有限公司 版权所有</label>
+      </el-row>
+    </el-footer>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'page403',
+    components: {},
+    data () {
+      return {
+        note: {
+          backgroundImage: 'url(' + require('../../assets/403bg.png') + ')',
+          height: '1080px',
+          width: '1920px',
+          marginBottom: '130px',
+          position: 'fixed',
+          zIndex: '-1'
+        },
+        total: 5,
+        timer: null
+      }
+    },
+    mounted () {
+      this.timer = window.setInterval(() => {
+        this.total--
+        if (this.total === 0) {
+          this.toLogin()
+        }
+      }, 1000)
+    },
+    methods: {
+      toLogin () {
+        window.location.href = `${this.LOGIN_URL}/#/login`
+      }
+    }
+  }
+</script>
+
+<style>
+  .el-header  {
+    background-color: #ffffff;
+    color: #333;
+    text-align: center;
+    line-height: 60px;
+    padding-left: 0px;
+    padding-right: 0px;
+  }
+
+  .el-footer {
+    position: absolute;
+    bottom: 0;
+    width:100%;
+  }
+  html,body{
+    height:100%;
+  }
+
+</style>

+ 20 - 0
src/views/login.vue

@@ -0,0 +1,20 @@
+<template>
+  <div>login</div>
+</template>
+
+<script>
+  export default {
+    name: 'Login',
+    components: {},
+    data () {
+      return {}
+    },
+    mounted () {
+    },
+    methods: {}
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 20 - 0
src/views/logout.vue

@@ -0,0 +1,20 @@
+<template>
+  <div>logout</div>
+</template>
+
+<script>
+  export default {
+    name: 'Logout',
+    components: {},
+    data () {
+      return {}
+    },
+    mounted () {
+    },
+    methods: {}
+  }
+</script>
+
+<style scoped>
+
+</style>

+ 134 - 0
src/views/main.vue

@@ -0,0 +1,134 @@
+<template>
+  <el-container>
+    <el-header style="height: 80px;width: 1920px;padding :0 0 0 0">
+      <div style="width: 200px; height: 80px; float: left;">
+        <img src="../assets/logo.png" width="184px" height="39px" style="display: inline-block; vertical-align: middle; padding-left: 13px;padding-top: 21px;cursor: pointer;" @click="jumpToPortal"/>
+      </div>
+
+      <div style="width: 200px;padding-top: 12px; float: left; font-weight:bold; margin-left: 50px;">
+        <span style="color: #008ac9;font-size: 20px">南航门户后台管理系统</span>
+      </div>
+
+      <div style="width: 100px; float: right;cursor: pointer;margin-right: 50px;padding-top: 12px">
+        <span style="width: 100px;height: 14px;font-family: MicrosoftYaHei;font-size: 14px;font-weight: normal;font-stretch: normal;line-height: 48px;letter-spacing: 1px;color: #4a5162;">SHAW-小岩</span>
+      </div>
+
+      <div style=" float: right; cursor: pointer;width: 25px;height: 25px;padding-top: 20px;padding-left: 5px;padding-right: 3px"  @click="jumpToPortal">
+       <img src="@/assets/head-logo.png" style="width: 25px;height: 25px"/>
+      </div>
+
+      <div style="padding-right: 27px;padding-top: 10px;width: 58px; float: right;cursor: pointer;height: 14px">
+        <el-button type="text" @click="toLogout" style="width: 58px;height: 14px;font-family: MicrosoftYaHei;font-size: 14px;font-weight: normal;font-stretch: normal;line-height: 48px;letter-spacing: 1px;color: #474e5f;">退出登录</el-button>
+      </div>
+
+    </el-header>
+
+    <el-container>
+      <el-aside width="200px">
+        <AsideMenu :collapse="isAsideMenuCollapse"></AsideMenu>
+      </el-aside>
+
+      <el-main :style="{height: (screenHeight - 80) + 'px', padding: 0 + 'px ' + 10 + 'px', paddingTop: 10 + 'px'}">
+        <transition name="el-fade-in-linear">
+          <router-view />
+        </transition>
+      </el-main>
+    </el-container>
+  </el-container>
+</template>
+
+<script>
+  import AsideMenu from '@/components/layout/aside-menu'
+  import logoutApi from '@/api/logoutApi'
+
+  export default {
+    name: 'Main',
+    components: {
+      AsideMenu
+    },
+    data () {
+      return {
+        screenHeight: document.documentElement.clientHeight, // 获取可视屏幕高度
+        isAsideMenuCollapse: false
+      }
+    },
+    watch: {
+      screenHeight (val) {
+        // 为了避免频繁触发resize函数导致页面卡顿,使用定时器
+        if (!this.timer) {
+          // 一旦监听到的screenWidth值改变,就将其重新赋给data里的screenWidth
+          this.screenHeight = val
+          this.timer = true
+          let that = this
+          setTimeout(function () {
+            that.timer = false
+          }, 400)
+        }
+      }
+    },
+    mounted () {
+      const that = this
+      window.onresize = () => {
+        return (() => {
+          window.screenHeight = document.documentElement.clientHeight
+          that.screenHeight = window.screenHeight
+        })()
+      }
+    },
+    methods: {
+
+      toLogout () {
+        logoutApi.logout().then(data => {
+          localStorage.removeItem('token')
+          window.location.href = `${this.LOGIN_URL}/#/login`
+        })
+      },
+      jumpToPortal () {
+        window.location.href = `${this.PORTAL_URL}/#/index`
+      }
+    }
+  }
+</script>
+
+<style>
+
+  .el-header, .el-footer {
+    background-color: #ffffff;
+    color: #333;
+    text-align: center;
+    line-height: 60px;
+    padding-left: 0px;
+    padding-right: 0px;
+  }
+
+  .el-footer {
+    height: 40px !important;
+    line-height: 40px !important;
+  }
+
+  .el-aside {
+    background-color: #fff;
+    color: #333;
+    text-align: center;
+  }
+
+  .el-main {
+    background-color: #eee;
+    color: #333;
+    text-align: center;
+    padding: 0px;
+  }
+
+  body > .el-container {
+    margin-bottom: 40px;
+  }
+
+  .el-container:nth-child(5) .el-aside,
+  .el-container:nth-child(6) .el-aside {
+    line-height: 260px;
+  }
+
+  .el-container:nth-child(7) .el-aside {
+    line-height: 320px;
+  }
+</style>

+ 63 - 0
src/views/material/dialog.vue

@@ -0,0 +1,63 @@
+<template>
+  <el-row>
+    <div @click="dialogVisible = true">
+      <el-card  shadow="hover" style="height: 131px;text-align: center;cursor: pointer;border-radius: 4px">
+        <el-row style="top: 23px;"><img src="@/assets/tianjia.png" style="width: 33px;height: 33px"></el-row>
+        <el-row style="top: 17px;"><label style="width: 91px;height: 14px;font-family: MicrosoftYaHei;font-size: 14px;font-weight: normal;font-stretch: normal;line-height: 24px;letter-spacing: 1px;color: #858585;">点击添加图片</label></el-row>
+      </el-card>
+     <!-- <el-image v-else :src="OSS_URL + value" fit="fill" style="width: 100%;height: 184px;cursor: pointer;border-radius: 4px"></el-image>-->
+    </div>
+
+    <el-dialog width="85%" :visible.sync="dialogVisible">
+      <material-list :sourceType="sourceType" @selected="selected"></material-list>
+
+      <span slot="footer" class="dialog-footer">
+      <el-button @click="dialogVisible = false">取 消</el-button>
+      <el-button type="primary" @click="onClickSelected">确 定</el-button>
+    </span>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script>
+  import materialList from './list'
+
+  export default {
+    name: 'MaterialDialog',
+    components: {
+      'material-list': materialList
+    },
+    props: {
+      value: { // 图片的path
+        type: String,
+        default: ''
+      },
+      sourceType: {
+        type: String,
+        default: ''
+      }
+    },
+    data () {
+      return {
+        dialogVisible: false,
+        selectedList: []
+      }
+    },
+    methods: {
+      selected (itemList) {
+        this.selectedList = []
+        this.selectedList = itemList
+      },
+      onClickSelected () {
+        if (this.selectedList.length !== 1) {
+          this.$message.warning('只能选中一个')
+          return
+        }
+
+        this.$emit('selected', this.selectedList[0])
+        this.$emit('input', this.selectedList[0].path)
+        this.dialogVisible = false
+      }
+    }
+  }
+</script>

+ 283 - 0
src/views/material/list.vue

@@ -0,0 +1,283 @@
+<template>
+  <el-row style="padding-top: 50px;padding-left: 40px">
+    <el-row >
+      <el-form>
+        <el-col style="text-align: left"><el-input v-model.trim="searchForm.searchKey" style="width: 184px;height: 32px" placeholder="关键字"></el-input>
+          <el-button type="primary" @click="list()" style="margin-left: 20px">搜索</el-button>
+          <el-button type="primary" @click="canelSearch()" style="margin-left: 15px">清除搜索</el-button></el-col>
+      </el-form>
+    </el-row>
+    <el-row style="margin-top: 20px">
+      <white-board style="margin-right: 50px;padding: 0px 0px 0px 0px;height: 650px;border-radius: 6px;">
+        <el-row v-if="sourceType === ''" style="padding-top: 20px">
+          <el-col :span="16">
+            <el-row>
+              <el-col :span="2" style="line-height: 28px;">模块</el-col>
+              <el-col :span="2">
+                <el-checkbox-button :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox-button>
+              </el-col>
+              <el-col :span="19" :offset="1" style="text-align: left;">
+                <el-checkbox-group v-model="checkedSourceType" @change="handleCheckedSourceTypeChange">
+                  <el-checkbox-button v-for="sourceType in sourceTypeList" :label="sourceType" :key="sourceType">{{sourceType}}</el-checkbox-button>
+                </el-checkbox-group>
+              </el-col>
+            </el-row>
+          </el-col>
+          <el-col :span="2" :offset="2">
+            <el-upload
+                :action="OSS_URL + '/image/upload'"
+                :data="imageForm"
+                :show-file-list="false"
+                :before-upload="handleBeforeUpload"
+                :on-success="handleSuccess"
+                :headers="{'authorization' : this.token}"
+            >
+              <el-button  type="primary">上传</el-button>
+            </el-upload>
+          </el-col>
+          <el-col :span="2">
+            <el-button type="primary" @click="handleAllCheck">{{isAllSelected ? '全部取消' : '全部选中'}}</el-button>
+          </el-col>
+          <el-col :span="2" >
+            <el-button type="primary" @click="handleDel">删除选中</el-button>
+          </el-col>
+        </el-row>
+
+        <el-row style="padding-top: 15px;padding-bottom: 15px"><div style="width: 100%;border-top:1px solid #e9e9e9;height: 1px"></div></el-row>
+
+        <el-row :gutter="12" style="margin: auto;margin-left: 200px">
+          <el-col :span="4" v-for="(item, index) in tableData" :key="index" style="margin: 5px;">
+            <div @click="onClickCard(item)" style="width: 201px;height: 216px">
+              <el-card shadow="hover" style="cursor: pointer;">
+                <el-row style="text-align: right;"><el-checkbox v-model="item.isSelected" @change="onClickCard(item)"></el-checkbox></el-row>
+                <el-row><img @click.stop="onClickImage(item)" :src="OSS_URL + item.path" fit="fill" style="width: 100%;height: 100px;" /></el-row>
+                <el-row style="text-align: center;padding-top: 20px;line-height: 20px;word-break: break-all;text-overflow: ellipsis;overflow: hidden;display: -webkit-box;-webkit-line-clamp: 1;-webkit-box-orient: vertical;">
+                  <el-tooltip effect="dark" placement="top">
+                    <div slot="content">
+                      <label>文件名:{{item.name}}</label><br />
+                      <label>类型:{{item.type === 1 ? '图片' : '视频'}}</label>
+                    </div>
+                    <label>{{item.name}}</label>
+                  </el-tooltip>
+                </el-row>
+                <el-row style="text-align: center;font-size: 12px;padding-top: 4px;color: #DCDFE6;"><label>({{item.size | bytes}})</label></el-row>
+              </el-card>
+            </div>
+          </el-col>
+        </el-row>
+
+        <el-row style="padding-top: 30px;padding-bottom: 20px">
+          <el-pagination @size-change="changeSizeChange" @current-change="changeCurrentChange" :current-page="searchForm.page" :page-sizes="[10, 20, 50, 100]" :page-size="10" layout="prev, pager, next, jumper, total" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+
+    <el-dialog :title="image.name" width="40%" :visible.sync="imageDialogTableVisible">
+      <el-image :src="OSS_URL + image.path" fit="fill" style="width: 100%;"></el-image>
+    </el-dialog>
+  </el-row>
+</template>
+
+<script>
+  import materialApi from '@/api/materialApi'
+  const sourceTypeOptions = ['新闻资讯', '课程介绍', '全景图/3D模型', 'banner']
+  const sourceTypeOptionsMap = {
+    '新闻资讯': 'news',
+    '课程介绍': 'course',
+    '全景图/3D模型': 'vr3d',
+    'banner': 'banner'
+  }
+  export default {
+    name: 'Material',
+    components: {
+    },
+    props: {
+      sourceType: { // 模块类型
+        type: String,
+        default: ''
+      }
+    },
+    data () {
+      return {
+        searchForm: {
+          searchKey: '',
+          sourceType: '', // 筛选类型 1或者空为全选 news为新闻 course为课程 vr3d为全景图/3D模型 banner为banner
+          page: 1,
+          size: 10
+        },
+        total: 0,
+        tableData: [],
+        imageForm: {
+          sourceType: '',
+          type: ''
+        },
+        token: '',
+        checkAll: false,
+        isIndeterminate: true,
+        checkedSourceType: [],
+        sourceTypeList: sourceTypeOptions,
+
+        isAllSelected: false,
+        imageDialogTableVisible: false,
+        image: {
+          name: '',
+          path: ''
+        }
+      }
+    },
+    mounted () {
+      this.token = 'Bearer ' + localStorage.getItem('token')
+      if (this.sourceType) {
+        this.checkedSourceType.push(this.sourceType)
+      }
+
+      this.list()
+    },
+    methods: {
+      onClickImage (item) {
+        this.imageDialogTableVisible = true
+        this.image = item
+      },
+      handleCheckAllChange (val) {
+        this.checkedSourceType = val ? sourceTypeOptions : []
+        this.isIndeterminate = false
+        this.list()
+      },
+      handleCheckedSourceTypeChange (value) {
+        this.searchForm.page = 1
+        let checkedCount = value.length
+        this.checkAll = checkedCount === this.sourceTypeList.length
+        this.isIndeterminate = checkedCount > 0 && checkedCount < this.sourceTypeList.length
+        this.list()
+      },
+      handleBeforeUpload (file) {
+        if (this.checkedSourceType.length !== 1) {
+          this.$message.warning('只能选中一个模块进行上传')
+          return false
+        }
+        const isLt2M = file.size / 1024 / 1024 < 5
+        if (!isLt2M) {
+          this.$message.error('上传图片大小不能超过 5MB!')
+          return false
+        }
+
+        this.imageForm['sourceType'] = sourceTypeOptionsMap[this.checkedSourceType[0]]
+        this.imageForm['type'] = 1 // todo
+      },
+      handleSuccess (response, file, fileList) {
+        this.list()
+      },
+      handleAllCheck () {
+        this.isAllSelected = !this.isAllSelected
+        this.tableData.map(item => { item.isSelected = this.isAllSelected })
+        this.throwSelected()
+      },
+      handleDel () {
+        this.$confirm('是否删除', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          let idList = this.tableData.filter(item => item.isSelected === true).map(item => item.id)
+          materialApi.delete({idList: idList}).then(data => {
+            this.list()
+          })
+        }).catch(() => {
+          this.$message({type: 'info', message: '已取消删除'})
+        })
+      },
+      onClickCard (item) {
+        item.isSelected = !item.isSelected
+        this.throwSelected()
+      },
+      throwSelected () {
+        this.$emit('selected', this.tableData.filter(item => item.isSelected === true))
+      },
+      canelSearch () {
+        this.searchForm['searchKey'] = ''
+        this.list()
+      },
+      list () {
+        this.tableData = []
+        let sourceTypeList = []
+        for (let sourceType of this.checkedSourceType) {
+          sourceTypeList.push(sourceTypeOptionsMap[sourceType])
+        }
+        this.searchForm['sourceType'] = sourceTypeList.join(',')
+        materialApi.page(this.searchForm).then(data => {
+          this.total = data.totalCount
+          data.list.map(item => {
+            item.isSelected = false
+          })
+          this.tableData = data.list
+        })
+      },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.searchForm.page = val
+        this.list()
+      }
+    }
+  }
+</script>
+
+<style>
+  .el-card__body {
+    padding: 10px;
+  }
+
+</style>
+<style scoped>
+ /deep/ .el-table th>.cell{
+   width: 111px;
+   height: 15px;
+   font-family: MicrosoftYaHei;
+   font-size: 14px;
+   font-weight: bold;
+   font-stretch: normal;
+   line-height: 42px;
+   letter-spacing: 1px;
+   color: #333333;
+  }
+ /deep/ .el-pagination__total{
+   margin-left: 17px;
+   font-family: MicrosoftYaHei;
+   font-size: 14px;
+   font-weight: normal;
+   font-stretch: normal;
+   line-height: 42px;
+   letter-spacing: 0px;
+   color: #333333;
+ }
+ /deep/ .el-pagination__jump{
+   margin-left: 15px;
+   font-family: MicrosoftYaHei;
+   font-size: 14px;
+   font-weight: normal;
+   font-stretch: normal;
+   line-height: 42px;
+   letter-spacing: 0px;
+   color: #333333;
+ }
+ /deep/ .el-radio__inner{
+   width: 16px;
+   height: 16px;
+ }
+ /deep/ .el-radio__inner::after{
+   width: 8px;
+   height: 8px;
+ }
+ /deep/ .el-button--primary {
+   background-color: #2493e5;
+   border-color: #2493e5;
+ }
+ /deep/ .el-button--mini, .el-button--mini.is-round{
+   padding: 9px 19px;
+ }
+ /deep/ .el-input--mini .el-input__inner{
+   height: 32px;
+ }
+</style>

+ 243 - 0
src/views/news/edit.vue

@@ -0,0 +1,243 @@
+<template>
+  <el-row>
+    <el-row style="padding-top: 50px;width: 1500px;padding-left: 30px">
+      <el-form ref="form" :model="form" :rules="formRules" label-width="120px">
+        <el-row>
+          <el-col :span="12">
+            <el-row>
+              <el-form-item label="标题" prop="newsTitle">
+                <el-input v-model="form.newsTitle" placeholder="在这里输入标题" show-word-limit maxlength="40"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row>
+              <el-form-item label="简介" prop="intro" style="margin-top: 5px">
+                <el-input v-model="form.intro" type="textarea" :rows="7" placeholder="在这里输入简介" show-word-limit maxlength="300"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row style="margin-top: 20px">
+              <el-form-item label="网址">
+                <el-col :span="20"><el-input v-model="form.newUrl" placeholder="在这里粘贴文章地址"></el-input></el-col>
+                <el-col  :span="4"><el-button type="primary" @click="crawler">获取内容</el-button></el-col>
+              </el-form-item>
+            </el-row>
+          </el-col>
+          <el-col :span="12">
+            <el-row>
+              <el-row style="height: 131px">
+                <el-col  :span="12">
+                  <el-form-item label="缩略图" prop="imageUrl">
+                    <div @click="onClick">
+                    <material-dialog  v-model="form.imageUrl" sourceType="新闻资讯"></material-dialog>
+                    </div>
+                  </el-form-item>
+                </el-col>
+                <el-col  :span="12">
+                  <div style="height: 131px"><img :src="OSS_URL + form.imageUrl" style="height: 131px;width: 248px;border-radius: 4px"></div>
+                </el-col>
+              </el-row>
+            </el-row>
+            <el-row style="padding-top: 15px;height: 130px;">
+              <el-col :span="24"><img @click="changeBorder('1')" class="img1" src="../../assets/news2.png" style="margin-left: 56px;border-radius: 4px;box-sizing: border-box;width: 122px;height: 82px">
+              <img @click="changeBorder('2')" class="img2" src="../../assets/hangye2.png" style="margin-left: 18px;border-radius: 4px;box-sizing: border-box;width: 122px;height: 82px">
+              <img @click="changeBorder('3')" class="img3" src="../../assets/hangye.png"   style="margin-left: 19px;border-radius: 4px;box-sizing: border-box;width: 122px;height: 82px">
+            <img @click="changeBorder('4')" class="img4" src="../../assets/hangye.png"  style="margin-left: 19px;border-radius: 4px;box-sizing: border-box;width: 122px;height: 82px">
+          </el-col>
+            </el-row>
+          </el-col>
+        </el-row>
+        <el-row style="margin-top: 0px">
+          <el-form-item label="内容">
+            <quill-editor ref="myTextEditor" v-model="form.content" :options="editorOption" style="height: 300px;"></quill-editor>
+          </el-form-item>
+        </el-row>
+      </el-form>
+    </el-row>
+
+    <el-row style="padding: 50px 150px;text-align: left;">
+      <el-button type="primary" @click="handleSave">保存</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toPortalDetail">预览</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="checkboxrelease">发布</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toBack">取消</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import materialDialog from '@/views/material/dialog'
+  import newsApi from '@/api/newsApi'
+  import crawlerApi from '@/api/crawlerApi'
+
+  export default {
+    name: 'courseIntroduce',
+    components: {
+      'material-dialog': materialDialog
+    },
+    data () {
+      return {
+        saveMark: 0,
+        form: {
+          id: '',
+          newsTitle: '',
+          content: '',
+          newUrl: '',
+          intro: '',
+          imageUrl: '',
+          classtype: 1,
+          releaseStatue: 0,
+          pushIndex: 0,
+          secondTop: 0
+        },
+        formRules: {
+          newsTitle: { required: true, message: '请输入标题', trigger: 'blur' },
+          intro: { required: true, message: '请输入简介', trigger: 'blur' },
+          imageUrl: { required: true, message: '请选择缩略图', trigger: 'blur' }
+        },
+        editorOption: {
+          placeholder: '请输入内容'
+        }
+      }
+    },
+    mounted () {
+      this.form['id'] = this.$route.params.msgKey
+      this.findById()
+    },
+    methods: {
+      toBack () {
+        this.$confirm('是否取消创建/修改文章?,并退出编辑页面?', {
+          confirmButtonText: '继续退出',
+          cancelButtonText: '留下',
+          type: 'primary'
+        }).then(() => {
+          this.$router.push({name: 'News'})
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已退出编辑页面'
+          })
+        })
+      },
+      onClick () {
+        var d = document.getElementsByClassName('el-dialog')
+        d[0].style.marginTop = '10vh'
+        d[1].style.marginTop = '10vh'
+      },
+      crawler () {
+        if (this.form.newUrl === '') {
+          this.$message({
+            type: 'info',
+            message: '网址不能为空'
+          })
+        }
+        crawlerApi.crawler(this.form.newUrl).then(data => {
+          this.form.newsTitle = data.newsTitle
+          this.form.content = data.content
+          this.form.intro = data.intro
+        })
+      },
+      handleSave () {
+        this.$refs['form'].validate((valid) => {
+          if (!valid) {
+            return false
+          }
+        this.$confirm('是否马上保存文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+            newsApi.save(this.form).then(data => {
+              this.saveMark = 1
+          })
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+        })
+      },
+      changeBorder (temp) {
+        let img1 = document.getElementsByClassName('img1')
+        img1[0].style.marginLeft = '60px'
+        for (var i = 1; i < 5; i++) {
+          let img2 = document.getElementsByClassName('img' + i)
+          img2[0].style.border = ''
+        }
+       let img3 = document.getElementsByClassName('img' + temp)
+        img3[0].style.border = 'solid 4px #00afff'
+        if (temp === '1') {
+          this.form['imageUrl'] = '/vr_resource/images/news/static/news2.png'
+        }
+        if (temp === '2') {
+          this.form['imageUrl'] = '/vr_resource/images/news/static/hangye2.png'
+        }
+        if (temp === '3') {
+          this.form['imageUrl'] = '/vr_resource/images/news/static/news.png'
+        }
+        if (temp === '4') {
+          this.form['imageUrl'] = '/vr_resource/images/news/static/hangye.png'
+        }
+      },
+      toPortalDetail () {
+        if (this.form['id'] !== undefined) {
+          window.open(`${this.PORTAL_URL}/#/main/new/${this.form['id']}`, '_blank')
+        } else {
+          this.$message.error('请保存后再进行预览')
+        }
+      },
+      findById () {
+        if (this.form['id']) {
+          newsApi.findById(this.form['id']).then(data => {
+            this.form['id'] = data.id
+            this.form['newsTitle'] = data.newsTitle
+            this.form['content'] = data.content
+            this.form['newUrl'] = data.newUrl
+            this.form['intro'] = data.intro
+            this.form['imageUrl'] = data.imageUrl
+            this.form['classtype'] = data.classtype
+            this.form['releaseStatue'] = data.releaseStatue
+            this.form['pushIndex'] = data.pushIndex
+            this.form['secondTop'] = data.secondTop
+          })
+        }
+      },
+      checkboxrelease () {
+        this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+          if (this.form['id'] !== undefined | this.saveMark === 1) {
+            newsApi.checkboxrelease(this.form['id']).then(data => {
+              this.list()
+            })
+          } else {
+            this.$message.error('请保存后再进行发布')
+          }
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+      }
+    }
+  }
+</script>
+<style scoped>
+  .el-icon-circle-plus {
+    font-size: 50px;
+  }
+  /deep/ .el-dialog {
+    margin-top: 10vh
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+</style>

+ 366 - 0
src/views/news/list.vue

@@ -0,0 +1,366 @@
+<template>
+  <el-row style="padding-top: 50px;padding-left: 40px">
+
+    <el-row >
+      <el-form>
+        <el-col style="text-align: left"><el-input v-model.trim="searchForm.searchKey" style="width: 184px;height: 32px" placeholder="关键字"></el-input>
+          <el-button type="primary" @click="list()" style="margin-left: 15px">搜索</el-button>
+          <el-button type="primary" @click="canelSearch()" style="margin-left: 15px">清除搜索</el-button></el-col>
+      </el-form>
+    </el-row>
+
+    <el-row style="margin-top: 20px;">
+      <white-board style="margin-right: 50px;padding: 0px 0px 0px 0px;height: 714px;border-radius: 6px;">
+        <el-row style="padding-top: 30px">
+          <el-col  style="margin-left: 29px;text-align: left">
+            <el-button type="primary" @click="toEdit" style="">新建</el-button>
+            <el-button type="primary" @click="handleDel" style="margin-left: 1370px">删除选中</el-button>
+          </el-col>
+        </el-row>
+
+        <el-row style=" padding-top: 20px;padding-left: 3px">
+          <el-table :data="tableData" row-key="id" ref="table" style=" width: 100%;" @selection-change="handleSelectionChange" highlight-current-row stripe>
+            <el-table-column type="selection" width="55" align="center"></el-table-column>
+            <el-table-column prop="seqencing" label="序号" width="100" align="center" ></el-table-column>
+            <el-table-column width="250" prop="newsTitle" label="标题" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column label="缩略图" width="200" align="center">
+              <template slot-scope="scope">
+                <div style="height: 88px"><img :src="OSS_URL + scope.row.imageUrl" v-if="scope.row.imageUrl" style="height: 88px;width: 147px;"></div>
+              </template>
+            </el-table-column>
+            <el-table-column label="发布" width="150" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.releaseStatue" style="text-align: left;">
+                  <el-row>
+                  <el-radio :label="1" @change="checkboxrelease(scope.row)">是</el-radio>
+                  </el-row>
+                  <el-row>
+                  <el-radio :label="0"  style="margin-top: 15px" @change="checkboxnorelease(scope.row)">否</el-radio>
+                  </el-row>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column label="分类" width="130" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.classtype" style="text-align: left;">
+                  <el-radio :label="1" @change="changeClasstype(scope.row)">新闻咨询</el-radio>
+                  <el-radio :label="2" style="margin-top: 15px" @change="changeClasstype(scope.row)">行业信息</el-radio>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column label="强推首页" width="150" align="center">
+              <template slot-scope="scope">
+                <el-row>
+                  <el-col :span="5">
+                    <el-checkbox v-model="scope.row.pushIndex" @change="pushChange(scope.row)" :true-label="1" style="padding-top: 3px"></el-checkbox>
+                  </el-col>
+                  <el-col :span="19" v-if="scope.row.pushIndex === 1">
+                    <el-select v-model="scope.row.seqencing" placeholder="请选择" @change="changeSeqencing(scope.row)">
+                      <el-option
+                        v-for="item in options"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value">
+                      </el-option>
+                    </el-select>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+            <el-table-column label="二级置顶" width="200" align="center">
+              <template slot-scope="scope"><el-checkbox v-model="scope.row.secondTop" @change="changSecondTop(scope.row)" :true-label="1"></el-checkbox></template>
+            </el-table-column>
+            <el-table-column prop="gmtCreated" label="创建日期" width="200" align="center"></el-table-column>
+            <el-table-column width="150" align="center">
+              <template slot-scope="scope">
+                <el-row>
+                <el-col :span="12">
+                <el-button type="text" style="color: red" @click="toPortalDetail(scope.row)" plain>预览</el-button>
+                </el-col>
+                <el-col :span="12">
+                <el-button type="text" style="color: red" plain @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
+                </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-row>
+
+        <el-row style="padding-top: 25px;padding-bottom: 20px">
+          <el-pagination @size-change="changeSizeChange" @current-change="changeCurrentChange" :current-page="searchForm.page" :page-sizes="[10, 20, 50, 100]" :page-size="5" layout="prev, pager, next, jumper, total" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import newsApi from '@/api/newsApi'
+
+  export default {
+    name: 'Newsindex',
+    components: {
+    },
+    data () {
+      return {
+        lessData: 0,
+        searchForm: {
+          searchKey: '',
+          page: 1,
+          size: 5
+        },
+        total: 0,
+        tableData: [],
+        options: [{
+          value: '1',
+          label: '1'
+        }, {
+          value: '2',
+          label: '2'
+        }, {
+          value: '3',
+          label: '3'
+        }],
+        temp: [],
+        IdList: []
+      }
+    },
+    mounted () {
+      this.list()
+    },
+    methods: {
+      handleDel () {
+        this.$confirm('是否删除文章', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'primary'
+        }).then(() => {
+          this.IdList = []
+          for (let i = 0; i < this.temp.length; i++) {
+            this.IdList.push(this.temp[i].id)
+          }
+          newsApi.delete({idList: this.IdList}).then(data => {
+            this.list()
+          })
+        }).catch(() => {
+          this.$message({type: 'info', message: '已取消删除'})
+        })
+      },
+      toPortalDetail (row) {
+        window.open(`${this.PORTAL_URL}/#/main/new/${row.id}`, '_blank')
+      },
+      toEdit () {
+        this.$router.push({name: 'NewsEdit'})
+      },
+      handleEdit (index, row) {
+        this.$router.push({name: 'NewsEdit', params: {key: 'newId', msgKey: row.id}})
+      },
+      handleSelectionChange (val) {
+        this.temp = val
+      },
+      canelSearch () {
+        this.searchForm['searchKey'] = ''
+        this.list()
+      },
+      list () {
+        this.tableData = []
+        newsApi.page(this.searchForm).then(data => {
+          this.total = data.totalCount
+          this.searchForm.page = data.pageIndex
+          this.tableData = data.list
+        })
+      },
+      init () {
+        if (this.searchForm.page === 1) {
+          this.lessData = 0
+          for (var item of this.tableData) {
+            if (item.pushIndex === 1) {
+              this.lessData += 1
+            }
+          }
+        }
+      },
+      changeSeqencing (row) {
+        newsApi.changeSeq(row.id, row.seqencing).then(data => {
+          this.list()
+        })
+      },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.init()
+        this.searchForm.page = val
+        this.list()
+      },
+      forward (row) {
+        newsApi.forward(row.id).then(data => {
+          this.list()
+        })
+      },
+      backward (row) {
+        newsApi.backward(row.id).then(data => {
+          this.list()
+        })
+      },
+      checkboxrelease (row) {
+        newsApi.checkboxrelease(row.id).then(data => {
+          this.list()
+        })
+      },
+      checkboxnorelease (row) {
+        if (row.pushIndex === 1) {
+          this.$message.error('取消发布前必须取消强推首页')
+          this.list()
+        } else {
+          newsApi.checkboxnorelease(row.id).then(data => {
+            this.list()
+          })
+        }
+      },
+      pushChange (row) {
+        if (row.releaseStatue !== 1) {
+          this.$message.error('强推首页前必须发布')
+          this.list()
+        } else {
+        if (this.searchForm.page === 1) {
+          var localLessData = 0
+          for (var item of this.tableData) {
+            if (item.pushIndex === 1) {
+              localLessData += 1
+            }
+          }
+          if (localLessData < 4) {
+            newsApi.changePushIndex(row.id).then(data => {
+              this.list()
+            })
+          } else {
+            this.$message.error('强推首页不能有大于3条记录')
+            this.list()
+          }
+        } else {
+          if (this.lessData + 1 < 4) {
+            newsApi.changePushIndex(row.id).then(data => {
+              this.list()
+            })
+          } else {
+            this.$message.error('强推首页不能有大于3条记录')
+            this.list()
+          }
+        }
+        }
+      },
+      changSecondTop (row) {
+        newsApi.changeSecondTop(row.id).then(data => {
+          this.list()
+        })
+      },
+      changeClasstype (row) {
+        newsApi.changeClasstype(row.id).then(data => {
+          this.list()
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-radio {
+    margin-right: 0px;
+  }
+  /deep/ .el-table th>.cell{
+    font-family: MicrosoftYaHei-Bold;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row el-table__row--striped {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+/*    background-color: rgba(233,244,252,0.3);*/
+  }
+  /deep/ .el-pagination__total {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .el-table td, .el-table th.is-leaf{
+    border: 0px;
+  }
+  /deep/ .el-pagination__total{
+    margin-left: 17px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump{
+    margin-left: 15px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row el-table__row--striped{
+    background-color: #ffffff;
+  }
+  /deep/ .el-radio__input.is-checked .el-radio__inner{
+    border-color: #2493e5;
+    background: #2493e5;
+  }
+  /deep/ .el-radio__inner{
+    width: 16px;
+    height: 16px;
+  }
+  /deep/ .el-radio__inner::after{
+    width: 8px;
+    height: 8px;
+  }
+</style>

+ 249 - 0
src/views/vr-panorama/edit.vue

@@ -0,0 +1,249 @@
+<template>
+  <el-row>
+    <el-row style="padding-top: 50px;width: 1500px;padding-left: 30px">
+      <el-form ref="form" :model="form" :rules="formRules" label-width="120px">
+        <el-row>
+          <el-col :span="12">
+            <el-row>
+              <el-form-item label="标题" prop="vrTitle">
+                <el-input v-model="form.vrTitle" placeholder="在这里输入标题" show-word-limit maxlength="40"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row>
+              <el-form-item label="简介" prop="intro">
+                <el-input v-model="form.intro" type="textarea" :rows="7" placeholder="在这里输入简介" show-word-limit maxlength="300"></el-input>
+              </el-form-item>
+            </el-row>
+            <el-row style="width: 232px;margin-top: 10px">
+              <el-form-item label="二维码" prop="qrcodeUrl">
+                <el-upload
+                  drag
+                  class="avatar-qrcode-uploader"
+                  :action="action"
+                  :data="fileform"
+                  :before-upload="beforeqrcodeUpload"
+                  :on-success="qrcodeSuccess"
+                  :show-file-list="false"
+                  :headers="{'authorization' : this.token}"
+                >
+                  <img v-if="form.qrcodeUrl !== ''" width="100%" :src="imgQrcodeUrl" alt="">
+                  <el-row>
+                    <img v-if="form.qrcodeUrl === ''" src="@/assets/tianjia.png" style="width: 33px;height: 33px;padding-top: 80px">
+                  </el-row>
+                  <el-row>
+                    <label v-if="form.qrcodeUrl === ''" style="width: 106px;height: 14px;font-family: MicrosoftYaHei;font-size: 14px;font-weight: normal;font-stretch: normal;line-height: 24px;letter-spacing: 1px;color: #858585;">点击添加二维码</label>
+                  </el-row>
+                </el-upload>
+
+              </el-form-item>
+            </el-row>
+          </el-col>
+          <el-col :span="12">
+            <el-row>
+              <el-col  :span="12">
+              <el-form-item label="缩略图" prop="imageUrl">
+                <material-dialog v-model="form.imageUrl" sourceType="全景图/3D模型"></material-dialog>
+              </el-form-item>
+              </el-col>
+              <el-col  :span="12">
+                <div style="height: 131px"><img :src="OSS_URL + form.imageUrl" style="height: 131px;width: 248px;border-radius: 4px"></div>
+              </el-col>
+            </el-row>
+          </el-col>
+        </el-row>
+      </el-form>
+    </el-row>
+    <el-row style="padding: 0px 150px;text-align: left;">
+      <el-button type="primary" @click="handleSave">保存</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toOpenZip">预览</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="checkboxrelease">发布</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+      <el-button type="primary" @click="toBack">取消</el-button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import materialDialog from '@/views/material/dialog'
+  import vrPanoramaApi from '@/api/vrPanoramaApi'
+
+  export default {
+    name: 'dModeld',
+    components: {
+      'material-dialog': materialDialog
+    },
+    data () {
+      return {
+        form: {
+          id: '',
+          saveMark: 0,
+          vrTitle: '',
+          intro: '',
+          imageUrl: '',
+          releaseStatue: 0,
+          pushIndex: 0,
+          secondTop: 0,
+          qrcodeUrl: '',
+          zipUrl: ''
+        },
+        token: '',
+        imgQrcodeUrl: '',
+        action: `${this.OSS_URL}/file/upload`,
+        fileform: {
+          sourceType: ''
+        },
+        formRules: {
+          vrTitle: { required: true, message: '请输入标题', trigger: 'blur' },
+          intro: { required: true, message: '请输入简介', trigger: 'blur' },
+          imageUrl: { required: true, message: '请选择缩略图', trigger: 'blur' }
+        }
+      }
+    },
+    mounted () {
+      this.token = 'Bearer ' + localStorage.getItem('token')
+      this.form['id'] = this.$route.params.msgKey
+      this.findById()
+    },
+    methods: {
+      toOpenZip (index, row) {
+        if (this.form['id'] !== undefined) {
+          window.open(`${this.YUN_URL}${this.form['zipUrl']}`, '_blank')
+        } else {
+          this.$message.error('请保存后再进行预览')
+        }
+      },
+      checkboxrelease () {
+        this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+          if (this.form['id'] !== undefined | this.saveMark === 1) {
+            vrPanoramaApi.checkboxrelease(this.form['id']).then(data => {
+              this.list()
+            })
+          } else {
+            this.$message.error('请保存后再进行发布')
+          }
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+      },
+      toBack () {
+        this.$confirm('是否取消创建/修改文章?,并退出编辑页面', {
+          confirmButtonText: '继续退出',
+          cancelButtonText: '留下',
+          type: 'primary'
+        }).then(() => {
+          this.$router.push({name: 'vrPanorama'})
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已退出编辑页面'
+          })
+        })
+      },
+      handleSave () {
+        this.$refs['form'].validate((valid) => {
+          if (!valid) {
+            return false
+          }
+        this.$confirm('是否马上发布文章?', {
+          confirmButtonText: '是',
+          cancelButtonText: '否',
+          type: 'primary'
+        }).then(() => {
+            vrPanoramaApi.save(this.form).then(data => {
+              this.saveMark = 1
+          })
+        }).catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消发布'
+          })
+        })
+        })
+      },
+      findById () {
+        if (this.form['id']) {
+          vrPanoramaApi.findById(this.form['id']).then(data => {
+            this.form['id'] = data.id
+            this.form['vrTitle'] = data.vrTitle
+            this.form['intro'] = data.intro
+            this.form['imageUrl'] = data.imageUrl
+            this.form['releaseStatue'] = data.releaseStatue
+            this.form['pushIndex'] = data.pushIndex
+            this.form['secondTop'] = data.secondTop
+            this.form['qrcodeUrl'] = data.qrcodeUrl
+            this.form['zipUrl'] = data.zipUrl
+            this.imgQrcodeUrl = `${this.OSS_URL}` + data.qrcodeUrl
+          })
+        }
+      },
+      beforeqrcodeUpload (file) {
+        var i = 0
+        const type = file.type
+        if (type === 'image/png') {
+          i = 1
+        }
+        if (type === 'image/jpeg') {
+          i = 1
+        }
+        let isJPG = true // todo 临时增加
+        const isLt2M = file.size / 1024 / 1024 < 10
+        if (i === 0) {
+          this.$message.error('上传头像图片格式非法')
+          return false
+        }
+        if (!isLt2M) {
+          this.$message.error('上传头像图片大小不能超过 10MB!')
+          return false
+        }
+        this.fileform.sourceType = 'vr3d'
+        return isJPG && isLt2M
+      },
+      qrcodeSuccess (data) {
+        this.form['qrcodeUrl'] = data.data
+        this.imgQrcodeUrl = `${this.OSS_URL}` + data.data
+      }
+    }
+  }
+</script>
+<style>
+  .el-icon-circle-plus {
+    font-size: 50px;
+  }
+
+  .avatar-qrcode-uploader .el-upload-dragger {
+    height: 150px;
+    width: 150px;
+  }
+
+  .avatar-zip-uploader .el-upload-dragger {
+    height: 50px;
+    width: 150px;
+    background-color: #eeeeee;
+    border: 0px;
+  }
+</style>
+<style scoped>
+  /deep/ .el-dialog {
+    margin-top: 10vh
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .avatar-qrcode-uploader .el-upload-dragger{
+    width: 232px;
+    height: 232px;
+  }
+</style>

+ 436 - 0
src/views/vr-panorama/list.vue

@@ -0,0 +1,436 @@
+<template>
+  <el-row style="padding-top: 50px;padding-left: 40px"> <el-row>
+    <el-form>
+      <el-col style="text-align: left"><el-input v-model.trim="searchForm.searchKey" style="width: 184px;height: 32px" placeholder="关键字"></el-input>
+        <el-button type="primary" @click="list()" style="margin-left: 15px">搜索</el-button>
+        <el-button type="primary" @click="canelSearch()" style="margin-left: 15px">清除搜索</el-button></el-col>
+    </el-form>
+  </el-row>
+
+    <el-row style="margin-top: 20px">
+      <white-board style="margin-right: 50px;padding: 0px 0px 0px 0px;height: 714px;border-radius: 6px;">
+        <el-row style="padding-top: 30px">
+          <el-col  style="margin-left: 29px;text-align: left">
+            <el-button type="primary" @click="toEdit" style="float: left;">新建</el-button>
+                <el-upload
+                  drag
+                  class="avatar-zip-uploader"
+                  ref="upload"
+                  :action="action"
+                  :before-upload="beforezipUpload"
+                  :data="fileform"
+                  :on-success="zipSuccess"
+                  :show-file-list="false"
+                  :headers="{'authorization' : this.token}"
+                  style="float: left;margin-left: 15px"
+                >
+                  <el-button  type="primary">上传</el-button>
+                </el-upload>
+            <el-button type="primary" @click="handleDel" style="margin-left: 1300px">删除选中</el-button>
+          </el-col>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-left: 3px">
+          <el-table :data="tableData" row-key="id" ref="table" style="width: 100%;" @selection-change="handleSelectionChange" highlight-current-row stripe>
+            <el-table-column type="selection" width="55" align="center"></el-table-column>
+            <el-table-column prop="seqencing" label="序号" width="100" align="center"></el-table-column>
+            <el-table-column width="300" prop="vrTitle" label="标题" align="center" show-overflow-tooltip></el-table-column>
+            <el-table-column label="缩略图" width="200" align="center">
+              <template slot-scope="scope">
+                <div style="height: 88px"><img :src="OSS_URL + scope.row.imageUrl" v-if="scope.row.imageUrl" style="height: 88px;width: 147px;"></div>
+              </template>
+            </el-table-column>
+            <el-table-column label="发布" width="150" align="center">
+              <template slot-scope="scope">
+                <el-radio-group v-model="scope.row.releaseStatue" style="text-align: left;">
+                  <el-row>
+                  <el-radio :label="1" @change="checkboxrelease(scope.row)">是</el-radio>
+                  </el-row>
+                  <el-row>
+                  <el-radio :label="0" style="margin-top: 15px" @change="checkboxnorelease(scope.row)">否</el-radio>
+                  </el-row>
+                </el-radio-group>
+              </template>
+            </el-table-column>
+            <el-table-column label="强推首页" width="150" align="center">
+              <template slot-scope="scope">
+                <el-row>
+                  <el-col :span="5">
+                    <el-checkbox v-model="scope.row.pushIndex" @change="pushChange(scope.row)" :true-label="1" style="padding-top: 3px"></el-checkbox>
+                  </el-col>
+                  <el-col :span="19" v-if="scope.row.pushIndex === 1">
+                    <el-select v-model="scope.row.seqencing" placeholder="请选择" @change="changeSeqencing(scope.row)">
+                      <el-option
+                        v-for="item in options"
+                        :key="item.value"
+                        :label="item.label"
+                        :value="item.value">
+                      </el-option>
+                    </el-select>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+            <el-table-column label="二级置顶" width="180" align="center">
+              <template slot-scope="scope"><el-checkbox v-model="scope.row.secondTop" @change="changSecondTop(scope.row)" :true-label="1"></el-checkbox></template>
+            </el-table-column>
+            <el-table-column label="是否上传" width="100" align="center">
+              <template slot-scope="scope"><el-checkbox   :checked="scope.row.zipUrl === ''?  false : scope.row.zipUrl === null ? false : true "></el-checkbox></template>
+            </el-table-column>
+            <el-table-column prop="gmtCreated" label="创建日期" width="200" align="center"></el-table-column>
+            <el-table-column width="150" align="center">
+              <template slot-scope="scope">
+                <el-row style="text-align: left;">
+                  <el-col :span="12">
+                  <el-button type="text" style="color: red;" plain @click="toOpenZip(scope.$index, scope.row)">预览</el-button>
+                  </el-col>
+                  <el-col :span="12">
+                  <el-button type="text" style="color: red;" plain @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
+                  </el-col>
+                </el-row>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-row>
+
+        <el-row style="padding-top: 20px;padding-bottom: 20px">
+          <el-pagination  @current-change="changeCurrentChange" :current-page="searchForm.page" :page-sizes="[10, 20, 50, 100]" :page-size="5" layout=" prev, pager, next, jumper, total" :total="total" small></el-pagination>
+        </el-row>
+      </white-board>
+    </el-row>
+  </el-row>
+</template>
+
+<script>
+  import vrPanoramaApi from '@/api/vrPanoramaApi'
+
+  export default {
+    name: 'vrPanoramaIndex',
+    components: {
+    },
+    data () {
+      return {
+        lessData: '',
+        searchForm: {
+          searchKey: '',
+          page: 1,
+          size: 5
+        },
+        fileform: {
+          sourceType: 'zip'
+        },
+        form: {
+          id: '',
+          saveMark: 0,
+          vrTitle: '',
+          intro: '',
+          imageUrl: '',
+          releaseStatue: 0,
+          pushIndex: 0,
+          secondTop: 0,
+          qrcodeUrl: '',
+          zipUrl: ''
+        },
+        token: '',
+        zipUrl: '',
+        action: `${this.OSS_URL}/file/upload`,
+        total: 0,
+        tableData: [],
+        options: [{
+          value: '1',
+          label: '1'
+        }, {
+          value: '2',
+          label: '2'
+        }, {
+          value: '3',
+          label: '3'
+        }, {
+          value: '4',
+          label: '4'
+        }],
+        temp: [],
+        IdList: []
+      }
+    },
+    mounted () {
+      this.token = 'Bearer ' + localStorage.getItem('token')
+      this.list()
+    },
+    methods: {
+      beforezipUpload (file) {
+        if (this.temp.length === 0) {
+          this.$message.error('请先勾选要上传的数据')
+          return false
+        }
+        if (this.temp.length > 1) {
+          this.$message.error('只能同时上传一列')
+          return false
+        }
+        const type = file.type
+        let isJPG = true // todo 临时增加
+        const isLt2M = file.size / 1024 / 1024 < 1024
+        if (type !== 'application/x-zip-compressed') {
+          this.$message.error('上传文件格式非法')
+          return false
+        }
+        if (!isLt2M) {
+          this.$message.error('上传大小不能超过 1G!')
+          return false
+        }
+        return isJPG && isLt2M
+      },
+    zipSuccess (data) {
+      this.form.id = this.temp[0].id
+      this.form['vrTitle'] = this.temp[0].vrTitle
+      this.form['intro'] = this.temp[0].intro
+      this.form['imageUrl'] = this.temp[0].imageUrl
+      this.form['releaseStatue'] = this.temp[0].releaseStatue
+      this.form['pushIndex'] = this.temp[0].pushIndex
+      this.form['secondTop'] = this.temp[0].secondTop
+      this.form['qrcodeUrl'] = this.temp[0].qrcodeUrl
+      this.form.zipUrl = data.data
+      vrPanoramaApi.save(this.form).then(data => {
+        this.$message.success('上传成功')
+        this.list()
+      })
+    },
+      onCopy () {
+        this.$message.success('复制成功!')
+      },
+      onError () {
+        this.$message.error('复制失败!')
+      },
+      handleDel () {
+        this.$confirm('是否删除文章', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'primary'
+        }).then(() => {
+          this.IdList = []
+          for (let i = 0; i < this.temp.length; i++) {
+            this.IdList.push(this.temp[i].id)
+          }
+          vrPanoramaApi.delete({idList: this.IdList}).then(data => {
+            this.list()
+          })
+        }).catch(() => {
+          this.$message({type: 'info', message: '已取消删除'})
+        })
+      },
+      init () {
+        if (this.searchForm.page === 1) {
+          this.lessData = 0
+          for (var item of this.tableData) {
+            if (item.pushIndex === 1) {
+              this.lessData += 1
+            }
+          }
+        }
+      },
+      toEdit () {
+        this.$router.push({name: 'vrPanoramaEdit'})
+      },
+      handleEdit (index, row) {
+        this.$router.push({name: 'vrPanoramaEdit', params: {key: 'vrPanoramaId', msgKey: row.id}})
+      },
+      handleSelectionChange (val) {
+        this.temp = val
+      },
+      canelSearch () {
+        this.searchForm['searchKey'] = ''
+        this.list()
+      },
+      list () {
+        this.tableData = []
+        vrPanoramaApi.page(this.searchForm).then(data => {
+          this.total = data.totalCount
+          this.searchForm.page = data.pageIndex
+          this.tableData = data.list
+        })
+      },
+      changeSizeChange (val) {
+        this.searchForm.size = val
+        this.list()
+      },
+      changeCurrentChange (val) {
+        this.init()
+        this.searchForm.page = val
+        this.list()
+      },
+      forward (row) {
+        vrPanoramaApi.forward(row.id).then(data => {
+          this.list()
+        })
+      },
+      backward (row) {
+        vrPanoramaApi.backward(row.id).then(data => {
+          this.list()
+        })
+      },
+      changeSeqencing (row) {
+        vrPanoramaApi.changeSeq(row.id, row.seqencing).then(data => {
+          this.list()
+        })
+      },
+      checkboxrelease (row) {
+        vrPanoramaApi.checkboxrelease(row.id).then(data => {
+          this.list()
+        })
+      },
+      checkboxnorelease (row) {
+        if (row.pushIndex === 1) {
+          this.$message.error('取消发布前必须取消强推首页')
+          this.list()
+        } else {
+          vrPanoramaApi.checkboxnorelease(row.id).then(data => {
+            this.list()
+          })
+        }
+      },
+      pushChange (row) {
+        if (row.releaseStatue !== 1) {
+          this.$message.error('强推首页前必须发布')
+          this.list()
+        } else {
+          if (this.searchForm.page === 1) {
+            var localLessData = 0
+            for (var item of this.tableData) {
+              if (item.pushIndex === 1) {
+                localLessData += 1
+              }
+            }
+            if (localLessData < 5) {
+              vrPanoramaApi.changePushIndex(row.id).then(data => {
+                this.list()
+              })
+            } else {
+              this.$message.error('强推首页不能有大于4条记录')
+              this.list()
+            }
+          } else {
+            if (this.lessData + 1 < 5) {
+              vrPanoramaApi.changePushIndex(row.id).then(data => {
+                this.list()
+              })
+            } else {
+              this.$message.error('强推首页不能有大于4条记录')
+              this.list()
+            }
+          }
+        }
+      },
+      changSecondTop (row) {
+        vrPanoramaApi.changeSecondTop(row.id).then(data => {
+          this.list()
+        })
+      },
+      toOpenZip (index, row) {
+        window.open(`${this.YUN_URL}${row.zipUrl}`, '_blank')
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .el-radio {
+    margin-right: 0px;
+  }
+  /deep/ .el-table th>.cell{
+    font-family: MicrosoftYaHei-Bold;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row el-table__row--striped {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-table__row {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__total {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump {
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-upload-dragger{
+    border: 0px;
+    width: 57px;
+    height: 28px;
+    border-radius: 3px
+  }
+  /deep/ .avatar-zip-uploader .el-upload-dragger{
+    background-color: #ffffff;
+    width: 64px;
+    height: 32px;
+  }
+  /deep/ .el-button--primary {
+    background-color: #2493e5;
+    border-color: #2493e5;
+  }
+  /deep/ .el-button--mini, .el-button--mini.is-round{
+    padding: 9px 19px;
+  }
+  /deep/ .el-input--mini .el-input__inner{
+    height: 32px;
+  }
+  /deep/ .el-table td, .el-table th.is-leaf{
+    border: 0px;
+  }
+  /deep/ .el-pagination__total{
+    margin-left: 17px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-pagination__jump{
+    margin-left: 15px;
+    font-family: MicrosoftYaHei;
+    font-size: 14px;
+    font-weight: normal;
+    font-stretch: normal;
+    line-height: 42px;
+    letter-spacing: 0px;
+    color: #333333;
+  }
+  /deep/ .el-radio__inner{
+    width: 16px;
+    height: 16px;
+  }
+  /deep/ .el-radio__inner::after{
+    width: 8px;
+    height: 8px;
+  }
+</style>

+ 0 - 0
static/.gitkeep


Деякі файли не було показано, через те що забагато файлів було змінено