├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .travis.yml ├── LICENSE ├── README-zh.md ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── favicon.ico ├── index.html ├── package.json ├── src ├── App.vue ├── api │ ├── account.js │ ├── indicator.js │ ├── login.js │ ├── nodes.js │ ├── order.js │ ├── robot.js │ ├── strategy.js │ ├── symbol.js │ ├── table.js │ └── user.js ├── assets │ └── 404_images │ │ ├── 404.png │ │ └── 404_cloud.png ├── components │ ├── Breadcrumb │ │ └── index.vue │ ├── Hamburger │ │ └── index.vue │ ├── Pagination │ │ └── index.vue │ └── SvgIcon │ │ └── index.vue ├── directive │ ├── clipboard │ │ ├── clipboard.js │ │ └── index.js │ ├── el-dragDialog │ │ ├── drag.js │ │ └── index.js │ ├── el-table │ │ ├── adaptive.js │ │ └── index.js │ ├── permission │ │ ├── index.js │ │ └── permission.js │ ├── sticky.js │ └── waves │ │ ├── index.js │ │ ├── waves.css │ │ └── waves.js ├── icons │ ├── index.js │ ├── svg │ │ ├── 404.svg │ │ ├── bug.svg │ │ ├── chart.svg │ │ ├── clipboard.svg │ │ ├── component.svg │ │ ├── dashboard.svg │ │ ├── documentation.svg │ │ ├── drag.svg │ │ ├── edit.svg │ │ ├── email.svg │ │ ├── example.svg │ │ ├── excel.svg │ │ ├── exit-fullscreen.svg │ │ ├── eye-open.svg │ │ ├── eye.svg │ │ ├── form.svg │ │ ├── fullscreen.svg │ │ ├── guide 2.svg │ │ ├── guide.svg │ │ ├── icon.svg │ │ ├── international.svg │ │ ├── language.svg │ │ ├── link.svg │ │ ├── list.svg │ │ ├── lock.svg │ │ ├── message.svg │ │ ├── money.svg │ │ ├── nested.svg │ │ ├── password.svg │ │ ├── pdf.svg │ │ ├── people.svg │ │ ├── peoples.svg │ │ ├── qq.svg │ │ ├── search.svg │ │ ├── shopping.svg │ │ ├── size.svg │ │ ├── star.svg │ │ ├── tab.svg │ │ ├── table.svg │ │ ├── theme.svg │ │ ├── tree-table.svg │ │ ├── tree.svg │ │ ├── user.svg │ │ ├── wechat.svg │ │ └── zip.svg │ └── svgo.yml ├── main.js ├── permission.js ├── router │ └── index.js ├── store │ ├── getters.js │ ├── index.js │ └── modules │ │ ├── app.js │ │ └── user.js ├── styles │ ├── btn.scss │ ├── element-ui.scss │ ├── element-variables.scss │ ├── index.scss │ ├── mixin.scss │ ├── sidebar.scss │ ├── transition.scss │ └── variables.scss ├── utils │ ├── auth.js │ ├── clipboard.js │ ├── date.js │ ├── errorLog.js │ ├── i18n.js │ ├── index.js │ ├── openWindow.js │ ├── permission.js │ ├── request.js │ ├── scrollTo.js │ └── validate.js └── views │ ├── 404.vue │ ├── account │ ├── email.vue │ ├── index.vue │ └── info.vue │ ├── dashboard │ └── index.vue │ ├── layout │ ├── Layout.vue │ ├── components │ │ ├── AppMain.vue │ │ ├── Navbar.vue │ │ ├── Sidebar │ │ │ ├── Item.vue │ │ │ ├── Link.vue │ │ │ ├── SidebarItem.vue │ │ │ └── index.vue │ │ └── index.js │ └── mixin │ │ └── ResizeHandler.js │ ├── login │ └── index.vue │ ├── order │ ├── list.vue │ └── profit.vue │ ├── robot │ ├── index.vue │ ├── info.vue │ └── list.vue │ └── strategy │ ├── index.vue │ ├── indicator.vue │ └── list.vue └── static └── .gitkeep /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins":["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | src/assets 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | sourceType: 'module' 6 | }, 7 | env: { 8 | browser: true, 9 | node: true, 10 | es6: true, 11 | }, 12 | extends: ['plugin:vue/recommended', 'eslint:recommended'], 13 | 14 | // add your custom rules here 15 | //it is base on https://github.com/vuejs/eslint-config-vue 16 | rules: { 17 | "vue/max-attributes-per-line": [2, { 18 | "singleline": 10, 19 | "multiline": { 20 | "max": 1, 21 | "allowFirstLine": false 22 | } 23 | }], 24 | "vue/name-property-casing": ["error", "PascalCase"], 25 | 'accessor-pairs': 2, 26 | 'arrow-spacing': [2, { 27 | 'before': true, 28 | 'after': true 29 | }], 30 | 'block-spacing': [2, 'always'], 31 | 'brace-style': [2, '1tbs', { 32 | 'allowSingleLine': true 33 | }], 34 | 'camelcase': [0, { 35 | 'properties': 'always' 36 | }], 37 | 'comma-dangle': [2, 'never'], 38 | 'comma-spacing': [2, { 39 | 'before': false, 40 | 'after': true 41 | }], 42 | 'comma-style': [2, 'last'], 43 | 'constructor-super': 2, 44 | 'curly': [2, 'multi-line'], 45 | 'dot-location': [2, 'property'], 46 | 'eol-last': 2, 47 | 'eqeqeq': [2, 'allow-null'], 48 | 'generator-star-spacing': [2, { 49 | 'before': true, 50 | 'after': true 51 | }], 52 | 'handle-callback-err': [2, '^(err|error)$'], 53 | 'indent': [2, 2, { 54 | 'SwitchCase': 1 55 | }], 56 | 'jsx-quotes': [2, 'prefer-single'], 57 | 'key-spacing': [2, { 58 | 'beforeColon': false, 59 | 'afterColon': true 60 | }], 61 | 'keyword-spacing': [2, { 62 | 'before': true, 63 | 'after': true 64 | }], 65 | 'new-cap': [2, { 66 | 'newIsCap': true, 67 | 'capIsNew': false 68 | }], 69 | 'new-parens': 2, 70 | 'no-array-constructor': 2, 71 | 'no-caller': 2, 72 | 'no-console': 'off', 73 | 'no-class-assign': 2, 74 | 'no-cond-assign': 2, 75 | 'no-const-assign': 2, 76 | 'no-control-regex': 2, 77 | 'no-delete-var': 2, 78 | 'no-dupe-args': 2, 79 | 'no-dupe-class-members': 2, 80 | 'no-dupe-keys': 2, 81 | 'no-duplicate-case': 2, 82 | 'no-empty-character-class': 2, 83 | 'no-empty-pattern': 2, 84 | 'no-eval': 2, 85 | 'no-ex-assign': 2, 86 | 'no-extend-native': 2, 87 | 'no-extra-bind': 2, 88 | 'no-extra-boolean-cast': 2, 89 | 'no-extra-parens': [2, 'functions'], 90 | 'no-fallthrough': 2, 91 | 'no-floating-decimal': 2, 92 | 'no-func-assign': 2, 93 | 'no-implied-eval': 2, 94 | 'no-inner-declarations': [2, 'functions'], 95 | 'no-invalid-regexp': 2, 96 | 'no-irregular-whitespace': 2, 97 | 'no-iterator': 2, 98 | 'no-label-var': 2, 99 | 'no-labels': [2, { 100 | 'allowLoop': false, 101 | 'allowSwitch': false 102 | }], 103 | 'no-lone-blocks': 2, 104 | 'no-mixed-spaces-and-tabs': 2, 105 | 'no-multi-spaces': 2, 106 | 'no-multi-str': 2, 107 | 'no-multiple-empty-lines': [2, { 108 | 'max': 1 109 | }], 110 | 'no-native-reassign': 2, 111 | 'no-negated-in-lhs': 2, 112 | 'no-new-object': 2, 113 | 'no-new-require': 2, 114 | 'no-new-symbol': 2, 115 | 'no-new-wrappers': 2, 116 | 'no-obj-calls': 2, 117 | 'no-octal': 2, 118 | 'no-octal-escape': 2, 119 | 'no-path-concat': 2, 120 | 'no-proto': 2, 121 | 'no-redeclare': 2, 122 | 'no-regex-spaces': 2, 123 | 'no-return-assign': [2, 'except-parens'], 124 | 'no-self-assign': 2, 125 | 'no-self-compare': 2, 126 | 'no-sequences': 2, 127 | 'no-shadow-restricted-names': 2, 128 | 'no-spaced-func': 2, 129 | 'no-sparse-arrays': 2, 130 | 'no-this-before-super': 2, 131 | 'no-throw-literal': 2, 132 | 'no-trailing-spaces': 2, 133 | 'no-undef': 2, 134 | 'no-undef-init': 2, 135 | 'no-unexpected-multiline': 2, 136 | 'no-unmodified-loop-condition': 2, 137 | 'no-unneeded-ternary': [2, { 138 | 'defaultAssignment': false 139 | }], 140 | 'no-unreachable': 2, 141 | 'no-unsafe-finally': 2, 142 | 'no-unused-vars': [2, { 143 | 'vars': 'all', 144 | 'args': 'none' 145 | }], 146 | 'no-useless-call': 2, 147 | 'no-useless-computed-key': 2, 148 | 'no-useless-constructor': 2, 149 | 'no-useless-escape': 0, 150 | 'no-whitespace-before-property': 2, 151 | 'no-with': 2, 152 | 'one-var': [2, { 153 | 'initialized': 'never' 154 | }], 155 | 'operator-linebreak': [2, 'after', { 156 | 'overrides': { 157 | '?': 'before', 158 | ':': 'before' 159 | } 160 | }], 161 | 'padded-blocks': [2, 'never'], 162 | 'quotes': [2, 'single', { 163 | 'avoidEscape': true, 164 | 'allowTemplateLiterals': true 165 | }], 166 | 'semi': [2, 'never'], 167 | 'semi-spacing': [2, { 168 | 'before': false, 169 | 'after': true 170 | }], 171 | 'space-before-blocks': [2, 'always'], 172 | 'space-before-function-paren': [2, 'never'], 173 | 'space-in-parens': [2, 'never'], 174 | 'space-infix-ops': 2, 175 | 'space-unary-ops': [2, { 176 | 'words': true, 177 | 'nonwords': false 178 | }], 179 | 'spaced-comment': [2, 'always', { 180 | 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ','] 181 | }], 182 | 'template-curly-spacing': [2, 'never'], 183 | 'use-isnan': 2, 184 | 'valid-typeof': 2, 185 | 'wrap-iife': [2, 'any'], 186 | 'yield-star-spacing': [2, 'both'], 187 | 'yoda': [2, 'never'], 188 | 'prefer-const': 2, 189 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 190 | 'object-curly-spacing': [2, 'always', { 191 | objectsInObjects: false 192 | }], 193 | 'array-bracket-spacing': [2, 'never'] 194 | } 195 | } 196 | 197 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | src/views/dashboard/index.vue 17 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | script: npm run test 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present PanJiaChen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # vue-admin-template 2 | 3 | > 这是一个 极简的 vue admin 管理后台 它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。 4 | 5 | [线上地址](http://panjiachen.github.io/vue-admin-template) 6 | 7 | [国内访问](https://panjiachen.gitee.io/vue-admin-template) 8 | 9 | ## Extra 10 | 11 | 如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control) 12 | 13 | 本项目基于`webpack4`开发,若还想使用`webpack3`开发,请使用该分支[webpack3](https://github.com/PanJiaChen/vue-admin-template/tree/webpack3) 14 | 15 | 如果你想使用基于 vue + typescript 的管理后台, 可以看看这个项目: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour)) 16 | 17 | ## 相关项目 18 | 19 | [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) 20 | 21 | [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin) 22 | 23 | [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) 24 | 25 | 写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目: 26 | 27 | - [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2) 28 | - [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac) 29 | - [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35) 30 | - [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56) 31 | - [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836) 32 | 33 | ## Build Setup 34 | 35 | ```bash 36 | # Clone project 37 | git clone https://github.com/PanJiaChen/vue-admin-template.git 38 | 39 | # Install dependencies 40 | npm install 41 | 42 | # 建议不要用cnpm 安装有各种诡异的bug 可以通过如下操作解决npm速度慢的问题 43 | npm install --registry=https://registry.npm.taobao.org 44 | 45 | # Serve with hot reload at localhost:9528 46 | npm run dev 47 | 48 | # Build for production with minification 49 | npm run build 50 | 51 | # Build for production and view the bundle analyzer report 52 | npm run build --report 53 | ``` 54 | 55 | ## Demo 56 | 57 | ![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif) 58 | 59 | ### Element-Ui 使用 cdn 教程 60 | 61 | 首先找到 `index.html` ([根目录下](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/index.html)) 62 | 63 | 引入 Element 的 css 和 js ,并且引入 vue 。因为 Element-Ui 是依赖 vue 的,所以必须在它之前引入 vue 。 64 | 65 | 之后找到 [webpack.base.conf.js](https://github.com/PanJiaChen/vue-admin-template/blob/element-ui-cdn/build/webpack.base.conf.js) 加入 `externals` 让 webpack 不打包 vue 和 element 66 | 67 | ``` 68 | externals: { 69 | vue: 'Vue', 70 | 'element-ui':'ELEMENT' 71 | } 72 | ``` 73 | 74 | 之后还有一个小细节是如果你用了全局对象方式引入 vue,就不需要 手动 `Vue.use(Vuex)` ,它会自动挂载,具体见 [issue](https://github.com/vuejs/vuex/issues/731) 75 | 76 | 最终你可以使用 `npm run build --report` 查看效果 77 | 如图: 78 | ![demo](https://panjiachen.github.io/images/element-cdn.png) 79 | 80 | **[具体代码](https://github.com/PanJiaChen/vue-admin-template/commit/746aff560932704ae821f82f10b8b2a9681d5177)** 81 | 82 | **[对应分支](https://github.com/PanJiaChen/vue-admin-template/tree/element-ui-cdn)** 83 | 84 | ## Browsers support 85 | 86 | Modern browsers and Internet Explorer 10+. 87 | 88 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | 89 | | --------- | --------- | --------- | --------- | 90 | | IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions 91 | 92 | ## License 93 | 94 | [MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license. 95 | 96 | Copyright (c) 2017-present PanJiaChen 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quant-admin 2 | 3 | 4 | ## Build Setup 5 | 6 | ```bash 7 | # Clone project 8 | git clone https://github.com/tokenIsme/quant-admin.git 9 | 10 | # Install dependencies 11 | npm install 12 | 13 | # Serve with hot reload at localhost:9528 14 | npm run dev 15 | 16 | # Build for production with minification 17 | npm run build 18 | 19 | # Build for production and view the bundle analyzer report 20 | npm run build --report 21 | 22 | ``` 23 | ## Donate 24 | 25 | 如果你觉得这个项目帮助到了你,你可以帮作者点个star表示鼓励 :tropical_drink: 26 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write( 23 | stats.toString({ 24 | colors: true, 25 | modules: false, 26 | children: false, 27 | chunks: false, 28 | chunkModules: false 29 | }) + '\n\n' 30 | ) 31 | 32 | if (stats.hasErrors()) { 33 | console.log(chalk.red(' Build failed with errors.\n')) 34 | process.exit(1) 35 | } 36 | 37 | console.log(chalk.cyan(' Build complete.\n')) 38 | console.log( 39 | chalk.yellow( 40 | ' Tip: built files are meant to be served over an HTTP server.\n' + 41 | " Opening index.html over file:// won't work.\n" 42 | ) 43 | ) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec(cmd) { 8 | return require('child_process') 9 | .execSync(cmd) 10 | .toString() 11 | .trim() 12 | } 13 | 14 | const versionRequirements = [ 15 | { 16 | name: 'node', 17 | currentVersion: semver.clean(process.version), 18 | versionRequirement: packageConfig.engines.node 19 | } 20 | ] 21 | 22 | if (shell.which('npm')) { 23 | versionRequirements.push({ 24 | name: 'npm', 25 | currentVersion: exec('npm --version'), 26 | versionRequirement: packageConfig.engines.npm 27 | }) 28 | } 29 | 30 | module.exports = function() { 31 | const warnings = [] 32 | 33 | for (let i = 0; i < versionRequirements.length; i++) { 34 | const mod = versionRequirements[i] 35 | 36 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 37 | warnings.push( 38 | mod.name + 39 | ': ' + 40 | chalk.red(mod.currentVersion) + 41 | ' should be ' + 42 | chalk.green(mod.versionRequirement) 43 | ) 44 | } 45 | } 46 | 47 | if (warnings.length) { 48 | console.log('') 49 | console.log( 50 | chalk.yellow( 51 | 'To use this template, you must update following to modules:' 52 | ) 53 | ) 54 | console.log() 55 | 56 | for (let i = 0; i < warnings.length; i++) { 57 | const warning = warnings[i] 58 | console.log(' ' + warning) 59 | } 60 | 61 | console.log() 62 | process.exit(1) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndexOutOfBounds998/quant-admin/e894b3d33ee3abd2e9b2b946b7e1b5676bce16cb/build/logo.png -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function(_path) { 8 | const assetsSubDirectory = 9 | process.env.NODE_ENV === 'production' 10 | ? config.build.assetsSubDirectory 11 | : config.dev.assetsSubDirectory 12 | 13 | return path.posix.join(assetsSubDirectory, _path) 14 | } 15 | 16 | exports.cssLoaders = function(options) { 17 | options = options || {} 18 | 19 | const cssLoader = { 20 | loader: 'css-loader', 21 | options: { 22 | sourceMap: options.sourceMap 23 | } 24 | } 25 | 26 | const postcssLoader = { 27 | loader: 'postcss-loader', 28 | options: { 29 | sourceMap: options.sourceMap 30 | } 31 | } 32 | 33 | // generate loader string to be used with extract text plugin 34 | function generateLoaders(loader, loaderOptions) { 35 | const loaders = [] 36 | 37 | // Extract CSS when that option is specified 38 | // (which is the case during production build) 39 | if (options.extract) { 40 | loaders.push(MiniCssExtractPlugin.loader) 41 | } else { 42 | loaders.push('vue-style-loader') 43 | } 44 | 45 | loaders.push(cssLoader) 46 | 47 | if (options.usePostCSS) { 48 | loaders.push(postcssLoader) 49 | } 50 | 51 | if (loader) { 52 | loaders.push({ 53 | loader: loader + '-loader', 54 | options: Object.assign({}, loaderOptions, { 55 | sourceMap: options.sourceMap 56 | }) 57 | }) 58 | } 59 | 60 | return loaders 61 | } 62 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 63 | return { 64 | css: generateLoaders(), 65 | postcss: generateLoaders(), 66 | less: generateLoaders('less'), 67 | sass: generateLoaders('sass', { 68 | indentedSyntax: true 69 | }), 70 | scss: generateLoaders('sass'), 71 | stylus: generateLoaders('stylus'), 72 | styl: generateLoaders('stylus') 73 | } 74 | } 75 | 76 | // Generate loaders for standalone style files (outside of .vue) 77 | exports.styleLoaders = function(options) { 78 | const output = [] 79 | const loaders = exports.cssLoaders(options) 80 | 81 | for (const extension in loaders) { 82 | const loader = loaders[extension] 83 | output.push({ 84 | test: new RegExp('\\.' + extension + '$'), 85 | use: loader 86 | }) 87 | } 88 | 89 | return output 90 | } 91 | 92 | exports.createNotifierCallback = () => { 93 | const notifier = require('node-notifier') 94 | 95 | return (severity, errors) => { 96 | if (severity !== 'error') return 97 | 98 | const error = errors[0] 99 | const filename = error.file && error.file.split('!').pop() 100 | 101 | notifier.notify({ 102 | title: packageConfig.name, 103 | message: severity + ': ' + error.name, 104 | subtitle: filename || '', 105 | icon: path.join(__dirname, 'logo.png') 106 | }) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | //You can set the vue-loader configuration by yourself. 5 | 6 | } 7 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const { VueLoaderPlugin } = require('vue-loader') 6 | const vueLoaderConfig = require('./vue-loader.conf') 7 | 8 | function resolve(dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const createLintingRule = () => ({ 13 | test: /\.(js|vue)$/, 14 | loader: 'eslint-loader', 15 | enforce: 'pre', 16 | include: [resolve('src'), resolve('test')], 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | module.exports = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | app: ['./node_modules/babel-polyfill/dist/polyfill.js','./src/main.js'] 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: '[name].js', 31 | publicPath: 32 | process.env.NODE_ENV === 'production' 33 | ? config.build.assetsPublicPath 34 | : config.dev.assetsPublicPath 35 | }, 36 | resolve: { 37 | extensions: ['.js', '.vue', '.json'], 38 | alias: { 39 | '@': resolve('src') 40 | } 41 | }, 42 | module: { 43 | rules: [ 44 | // ...(config.dev.useEslint ? [createLintingRule()] : []), 45 | { 46 | test: /\.vue$/, 47 | loader: 'vue-loader', 48 | options: vueLoaderConfig 49 | }, 50 | { 51 | test: /\.js$/, 52 | loader: 'babel-loader', 53 | include: [ 54 | resolve('src'), 55 | resolve('test'), 56 | resolve('mock'), 57 | resolve('node_modules/webpack-dev-server/client') 58 | ] 59 | }, 60 | { 61 | test: /\.svg$/, 62 | loader: 'svg-sprite-loader', 63 | include: [resolve('src/icons')], 64 | options: { 65 | symbolId: 'icon-[name]' 66 | } 67 | }, 68 | { 69 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 70 | loader: 'url-loader', 71 | exclude: [resolve('src/icons')], 72 | options: { 73 | limit: 10000, 74 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 75 | } 76 | }, 77 | { 78 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 79 | loader: 'url-loader', 80 | options: { 81 | limit: 10000, 82 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 83 | } 84 | }, 85 | { 86 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 87 | loader: 'url-loader', 88 | options: { 89 | limit: 10000, 90 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 91 | } 92 | } 93 | ] 94 | }, 95 | plugins: [new VueLoaderPlugin()], 96 | node: { 97 | // prevent webpack from injecting useless setImmediate polyfill because Vue 98 | // source contains it (although only uses it if it's native). 99 | setImmediate: false, 100 | // prevent webpack from injecting mocks to Node native modules 101 | // that does not make sense for the client 102 | dgram: 'empty', 103 | fs: 'empty', 104 | net: 'empty', 105 | tls: 'empty', 106 | child_process: 'empty' 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const HtmlWebpackPlugin = require('html-webpack-plugin') 9 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 10 | const portfinder = require('portfinder') 11 | 12 | function resolve(dir) { 13 | return path.join(__dirname, '..', dir) 14 | } 15 | 16 | const HOST = process.env.HOST 17 | const PORT = process.env.PORT && Number(process.env.PORT) 18 | 19 | const devWebpackConfig = merge(baseWebpackConfig, { 20 | mode: 'development', 21 | module: { 22 | rules: utils.styleLoaders({ 23 | sourceMap: config.dev.cssSourceMap, 24 | usePostCSS: true 25 | }) 26 | }, 27 | // cheap-module-eval-source-map is faster for development 28 | devtool: config.dev.devtool, 29 | 30 | // these devServer options should be customized in /config/index.js 31 | devServer: { 32 | clientLogLevel: 'warning', 33 | historyApiFallback: true, 34 | disableHostCheck: true, 35 | hot: true, 36 | compress: true, 37 | host: HOST || config.dev.host, 38 | port: PORT || config.dev.port, 39 | open: config.dev.autoOpenBrowser, 40 | overlay: config.dev.errorOverlay 41 | ? { warnings: false, errors: true } 42 | : false, 43 | publicPath: config.dev.assetsPublicPath, 44 | proxy: config.dev.proxyTable, 45 | quiet: true, // necessary for FriendlyErrorsPlugin 46 | watchOptions: { 47 | poll: config.dev.poll 48 | } 49 | }, 50 | plugins: [ 51 | new webpack.DefinePlugin({ 52 | 'process.env': require('../config/dev.env') 53 | }), 54 | new webpack.HotModuleReplacementPlugin(), 55 | // https://github.com/ampedandwired/html-webpack-plugin 56 | new HtmlWebpackPlugin({ 57 | filename: 'index.html', 58 | template: 'index.html', 59 | inject: true, 60 | favicon: resolve('favicon.ico'), 61 | title: '量化交易' 62 | }) 63 | ] 64 | }) 65 | 66 | module.exports = new Promise((resolve, reject) => { 67 | portfinder.basePort = process.env.PORT || config.dev.port 68 | portfinder.getPort((err, port) => { 69 | if (err) { 70 | reject(err) 71 | } else { 72 | // publish the new Port, necessary for e2e tests 73 | process.env.PORT = port 74 | // add port to devServer config 75 | devWebpackConfig.devServer.port = port 76 | 77 | // Add FriendlyErrorsPlugin 78 | devWebpackConfig.plugins.push( 79 | new FriendlyErrorsPlugin({ 80 | compilationSuccessInfo: { 81 | messages: [ 82 | `Your application is running here: http://${ 83 | devWebpackConfig.devServer.host 84 | }:${port}` 85 | ] 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | }) 91 | ) 92 | 93 | resolve(devWebpackConfig) 94 | } 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin') 11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 12 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 13 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 14 | 15 | function resolve(dir) { 16 | return path.join(__dirname, '..', dir) 17 | } 18 | 19 | const env = require('../config/prod.env') 20 | 21 | // For NamedChunksPlugin 22 | const seen = new Set() 23 | const nameLength = 4 24 | 25 | const webpackConfig = merge(baseWebpackConfig, { 26 | mode: 'production', 27 | module: { 28 | rules: utils.styleLoaders({ 29 | sourceMap: config.build.productionSourceMap, 30 | extract: true, 31 | usePostCSS: true 32 | }) 33 | }, 34 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 35 | output: { 36 | path: config.build.assetsRoot, 37 | filename: utils.assetsPath('js/[name].[chunkhash:8].js'), 38 | chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js') 39 | }, 40 | plugins: [ 41 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 42 | new webpack.DefinePlugin({ 43 | 'process.env': env 44 | }), 45 | // extract css into its own file 46 | new MiniCssExtractPlugin({ 47 | filename: utils.assetsPath('css/[name].[contenthash:8].css'), 48 | chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css') 49 | }), 50 | // generate dist index.html with correct asset hash for caching. 51 | // you can customize output by editing /index.html 52 | // see https://github.com/ampedandwired/html-webpack-plugin 53 | new HtmlWebpackPlugin({ 54 | filename: config.build.index, 55 | template: 'index.html', 56 | inject: true, 57 | favicon: resolve('favicon.ico'), 58 | title: '量化交易', 59 | minify: { 60 | removeComments: true, 61 | collapseWhitespace: true, 62 | removeAttributeQuotes: true 63 | // more options: 64 | // https://github.com/kangax/html-minifier#options-quick-reference 65 | } 66 | // default sort mode uses toposort which cannot handle cyclic deps 67 | // in certain cases, and in webpack 4, chunk order in HTML doesn't 68 | // matter anyway 69 | }), 70 | new ScriptExtHtmlWebpackPlugin({ 71 | //`runtime` must same as runtimeChunk name. default is `runtime` 72 | inline: /runtime\..*\.js$/ 73 | }), 74 | // keep chunk.id stable when chunk has no name 75 | new webpack.NamedChunksPlugin(chunk => { 76 | if (chunk.name) { 77 | return chunk.name 78 | } 79 | const modules = Array.from(chunk.modulesIterable) 80 | if (modules.length > 1) { 81 | const hash = require('hash-sum') 82 | const joinedHash = hash(modules.map(m => m.id).join('_')) 83 | let len = nameLength 84 | while (seen.has(joinedHash.substr(0, len))) len++ 85 | seen.add(joinedHash.substr(0, len)) 86 | return `chunk-${joinedHash.substr(0, len)}` 87 | } else { 88 | return modules[0].id 89 | } 90 | }), 91 | // keep module.id stable when vender modules does not change 92 | new webpack.HashedModuleIdsPlugin(), 93 | // copy custom static assets 94 | new CopyWebpackPlugin([ 95 | { 96 | from: path.resolve(__dirname, '../static'), 97 | to: config.build.assetsSubDirectory, 98 | ignore: ['.*'] 99 | } 100 | ]) 101 | ], 102 | optimization: { 103 | splitChunks: { 104 | chunks: 'all', 105 | cacheGroups: { 106 | libs: { 107 | name: 'chunk-libs', 108 | test: /[\\/]node_modules[\\/]/, 109 | priority: 10, 110 | chunks: 'initial' // 只打包初始时依赖的第三方 111 | }, 112 | elementUI: { 113 | name: 'chunk-elementUI', // 单独将 elementUI 拆包 114 | priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app 115 | test: /[\\/]node_modules[\\/]element-ui[\\/]/ 116 | } 117 | } 118 | }, 119 | runtimeChunk: 'single', 120 | minimizer: [ 121 | new UglifyJsPlugin({ 122 | uglifyOptions: { 123 | mangle: { 124 | safari10: true 125 | } 126 | }, 127 | sourceMap: config.build.productionSourceMap, 128 | cache: true, 129 | parallel: true 130 | }), 131 | // Compress extracted CSS. We are using this plugin so that possible 132 | // duplicated CSS from different components can be deduped. 133 | new OptimizeCSSAssetsPlugin() 134 | ] 135 | } 136 | }) 137 | 138 | if (config.build.productionGzip) { 139 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 140 | 141 | webpackConfig.plugins.push( 142 | new CompressionWebpackPlugin({ 143 | algorithm: 'gzip', 144 | test: new RegExp( 145 | '\\.(' + config.build.productionGzipExtensions.join('|') + ')$' 146 | ), 147 | threshold: 10240, 148 | minRatio: 0.8 149 | }) 150 | ) 151 | } 152 | 153 | if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) { 154 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') 155 | .BundleAnalyzerPlugin 156 | 157 | if (config.build.bundleAnalyzerReport) { 158 | webpackConfig.plugins.push( 159 | new BundleAnalyzerPlugin({ 160 | analyzerPort: 8080, 161 | generateStatsFile: false 162 | }) 163 | ) 164 | } 165 | 166 | if (config.build.generateAnalyzerReport) { 167 | webpackConfig.plugins.push( 168 | new BundleAnalyzerPlugin({ 169 | analyzerMode: 'static', 170 | reportFilename: 'bundle-report.html', 171 | openAnalyzer: false 172 | }) 173 | ) 174 | } 175 | } 176 | 177 | module.exports = webpackConfig 178 | -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"', 7 | BASE_API: '"http://localhost:8080"', 8 | }) 9 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.6 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | // Paths 10 | assetsSubDirectory: 'static', 11 | assetsPublicPath: './', 12 | proxyTable: {}, 13 | 14 | // Various Dev Server settings 15 | host: 'localhost', // can be overwritten by process.env.HOST 16 | port: 9528, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 17 | autoOpenBrowser: true, 18 | errorOverlay: true, 19 | notifyOnErrors: false, 20 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 21 | 22 | // Use Eslint Loader? 23 | // If true, your code will be linted during bundling and 24 | // linting errors and warnings will be shown in the console. 25 | useEslint: true, 26 | // If true, eslint errors and warnings will also be shown in the error overlay 27 | // in the browser. 28 | showEslintErrorsInOverlay: false, 29 | 30 | /** 31 | * Source Maps 32 | */ 33 | 34 | // https://webpack.js.org/configuration/devtool/#development 35 | devtool: 'cheap-source-map', 36 | 37 | // CSS Sourcemaps off by default because relative paths are "buggy" 38 | // with this option, according to the CSS-Loader README 39 | // (https://github.com/webpack/css-loader#sourcemaps) 40 | // In our experience, they generally work as expected, 41 | // just be aware of this issue when enabling this option. 42 | cssSourceMap: false 43 | }, 44 | 45 | build: { 46 | // Template for index.html 47 | index: path.resolve(__dirname, '../dist/index.html'), 48 | 49 | // Paths 50 | assetsRoot: path.resolve(__dirname, '../dist'), 51 | assetsSubDirectory: 'static', 52 | 53 | /** 54 | * You can set by youself according to actual condition 55 | * You will need to set this if you plan to deploy your site under a sub path, 56 | * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/, 57 | * then assetsPublicPath should be set to "/bar/". 58 | * In most cases please use '/' !!! 59 | */ 60 | assetsPublicPath: '/', 61 | 62 | /** 63 | * Source Maps 64 | */ 65 | 66 | productionSourceMap: false, 67 | // https://webpack.js.org/configuration/devtool/#production 68 | devtool: 'source-map', 69 | 70 | // Gzip off by default as many popular static hosts such as 71 | // Surge or Netlify already gzip all static assets for you. 72 | // Before setting to `true`, make sure to: 73 | // npm install --save-dev compression-webpack-plugin 74 | productionGzip: false, 75 | productionGzipExtensions: ['js', 'css'], 76 | 77 | // Run the build command with an extra argument to 78 | // View the bundle analyzer report after build finishes: 79 | // `npm run build --report` 80 | // Set to `true` or `false` to always turn it on or off 81 | bundleAnalyzerReport: process.env.npm_config_report || false, 82 | 83 | // `npm run build:prod --generate_report` 84 | generateAnalyzerReport: process.env.npm_config_generate_report || false 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"', 4 | BASE_API: '"http://47.95.214.100:8180"', 5 | } 6 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndexOutOfBounds998/quant-admin/e894b3d33ee3abd2e9b2b946b7e1b5676bce16cb/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= htmlWebpackPlugin.options.title %> 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cyy", 3 | "version": "3.9.0", 4 | "license": "MIT", 5 | "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", 6 | "author": "cyy", 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js", 11 | "build:report": "npm_config_report=true npm run build", 12 | "lint": "eslint --ext .js,.vue src", 13 | "test": "npm run lint", 14 | "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml" 15 | }, 16 | "dependencies": { 17 | "axios": "0.18.0", 18 | "babel-polyfill": "^6.26.0", 19 | "echarts": "^4.2.1", 20 | "element-ui": "2.4.6", 21 | "js-cookie": "2.2.0", 22 | "mockjs": "1.0.1-beta3", 23 | "normalize.css": "7.0.0", 24 | "nprogress": "0.2.0", 25 | "stompjs": "^2.3.3", 26 | "v-charts": "^1.19.0", 27 | "vue": "2.5.17", 28 | "vue-router": "3.0.1", 29 | "vue-trading-view": "^1.0.1", 30 | "vuetify": "^1.5.14", 31 | "vuex": "3.0.1" 32 | }, 33 | "devDependencies": { 34 | "autoprefixer": "8.5.0", 35 | "babel-core": "6.26.0", 36 | "babel-eslint": "8.2.6", 37 | "babel-helper-vue-jsx-merge-props": "2.0.3", 38 | "babel-loader": "7.1.5", 39 | "babel-plugin-syntax-jsx": "6.18.0", 40 | "babel-plugin-transform-runtime": "6.23.0", 41 | "babel-plugin-transform-vue-jsx": "3.7.0", 42 | "babel-preset-env": "1.7.0", 43 | "babel-preset-stage-2": "6.24.1", 44 | "chalk": "2.4.1", 45 | "compression-webpack-plugin": "2.0.0", 46 | "copy-webpack-plugin": "4.5.2", 47 | "css-loader": "1.0.0", 48 | "eslint": "4.19.1", 49 | "eslint-friendly-formatter": "4.0.1", 50 | "eslint-loader": "2.0.0", 51 | "eslint-plugin-vue": "4.7.1", 52 | "eventsource-polyfill": "0.9.6", 53 | "file-loader": "1.1.11", 54 | "friendly-errors-webpack-plugin": "1.7.0", 55 | "html-webpack-plugin": "4.0.0-alpha", 56 | "mini-css-extract-plugin": "0.4.1", 57 | "node-notifier": "5.2.1", 58 | "node-sass": "^4.7.2", 59 | "optimize-css-assets-webpack-plugin": "5.0.0", 60 | "ora": "3.0.0", 61 | "path-to-regexp": "2.4.0", 62 | "portfinder": "1.0.16", 63 | "postcss-import": "12.0.0", 64 | "postcss-loader": "2.1.6", 65 | "postcss-url": "7.3.2", 66 | "rimraf": "2.6.2", 67 | "sass-loader": "7.0.3", 68 | "script-ext-html-webpack-plugin": "2.0.1", 69 | "semver": "5.5.0", 70 | "shelljs": "0.8.2", 71 | "svg-sprite-loader": "3.8.0", 72 | "svgo": "1.0.5", 73 | "uglifyjs-webpack-plugin": "1.2.7", 74 | "url-loader": "1.0.1", 75 | "vue-loader": "15.3.0", 76 | "vue-style-loader": "4.1.2", 77 | "vue-template-compiler": "2.5.17", 78 | "webpack": "4.16.5", 79 | "webpack-bundle-analyzer": "3.3.2", 80 | "webpack-cli": "3.1.0", 81 | "webpack-dev-server": "3.1.14", 82 | "webpack-merge": "4.1.4" 83 | }, 84 | "engines": { 85 | "node": ">= 6.0.0", 86 | "npm": ">= 3.0.0" 87 | }, 88 | "browserslist": [ 89 | "> 1%", 90 | "last 2 versions", 91 | "not ie <= 8" 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/api/account.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getAccountList(query) { 4 | return request({ 5 | url: '/account/accountsByUid', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function getAccounts(query) { 12 | return request({ 13 | url: '/account/accounts', 14 | method: 'get', 15 | params: query 16 | }) 17 | } 18 | /** 19 | * 获取账户余额 20 | */ 21 | export function getBalanceList(query) { 22 | return request({ 23 | url: '/balance/getBalanceList', 24 | method: 'get', 25 | params: query 26 | }) 27 | 28 | } 29 | 30 | export function createOrUpdateAccount(data) { 31 | return request({ 32 | url: '/account/addOrUpdate', 33 | method: 'post', 34 | data: data 35 | }) 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/api/indicator.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function backTest(pam) { 4 | return request({ 5 | url: '/indicator/backTest', 6 | method: 'post', 7 | data: pam 8 | }) 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/api/login.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function login(username, password) { 4 | return request({ 5 | url: '/user/login', 6 | method: 'post', 7 | data: { 8 | username, 9 | password 10 | } 11 | }) 12 | } 13 | 14 | export function getInfo(token) { 15 | return request({ 16 | url: '/user/info', 17 | method: 'get', 18 | params: { token } 19 | }) 20 | } 21 | 22 | export function logout() { 23 | return request({ 24 | url: '/user/logout', 25 | method: 'post' 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /src/api/nodes.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | /** 3 | * 获取节点信息 4 | */ 5 | export function getNodes() { 6 | return request({ 7 | url: '/node/nodes', 8 | method: 'get' 9 | }) 10 | } -------------------------------------------------------------------------------- /src/api/order.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getOrdersByRid(params) { 4 | return request({ 5 | url: '/order/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | export function getProfitInfo(params) { 11 | return request({ 12 | url: '/orderProfit/list', 13 | method: 'get', 14 | params 15 | }) 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/api/robot.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | /** 3 | * 提交机器人 4 | */ 5 | export function addOrUpdateRobot(data) { 6 | return request({ 7 | url: '/robot/addOrUpdateRobot', 8 | method: 'post', 9 | data: data 10 | }) 11 | } 12 | 13 | 14 | export function getRobotById(data) { 15 | return request({ 16 | url: '/robot/getRobotById', 17 | method: 'get', 18 | params: { id: data } 19 | }) 20 | } 21 | 22 | export function getRobots() { 23 | return request({ 24 | url: '/robot/list', 25 | method: 'get' 26 | }) 27 | } 28 | 29 | export function operatingRobot(id) { 30 | return request({ 31 | url: '/robot/operatingRobot', 32 | method: 'get', 33 | params: id 34 | }) 35 | } 36 | 37 | /** 38 | * 删除机器人 39 | */ 40 | export function deleteRobot(id) { 41 | return request({ 42 | url: '/robot/deleteRobot', 43 | method: 'get', 44 | params: id 45 | }) 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/api/strategy.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | /** 4 | * 添加策略 5 | */ 6 | export function addOrUpdateStrategy(data) { 7 | return request({ 8 | url: '/strategy/addOrUpdateStrategy', 9 | method: 'post', 10 | data: data 11 | }) 12 | } 13 | /** 14 | * 获取策略 15 | */ 16 | export function getStrategys() { 17 | return request({ 18 | url: '/strategy/strategyList', 19 | method: 'get' 20 | }) 21 | } 22 | /** 23 | * 获取策略simple 24 | */ 25 | export function getSimpleStrategys() { 26 | return request({ 27 | url: '/strategy/simpleStrategyList', 28 | method: 'get' 29 | }) 30 | } 31 | 32 | /** 33 | * 获取策略 34 | */ 35 | export function getStrategyById(params) { 36 | return request({ 37 | url: '/strategy/getStrategyById', 38 | method: 'get', 39 | params 40 | }) 41 | } 42 | 43 | export function deleteStrategy(params) { 44 | return request({ 45 | url: '/strategy/deleteStrategy', 46 | method: 'get', 47 | params 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /src/api/symbol.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | /** 3 | * 获取节点信息 4 | */ 5 | export function getSymbols() { 6 | return request({ 7 | url: '/symbol/symbols', 8 | method: 'get' 9 | }) 10 | } -------------------------------------------------------------------------------- /src/api/table.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getList(params) { 4 | return request({ 5 | url: '/table/list', 6 | method: 'get', 7 | params 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function emailEditer(emailEditer) { 4 | return request({ 5 | url: '/user/emailEditer', 6 | method: 'post', 7 | data: 8 | emailEditer 9 | }) 10 | } 11 | 12 | export function getUserEmail(token) { 13 | return request({ 14 | url: '/user/getUserEmail', 15 | method: 'get', 16 | params: { 17 | id: token 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/assets/404_images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndexOutOfBounds998/quant-admin/e894b3d33ee3abd2e9b2b946b7e1b5676bce16cb/src/assets/404_images/404.png -------------------------------------------------------------------------------- /src/assets/404_images/404_cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndexOutOfBounds998/quant-admin/e894b3d33ee3abd2e9b2b946b7e1b5676bce16cb/src/assets/404_images/404_cloud.png -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 57 | 58 | 70 | -------------------------------------------------------------------------------- /src/components/Hamburger/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | 32 | 43 | -------------------------------------------------------------------------------- /src/components/Pagination/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 92 | 93 | 102 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 34 | 35 | 44 | -------------------------------------------------------------------------------- /src/directive/clipboard/clipboard.js: -------------------------------------------------------------------------------- 1 | // Inspired by https://github.com/Inndy/vue-clipboard2 2 | const Clipboard = require('clipboard') 3 | if (!Clipboard) { 4 | throw new Error('you should npm install `clipboard` --save at first ') 5 | } 6 | 7 | export default { 8 | bind(el, binding) { 9 | if (binding.arg === 'success') { 10 | el._v_clipboard_success = binding.value 11 | } else if (binding.arg === 'error') { 12 | el._v_clipboard_error = binding.value 13 | } else { 14 | const clipboard = new Clipboard(el, { 15 | text() { return binding.value }, 16 | action() { return binding.arg === 'cut' ? 'cut' : 'copy' } 17 | }) 18 | clipboard.on('success', e => { 19 | const callback = el._v_clipboard_success 20 | callback && callback(e) // eslint-disable-line 21 | }) 22 | clipboard.on('error', e => { 23 | const callback = el._v_clipboard_error 24 | callback && callback(e) // eslint-disable-line 25 | }) 26 | el._v_clipboard = clipboard 27 | } 28 | }, 29 | update(el, binding) { 30 | if (binding.arg === 'success') { 31 | el._v_clipboard_success = binding.value 32 | } else if (binding.arg === 'error') { 33 | el._v_clipboard_error = binding.value 34 | } else { 35 | el._v_clipboard.text = function() { return binding.value } 36 | el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' } 37 | } 38 | }, 39 | unbind(el, binding) { 40 | if (binding.arg === 'success') { 41 | delete el._v_clipboard_success 42 | } else if (binding.arg === 'error') { 43 | delete el._v_clipboard_error 44 | } else { 45 | el._v_clipboard.destroy() 46 | delete el._v_clipboard 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/directive/clipboard/index.js: -------------------------------------------------------------------------------- 1 | import Clipboard from './clipboard' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('Clipboard', Clipboard) 5 | } 6 | 7 | if (window.Vue) { 8 | window.clipboard = Clipboard 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | Clipboard.install = install 13 | export default Clipboard 14 | -------------------------------------------------------------------------------- /src/directive/el-dragDialog/drag.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bind(el, binding, vnode) { 3 | const dialogHeaderEl = el.querySelector('.el-dialog__header') 4 | const dragDom = el.querySelector('.el-dialog') 5 | dialogHeaderEl.style.cssText += ';cursor:move;' 6 | dragDom.style.cssText += ';top:0px;' 7 | 8 | // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); 9 | const getStyle = (function() { 10 | if (window.document.currentStyle) { 11 | return (dom, attr) => dom.currentStyle[attr] 12 | } else { 13 | return (dom, attr) => getComputedStyle(dom, false)[attr] 14 | } 15 | })() 16 | 17 | dialogHeaderEl.onmousedown = (e) => { 18 | // 鼠标按下,计算当前元素距离可视区的距离 19 | const disX = e.clientX - dialogHeaderEl.offsetLeft 20 | const disY = e.clientY - dialogHeaderEl.offsetTop 21 | 22 | const dragDomWidth = dragDom.offsetWidth 23 | const dragDomHeight = dragDom.offsetHeight 24 | 25 | const screenWidth = document.body.clientWidth 26 | const screenHeight = document.body.clientHeight 27 | 28 | const minDragDomLeft = dragDom.offsetLeft 29 | const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth 30 | 31 | const minDragDomTop = dragDom.offsetTop 32 | const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight 33 | 34 | // 获取到的值带px 正则匹配替换 35 | let styL = getStyle(dragDom, 'left') 36 | let styT = getStyle(dragDom, 'top') 37 | 38 | if (styL.includes('%')) { 39 | styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100) 40 | styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100) 41 | } else { 42 | styL = +styL.replace(/\px/g, '') 43 | styT = +styT.replace(/\px/g, '') 44 | } 45 | 46 | document.onmousemove = function(e) { 47 | // 通过事件委托,计算移动的距离 48 | let left = e.clientX - disX 49 | let top = e.clientY - disY 50 | 51 | // 边界处理 52 | if (-(left) > minDragDomLeft) { 53 | left = -minDragDomLeft 54 | } else if (left > maxDragDomLeft) { 55 | left = maxDragDomLeft 56 | } 57 | 58 | if (-(top) > minDragDomTop) { 59 | top = -minDragDomTop 60 | } else if (top > maxDragDomTop) { 61 | top = maxDragDomTop 62 | } 63 | 64 | // 移动当前元素 65 | dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;` 66 | 67 | // emit onDrag event 68 | vnode.child.$emit('dragDialog') 69 | } 70 | 71 | document.onmouseup = function(e) { 72 | document.onmousemove = null 73 | document.onmouseup = null 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/directive/el-dragDialog/index.js: -------------------------------------------------------------------------------- 1 | import drag from './drag' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('el-drag-dialog', drag) 5 | } 6 | 7 | if (window.Vue) { 8 | window['el-drag-dialog'] = drag 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | drag.install = install 13 | export default drag 14 | -------------------------------------------------------------------------------- /src/directive/el-table/adaptive.js: -------------------------------------------------------------------------------- 1 | 2 | import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event' 3 | 4 | /** 5 | * How to use 6 | * ... 7 | * el-table height is must be set 8 | * bottomOffset: 30(default) // The height of the table from the bottom of the page. 9 | */ 10 | 11 | const doResize = (el, binding, vnode) => { 12 | const { componentInstance: $table } = vnode 13 | 14 | const { value } = binding 15 | 16 | if (!$table.height) { 17 | throw new Error(`el-$table must set the height. Such as height='100px'`) 18 | } 19 | const bottomOffset = (value && value.bottomOffset) || 30 20 | 21 | if (!$table) return 22 | 23 | const height = window.innerHeight - el.getBoundingClientRect().top - bottomOffset 24 | $table.layout.setHeight(height) 25 | $table.doLayout() 26 | } 27 | 28 | export default { 29 | bind(el, binding, vnode) { 30 | el.resizeListener = () => { 31 | doResize(el, binding, vnode) 32 | } 33 | 34 | addResizeListener(el, el.resizeListener) 35 | }, 36 | inserted(el, binding, vnode) { 37 | doResize(el, binding, vnode) 38 | }, 39 | unbind(el) { 40 | removeResizeListener(el, el.resizeListener) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/directive/el-table/index.js: -------------------------------------------------------------------------------- 1 | 2 | import adaptive from './adaptive' 3 | 4 | const install = function(Vue) { 5 | Vue.directive('el-height-adaptive-table', adaptive) 6 | } 7 | 8 | if (window.Vue) { 9 | window['el-height-adaptive-table'] = adaptive 10 | Vue.use(install); // eslint-disable-line 11 | } 12 | 13 | adaptive.install = install 14 | export default adaptive 15 | -------------------------------------------------------------------------------- /src/directive/permission/index.js: -------------------------------------------------------------------------------- 1 | import permission from './permission' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('permission', permission) 5 | } 6 | 7 | if (window.Vue) { 8 | window['permission'] = permission 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | permission.install = install 13 | export default permission 14 | -------------------------------------------------------------------------------- /src/directive/permission/permission.js: -------------------------------------------------------------------------------- 1 | 2 | import store from '@/store' 3 | 4 | export default { 5 | inserted(el, binding, vnode) { 6 | const { value } = binding 7 | const roles = store.getters && store.getters.roles 8 | 9 | if (value && value instanceof Array && value.length > 0) { 10 | const permissionRoles = value 11 | 12 | const hasPermission = roles.some(role => { 13 | return permissionRoles.includes(role) 14 | }) 15 | 16 | if (!hasPermission) { 17 | el.parentNode && el.parentNode.removeChild(el) 18 | } 19 | } else { 20 | throw new Error(`need roles! Like v-permission="['admin','editor']"`) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/directive/sticky.js: -------------------------------------------------------------------------------- 1 | const vueSticky = {} 2 | let listenAction 3 | vueSticky.install = Vue => { 4 | Vue.directive('sticky', { 5 | inserted(el, binding) { 6 | const params = binding.value || {} 7 | const stickyTop = params.stickyTop || 0 8 | const zIndex = params.zIndex || 1000 9 | const elStyle = el.style 10 | 11 | elStyle.position = '-webkit-sticky' 12 | elStyle.position = 'sticky' 13 | // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary) 14 | // if (~elStyle.position.indexOf('sticky')) { 15 | // elStyle.top = `${stickyTop}px`; 16 | // elStyle.zIndex = zIndex; 17 | // return 18 | // } 19 | const elHeight = el.getBoundingClientRect().height 20 | const elWidth = el.getBoundingClientRect().width 21 | elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}` 22 | 23 | const parentElm = el.parentNode || document.documentElement 24 | const placeholder = document.createElement('div') 25 | placeholder.style.display = 'none' 26 | placeholder.style.width = `${elWidth}px` 27 | placeholder.style.height = `${elHeight}px` 28 | parentElm.insertBefore(placeholder, el) 29 | 30 | let active = false 31 | 32 | const getScroll = (target, top) => { 33 | const prop = top ? 'pageYOffset' : 'pageXOffset' 34 | const method = top ? 'scrollTop' : 'scrollLeft' 35 | let ret = target[prop] 36 | if (typeof ret !== 'number') { 37 | ret = window.document.documentElement[method] 38 | } 39 | return ret 40 | } 41 | 42 | const sticky = () => { 43 | if (active) { 44 | return 45 | } 46 | if (!elStyle.height) { 47 | elStyle.height = `${el.offsetHeight}px` 48 | } 49 | 50 | elStyle.position = 'fixed' 51 | elStyle.width = `${elWidth}px` 52 | placeholder.style.display = 'inline-block' 53 | active = true 54 | } 55 | 56 | const reset = () => { 57 | if (!active) { 58 | return 59 | } 60 | 61 | elStyle.position = '' 62 | placeholder.style.display = 'none' 63 | active = false 64 | } 65 | 66 | const check = () => { 67 | const scrollTop = getScroll(window, true) 68 | const offsetTop = el.getBoundingClientRect().top 69 | if (offsetTop < stickyTop) { 70 | sticky() 71 | } else { 72 | if (scrollTop < elHeight + stickyTop) { 73 | reset() 74 | } 75 | } 76 | } 77 | listenAction = () => { 78 | check() 79 | } 80 | 81 | window.addEventListener('scroll', listenAction) 82 | }, 83 | 84 | unbind() { 85 | window.removeEventListener('scroll', listenAction) 86 | } 87 | }) 88 | } 89 | 90 | export default vueSticky 91 | 92 | -------------------------------------------------------------------------------- /src/directive/waves/index.js: -------------------------------------------------------------------------------- 1 | import waves from './waves' 2 | 3 | const install = function(Vue) { 4 | Vue.directive('waves', waves) 5 | } 6 | 7 | if (window.Vue) { 8 | window.waves = waves 9 | Vue.use(install); // eslint-disable-line 10 | } 11 | 12 | waves.install = install 13 | export default waves 14 | -------------------------------------------------------------------------------- /src/directive/waves/waves.css: -------------------------------------------------------------------------------- 1 | .waves-ripple { 2 | position: absolute; 3 | border-radius: 100%; 4 | background-color: rgba(0, 0, 0, 0.15); 5 | background-clip: padding-box; 6 | pointer-events: none; 7 | -webkit-user-select: none; 8 | -moz-user-select: none; 9 | -ms-user-select: none; 10 | user-select: none; 11 | -webkit-transform: scale(0); 12 | -ms-transform: scale(0); 13 | transform: scale(0); 14 | opacity: 1; 15 | } 16 | 17 | .waves-ripple.z-active { 18 | opacity: 0; 19 | -webkit-transform: scale(2); 20 | -ms-transform: scale(2); 21 | transform: scale(2); 22 | -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 23 | transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out; 24 | transition: opacity 1.2s ease-out, transform 0.6s ease-out; 25 | transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out; 26 | } -------------------------------------------------------------------------------- /src/directive/waves/waves.js: -------------------------------------------------------------------------------- 1 | import './waves.css' 2 | 3 | const context = '@@wavesContext' 4 | 5 | function handleClick(el, binding) { 6 | function handle(e) { 7 | const customOpts = Object.assign({}, binding.value) 8 | const opts = Object.assign({ 9 | ele: el, // 波纹作用元素 10 | type: 'hit', // hit 点击位置扩散 center中心点扩展 11 | color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色 12 | }, 13 | customOpts 14 | ) 15 | const target = opts.ele 16 | if (target) { 17 | target.style.position = 'relative' 18 | target.style.overflow = 'hidden' 19 | const rect = target.getBoundingClientRect() 20 | let ripple = target.querySelector('.waves-ripple') 21 | if (!ripple) { 22 | ripple = document.createElement('span') 23 | ripple.className = 'waves-ripple' 24 | ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px' 25 | target.appendChild(ripple) 26 | } else { 27 | ripple.className = 'waves-ripple' 28 | } 29 | switch (opts.type) { 30 | case 'center': 31 | ripple.style.top = rect.height / 2 - ripple.offsetHeight / 2 + 'px' 32 | ripple.style.left = rect.width / 2 - ripple.offsetWidth / 2 + 'px' 33 | break 34 | default: 35 | ripple.style.top = 36 | (e.pageY - rect.top - ripple.offsetHeight / 2 - document.documentElement.scrollTop || 37 | document.body.scrollTop) + 'px' 38 | ripple.style.left = 39 | (e.pageX - rect.left - ripple.offsetWidth / 2 - document.documentElement.scrollLeft || 40 | document.body.scrollLeft) + 'px' 41 | } 42 | ripple.style.backgroundColor = opts.color 43 | ripple.className = 'waves-ripple z-active' 44 | return false 45 | } 46 | } 47 | 48 | if (!el[context]) { 49 | el[context] = { 50 | removeHandle: handle 51 | } 52 | } else { 53 | el[context].removeHandle = handle 54 | } 55 | 56 | return handle 57 | } 58 | 59 | export default { 60 | bind(el, binding) { 61 | el.addEventListener('click', handleClick(el, binding), false) 62 | }, 63 | update(el, binding) { 64 | el.removeEventListener('click', el[context].removeHandle, false) 65 | el.addEventListener('click', handleClick(el, binding), false) 66 | }, 67 | unbind(el) { 68 | el.removeEventListener('click', el[context].removeHandle, false) 69 | el[context] = null 70 | delete el[context] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import SvgIcon from '@/components/SvgIcon' // svg组件 3 | 4 | // register globally 5 | Vue.component('svg-icon', SvgIcon) 6 | 7 | const requireAll = requireContext => requireContext.keys().map(requireContext) 8 | const req = require.context('./svg', false, /\.svg$/) 9 | requireAll(req) 10 | -------------------------------------------------------------------------------- /src/icons/svg/404.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/bug.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/chart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/clipboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/component.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/documentation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/email.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/example.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/excel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/exit-fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/eye-open.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/svg/eye.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/form.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/fullscreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/guide 2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/guide.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/international.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/language.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/link.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/money.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/password.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/pdf.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/peoples.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/shopping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/star.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/tab.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/theme.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/tree-table.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/tree.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/wechat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svg/zip.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/svgo.yml: -------------------------------------------------------------------------------- 1 | # replace default config 2 | 3 | # multipass: true 4 | # full: true 5 | 6 | plugins: 7 | 8 | # - name 9 | # 10 | # or: 11 | # - name: false 12 | # - name: true 13 | # 14 | # or: 15 | # - name: 16 | # param1: 1 17 | # param2: 2 18 | 19 | - removeAttrs: 20 | attrs: 21 | - 'fill' 22 | - 'fill-rule' 23 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets 4 | 5 | import ElementUI from 'element-ui' 6 | import 'element-ui/lib/theme-chalk/index.css' 7 | import locale from 'element-ui/lib/locale/lang/en' // lang i18n 8 | 9 | import '@/styles/index.scss' // global css 10 | 11 | import App from './App' 12 | import store from './store' 13 | import router from './router' 14 | 15 | import '@/icons' // icon 16 | import '@/permission' // permission control 17 | import Vuetify from 'vuetify' 18 | // index.js or main.js 19 | import 'vuetify/dist/vuetify.min.css' 20 | /** 21 | * This project originally used easy-mock to simulate data, 22 | * but its official service is very unstable, 23 | * and you can build your own service if you need it. 24 | * So here I use Mock.js for local emulation, 25 | * it will intercept your request, so you won't see the request in the network. 26 | * If you remove `../mock` it will automatically request easy-mock data. 27 | */ 28 | 29 | Vue.use(ElementUI, Vuetify, { locale }) 30 | 31 | Vue.config.productionTip = false 32 | 33 | new Vue({ 34 | el: '#app', 35 | router, 36 | store, 37 | render: h => h(App) 38 | }) 39 | -------------------------------------------------------------------------------- /src/permission.js: -------------------------------------------------------------------------------- 1 | import router from './router' 2 | import store from './store' 3 | import NProgress from 'nprogress' // progress bar 4 | import 'nprogress/nprogress.css' // progress bar style 5 | import { Message } from 'element-ui' 6 | import { getToken } from '@/utils/auth' // getToken from cookie 7 | 8 | NProgress.configure({ showSpinner: false })// NProgress configuration 9 | 10 | const whiteList = ['/login'] // 不重定向白名单 11 | router.beforeEach((to, from, next) => { 12 | NProgress.start() 13 | if (getToken()) { 14 | if (to.path === '/login') { 15 | next({ path: '/' }) 16 | NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it 17 | } else { 18 | if (store.getters.roles.length === 0) { 19 | store.dispatch('GetInfo').then(res => { // 拉取用户信息 20 | next() 21 | }).catch((err) => { 22 | store.dispatch('FedLogOut').then(() => { 23 | Message.error(err || 'Verification failed, please login again') 24 | next({ path: '/' }) 25 | }) 26 | }) 27 | } else { 28 | next() 29 | } 30 | } 31 | } else { 32 | if (whiteList.indexOf(to.path) !== -1) { 33 | next() 34 | } else { 35 | next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 36 | NProgress.done() 37 | } 38 | } 39 | }) 40 | 41 | router.afterEach(() => { 42 | NProgress.done() // 结束Progress 43 | }) 44 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | // in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading; 5 | // detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading 6 | 7 | Vue.use(Router) 8 | 9 | /* Layout */ 10 | import Layout from '../views/layout/Layout' 11 | 12 | /** 13 | * hidden: true if `hidden:true` will not show in the sidebar(default is false) 14 | * alwaysShow: true if set true, will always show the root menu, whatever its child routes length 15 | * if not set alwaysShow, only more than one route under the children 16 | * it will becomes nested mode, otherwise not show the root menu 17 | * redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb 18 | * name:'router-name' the name is used by (must set!!!) 19 | * meta : { 20 | title: 'title' the name show in subMenu and breadcrumb (recommend set) 21 | icon: 'svg-name' the icon show in the sidebar 22 | breadcrumb: false if false, the item will hidden in breadcrumb(default is true) 23 | } 24 | **/ 25 | export const constantRouterMap = [ 26 | { path: '/login', component: () => import('@/views/login/index'), hidden: true }, 27 | { path: '/404', component: () => import('@/views/404'), hidden: true }, 28 | 29 | { 30 | path: '/', 31 | component: Layout, 32 | redirect: '/dashboard', 33 | name: 'Dashboard', 34 | hidden: true, 35 | children: [{ 36 | path: 'dashboard', 37 | component: () => import('@/views/dashboard/index'), 38 | meta: { title: '控制面板' } 39 | }] 40 | }, 41 | { 42 | path: '/strategy', 43 | component: Layout, 44 | meta: { title: '策略中心', icon: 'example' }, 45 | children: [ 46 | { 47 | path: 'index', 48 | name: 'Index', 49 | component: () => import('@/views/strategy/index'), 50 | meta: { title: '简单策略', icon: 'table' } 51 | }, 52 | { 53 | path: 'indicator', 54 | name: 'Indicator', 55 | component: () => import('@/views/strategy/indicator'), 56 | meta: { title: '指标策略', icon: 'tree' } 57 | }, { 58 | path: 'list', 59 | name: 'List', 60 | component: () => import('@/views/strategy/list'), 61 | meta: { title: '我的策略', icon: 'excel' } 62 | } 63 | ] 64 | }, 65 | { 66 | path: '/robot', 67 | component: Layout, 68 | meta: { title: '托管中心', icon: 'people' }, 69 | children: [ 70 | { 71 | path: 'list', 72 | name: 'List', 73 | component: () => import('@/views/robot/list'), 74 | meta: { title: '我的机器人', icon: 'peoples' } 75 | }, { 76 | path: 'index', 77 | name: 'Index', 78 | component: () => import('@/views/robot/index'), 79 | meta: { title: '添加机器人', icon: 'people' } 80 | }, { 81 | path: 'info', 82 | name: 'Info', 83 | hidden: true, 84 | component: () => import('@/views/robot/info'), 85 | meta: { title: '运行监控', icon: 'table' } 86 | } 87 | ] 88 | }, 89 | { 90 | path: '/account', 91 | component: Layout, 92 | meta: { 93 | title: '配置中心', 94 | icon: 'nested' 95 | }, 96 | children: [ 97 | { 98 | path: 'index', 99 | name: 'Index', 100 | component: () => import('@/views/account/index'), 101 | meta: { title: 'api配置', icon: 'table' } 102 | }, { 103 | path: 'email', 104 | name: 'Email', 105 | component: () => import('@/views/account/email'), 106 | meta: { title: '邮件配置', icon: 'email' } 107 | }, { 108 | path: 'info', 109 | name: 'Info', 110 | hidden: true, 111 | component: () => import('@/views/account/info'), 112 | meta: { title: '账户信息', icon: 'table' } 113 | } 114 | ] 115 | }, 116 | { 117 | path: '/order', 118 | component: Layout, 119 | hidden: true, 120 | meta: { 121 | title: '订单', 122 | icon: 'nested' 123 | }, 124 | children: [ 125 | { 126 | path: 'list', 127 | name: 'List', 128 | hidden: true, 129 | component: () => import('@/views/order/list'), 130 | meta: { title: '订单列表', icon: 'table' } 131 | }, 132 | { 133 | path: 'profit', 134 | name: 'Profit', 135 | hidden: true, 136 | component: () => import('@/views/order/profit'), 137 | meta: { title: '盈亏列表', icon: 'table' } 138 | } 139 | ] 140 | }, 141 | 142 | { path: '*', redirect: '/404', hidden: true } 143 | ] 144 | 145 | export default new Router({ 146 | // mode: 'history', //后端支持可开 147 | scrollBehavior: () => ({ y: 0 }), 148 | routes: constantRouterMap 149 | }) 150 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | sidebar: state => state.app.sidebar, 3 | device: state => state.app.device, 4 | token: state => state.user.token, 5 | avatar: state => state.user.avatar, 6 | name: state => state.user.name, 7 | roles: state => state.user.roles 8 | } 9 | export default getters 10 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import app from './modules/app' 4 | import user from './modules/user' 5 | import getters from './getters' 6 | 7 | Vue.use(Vuex) 8 | 9 | const store = new Vuex.Store({ 10 | modules: { 11 | app, 12 | user 13 | }, 14 | getters 15 | }) 16 | 17 | export default store 18 | -------------------------------------------------------------------------------- /src/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const app = { 4 | state: { 5 | sidebar: { 6 | opened: !+Cookies.get('sidebarStatus'), 7 | withoutAnimation: false 8 | }, 9 | device: 'desktop' 10 | }, 11 | mutations: { 12 | TOGGLE_SIDEBAR: state => { 13 | if (state.sidebar.opened) { 14 | Cookies.set('sidebarStatus', 1) 15 | } else { 16 | Cookies.set('sidebarStatus', 0) 17 | } 18 | state.sidebar.opened = !state.sidebar.opened 19 | state.sidebar.withoutAnimation = false 20 | }, 21 | CLOSE_SIDEBAR: (state, withoutAnimation) => { 22 | Cookies.set('sidebarStatus', 1) 23 | state.sidebar.opened = false 24 | state.sidebar.withoutAnimation = withoutAnimation 25 | }, 26 | TOGGLE_DEVICE: (state, device) => { 27 | state.device = device 28 | } 29 | }, 30 | actions: { 31 | ToggleSideBar: ({ commit }) => { 32 | commit('TOGGLE_SIDEBAR') 33 | }, 34 | CloseSideBar({ commit }, { withoutAnimation }) { 35 | commit('CLOSE_SIDEBAR', withoutAnimation) 36 | }, 37 | ToggleDevice({ commit }, device) { 38 | commit('TOGGLE_DEVICE', device) 39 | } 40 | } 41 | } 42 | 43 | export default app 44 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | import { login, logout, getInfo } from '@/api/login' 2 | import { getToken, setToken, removeToken } from '@/utils/auth' 3 | 4 | const user = { 5 | state: { 6 | token: getToken(), 7 | name: '', 8 | avatar: '', 9 | roles: [] 10 | }, 11 | 12 | mutations: { 13 | SET_TOKEN: (state, token) => { 14 | state.token = token 15 | }, 16 | SET_NAME: (state, name) => { 17 | state.name = name 18 | }, 19 | SET_AVATAR: (state, avatar) => { 20 | state.avatar = avatar 21 | }, 22 | SET_ROLES: (state, roles) => { 23 | state.roles = roles 24 | } 25 | }, 26 | 27 | actions: { 28 | // 登录 29 | Login({ commit }, userInfo) { 30 | const username = userInfo.username.trim() 31 | return new Promise((resolve, reject) => { 32 | login(username, userInfo.password).then(response => { 33 | const data = response.data 34 | setToken(data) 35 | commit('SET_TOKEN', data) 36 | resolve() 37 | }).catch(error => { 38 | reject(error) 39 | }) 40 | }) 41 | }, 42 | 43 | // 获取用户信息 44 | GetInfo({ commit, state }) { 45 | return new Promise((resolve, reject) => { 46 | getInfo(state.token).then(response => { 47 | const data = response.data 48 | if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组 49 | commit('SET_ROLES', data.roles) 50 | } else { 51 | reject('getInfo: roles must be a non-null array !') 52 | } 53 | commit('SET_NAME', data.username) 54 | commit('SET_AVATAR', data.avatar) 55 | resolve(response) 56 | }).catch(error => { 57 | reject(error) 58 | }) 59 | }) 60 | }, 61 | 62 | // 登出 63 | LogOut({ commit, state }) { 64 | return new Promise((resolve, reject) => { 65 | logout(state.token).then(() => { 66 | commit('SET_TOKEN', '') 67 | commit('SET_ROLES', []) 68 | removeToken() 69 | resolve() 70 | }).catch(error => { 71 | reject(error) 72 | }) 73 | }) 74 | }, 75 | 76 | // 前端 登出 77 | FedLogOut({ commit }) { 78 | return new Promise(resolve => { 79 | commit('SET_TOKEN', '') 80 | removeToken() 81 | resolve() 82 | }) 83 | } 84 | } 85 | } 86 | 87 | export default user 88 | -------------------------------------------------------------------------------- /src/styles/btn.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | 3 | @mixin colorBtn($color) { 4 | background: $color; 5 | 6 | &:hover { 7 | color: $color; 8 | 9 | &:before, 10 | &:after { 11 | background: $color; 12 | } 13 | } 14 | } 15 | 16 | .blue-btn { 17 | @include colorBtn($blue) 18 | } 19 | 20 | .light-blue-btn { 21 | @include colorBtn($light-blue) 22 | } 23 | 24 | .red-btn { 25 | @include colorBtn($red) 26 | } 27 | 28 | .pink-btn { 29 | @include colorBtn($pink) 30 | } 31 | 32 | .green-btn { 33 | @include colorBtn($green) 34 | } 35 | 36 | .tiffany-btn { 37 | @include colorBtn($tiffany) 38 | } 39 | 40 | .yellow-btn { 41 | @include colorBtn($yellow) 42 | } 43 | 44 | .pan-btn { 45 | font-size: 14px; 46 | color: #fff; 47 | padding: 14px 36px; 48 | border-radius: 8px; 49 | border: none; 50 | outline: none; 51 | transition: 600ms ease all; 52 | position: relative; 53 | display: inline-block; 54 | 55 | &:hover { 56 | background: #fff; 57 | 58 | &:before, 59 | &:after { 60 | width: 100%; 61 | transition: 600ms ease all; 62 | } 63 | } 64 | 65 | &:before, 66 | &:after { 67 | content: ''; 68 | position: absolute; 69 | top: 0; 70 | right: 0; 71 | height: 2px; 72 | width: 0; 73 | transition: 400ms ease all; 74 | } 75 | 76 | &::after { 77 | right: inherit; 78 | top: inherit; 79 | left: 0; 80 | bottom: 0; 81 | } 82 | } 83 | 84 | .custom-button { 85 | display: inline-block; 86 | line-height: 1; 87 | white-space: nowrap; 88 | cursor: pointer; 89 | background: #fff; 90 | color: #fff; 91 | -webkit-appearance: none; 92 | text-align: center; 93 | box-sizing: border-box; 94 | outline: 0; 95 | margin: 0; 96 | padding: 10px 15px; 97 | font-size: 14px; 98 | border-radius: 4px; 99 | } 100 | -------------------------------------------------------------------------------- /src/styles/element-ui.scss: -------------------------------------------------------------------------------- 1 | //覆盖一些element-ui样式 2 | 3 | .el-breadcrumb__inner, 4 | .el-breadcrumb__inner a { 5 | font-weight: 400 !important; 6 | } 7 | 8 | .el-upload { 9 | input[type="file"] { 10 | display: none !important; 11 | } 12 | } 13 | 14 | .el-upload__input { 15 | display: none; 16 | } 17 | 18 | .cell { 19 | .el-tag { 20 | margin-right: 0px; 21 | } 22 | } 23 | 24 | .small-padding { 25 | .cell { 26 | padding-left: 5px; 27 | padding-right: 5px; 28 | } 29 | } 30 | 31 | .fixed-width { 32 | .el-button--mini { 33 | padding: 7px 10px; 34 | width: 60px; 35 | } 36 | } 37 | 38 | .status-col { 39 | .cell { 40 | padding: 0 10px; 41 | text-align: center; 42 | 43 | .el-tag { 44 | margin-right: 0px; 45 | } 46 | } 47 | } 48 | 49 | //暂时性解决dialog 问题 https://github.com/ElemeFE/element/issues/2461 50 | .el-dialog { 51 | transform: none; 52 | left: 0; 53 | position: relative; 54 | margin: 0 auto; 55 | } 56 | 57 | //文章页textarea修改样式 58 | .article-textarea { 59 | textarea { 60 | padding-right: 40px; 61 | resize: none; 62 | border: none; 63 | border-radius: 0px; 64 | border-bottom: 1px solid #bfcbd9; 65 | } 66 | } 67 | 68 | //element ui upload 69 | .upload-container { 70 | .el-upload { 71 | width: 100%; 72 | 73 | .el-upload-dragger { 74 | width: 100%; 75 | height: 200px; 76 | } 77 | } 78 | } 79 | 80 | //dropdown 81 | .el-dropdown-menu { 82 | a { 83 | display: block 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/styles/element-variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * I think element-ui's default theme color is too light for long-term use. 3 | * So I modified the default color and you can modify it to your liking. 4 | **/ 5 | 6 | /* theme color */ 7 | $--color-primary: #1890ff; 8 | $--color-success: #13ce66; 9 | $--color-warning: #FFBA00; 10 | $--color-danger: #ff4949; 11 | // $--color-info: #1E1E1E; 12 | 13 | $--button-font-weight: 400; 14 | 15 | // $--color-text-regular: #1f2d3d; 16 | 17 | $--border-color-light: #dfe4ed; 18 | $--border-color-lighter: #e6ebf5; 19 | 20 | $--table-border:1px solid#dfe6ec; 21 | 22 | /* icon font path, required */ 23 | $--font-path: '~element-ui/lib/theme-chalk/fonts'; 24 | 25 | @import "~element-ui/packages/theme-chalk/src/index"; 26 | 27 | // the :export directive is the magic sauce for webpack 28 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 29 | :export { 30 | theme: $--color-primary; 31 | } 32 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './variables.scss'; 2 | @import './mixin.scss'; 3 | @import './transition.scss'; 4 | @import './element-ui.scss'; 5 | @import './sidebar.scss'; 6 | @import './btn.scss'; 7 | 8 | body { 9 | height: 100%; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-font-smoothing: antialiased; 12 | text-rendering: optimizeLegibility; 13 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; 14 | } 15 | 16 | label { 17 | font-weight: 700; 18 | } 19 | 20 | html { 21 | height: 100%; 22 | box-sizing: border-box; 23 | } 24 | 25 | #app { 26 | height: 100%; 27 | } 28 | 29 | *, 30 | *:before, 31 | *:after { 32 | box-sizing: inherit; 33 | } 34 | 35 | .no-padding { 36 | padding: 0px !important; 37 | } 38 | 39 | .padding-content { 40 | padding: 4px 0; 41 | } 42 | 43 | a:focus, 44 | a:active { 45 | outline: none; 46 | } 47 | 48 | a, 49 | a:focus, 50 | a:hover { 51 | cursor: pointer; 52 | color: inherit; 53 | text-decoration: none; 54 | } 55 | 56 | div:focus { 57 | outline: none; 58 | } 59 | 60 | .fr { 61 | float: right; 62 | } 63 | 64 | .fl { 65 | float: left; 66 | } 67 | 68 | .pr-5 { 69 | padding-right: 5px; 70 | } 71 | 72 | .pl-5 { 73 | padding-left: 5px; 74 | } 75 | 76 | .block { 77 | display: block; 78 | } 79 | 80 | .pointer { 81 | cursor: pointer; 82 | } 83 | 84 | .inlineBlock { 85 | display: block; 86 | } 87 | 88 | .clearfix { 89 | &:after { 90 | visibility: hidden; 91 | display: block; 92 | font-size: 0; 93 | content: " "; 94 | clear: both; 95 | height: 0; 96 | } 97 | } 98 | 99 | code { 100 | background: #eef1f6; 101 | padding: 15px 16px; 102 | margin-bottom: 20px; 103 | display: block; 104 | line-height: 36px; 105 | font-size: 15px; 106 | font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; 107 | 108 | a { 109 | color: #337ab7; 110 | cursor: pointer; 111 | 112 | &:hover { 113 | color: rgb(32, 160, 255); 114 | } 115 | } 116 | } 117 | 118 | .warn-content { 119 | background: rgba(66, 185, 131, .1); 120 | border-radius: 2px; 121 | padding: 16px; 122 | padding: 1rem; 123 | line-height: 1.6rem; 124 | word-spacing: .05rem; 125 | 126 | a { 127 | color: #42b983; 128 | font-weight: 600; 129 | } 130 | } 131 | 132 | //main-container全局样式 133 | .app-container { 134 | padding: 20px; 135 | } 136 | 137 | .components-container { 138 | margin: 30px 50px; 139 | position: relative; 140 | } 141 | 142 | .pagination-container { 143 | margin-top: 30px; 144 | } 145 | 146 | .text-center { 147 | text-align: center 148 | } 149 | 150 | .sub-navbar { 151 | height: 50px; 152 | line-height: 50px; 153 | position: relative; 154 | width: 100%; 155 | text-align: right; 156 | padding-right: 20px; 157 | transition: 600ms ease position; 158 | background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); 159 | 160 | .subtitle { 161 | font-size: 20px; 162 | color: #fff; 163 | } 164 | 165 | &.draft { 166 | background: #d0d0d0; 167 | } 168 | 169 | &.deleted { 170 | background: #d0d0d0; 171 | } 172 | } 173 | 174 | .link-type, 175 | .link-type:focus { 176 | color: #337ab7; 177 | cursor: pointer; 178 | 179 | &:hover { 180 | color: rgb(32, 160, 255); 181 | } 182 | } 183 | 184 | .filter-container { 185 | padding-bottom: 10px; 186 | 187 | .filter-item { 188 | display: inline-block; 189 | vertical-align: middle; 190 | margin-bottom: 10px; 191 | } 192 | } 193 | 194 | //refine vue-multiselect plugin 195 | .multiselect { 196 | line-height: 16px; 197 | } 198 | 199 | .multiselect--active { 200 | z-index: 1000 !important; 201 | } 202 | -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | @mixin scrollBar { 10 | &::-webkit-scrollbar-track-piece { 11 | background: #d3dce6; 12 | } 13 | 14 | &::-webkit-scrollbar { 15 | width: 6px; 16 | } 17 | 18 | &::-webkit-scrollbar-thumb { 19 | background: #99a9bf; 20 | border-radius: 20px; 21 | } 22 | } 23 | 24 | @mixin relative { 25 | position: relative; 26 | width: 100%; 27 | height: 100%; 28 | } 29 | 30 | @mixin pct($pct) { 31 | width: #{$pct}; 32 | position: relative; 33 | margin: 0 auto; 34 | } 35 | 36 | @mixin triangle($width, $height, $color, $direction) { 37 | $width: $width/2; 38 | $color-border-style: $height solid $color; 39 | $transparent-border-style: $width solid transparent; 40 | height: 0; 41 | width: 0; 42 | 43 | @if $direction==up { 44 | border-bottom: $color-border-style; 45 | border-left: $transparent-border-style; 46 | border-right: $transparent-border-style; 47 | } 48 | 49 | @else if $direction==right { 50 | border-left: $color-border-style; 51 | border-top: $transparent-border-style; 52 | border-bottom: $transparent-border-style; 53 | } 54 | 55 | @else if $direction==down { 56 | border-top: $color-border-style; 57 | border-left: $transparent-border-style; 58 | border-right: $transparent-border-style; 59 | } 60 | 61 | @else if $direction==left { 62 | border-right: $color-border-style; 63 | border-top: $transparent-border-style; 64 | border-bottom: $transparent-border-style; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/styles/sidebar.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | 3 | // 主体区域 Main container 4 | .main-container { 5 | min-height: 100%; 6 | transition: margin-left .28s; 7 | margin-left: $sideBarWidth; 8 | position: relative; 9 | } 10 | 11 | // 侧边栏 Sidebar container 12 | .sidebar-container { 13 | transition: width 0.28s; 14 | width: $sideBarWidth !important; 15 | height: 100%; 16 | position: fixed; 17 | font-size: 0px; 18 | top: 0; 19 | bottom: 0; 20 | left: 0; 21 | z-index: 1001; 22 | overflow: hidden; 23 | 24 | //reset element-ui css 25 | .horizontal-collapse-transition { 26 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; 27 | } 28 | 29 | .scrollbar-wrapper { 30 | overflow-x: hidden !important; 31 | 32 | .el-scrollbar__view { 33 | height: 100%; 34 | } 35 | } 36 | 37 | .el-scrollbar__bar.is-vertical { 38 | right: 0px; 39 | } 40 | 41 | .el-scrollbar { 42 | height: 100%; 43 | } 44 | 45 | &.has-logo { 46 | .el-scrollbar { 47 | height: calc(100% - 50px); 48 | } 49 | } 50 | 51 | .is-horizontal { 52 | display: none; 53 | } 54 | 55 | a { 56 | display: inline-block; 57 | width: 100%; 58 | overflow: hidden; 59 | } 60 | 61 | .svg-icon { 62 | margin-right: 16px; 63 | } 64 | 65 | .el-menu { 66 | border: none; 67 | height: 100%; 68 | width: 100% !important; 69 | } 70 | 71 | // menu hover 72 | .submenu-title-noDropdown, 73 | .el-submenu__title { 74 | &:hover { 75 | background-color: $menuHover !important; 76 | } 77 | } 78 | 79 | .is-active>.el-submenu__title { 80 | color: $subMenuActiveText !important; 81 | } 82 | 83 | & .nest-menu .el-submenu>.el-submenu__title, 84 | & .el-submenu .el-menu-item { 85 | min-width: $sideBarWidth !important; 86 | background-color: $subMenuBg !important; 87 | 88 | &:hover { 89 | background-color: $subMenuHover !important; 90 | } 91 | } 92 | } 93 | 94 | .hideSidebar { 95 | .sidebar-container { 96 | width: 54px !important; 97 | } 98 | 99 | .main-container { 100 | margin-left: 54px; 101 | } 102 | 103 | .svg-icon { 104 | margin-right: 0px; 105 | } 106 | 107 | .submenu-title-noDropdown { 108 | padding: 0 !important; 109 | position: relative; 110 | 111 | .el-tooltip { 112 | padding: 0 !important; 113 | 114 | .svg-icon { 115 | margin-left: 20px; 116 | } 117 | } 118 | } 119 | 120 | .el-submenu { 121 | overflow: hidden; 122 | 123 | &>.el-submenu__title { 124 | padding: 0 !important; 125 | 126 | .svg-icon { 127 | margin-left: 20px; 128 | } 129 | 130 | .el-submenu__icon-arrow { 131 | display: none; 132 | } 133 | } 134 | } 135 | 136 | .el-menu--collapse { 137 | .el-submenu { 138 | &>.el-submenu__title { 139 | &>span { 140 | height: 0; 141 | width: 0; 142 | overflow: hidden; 143 | visibility: hidden; 144 | display: inline-block; 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | .el-menu--collapse .el-menu .el-submenu { 152 | min-width: $sideBarWidth !important; 153 | } 154 | 155 | // 适配移动端, Mobile responsive 156 | .mobile { 157 | .main-container { 158 | margin-left: 0px; 159 | } 160 | 161 | .sidebar-container { 162 | transition: transform .28s; 163 | width: $sideBarWidth !important; 164 | } 165 | 166 | &.hideSidebar { 167 | .sidebar-container { 168 | pointer-events: none; 169 | transition-duration: 0.3s; 170 | transform: translate3d(-$sideBarWidth, 0, 0); 171 | } 172 | } 173 | } 174 | 175 | .withoutAnimation { 176 | 177 | .main-container, 178 | .sidebar-container { 179 | transition: none; 180 | } 181 | } 182 | } 183 | 184 | // when menu collapsed 185 | .el-menu--vertical { 186 | &>.el-menu { 187 | .svg-icon { 188 | margin-right: 16px; 189 | } 190 | } 191 | 192 | .nest-menu .el-submenu>.el-submenu__title, 193 | .el-menu-item { 194 | &:hover { 195 | // you can use $subMenuHover 196 | background-color: $menuHover !important; 197 | } 198 | } 199 | 200 | // the scroll bar appears when the subMenu is too long 201 | >.el-menu--popup { 202 | max-height: 100vh; 203 | overflow-y: auto; 204 | 205 | &::-webkit-scrollbar-track-piece { 206 | background: #d3dce6; 207 | } 208 | 209 | &::-webkit-scrollbar { 210 | width: 6px; 211 | } 212 | 213 | &::-webkit-scrollbar-thumb { 214 | background: #99a9bf; 215 | border-radius: 20px; 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/styles/transition.scss: -------------------------------------------------------------------------------- 1 | //global transition css 2 | 3 | /*fade*/ 4 | .fade-enter-active, 5 | .fade-leave-active { 6 | transition: opacity 0.28s; 7 | } 8 | 9 | .fade-enter, 10 | .fade-leave-active { 11 | opacity: 0; 12 | } 13 | 14 | /*fade-transform*/ 15 | .fade-transform-leave-active, 16 | .fade-transform-enter-active { 17 | transition: all .5s; 18 | } 19 | 20 | .fade-transform-enter { 21 | opacity: 0; 22 | transform: translateX(-30px); 23 | } 24 | 25 | .fade-transform-leave-to { 26 | opacity: 0; 27 | transform: translateX(30px); 28 | } 29 | 30 | /*breadcrumb transition*/ 31 | .breadcrumb-enter-active, 32 | .breadcrumb-leave-active { 33 | transition: all .5s; 34 | } 35 | 36 | .breadcrumb-enter, 37 | .breadcrumb-leave-active { 38 | opacity: 0; 39 | transform: translateX(20px); 40 | } 41 | 42 | .breadcrumb-move { 43 | transition: all .5s; 44 | } 45 | 46 | .breadcrumb-leave-active { 47 | position: absolute; 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // base color 2 | $blue:#324157; 3 | $light-blue:#3A71A8; 4 | $red:#C03639; 5 | $pink: #E65D6E; 6 | $green: #30B08F; 7 | $tiffany: #4AB7BD; 8 | $yellow:#FEC171; 9 | $panGreen: #30B08F; 10 | 11 | //sidebar 12 | $menuText:#bfcbd9; 13 | $menuActiveText:#409EFF; 14 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951 15 | 16 | $menuBg:#304156; 17 | $menuHover:#263445; 18 | 19 | $subMenuBg:#1f2d3d; 20 | $subMenuHover:#001528; 21 | 22 | $sideBarWidth: 210px; 23 | 24 | // the :export directive is the magic sauce for webpack 25 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass 26 | :export { 27 | menuText: $menuText; 28 | menuActiveText: $menuActiveText; 29 | subMenuActiveText: $subMenuActiveText; 30 | menuBg: $menuBg; 31 | menuHover: $menuHover; 32 | subMenuBg: $subMenuBg; 33 | subMenuHover: $subMenuHover; 34 | sideBarWidth: $sideBarWidth; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import Cookies from 'js-cookie' 2 | 3 | const TokenKey = 'uid' 4 | 5 | export function getToken() { 6 | return Cookies.get(TokenKey) 7 | } 8 | 9 | export function setToken(token) { 10 | return Cookies.set(TokenKey, token) 11 | } 12 | 13 | export function removeToken() { 14 | return Cookies.remove(TokenKey) 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/clipboard.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Clipboard from 'clipboard' 3 | 4 | function clipboardSuccess() { 5 | Vue.prototype.$message({ 6 | message: 'Copy successfully', 7 | type: 'success', 8 | duration: 1500 9 | }) 10 | } 11 | 12 | function clipboardError() { 13 | Vue.prototype.$message({ 14 | message: 'Copy failed', 15 | type: 'error' 16 | }) 17 | } 18 | 19 | export default function handleClipboard(text, event) { 20 | const clipboard = new Clipboard(event.target, { 21 | text: () => text 22 | }) 23 | clipboard.on('success', () => { 24 | clipboardSuccess() 25 | clipboard.off('error') 26 | clipboard.off('success') 27 | clipboard.destroy() 28 | }) 29 | clipboard.on('error', () => { 30 | clipboardError() 31 | clipboard.off('error') 32 | clipboard.off('success') 33 | clipboard.destroy() 34 | }) 35 | clipboard.onClick(event) 36 | } 37 | -------------------------------------------------------------------------------- /src/utils/date.js: -------------------------------------------------------------------------------- 1 | 2 | // date.js 3 | export function formatDate(date, fmt) { 4 | if (/(y+)/.test(fmt)) { 5 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); 6 | } 7 | let o = { 8 | 'M+': date.getMonth() + 1, 9 | 'd+': date.getDate(), 10 | 'h+': date.getHours(), 11 | 'm+': date.getMinutes(), 12 | 's+': date.getSeconds() 13 | }; 14 | for (let k in o) { 15 | if (new RegExp(`(${k})`).test(fmt)) { 16 | let str = o[k] + ''; 17 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str)); 18 | } 19 | } 20 | return fmt; 21 | }; 22 | 23 | function padLeftZero(str) { 24 | return ('00' + str).substr(str.length); 25 | } -------------------------------------------------------------------------------- /src/utils/errorLog.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | import { isString, isArray } from '@/utils/validate' 4 | import settings from '@/settings' 5 | 6 | // you can set in settings.js 7 | // errorLog:'production' | ['production','development'] 8 | const { errorLog: needErrorLog } = settings 9 | 10 | function checkNeed(arg) { 11 | const env = process.env.NODE_ENV 12 | if (isString(needErrorLog)) { 13 | return env === needErrorLog 14 | } 15 | if (isArray(needErrorLog)) { 16 | return needErrorLog.includes(env) 17 | } 18 | return false 19 | } 20 | 21 | if (checkNeed()) { 22 | Vue.config.errorHandler = function(err, vm, info, a) { 23 | // Don't ask me why I use Vue.nextTick, it just a hack. 24 | // detail see https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500 25 | Vue.nextTick(() => { 26 | store.dispatch('errorLog/addErrorLog', { 27 | err, 28 | vm, 29 | info, 30 | url: window.location.href 31 | }) 32 | console.error(err, info) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/i18n.js: -------------------------------------------------------------------------------- 1 | // translate router.meta.title, be used in breadcrumb sidebar tagsview 2 | export function generateTitle(title) { 3 | const hasKey = this.$te('route.' + title) 4 | 5 | if (hasKey) { 6 | // $t :this method from vue-i18n, inject in @/lang/index.js 7 | const translatedTitle = this.$t('route.' + title) 8 | 9 | return translatedTitle 10 | } 11 | return title 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/openWindow.js: -------------------------------------------------------------------------------- 1 | /** 2 | *Created by jiachenpan on 16/11/29. 3 | * @param {Sting} url 4 | * @param {Sting} title 5 | * @param {Number} w 6 | * @param {Number} h 7 | */ 8 | 9 | export default function openWindow(url, title, w, h) { 10 | // Fixes dual-screen position Most browsers Firefox 11 | const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left 12 | const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top 13 | 14 | const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width 15 | const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height 16 | 17 | const left = ((width / 2) - (w / 2)) + dualScreenLeft 18 | const top = ((height / 2) - (h / 2)) + dualScreenTop 19 | const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left) 20 | 21 | // Puts focus on the newWindow 22 | if (window.focus) { 23 | newWindow.focus() 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/utils/permission.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | /** 4 | * @param {Array} value 5 | * @returns {Boolean} 6 | * @example see @/views/permission/directive.vue 7 | */ 8 | export default function checkPermission(value) { 9 | if (value && value instanceof Array && value.length > 0) { 10 | const roles = store.getters && store.getters.roles 11 | const permissionRoles = value 12 | 13 | const hasPermission = roles.some(role => { 14 | return permissionRoles.includes(role) 15 | }) 16 | 17 | if (!hasPermission) { 18 | return false 19 | } 20 | return true 21 | } else { 22 | console.error(`need roles! Like v-permission="['admin','editor']"`) 23 | return false 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { Message, MessageBox } from 'element-ui' 3 | import store from '../store' 4 | import { getToken } from '@/utils/auth' 5 | 6 | // 创建axios实例 7 | const service = axios.create({ 8 | baseURL: process.env.BASE_API, // api 的 base_url 9 | timeout: 5000 // 请求超时时间 10 | }) 11 | 12 | // request拦截器 13 | service.interceptors.request.use( 14 | config => { 15 | if (store.getters.token) { 16 | config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 17 | } 18 | return config 19 | }, 20 | error => { 21 | // Do something with request error 22 | console.log(error) // for debug 23 | Promise.reject(error) 24 | } 25 | ) 26 | 27 | // response 拦截器 28 | service.interceptors.response.use( 29 | response => { 30 | /** 31 | * code为非20000是抛错 可结合自己业务进行修改 32 | */ 33 | const res = response.data 34 | if (res.code !== 20000) { 35 | Message({ 36 | message: res.message, 37 | type: 'error', 38 | duration: 5 * 1000 39 | }) 40 | 41 | // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; 42 | if (res.code === 50008 || res.code === 50012 || res.code === 50014) { 43 | MessageBox.confirm( 44 | '你已被登出,可以取消继续留在该页面,或者重新登录', 45 | '确定登出', 46 | { 47 | confirmButtonText: '重新登录', 48 | cancelButtonText: '取消', 49 | type: 'warning' 50 | } 51 | ).then(() => { 52 | store.dispatch('FedLogOut').then(() => { 53 | location.reload() // 为了重新实例化vue-router对象 避免bug 54 | }) 55 | }) 56 | } 57 | return Promise.reject('error') 58 | } else { 59 | return response.data 60 | } 61 | }, 62 | error => { 63 | console.log('err' + error) // for debug 64 | Message({ 65 | message: error.message, 66 | type: 'error', 67 | duration: 5 * 1000 68 | }) 69 | return Promise.reject(error) 70 | } 71 | ) 72 | 73 | export default service 74 | -------------------------------------------------------------------------------- /src/utils/scrollTo.js: -------------------------------------------------------------------------------- 1 | Math.easeInOutQuad = function(t, b, c, d) { 2 | t /= d / 2 3 | if (t < 1) { 4 | return c / 2 * t * t + b 5 | } 6 | t-- 7 | return -c / 2 * (t * (t - 2) - 1) + b 8 | } 9 | 10 | // requestAnimationFrame for Smart Animating http://goo.gl/sx5sts 11 | var requestAnimFrame = (function() { 12 | return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) } 13 | })() 14 | 15 | // because it's so fucking difficult to detect the scrolling element, just move them all 16 | function move(amount) { 17 | document.documentElement.scrollTop = amount 18 | document.body.parentNode.scrollTop = amount 19 | document.body.scrollTop = amount 20 | } 21 | 22 | function position() { 23 | return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop 24 | } 25 | 26 | export function scrollTo(to, duration, callback) { 27 | const start = position() 28 | const change = to - start 29 | const increment = 20 30 | let currentTime = 0 31 | duration = (typeof (duration) === 'undefined') ? 500 : duration 32 | var animateScroll = function() { 33 | // increment the time 34 | currentTime += increment 35 | // find the value with the quadratic in-out easing function 36 | var val = Math.easeInOutQuad(currentTime, start, change, duration) 37 | // move the document.body 38 | move(val) 39 | // do the animation unless its over 40 | if (currentTime < duration) { 41 | requestAnimFrame(animateScroll) 42 | } else { 43 | if (callback && typeof (callback) === 'function') { 44 | // the animation is done so lets callback 45 | callback() 46 | } 47 | } 48 | } 49 | animateScroll() 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jiachenpan on 16/11/18. 3 | */ 4 | 5 | export function isExternal(path) { 6 | return /^(https?:|mailto:|tel:)/.test(path) 7 | } 8 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 229 | -------------------------------------------------------------------------------- /src/views/account/email.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 66 | 67 | 77 | -------------------------------------------------------------------------------- /src/views/account/index.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 259 | 260 | 270 | -------------------------------------------------------------------------------- /src/views/account/info.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 51 | 69 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 47 | 48 | 70 | -------------------------------------------------------------------------------- /src/views/layout/components/AppMain.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 30 | -------------------------------------------------------------------------------- /src/views/layout/components/Navbar.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 52 | 53 | 95 | 96 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/Item.vue: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/Link.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 37 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 92 | -------------------------------------------------------------------------------- /src/views/layout/components/Sidebar/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | -------------------------------------------------------------------------------- /src/views/layout/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navbar } from './Navbar' 2 | export { default as Sidebar } from './Sidebar' 3 | export { default as AppMain } from './AppMain' 4 | -------------------------------------------------------------------------------- /src/views/layout/mixin/ResizeHandler.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | 3 | const { body } = document 4 | const WIDTH = 992 // refer to Bootstrap's responsive design 5 | 6 | export default { 7 | watch: { 8 | $route(route) { 9 | if (this.device === 'mobile' && this.sidebar.opened) { 10 | store.dispatch('CloseSideBar', { withoutAnimation: false }) 11 | } 12 | } 13 | }, 14 | beforeMount() { 15 | window.addEventListener('resize', this.resizeHandler) 16 | }, 17 | mounted() { 18 | const isMobile = this.isMobile() 19 | if (isMobile) { 20 | store.dispatch('ToggleDevice', 'mobile') 21 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 22 | } 23 | }, 24 | methods: { 25 | isMobile() { 26 | const rect = body.getBoundingClientRect() 27 | return rect.width - 1 < WIDTH 28 | }, 29 | resizeHandler() { 30 | if (!document.hidden) { 31 | const isMobile = this.isMobile() 32 | store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop') 33 | 34 | if (isMobile) { 35 | store.dispatch('CloseSideBar', { withoutAnimation: true }) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 117 | 118 | 150 | 151 | 205 | -------------------------------------------------------------------------------- /src/views/order/list.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/order/profit.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | -------------------------------------------------------------------------------- /src/views/robot/index.vue: -------------------------------------------------------------------------------- 1 | 54 | -------------------------------------------------------------------------------- /src/views/robot/info.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | -------------------------------------------------------------------------------- /src/views/robot/list.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/strategy/list.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 208 | 209 | 219 | -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndexOutOfBounds998/quant-admin/e894b3d33ee3abd2e9b2b946b7e1b5676bce16cb/static/.gitkeep --------------------------------------------------------------------------------