Explorar o código

'初始配置'

zhangyu %!s(int64=4) %!d(string=hai) anos
achega
4ed86052dd
Modificáronse 59 ficheiros con 5255 adicións e 0 borrados
  1. 12 0
      .babelrc
  2. 9 0
      .editorconfig
  3. 15 0
      .gitignore
  4. 10 0
      .postcssrc.js
  5. 155 0
      README.md
  6. 45 0
      build/build.js
  7. 56 0
      build/check-versions.js
  8. BIN=BIN
      build/logo.png
  9. 100 0
      build/utils.js
  10. 20 0
      build/vue-loader.conf.js
  11. 99 0
      build/webpack.base.conf.js
  12. 93 0
      build/webpack.dev.conf.js
  13. 140 0
      build/webpack.prod.conf.js
  14. 9 0
      config/dev.env.js
  15. 84 0
      config/index.js
  16. 6 0
      config/prod.env.js
  17. 15 0
      index.html
  18. 82 0
      package.json
  19. 39 0
      publish.js
  20. 15 0
      src/App.vue
  21. 110 0
      src/api/framework.js
  22. 107 0
      src/api/httputils.js
  23. 1 0
      src/api/scan/config.js
  24. 41 0
      src/api/scan/fetch.js
  25. 54 0
      src/api/scan/httpUtil.js
  26. 27 0
      src/api/scan/request.js
  27. BIN=BIN
      src/assets/logo.png
  28. 96 0
      src/assets/scss/reset.scss
  29. 489 0
      src/assets/style/iconfont/iconfont.css
  30. BIN=BIN
      src/assets/style/iconfont/iconfont.eot
  31. 380 0
      src/assets/style/iconfont/iconfont.svg
  32. BIN=BIN
      src/assets/style/iconfont/iconfont.ttf
  33. BIN=BIN
      src/assets/style/iconfont/iconfont.woff
  34. 100 0
      src/assets/style/style.scss
  35. 38 0
      src/components/HelloWorld.vue
  36. 242 0
      src/components/model/file/floorTable.vue
  37. 326 0
      src/data/menus.js
  38. 182 0
      src/framework/layout/Login.vue
  39. 87 0
      src/framework/layout/Main.vue
  40. 142 0
      src/framework/layout/PageHeader.vue
  41. 91 0
      src/framework/layout/PageSidebar.vue
  42. 18 0
      src/framework/style/awesome.less
  43. 424 0
      src/framework/style/layout.scss
  44. 106 0
      src/framework/style/theme.scss
  45. 30 0
      src/main.js
  46. 18 0
      src/router/index.js
  47. 30 0
      src/router/system.js
  48. 38 0
      src/store/index.js
  49. 153 0
      src/store/modules/layout-store.js
  50. 116 0
      src/utils/authutils.js
  51. 2 0
      src/utils/bus.js
  52. 381 0
      src/utils/getFirstLetter.js
  53. 33 0
      src/utils/localStorage.js
  54. 33 0
      src/utils/storage.js
  55. 64 0
      src/utils/tools.js
  56. 71 0
      src/views/dasboard/index.vue
  57. 221 0
      src/views/model/file/index.vue
  58. 0 0
      static/.gitkeep
  59. BIN=BIN
      static/favicon.ico

+ 12 - 0
.babelrc

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

+ 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

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+.DS_Store
+node_modules/
+/dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 10 - 0
.postcssrc.js

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

+ 155 - 0
README.md

@@ -0,0 +1,155 @@
+# sagacloud-admin
+
+> A Vue.js project
+
+## Build Setup
+
+```bash
+npm install
+npm install vuex axios element-ui echarts font-awesome
+npm install less less-loader node-sass sass-loader --save-dev
+
+# serve with hot reload at localhost:8080
+npm run dev
+# build for production with minification
+npm run build
+# build for production and view the bundle analyzer report
+npm run build --report
+```
+
+## 项目结构说明
+
+#### 目录结构
+
+```
+│  .editorconfig                        # 格式化配置,修改缩进为2
+│  index.html                           # 修改页面title
+│  package.json                         # 包
+│  README.md                            # 说明文档
+│
+├─build                                 # 不变
+│
+├─config
+│      dev.env.js                       # 开发环境变量
+│      index.js                         # 设置代理
+│      prod.env.js                      # 生产环境变量
+│
+├─src
+│  ├─api                                # 业务逻辑api, http的请求都放在这里
+│  │  |─framework.js                    # 框架预留接口,实现登录,权限校验等功能
+│  │  └─httputils.js                    # http 工具类
+│  ├─assets                             # 静态资源, 替换logo图片
+│  ├─components                         # 业务相关组件
+│  ├─data                               # 业务相关静态数据
+│  │      menus.js                      # 菜单数据
+│  ├─demo                               # Demo主目录
+│  │  │  demo-menus.js                  # Demo按钮
+│  │  │  demo-routes.js                 # Demo路由
+│  │  └─views                           # Demo页面
+│  ├─framework                          # 框架主目录
+│  │  ├─components                      # 业务无关组件
+│  │  │
+│  │  ├─demo                            # Demo主目录
+│  │  │  │  demo-menus.js               # Demo按钮
+│  │  │  │  demo-routes.js              # Demo路由
+│  │  │  └─views                        # Demo页面
+│  │  │
+│  │  ├─layout                          # 框架主目录
+│  │  │      layout-store.js            # 框架状态定义
+│  │  │      Login.vue                  # 登录页
+│  │  │      Main.vue                   # 框架主页
+│  │  │      PageHeader.vue             # 顶部栏
+│  │  │      PageSidebar.vue            # 左侧菜单栏
+│  │  │
+│  │  ├─plugins                         # 插件,业务无关
+│  │  │      components.js
+│  │  ├─style                           # 样式
+│  │  │
+│  │  ├─template                        # 页面模板
+│  │  │      TablePageTemplate.vue
+│  │  │
+│  │  └─utils                           # 工具类,业务无关
+│  │
+│  ├─router                             # 主路由
+│  │      index.js
+│  │      system.js
+│  │
+│  ├─store                              # vuex
+│  │      index.js
+│  │
+│  └─views                              # 业务页面主路径
+|
+└─static
+        .gitkeep
+```
+
+#### /api/framework.js
+
+必须实现以下方法
+
+```
+export default {
+
+    // 路由拦截
+    routerBeforeEach: async function(to, from, next){},
+
+    // 获取左侧菜单数据
+    getMenus: function(permissions){},
+
+    // 跳转到登录页面
+    toLoginPage: function(){},
+
+    // 登录接口
+    login: function(username, password){}
+
+    // 加载当前登录用户信息
+    loadUserInfo: function(){},
+
+    // 退出登录
+    toLogout: function(){}
+}
+
+```
+
+#### 工具类说明
+
+/src/utils/basicutils.js
+
+```
+    formatDateByPattern(date, pattern) // 日期格式化
+    indexInArray(arr, key, val)        // 找出数组arr中第一个属性key的值等于val的元素的下标
+    itemInArray(arr, key, val)         // 找出数组arr中第一个属性key的值等于val的元素
+    deleteInArray(arr, key, val)       // 从数组中将属性key的值等于val的所有元素删除
+```
+
+/src/api/httputils.js // 一般在服务端接口 API 中使用,不建议在别的地方使用,有授权标记
+
+```
+    getCookie(name)                    // 获取cookie值
+    getJson(url, params)               // 发送GET请求
+    postJson(url, data)                // 发送POST请求
+```
+
+/src/utils/storage.js
+
+```
+    set(key, value, fn)                // 在浏览器缓存中设置数据, 如果value是对象会转化成json
+    get(key, fn)                       // 在浏览器缓存中取出数据, 取出数据后会尝试js对象返回
+    remove(key)                        // 在浏览器缓存中删除数据
+```
+
+#### 设置面包屑方法
+
+```
+在路由router中配置meta.breadcrumbs,在进入路由页面时会自动解析为面包屑
+this.$store.dispatch('setBreadcrumb', [{ label: 'Demo' }, { label: 'Form'  ])
+this.$store.dispatch('setBreadcrumb', [{ label: 'Demo', path: '' }, { label: 'Form' , path: ''}])
+```
+#### 上传管理器调用方法(上传较大文件时使用)
+
+```
+在组件中引入Bus中央时间总线
+    Bus.$emit('openUploader', query, file)    //添加文件到上传管理器中, query为参数, file为H5原生的文件对象
+    Bus.$on('fileAdded', () => {})            //文件选择后的回调
+    Bus.$on('fileSuccess', () => {})          //文件上传成功后的回调
+```

+ 45 - 0
build/build.js

@@ -0,0 +1,45 @@
+'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"
+            )
+        )
+    })
+})

+ 56 - 0
build/check-versions.js

@@ -0,0 +1,56 @@
+'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=BIN
build/logo.png


+ 100 - 0
build/utils.js

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

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

@@ -0,0 +1,20 @@
+'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'
+    }
+}

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

@@ -0,0 +1,99 @@
+'use strict'
+const path = require('path')
+const utils = require('./utils')
+const config = require('../config')
+const vueLoaderConfig = require('./vue-loader.conf')
+const webpack = require('webpack')
+
+function resolve(dir) {
+    return path.join(__dirname, '..', dir)
+}
+
+
+
+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: ['.ts', '.js', '.vue', '.json'],
+        alias: {
+            'vue$': 'vue/dist/vue.esm.js',
+            '@': resolve('src'),
+            'jquery': 'jquery',
+        }
+    },
+    // 增加一个plugins
+    plugins: [
+        new webpack.ProvidePlugin({
+            $: "jquery",
+            jQuery: "jquery"
+        })
+    ],
+    module: {
+        rules: [
+            {
+                test: /\.vue$/,
+                loader: 'vue-loader',
+                options: vueLoaderConfig
+            },
+            {
+                test: /\.tsx?$/,
+                loader: 'ts-loader',
+                exclude: /node_modules/,
+                options: {
+                  appendTsSuffixTo: [/\.vue$/],
+                }
+            },
+            {
+                test: /\.js$/,
+                loader: 'babel-loader',
+                include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
+            },
+            {
+                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+                loader: 'url-loader',
+                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'
+    }
+}

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

@@ -0,0 +1,93 @@
+'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,
+            favicon: path.resolve(__dirname, '../static/favicon.ico')
+        }),
+        // 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)
+        }
+    })
+})

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

@@ -0,0 +1,140 @@
+'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',
+            favicon: path.resolve(__dirname, '../static/favicon.ico')
+        }),
+        // 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

+ 9 - 0
config/dev.env.js

@@ -0,0 +1,9 @@
+'use strict'
+const merge = require('webpack-merge')
+const prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+    NODE_ENV: '"development"',
+    SSO_SERVER: '"http://172.16.44.235:8081"', //测试环境
+    // SSO_SERVER: '"http://sso.sagacloud.cn"',  //正式环境
+})

+ 84 - 0
config/index.js

@@ -0,0 +1,84 @@
+'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: {
+        '/api': {
+            target: 'http://172.16.44.235:8080',
+            changeOrigin: true,
+            pathRewrite: {
+                "^/api": "/"
+            }
+        },
+        '/image-service': {
+            // 目标 API 地址
+            target: 'http://172.16.42.210:8080/',
+            // 如果要代理 websockets
+            ws: true,
+            // 将主机标头的原点更改为目标URL
+            changeOrigin: false
+        },
+    },
+
+    // Various Dev Server settings
+    host: '0.0.0.0', // can be overwritten by process.env.HOST
+    port: 9099, // 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-
+
+    /**
+     * 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
+  }
+}

+ 6 - 0
config/prod.env.js

@@ -0,0 +1,6 @@
+'use strict'
+module.exports = {
+    NODE_ENV: '"production"',
+    SSO_SERVER: '"http://172.16.44.235:8081"', //测试环境
+    // SSO_SERVER: '"http://sso.sagacloud.cn"',  //正式环境
+}

+ 15 - 0
index.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
+    <link rel="stylesheet" type="text/css" href="//at.alicdn.com/t/font_1318214_i7toym6dora.css">
+    <title>产品管理平台</title>
+</head>
+
+<body>
+    <div id="app"></div>
+</body>
+
+</html>

+ 82 - 0
package.json

@@ -0,0 +1,82 @@
+{
+  "name": "pmd-admin",
+  "version": "1.0.0",
+  "description": "A Vue.js project",
+  "author": "zy <zhangyu@sagacloud.cn>",
+  "private": true,
+  "remote": {
+    "host": "192.168.20.236",
+    "path": "/opt/nginx/ibms/dist",
+    "user": "root",
+    "password": "saga",
+    "local": "dist"
+  },
+  "scripts": {
+    "i": "npm install",
+    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
+    "start": "npm run dev",
+    "build": "node build/build.js",
+    "publish": "node publish.js"
+  },
+  "dependencies": {
+    "axios": "^0.18.0",
+    "element-ui": "^2.13.1",
+    "font-awesome": "^4.7.0",
+    "vue": "^2.5.2",
+    "vue-axios": "^2.1.4",
+    "vue-router": "^3.0.1",
+    "vuex": "^3.1.0"
+  },
+  "devDependencies": {
+    "ajv": "^6.9.1",
+    "autoprefixer": "^7.1.2",
+    "babel-core": "^6.22.1",
+    "babel-helper-vue-jsx-merge-props": "^2.0.3",
+    "babel-loader": "^7.1.1",
+    "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",
+    "extract-text-webpack-plugin": "^3.0.0",
+    "file-loader": "^1.1.4",
+    "file-saver": "^2.0.2",
+    "friendly-errors-webpack-plugin": "^1.6.1",
+    "html-webpack-plugin": "^2.30.1",
+    "less": "^3.9.0",
+    "less-loader": "^4.1.0",
+    "node-notifier": "^5.1.2",
+    "node-sass": "^4.11.0",
+    "node-ssh": "^6.0.0",
+    "optimize-css-assets-webpack-plugin": "^3.2.0",
+    "ora": "^1.2.0",
+    "portfinder": "^1.0.13",
+    "postcss-import": "^11.0.0",
+    "postcss-loader": "^2.0.8",
+    "postcss-url": "^7.2.1",
+    "rimraf": "^2.6.0",
+    "sass-loader": "^7.1.0",
+    "semver": "^5.3.0",
+    "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"
+  ]
+}

+ 39 - 0
publish.js

@@ -0,0 +1,39 @@
+/*
+ * @Author: zhangyu
+ * @Date: 2019-12-18 16:18:30
+ * @Info: 自动化部署
+ * @LastEditTime: 2020-05-06 14:48:27
+ */
+
+const Client = require("node-ssh");
+const ssh = new Client(); 
+
+ssh.connect({
+  host: process.env.npm_package_remote_host,
+  port: "22",
+  username: process.env.npm_package_remote_user,
+  password: process.env.npm_package_remote_password
+}).then(() => {
+  const failedList = [];
+  ssh.putDirectory(
+    process.env.npm_package_remote_local,
+    process.env.npm_package_remote_path,
+    {
+      recursive: true,
+      concurrency: 1,
+      tick: function(localPath, remotePath, error) {
+        if (error) {
+          failedList.push(localPath);
+        }
+      }
+    }
+  ).then(status => {
+    if (failedList.length > 0) {
+      console.log("发布失败");
+      console.log("failed transfers", failedList.join(", "));
+    } else {
+      console.log(status ? "发布成功" : "发布失败");
+    }
+    ssh.dispose();
+  });
+});

+ 15 - 0
src/App.vue

@@ -0,0 +1,15 @@
+<template>
+    <div id='app'>
+        <router-view/>
+    </div>
+</template>
+
+<script>
+
+export default {
+    name: 'App',
+    components: {
+        
+    },
+}
+</script>

+ 110 - 0
src/api/framework.js

@@ -0,0 +1,110 @@
+import store from '@/store'
+import menus from '@/data/menus'
+import router from '@/router'
+import httputils from '@/api/httputils'
+// const userInfo = { username: 'admin', permissions: ['demo'] }
+
+const userInfo = null
+function toLogin() {
+    router.push('/login')
+}
+
+function checkMenu(menu, ps) {
+    let result = { name: menu.name, icon: menu.icon, path: menu.path, disabled: menu.disabled }
+    if (menu.children) {
+        // 如果有下级菜单权限,则自动拥有上级菜单权限
+        result.children = []
+        menu.children.forEach(child => {
+            let submenu = checkMenu(child, ps)
+            if (submenu) {
+                result.children.push(submenu)
+            }
+        })
+        return result.children.length > 0 ? result : null
+    } else if (menu.opts) {
+        return menu.opts.some(opt => ps[opt.permission]) ? result : null
+    } else {
+        // 如果没有下级菜单且没有opts属性, 菜单可以直接访问,不需要权限
+        return result
+    }
+}
+
+export default {
+    /**
+     *  路由守卫, 每次路由跳转时验证登录
+     * @param {*} to
+     * @param {*} from
+     * @param {*} next
+     */
+    routerBeforeEach: async function(to, from, next) {
+        console.log('router before ', to)
+        if (to.path == '/auth' || to.path == '/login') {
+            next()
+        } else {
+            if (to.meta.breadcrumbs) {
+                store.dispatch('setBreadcrumb', to.meta.breadcrumbs)
+            } else {
+                store.dispatch('setBreadcrumb', [])
+            }
+            // next()
+            let userInfo = store.getters['layout/userInfo']
+            if (!userInfo) {
+                // 本地是未登录状态, 保存目标页面地址, 去登录
+                let lastRoute = { path: to.path, params: to.params, query: to.query }
+                store.commit('setLastRoute', lastRoute)
+                toLogin()
+            } else {
+                if (to.meta.breadcrumbs) {
+                    store.dispatch('setBreadcrumb', to.meta.breadcrumbs)
+                }
+                next()
+            }
+            return true
+        }
+    },
+
+    getMenus(permissions) {
+        let result = []
+        let allMenus = []
+        // 开发环境下展示demo页面
+        // if (process.env.NODE_ENV === 'development') {
+        //     allMenus = demoMenus
+        // }
+        allMenus = allMenus.concat(menus)
+        let ps = !permissions ? {} : permissions
+        console.log(ps)
+        allMenus.forEach(item => {
+            let menu = checkMenu(item, ps)
+            if (menu) {
+                result.push(menu)
+            }
+        })
+        return result
+    },
+
+    login(username, password) {
+        return new Promise((resolve, reject) => {
+            if (username == 'admin') {
+                resolve({ result: 'success', ssoToken: 'admin-token' })
+            } else {
+                resolve({ result: 'fail', message: 'username or password error' })
+            }
+        })
+    },
+
+    loadUserInfo() {
+        // return new Promise((resolve, reject) => resolve({ result: 'success', ...userInfo }))
+        return httputils.getJson(`/admin/ibms/user/userInfo`)
+    },
+
+    toLogout() {
+        // TODO
+        router.push('/auth')
+        store.commit('setSsoToken', null)
+        let ssoServer = process.env.SSO_SERVER
+        let redirectUrl = window.location.protocol + '//' + window.location.host + '/'
+        window.location.href = `${ssoServer}/logout?redirectUrl=${redirectUrl}`
+    },
+
+    toLoginPage: toLogin
+}

+ 107 - 0
src/api/httputils.js

@@ -0,0 +1,107 @@
+import axios from 'axios'
+import store from '@/store'
+import { MessageBox } from 'element-ui'
+
+var CancelToken = axios.CancelToken
+var cancel
+
+// 创建axios实例
+const axiosservice = axios.create({
+    timeout: 3000000, // 请求超时时间
+    retry: 4, //重新请求次数
+    retryDelay: 1000, //重新请求的间隔
+    credentials: true, // 接口每次请求会跨域携带cookie
+    cancelToken: new CancelToken(function executor(c) {
+        // executor 函数接收一个 cancel 函数作为参数
+        cancel = c
+    })
+})
+
+axiosservice.interceptors.request.use(
+    config => {
+        config.withCredentials = true // 允许携带token ,这个是解决跨域产生的相关问题
+        let token = store.getters['ssoToken']
+        if (token) {
+            config.headers = {
+                'sso-token': token
+            }
+        }
+        return config
+    },
+    error => {
+        return Promise.reject(error)
+    }
+)
+
+axiosservice.interceptors.response.use(
+    function(res) {
+        //在这里对返回的数据进行处理
+        //console.log('axios interceptors res = ', res.status, res)
+        let resp = res.data
+        if (resp.result === 'unauthc') {
+            store.commit('logined', false)
+            MessageBox.confirm('未登陆或登陆信息已失效, 是否重新登陆?', '提示', {
+                confirmButtonText: '确定',
+                cancelButtonText: '取消',
+                type: 'error'
+            })
+                .then(resp => {
+                    console.log(resp,'--------')
+                    //console.log('--------------------------- confirm', resp)
+                    //router.push('/login')
+                    window.location.reload()
+                })
+                .catch(error => {
+                    //console.log('--------------------------- cancel', error)
+                    console.log('')
+                })
+        }
+        //console.log('axios interceptors resp2 = ', resp.success, resp.errorCode, resp.errorMessage, res)
+        return res
+    },
+    function(err) {
+        //Do something with response error
+        console.log('axios interceptors err = ', err)
+        return Promise.reject(err)
+    }
+)
+
+export default {
+    //获取cookie
+    getCookie(name) {
+        var arr,
+            reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)')
+        if ((arr = document.cookie.match(reg))) {
+            return unescape(arr[2])
+        } else {
+            /* 如果没有参数,那么就获取本域下的所有cookie */
+            return document.cookie
+        }
+    },
+
+    async getJson(url, params) {
+        try {
+            let response = await axiosservice({
+                url,
+                params,
+                method: 'get'
+            })
+            return response.data
+        } catch (err) {
+            throw err
+        }
+    },
+    async postJson(url, data) {
+        try {
+            let response = await axiosservice({
+                url,
+                data,
+                method: 'post'
+            })
+            return response.data
+        } catch (err) {
+            throw err
+        }
+    },
+    axios: axiosservice
+}

+ 1 - 0
src/api/scan/config.js

@@ -0,0 +1 @@
+export const baseUrl = '/api';

+ 41 - 0
src/api/scan/fetch.js

@@ -0,0 +1,41 @@
+import Vue from 'vue'
+import axios from 'axios'
+import vueAxios from 'vue-axios'
+import { Message } from 'element-ui';
+
+Vue.use(vueAxios, axios)
+//创建实例
+
+const service = axios.create({
+  timeout: 3000000, //请求时间超出
+  withCredentials: true, //是否跨站点访问控制请求
+})
+
+//request拦截
+service.interceptors.request.use(config => {
+  return config
+}, error => {
+  console.log(error)
+  Promise.reject(error)
+})
+
+//response拦截器
+service.interceptors.response.use(
+  response => {
+    // let resp = response.data;
+    // let result = resp.result || resp.Result
+    return response
+    // if (result == 'success' || result == 'Success') {
+    //     return response
+    // } else {
+    //     let msg = resp.message ? resp.message : resp.ResultMsg? resp.ResultMsg: resp.Message;
+    //     Message.error({ message: msg });
+    // }
+  },
+  error => {
+    console.log('err' + error)
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 54 - 0
src/api/scan/httpUtil.js

@@ -0,0 +1,54 @@
+import {Message} from 'element-ui';
+import fetch from './fetch'
+import storage from '@/framework/utils/storage'
+
+function successResponse(vm, response, success, failed) {
+  let resp = response.data;
+  let result = resp.result ? resp.result : resp.Result;
+  if (result === 'success') {
+    if (success) {
+      success(resp);
+    }
+  } else {
+    let msg = resp.message ? resp.message : resp.ResultMsg || resp.resultMsg || resp.Message;
+    Message.error({ message: msg });
+    if (failed) {
+      failed(resp);
+    }
+  }
+
+}
+
+function errorResponse(vm, response, err) {
+  let json = JSON.parse(JSON.stringify(response))
+  console.error(response)
+  if (json.response) {
+    Message.error({ message: `接口:${json.response.config.url}请求错误,错误状态为:${json.response.status}` })
+  } else {
+    console.error(vm, response, err)
+    Message.error({ message: '请求错误' });
+  }
+}
+
+export default {
+  getJson: function(url, data, success, failed, err) {
+    let ProjectId = localStorage.getItem("projectId")
+    let userName = storage.get("user_name")
+    let vm = this;
+    fetch({ url: url, method: 'get', params: data, headers: {'ProjectId': ProjectId, 'Comming': 'adm' ,'Account': userName}}).then((response) => {
+      successResponse(vm, response, success, failed)
+    }).catch(error => {
+      errorResponse(vm, error, err);
+    });
+  },
+  postJson: function(url, data, success, failed, err) {
+    let ProjectId = localStorage.getItem("projectId")
+    let userName = storage.get("user_name")
+    let vm = this;
+    fetch({ url: url, method: 'post', data: data, headers: {'ProjectId': ProjectId, 'Comming': 'adm' ,'Account': userName} }).then((response) => {
+      successResponse(vm, response, success, failed)
+    }).catch(error => {
+      errorResponse(vm, error, err);
+    });
+  }
+}

+ 27 - 0
src/api/scan/request.js

@@ -0,0 +1,27 @@
+import {baseUrl} from './config.js'
+import http from './httpUtil'
+
+//设备清单-查询当前设备类下的部件分类
+export function getPartsAllType(param, success) {
+  let url = `${baseUrl}/equip-component/equip-query/equip-compon`;
+  http.getJson(url, param, success)
+}
+
+//字典查询接口 竖井所有类型
+export function queryShaftType(success) {
+  let url = `${baseUrl}/datacenter/data-dictionary/query-shaft-type`;
+  http.getJson(url, {}, success)
+}
+
+//all - 查询数据字典 --列表表头
+export function getDataDictionary(param, success) {
+  let url = `${baseUrl}/datacenter/data-dictionary/project/query?type=${param.type}`;
+  let data = param.data
+  http.postJson(url, data, success)
+}
+
+//all - 查询数据字典 --列表表头
+export function cacheDictionary(param, success) {
+  let url = `${baseUrl}/datacenter/class-def/query-dict`;
+  http.postJson(url, param, success)
+}

BIN=BIN
src/assets/logo.png


+ 96 - 0
src/assets/scss/reset.scss

@@ -0,0 +1,96 @@
+$fontFamily: "Spurce Han Sans,Wah Kang font,Microsoft YaHei";
+$allColor: #303133; //主要文字
+$color1: #606266; //常规文字
+$color2: #909399; //次要文字
+$color3: #C0C4CC; //占位文字
+$colorList: ( 1:$color1, 2:$color2, 3:$color3);
+$theme1: #5BA7FF;
+$theme2: #FFF;
+$theme3: #8E9CC1;
+$themeList: (1:$theme1, 2:$theme2, 3:$theme3);
+$auxiliary1: #548FF0; //辅助色彩
+$auxiliary2: #FF4121;
+$auxiliary3: #7ED321;
+$auxiliary4: #F5A623;
+$auxiliaryList: ( 1:$auxiliary1, 2:$auxiliary2, 3:$auxiliary3, 4:$auxiliary4);
+$borderColor1: #C2CEDB; //线条色彩
+$borderColor2: #DDD;
+$borderColorList: (1:$borderColor1, 2:$borderColor2);
+$bg1: #FFF;
+$bg2: #F2FBFF;
+$bg3: #EEFAFF;
+$bg4: #FFF5E4;
+$bg5: #E5F6D3;
+$bgList: ( 1:$bg1, 2:$bg2, 3:$bg3, 4:$bg4, 5:$bg5);
+$allSize: 14px; //正常文字大小
+$size1: 12px; //辅助文字大小
+.el-dialog__header {
+    border-bottom: 1px solid #C2CEDB;
+    box-sizing: border-box;
+    .el-dialog__title {
+        height: 20px;
+        line-height: 20px;
+        font-size: 14px;
+    }
+}
+
+//文字色
+@each $i,
+$color in $colorList {
+    .color#{$i} {
+        color: $color;
+    }
+}
+
+//主题色
+@each $i,
+$theme in $themeList {
+    .theme#{$i} {
+        background-color: $theme;
+    }
+}
+
+//辅助背景色
+@each $i,
+$auxiliary in $auxiliaryList {
+    .auxiliary#{$i} {
+        background-color: $auxiliary;
+    }
+}
+
+//辅助色
+@each $i,
+$auxiliary in $auxiliaryList {
+    .auxiliary-color#{$i} {
+        color: $auxiliary;
+    }
+}
+
+//线条色
+@each $i,
+$borderColor in $borderColorList {
+    .border-color#{$i} {
+        border-color: $borderColor;
+    }
+}
+
+//背景色
+@each $i,
+$bg in $bgList {
+    .bg#{$i} {
+        background-color: $bg;
+    }
+}
+
+@for $i from 1 to 51 {
+    .border-radius#{$i} {
+        border-radius: #{$i}px;
+    }
+}
+
+@for $i from 1 to 11 {
+    .border#{$i} {
+        border-width: #{$i}px;
+        border-style: solid;
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 489 - 0
src/assets/style/iconfont/iconfont.css


BIN=BIN
src/assets/style/iconfont/iconfont.eot


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 380 - 0
src/assets/style/iconfont/iconfont.svg


BIN=BIN
src/assets/style/iconfont/iconfont.ttf


BIN=BIN
src/assets/style/iconfont/iconfont.woff


+ 100 - 0
src/assets/style/style.scss

@@ -0,0 +1,100 @@
+body{
+    /deep/ .el-notification.right{
+        text-align: left;
+    }
+}
+
+.icon-wushuju {
+    display: block;
+    width: 100px;
+    height: 100px;
+    font-size: 50px !important;
+    margin: auto;
+}
+
+.right {
+    text-align: right;
+    background: #fff;
+    padding-top: 10px;
+}
+
+.left {
+    text-align: left;
+    background: #fff;
+    padding-top: 10px;
+}
+
+.right-nobg {
+    text-align: right;
+}
+
+.el-loading-mask {
+    z-index: 499;
+}
+
+.search-title {
+    height: 53px;
+    border: 1px solid #dfe6ec;
+    background-color: #fff;
+    padding: 10px 10px 10px 20px;
+    box-sizing: border-box;
+    margin-bottom: 10px;
+    /deep/ .el-input__inner{
+        vertical-align: baseline;
+    }
+}
+
+.p12 {
+    padding-right: 12px;
+    color: #999;
+}
+
+iframe {
+    border: none;
+}
+
+.el-button--success.is-plain,
+.el-button--primary.is-plain,
+.el-button--info.is-plain,
+.el-button--warning.is-plain,
+.el-button--danger.is-plain {
+    background-color: #fff;
+}
+
+.el-dropdown-link {
+    cursor: pointer;
+    color: #409eff;
+}
+
+.middle_sty {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.el-table__empty-block {
+    align-items: initial;
+}
+
+.p10 {
+    padding: 0 10px;
+    box-sizing: border-box;
+}
+
+.el-checkbox__label{
+    font-size: 12px;
+}
+jmnodes.theme-greensea jmnode {
+    color: #fff;
+    background-color: #67c23a;
+    border-color: #67c23a;
+}
+
+// jmnodes.theme-greensea jmnode.selected {
+//     background: #85ce61;
+//     border-color: #85ce61;
+// }
+// jmnodes.theme-greensea jmnode:hover {
+//     background: #85ce61;
+//     border-color: #85ce61;
+// }

+ 38 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,38 @@
+<template>
+    <div class='hello'>
+        <template v-for='i in 10'>
+            <h1 :key='i'>{{ msg }} ---- {{i}}</h1>
+            <h2 :key='i'>Essential Links</h2>
+        </template>
+    </div>
+</template>
+
+<script>
+export default {
+    name: 'HelloWorld',
+    data() {
+        return {
+            msg: 'Welcome to Your Vue.js App'
+        }
+    }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+h1,
+h2 {
+    font-weight: normal;
+}
+ul {
+    list-style-type: none;
+    padding: 0;
+}
+li {
+    display: inline-block;
+    margin: 0 10px;
+}
+a {
+    color: #42b983;
+}
+</style>

+ 242 - 0
src/components/model/file/floorTable.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="filterTable-container">
+    <el-table ref="filterTable" :data="tableData" style="width: 100%" height="100%" :header-cell-style="{background:'#d9d9d9',color:'#2b2b2b'}">
+      <el-table-column prop="FloorName" label="模型文件" width="180">
+        <template slot-scope="scope">
+          <i v-if="scope.row.Status == 4" class="iconfont icon-yun--tianchong" style="color:#67C23A;cursor:pointer;fontSize:18px;" title="模型校验完成..."
+            @click="toPathControl(scope.row)"></i>
+          <i v-else-if="scope.row.Status == 31" class="iconfont icon-yun--tianchong1" style="color:#F56C6C;cursor:pointer;fontSize:18px;"
+            title="同步到数据中心出问题..." @click="toPathControl(scope.row)"></i>
+          <i v-else-if="scope.row.Status == 21" class="iconfont icon-yun--tianchong1" style="color:#F56C6C;cursor:pointer;fontSize:18px;"
+            title="模型数据导出出问题..." @click="toPathControl(scope.row)"></i>
+          <i v-else class="iconfont icon-yun--tianchong2" style="color:#E6A23C;cursor:pointer;fontSize:18px;" title="模型校验进行中..."
+            @click="toPathControl(scope.row)"></i>
+          <span style="margin-left: 10px">{{ scope.row.FloorName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="Note" label="备注" width="240"></el-table-column>
+      <el-table-column label="版本号">
+        <template slot-scope="scope">
+          <span style="margin-right: 5px">{{ scope.row.Version }}</span>
+          <i v-show="scope.row.Version && scope.row.Status == 4" class="iconfont icon-warn" style="cursor:pointer;position:relative;top:1px;" title="查看版本更新信息" @click="handleClickVersion(scope.row)"></i>
+        </template>
+      </el-table-column>
+      <el-table-column prop="RevitVersion" label="revit版本"></el-table-column>
+      <el-table-column prop="AcceptTime" label="上传时间" width="200"></el-table-column>
+      <el-table-column prop="UserName" label="上传人"></el-table-column>
+      <el-table-column prop="address" align="center" label="操作" width="240">
+        <template slot-scope="scope">
+          <div class="operate" v-show="scope.row.Status == 4">
+            <el-button title="下载模型" type="primary" size="mini" class="iconfont icon-download" @click="downloadModel(scope.row)"></el-button>
+            <el-button title="替换模型" type="primary" size="mini" class="iconfont icon-replace" @click="repliaceModel(scope.row)"></el-button>
+            <el-button title="查看版本日志" type="primary" size="mini" class="iconfont icon-Log" @click="queryModelLog(scope.row)"></el-button>
+          </div>
+          <div :class="['upLoad-loading']" v-show="scope.row.Status != 4">
+            <div class="progress">
+              <el-progress :text-inside="false" :stroke-width="20" :percentage="100" :color="scope.row.Status?'#909399':'#67C23A'"></el-progress>
+            </div>
+            <div class="progress-right">
+              <span v-if="!scope.row.Status">上传中...</span>
+              <span v-else-if="scope.row.Status == 1">等待检查...</span>
+              <span v-else>模型检查中...</span>
+
+              <!-- <span v-show="!scope.row.Status">上传中...</span>
+              <span v-show="scope.row.Status == 1">等待检查...</span>
+              <span v-show="scope.row.Status == 10">模型检查中...</span>
+              <span v-show="scope.row.Status == 11">未通过检查</span>
+              <span v-show="scope.row.Status == 2 || scope.row.Status == 20">等待数据导出...</span>
+              <span v-show="scope.row.Status == 21">模型数据导出失败</span>
+              <span v-show="scope.row.Status == 3">处理导出数据中...</span>
+              <span v-show="scope.row.Status == 31">同步到数据中心失败</span> -->
+            </div>
+          </div>
+        </template>
+      </el-table-column>
+    </el-table>
+  </div>
+</template>
+<script>
+import { mapGetters } from "vuex";
+export default {
+  components: {
+    versionDialog
+  },
+  props: {
+    tableData: Array,
+    persentList: Array,
+    modelFolderName: String
+  },
+  data() {
+    return {
+      maxHeight: 0,
+      dialogVisible: false,
+      modelFile: null
+    };
+  },
+  computed: {
+    ...mapGetters("layout", ["projectId", "userInfo", "userId", "secret"])
+  },
+  methods: {
+    // 查看日志
+    queryModelLog(item) {
+      this.$emit("openModelLog", item);
+    },
+    // 查看版本信息
+    handleClickVersion(item) {
+      this.modelFile = item;
+      this.dialogVisible = true;
+    },
+    // 替换日志
+    repliaceModel(item) {
+      if (item.Status != 4) {
+        this.$alert("正在识别模型对象,请稍后再替换。", "替换模型", {
+          confirmButtonText: "确定",
+          callback: action => {
+          }
+        });
+      } else {
+        this.$emit("replaceModel", item);
+      }
+    },
+    filterTag(Id, precent) {
+      this.$refs.filterTable.data.map(item => {
+        if (item.Id == Id) {
+          if (precent >= 100) {
+            // 如过precent == 100 不能关闭进度条,
+            if (precent == 100) {
+              item.precent = 99;
+            } else if (precent == 101) {
+              // 如过precent == 101 则返回结果为suceess 不能关闭进度条,
+              item.precent = 100;
+              this.$emit("percentFinish");
+            }
+            return;
+          } else {
+            item.precent = precent;
+          }
+        }
+      });
+    },
+    // 下载模型文件
+    downloadModel(item) {
+      let url = item.Url.match(/(\/image-service\S*)$/g) ? item.Url.match(/(\/image-service\S*)$/g)[0] : ''
+      if (url) {
+        let a = document.createElement("a");
+        a.href = url;
+        a.download = `${this.modelFolderName}${item.FloorName}模型文件v${item.Version}.rvt`;
+        a.click();
+        document.body.removeChild(a);
+      } else {
+        this.$message({
+          message: "该文件夹下没有资源",
+          type: "error"
+        });
+      }
+    },
+    // 停止上传
+    closeUpdate(item) {
+      if (this.userInfo.userName == item.UserName) {
+        this.$emit("closeUpdateFile", item);
+      } else {
+        this.$message({
+          message: "您不是该文件的上传者,不能停止该文件上传!",
+          type: "error"
+        });
+      }
+    },
+    // 跳转至模型流程处理监控
+    toPathControl(row) {
+      this.$router.push({
+        path: '/model/path',
+        query: {
+          Id: row.CurrentModelId,
+          Size: row.Size,
+          OriginalName: row.OriginalName
+        }
+      })
+    }
+  },
+  watch: {
+    persentList: {
+      immediate: true,
+      deep: true,
+      handler: function (val, oldVal) {
+        if (val.length != 0) {
+          val.map(item => {
+            if (item.precent != 0) {
+              this.filterTag(item.Id, item.precent);
+            }
+          });
+        }
+      }
+    }
+  }
+};
+</script>
+<style scoped lang="less">
+.box-card {
+  height: 100%;
+  .filterTable-container {
+    height: calc(100% - 54px);
+    margin-bottom: 8px;
+  }
+  .operate {
+    .iconfont {
+      font-size: 12px;
+      padding: 7px 12px;
+    }
+  }
+  .icon-termination {
+    color: #f56c6c;
+    background: #fff;
+    padding: 0;
+    border: 0;
+    font-size: 20px;
+    margin-left: 5px;
+  }
+  .upLoad {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 4px 0;
+    .progress {
+      // width: 150px;
+      width: 175px;
+      height: 20px;
+    }
+    .progress-right {
+      height: 20px;
+      line-height: 20px;
+    }
+  }
+  .upLoad-loading {
+    position: relative;
+    justify-content: center;
+    .progress {
+      width: 220px;
+      height: 20px;
+    }
+    .progress-right {
+      position: absolute;
+      left: 50%;
+      top: 50%;
+      transform: translate(-50%, -50%);
+      color: #fff;
+    }
+  }
+}
+/deep/ .el-icon-warning {
+  display: none;
+  // color: transparent;
+}
+/deep/ .el-progress__text {
+  display: none;
+}
+/deep/ .upLoad-loading .el-progress-bar {
+  padding-right: 44px;
+  margin-right: -44px;
+}
+/deep/ .el-progress-bar__inner {
+  text-align: center;
+}
+</style>

+ 326 - 0
src/data/menus.js

@@ -0,0 +1,326 @@
+export default [
+	/******************** 平台管理 ***************************/
+	{
+		path: '/platform',
+		name: '平台管理',
+		disabled: true,
+	},
+	// 项目管理
+	{
+		path: '/platform/project',
+		name: '项目管理',
+		icon: 'icon-project-o'
+	},
+	// 人员管理
+	{
+		path: '/platform/user',
+		name: '人员管理',
+		icon: 'icon-renyuanguanli',
+		opts: [{
+			name: '查看',
+			basic: true,
+			permission: 'system:user:query'
+		}]
+	},
+	// 角色管理
+	{
+		path: '/platform/role',
+		name: '角色管理',
+		icon: 'icon-jiaoseguanli',
+		opts: [{
+			name: '查看',
+			basic: true,
+			permission: 'system:role:query'
+		}]
+	},
+	/******************** 前期准备 ***************************/
+	{
+		path: '/ready',
+		name: '前期准备',
+		disabled: true,
+	},
+	// 建筑楼层管理
+	{
+		path: '/ready/buildfloor',
+		name: '建筑楼层管理',
+		icon: 'icon-jianzhu'
+	},
+	// 需采集的信息点
+	{
+		path: '/ready/collectsetting',
+		name: '需采集的信息点',
+		icon: 'icon-xinxi'
+	},
+	// 扫楼App用户管理
+	{
+		path: '/ready/appuser',
+		name: '扫楼App用户管理',
+		icon: 'icon-app4'
+	},
+	/******************** 信息收集 ***************************/
+	{
+		path: '/information',
+		name: '信息收集',
+		disabled: true,
+	},
+	// 模型管理
+	{
+		path: '/model',
+		name: '模型管理',
+		icon: 'icon-moxing___',
+		children: [{
+			path: '/model/file',
+			name: '模型文件管理',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/model/report',
+			name: '模型质量报告',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}]
+	},
+	// 系统集成
+	{
+		path: '/point',
+		name: '系统集成',
+		icon: 'icon-xitong',
+		children: [{
+			path: '/point/pointsetting',
+			name: '子系统点位接入',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/point/dynamicdata',
+			name: '配置动参从点位取值',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		},
+		// {
+		// 	path: '/point/objectdata',
+		// 	name: '配置动参从对象取值',
+		// 	icon: '',
+		// 	opts: [{
+		// 		name: '查看',
+		// 		basic: true,
+		// 		permission: 'system:role:query'
+		// 	}]
+		// },
+		{
+			path: '/point/report',
+			name: '系统集成成果管理',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}]
+	},
+	// 扫楼作业
+	{
+		path: '/floor',
+		name: '扫楼作业',
+		icon: 'icon-shanglou',
+		children: [{
+			path: '/floor/task',
+			name: '现场任务管理',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		},{
+			path: '/floor/data',
+			name: '现场数据整理',
+			icon: '',
+			children: [ {
+				path: '/floor/data',
+				name: '信息点整理',
+				icon: '',
+				opts: [{
+					name: '查看',
+					basic: true,
+					permission: 'system:role:query'
+				}]
+			},
+			{
+				path: '/floor/plan',
+				name: '位置标签整理',
+				icon: '',
+				opts: [{
+					name: '查看',
+					basic: true,
+					permission: 'system:role:query'
+				}]
+			}]
+		}, {
+			path: '/floor/abnormalprop',
+			name: '扫楼报告',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/floor/log',
+			name: '扫楼日志查看',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}]
+	},
+	// 台账管理
+	{
+		path: '/ledger',
+		name: '台账管理',
+		icon: 'icon-taizhangzhangbushezhi',
+		children: [{
+			path: '/ledger/facility',
+			name: '设备台账',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/ledger/property',
+			name: '资产台账',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/ledger/list',
+			name: '系统台账',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/ledger/spacelist',
+			name: '业务空间台账',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+				path: '/ledger/cenotelist',
+				name: '竖井台账',
+				icon: '',
+				opts: [{
+					name: '查看',
+					basic: true,
+					permission: 'system:role:query'
+				}]
+		},{
+			path: '/ledger/rentlist',
+			name: '租户台账',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}, {
+			path: '/ledger/datareport',
+			name: '数据质量报告',
+			icon: '',
+			opts: [{
+				name: '查看',
+				basic: true,
+				permission: 'system:role:query'
+			}]
+		}]
+	},
+	/******************** 关系维护并计算 ***************************/
+	{
+		path: '/relation',
+		name: '关系维护并计算',
+		disabled: true,
+	},
+	// 关系维护
+	// {
+	// 	path: '/relation/maintain',
+	// 	name: '关系维护',
+	// 	icon: 'icon-guanxi',
+	// 	opts: [{
+	// 		name: '查看',
+	// 		basic: true,
+	// 		permission: 'system:user:query'
+	// 	}]
+	// },
+	// 全部关系总览
+	{
+		path: '/relation/overview',
+		name: '全部关系总览',
+		icon: 'icon-jiqixuexi-',
+		opts: [{
+			name: '查看',
+			basic: true,
+			permission: 'system:role:query'
+		}]
+	},
+	/******************** 数据项目化交付 ***************************/
+	{
+		path: '/deliver',
+		name: '数据项目化交付',
+		disabled: true,
+	},
+	// 项目数据转换
+	{
+		path: '/relation/data',
+		name: '项目数据转换',
+		icon: 'icon-view',
+		opts: [{
+			name: '查看',
+			basic: true,
+			permission: 'system:role:query'
+		}]
+	},
+	/******************** 通用字典 ***************************/
+	// {
+	// 	path: '/dictionaries',
+	// 	name: '通用字典',
+	// 	disabled: true,
+	// },
+	// // 厂家库
+	// {
+	// 	path: '/manufactor/supplier',
+	// 	name: '厂家库',
+	// 	icon: '',
+	// 	opts: [{
+	// 		name: '查看',
+	// 		basic: true,
+	// 		permission: 'system:role:query'
+	// 	}]
+	// }
+]

+ 182 - 0
src/framework/layout/Login.vue

@@ -0,0 +1,182 @@
+<template>
+    <div class='login-container'>
+        <el-form
+            class='card-box login-form'
+            autocomplete='on'
+            :model='loginForm'
+            :rules='loginRules'
+            ref='loginForm'
+            label-position='left'
+        >
+            <h3 class='title'>系统登录</h3>
+            <el-form-item prop='username'>
+                <span class='svg-container svg-container_login'>
+                    <i class='el-icon-fa-user-o'></i>
+                </span>
+                <el-input name='username' type='text' v-model='loginForm.username' autocomplete='on' placeholder='邮箱'/>
+            </el-form-item>
+            <el-form-item prop='password'>
+                <span class='svg-container'>
+                    <i class='el-icon-fa-key'></i>
+                </span>
+                <el-input
+                    name='password'
+                    type='password'
+                    v-model='loginForm.password'
+                    placeholder='密码'
+                    @keyup.enter.native.prevent='handleLogin'
+                />
+            </el-form-item>
+            <el-button
+                type='primary'
+                style='width:100%;margin-bottom:30px;'
+                :loading='loading'
+                @click.native.prevent='handleLogin'
+            >登录</el-button>
+        </el-form>
+    </div>
+</template>
+<script>
+function isvalidUsername(val) {
+    return true
+}
+import frameworkApi from '@/api/framework'
+import { mapActions, mapGetters } from 'vuex'
+export default {
+    components: {},
+    name: 'login',
+    data() {
+        const validateUsername = (rule, value, callback) => {
+            if (!isvalidUsername(value)) {
+                callback(new Error('请输入正确的用户名'))
+            } else {
+                callback()
+            }
+        }
+        const validatePassword = (rule, value, callback) => {
+            if (value.length < 6) {
+                callback(new Error('密码不能小于6位'))
+            } else {
+                callback()
+            }
+        }
+        return {
+            loginForm: {
+                username: 'admin',
+                password: '123456'
+            },
+            loginRules: {
+                username: [
+                    {
+                        required: true,
+                        trigger: 'blur',
+                        validator: validateUsername
+                    }
+                ],
+                password: [
+                    {
+                        required: true,
+                        trigger: 'blur',
+                        validator: validatePassword
+                    }
+                ]
+            },
+            loading: false
+        }
+    },
+    computed: {
+        //...mapGetters(['beforeUrl'])
+    },
+    methods: {
+        async handleLogin() {
+            let vm = this
+            vm.$refs.loginForm.validate(valid => {
+                if (valid) {
+                    let username = this.loginForm.username
+                    let password = this.loginForm.password
+                    frameworkApi.login(username, password).then(resp => {
+                        console.log('login result => ', resp)
+                        if (resp.result == 'success') {
+                            this.$store.dispatch('layout/loadUserInfo').then(resp => {
+                                console.log('store dispatch result ', resp)
+                                if (resp.result == 'success') {
+                                    vm.$router.push('/demo/table', () => {})
+                                } else {
+                                }
+                            })
+                        } else {
+                        }
+                    })
+                } else {
+                    console.log('error submit!!')
+                    return false
+                }
+            })
+        }
+    }
+}
+</script>
+<style rel="stylesheet/scss" lang="scss">
+$bg: #2d3a4b;
+$dark_gray: #889aa4;
+$light_gray: #eee;
+.login-container {
+    height: 100vh;
+    background-color: $bg;
+    input:-webkit-autofill {
+        -webkit-box-shadow: 0 0 0px 1000px #293444 inset !important;
+        -webkit-text-fill-color: #fff !important;
+    }
+    input {
+        background: transparent;
+        border: 0px;
+        -webkit-appearance: none;
+        border-radius: 0px;
+        padding: 12px 5px 12px 15px;
+        color: $light_gray;
+        height: 47px;
+    }
+    .el-input {
+        display: inline-block;
+        height: 47px;
+        width: 85%;
+    }
+    .tips {
+        font-size: 14px;
+        color: #fff;
+        margin-bottom: 10px;
+    }
+    .svg-container {
+        padding: 6px 5px 6px 15px;
+        color: $dark_gray;
+        vertical-align: middle;
+        width: 30px;
+        display: inline-block;
+        &_login {
+            font-size: 20px;
+        }
+    }
+    .title {
+        font-size: 26px;
+        font-weight: 400;
+        color: $light_gray;
+        margin: 0px auto 40px auto;
+        text-align: center;
+        font-weight: bold;
+    }
+    .login-form {
+        position: absolute;
+        left: 0;
+        right: 0;
+        width: 400px;
+        padding: 35px 35px 15px 35px;
+        margin: 120px auto;
+    }
+    .el-form-item {
+        border: 1px solid rgba(255, 255, 255, 0.1);
+        background: rgba(0, 0, 0, 0.1);
+        border-radius: 5px;
+        color: #454545;
+    }
+}
+</style>

+ 87 - 0
src/framework/layout/Main.vue

@@ -0,0 +1,87 @@
+<template>
+    <div id='page-main' v-bind:class='{"page-sidebar-closed": sidebarClosed}'>
+        <page-header></page-header>
+
+        <div id='page-container' class='page-container'>            
+            <page-sidebar></page-sidebar>            
+            <div id='page-content-wrapper' class='page-content-wrapper'>
+                <div class='page-bar'>
+                    <el-breadcrumb separator='/'>
+                    <el-breadcrumb-item v-show="!breadcrumb.length"  :to='{ path: "/" }'>首页</el-breadcrumb-item>
+                    <el-breadcrumb-item v-show="breadcrumb.length" v-for='(b, index) in breadcrumb' :key='index' :to='b.path ? { path: b.path } : null'>{{b.label}}</el-breadcrumb-item>                       
+                    </el-breadcrumb>
+                </div>
+                <!-- <router-view class='page-content'/> -->
+                <keep-alive>
+                    <router-view v-if='$route.meta.keepAlive' class='page-content'></router-view>
+                </keep-alive>
+                <router-view v-if='!$route.meta.keepAlive' class='page-content'></router-view>
+            </div>
+        </div>
+        <!-- <div class='page-footer'>
+            © 2016 SAGACLOUD. All rights reserved 京ICP备16059843号-1
+        </div>-->
+    </div>
+</template>
+<script>
+import { mapGetters, mapMutations } from 'vuex'
+import PageHeader from './PageHeader'
+import PageSidebar from './PageSidebar'
+import menus from '@/data/menus' 
+export default {
+    name: 'Main',
+    components: {
+        'page-header': PageHeader,
+        'page-sidebar': PageSidebar
+    },
+    props: [],
+    data() {
+        return {
+            isPath:  false
+        }
+    },
+    computed: {
+        ...mapGetters('layout', ['sidebarClosed', 'breadcrumb'])
+    },
+    methods: {
+        ...mapMutations('layout', ['setSidebarClosed','setSidebarSelected']),
+        windwoResize() {
+            // 窗口大小发生变化时
+            let clientWidth = `${document.documentElement.clientWidth}`
+            let clientHeight = `${document.documentElement.clientHeight}`
+            //this.setPageContentHeight(height)
+            if (clientWidth > 1000) {
+                this.setSidebarClosed(false)
+            } else if (clientWidth > 800) {
+                this.setSidebarClosed(true)
+            } else {
+                this.setSidebarClosed(false)
+            }
+        },
+        findPathInArray(arr, path) {
+            arr.forEach(ele => {
+                if(ele.path == path) {
+                    this.isPath = true
+                }
+                if(ele.children) {
+                   this.findPathInArray(ele.children, path)
+                }
+            })
+        }
+    },
+    watch: {
+        "$route": {
+            handler: function(route, oldVal){
+                let path = route.path
+                this.isPath = false;
+                this.findPathInArray(menus,path)
+                if(this.isPath) {
+                    this.setSidebarSelected(path)
+                }
+            },
+            // 深度观察监听
+            deep: true
+        }
+    },
+}
+</script>

+ 142 - 0
src/framework/layout/PageHeader.vue

@@ -0,0 +1,142 @@
+<template>
+    <div class='page-header'>
+        <div id='page-header-logo' class='page-logo'>
+            <a @click="toIndex">
+                <img src='@/assets/logo.png' alt='logo' class='logo-default'>
+            </a>
+            <div class='menu-toggler sidebar-toggler' @click.stop='toggleSidebar'>
+                <span>
+                    <i class='el-icon-fa-bars'></i>
+                </span>
+            </div>
+        </div>
+        <div id='page-header-data-menu' class='data-menu'>
+            <el-select v-model='selectedProjectId' placeholder='请选择' filterable>
+                <el-option-group v-for="group in projectGropList" :key="group.label" :label="group.label">
+                    <el-option v-for='item in group.options' :key='item.id' :label='item.name' :value='item.id'></el-option>
+                </el-option-group>
+            </el-select>
+        </div>
+        <div id='page-header-user-menu' class='user-menu'>
+            <el-dropdown trigger='hover' class='user-menu-dropdown' @command='userMenuCommand'>
+                <span class='el-dropdown-link'>
+                    {{userInfo.userName}}
+                    <i class='el-icon-fa-user el-icon--right'></i>
+                </span>
+                <el-dropdown-menu slot='dropdown'>
+                    <el-dropdown-item icon='el-icon-plus' command='logout'>退出</el-dropdown-item>
+                    <el-dropdown-item icon='el-icon-circle-plus'>修改密码</el-dropdown-item>
+                </el-dropdown-menu>
+            </el-dropdown>
+            <ul class="header-nav">
+                <li class="header-nav-Splitscreen">
+                    <a href="/splitScreen" target="_blank"><i class="el-icon-s-platform"></i></a>
+                </li>
+            </ul>
+        </div>
+    </div>
+</template>
+<script>
+import frameworkApi from '@/api/framework'
+import { mapGetters, mapMutations } from 'vuex'
+import getFirstLetter from '@/utils/getFirstLetter'
+import lStorage from '@/utils/localStorage'
+export default {
+    name: 'PageHeader',
+    props: [],
+    data() {
+        return {
+            selectedProjectId: null
+        }
+    },
+    computed: {
+        ...mapGetters('layout', ['sidebarClosed', 'userInfo', 'projects', 'projectId']),
+        projectGropList () {
+            const cacheInfo = lStorage.get('historyInfo')?lStorage.get('historyInfo'):{}
+            let options = [{
+                label: '最近使用',
+                options: []
+            }]
+            const projectHistory = cacheInfo[this.userInfo.userName] || []
+            options[0].options = projectHistory.map(projectId => {
+                let obj = this.projects.find(project => {
+                    return project.id === projectId
+                })
+                return obj?obj:[]
+            })
+
+            let helpObj = {}
+            this.projects.forEach(p => {
+                const label = getFirstLetter((p.name || p.id).slice(0, 1))
+                if (helpObj[label]) {
+                    helpObj[label].push(p)
+                } else {
+                    helpObj[label] = [p]
+                }
+            })
+            const arr = Object.keys(helpObj).sort().map(label => {
+                return {
+                    label: label,
+                    options: helpObj[label]
+                }
+            })
+            return [...(options[0].options.length > 0 ? options : []), ...arr]
+        }
+    },
+    watch: {
+        selectedProjectId(n, o) {
+            this.setprojectId(n)
+        }
+    },
+    methods: {
+        ...mapMutations('layout', ['setSidebarClosed', 'setprojectId']),
+        handleSelect() {},
+        toggleSidebar() {
+            this.setSidebarClosed(!this.sidebarClosed)
+        },
+        userMenuCommand(cmd) {
+            // console.log('userMenuCommand ', cmd)
+            if (cmd == 'logout') {
+                console.log("点击登出l")
+                frameworkApi.toLogout()
+            }
+        },
+        toIndex() {
+            this.$router.push('/')
+        }
+    },
+    created() {
+        let cacheInfo = lStorage.get('historyInfo')?lStorage.get('historyInfo'):{}
+        if(cacheInfo[this.userInfo.userName] && 
+            cacheInfo[this.userInfo.userName][0] && 
+            this.projects.some((item) => {return item.id == cacheInfo[this.userInfo.userName][0]})
+        ){
+            this.selectedProjectId = cacheInfo[this.userInfo.userName][0]
+        } else {
+            this.selectedProjectId = this.projectId
+        }
+    },
+    mounted() {}
+}
+</script>
+<style lang="scss" scoped>
+.header-nav-Splitscreen>a{
+    display: flex;
+    width: 100%;
+    height: 100%;
+    position: relative;
+    padding: 16px 10px 10px;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    color: #79869a;
+    font-size: 24px;
+    text-decoration: none;
+    cursor: pointer;
+    i{transition:all 0ms ease 0ms}
+}
+.header-nav-Splitscreen>a:hover{
+    text-decoration: none;
+    color: #d3d8e2;
+    background-color: #3f4f62;
+}
+</style>

+ 91 - 0
src/framework/layout/PageSidebar.vue

@@ -0,0 +1,91 @@
+<template>
+    <div id='page-sidebar-wrapper' class='page-sidebar-wrapper'>
+        <el-scrollbar tag="div" wrapClass="content-scrollbar">
+        <el-menu
+            :collapse='sidebarClosed'
+            :default-active='sidebarSelected'
+            @open='handleOpen'
+            @close='handleClose'
+            @select='menuSelect'
+            background-color='#364150'
+            text-color='#fff'
+            active-text-color='#ffd04b'
+            unique-opened
+            router
+            class='sidebar-menu'
+        >
+            <template v-for='menu in menus'>
+                <el-submenu v-if='menu.children' :index='menu.path' :key='menu.path' class='sidebar-menu-submenu'>
+                    <template slot='title'>
+                        <i v-if='menu.icon' :class='"iconfont "+ menu.icon'></i>
+                        <span>{{menu.name}}</span>
+                    </template>
+                    <template v-for='submenu in menu.children'>
+                        <el-submenu v-if='submenu.children' :index='submenu.path' :key='submenu.path' class='sidebar-menu-submenu'>
+                            <template slot='title'>
+                                <i v-if='submenu.icon' :class='"iconfont "+ submenu.icon'></i>
+                                <span>{{submenu.name}}</span>
+                            </template>
+                            <template v-for='threemenu in submenu.children'>
+                                <el-menu-item :index='threemenu.path' :key='threemenu.path' class='sidebar-menu-item'>
+                                    <i v-if='threemenu.icon' :class='"iconfont "+ threemenu.icon'></i>
+                                    <span slot='title'>{{threemenu.name}}</span>
+                                </el-menu-item>
+                            </template>
+                        </el-submenu>
+                        <el-menu-item v-else :index='submenu.path' :key='submenu.path' class='sidebar-menu-item'>
+                            <i v-if='submenu.icon' :class='"iconfont "+ submenu.icon'></i>
+                            <span slot='title'>{{submenu.name}}</span>
+                        </el-menu-item>
+                    </template>
+                </el-submenu>
+                <el-menu-item v-else :index='menu.path' :key='menu.path' class='sidebar-menu-item' :disabled="menu.disabled">
+                    <i v-if='menu.icon' :class='"iconfont "+ menu.icon'></i>
+                    <span slot='title'>{{menu.name}}</span>
+                </el-menu-item>
+            </template>
+        </el-menu>
+        </el-scrollbar>
+    </div>
+</template>
+<script>
+import { mapGetters, mapMutations } from 'vuex'
+import frameworkApi from '@/api/framework'
+export default {
+    name: 'PageSidebar',
+    props: [],
+    data() {
+        return {}
+    },
+    computed: {
+        ...mapGetters('layout', ['sidebarClosed', 'sidebarSelected', 'permissions']),
+        menus() {
+            // console.log(this.permissions)
+            let menus = frameworkApi.getMenus(this.permissions)
+            // console.log('sidebar menus ', menus)
+            return menus
+        }
+    },
+    methods: {
+        ...mapMutations('layout', ['setSidebarSelected']),
+        handleOpen(val) {
+            // console.log('handleOpen------------- ', val)
+        },
+        handleClose(val) {
+            // console.log('handleClose------------- ', val)
+        },
+        menuSelect(index, indexPath) {
+            this.setSidebarSelected(index)
+            // console.log('menu select ', index, indexPath)
+        }
+    },
+    created() {
+        // console.log('--------------------- PageSidebar created')
+        // console.log('menus ', this.menus)
+    },
+
+    mounted() {},
+    components: {}
+}
+</script>
+

+ 18 - 0
src/framework/style/awesome.less

@@ -0,0 +1,18 @@
+[class^='el-icon-fa'],
+[class*=' el-icon-fa'] {
+  font-family: FontAwesome !important;
+  font-size: inherit;
+  speak: none;
+  font-style: normal;
+  font-weight: 400;
+  font-variant: normal;
+  text-transform: none;
+  line-height: 1;
+  vertical-align: baseline;
+  display: inline-block;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+@import url('../../../node_modules/font-awesome/less/font-awesome');
+@fa-css-prefix: el-icon-fa;

+ 424 - 0
src/framework/style/layout.scss

@@ -0,0 +1,424 @@
+@import './theme.scss';
+$pageHeaderHeight: 50px;
+$pageSidebarWidth: 235px;
+$pageSidebarClosedWidth: 64px;
+$pageSidebarItemHeight: 40px;
+$pageFooterHeight: 40px;
+html,
+body {
+    padding: 0 !important;
+    margin: 0 !important;
+    font-size: 14px;
+    width: 100%;
+    height: 100%;
+}
+
+#app {
+    width: 100%;
+    height: 100%;
+}
+/*---滚动条默认显示样式--*/
+::-webkit-scrollbar-thumb{
+    height:50px;
+    outline-offset:-2px;
+    outline:2px solid #dddee0;
+    border: 2px solid #dddee0;
+    background-color: #dddee0;
+    border-radius: 4px;
+}
+
+/*---鼠标点击滚动条显示样式--*/
+::-webkit-scrollbar-thumb:hover{
+    height:50px;
+}
+
+
+/*---滚动条大小--*/
+::-webkit-scrollbar{
+    width:8px;
+    height:8px;
+}
+
+/*---滚动框背景样式--*/
+::-webkit-scrollbar-track-piece{
+    
+}
+#page-main {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    /* 垂直 从上到下 */
+    justify-content: flex-start;
+    /* 垂直 左对齐 */
+    align-items: stretch;
+    .page-header {
+        order: 0;
+        flex-grow: 0;
+        flex-shrink: 0;
+        height: $pageHeaderHeight;
+    }
+    .page-container {
+        order: 1;
+        flex-grow: 1;
+        flex-shrink: 1;
+        display: flex;
+        flex-direction: row;
+        /* 水平,从左到右 */
+        justify-content: flex-start;
+        /* 水平上对齐 */
+        .page-sidebar-wrapper {
+            width: $pageSidebarWidth;
+            // overflow: auto;
+            flex-grow: 0;
+            flex-shrink: 0;
+        }
+        .page-content-wrapper {
+            flex-grow: 1;
+            flex-shrink: 1;
+        }
+    }
+    .page-footer {
+        order: 2;
+        flex-grow: 0;
+        flex-shrink: 0;
+        height: $pageFooterHeight;
+        line-height: $pageFooterHeight;
+        text-align: center;
+        color: #6d6d6d;
+    }
+}
+
+#page-main .page-header {
+    display: flex;
+    flex-direction: row;
+    /* 水平, 从左向右 */
+    justify-content: flex-start;
+    /* 垂直 左对齐 */
+    .page-logo {
+        flex-grow: 0;
+        flex-shrink: 0;
+        padding-left: 20px;
+        padding-right: 20px;
+        width: 195px;
+        display: inline-flex;
+        flex-direction: row;
+        justify-content: space-between;
+        >a {
+            display: inline-block;
+            .logo-default {
+                margin: 18px 0 0;
+                height: 18px;
+            }
+        }
+        .menu-toggler.sidebar-toggler {
+            margin-top: 10.5px;
+        }
+    }
+    .data-menu {
+        flex-grow: 0;
+        flex-shrink: 1;
+        width: 240px;
+        height: 100%;
+        line-height: $pageHeaderHeight;
+        .data-menu-dropdown {
+            height: 100%;
+            line-height: $pageHeaderHeight;
+            font-size: 18px;
+        }
+        .el-select .el-input--suffix .el-input__inner {
+            height: 30px;
+            line-height: 30px;
+        }
+    }
+    .user-menu {
+        flex-grow: 1;
+        flex-shrink: 1;
+        margin: 0 20px 0 0;
+        padding: 0;
+        height: 100%;
+        display: inline-flex;
+        flex-direction: row-reverse;
+        .header-nav{
+            margin-right: 10px;
+            >li{
+                float: left;
+                width: 50px;
+                height: 50px;
+                padding: 0 4px;
+                box-sizing: border-box;
+            }
+        }
+        .user-menu-dropdown {
+            height: 100%;
+            line-height: $pageHeaderHeight;
+            font-size: 18px;
+            margin-left: 20px;
+        }
+    }
+}
+
+#page-main .page-container {
+    .page-sidebar-wrapper {
+        .el-menu {}
+    }
+}
+
+#page-main .page-container #page-sidebar-wrapper {
+    .sidebar-menu.el-menu {
+        border-right: none;
+        padding-bottom: 10px;
+        .el-submenu {
+            .el-menu-item {
+                height: $pageSidebarItemHeight;
+                line-height: $pageSidebarItemHeight;
+            }
+        }
+    }
+}
+
+
+/** sidebar 弹框 */
+
+.el-menu--vertical {
+    .el-menu.el-menu--popup.el-menu--popup-right-start {
+        .el-menu-item.sidebar-menu-item {
+            height: $pageSidebarItemHeight;
+            line-height: $pageSidebarItemHeight;
+        }
+    }
+}
+
+#page-main .page-container .page-content-wrapper {
+    margin: 0px 10px 10px 10px;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    overflow: hidden;
+    .page-bar {
+        flex-grow: 0;
+        flex-shrink: 0;
+        height: 30px;
+        padding: 0 10px;
+        margin-bottom: 10px;
+        border-bottom: 1px solid #333;
+        .el-breadcrumb {
+            font-size: 14px;
+            line-height: 30px;
+        }
+    }
+    .page-content {
+        flex-grow: 1;
+        flex-shrink: 1;
+        overflow-y: auto;
+    }
+}
+
+// #page-main #page-container #page-content-wrapper .page-content.page-table-template {
+#page-main #page-container #page-content-wrapper .page-table-template {
+    // display: flex;
+    // flex-direction: column;
+    // justify-content: flex-start;
+    // align-items: stretch;
+    // flex-grow: 1;
+    // flex-shrink: 1;
+    height: 100%;
+    >.query-form {
+        flex-grow: 0;
+        flex-shrink: 0;
+    }
+    >.data-table {
+        // flex-grow: 1;
+        // flex-shrink: 1;
+        height: calc(100% - 54px);
+        // overflow-x: auto;
+        // overflow-y: auto;
+        background: #fff;
+        // margin-top: 10px;
+    }
+    >.data-table-pagination {
+        //     z-index: 99999;
+        margin-top: 10px;
+        //     flex-grow: 0;
+        //     flex-shrink: 0;
+        //     display: flex;
+        //     justify-content: flex-end;
+        text-align: right;
+        //     position: fixed;
+        //     bottom: 0;
+        //     background: #f2f2f2;
+        //     left: 242px;
+        //     right: 0;
+        //     padding-top: 10px;
+        //     padding-bottom: 10px;
+    }
+}
+
+#page-main #page-container #page-content-wrapper .page-content>.query-form {
+    padding: 5px 10px 0px 10px;
+    margin-bottom: 10px;
+    .el-form {
+        .el-form-item {
+            margin-bottom: 5px;
+            vertical-align: middle;
+            .el-form-item__label {
+                //min-width: 72px;
+            }
+            .el-form-item__content {
+                margin-left: 0px;
+            }
+        }
+        .halving_line {
+            width: 100%;
+            border-top: 1px solid #dfe6ec;
+        }
+    }
+}
+
+#page-main #page-container #page-content-wrapper .page-content .data-table {}
+
+// #page-main #page-container #page-content-wrapper .page-content  .data-table {
+.data-table {
+    position: relative;
+    >.el-table {
+        position: absolute;
+        .el-table__header-wrapper {
+            .el-table__header {
+                thead tr th {}
+            }
+        }
+        .el-table__body-wrapper {
+            .el-table__body {
+                tr td {
+                    padding: 6px 0;
+                    .cell {
+                        line-height: 20px;
+                    }
+                    &.btn {
+                        padding: 2px 0;
+                    }
+                }
+            }
+        }
+    }
+}
+
+#page-main #page-container #page-content-wrapper .page-content.input-form {
+    form {
+        max-width: 800px;
+        margin: 50px auto;
+        >.el-form-item {
+            margin-bottom: 18px;
+            >.el-form-item__label {
+                width: 180px;
+            }
+            >.el-form-item__content {
+                max-width: 600px;
+                margin-left: 180px;
+            }
+        }
+    }
+}
+
+#page-main.page-sidebar-closed {
+    .page-header {
+        .page-logo {
+            padding-left: 20px;
+            padding-right: 20px;
+            width: 20px;
+            >a {
+                display: none;
+            }
+        }
+    }
+    .page-container {
+        .page-sidebar-wrapper {
+            width: $pageSidebarClosedWidth;
+        }
+    }
+}
+
+// @media screen and (max-width: 1000px) {
+//     #page-main {
+//         .page-header {
+//             .page-logo {
+//                 padding-left: 20px;
+//                 padding-right: 20px;
+//                 width: 20px;
+//                 > a {
+//                     display: none;
+//                 }
+//             }
+//         }
+//         .page-container {
+//             .page-sidebar-wrapper {
+//                 width: $pageSidebarClosedWidth;
+//             }
+//         }
+//     }
+// }
+// @media screen and (max-width: 800px) {
+//     #page-main {
+//         .page-header {
+//             .page-logo {
+//                 display: none;
+//             }
+//         }
+//         .page-container {
+//             display: flex;
+//             flex-direction: column;
+//             justify-content: flex-start;
+//             .page-sidebar-wrapper {
+//                 width: 100%;
+//             }
+//         }
+//     }
+// }
+// @media screen and (max-height: 700px) {
+//     #page-main {
+//         .page-footer {
+//             display: none;
+//         }
+//     }
+// }
+.content-scrollbar {
+    height: calc(100vh - 33px);
+    background: #364150;
+    .el-scrollbar__bar {
+        &.is-vertical {
+            display: none;
+        }
+    }
+}
+
+#page-main #page-container #page-content-wrapper .box {
+    display: flex;
+    flex-direction: column;
+    .el-tabs {
+        flex-grow: 1;
+        flex-shrink: 1;
+        display: flex;
+        flex-direction: column;
+        .el-tabs__header {
+            flex-grow: 0;
+            flex-shrink: 0;
+        }
+        .el-tabs__content {
+            flex-grow: 1;
+            flex-shrink: 1;
+            display: flex;
+            flex-direction: column;
+            .el-tab-pane {
+                height: 100%;
+                // flex-grow: 1;
+                // flex-shrink: 1;
+                display: flex;
+                flex-direction: column;
+                // // > div {
+                // //     flex-grow: 1;
+                // //     flex-shrink: 1;
+                // // }
+            }
+        }
+    }
+}

+ 106 - 0
src/framework/style/theme.scss

@@ -0,0 +1,106 @@
+$colorWhite: #fff;
+
+$pageHeaderBackground: #2b3643;
+.body {
+    color: #333;
+}
+#app #page-main {
+    .page-header {
+        background: $pageHeaderBackground;
+
+        .page-logo {
+            .menu-toggler.sidebar-toggler {
+                color: $colorWhite;
+            }
+        }
+
+        .data-menu {
+            .data-menu-dropdown {
+                color: $colorWhite;
+            }
+            .el-select .el-input--suffix .el-input__inner {
+                background: #e3e4e6;
+            }
+        }
+        .user-menu {
+            .user-menu-dropdown {
+                color: $colorWhite;
+            }
+        }
+    }
+
+    #page-container {
+        background: #f2f2f2;
+        overflow: hidden;
+        #page-sidebar-wrapper {
+            color: azure;
+            background-color: #364150;
+            .sidebar-menu.el-menu {
+                background: #364150;
+
+                .sidebar-menu-submenu.el-submenu {
+                    &.is-active>.el-submenu__title {
+                        background: #36c6d3 !important;
+                        color: $colorWhite !important;
+                    }
+                    .el-submenu__title {
+                        height: 40px;
+                        line-height: 40px;
+                        &:hover {
+                            background: #000;
+                        }
+                    }
+                }
+                .sidebar-menu-item.el-menu-item {
+                    height: 40px;
+                    line-height: 40px;
+                    &:hover {
+                        background: #000;
+                    }
+                    &.is-active {
+                        background: #36c6d3 !important;
+                        color: $colorWhite !important;
+                    }
+                }
+            }
+        }
+        .page-content-wrapper {
+            .page-content {
+                .query-form {
+                    border: 1px solid #dfe6ec;
+                    background-color: $colorWhite;
+                }
+                .data-table {
+                    .el-table__header-wrapper {
+                        .el-table__header {
+                            thead tr th {
+                                background-color: #d9d9d9;
+                                color: #000;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    .page-footer {
+        background: #28303b;
+    }
+}
+
+.el-menu--vertical {
+    .el-menu.el-menu--popup.el-menu--popup-right-start {
+        .el-menu-item.sidebar-menu-item.is-active {
+            background: #36c6d3 !important;
+            color: $colorWhite !important;
+        }
+    }
+}
+
+.sidebar-menu-item.el-menu-item,
+.sidebar-menu-submenu.el-submenu .el-submenu__title {
+    i {
+        color: $colorWhite;
+    }
+}

+ 30 - 0
src/main.js

@@ -0,0 +1,30 @@
+// 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 store from './store'
+import 'element-ui/lib/theme-chalk/base.css';
+import CollapseTransition from 'element-ui/lib/transitions/collapse-transition';
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import './framework/style/awesome.less'
+Vue.use(uploader)
+Vue.use(ElementUI, { size: "small", zIndex: 1000 })
+
+Vue.component(CollapseTransition.name, CollapseTransition)
+import './framework/style/layout.scss'
+
+import '@/assets/style/style.scss'
+import '@/assets/style/iconfont/iconfont.css'
+
+Vue.config.productionTip = false
+Vue.config.devtools = true;
+/* eslint-disable no-new */
+new Vue({
+    el: '#app',
+    router,
+    store,
+    components: { App },
+    template: '<App/>'
+})

+ 18 - 0
src/router/index.js

@@ -0,0 +1,18 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+import authutils from '@/utils/authutils'
+import systemRoute from './system'
+Vue.use(Router)
+
+let routes = []
+// routes = routes.concat(demoRoute)
+routes = routes.concat(systemRoute)
+
+const router = new Router({
+    mode: 'history',
+    routes: routes
+})
+
+router.beforeEach(authutils.routerBeforeEach)
+
+export default router

+ 30 - 0
src/router/system.js

@@ -0,0 +1,30 @@
+import LayoutMain from '@/framework/layout/Main'
+import Dasboard from '@/views/dasboard'
+
+/**模型管理 */
+import modelFile from '@/views/model/file'
+
+export default [{
+        path: '/',
+        name: '',
+        component: LayoutMain,
+        children: [
+            { path: '', name: 'blank', component: Dasboard }
+        ]
+    },
+    //模型管理
+    {
+        path: '/model',
+        name: 'LayoutMain',
+        component: LayoutMain,
+        children: [
+            { path: '', name: 'modelFile', component: modelFile },
+            {
+                path: 'file',
+                name: 'modelFile',
+                component: modelFile,
+                meta: { keepAlive: false, breadcrumbs: [{ label: '模型管理', path: '/model/file' }, { label: '模型文件管理' }] }
+            },
+        ]
+    },
+]

+ 38 - 0
src/store/index.js

@@ -0,0 +1,38 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import storage from '@/utils/storage'
+import layout from './modules/layout-store'
+Vue.use(Vuex)
+const KEY_LAST_ROUTE = 'last_route'
+
+export default new Vuex.Store({
+  state: {
+    flag: 'sagacloud-admin',
+    ssoToken: null,
+    lastRoute: null
+  },
+  getters: {
+    flag: state => state.flag,
+    ssoToken: state => state.ssoToken,
+    lastRoute: state => {
+      if (!state.lastRoute) {
+        let lastRoute = storage.get(KEY_LAST_ROUTE)
+        if (lastRoute) {
+          state.lastRoute = lastRoute
+        }
+      }
+      return state.lastRoute
+    }
+  },
+  mutations: {
+    setSsoToken: (state, val) => (state.ssoToken = val),
+    setLastRoute: (state, val) => {
+      state.lastRoute = val
+      storage.set(KEY_LAST_ROUTE, val)
+    }
+  },
+  actions: {},
+  modules: {
+    layout
+  }
+})

+ 153 - 0
src/store/modules/layout-store.js

@@ -0,0 +1,153 @@
+import frameworkApi from '@/api/framework'
+import storage from '@/utils/storage'
+import lStorage from '@/utils/localStorage'
+
+const KEY_MENU_SELECTED = 'menu_selected'
+const KEY_PROJECT_SELECTED = 'global_project_selected'
+const KEY_PAGE_BRANDCRUMB = 'page_brandcrumb'
+
+export default {
+  namespaced: true,
+    state: {
+      sidebarClosed: false,
+      sidebarSelected: '', // sidebar选中的选项
+      userInfo: null, //{ username: 'admin' },
+      permissions: {
+        "system:role:delete": true,
+        "system:role:create": true,
+        "system:role:query": true,
+        "system:role:setOpts": true
+      },
+      projectId: '',
+      projects: [],
+      breadcrumb: [],
+      uploaderList: [], //当前上传文件列表
+      secret: "", //项目密码
+      userId: "", //用户id
+      rowEdit: false,//表格数据变化
+    },
+    getters: {
+      sidebarClosed: state => state.sidebarClosed,
+      secret: state => state.secret,
+      userId: state => state.userId,
+      sidebarSelected: state => {
+        // if (!state.pageSidebarSelected) {
+        //     let menu = storage.get(KEY_MENU_SELECTED)
+        //     if (menu) {
+        //         state.pageSidebarSelected = menu
+        //     }
+        // }
+        // return state.pageSidebarSelected
+        return state.sidebarSelected
+      },
+      userInfo: state => state.userInfo,
+      permissions: state => state.permissions,
+      projects: state => state.projects,
+      uploaderList: state => state.uploaderList,
+      projectId: state => {
+        if (!state.projectId) {
+          let pid = storage.get(KEY_PROJECT_SELECTED)
+          if (pid) {
+            state.projectId = pid
+          }
+        }
+        return state.projectId
+      },
+      breadcrumb: state => {
+        if (!state.breadcrumb) {
+          let arr = storage.get(KEY_PAGE_BRANDCRUMB)
+          if (arr) {
+            state.breadcrumb = arr
+          }
+        }
+        return state.breadcrumb
+      }
+    },
+    mutations: {
+      setRowEdit: (state, val) => (state.rowEdit = val),
+      setSidebarClosed: (state, val) => (state.sidebarClosed = val),
+      setSidebarSelected: (state, val) => {
+        state.sidebarSelected = val
+        storage.set(KEY_MENU_SELECTED, val)
+        lStorage.set('screen_data', {path: val, data: {}})
+      },
+      setprojectId: (state, val) => {
+        lStorage.remove('cacheInfo') //待删除(删除用户浏览器无用缓存)
+        let cacheInfo = lStorage.get('historyInfo') ? lStorage.get('historyInfo') : {}
+        state.projectId = val
+        localStorage.setItem('projectId', val)
+        if (cacheInfo[state.userInfo.userName]) {
+          // cacheInfo[state.userInfo.userName].projectId = val
+          cacheInfo[state.userInfo.userName] = [...new Set([val, ...cacheInfo[state.userInfo.userName]])].slice(0,3)
+          lStorage.set('historyInfo', cacheInfo)
+        } else {
+          cacheInfo[state.userInfo.userName] = [val]
+          lStorage.set('historyInfo', cacheInfo)
+        }
+        storage.set(KEY_PROJECT_SELECTED, val)
+        state.projects.map((item) => {
+          if (item.id == val) {
+            state.secret = item.pwd
+            localStorage.setItem('secret', item.pwd)
+          }
+        })
+      },
+      setUploaderList: (state, val) => {
+        state.uploaderList = val ? val : []
+      },
+
+    },
+  actions: {
+    setRowEdit(contentx, value) {
+      contentx.commit('setRowEdit', value)
+    },
+    loadUserInfo({state}) {
+      console.log(state)
+      return new Promise((resolve, reject) => {
+        frameworkApi.loadUserInfo().then(resp => {
+          console.log(resp)
+          if (resp.Result == 'success' && resp.UserId) {
+            state.userInfo = {userName: resp.Username, userId: resp.UserId}
+            state.userId = resp.UserId
+            storage.set('user_name', resp.Username)
+            storage.set('user_id', resp.UserId)
+            state.permissions = {}
+            if (resp.Permissions) {
+              resp.Permissions.forEach(p => (state.permissions[p] = true))
+            }
+            state.projects = []
+            if (resp.Projects) {
+              if (resp.Projects[0] && resp.Projects[0].ProjId) {
+                state.projectId = resp.Projects[0].ProjId
+                state.secret = resp.Projects[0].Secret ? resp.Projects[0].Secret : ""
+              }
+              resp.Projects.forEach(proj =>
+                state.projects.push({
+                  id: proj.ProjId,
+                  name: proj.ProjLocalName,
+                  pwd: proj.Secret ? proj.Secret : ""
+                })
+              )
+            }
+          } else {
+            state.userInfo = null
+
+          }
+          resolve(resp)
+        })
+      })
+    },
+    setBreadcrumb: {
+      root: true,
+      handler({state, commit}, val) {
+        let label = val[0].label;
+        if (label === "消息中心") {
+          commit("setSidebarSelected", "message"); // 当进入消息中心页面的时候不选中导航栏
+        }
+        state.breadcrumb = []
+        state.breadcrumb = val
+        storage.set(KEY_PAGE_BRANDCRUMB, val)
+      }
+    }
+  }
+}

+ 116 - 0
src/utils/authutils.js

@@ -0,0 +1,116 @@
+import store from '@/store'
+import menus from '@/data/menus'
+import session from "@/utils/storage"
+import { MessageBox } from 'element-ui'
+
+function toLogin() {
+    let ssoServer = process.env.SSO_SERVER
+    let redirectUrl = window.location.protocol + '//' + window.location.host
+    console.log('tologin ', `${ssoServer}/login?redirectUrl=${redirectUrl}/auth`)
+    window.location.href = `${ssoServer}/login?redirectUrl=${redirectUrl}/auth`
+}
+
+function checkMenu(menu, ps) {
+    let result = { name: menu.name, icon: menu.icon, path: menu.path }
+    if (menu.children) {
+        // 如果有下级菜单权限,则自动拥有上级菜单权限
+        result.children = []
+        menu.children.forEach(child => {
+            let submenu = checkMenu(child, ps)
+            if (submenu) {
+                result.children.push(submenu)
+            }
+        })
+        return result.children.length > 0 ? result : null
+    } else if (menu.opts) {
+        return menu.opts.some(opt => ps[opt.permission]) ? result : null
+    } else {
+        // 如果没有下级菜单且没有opts属性, 菜单可以直接访问,不需要权限
+        return result
+    }
+}
+
+export default {
+    /**
+     *  路由守卫, 每次路由跳转时验证登录
+     * @param {*} to
+     * @param {*} from
+     * @param {*} next
+     */
+    routerBeforeEach: async function(to, from, next) {
+        console.log('router before ', to)
+        if (to.path == '/auth' || to.path == '/nouser') {
+            next()
+        } else {
+            let userInfo = store.getters['layout/userInfo']
+            console.log("user info ", userInfo)
+            if (!userInfo) {
+                // 本地是未登录状态, 保存目标页面地址, 去登录
+                let lastRoute = { path: to.path, params: to.params, query: to.query }
+                store.commit('setLastRoute', lastRoute)
+                toLogin()
+            } else {
+                if (to.meta.breadcrumbs) {
+                    store.dispatch('setBreadcrumb', to.meta.breadcrumbs)
+                }
+                if(
+                    (from.path == "/ledger/rentadd" && session.get("rentAddData") && session.get("rentAddData").length) ||
+                    (from.path == "/ledger/cenoteadd" && session.get("cenoteAddData") && session.get("cenoteAddData").length) ||
+                    (from.path == "/ledger/deviceadd" && session.get("deviceAddData") && session.get("deviceAddData").length) ||
+                    (from.path == "/ledger/spaceadd" && session.get("spaceAddData") && session.get("spaceAddData").length) ||
+                    (from.path == "/ledger/propertyadd" && session.get("propertyAddData") && session.get("propertyAddData").length) ||
+                    (from.path == "/ledger/partsadd" && session.get("partsAddData") && session.get("partsAddData").length) ||
+                    (from.path == "/ledger/systemadd" && session.get("systemAddData") && session.get("systemAddData").length)
+                ) { //判断新增信息是否保存
+                    MessageBox.confirm("新增信息未保存,离开将丢失新增信息,是否继续?", "提示", {
+                        confirmButtonText: '确定',
+                        cancelButtonText: '取消',
+                        type: 'warning'
+                    }).then(() => {
+                        let arr = from.path.split("/")
+                        let srt = arr[arr.length - 1].slice(0,-3)
+                        session.remove(`${srt}AddData`)
+                        next()
+                    }).catch(() => {
+                        next(false)
+                    })
+                } else {
+                    next()
+                }
+            }
+            return true
+        }
+    },
+    toNoUser(){
+        console.log(this)
+        this.$router.replace('/nouser')
+    },
+
+    getMenus(permissions) {
+        let result = []
+        let allMenus = []
+        // 开发环境下展示demo页面
+        // if (process.env.NODE_ENV === 'development') {
+        //     allMenus = menus.demoMenus
+        // }
+        allMenus = allMenus.concat(menus.menus)
+        let ps = !permissions ? {} : permissions
+        allMenus.forEach(item => {
+            let menu = checkMenu(item, ps)
+            if (menu) {
+                result.push(menu)
+            }
+        })
+        return result
+    },
+
+    toLogout() {
+        // TODO
+        store.commit('setSsoToken', null)
+        let ssoServer = process.env.SSO_SERVER
+        let redirectUrl = window.location.protocol + '//' + window.location.host + '/'
+        window.location.href = `${ssoServer}/logout?redirectUrl=${redirectUrl}`
+    },
+
+    toLoginPage: toLogin
+}

+ 2 - 0
src/utils/bus.js

@@ -0,0 +1,2 @@
+import Vue from 'vue'
+export default new Vue

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 381 - 0
src/utils/getFirstLetter.js


+ 33 - 0
src/utils/localStorage.js

@@ -0,0 +1,33 @@
+const PREFIX = '_sagacloud_admin_store_'
+const loaclStorage = window.localStorage
+
+export default {
+    set(key, value, fn) {
+        let _value = null
+        try {
+            _value = JSON.stringify(value)
+        } catch (e) {
+            _value = value
+        }
+        loaclStorage.setItem(PREFIX + key, _value)
+        fn && fn()
+    },
+    get(key, fn) {
+        if (!key) {
+            return null
+        }
+        if (typeof key === 'object') {
+            throw new Error('key不能是一个对象。')
+        }
+        var _value = loaclStorage.getItem(PREFIX + key)
+        if (_value !== null) {
+            try {
+                return JSON.parse(_value)
+            } catch (e) {}
+        }
+        return _value
+    },
+    remove(key) {
+        loaclStorage.removeItem(PREFIX + key)
+    }
+}

+ 33 - 0
src/utils/storage.js

@@ -0,0 +1,33 @@
+const PREFIX = '_sagacloud_admin_store_'
+const sessionStorage = window.sessionStorage
+
+export default {
+    set(key, value, fn) {
+        let _value = null
+        try {
+            _value = JSON.stringify(value)
+        } catch (e) {
+            _value = value
+        }
+        sessionStorage.setItem(PREFIX + key, _value)
+        fn && fn()
+    },
+    get(key, fn) {
+        if (!key) {
+            return null
+        }
+        if (typeof key === 'object') {
+            throw new Error('key不能是一个对象。')
+        }
+        var _value = sessionStorage.getItem(PREFIX + key)
+        if (_value !== null) {
+            try {
+                return JSON.parse(_value)
+            } catch (e) {}
+        }
+        return _value
+    },
+    remove(key) {
+        sessionStorage.removeItem(PREFIX + key)
+    }
+}

+ 64 - 0
src/utils/tools.js

@@ -0,0 +1,64 @@
+const tools = {}
+
+//存储cookie
+tools.setCookie = (name, value) => {
+    var Days = 30; /* 设置cookie保存时间 */
+    var exp = new Date();
+    exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
+    document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
+}
+
+
+//获取cookie
+tools.getCookie = name => {
+    var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
+    if (arr = document.cookie.match(reg)) {
+        return unescape(arr[2]);
+    } else {
+        /* 如果没有参数,那么就获取本域下的所有cookie */
+        return document.cookie;
+    }
+}
+
+tools.delObjKey = (obj, key) => {
+    if (obj.hasOwnProperty(key)) {
+        delete obj[key]
+    }
+}
+
+tools.deepCopy = (obj) => {
+    let temp = obj.constructor === Array ? [] : {}
+    for (let val in obj) {
+        temp[val] = typeof obj[val] == 'object' ? tools.deepCopy(obj[val]) : obj[val]
+    }
+    return temp
+}
+
+//判断watch监听的值是否相同
+tools.isSimilarly = (newValue, oldValue) => {
+    if(typeof newValue === typeof oldValue){
+        if(newValue === oldValue){
+            return true
+        } else {
+            let flag = false
+            try {
+                Object.keys(newValue).forEach((item, index, arr) => {
+                    if(oldValue.hasOwnProperty(item) && newValue[item] === oldValue[item]){
+                        if(index == arr.length-1){
+                            flag = true
+                        }
+                    } else {
+                        throw new Error()
+                    }
+                })
+            } catch (error) {
+                return false
+            }
+            return flag
+        }
+    } else {
+        return false
+    }
+}
+
+export default tools

+ 71 - 0
src/views/dasboard/index.vue

@@ -0,0 +1,71 @@
+<template>
+    <div class="main-box">
+        <div class="mian-icon">
+            
+        </div>
+        <div class="main-text">
+            <div class="main-content">
+                <h1>功能设计中。。。</h1>
+                <p class="time"><b>计划完成:</b><slot name="plan"></slot><b>开发完成:</b><slot name="finish"></slot></p>
+                <p><b>上线时间:</b><slot name="onLine"></slot></p>
+                <p class="explain">业务功能说明:</p>
+                <div><slot name="explain"></slot></div>
+            </div>
+        </div>
+    </div>
+    <!-- <h4>开发中...</h4> -->
+    <!-- <button v-if="hasPermission('system:role:query')">测试权限 </button> -->
+</template>
+<script>
+export default {
+    name: 'Dasboard',
+    props: [],
+    data() {
+        return {}
+    },
+    computed: {},
+    methods: {},
+    created() {
+        // console.log('--------------------- index created')
+    },
+    mounted() {},
+    components: {}
+}
+</script>
+<style lang='scss' scoped>
+.main-box{
+    width: 100%;
+    height: 100%;
+    display: flex;
+}
+.mian-icon{
+    flex: 2;
+    text-align: center;
+    position: relative;
+    img{
+        position: absolute;
+        top: 25%;
+    }
+}
+.main-text{
+    flex: 3;
+    position: relative;
+    .main-content{
+        position: absolute;
+        padding-right: 200px;
+        top: 20%;
+        .time{
+            margin-top: 10px;
+        }
+        .explain{
+            margin-top: 20px;
+        }
+        p{
+            font-weight: bold;
+            span{
+                margin-right: 20px;
+            }
+        }
+    }
+}
+</style>

+ 221 - 0
src/views/model/file/index.vue

@@ -0,0 +1,221 @@
+<template>
+  <div id="file_moddle_manage" v-loading="loading">
+    <!-- 左边模型文件夹列表 -->
+    <div class="col_left">
+      <div class="grid-content grid-left">
+        <el-card class="box-card" :body-style="{ padding: '0px', height: '100%' }">
+          <div class="top_hand left_top_hand">
+            <div class="folder-box">
+              <el-tooltip class="item" effect="dark" content="新建文件夹" placement="top-start">
+                <el-button icon="el-icon-plus" size="small" @click="addFolder"></el-button>
+              </el-tooltip>
+              <el-tooltip class="item" effect="dark" content="删除文件夹" placement="top-start">
+                <el-button icon="el-icon-minus" size="small" @click="removeFolder"></el-button>
+              </el-tooltip>
+              <el-tooltip class="item" effect="dark" content="编辑文件夹" placement="top-start">
+                <el-button @click="editFolder" icon="el-icon-edit-outline" size="small"></el-button>
+              </el-tooltip>
+            </div>
+          </div>
+          <div class="folder-list">
+            <div class="head">建筑模型文件夹
+              <span>(建议按建筑模型进行命名)</span>
+            </div>
+            <ul class="lists">
+              <el-scrollbar style="height:100%;">
+                <li @click="openFolder(index,item)" v-for="(item,index) in navigationModel" :key="index"
+                  :class="[choiceIndex == index + 1 ? 'li-active' : '']">
+                  <i :class="[choiceIndex == index + 1 ?  'el-icon-folder-opened':'el-icon-folder','icon_font']" width="40" height="40"></i>
+                  <span :title="item.Name">{{item.Name}}</span>
+                </li>
+              </el-scrollbar>
+            </ul>
+          </div>
+        </el-card>
+      </div>
+    </div>
+    <!-- 右边文件表格 -->
+    <div class="col_right">
+      <el-card class="box-card" :body-style="{ padding: '0px', height:'100%' }">
+        <!-- 顶部操作栏 -->
+        <div class="top_hand right_top_hand">
+          <el-button size="small" @click="addFloorFile">添加楼层文件</el-button>
+          <el-button size="small" @click="queryFloorFile(currentFolderId)">刷新</el-button>
+        </div>
+        <!-- 列表 -->
+        <floorTable v-loading="tableLoading" ref="floorTable" :tableData="tableData" :modelFolderName="currentFolderName" @openModelLog="queryModelLog"
+          @replaceModel="repliaceModel" @percentFinish="queryFloorFile(currentFolderId)"
+          :persentList="persentList"></floorTable>
+      </el-card>
+    </div>
+  </div>
+</template>
+<script>
+import { mapGetters } from "vuex";
+import Bus from '@/utils/bus.js'
+import floorTable from "@/components/model/file/floorTable"; //右侧list表
+export default {
+  computed: {
+    ...mapGetters("layout", ["projectId", "userInfo", "userId", "secret", "uploaderList"])
+  },
+  components: {
+    floorTable
+  },
+  data() {
+    return {
+      addFolderVisible: false, //是否显示新增文件夹弹窗
+      addFloorFileVisible: false, //是否显示增加楼层文件弹窗
+      repliceModelVisible: false, //是否显示替换楼层模型弹窗
+      modelLogVisible: false, //是否显示模型日志弹窗
+      changeFolderNameVisible: false, //是否显示编辑文件夹弹窗
+      folderName: "", //新建文件夹名称
+      navigationModel: [], //文件夹模型list
+      choiceIndex: 0, //当前文件夹index
+      currentFolderId: "", //当前选择的文件夹id
+      currentFolderName: "", //当前选择文件夹的Name
+      currentFloorModelId: "", //当前选择的楼层文件id
+      tableData: [],
+      loading: false, //加载loading
+      tableLoading: false, //表格加载loading
+      logData: [], //楼层文件对应的模型日志
+      replaceModelItem: null, //替换文件的item
+      uploadFloorModelIdList: [], //上传楼层文件得数组,上传完成则为空
+      //请求头
+      headers: {
+        ProjectId: ""
+      },
+      updataData: {
+        model: {}
+      },
+      persentList: [], //请求进度列表
+      isJump: true //是否可以调整页面
+    };
+  },
+  mounted() {
+    // // 文件选择后的回调
+    // Bus.$on('fileAdded', () => {
+    //   console.log('文件已选择')
+    // });
+    // // 文件上传成功的回调
+    // Bus.$on('fileSuccess', () => {
+    //   console.log('文件上传成功')
+    // });
+  },
+  destroyed() {
+    // Bus.$off('fileAdded');
+    // Bus.$off('fileSuccess');
+  },
+  methods: {
+
+  },
+  watch: {
+
+  },
+  mounted() {
+    Bus.$on('modelStatusChange', message => {
+      this.queryFloorFile(this.currentFolderId);
+    })
+  }
+};
+</script>
+<style lang="less" scoped>
+#file_moddle_manage {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  overflow: hidden !important;
+  .col_left {
+    width: 280px;
+    height: 100%;
+  }
+  .col_right {
+    width: calc(100% - 280px);
+    height: 100%;
+  }
+  .grid-content {
+    height: 100%;
+  }
+  .box-card {
+    height: 100%;
+  }
+  .grid-left {
+    padding-right: 10px;
+    box-sizing: border-box;
+  }
+  // 顶部
+  .top_hand {
+    // height: 60px;
+    width: 100%;
+    padding: 10px;
+    box-sizing: border-box;
+    display: flex;
+  }
+  .left_top_hand {
+    align-items: center;
+    justify-content: space-between;
+    .folder-box {
+      /deep/ .el-button--small {
+        padding: 10px 11px;
+      }
+    }
+  }
+  // 左侧文件夹列表
+  .folder-list {
+    width: 100%;
+    height: calc(100% - 60px);
+    .head {
+      height: 42px;
+      width: 100%;
+      padding-left: 10px;
+      box-sizing: border-box;
+      background: #d9d9d9;
+      color: #2b2b2b;
+      display: flex;
+      justify-content: left;
+      align-items: center;
+      font-weight: bold;
+      span{
+        font-weight: normal;
+        font-size: 12px;
+        color: #606266;
+      }
+    }
+    .lists {
+      width: 100%;
+      margin-top: 10px;
+      height: calc(100% - 52px);
+      overflow-y: auto;
+      li {
+        height: 42px;
+        display: flex;
+        justify-content: left;
+        align-items: center;
+        padding-left: 20px;
+        box-sizing: border-box;
+        color: #555;
+        cursor: pointer;
+        span {
+          padding-left: 6px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+      li:hover {
+        background-color: #f5f7fa;
+        color: #000;
+      }
+      .li-active {
+        background-color: #f5f7fa;
+        color: #000;
+      }
+    }
+  }
+  .icon_font {
+    font-size: 18px;
+  }
+}
+/deep/ .el-scrollbar__wrap {
+  overflow-x: hidden;
+}
+</style>

+ 0 - 0
static/.gitkeep


BIN=BIN
static/favicon.ico