├── static ├── .gitkeep └── css │ └── reset.css ├── src ├── scss │ ├── animation │ │ ├── .gitkeep │ │ ├── operate.styl │ │ ├── collapse.styl │ │ ├── make-transitions.styl │ │ └── slide.styl │ ├── mixins │ │ ├── clearfix.styl │ │ ├── scrollbar.styl │ │ └── multiLineEllipsis.styl │ ├── button.styl │ ├── reset.styl │ ├── app.ss │ ├── analysis.styl │ ├── config.styl │ ├── common.styl │ └── iconfont.styl ├── common │ ├── stylus │ │ ├── variables.styl │ │ ├── index.styl │ │ ├── elment.styl │ │ ├── base.styl │ │ ├── mixin.styl │ │ └── icon.styl │ ├── common.md │ ├── image │ │ └── default.png │ ├── img │ │ └── user-logo.png │ ├── fonts │ │ ├── icon-demo.eot │ │ ├── icon-demo.ttf │ │ └── icon-demo.woff │ └── js │ │ ├── util.js │ │ ├── dom.js │ │ ├── array.js │ │ ├── ease.js │ │ └── date.js ├── views │ ├── flow-template │ │ ├── node-menu │ │ │ ├── node-menu.styl │ │ │ ├── node-menu.vue │ │ │ └── node-menu.js │ │ ├── node │ │ │ ├── node.styl │ │ │ ├── node.vue │ │ │ └── node.js │ │ ├── flow-template.styl │ │ ├── flow-template.vue │ │ └── flow-template.js │ ├── jsplumb-learn │ │ ├── node-menu │ │ │ ├── node-menu.styl │ │ │ ├── node-menu.vue │ │ │ └── node-menu.js │ │ ├── img │ │ │ ├── edit.png │ │ │ ├── delete.png │ │ │ ├── node-close.png │ │ │ ├── node-img.png │ │ │ └── node-timer.png │ │ ├── jsplumb-learn.styl │ │ ├── data_A.js │ │ ├── jsplumb-learn.vue │ │ └── node.vue │ ├── views.md │ ├── login │ │ ├── img │ │ │ ├── bg0_0.jpg │ │ │ ├── bg0_1.jpg │ │ │ ├── bg0_2.jpg │ │ │ ├── bg1_0.png │ │ │ ├── bg1_1.png │ │ │ ├── bg1_2.png │ │ │ ├── check.png │ │ │ ├── checked.png │ │ │ └── check_hover.png │ │ ├── login.js │ │ ├── login.vue │ │ └── login.styl │ ├── header │ │ ├── user-logo.png │ │ └── header.vue │ ├── sys-setting │ │ ├── welcome-page │ │ │ ├── features.png │ │ │ └── welcome-page.vue │ │ ├── runners │ │ │ └── runners.vue │ │ ├── withdraw │ │ │ └── withdraw.vue │ │ ├── sys-setting.js │ │ ├── sys-setting.styl │ │ ├── sys-setting.vue │ │ ├── base-manage │ │ │ └── base-manage.vue │ │ └── scholl-info │ │ │ └── scholl-info.vue │ ├── 404.vue │ ├── company-help │ │ └── company-help.vue │ └── user-center │ │ ├── directive-define │ │ └── directive-define.vue │ │ ├── define-component │ │ └── define-component.vue │ │ ├── user-center.js │ │ ├── define-filter │ │ └── define-filter.vue │ │ ├── user-center.styl │ │ └── user-center.vue ├── api │ ├── index.js │ ├── user.js │ └── api.js ├── utils │ ├── bus.js │ ├── checkDataType.js │ ├── uuid.js │ ├── dom.js │ ├── errorHandler.js │ ├── nodeFilter.js │ ├── atomFilter.js │ ├── tools.js │ ├── draft.js │ └── validatePipeline.js ├── directive │ ├── index.js │ └── heigh-light.js ├── store │ ├── modules │ │ ├── index.js │ │ ├── templateList.js │ │ └── atomList.js │ └── index.js ├── lang │ ├── index.js │ ├── zh.js │ └── en.js ├── component │ ├── base │ │ ├── BaseTitle.vue │ │ ├── NoData.vue │ │ ├── BaseInput.vue │ │ ├── BaseCheckbox.vue │ │ ├── BaseCollapse.vue │ │ └── BaseSearch.vue │ └── com-panel │ │ └── com-panel.vue ├── App.vue ├── main.js ├── constants │ └── index.js └── router │ └── index.js ├── test └── unit │ ├── setup.js │ ├── .eslintrc │ ├── specs │ └── HelloWorld.spec.js │ └── jest.conf.js ├── present ├── demo0.png └── mock.png ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── .eslintignore ├── .editorconfig ├── .gitignore ├── index.html ├── .postcssrc.js ├── code-standards ├── file.md ├── structure.md ├── js.md └── css.md ├── mocks ├── example │ ├── data.json │ └── links.js ├── syssetting │ ├── getMenuList.js │ └── getSchoolList.js └── usercenter │ └── getMenuList.js ├── .babelrc ├── prod.server.js ├── README.md ├── .eslintrc.js ├── data ├── data.json └── usercenter.json └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/scss/animation/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/stylus/variables.styl: -------------------------------------------------------------------------------- 1 | blue = #324157 -------------------------------------------------------------------------------- /src/views/flow-template/node-menu/node-menu.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/node-menu/node-menu.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/views/views.md: -------------------------------------------------------------------------------- 1 | ## 文件描述 2 | * login 登录界面 3 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | import * as api from './api'; 2 | 3 | export default api; 4 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /present/demo0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/present/demo0.png -------------------------------------------------------------------------------- /present/mock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/present/mock.png -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/bus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const bus = new Vue(); 4 | 5 | export default bus; 6 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/common/common.md: -------------------------------------------------------------------------------- 1 | ## 文件描述 2 | * fonts 字体图标库 3 | * img 通用图片 4 | * js 通用JS文件 5 | * stylus 通用样式文件 6 | -------------------------------------------------------------------------------- /src/common/image/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/common/image/default.png -------------------------------------------------------------------------------- /src/common/img/user-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/common/img/user-logo.png -------------------------------------------------------------------------------- /src/views/login/img/bg0_0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/bg0_0.jpg -------------------------------------------------------------------------------- /src/views/login/img/bg0_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/bg0_1.jpg -------------------------------------------------------------------------------- /src/views/login/img/bg0_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/bg0_2.jpg -------------------------------------------------------------------------------- /src/views/login/img/bg1_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/bg1_0.png -------------------------------------------------------------------------------- /src/views/login/img/bg1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/bg1_1.png -------------------------------------------------------------------------------- /src/views/login/img/bg1_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/bg1_2.png -------------------------------------------------------------------------------- /src/views/login/img/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/check.png -------------------------------------------------------------------------------- /src/common/fonts/icon-demo.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/common/fonts/icon-demo.eot -------------------------------------------------------------------------------- /src/common/fonts/icon-demo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/common/fonts/icon-demo.ttf -------------------------------------------------------------------------------- /src/common/fonts/icon-demo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/common/fonts/icon-demo.woff -------------------------------------------------------------------------------- /src/views/header/user-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/header/user-logo.png -------------------------------------------------------------------------------- /src/views/login/img/checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/checked.png -------------------------------------------------------------------------------- /src/views/jsplumb-learn/img/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/jsplumb-learn/img/edit.png -------------------------------------------------------------------------------- /src/views/login/img/check_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/login/img/check_hover.png -------------------------------------------------------------------------------- /src/views/jsplumb-learn/img/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/jsplumb-learn/img/delete.png -------------------------------------------------------------------------------- /src/views/jsplumb-learn/img/node-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/jsplumb-learn/img/node-close.png -------------------------------------------------------------------------------- /src/views/jsplumb-learn/img/node-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/jsplumb-learn/img/node-img.png -------------------------------------------------------------------------------- /src/views/jsplumb-learn/img/node-timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/jsplumb-learn/img/node-timer.png -------------------------------------------------------------------------------- /src/common/stylus/index.styl: -------------------------------------------------------------------------------- 1 | @import "./icon"; 2 | @import "./variables"; 3 | @import "./mixin"; 4 | @import "./base"; 5 | @import "./elment"; 6 | -------------------------------------------------------------------------------- /src/views/sys-setting/welcome-page/features.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toutouping/vue-web-demo/HEAD/src/views/sys-setting/welcome-page/features.png -------------------------------------------------------------------------------- /src/directive/index.js: -------------------------------------------------------------------------------- 1 | import initHighLight from './heigh-light'; 2 | 3 | export default { 4 | install (Vue) { 5 | initHighLight(Vue); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/scss/mixins/clearfix.styl: -------------------------------------------------------------------------------- 1 | @mixin clearfix() { 2 | &::after { 3 | display: block; 4 | content: ""; 5 | clear: both; 6 | } 7 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | /test/unit/coverage/ 6 | static/**/*.js 7 | src/assets/js/*.js 8 | src/assets/bk-magic 9 | 10 | -------------------------------------------------------------------------------- /src/utils/checkDataType.js: -------------------------------------------------------------------------------- 1 | export function checkDataType (data) { 2 | const typeString = Object.prototype.toString.call(data); 3 | 4 | return typeString.slice(8, -1); 5 | } 6 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /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 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = false 10 | -------------------------------------------------------------------------------- /src/scss/button.styl: -------------------------------------------------------------------------------- 1 | .common-green-btn { 2 | color: $whiteDefault; 3 | background: $greenDefault; 4 | &:hover { 5 | color: $whiteDefault; 6 | background: $greenDark; 7 | } 8 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | -------------------------------------------------------------------------------- /src/scss/animation/operate.styl: -------------------------------------------------------------------------------- 1 | @keyframes operate-loading { 2 | 0% { 3 | background-color: #abeabb; 4 | } 5 | 50% { 6 | background-color: #57d577; 7 | } 8 | 100% { 9 | background-color: #57d577; 10 | } 11 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-web-demo 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/common/js/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取浏览器的默认语言设置 3 | */ 4 | export function getLanguage () { 5 | let lang = navigator.language || navigator.userLanguage; 6 | 7 | lang = lang.substr(0, 2); 8 | // return localStorage.getItem('web-language') || lang; 9 | return 'zh'; 10 | } 11 | -------------------------------------------------------------------------------- /src/store/modules/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.js'; 2 | import templateList from './templateList.js'; 3 | import atomList from './atomList.js'; 4 | 5 | const modules = { 6 | template, 7 | templateList, 8 | atomList 9 | }; 10 | 11 | export default modules; 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/scss/animation/collapse.styl: -------------------------------------------------------------------------------- 1 | .collaspe-enter-active, 2 | .collaspe-leave-active{ 3 | transition: all 0.5s ease-in-out; 4 | } 5 | .collaspe-enter, 6 | .collaspe-leave-to { 7 | transform: transformY(100%); 8 | } 9 | .collaspe-leave, 10 | .collaspe-enter-to { 11 | transform: transformY(-100%); 12 | } -------------------------------------------------------------------------------- /src/views/company-help/company-help.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/utils/uuid.js: -------------------------------------------------------------------------------- 1 | export function random4 () { 2 | return Math.floor((1 + Math.random()) * 0x10000) 3 | .toString(16) 4 | .substring(1); 5 | } 6 | 7 | export function uuid () { 8 | let id = ''; 9 | 10 | for (let i = 0; i < 7; i++) { 11 | id += random4(); 12 | } 13 | return id; 14 | } 15 | -------------------------------------------------------------------------------- /src/scss/mixins/scrollbar.styl: -------------------------------------------------------------------------------- 1 | @mixin scrollbar { // 页面滚动条 2 | &::-webkit-scrollbar { 3 | width: 4px; 4 | height: 4px; 5 | &-thumb { 6 | border-radius: 20px; 7 | background: #a5a5a5; 8 | box-shadow: inset 0 0 6px hsla(0,0%,80%,.3); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /code-standards/file.md: -------------------------------------------------------------------------------- 1 | ## 文件命名 2 | 3 | * 不能使用中文、数字(需要数字时用英文first、second字符代替) 4 | * 文件名使用全小写 5 | * vue、css、jpg等图片格式多单词文件名使用中划线`'-'`分隔 6 | 例如:follow-up-customer.vue、reset-common-style.css、radio-active.jpg 7 | * js多单词文件名使用`'.'`分隔 8 | 例如:build.systom.js 9 | 10 | ## 文件夹命名 11 | 12 | * 不能使用中文、数字(需要数字时用英文first、second字符代替) 13 | * 文件名使用全小写 14 | * 多单词分隔统一使用中划线命名 15 | -------------------------------------------------------------------------------- /src/common/js/dom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 操作DOM元素 3 | */ 4 | let collection = { 5 | /** 6 | * 判断是否包含某class样式 7 | * @param {元素} el 8 | * @param {样式名} className 9 | */ 10 | hasClass (el, className) { 11 | let reg = new RegExp("(^|\\s)" + className + "(\\s|$)"); 12 | return reg.test(el.className); 13 | } 14 | } 15 | 16 | export default collection; 17 | -------------------------------------------------------------------------------- /src/scss/reset.styl: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | ul, 12 | li { 13 | margin: 0; 14 | padding: 0; 15 | list-style: none; 16 | } 17 | 18 | dl, 19 | dt, 20 | dd, 21 | p { 22 | margin: 0; 23 | padding: 0; 24 | } 25 | 26 | a { 27 | text-decoration: none; 28 | } 29 | 30 | button { 31 | outline: none; 32 | } 33 | -------------------------------------------------------------------------------- /mocks/example/data.json: -------------------------------------------------------------------------------- 1 | /** 2 | * mock接口案例1 3 | * @url /example-data 4 | * Here you can write a detailed description 5 | * of the parameters of the information. 6 | */ 7 | 8 | { 9 | "code|1": [0, 0, 0, 0, 1], // simulation error code, 1/5 probability of error code 1. 10 | "data": { 11 | "list|5": [ 12 | {"title": "@title", "info": "@sentence(3, 5)", "link": "@url"} 13 | ] 14 | } 15 | } -------------------------------------------------------------------------------- /mocks/example/links.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mock接口案例2 3 | * @url /example-links 4 | * Here you can write a detailed description 5 | * of the parameters of the information. 6 | */ 7 | 8 | module.exports = { 9 | "code": function () { // simulation error code, 1/10 probability of error code 1. 10 | return Math.random() < 0.1 ? 1 : 0; 11 | }, 12 | "list|5-10": [ 13 | {"title": "@title", "link": "@url"} 14 | ] 15 | }; -------------------------------------------------------------------------------- /src/scss/app.ss: -------------------------------------------------------------------------------- 1 | @import './config.styl'; 2 | @import './reset.styl'; 3 | @import './iconfont.styl'; 4 | @import './button.styl'; 5 | @import './animation/slide.styl'; 6 | @import './mixins/clearfix.styl'; 7 | @import './common.styl'; 8 | 9 | body { 10 | color: #333333; 11 | font-family: 'Microsoft YaHei','PingFang SC','Hiragino Sans GB','SimSun','sans-serif'; 12 | } 13 | 14 | .clearfix { 15 | @include clearfix; 16 | } -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from 'src/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/directive/heigh-light.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 高亮代码段内容 3 | */ 4 | import hljs from 'highlight.js'; 5 | import 'highlight.js/styles/googlecode.css'; // 样式文件 6 | 7 | function initHighLight (Vue) { 8 | Vue.directive('highlight', function (el) { 9 | let blocks = el.querySelectorAll('pre code'); 10 | 11 | blocks.forEach((block) => { 12 | hljs.highlightBlock(block); 13 | }); 14 | }); 15 | } 16 | 17 | export default initHighLight; 18 | -------------------------------------------------------------------------------- /src/utils/dom.js: -------------------------------------------------------------------------------- 1 | const dom = { 2 | nodeContains: function (root, el) { 3 | if (root.compareDocumentPosition) { 4 | return root === el || !!(root.compareDocumentPosition(el) & 16); 5 | } 6 | if (root.contains && el.nodeType === 1) { 7 | return root.contains(el) && root !== el; 8 | } 9 | while ((el = el.parentNode)) { 10 | if (el === root) return true; 11 | } 12 | return false; 13 | } 14 | }; 15 | 16 | export default dom; 17 | -------------------------------------------------------------------------------- /src/views/sys-setting/runners/runners.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /src/views/sys-setting/withdraw/withdraw.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /src/common/stylus/elment.styl: -------------------------------------------------------------------------------- 1 | /* web-main */ 2 | .user-el-popover.el-popover{ 3 | padding: 12px 0px; 4 | .opera-list { 5 | a { 6 | display: block; 7 | padding: 4px 10px 4px 15px; 8 | overflow: hidden; 9 | color: #24292e; 10 | text-overflow: ellipsis; 11 | white-space: nowrap; 12 | &:hover { 13 | color: #fff; 14 | text-decoration: none; 15 | background-color: #0366d6; 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.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 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/store/modules/templateList.js: -------------------------------------------------------------------------------- 1 | const templateList = { 2 | namespaced: true, 3 | state: { 4 | templateListData: [], 5 | commonTemplateData: [] 6 | }, 7 | mutations: { 8 | setTemplateListData (state, payload) { 9 | const {list, isCommon} = payload; 10 | 11 | if (isCommon) { 12 | state.commonTemplateData = list; 13 | } else { 14 | state.templateListData = list; 15 | } 16 | } 17 | }, 18 | actions: { 19 | }, 20 | getters: {} 21 | }; 22 | 23 | export default templateList; 24 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/jsplumb-learn.styl: -------------------------------------------------------------------------------- 1 | .jsplumb-learn { 2 | display: flex; 3 | padding: 20px; 4 | height: 100vh; 5 | .menu-content { 6 | width: 200px; 7 | } 8 | .flex-container { 9 | flex: 1; 10 | } 11 | .flow-container { 12 | background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgba(0, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%); 13 | background-size: 20px 20px; 14 | background-color: rgb(251, 251, 251); 15 | position: relative; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/api/user.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // create an axios instance 4 | const service = axios.create({ 5 | baseURL: '/api', // api的base_url 6 | timeout: 5000 // request timeout 7 | }); 8 | 9 | export const getUserMenuList = params => { 10 | return service.post('/usercenter/getMenuList', params).then(res => res.data); 11 | }; 12 | 13 | export const getUserSchoolList = params => { 14 | return service.post('/usercenter/getSchoolList', params).then(res => res.data); 15 | }; 16 | 17 | export default { 18 | getUserMenuList, 19 | getUserSchoolList 20 | }; 21 | -------------------------------------------------------------------------------- /src/common/js/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 操作数组 3 | */ 4 | let collection = { 5 | // 去重 6 | uniq(array){ 7 | var temp = {}, r = [], len = array.length, val, type; 8 | for (var i = 0; i < len; i++) { 9 | val = array[i]; 10 | type = typeof val; 11 | if (!temp[val]) { 12 | temp[val] = [type]; 13 | r.push(val); 14 | } else if (temp[val].indexOf(type) < 0) { 15 | temp[val].push(type); 16 | r.push(val); 17 | } 18 | } 19 | return r; 20 | } 21 | } 22 | 23 | export default collection; 24 | 25 | -------------------------------------------------------------------------------- /src/common/js/ease.js: -------------------------------------------------------------------------------- 1 | export const ease = { 2 | // easeOutQuint 3 | swipe: { 4 | style: "cubic-bezier(0.23, 1, 0.32, 1)", 5 | fn: function (t) { 6 | return 1 + (--t * t * t * t * t); 7 | } 8 | }, 9 | // easeOutQuard 10 | swipeBounce: { 11 | style: "cubic-bezier(0.25, 0.46, 0.45, 0.94)", 12 | fn: function (t) { 13 | return t * (2 - t); 14 | } 15 | }, 16 | // easeOutQuart 17 | bounce: { 18 | style: "cubic-bezier(0.165, 0.84, 0.44, 1)", 19 | fn: function (t) { 20 | return 1 - (--t * t * t * t); 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/views/flow-template/node/node.styl: -------------------------------------------------------------------------------- 1 | 2 | .node-content { 3 | .node-inner { 4 | position: relative; 5 | z-index: 1; 6 | } 7 | .start-inner { 8 | width: 60px; 9 | height: 60px; 10 | line-height: 60px; 11 | text-align: center; 12 | font-size: 30px; 13 | color: #53699d; 14 | background: #fafafa; 15 | border-radius: 50%; 16 | border: 1px dashed #b1b5bc; 17 | } 18 | .task-inner { 19 | display: inline-block; 20 | width: 152px; 21 | font-size: 12px; 22 | height: 60px; 23 | background: #fafafa; 24 | border: 1px solid #a9adb5; 25 | border-bottom: none; 26 | table-layout: fixed; 27 | overflow: hidden; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/common/js/date.js: -------------------------------------------------------------------------------- 1 | export function formatDate(date, fmt) { 2 | if (/(y+)/.test(fmt)) { // 年份 3 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 4 | } 5 | let o = { 6 | "M+": date.getMonth() + 1, 7 | "d+": date.getDate(), 8 | "h+": date.getHours(), 9 | "m+": date.getMinutes(), 10 | "s+": date.getSeconds() 11 | }; 12 | for (let k in o) { 13 | if (new RegExp(`(${k})`).test(fmt)) { 14 | let str = o[k] + ""; 15 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : _pageLeftZero(str)); 16 | } 17 | } 18 | return fmt; 19 | }; 20 | 21 | function _pageLeftZero(str) { 22 | return ("00" + str).substr(str.length); 23 | } 24 | -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueI18n from 'vue-i18n'; // 国际化 3 | import elementEnLocale from 'element-ui/lib/locale/lang/en'; 4 | import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'; 5 | import {getLanguage} from 'common/js/util.js'; 6 | import enLocale from './en'; 7 | import zhLocale from './zh'; 8 | 9 | Vue.use(VueI18n); // 国际化 10 | 11 | const i18n = new VueI18n({ 12 | locale: getLanguage() || 'zh', 13 | messages: { 14 | en: { 15 | ...enLocale, // require('common/lang/en.js'), 16 | ...elementEnLocale 17 | }, 18 | zh: { 19 | ...zhLocale, // require('common/lang/zh.js'), 20 | ...elementZhLocale 21 | } 22 | } 23 | }); 24 | 25 | export default i18n; 26 | -------------------------------------------------------------------------------- /src/scss/analysis.styl: -------------------------------------------------------------------------------- 1 | .analysis-empty-data { 2 | margin-left: -10%; 3 | height: 600px; 4 | } 5 | .analysis-plotly-class { 6 | margin-left: 5%; 7 | } 8 | .analysis-date-picker { 9 | margin-top: 10px; 10 | min-width: 435px; 11 | /deep/ .el-date-editor { 12 | width: 260px; 13 | } 14 | } 15 | .analysis-select { 16 | margin-top: 10px; 17 | min-width: 290px; 18 | } 19 | .analysis-atom-select { 20 | margin-top: 10px; 21 | min-width: 265px; 22 | } 23 | .analysis-dataTable { 24 | margin-top: 10px; 25 | margin-top: 40px; 26 | } 27 | .analysis-date-picker-extra { 28 | margin-top: 10px; 29 | min-width: 400px; 30 | /deep/ .el-date-editor { 31 | width: 260px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/component/base/BaseTitle.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 31 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/data_A.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: '流程A', 3 | nodeList: [ 4 | { 5 | id: 'nodeA', 6 | name: '流程A-节点A', 7 | left: '26px', 8 | top: '161px', 9 | ico: 'el-icon-user-solid', 10 | show: true 11 | }, 12 | { 13 | id: 'nodeB', 14 | name: '流程A-节点B', 15 | left: '340px', 16 | top: '161px', 17 | ico: 'el-icon-goods', 18 | show: true 19 | }, 20 | { 21 | id: 'nodeC', 22 | name: '流程A-节点C', 23 | left: '739px', 24 | top: '161px', 25 | ico: 'el-icon-present', 26 | show: true 27 | } 28 | ], 29 | lineList: [{ 30 | from: 'nodeA', 31 | to: 'nodeB' 32 | }, { 33 | from: 'nodeB', 34 | to: 'nodeC' 35 | }] 36 | }; 37 | -------------------------------------------------------------------------------- /src/common/stylus/base.styl: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | min-height: 100vh; 6 | overflow: hidden; 7 | } 8 | a:focus, a:hover { 9 | text-decoration: none; 10 | } 11 | .clearfix { 12 | display: inline-block; 13 | } 14 | .clearfix:after { 15 | display: block; 16 | content: "."; 17 | height: 0; 18 | line-height: 0; 19 | clear: both; 20 | visibility: hidden; 21 | } 22 | input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { 23 | color: #9e9e9e; 24 | } 25 | input:-moz-placeholder, textarea:-moz-placeholder { 26 | color:#9e9e9e; 27 | } 28 | input::-moz-placeholder, textarea::-moz-placeholder { 29 | color:#9e9e9e; 30 | } 31 | input:-ms-input-placeholder, textarea:-ms-input-placeholder { 32 | color:#9e9e9e; 33 | } 34 | -------------------------------------------------------------------------------- /src/views/flow-template/flow-template.styl: -------------------------------------------------------------------------------- 1 | .flow-template { 2 | .top-content { 3 | padding: 0 60px; 4 | height: 58px; 5 | line-height: 58px; 6 | background: #f4f7fa; 7 | border-bottom: 1px solid #dddddd; 8 | } 9 | .main-content { 10 | display: flex; 11 | height: calc(100vh - 115px); 12 | .left { 13 | width: 60px; 14 | background: #f2f2f2; 15 | border: 1px solid #dddddd; 16 | height: 100%; 17 | } 18 | .right { 19 | flex: 1; 20 | background: #e1e4e8; 21 | height: 100%; 22 | .flow-contain { 23 | position: relative; 24 | height: 100%; 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/scss/mixins/multiLineEllipsis.styl: -------------------------------------------------------------------------------- 1 | /* mixin for multiline */ 2 | // @mixin multiLineEllipsis($lineHeight: 1.2em, $lineCount: 1, $bgColor: white){ 3 | multiLineEllipsis($lineHeight: 1.2em, $lineCount: 1, $bgColor: white){ 4 | overflow: hidden; 5 | position: relative; 6 | line-height: $lineHeight; 7 | max-height: $lineHeight * $lineCount; 8 | text-align: justify; 9 | margin-right: -1em; 10 | padding-right: 1em; 11 | &:before { 12 | content: '...'; 13 | position: absolute; 14 | right: 3px; 15 | bottom: 2px; 16 | } 17 | &:after { 18 | content: ''; 19 | position: absolute; 20 | right: 0; 21 | width: 1em; 22 | height: 1em; 23 | margin-top: 0.2em; 24 | background: $bgColor; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 18 | setupFiles: ['/test/unit/setup'], 19 | mapCoverage: true, 20 | coverageDirectory: '/test/unit/coverage', 21 | collectCoverageFrom: [ 22 | 'src/**/*.{js,vue}', 23 | '!src/main.js', 24 | '!src/router/index.js', 25 | '!**/node_modules/**' 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /code-standards/structure.md: -------------------------------------------------------------------------------- 1 | ### 项目组织架构 2 | >├─ .eslintrc.js 3 | >├─ index.html 4 | >├─ package.json // 配置文件 5 | >├─ README.md // 说明文件 6 | >├─ build // webpack打包执行文件 7 | >├─ config // webpack打包配置文件 8 | >├─ code-standards //编码规范 9 | >├─ src   10 | >│  ├─ main.js // webpack入口/项目启动入口 11 | >│  ├─ api // 存放api接口文件,服务层 12 | >│  ├─ common // 存放私有系统的公共样式、脚本、图片 13 | >│  │  ├─ css 14 | >│  │  │  └─ common.css // 公共样式 15 | >│  │  ├─ img // 公共图片 16 | >│  │  ├─ js 17 | >│  │  │  ├─ common.js // 公共脚本 18 | >│  │  │  └─ utils.js // 工具类 19 | >│  ├─ config 20 | >│  │  ├─ index.js // 共有配置文件 21 | >│  ├─ router 22 | >│  │  ├─ index.js // 存放路由 23 | >│  ├─ views // 视图 (路由跳转的页面) 24 | >│  ├─ pages //子视图(嵌套) 25 | >│  │  ├─ pages.md 26 | >│  ├─ vuex // 这一块将存放于common项目 27 | >│  │  ├─ index.js 28 | >│  │  ├─ actions 29 | >│  │  ├─ getters 30 | >│  │  └─ modules 31 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/jsplumb-learn.vue: -------------------------------------------------------------------------------- 1 | 20 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/lang/zh.js: -------------------------------------------------------------------------------- 1 | export default { 2 | login: { // 登录界面 3 | username: 'admin', 4 | password: '123456', 5 | loginBtn: '登录', 6 | userLogin: '用户登录', 7 | helpCenter: '帮助中心', 8 | cialWebsite: '企业官网', 9 | remember: '记住用户名', 10 | chinese: '简体中文', 11 | english: 'English', 12 | languageDesc: '语言:简体中文', 13 | emptyMsg: '用户名和密码不能为空', 14 | errMsg: '用户名或密码错误,或登录受到限制' 15 | }, 16 | header: { 17 | sysName: 'VUE 案例系统', 18 | settingManage: '配置管理', 19 | userCenter: '用户中心', 20 | companyHelp: '企业帮助', 21 | setting: '设置', 22 | logout: '退出', 23 | zh: '中文', 24 | en: 'English', 25 | help: '帮助中心' 26 | }, 27 | welcom: { 28 | userTitle: '下午好,admin', 29 | account: '账号信息:', 30 | mail: '绑定邮箱信息:8767XXXqq.com', 31 | mailManage: '管理', 32 | nearLogin: '最近登录:2018年1月18日星期四下午4点28分9秒', 33 | loginDetail: '详情', 34 | sysTitle: '系统功能介绍', 35 | selfSearch: '自助查询', 36 | support: '支持XXXXX,管理用户权限XXXX' 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/scss/config.styl: -------------------------------------------------------------------------------- 1 | // color settings 2 | $whiteDefault: #ffffff; 3 | $whiteMainBg: #f5f5f5; 4 | $whiteNodeBg: #fafafa; 5 | $whiteCanvasBg: #fcfbfb; 6 | $whitePanelBg: #fefcfc; 7 | $whiteDash: #f4f3f8; 8 | $whiteThinBg: #f7f9fa; 9 | 10 | $blackDefault: #000000; 11 | $blackBack: #2a2a2a; 12 | $black: #333333; 13 | 14 | $blueDefault: #3c96ff; 15 | $blueDark: #52699d; 16 | $blueDisable: #878c9c; 17 | $blueThinBg: #93c2f5; 18 | $blueDarkBg: #afd3ff; 19 | $blueStatus: #d9e8f8; 20 | $blueDashBg: #edf4fd; 21 | $blueNavBg: #f0f7ff; 22 | 23 | $redDefault: #ff5757; 24 | $redDark: #ff2602; 25 | 26 | $yellowDefault: #f8b53f; 27 | $yellowBg: #f4aa1a; 28 | 29 | $greenDefault: #30d878; 30 | $greenDark: #00c873; 31 | 32 | $greyDefault: #666666; 33 | $greyDark: #999999; 34 | $greyDisable: #cfcccc; 35 | $greyStatus: #727272; 36 | $greySkip: #ced1d6; 37 | $greyDash: #f0f1f2; 38 | 39 | $commonBgColor: #f2f2f2; 40 | $commonBorderColor: #dddddd; 41 | $formBorderColor: #c3cdd7; 42 | $stepNavColor: #dde4eb; 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 39 | -------------------------------------------------------------------------------- /src/views/flow-template/node-menu/node-menu.vue: -------------------------------------------------------------------------------- 1 | 25 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/common/stylus/mixin.styl: -------------------------------------------------------------------------------- 1 | clearfix { 2 | &:after { 3 | content: ""; 4 | display: table; 5 | clear: both; 6 | } 7 | } 8 | 9 | triangle($width, $height, $color, $direction) { 10 | $width = $width/2; 11 | $color-border-style = $height solid $color; 12 | $transparent-border-style = $width solid transparent; 13 | height: 0; 14 | width: 0; 15 | if $direction==up { 16 | border-bottom: $color-border-style; 17 | border-left: $transparent-border-style; 18 | border-right: $transparent-border-style; 19 | } 20 | else if $direction==right { 21 | border-left: $color-border-style; 22 | border-top: $transparent-border-style; 23 | border-bottom: $transparent-border-style; 24 | } 25 | else if $direction==down { 26 | border-top: $color-border-style; 27 | border-left: $transparent-border-style; 28 | border-right: $transparent-border-style; 29 | } 30 | else if $direction==left { 31 | border-right: $color-border-style; 32 | border-top: $transparent-border-style; 33 | border-bottom: $transparent-border-style; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/scss/animation/make-transitions.styl: -------------------------------------------------------------------------------- 1 | @mixin make-transitions($base, $names, $animationDuration: 0.5s) { 2 | 3 | .#{$base}-enter-active, .#{$base}In, 4 | .#{$base}-leave-active, .#{$base}Out { 5 | animation-duration: $animationDuration; 6 | animation-fill-mode: both; 7 | } 8 | 9 | .#{$base}-enter-active, .#{$base}In { 10 | animation-name: #{$base}In; 11 | } 12 | 13 | .#{$base}-leave-active, .#{$base}Out { 14 | animation-name: #{$base}Out; 15 | } 16 | 17 | @each $name in $names { 18 | .#{$base}#{$name}-enter-active, .#{$base}In#{$name}, 19 | .#{$base}#{$name}-leave-active, .#{$base}Out#{$name} { 20 | animation-duration: $animationDuration; 21 | animation-fill-mode: both; 22 | } 23 | 24 | .#{$base}#{$name}-enter-active, .#{$base}In#{$name} { 25 | animation-name: #{$base}In#{$name}; 26 | } 27 | .#{$base}#{$name}-leave-active, .#{$base}Out#{$name} { 28 | animation-name: #{$base}Out#{$name}; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lang/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | login: { // 登录界面 3 | username: 'user name', 4 | password: 'password', 5 | loginBtn: 'Login', 6 | userLogin: 'login user', 7 | helpCenter: 'Help center', 8 | cialWebsite: 'Cial website', 9 | remember: 'Remember username', 10 | chinese: '简体中文', 11 | english: 'English', 12 | languageDesc: 'Language:English', 13 | emptyMsg: 'The user name or password can\'t be null', 14 | errMsg: 'The user name or password is wrong, or the login is restricted' 15 | }, 16 | header: { 17 | sysName: 'VUE CASE', 18 | settingManage: 'System Manage', 19 | userCenter: 'User center', 20 | companyHelp: 'Company Help', 21 | setting: 'setting', 22 | logout: 'logout', 23 | zh: '中文', 24 | en: 'English', 25 | help: '帮助中心' 26 | }, 27 | welcom: { 28 | userTitle: 'user title', 29 | account: 'account', 30 | mail: 'mail:8767XXXqq.com', 31 | mailManage: 'manageer', 32 | nearLogin: 'near login 2018-12-12 16:22:22', 33 | loginDetail: 'detail', 34 | sysTitle: 'system title', 35 | selfSearch: 'slef search', 36 | support: 'suppport XXXX' 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/views/flow-template/flow-template.vue: -------------------------------------------------------------------------------- 1 | 26 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/node-menu/node-menu.vue: -------------------------------------------------------------------------------- 1 | 25 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/views/flow-template/node/node.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/flow-template/node/node.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | node: Object 4 | }, 5 | data () { 6 | return { 7 | // 控制节点操作显示 8 | mouseEnter: false 9 | }; 10 | }, 11 | computed: { 12 | // 节点容器样式 13 | nodeContainerStyle () { 14 | return { 15 | position: 'absolute', 16 | left: this.node.x, 17 | top: this.node.y 18 | }; 19 | }, 20 | nodeIcoClass () { 21 | var nodeIcoClass = {}; 22 | 23 | nodeIcoClass[this.node.ico] = true; 24 | // 添加该class可以推拽连线出来 25 | nodeIcoClass['flow-node-drag'] = true; 26 | return nodeIcoClass; 27 | } 28 | }, 29 | methods: { 30 | // 删除节点 31 | deleteNode () { 32 | this.$emit('deleteNode', this.node.id); 33 | }, 34 | // 编辑节点 35 | editNode () { 36 | this.$emit('editNode', this.node.id); 37 | }, 38 | // 鼠标移动后抬起 39 | changeNodePosition () { 40 | // 避免抖动 41 | if (this.node.x === this.$refs.node.style.left && this.node.y === this.$refs.node.style.top) { 42 | return; 43 | } 44 | this.$emit('changeNodeSite', { 45 | nodeId: this.node.id, 46 | x: this.$refs.node.style.left, 47 | y: this.$refs.node.style.top 48 | }); 49 | } 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/component/base/NoData.vue: -------------------------------------------------------------------------------- 1 | 11 | 31 | 52 | -------------------------------------------------------------------------------- /src/views/user-center/directive-define/directive-define.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 19 | 20 | 48 | -------------------------------------------------------------------------------- /src/views/user-center/define-component/define-component.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 20 | 21 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import router from './router'; // 路由相关配置 3 | import VueProgressBar from 'vue-progressbar'; // 进度条 4 | import i18n from 'src/lang'; // 国际化 5 | import Element from 'element-ui'; // 引入element-ui组件 6 | import 'element-ui/lib/theme-chalk/index.css'; // 引入element-ui的样式 7 | import store from 'src/store/index.js'; 8 | import directive from 'src/directive'; // 引入自定义指令 9 | import App from './App'; 10 | import 'common/stylus/index.styl'; 11 | import jquery from 'jquery'; 12 | 13 | Vue.config.productionTip = false; // 关闭生产模式下给出的提示 14 | 15 | Vue.use(VueProgressBar, { // 进度条 16 | color: 'rgb(143, 255, 199)', 17 | failedColor: 'red', 18 | height: '2px' 19 | }); 20 | Vue.use(directive); 21 | Vue.use(Element, {// 注册Element组件 22 | i18n: (key, value) => i18n.t(key, value) 23 | }); 24 | 25 | window.jquery = window.$ = jquery; 26 | if (typeof window.gettext !== 'function') { 27 | window.gettext = function gettext (string) { 28 | return string; 29 | }; 30 | } 31 | 32 | /* eslint-disable no-new */ 33 | new Vue({ 34 | router, 35 | i18n, 36 | store, 37 | el: '#app', // 相当于 new Vue({}).$mount('#app'); 38 | template: '', // 1、可以通过在 #app 内加入替代 2、或者 通过 render: h => h(App) 渲染一个视图,然后提供给el挂载 39 | components: { // vue2中可以通过 render: h => h(App) 渲染一个视图,然后提供给el挂载 40 | App 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /src/views/sys-setting/sys-setting.js: -------------------------------------------------------------------------------- 1 | import {mapActions, mapGetters} from 'vuex'; 2 | import api from 'api/index'; 3 | import welcomePage from 'views/sys-setting/welcome-page/welcome-page'; 4 | 5 | export default { 6 | data () { 7 | return { 8 | lang: this.$i18n.locale, // 当前语言 9 | menuList: [], // 菜单集合 10 | showMenuFlag: true, // 是否展示菜单 11 | isCollapse: false // 是否折叠菜单 12 | }; 13 | }, 14 | mounted () { 15 | this.$Progress.start(); // 显示进度条 http://hilongjw.github.io/vue-progressbar/index.html 16 | api.getSysMenuList().then((res) => { 17 | this.menuList = res.data; 18 | this.$Progress.finish(); 19 | }); 20 | }, 21 | methods: { 22 | ...mapActions([ // https://vuex.vuejs.org/zh-cn/actions.html 23 | 'sysAddTab', 24 | 'sysRemoveTab', 25 | 'sysClickTab' 26 | ]) 27 | }, 28 | computed: { 29 | ...mapGetters([ // https://vuex.vuejs.org/zh-cn/getters.html 30 | 'getSysHomeCurrentTab', 31 | 'getSysHomeTabs' 32 | ]) 33 | }, 34 | components: { 35 | welcomePage, 36 | baseManage: () => import('views/sys-setting/base-manage/base-manage.vue'), 37 | runners: () => import('views/sys-setting/runners/runners.vue'), 38 | withdraw: () => import('views/sys-setting/withdraw/withdraw.vue'), 39 | schollInfo: () => import('views/sys-setting/scholl-info/scholl-info.vue') // 异步组件 40 | 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/api/api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {Message} from 'element-ui'; 3 | 4 | // create an axios instance 5 | const service = axios.create({ 6 | baseURL: '/api', // api的base_url 7 | timeout: 5000 // request timeout 8 | }); 9 | 10 | // // request interceptor 11 | // service.interceptors.request.use(config => { 12 | // // Do something before request is sent 13 | // if (store.getters.token) { 14 | // config.headers['X-Token'] = getToken() // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改 15 | // } 16 | // return config 17 | // }, error => { 18 | // // Do something with request error 19 | // console.log(error) // for debug 20 | // Promise.reject(error) 21 | // }) 22 | 23 | // respone interceptor 24 | service.interceptors.response.use( 25 | response => response, 26 | error => { 27 | console.log('err' + error); // for debug 28 | Message({ 29 | message: error.message, 30 | type: 'error', 31 | duration: 5 * 1000 32 | }); 33 | return Promise.reject(error); 34 | }); 35 | 36 | export const getSysMenuList = params => { 37 | return service.post('/syssetting/getMenuList', params).then(res => res.data); 38 | }; 39 | 40 | export const getSysSchoolList = params => { 41 | return service.post('/syssetting/getSchoolList', params).then(res => res.data); 42 | }; 43 | 44 | let api = { 45 | getSysMenuList, 46 | getSysSchoolList 47 | }; 48 | 49 | export default api; 50 | -------------------------------------------------------------------------------- /src/utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * vue组件接口请求异常处理函数 3 | * @param {Object} error 错误对象 4 | * @param {Object} instance 5 | */ 6 | export function errorHandler (error, instance) { 7 | const data = error.data; 8 | 9 | console.error(error); 10 | if (data && data.code) { 11 | if (!data.code || data.code === 404) { 12 | instance.exception = { 13 | code: '404', 14 | msg: window.gettext('当前访问的页面不存在') 15 | }; 16 | } else if (data.code === 403) { 17 | instance.exception = { 18 | code: '403', 19 | msg: window.gettext('sorry,您没有访问权限!') 20 | }; 21 | } else if (data.code === 405) { 22 | instance.exception = { 23 | code: '405', 24 | msg: window.gettext('Sorry,您的权限不足!') 25 | }; 26 | } else if (data.code === 406) { 27 | instance.exception = { 28 | code: '405', 29 | msg: window.gettext('Sorry,您的权限不足!') 30 | }; 31 | } else if (data.code === 500) { 32 | instance.exception = { 33 | code: '500', 34 | msg: window.gettext('系统出现异常, 请记录下错误场景并与开发人员联系, 谢谢!') 35 | }; 36 | } else if (data.code === 502) { 37 | instance.exception = { 38 | code: '502', 39 | msg: window.gettext('系统出现异常, 请记录下错误场景并与开发人员联系, 谢谢!') 40 | }; 41 | } 42 | } else { 43 | instance.bkMessageInstance = instance.$bkMessage({ 44 | theme: 'error', 45 | message: error.message || error.data.msg 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mocks/syssetting/getMenuList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置管理模块 > 菜单列表信息 3 | * @url /syssetting/getMenuList 4 | * Here you can write a detailed description 5 | * of the parameters of the information. 6 | */ 7 | module.exports = { 8 | "code|1": [0, 0, 0, 0, 1], // simulation error code, 1/5 probability of error code 1. 9 | "message|1": ["success","success","success","success","fail"], 10 | "data": [{ 11 | "menuId": "id-1", 12 | "menuNameCn":"基础管理", 13 | "menuNameEn":"Base Manage", 14 | "iconCls": "el-icon-location", 15 | "children":[], 16 | "comp":"baseManage", 17 | "url":"/baseManage" 18 | },{ 19 | "menuId": "id-2", 20 | "menuNameCn":"学校管理", 21 | "menuNameEn":"Scholl", 22 | "iconCls": "el-icon-location", 23 | "children":[{ 24 | "menuId": "id-3", 25 | "menuNameCn":"学校信息", 26 | "menuNameEn":"scholl Info", 27 | "children":[], 28 | "comp":"schollInfo", 29 | "url":"/schollInfo" 30 | },{ 31 | "menuId": "id-4", 32 | "menuNameCn":"提现管理", 33 | "menuNameEn":"withdraw", 34 | "children":[], 35 | "comp":"withdraw", 36 | "url":"/withdraw" 37 | }], 38 | "comp":"", 39 | "url":"" 40 | },{ 41 | "menuId": "id-5", 42 | "menuNameCn":"跑腿人", 43 | "menuNameEn":"Runners", 44 | "iconCls": "el-icon-location", 45 | "children":[], 46 | "comp":"runners", 47 | "url":"/runners" 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /src/views/user-center/user-center.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import {mapActions, mapGetters} from 'vuex'; 3 | import userApi from 'api/user'; 4 | 5 | export default { 6 | data () { 7 | return { 8 | lang: this.$i18n.locale, // 当前语言 9 | menuList: [], // 菜单集合 10 | showMenuFlag: true, // 是否展示菜单 11 | isCollapse: false // 是否折叠菜单 12 | }; 13 | }, 14 | mounted () { 15 | this.$Progress.start(); // 显示进度条 http://hilongjw.github.io/vue-progressbar/index.html 16 | userApi.getUserMenuList().then((res) => { 17 | this.menuList = res.data; 18 | this.$Progress.finish(); 19 | }); 20 | }, 21 | methods: { 22 | ...mapActions([ // https://vuex.vuejs.org/zh-cn/actions.html 23 | 'userAddTab', 24 | 'userRemoveTab', 25 | 'userClickTab' 26 | ]) 27 | }, 28 | computed: { 29 | ...mapGetters([ // https://vuex.vuejs.org/zh-cn/getters.html 30 | 'getUserHomeCurrentTab', 31 | 'getUserHomeTabs' 32 | ]), 33 | currentMenu () { 34 | return this.$store.state.userCenter.homeCurrentTab; 35 | } 36 | }, 37 | components: { 38 | directiveInlay: () => import('views/user-center/directive-inlay/directive-inlay.vue'), 39 | directiveDefine: () => import('views/user-center/directive-define/directive-define.vue'), 40 | defineFilter: () => import('views/user-center/define-filter/define-filter.vue'), 41 | defineComponent: () => import('views/user-center/define-component/define-component.vue') 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /prod.server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var config = require('./config/index'); 3 | 4 | var port = process.env.PORT || config.build.port; 5 | 6 | var app = express(); 7 | 8 | var router = express.Router(); 9 | 10 | router.get('/', function (req, res, next) { 11 | req.url = '/index.html'; 12 | next(); 13 | }); 14 | 15 | app.use(router); 16 | 17 | var syssetting = require('./data/data.json'); 18 | var usercenter = require('./data/usercenter.json'); 19 | var apiRoutes = express.Router(); 20 | 21 | 22 | app.post('/syssetting/getMenuList', function(req, res) { 23 | res.json({ 24 | code: 0, 25 | message: 'success', 26 | data: syssetting.menuList 27 | }); 28 | }); 29 | app.post('/syssetting/getSchoolList', function(req, res) { 30 | res.json({ 31 | code: 0, 32 | message: 'success', 33 | data: syssetting.schoolList 34 | }); 35 | }); 36 | app.post('/usercenter/getMenuList', function(req, res) { 37 | res.json({ 38 | code: 0, 39 | message: 'success', 40 | data: usercenter.menuList 41 | }); 42 | }); 43 | app.post('/usercenter/getSchoolList', function(req, res) { 44 | res.json({ 45 | code: 0, 46 | message: 'success', 47 | data: usercenter.schoolList 48 | }); 49 | }); 50 | 51 | app.use('/api', apiRoutes); 52 | 53 | app.use(express.static('./dist')); 54 | 55 | module.exports = app.listen(port, function (err) { 56 | if (err) { 57 | console.log(err); 58 | return 59 | } 60 | console.log('Listening at http://localhost:' + port + '\n') 61 | }); 62 | -------------------------------------------------------------------------------- /src/store/modules/atomList.js: -------------------------------------------------------------------------------- 1 | const atomList = { 2 | namespaced: true, 3 | state: { 4 | singleAtom: [], 5 | subAtom: [], 6 | searchAtomResult: [] 7 | }, 8 | mutations: { 9 | setSingleAtom (state, data) { 10 | state.singleAtom = [...data]; 11 | }, 12 | setSubAtom (state, data) { 13 | state.subAtom = [...data]; 14 | }, 15 | searchAtom (state, payload) { 16 | const dict = { 17 | 'tasknode': 'singleAtom', 18 | 'subflow': 'subAtom' 19 | }; 20 | const data = state[dict[payload.type]]; 21 | const reg = new RegExp(payload.text); 22 | 23 | state.searchAtomResult = data.filter(item => { 24 | return payload.exclude.indexOf(item.id) === -1 && reg.test(item.name); 25 | }); 26 | } 27 | }, 28 | actions: { 29 | }, 30 | getters: { 31 | singleAtomGrouped (state) { 32 | const primaryData = state.singleAtom; 33 | const groups = []; 34 | const atomGrouped = []; 35 | 36 | primaryData.forEach(item => { 37 | const type = item.group_name; 38 | const index = groups.indexOf(type); 39 | 40 | if (index > -1) { 41 | atomGrouped[index].list.push(item); 42 | } else { 43 | const newGroup = { 44 | type, 45 | group_name: item.group_name, 46 | group_icon: item.group_icon, 47 | list: [item] 48 | }; 49 | 50 | groups.push(type); 51 | atomGrouped.push(newGroup); 52 | } 53 | }); 54 | 55 | return [...atomGrouped]; 56 | } 57 | } 58 | }; 59 | 60 | export default atomList; 61 | -------------------------------------------------------------------------------- /src/component/base/BaseInput.vue: -------------------------------------------------------------------------------- 1 | 13 | 40 | 67 | -------------------------------------------------------------------------------- /src/views/sys-setting/sys-setting.styl: -------------------------------------------------------------------------------- 1 | .web-home { 2 | .home-wrapper { 3 | box-sizing: border-box; 4 | min-height: 100vh; 5 | .aside-menu { 6 | position: relative; 7 | width: 200px; 8 | max-height: calc(100vh - 55px); 9 | overflow-y: auto; 10 | background-color: rgb(84, 92, 100); 11 | &.aside-menu-leave-active, &.aside-menu-enter-active { 12 | transition: all 0.25s; 13 | } 14 | &.aside-menu-leave, &.aside-menu-enter-to { 15 | margin-left: 0; 16 | } 17 | &.aside-menu-enter, &.aside-menu-leave-to { 18 | margin-left: -200px; 19 | } 20 | .aside-el-menu-vertical { 21 | margin-bottom: 20px; 22 | } 23 | ul { 24 | width: 100%; 25 | li { 26 | width: 100%; 27 | } 28 | } 29 | .menu-operate-collapse { 30 | box-sizing: border-box; 31 | width: 100%; 32 | height: 30px; 33 | padding-right: 10px; 34 | text-align: right; 35 | background: #333; 36 | .opeerate { 37 | font-size: 20px; 38 | font-weight: bold; 39 | line-height: 30px; 40 | color: #eee; 41 | cursor: hover; 42 | &:hover { 43 | color: #ffffff; 44 | } 45 | } 46 | } 47 | } 48 | .main-wrapper { 49 | flex: 1; 50 | box-sizing: border-box; 51 | padding: 10px 0px 0px 10px; 52 | background: #e4e5e6; 53 | height: calc(100vh - 55px); 54 | overflow-y: auto; 55 | // .navigation { 56 | // padding: .75rem 1rem; 57 | // background: #fff; 58 | // border-bottom: 1px solid #c2cfd6; 59 | // } 60 | .home-content { 61 | background-color: #fff; 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/scss/animation/slide.styl: -------------------------------------------------------------------------------- 1 | @import './make-transitions.styl'; 2 | 3 | @keyframes slideInLeft { 4 | from { 5 | transform: translate3d(-100%, 0, 0); 6 | visibility: visible; 7 | } 8 | 9 | to { 10 | transform: translate3d(0, 0, -1); 11 | } 12 | } 13 | 14 | @keyframes slideOutLeft { 15 | from { 16 | transform: translate3d(0, 0, -1); 17 | } 18 | 19 | to { 20 | visibility: hidden; 21 | transform: translate3d(-100%, 0, 0); 22 | } 23 | } 24 | 25 | @keyframes slideInRight { 26 | from { 27 | transform: translate3d(100%, 0, 0); 28 | visibility: visible; 29 | } 30 | 31 | to { 32 | transform: translate3d(0, 0, 0); 33 | } 34 | } 35 | 36 | @keyframes slideOutRight { 37 | from { 38 | transform: translate3d(0, 0, 0); 39 | } 40 | 41 | to { 42 | visibility: hidden; 43 | transform: translate3d(100%, 0, 0); 44 | } 45 | } 46 | 47 | @keyframes slideInDown { 48 | from { 49 | transform: translate3d(0, -100%, 0); 50 | visibility: visible; 51 | } 52 | 53 | to { 54 | transform: translate3d(0, 0, 0); 55 | } 56 | } 57 | 58 | @keyframes slideOutDown { 59 | from { 60 | transform: translate3d(0, 0, 0); 61 | } 62 | 63 | to { 64 | visibility: hidden; 65 | transform: translate3d(0, -100%, 0); 66 | } 67 | } 68 | 69 | @keyframes slideInUp { 70 | from { 71 | transform: translate3d(0, 100%, 0); 72 | visibility: visible; 73 | } 74 | 75 | to { 76 | transform: translate3d(0, 0, 0); 77 | } 78 | } 79 | 80 | @keyframes slideOutUp { 81 | from { 82 | transform: translate3d(0, 0, 0); 83 | } 84 | 85 | to { 86 | visibility: hidden; 87 | transform: translate3d(0, -100%, 0); 88 | } 89 | } 90 | 91 | $slides: Down, Right, Up, Left; 92 | @include make-transitions('slide', $slides); -------------------------------------------------------------------------------- /src/component/base/BaseCheckbox.vue: -------------------------------------------------------------------------------- 1 | 8 | 30 | 69 | -------------------------------------------------------------------------------- /src/views/user-center/define-filter/define-filter.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 39 | 40 | 68 | -------------------------------------------------------------------------------- /src/views/user-center/user-center.styl: -------------------------------------------------------------------------------- 1 | .user-center { 2 | .home-wrapper { 3 | box-sizing: border-box; 4 | min-height: 100vh; 5 | .aside-menu { 6 | position: relative; 7 | width: 200px; 8 | max-height: calc(100vh - 55px); 9 | overflow-y: auto; 10 | background-color: rgb(84, 92, 100); 11 | &.aside-menu-leave-active, &.aside-menu-enter-active { 12 | transition: all 0.25s; 13 | } 14 | &.aside-menu-leave, &.aside-menu-enter-to { 15 | margin-left: 0; 16 | } 17 | &.aside-menu-enter, &.aside-menu-leave-to { 18 | margin-left: -200px; 19 | } 20 | .aside-el-menu-vertical { 21 | margin-bottom: 20px; 22 | } 23 | ul { 24 | width: 100%; 25 | li { 26 | width: 100%; 27 | } 28 | } 29 | .menu-operate-collapse { 30 | box-sizing: border-box; 31 | width: 100%; 32 | height: 30px; 33 | padding-right: 10px; 34 | text-align: right; 35 | background: #333; 36 | .opeerate { 37 | font-size: 20px; 38 | font-weight: bold; 39 | line-height: 30px; 40 | color: #eee; 41 | cursor: hover; 42 | &:hover { 43 | color: #ffffff; 44 | } 45 | } 46 | } 47 | } 48 | .main-wrapper { 49 | flex: 1; 50 | box-sizing: border-box; 51 | padding: 10px 0px 0px 10px; 52 | background: #e4e5e6; 53 | height: calc(100vh - 55px); 54 | overflow-y: auto; 55 | // .navigation { 56 | // padding: .75rem 1rem; 57 | // background: #fff; 58 | // border-bottom: 1px solid #c2cfd6; 59 | // } 60 | .home-content { 61 | background-color: #fff; 62 | .default-page { 63 | padding: 50px 0; 64 | font-size: 20px; 65 | text-align: center; 66 | color: rgb(192, 204, 218); 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /mocks/usercenter/getMenuList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用户中心模块 > 菜单列表信息 3 | * @url /usercenter/getMenuList 4 | * Here you can write a detailed description 5 | * of the parameters of the information. 6 | */ 7 | module.exports = { 8 | "code|1": [0, 0, 0, 0, 1], // simulation error code, 1/5 probability of error code 1. 9 | "message|1": ["success","success","success","success","fail"], 10 | "data": [{ 11 | "menuId": "id-1", 12 | "menuNameCn":"VUE基础", 13 | "menuNameEn":"VUE基础", 14 | "iconCls": "el-icon-location", 15 | "children":[{ 16 | "menuId": "id-2", 17 | "menuNameCn":"内置指令", 18 | "menuNameEn":"内置指令", 19 | "iconCls": "el-icon-location", 20 | "children":[], 21 | "comp":"directiveInlay", 22 | "url":"/directiveInlay" 23 | },{ 24 | "menuId": "id-3", 25 | "menuNameCn":"自定义指令", 26 | "menuNameEn":"自定义指令", 27 | "iconCls": "el-icon-location", 28 | "children":[], 29 | "comp":"directiveDefine", 30 | "url":"/directiveDefine" 31 | },{ 32 | "menuId": "id-5", 33 | "menuNameCn":"过滤器", 34 | "menuNameEn":"过滤器", 35 | "iconCls": "el-icon-location", 36 | "children":[], 37 | "comp":"defineFilter", 38 | "url":"/defineFilter" 39 | },{ 40 | "menuId": "id-6", 41 | "menuNameCn":"组件", 42 | "menuNameEn":"组件", 43 | "iconCls": "el-icon-location", 44 | "children":[], 45 | "comp":"defineComponent", 46 | "url":"/defineComponent" 47 | }], 48 | "comp":"", 49 | "url":"" 50 | },{ 51 | "menuId": "id-7", 52 | "menuNameCn":"菜单二", 53 | "menuNameEn":"Scholl", 54 | "iconCls": "el-icon-location", 55 | "children":[], 56 | "comp":"", 57 | "url":"" 58 | },{ 59 | "menuId": "id-8", 60 | "menuNameCn":"菜单三", 61 | "menuNameEn":"Runners", 62 | "iconCls": "el-icon-location", 63 | "children":[], 64 | "comp":"", 65 | "url":"/" 66 | } 67 | ] 68 | } -------------------------------------------------------------------------------- /src/component/com-panel/com-panel.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 60 | 61 | 85 | -------------------------------------------------------------------------------- /src/views/login/login.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | userName: 'zh', // localStorage.getItem('loginName'), 5 | password: '', 6 | errMessage: '', 7 | rememberFlag: 'zh', // localStorage.getItem('rememberFlag'), 8 | langFlag: false, 9 | currentLang: '', 10 | random: Math.floor(Math.random(0, 1) * 3) 11 | }; 12 | }, 13 | created () { 14 | let lang = 'zh'; // localStorage.getItem('langulage') || 'zh'; 15 | 16 | this.$i18n.locale = lang; 17 | this.$store.state.lang = lang; 18 | this.currentLang = lang; 19 | }, 20 | methods: { 21 | loginFn () { // 登录事件 22 | this._confirmLogin(); 23 | }, 24 | rememberFn () { // 是否记住用户 25 | this.rememberFlag = !this.rememberFlag; 26 | localStorage.setItem('rememberFlag', this.rememberFlag); 27 | }, 28 | showLangFn () { // 展开语言选择 29 | this.langFlag = !this.langFlag; 30 | }, 31 | hideLangFn () { 32 | this.langFlag = false; 33 | }, 34 | changeLangFn (lang) { // 切换语言 35 | this.currentLang = lang; 36 | this.$i18n.locale = lang; 37 | this.$store.state.lang = lang; 38 | }, 39 | keydownFn (event) { 40 | if (event.keyCode === 13) { 41 | this._confirmLogin(); 42 | } 43 | }, 44 | _confirmLogin () { 45 | if (this.rememberFlag) { // 是否记住用户名 46 | // localStorage.setItem('loginName', this.userName); 47 | } else { 48 | // localStorage.removeItem('loginName'); 49 | } 50 | 51 | if (!this.userName || !this.password) { 52 | this.errMessage = this.$t('login.emptyMsg'); 53 | } else if (this.userName === 'admin' && this.password === '123456') { // 判断用户名密码是否为空 54 | this.errMessage = ''; 55 | this.$store.state.isLogin = true; 56 | if (this.$route.query.redirect) { // 跳转到指定链接 57 | this.$router.push({path: this.$route.query.redirect}); 58 | } else { 59 | this.$router.push({path: '/sysSetting'}); 60 | } 61 | } else { 62 | this.errMessage = this.$t('login.errMsg'); 63 | } 64 | } 65 | }, 66 | computed: { 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /src/component/base/BaseCollapse.vue: -------------------------------------------------------------------------------- 1 | 12 | 38 | 73 | -------------------------------------------------------------------------------- /src/common/stylus/icon.styl: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'icon-demo'; 3 | src: url('../fonts/icon-demo.eot?rukk03'); 4 | src: url('../fonts/icon-demo.eot?rukk03#iefix') format('embedded-opentype'), 5 | url('../fonts/icon-demo.ttf?rukk03') format('truetype'), 6 | url('../fonts/icon-demo.woff?rukk03') format('woff'), 7 | url('../fonts/icon-demo.svg?rukk03#icon-demo') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'icon-demo' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .icon-uniF0A6F9:before { 28 | content: "\f0a6"; 29 | } 30 | .icon-uniF0A7F9:before { 31 | content: "\f0a7"; 32 | } 33 | .icon-uniF0A8F9:before { 34 | content: "\f0a8"; 35 | } 36 | .icon-uniF0A9F9:before { 37 | content: "\f0a9"; 38 | } 39 | .icon-uniF0AAF9:before { 40 | content: "\f0aa"; 41 | } 42 | .icon-uniF0ABF9:before { 43 | content: "\f0ab"; 44 | } 45 | .icon-uniF0ACF9:before { 46 | content: "\f0ac"; 47 | } 48 | .icon-uniF0ADF9:before { 49 | content: "\f0ad"; 50 | } 51 | .icon-uniF0C3F9:before { 52 | content: "\f0c3"; 53 | } 54 | .icon-uniF0C4F9:before { 55 | content: "\f0c4"; 56 | } 57 | .icon-uniF0C5F9:before { 58 | content: "\f0c5"; 59 | } 60 | .icon-uniF0C6F9:before { 61 | content: "\f0c6"; 62 | } 63 | .icon-uniF0C7F9:before { 64 | content: "\f0c7"; 65 | } 66 | .icon-uniF0C8F9:before { 67 | content: "\f0c8"; 68 | } 69 | .icon-uniF0CAF9:before { 70 | content: "\f0ca"; 71 | } 72 | .icon-uniF0CBF9:before { 73 | content: "\f0cb"; 74 | } 75 | .icon-uniF0F0F9:before { 76 | content: "\f0f0"; 77 | } 78 | .icon-uniF104F9:before { 79 | content: "\f104"; 80 | } 81 | .icon-uniF105F9:before { 82 | content: "\f105"; 83 | } 84 | .icon-uniF106F9:before { 85 | content: "\f106"; 86 | } 87 | .icon-uniF107F9:before { 88 | content: "\f107"; 89 | } 90 | .icon-uniF13EF9:before { 91 | content: "\f13e"; 92 | } 93 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/node-menu/node-menu.js: -------------------------------------------------------------------------------- 1 | import draggable from 'vuedraggable'; 2 | 3 | export default { 4 | components: { 5 | draggable 6 | }, 7 | data () { 8 | return { 9 | draggableOptions: { 10 | preventOnFilter: false 11 | }, 12 | // 默认打开的左侧菜单的id 13 | defaultOpeneds: ['1'], 14 | // 菜单列表 15 | menuList: [ 16 | { 17 | id: '1', 18 | type: 'group', 19 | name: '开始节点', 20 | ico: 'el-icon-video-play', 21 | children: [ 22 | { 23 | id: '11', 24 | type: 'timer', 25 | name: '定时器', 26 | ico: 'el-icon-time' 27 | }, { 28 | id: '12', 29 | type: 'task', 30 | name: '定时任务', 31 | ico: 'el-icon-odometer' 32 | } 33 | ] 34 | }, 35 | { 36 | id: '2', 37 | type: 'group', 38 | name: '结束节点', 39 | ico: 'el-icon-video-pause', 40 | children: [ 41 | { 42 | id: '21', 43 | type: 'end', 44 | name: '结束', 45 | ico: 'el-icon-caret-right' 46 | }, { 47 | id: '22', 48 | type: 'over', 49 | name: '清理', 50 | ico: 'el-icon-shopping-cart-full' 51 | } 52 | ] 53 | } 54 | ], 55 | mousePosition: { 56 | left: -1, 57 | top: -1 58 | } 59 | }; 60 | }, 61 | methods: { 62 | // 拖拽开始时触发 63 | beginMoveFn (evt) { 64 | const ths = this; 65 | var type = evt.item.attributes.type.nodeValue; 66 | 67 | ths.nodeMenu = ths.getMenu(type); 68 | }, 69 | // 拖拽结束时触发 70 | addNodeFn (evt, e) { 71 | const ths = this; 72 | 73 | ths.$emit('add-node', evt, ths.nodeMenu, ths.mousePosition); 74 | }, 75 | // 根据类型获取左侧菜单对象 76 | getMenu (type) { 77 | for (let i = 0; i < this.menuList.length; i++) { 78 | let children = this.menuList[i].children; 79 | 80 | for (let j = 0; j < children.length; j++) { 81 | if (children[j].type === type) { 82 | return children[j]; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | const TASK_STATE_DICT = { 2 | 'CREATED': window.gettext('未执行'), 3 | 'RUNNING': window.gettext('执行中'), 4 | 'SUSPENDED': window.gettext('暂停'), 5 | 'NODE_SUSPENDED': window.gettext('节点暂停'), 6 | 'FAILED': window.gettext('失败'), 7 | 'FINISHED': window.gettext('完成'), 8 | 'REVOKED': window.gettext('撤销') 9 | }; 10 | 11 | const NODE_DICT = { 12 | 'startpoint': window.gettext('开始节点'), 13 | 'endpoint': window.gettext('结束节点'), 14 | // 'startPoint': window.gettext('开始节点'), 15 | // 'endPoint': window.gettext('结束节点'), 16 | 'parallelgateway': window.gettext('并行网关'), 17 | 'branchgateway': window.gettext('分支网关'), 18 | 'convergegateway': window.gettext('汇聚网关'), 19 | 'tasknode': window.gettext('标准插件节点'), 20 | 'subflow': window.gettext('子流程节点') 21 | }; 22 | 23 | // 最大长度常量 24 | const TEMPLATE_NAME_MAX_LENGTH = 50; 25 | const TEMPLATE_NODE_NAME_MAX_LENGTH = 50; 26 | const TASK_NAME_MAX_LENGTH = 100; 27 | const STAGE_NAME_MAX_LENGTH = 50; 28 | const DRAFT_NAME_MAX_LENGTH = 20; 29 | const SCHEME_NAME_MAX_LENGTH = 30; 30 | const APP_NAME_MAX_LENGTH = 20; 31 | const APP_DESCRIPTION_MAX_LENGTH = 30; 32 | const VARIABLE_NAME_MAX_LENGTH = 20; 33 | const VARIABLE_KEY_MAX_LENGTH = 20; 34 | 35 | const STRING_LENGTH = { 36 | TEMPLATE_NAME_MAX_LENGTH, 37 | TEMPLATE_NODE_NAME_MAX_LENGTH, 38 | TASK_NAME_MAX_LENGTH, 39 | STAGE_NAME_MAX_LENGTH, 40 | DRAFT_NAME_MAX_LENGTH, 41 | SCHEME_NAME_MAX_LENGTH, 42 | APP_NAME_MAX_LENGTH, 43 | APP_DESCRIPTION_MAX_LENGTH, 44 | VARIABLE_NAME_MAX_LENGTH, 45 | VARIABLE_KEY_MAX_LENGTH 46 | }; 47 | 48 | // const NAME_REG = /^[^'"‘’“”\$<>]+$/; 49 | const NAME_REG = /^[^'"‘’“”$<>]+$/; 50 | 51 | // celery的crontab时间表达式正则表达式(分钟 小时 星期 日 月)(以空格分割) 52 | // 例子请见图片assets/images/task-zh.png 53 | const PERIODIC_REG = /^((\*\/)?(([0-5]?\d[,-/])*([0-5]?\d))|\*)[ ]((\*\/)?(([0]?[0-9]|1\d|2[0-3])[,-/])*(([0]?[0-9]|1\d|2[0-3]))|\*)[ ]((\*\/)?((([0-6][,-/])*[0-6])|((mon|tue|wed|thu|fri|sat|sun)[,-/])*(mon|tue|wed|thu|fri|sat|sun))|\*)[ ]((\*\/)?((0?[1-9]|[12]\d|3[01])[,-/])*((0?[1-9]|[12]\d|3[01]))|\*)[ ]((\*\/)?((0?[1-9]|1[0-2])[,-/])*(0?[1-9]|1[0-2])|\*)$/; 54 | 55 | /* eslint-disable */ 56 | const URL_REG= new RegExp('^(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]$') 57 | /* eslint-enable */ 58 | 59 | export {TASK_STATE_DICT, NODE_DICT, NAME_REG, URL_REG, PERIODIC_REG, STRING_LENGTH}; 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vue-web-demo 2 | vue 实现网页版前端框架搭建,只需在此基础上增加独立组件,便可很快速开发一个完善的后台管理系统。 3 | 4 | ## 主要功能 5 | > 登录退出功能 6 | 7 | > 国际化中英文界面切换 8 | 9 | > 动态菜单列表 10 | 11 | > 通过动态页签增减实现组件切换展示 12 | 13 | > 路由切换菜单功能 14 | 15 | > 通过mock + express 实现前后端分离 16 | 17 | ## 效果展示 18 | 19 | ![demo](./present/demo0.png) 20 | 21 | ![mock](./present/mock.png) 22 | 23 | 24 | ## 技术栈 25 | > [vue.js](https://cn.vuejs.org/) 构建用户界面的 MVVM 框架,核心思想是:数据驱动、组件系统。 26 | 27 | > [vue-cli](https://www.npmjs.com/package/vue-cli) 是vue的脚手架工具,目录结构、本地调试、代码部署、热加载、单元测试。 28 | 29 | > [vue-router](https://router.vuejs.org/zh-cn/) 是官方提供的路由器,使用vue.js构建单页面应用程序变得轻而易举。 30 | 31 | > [vue-resource](https://www.npmjs.com/package/vue-resource) 请求数据,服务器通讯。 32 | 33 | > [vuex](https://vuex.vuejs.org/zh-cn/) 是一个专为 vue.js 应用程序开发的状态管理模式,简单来说Vuex就是管理数据的。 34 | 35 | > [Element](http://element-cn.eleme.io/#/zh-CN) 一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库 36 | 37 | > [Vue-progressbar](http://hilongjw.github.io/vue-progressbar/) 进度条组件。 38 | 39 | > [vue-i18n](https://kazupon.github.io/vue-i18n/) 国际化资源管理组件。 40 | 41 | > [mock](http://mockjs.com/) mock是一个模拟数据生成器,旨在帮助前端独立于后端进行开发,帮助编写单元测试。 42 | 43 | > [bk-sops](https://github.com/Tencent/bk-sops) bk-sops图形拖拽设计学习 44 | 45 | > [jsPlumb](http://xiaoka2017.gitee.io/easy-flow/#) 照着这位大佬的项目学习的jsPlumb(非常感谢) 46 | 47 | > A Vue.js project 48 | 49 | ## Build Setup 50 | 51 | ``` bash 52 | # install dependencies 53 | cnpm install 54 | 55 | # 启动前端数据接口(前后端分离)at localhost:8082/api/ 56 | npm run mock 57 | 58 | # serve with hot reload at localhost:8080 59 | npm run dev 60 | 61 | 62 | ``` 63 | 64 | ## 项目组织架构 65 | >├─ .eslintrc.js 66 | >├─ index.html 67 | >├─ package.json // 配置文件 68 | >├─ README.md // 说明文件 69 | >├─ build // webpack打包执行文件 70 | >├─ mock // mock前后端分离实现的数据接口 71 | >├─ config // webpack打包配置文件 72 | >├─ code-standards //编码规范 73 | >├─ src   74 | >│  ├─ main.js // webpack入口/项目启动入口 75 | >│  ├─ api // 存放api接口文件,服务层 76 | >│  ├─ common // 存放私有系统的公共样式、脚本、图片 77 | >│  │  ├─ css 78 | >│  │  │  └─ common.css // 公共样式 79 | >│  │  ├─ img // 公共图片 80 | >│  │  ├─ js 81 | >│  │  │  ├─ common.js // 公共脚本 82 | >│  │  │  └─ utils.js // 工具类 83 | >│  ├─ config 84 | >│  │  ├─ index.js // 共有配置文件 85 | >│  ├─ router 86 | >│  │  ├─ index.js // 存放路由 87 | >│  ├─ views // 视图 88 | >│  │  ├─ views.md 89 | >│  ├─ vuex // 这一块将存放于common项目 90 | >│  │  ├─ index.js 91 | >│  │  ├─ actions 92 | >│  │  ├─ getters 93 | >│  │  └─ modules 94 | 95 | -------------------------------------------------------------------------------- /mocks/syssetting/getSchoolList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置管理模块 > 学校列表信息 3 | * @url /syssetting/getSchoolList 4 | * Here you can write a detailed description 5 | * of the parameters of the information. 6 | */ 7 | module.exports = { 8 | "code|1": [0, 0, 0, 0, 1], 9 | "message|1": ["success","success","success","success","fail"], 10 | "data": [{ 11 | "id": "1", 12 | "name": "广东石油化工学院", 13 | "manager": "钟小明", 14 | "contact": "147180xxx02", 15 | "commission": "2", 16 | "province": "广东", 17 | "address": "广东省茂名市广东石油化工学院" 18 | },{ 19 | "id": "2", 20 | "name": "广东海洋大学", 21 | "manager": "小林", 22 | "contact": "147180xxx02", 23 | "commission": "2", 24 | "province": "广东", 25 | "address": "广东省湛江水广东海洋大学" 26 | },{ 27 | "id": "3", 28 | "name": "嘉应学院", 29 | "manager": "钟小明", 30 | "contact": "147180xxx02", 31 | "commission": "2", 32 | "province": "广东", 33 | "address": "广东省茂名市广东石油化工学院" 34 | },{ 35 | "id": "4", 36 | "name": "中山大学", 37 | "manager": "钟小明", 38 | "contact": "147180xxx02", 39 | "commission": "2", 40 | "province": "广东", 41 | "address": "广东省茂名市广东石油化工学院" 42 | },{ 43 | "id": "5", 44 | "name": "惠州学院", 45 | "manager": "钟小明", 46 | "contact": "147180xxx02", 47 | "commission": "1", 48 | "province": "广东", 49 | "address": "广东省惠州市惠州学院" 50 | },{ 51 | "id": "6", 52 | "name": "深圳大学", 53 | "manager": "钟小明", 54 | "contact": "147180xxx02", 55 | "commission": "2", 56 | "province": "广东", 57 | "address": "广东省深圳市深圳大学" 58 | },{ 59 | "id": "7", 60 | "name": "广州医学院", 61 | "manager": "钟小明", 62 | "contact": "147180xxx02", 63 | "commission": "3", 64 | "province": "广东", 65 | "address": "广东省广州市广州医学院" 66 | },{ 67 | "id": "8", 68 | "name": "广东医学院", 69 | "manager": "钟小明", 70 | "contact": "147180xxx02", 71 | "commission": "4", 72 | "province": "广东", 73 | "address": "广东省广州市广东医学院" 74 | },{ 75 | "id": "9", 76 | "name": "华南理工", 77 | "manager": "钟小明", 78 | "contact": "147180xxx02", 79 | "commission": "5", 80 | "province": "广东", 81 | "address": "广东省广州市华南理工" 82 | }] 83 | } -------------------------------------------------------------------------------- /code-standards/js.md: -------------------------------------------------------------------------------- 1 | # 1.JS编码规范 2 | 3 | ## 1. 使用驼峰式变量命名、属性 4 | ```javascript 5 | var addTab = 1; 6 | ``` 7 | 8 | ## 2. 常量采用全大写,用下划线“_”作为单词分割 9 | ```javascript 10 | var TABLE_WIDTH = 120; 11 | ``` 12 | 13 | ## 3. 总是使用分号,句末习惯性加上分号 14 | 15 | ## 4. 使用2个空格缩进 16 | 17 | ## 5. 总是使用空格符 18 | ```javascript 19 | // bad 20 | var arr = {a:1,b:2,c:3}; 21 | 22 | // good 23 | var arr = {a: 1, b: 2, c: 3}; 24 | ``` 25 | 26 | ```javascript 27 | // bad 28 | function foo(){ 29 | return true; 30 | } 31 | 32 | // good 33 | function foo() { 34 | return true; 35 | } 36 | ``` 37 | 38 | ```javascript 39 | // bad 40 | if (condition) { 41 | // TODO 42 | } 43 | else { 44 | // TODO 45 | } 46 | 47 | // good 48 | if (condition) { 49 | // TODO 50 | } else { 51 | // TODO 52 | } 53 | ``` 54 | 55 | ## 6. 构造函数命名使用驼峰式且第一个字母大写 56 | ```javascript 57 | function BoxSize () { 58 | // TODO 59 | } 60 | 61 | var boxSize = new BoxSize(); 62 | ``` 63 | 64 | ## 7. 总是使用花括号 65 | ```javascript 66 | // bad 67 | if (true) return; 68 | 69 | // good 70 | if (true) { 71 | return; 72 | } 73 | ``` 74 | 75 | ## 8. 使用空行将逻辑相对独立的两块代码隔开 76 | ```javascript 77 | let that = this, 78 | arr = [1, 3, 5], 79 | box = 'tostring'; 80 | 81 | arr.forEach(function (item, index) { 82 | console.log(index) 83 | }); 84 | 85 | return arr; 86 | ``` 87 | 88 | ## 9. 比较变量强制使用‘===’替代‘==’ 89 | ```javascript 90 | // bad 91 | if (a == b) { 92 | // TODO 93 | } 94 | 95 | // good 96 | if (a === b) { 97 | // TODO 98 | } 99 | ``` 100 | 101 | ## 10. 条件种类超过3种时,使用switch代替if 102 | ```javascript 103 | // bad 104 | if (a === 1) { 105 | // TODO 106 | } else if (a === 2) { 107 | // TODO 108 | } else if (a === 3) { 109 | // TODO 110 | } else if (a === 4) { 111 | // TODO 112 | } 113 | 114 | // good 115 | switch (a) { 116 | case 1: 117 | break; 118 | case 2: 119 | break; 120 | case 3: 121 | break; 122 | case 4: 123 | break; 124 | } 125 | ``` 126 | 127 | ## 11. 使用forEach代替for循环 128 | 129 | ## 12. 函数作用域中的私有函数需要加上_ 130 | ```javascript 131 | function fn () { 132 | 133 | function _cb () {}| 134 | 135 | return { 136 | getIndex: function () { 137 | _cb(); 138 | } 139 | } 140 | } 141 | ``` 142 | -------------------------------------------------------------------------------- /src/views/flow-template/node-menu/node-menu.js: -------------------------------------------------------------------------------- 1 | import draggable from 'vuedraggable'; 2 | 3 | export default { 4 | components: { 5 | draggable 6 | }, 7 | data () { 8 | return { 9 | draggableOptions: { 10 | preventOnFilter: false 11 | }, 12 | // 默认打开的左侧菜单的id 13 | defaultOpeneds: ['1'], 14 | // 菜单列表 15 | menuList: [ 16 | { 17 | id: '1', 18 | type: 'start', 19 | name: '开始', 20 | ico: 'el-icon-video-play' 21 | }, 22 | { 23 | id: '2', 24 | type: 'end', 25 | name: '结束', 26 | ico: 'el-icon-cpu' 27 | }, 28 | { 29 | id: '3', 30 | type: 'task', 31 | name: '任务', 32 | ico: 'el-icon-folder' 33 | }, 34 | { 35 | id: '4', 36 | type: 'task-branch', 37 | name: '分支网关', 38 | ico: 'el-icon-circle-plus-outline' 39 | }, 40 | { 41 | id: '5', 42 | type: 'task-parallel', 43 | name: '并行网关', 44 | ico: 'el-icon-video-pause' 45 | } 46 | ], 47 | mousePosition: { 48 | left: -1, 49 | top: -1 50 | } 51 | }; 52 | }, 53 | methods: { 54 | // 拖拽开始时触发 55 | beginMoveFn (evt) { 56 | console.log('-------------node-menu.js beginMoveFn begin---------'); 57 | console.log(evt); 58 | const ths = this; 59 | var type = evt.item.attributes.type.nodeValue; 60 | 61 | ths.nodeMenu = ths.getMenu(type); 62 | 63 | // 计算出鼠标相对点击元素的位置,e.clientX获取的是鼠标的位置,OffsetLeft是元素相对于外层元素的位置 64 | this.mousePosition = { 65 | left: evt.originalEvent.layerX, // evt.originalEvent.clientX - evt.item.offsetLeft, 66 | top: evt.originalEvent.layerY // evt.originalEvent.clientY - evt.item.offsetTop 67 | }; 68 | console.log('-------------node-menu.js beginMoveFn end---------'); 69 | }, 70 | // 拖拽结束时触发 71 | addNodeFn (evt, e) { 72 | console.log('-------------node-menu.js addNodeFn begin---------'); 73 | console.log(evt, e); 74 | const ths = this; 75 | 76 | ths.$emit('add-node', evt, ths.nodeMenu, ths.mousePosition); 77 | console.log('-------------node-menu.js addNodeFn end---------'); 78 | }, 79 | // 根据类型获取左侧菜单对象 80 | getMenu (type) { 81 | console.log('-------------node-menu.js getMenu begin---------'); 82 | console.log(type); 83 | for (let i = 0; i < this.menuList.length; i++) { 84 | if (this.menuList[i].type === type) { 85 | return this.menuList[i]; 86 | } 87 | } 88 | console.log('-------------node-menu.js getMenu end---------'); 89 | } 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /src/views/login/login.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.2.8 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 | '/api': { 14 | target: 'http://localhost:8082', // 你接口的域名 15 | //secure: false, // 如果是https接口,需要配置这个参数 16 | changeOrigin: true // 如果接口跨域,需要进行这个参数配置 17 | // pathRewrite: { 18 | // '^/api': '' 19 | // } 20 | } 21 | }, 22 | // Various Dev Server settings 23 | host: 'localhost', // can be overwritten by process.env.HOST 24 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 25 | autoOpenBrowser: false, 26 | errorOverlay: true, 27 | notifyOnErrors: true, 28 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 29 | 30 | // Use Eslint Loader? 31 | // If true, your code will be linted during bundling and 32 | // linting errors and warnings will be shown in the console. 33 | useEslint: true, 34 | // If true, eslint errors and warnings will also be shown in the error overlay 35 | // in the browser. 36 | showEslintErrorsInOverlay: false, 37 | 38 | /** 39 | * Source Maps 40 | */ 41 | 42 | // https://webpack.js.org/configuration/devtool/#development 43 | devtool: 'cheap-module-eval-source-map', 44 | 45 | // If you have problems debugging vue-files in devtools, 46 | // set this to false - it *may* help 47 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 48 | cacheBusting: true, 49 | 50 | cssSourceMap: true, 51 | }, 52 | 53 | build: { 54 | // Template for index.html 55 | index: path.resolve(__dirname, '../dist/index.html'), 56 | 57 | // Paths 58 | assetsRoot: path.resolve(__dirname, '../dist'), 59 | assetsSubDirectory: 'static', 60 | assetsPublicPath: '/', 61 | 62 | /** 63 | * Source Maps 64 | */ 65 | 66 | productionSourceMap: true, 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 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | 'array-bracket-spacing': ["error", "never"], // 禁止在数组括号内出现空格 ['a': 12] 21 | 'arrow-spacing': ["error", {"before": true, "after": true}], // 箭头函数使用一致空格 a => b 22 | 'block-spacing': ["error", "never"], // 禁止在单行代码块中使用空格 function foo() {return true;} 23 | 'camelcase': 'off', // ["error", {"properties": "always"}], // 使用驼峰式命名,常量使用下划线 24 | 'o-throw-literal': 'off', 25 | 'new-cap': 'off', 26 | 'comma-dangle': ["error", "never"], // 禁用拖尾逗号 ['a': 1, 'b': 3],3后面不能有逗号 27 | 'comma-spacing': ["error", { "before": false, "after": true }], // 强制在逗号周围使用空格 a = 1, b = 3; 前面不要有空格,后面有空格 28 | 'computed-property-spacing': ["error", "never"], // 禁止在计算属性中使用空格 a['key'] 29 | 'curly': "error", // 要求遵循大括号约定 if () {} 必须要有{} 30 | 'eqeqeq': "error", // 要求使用 === 和 !== 31 | 'indent': ["error", 2, {"SwitchCase": 1}], 32 | 'key-spacing': ["error", {"beforeColon": false, "afterColon": true}],// 键值与冒号之间的空格一致性 33 | 'keyword-spacing': ["error", {"before": true, "after": true}], // 强制关键字周围空格的一致性 } else { 'else'是关键字 34 | 'lines-around-comment': ["error", {"beforeBlockComment": true, "allowBlockStart": true, "allowObjectStart": true, "allowArrayStart": true}], // 强制注释周围有空行 35 | 'newline-after-var': ["error", "always"], // 要求变量声明语句后有一行空行 36 | 'no-console': "off", // 允许使用console 37 | 'no-empty': "warn", // 禁止空块语句 38 | 'no-extra-semi': "warn", // 警告不必要的分号 39 | 'no-extra-boolean-cast': 'warn', // 警告不必要的布尔类型转换 40 | 'no-redeclare': "warn", // 警告重新声明变量 41 | 'no-spaced-func': "error", // 函数调用时函数名与()之间不能有空格 42 | 'no-undef': "warn", // 警告没定义 43 | 'no-unused-vars': "off", // 关闭未使用过的变量 44 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 45 | 'object-curly-spacing': ["error", "never"], // 强制在花括号中使用一致的空格 {'a': 1, 'b': 2} 46 | 'semi': ["error", "always"], // 要求句末添加分号 47 | 'semi-spacing': ["error", {"before": false, "after": true}], // 强制分号周围的空格 48 | 'space-before-blocks': ["error", "always"], // 要求语句块之前的空格 49 | 'space-before-function-paren': ["error", "always"], // 要求函数圆括号之前有一个空格 50 | 'space-in-parens': ["error", "never"], // 强制圆括号内没有空格 51 | 'space-infix-ops': ["error", {"int32Hint": false}], // 要求中缀操作符周围有空格 52 | 'space-unary-ops': "error" // 要求在一元操作符之前或之后存在空格 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /data/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "menuList": [{ 3 | "menuId": "id-1", 4 | "menuNameCn":"基础管理", 5 | "menuNameEn":"Base Manage", 6 | "iconCls": "el-icon-location", 7 | "children":[], 8 | "comp":"baseManage", 9 | "url":"/baseManage" 10 | },{ 11 | "menuId": "id-2", 12 | "menuNameCn":"学校管理", 13 | "menuNameEn":"Scholl", 14 | "iconCls": "el-icon-location", 15 | "children":[{ 16 | "menuId": "id-3", 17 | "menuNameCn":"学校信息", 18 | "menuNameEn":"scholl Info", 19 | "children":[], 20 | "comp":"schollInfo", 21 | "url":"/schollInfo" 22 | },{ 23 | "menuId": "id-4", 24 | "menuNameCn":"提现管理", 25 | "menuNameEn":"withdraw", 26 | "children":[], 27 | "comp":"withdraw", 28 | "url":"/withdraw" 29 | }], 30 | "comp":"", 31 | "url":"" 32 | },{ 33 | "menuId": "id-5", 34 | "menuNameCn":"跑腿人", 35 | "menuNameEn":"Runners", 36 | "iconCls": "el-icon-location", 37 | "children":[], 38 | "comp":"runners", 39 | "url":"/runners" 40 | } 41 | ], 42 | "schoolList": [{ 43 | "id": "1", 44 | "name": "广东石油化工学院", 45 | "manager": "钟小明", 46 | "contact": "147180xxx02", 47 | "commission": "2", 48 | "province": "广东", 49 | "address": "广东省茂名市广东石油化工学院" 50 | },{ 51 | "id": "2", 52 | "name": "广东海洋大学", 53 | "manager": "小林", 54 | "contact": "147180xxx02", 55 | "commission": "2", 56 | "province": "广东", 57 | "address": "广东省湛江水广东海洋大学" 58 | },{ 59 | "id": "3", 60 | "name": "嘉应学院", 61 | "manager": "钟小明", 62 | "contact": "147180xxx02", 63 | "commission": "2", 64 | "province": "广东", 65 | "address": "广东省茂名市广东石油化工学院" 66 | },{ 67 | "id": "4", 68 | "name": "中山大学", 69 | "manager": "钟小明", 70 | "contact": "147180xxx02", 71 | "commission": "2", 72 | "province": "广东", 73 | "address": "广东省茂名市广东石油化工学院" 74 | },{ 75 | "id": "5", 76 | "name": "惠州学院", 77 | "manager": "钟小明", 78 | "contact": "147180xxx02", 79 | "commission": "1", 80 | "province": "广东", 81 | "address": "广东省惠州市惠州学院" 82 | },{ 83 | "id": "6", 84 | "name": "深圳大学", 85 | "manager": "钟小明", 86 | "contact": "147180xxx02", 87 | "commission": "2", 88 | "province": "广东", 89 | "address": "广东省深圳市深圳大学" 90 | },{ 91 | "id": "7", 92 | "name": "广州医学院", 93 | "manager": "钟小明", 94 | "contact": "147180xxx02", 95 | "commission": "3", 96 | "province": "广东", 97 | "address": "广东省广州市广州医学院" 98 | },{ 99 | "id": "8", 100 | "name": "广东医学院", 101 | "manager": "钟小明", 102 | "contact": "147180xxx02", 103 | "commission": "4", 104 | "province": "广东", 105 | "address": "广东省广州市广东医学院" 106 | },{ 107 | "id": "9", 108 | "name": "华南理工", 109 | "manager": "钟小明", 110 | "contact": "147180xxx02", 111 | "commission": "5", 112 | "province": "广东", 113 | "address": "广东省广州市华南理工" 114 | }] 115 | } 116 | -------------------------------------------------------------------------------- /code-standards/css.md: -------------------------------------------------------------------------------- 1 | # 1. css编码规范 2 | 3 | ## 1. css选择器命名 4 | * 全英文小写(不要用中文拼音命名) 5 | * 使用中划线分割多单词的选择器名(.ui-element) 6 | * 禁止使用下划线 7 | 8 | ## 2. 使用tab(4个空格) 9 | 10 | ## 3. 空格 11 | * '{'前留一个空格 12 | * ':'后留一个空格 13 | * 句末不要留多余空格 14 | 15 | ## 4. 每个属性声明未尾都要添加分号 16 | ```css 17 | .element { 18 | width: 100px; 19 | height: 100px; 20 | } 21 | ``` 22 | 23 | ## 5. 换行 24 | * '{' 后和 '}'后换行 25 | * 多个规则的选择器用','分隔并换行 26 | 27 | ```css 28 | // bad 29 | .item, .element { 30 | width: 100px; 31 | height: 100px; 32 | } 33 | 34 | // good 35 | .item, 36 | .element { 37 | width: 100px; 38 | height: 100px; 39 | } 40 | ``` 41 | 42 | ## 6. 属性声明顺序 43 | 1. 定位:其中有的属性为:position z-index left right top bottom clip 44 | 2. 盒子模型:其中属性为:width height min-height max-height min-width max-width() 45 | 3. 文字:其中属性有:color font-size letter-spacing, color- text-align等 46 | 4. 背景:其中属性有:background-image border等 47 | 5. 其他:一般有:animation, transition等 48 | 49 | ```css 50 | .element { 51 | position: relative; 52 | left: 10px; 53 | width: 20px; 54 | height: 100px; 55 | margin: 10px 20px; 56 | background: #ccc; 57 | } 58 | ``` 59 | 60 | ## 7. 属性缩写 61 | * 属性合并,包括有margin、padding、border、background等 62 | 63 | ```css 64 | // not good 65 | .element { 66 | padding-top: 5px; 67 | padding-right: 5px; 68 | padding-bottom: 5px; 69 | margin-top: 10px; 70 | margin-left: 20px; 71 | } 72 | 73 | // good 74 | .element { 75 | padding: 5px 0 5px 5px; 76 | margin: 10px 0 0 20px; 77 | } 78 | ``` 79 | 80 | * 0符号缩写 81 | 82 | ```css 83 | // not good 84 | .element { 85 | width: 0px; 86 | opacity: 0.5; 87 | } 88 | 89 | // good 90 | .element { 91 | width: 0; 92 | opacity: .5; 93 | } 94 | ``` 95 | 96 | ## 8. 性能优化 97 | * 禁止使用行内样式,也就是在dom节点中写入style=''. 规避这种写法的用意在于行内样式不易于维护、不可复用、扩大html容量。 `除非特殊情况(-webkit-box-origin stylus无法解析只能写在行内样式中等)` 98 | * 禁止使用标签选择器. 规避这种写法的用意在于css解析是从右到左的,如果使用标签选择器,则css渲染会花费很多性能。另外会影响所有该标签选择器的元素样式,导致特殊情况需要样式重置,不利于维护。 99 | 100 | ```css 101 | // bad 标签选择器 102 | .box span {} 103 | .table ul li {} 104 | ``` 105 | 106 | 107 | 108 | ## 9. 模块化命名 109 | > 样式选择器命名以模块为单位,例如以下是一个box模块 110 | 111 | ```html 112 |
113 |

114 | title 115 |

116 |
117 | 118 |

content

119 |
120 |
121 | ``` 122 | 123 | > css模块化命名 124 | 125 | ```css 126 | .box {} 127 | .box .title {} 128 | .box .content {} 129 | .box .icon {} 130 | .box .text {} 131 | ``` 132 | 133 | `这样命名的好处是,知道该模块的整体样式,益于维护模块迁移或者删除,并且每个样式块都有前缀,不会被覆盖。` 134 | 135 | ## 10. 模块样式跟其他模块之间要空一行,便于维护 136 | ```css 137 | 138 | .box {} 139 | .box .title {} 140 | .box .content {} 141 | .box .icon {} 142 | .box .text {} 143 | 144 | 145 | .banner {} 146 | .banner .title {} 147 | ``` 148 | -------------------------------------------------------------------------------- /src/utils/nodeFilter.js: -------------------------------------------------------------------------------- 1 | const nodeFilter = { 2 | isNodeExisted (type, data) { 3 | if (type && data) { 4 | return data.some(item => { 5 | return item.type === type; 6 | }); 7 | } 8 | }, 9 | getNodeTypeById (id, data) { 10 | let nodeIndex; 11 | 12 | data.some((item, index) => { 13 | if (item.id === id) { 14 | nodeIndex = index; 15 | return true; 16 | } 17 | }); 18 | return data[nodeIndex].type; 19 | }, 20 | getNewValidId (id, prefix) { 21 | prefix = prefix || '^node|^line'; 22 | const reg = new RegExp(prefix); 23 | 24 | if (!reg.test(id)) { 25 | return `${prefix}${id}`; 26 | } 27 | return id; 28 | }, 29 | convertInvalidIdData (key, primaryData) { 30 | const keysOfIdRelated = ['id', 'incoming', 'outgoing', 'source', 'target', 'conditions']; 31 | let newData = {}; 32 | 33 | switch (key) { 34 | case 'activities': 35 | case 'flows': 36 | case 'gateways': 37 | for (const key in primaryData) { 38 | const newKey = this.getNewValidId(key); 39 | 40 | newData[newKey] = primaryData[key]; 41 | keysOfIdRelated.forEach(item => { 42 | const val = newData[newKey][item]; 43 | 44 | if (val !== undefined && val !== '') { 45 | if (typeof val === 'string') { 46 | newData[newKey][item] = this.getNewValidId(val); 47 | } else if (Array.isArray(val)) { 48 | newData[newKey][item] = val.map(v => { 49 | return this.getNewValidId(v); 50 | }); 51 | } 52 | if (item === 'conditions') { 53 | const newVal = {}; 54 | 55 | for (const conditionId in val) { 56 | const newConditionId = this.getNewValidId(conditionId); 57 | 58 | newVal[newConditionId] = val[conditionId]; 59 | newVal[newConditionId].tag = newVal[newConditionId].tag.split('_').map((id, index) => { 60 | return index > 0 ? this.getNewValidId(id) : id; 61 | }).join('_'); 62 | } 63 | newData[newKey][item] = newVal; 64 | } 65 | } 66 | }); 67 | } 68 | return newData; 69 | case 'end_event': 70 | case 'start_event': 71 | newData = Object.assign({}, primaryData); 72 | keysOfIdRelated.forEach(item => { 73 | const val = newData[item]; 74 | 75 | if (val !== undefined && val !== '') { 76 | newData[item] = this.getNewValidId(val); 77 | } 78 | }); 79 | return newData; 80 | case 'line': 81 | newData = [...primaryData]; 82 | newData.forEach((item, index) => { 83 | item.id = this.getNewValidId(item.id); 84 | item.source.id = this.getNewValidId(item.source.id); 85 | item.target.id = this.getNewValidId(item.target.id); 86 | }); 87 | return newData; 88 | case 'location': 89 | newData = [...primaryData]; 90 | newData.forEach((item) => { 91 | item.id = this.getNewValidId(item.id); 92 | }); 93 | return newData; 94 | default: 95 | return primaryData; 96 | } 97 | } 98 | }; 99 | 100 | export default nodeFilter; 101 | -------------------------------------------------------------------------------- /src/component/base/BaseSearch.vue: -------------------------------------------------------------------------------- 1 | /** 2 | * Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community 3 | * Edition) available. 4 | * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved. 5 | * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * http://opensource.org/licenses/MIT 8 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 9 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 10 | * specific language governing permissions and limitations under the License. 11 | */ 12 | 25 | 26 | 50 | 51 | 104 | -------------------------------------------------------------------------------- /src/utils/atomFilter.js: -------------------------------------------------------------------------------- 1 | import tools from './tools.js'; 2 | 3 | const atomFilter = { 4 | formFilter (tag_code, config) { 5 | let formConfig; 6 | 7 | if (tag_code && config) { 8 | config.some(item => { 9 | if (item.tag_code === tag_code) { 10 | formConfig = item; 11 | return true; 12 | } 13 | 14 | /** 15 | * combine类型的tag勾选为为统一勾选,子tag没有勾选选项,暂时注释 16 | */ 17 | // if (item.type === 'combine') { 18 | // debugger 19 | // formConfig = this.formFilter(tag_code, item.attrs.children) 20 | // return true 21 | // } 22 | }); 23 | } 24 | return formConfig; 25 | }, 26 | getFormItemDefaultValue (config) { 27 | const value = {}; 28 | 29 | config.forEach(item => { 30 | if (item.type === 'combine') { 31 | value[item.tag_code] = this.getFormItemDefaultValue(item.attrs.children); 32 | } else { 33 | let val; 34 | 35 | if ('value' in item.attrs) { 36 | val = tools.deepClone(item.attrs.value); 37 | } else if ('default' in item.attrs) { 38 | val = tools.deepClone(item.attrs.default); 39 | } else { 40 | switch (item.type) { 41 | case 'input': 42 | case 'textarea': 43 | case 'radio': 44 | case 'text': 45 | case 'datetime': 46 | case 'password': 47 | val = ''; 48 | break; 49 | case 'checkbox': 50 | case 'datatable': 51 | case 'tree': 52 | case 'upload': 53 | val = []; 54 | break; 55 | case 'select': 56 | val = item.attrs.multiple ? [] : ''; 57 | break; 58 | case 'int': 59 | val = 0; 60 | break; 61 | case 'ip_selector': 62 | val = { 63 | selectors: [], 64 | ip: [], 65 | topo: [], 66 | filters: [], 67 | excludes: [] 68 | }; 69 | break; 70 | default: 71 | val = ''; 72 | } 73 | } 74 | 75 | value[item.tag_code] = val; 76 | } 77 | }); 78 | 79 | return value; 80 | }, 81 | 82 | /** 83 | * 通过变量配置项获取需要加载标准插件的相关参数 84 | * @param {Object} variable 变量配置项 85 | * 86 | * @return {String} atomType 标准插件code 87 | * @return {String} atom 标准插件注册名称 88 | * @return {String} tagCode 标准插件中的某一项表单tagCode 89 | * @return {String} classify 标准插件分类:变量、组件 90 | */ 91 | getVariableArgs (variable) { 92 | const {source_tag, custom_type} = variable; 93 | let atomType = ''; // 需要加载标准插件文件的code 94 | let atom = ''; // 标准插件名称,对应绑定在$.atoms上的key 95 | let tagCode = ''; 96 | let classify = ''; 97 | 98 | if (custom_type) { 99 | atomType = custom_type; 100 | atom = source_tag ? source_tag.split('.')[0] : custom_type; // 兼容旧数据自定义变量source_tag为空 101 | tagCode = source_tag ? source_tag.split('.')[1] : custom_type; 102 | classify = 'variable'; 103 | } else { 104 | [atomType, tagCode] = source_tag.split('.'); 105 | atom = atomType; 106 | classify = 'component'; 107 | } 108 | return {atomType, atom, tagCode, classify}; 109 | } 110 | }; 111 | 112 | export default atomFilter; 113 | -------------------------------------------------------------------------------- /data/usercenter.json: -------------------------------------------------------------------------------- 1 | { 2 | "menuList": [{ 3 | "menuId": "id-1", 4 | "menuNameCn":"VUE基础", 5 | "menuNameEn":"VUE基础", 6 | "iconCls": "el-icon-location", 7 | "children":[{ 8 | "menuId": "id-2", 9 | "menuNameCn":"内置指令", 10 | "menuNameEn":"内置指令", 11 | "iconCls": "el-icon-location", 12 | "children":[], 13 | "comp":"directiveInlay", 14 | "url":"/directiveInlay" 15 | },{ 16 | "menuId": "id-3", 17 | "menuNameCn":"自定义指令", 18 | "menuNameEn":"自定义指令", 19 | "iconCls": "el-icon-location", 20 | "children":[], 21 | "comp":"directiveDefine", 22 | "url":"/directiveDefine" 23 | },{ 24 | "menuId": "id-5", 25 | "menuNameCn":"过滤器", 26 | "menuNameEn":"过滤器", 27 | "iconCls": "el-icon-location", 28 | "children":[], 29 | "comp":"defineFilter", 30 | "url":"/defineFilter" 31 | },{ 32 | "menuId": "id-6", 33 | "menuNameCn":"组件", 34 | "menuNameEn":"组件", 35 | "iconCls": "el-icon-location", 36 | "children":[], 37 | "comp":"defineComponent", 38 | "url":"/defineComponent" 39 | }], 40 | "comp":"", 41 | "url":"" 42 | },{ 43 | "menuId": "id-7", 44 | "menuNameCn":"菜单二", 45 | "menuNameEn":"Scholl", 46 | "iconCls": "el-icon-location", 47 | "children":[], 48 | "comp":"", 49 | "url":"" 50 | },{ 51 | "menuId": "id-8", 52 | "menuNameCn":"菜单三", 53 | "menuNameEn":"Runners", 54 | "iconCls": "el-icon-location", 55 | "children":[], 56 | "comp":"", 57 | "url":"/" 58 | } 59 | ], 60 | "schoolList": [{ 61 | "id": "1", 62 | "name": "广东石油化工学院", 63 | "manager": "钟小明", 64 | "contact": "147180xxx02", 65 | "commission": "2", 66 | "province": "广东", 67 | "address": "广东省茂名市广东石油化工学院" 68 | },{ 69 | "id": "2", 70 | "name": "广东海洋大学", 71 | "manager": "小林", 72 | "contact": "147180xxx02", 73 | "commission": "2", 74 | "province": "广东", 75 | "address": "广东省湛江水广东海洋大学" 76 | },{ 77 | "id": "3", 78 | "name": "嘉应学院", 79 | "manager": "钟小明", 80 | "contact": "147180xxx02", 81 | "commission": "2", 82 | "province": "广东", 83 | "address": "广东省茂名市广东石油化工学院" 84 | },{ 85 | "id": "4", 86 | "name": "中山大学", 87 | "manager": "钟小明", 88 | "contact": "147180xxx02", 89 | "commission": "2", 90 | "province": "广东", 91 | "address": "广东省茂名市广东石油化工学院" 92 | },{ 93 | "id": "5", 94 | "name": "惠州学院", 95 | "manager": "钟小明", 96 | "contact": "147180xxx02", 97 | "commission": "1", 98 | "province": "广东", 99 | "address": "广东省惠州市惠州学院" 100 | },{ 101 | "id": "6", 102 | "name": "深圳大学", 103 | "manager": "钟小明", 104 | "contact": "147180xxx02", 105 | "commission": "2", 106 | "province": "广东", 107 | "address": "广东省深圳市深圳大学" 108 | },{ 109 | "id": "7", 110 | "name": "广州医学院", 111 | "manager": "钟小明", 112 | "contact": "147180xxx02", 113 | "commission": "3", 114 | "province": "广东", 115 | "address": "广东省广州市广州医学院" 116 | },{ 117 | "id": "8", 118 | "name": "广东医学院", 119 | "manager": "钟小明", 120 | "contact": "147180xxx02", 121 | "commission": "4", 122 | "province": "广东", 123 | "address": "广东省广州市广东医学院" 124 | },{ 125 | "id": "9", 126 | "name": "华南理工", 127 | "manager": "钟小明", 128 | "contact": "147180xxx02", 129 | "commission": "5", 130 | "province": "广东", 131 | "address": "广东省广州市华南理工" 132 | }] 133 | } 134 | -------------------------------------------------------------------------------- /src/views/jsplumb-learn/node.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 80 | 81 | 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-web-demo", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "toutouping ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "mock": "./node_modules/.bin/mocha build/mock-server.js", 11 | "unit": "jest --config test/unit/jest.conf.js --coverage", 12 | "test": "npm run unit", 13 | "lint": "eslint --ext .js,.vue src test/unit/specs", 14 | "build": "node build/build.js" 15 | }, 16 | "dependencies": { 17 | "art-template": "^4.13.2", 18 | "axios": "^0.17.1", 19 | "element-ui": "^2.0.11", 20 | "highlight.js": "^9.12.0", 21 | "install": "^0.10.2", 22 | "jquery": "^3.3.1", 23 | "jsplumb": "^2.7.3", 24 | "vue": "^2.5.2", 25 | "vue-i18n": "^7.4.0", 26 | "vue-markdown": "^2.2.4", 27 | "vue-progressbar": "^0.7.3", 28 | "vue-router": "^3.0.1", 29 | "vuedraggable": "^2.23.0", 30 | "vuex": "^3.0.1" 31 | }, 32 | "devDependencies": { 33 | "art-template-loader": "^1.4.3", 34 | "autoprefixer": "^7.1.2", 35 | "babel-core": "^6.22.1", 36 | "babel-eslint": "^7.1.1", 37 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 38 | "babel-jest": "^21.0.2", 39 | "babel-loader": "^7.1.1", 40 | "babel-plugin-dynamic-import-node": "^1.2.0", 41 | "babel-plugin-syntax-jsx": "^6.18.0", 42 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 43 | "babel-plugin-transform-runtime": "^6.22.0", 44 | "babel-plugin-transform-vue-jsx": "^3.5.0", 45 | "babel-preset-env": "^1.3.2", 46 | "babel-preset-stage-2": "^6.22.0", 47 | "chalk": "^2.0.1", 48 | "copy-webpack-plugin": "^4.0.1", 49 | "css-loader": "^0.28.0", 50 | "dev-ip": "^1.0.1", 51 | "eslint": "^3.19.0", 52 | "eslint-config-standard": "^10.2.1", 53 | "eslint-friendly-formatter": "^3.0.0", 54 | "eslint-loader": "^1.7.1", 55 | "eslint-plugin-html": "^3.0.0", 56 | "eslint-plugin-import": "^2.7.0", 57 | "eslint-plugin-node": "^5.2.0", 58 | "eslint-plugin-promise": "^3.4.0", 59 | "eslint-plugin-standard": "^3.0.1", 60 | "express-mockjs": "^0.4.9", 61 | "extract-text-webpack-plugin": "^3.0.0", 62 | "file-loader": "^1.1.4", 63 | "friendly-errors-webpack-plugin": "^1.6.1", 64 | "html-webpack-plugin": "^2.30.1", 65 | "jest": "^21.2.0", 66 | "jest-serializer-vue": "^0.3.0", 67 | "mocha": "^5.0.3", 68 | "node-notifier": "^5.1.2", 69 | "optimize-css-assets-webpack-plugin": "^3.2.0", 70 | "ora": "^1.2.0", 71 | "portfinder": "^1.0.13", 72 | "postcss-import": "^11.0.0", 73 | "postcss-loader": "^2.0.8", 74 | "postcss-url": "^7.2.1", 75 | "rimraf": "^2.6.0", 76 | "semver": "^5.3.0", 77 | "shelljs": "^0.7.6", 78 | "stylus": "^0.54.5", 79 | "stylus-loader": "^3.0.1", 80 | "supertest": "^3.0.0", 81 | "uglifyjs-webpack-plugin": "^1.1.1", 82 | "url-loader": "^0.5.8", 83 | "vue-jest": "^1.0.2", 84 | "vue-loader": "^13.3.0", 85 | "vue-style-loader": "^3.0.1", 86 | "vue-template-compiler": "^2.5.2", 87 | "webpack": "^3.6.0", 88 | "webpack-bundle-analyzer": "^2.9.0", 89 | "webpack-dev-server": "^2.9.1", 90 | "webpack-merge": "^4.1.0" 91 | }, 92 | "engines": { 93 | "node": ">= 6.0.0", 94 | "npm": ">= 3.0.0" 95 | }, 96 | "browserslist": [ 97 | "> 1%", 98 | "last 2 versions", 99 | "not ie <= 8" 100 | ] 101 | } 102 | -------------------------------------------------------------------------------- /src/utils/tools.js: -------------------------------------------------------------------------------- 1 | import cloneDeepWith from 'lodash/cloneDeepWith'; 2 | import {checkDataType} from './checkDataType.js'; 3 | 4 | const tools = { 5 | /** 6 | * 防抖函数 7 | * @param {Function} fn 回调函数 8 | * @param {Number} delay 延迟时间 9 | */ 10 | debounce (fn, delay) { 11 | let timer; 12 | 13 | return function () { 14 | const context = this; 15 | const args = arguments; 16 | 17 | clearTimeout(timer); 18 | 19 | timer = setTimeout(function () { 20 | fn.apply(context, args); 21 | }, delay); 22 | }; 23 | }, 24 | 25 | /** 26 | * 节流函数 27 | * @param {Function} fn 回调函数 28 | * @param {*} threshhold 时间间隔 29 | */ 30 | throttle (fn, threshhold) { 31 | let last, timer; 32 | 33 | threshhold || (threshhold = 250); 34 | 35 | return function () { 36 | const context = this; 37 | const args = arguments; 38 | const now = +new Date(); 39 | 40 | if (last && now < last + threshhold) { 41 | clearTimeout(timer); 42 | 43 | timer = setTimeout(function () { 44 | last = now; 45 | fn.apply(context, args); 46 | }, threshhold); 47 | } else { 48 | last = now; 49 | fn.apply(context, args); 50 | } 51 | }; 52 | }, 53 | 54 | /** 55 | * 深拷贝函数 56 | * @param {Object}} obj copy 对象 57 | */ 58 | deepClone (obj) { 59 | return cloneDeepWith(obj); 60 | }, 61 | 62 | /** 63 | * 对象比较 64 | * @param {Object} a 对象 a 65 | * @param {Object} b 对象 b 66 | */ 67 | isObjEqual (a, b) { 68 | let p, t; 69 | 70 | for (p in a) { 71 | if (typeof b[p] === 'undefined') { 72 | return false; 73 | } 74 | if (b[p] && !a[p]) { 75 | return false; 76 | } 77 | t = typeof a[p]; 78 | if (t === 'object' && !this.isObjEqual(a[p], b[p])) { 79 | return false; 80 | } 81 | if (t === 'function' && (typeof b[p] === 'undefined' || a[p].toString() !== b[p].toString())) { 82 | return false; 83 | } 84 | if (a[p] !== b[p]) { 85 | return false; 86 | } 87 | } 88 | for (p in b) { 89 | if (typeof a[p] === 'undefined') { 90 | return false; 91 | } 92 | } 93 | return true; 94 | }, 95 | 96 | /** 97 | * 判断传入值是否为空 98 | * @param {Any} value 值 99 | */ 100 | isEmpty (value) { 101 | const dataType = checkDataType(value); 102 | let isEmpty; 103 | 104 | switch (dataType) { 105 | case 'String': 106 | isEmpty = value === ''; 107 | break; 108 | case 'Array': 109 | isEmpty = !value.length; 110 | break; 111 | case 'Object': 112 | isEmpty = !Object.keys(value).length; 113 | break; 114 | default: 115 | isEmpty = false; 116 | } 117 | return isEmpty; 118 | }, 119 | 120 | /** 121 | * 时间转化函数,毫秒转换为特定字符串格式 122 | * @param {String|Number} time 时间 123 | */ 124 | timeTransform (time) { 125 | const val = Number(time); 126 | 127 | if (val > 0) { 128 | if (val < 60) { 129 | return val + window.gettext(' 秒'); 130 | } else if (val < 3600) { 131 | return parseFloat(val / 60).toFixed(1) + window.gettext(' 分'); 132 | } else if (val < 86400) { 133 | return parseFloat(val / 3600).toFixed(1) + window.gettext(' 小时'); 134 | } else { 135 | return parseFloat(val / 86400).toFixed(1) + window.gettext(' 天'); 136 | } 137 | } else { 138 | return '--'; 139 | } 140 | } 141 | }; 142 | 143 | export default tools; 144 | -------------------------------------------------------------------------------- /src/views/user-center/user-center.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/views/sys-setting/sys-setting.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/scss/common.styl: -------------------------------------------------------------------------------- 1 | .common-section-title { 2 | position: relative; 3 | margin: 0; 4 | padding: 0 0 0 16px; 5 | line-height: 1; 6 | font-size: 16px; 7 | font-weight: normal; 8 | color: #666666; 9 | &:before { 10 | content: ''; 11 | display: inline-block; 12 | position: absolute; 13 | top: -2px; 14 | left: 0; 15 | width: 4px; 16 | height: 20px; 17 | background: $blueDefault; 18 | } 19 | } 20 | 21 | .common-form-item { 22 | margin-bottom: 20px; 23 | @include clearfix; 24 | &:last-child { 25 | margin-bottom: 0; 26 | } 27 | &>label { 28 | position: relative; 29 | float: left; 30 | margin-top: 6px; 31 | width: 100px; 32 | font-size: 14px; 33 | font-weight: bold; 34 | color: #666666; 35 | text-align: right; 36 | &.required:after { 37 | content: '*'; 38 | position: absolute; 39 | top: 0px; 40 | right: -10px; 41 | color: #ff2602; 42 | font-family: "SimSun"; 43 | } 44 | } 45 | .common-form-content { 46 | margin-left: 120px; 47 | min-height: 36px; 48 | } 49 | } 50 | 51 | .step-section-title { 52 | text-align: left; 53 | margin: 0; 54 | line-height: 67px; 55 | color: #313238; 56 | span { 57 | padding-left: 30px; 58 | font-size: 14px; 59 | font-family: PingFangSC-Semibold; 60 | font-weight: 600; 61 | } 62 | &:before { 63 | content: ''; 64 | display: inline-block; 65 | position: relative; 66 | top: 4px; 67 | left: 20px; 68 | width: 2px; 69 | height: 20px; 70 | background: #A3C5FD; 71 | } 72 | } 73 | 74 | .common-dialog { 75 | .bk-dialog-header { 76 | padding: 20px 30px; 77 | border-bottom: 1px solid #ebebeb; 78 | .bk-dialog-title { 79 | text-align: left; 80 | line-height: 1; 81 | } 82 | } 83 | .bk-dialog-tool { 84 | min-height: 0px; 85 | .bk-dialog-close { 86 | margin-top: 16px; 87 | margin-right: 20px; 88 | } 89 | } 90 | } 91 | 92 | .common-error-tip { 93 | display: inline-block; 94 | font-size: 14px; 95 | line-height: 1; 96 | color: #ff5757; 97 | } 98 | 99 | // 弹窗按钮 100 | .bk-dialog-wrapper .bk-dialog-footer .bk-dialog-outer button { 101 | width: 90px; 102 | height: 32px; 103 | line-height: 30px; 104 | } 105 | .common-warning-tip { 106 | display: inline-block; 107 | font-size: 14px; 108 | color: #ffb400; 109 | } 110 | 111 | // tooltip组件的箭头会挡住操作区域 112 | .v-tooltips-container.offset-left-tooltip { 113 | margin-left: -7px; 114 | } 115 | // bk-selector组件选项过长会换行的问题兼容 116 | .bk-selector label { 117 | overflow: hidden; 118 | white-space: nowrap; 119 | text-overflow: ellipsis; 120 | } 121 | // 表单下拉框 122 | .bk-selector-wrapper { 123 | .bk-selector-icon { 124 | top: 10px; 125 | } 126 | .bk-selector-icon.clear-icon { 127 | top: 7px; 128 | } 129 | .bk-selector-input { 130 | height: 32px; 131 | line-height: 32px; 132 | } 133 | } 134 | // 列表分页 135 | .panagation { 136 | .bk-page .page-item { 137 | float: none; 138 | } 139 | .bk-page .page-item.cur-page { 140 | background: #3c96ff; 141 | } 142 | .bk-page .page-item .page-button { 143 | padding: 0 7px; 144 | } 145 | .bk-page .page-item.disabled .page-button { 146 | padding: 0 5px; 147 | color: #737987; 148 | &:hover { 149 | color: #737987; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import store from 'src/store/index.js'; 4 | import NotFound from 'views/404.vue'; 5 | import login from 'views/login/login.vue'; 6 | import sysSetting from 'views/sys-setting/sys-setting.vue'; 7 | const companyHelp = resolve => require(['views/company-help/company-help.vue'], resolve); 8 | const userCenter = resolve => require(['views/user-center/user-center.vue'], resolve); 9 | // 异步加载:const meetVipRate = resolve => require(['views/meet-vip-rate/meet-vip-rate.vue'], resolve); 10 | const flowTemplate = resolve => require(['views/flow-template/flow-template.vue'], resolve); 11 | const jsplumbLearn = resolve => require(['views/jsplumb-learn/jsplumb-learn.vue'], resolve); 12 | 13 | Vue.use(Router); 14 | 15 | const router = new Router({ 16 | routes: [ 17 | { 18 | path: '/login', /* 登录界面 */ 19 | name: 'login', 20 | component: login/* ,hidden: true, // 自定义属性,在组件中可以通过 this.$route.hidden 获取值 */ 21 | }, 22 | { 23 | path: '/jsplumbLearn', /* 登录界面 */ 24 | name: 'jsplumbLearn', 25 | component: jsplumbLearn, /* this.$route.matched.filter(item => item.name) */ 26 | meta: { 27 | keepAlive: false, /* 用于在 中使用,判断是否需要进行缓存 */ 28 | auth: true, /* 自定义属性,用于判断是否进行校验,在router.beforeEach中使用 */ 29 | title: '学习jsplumb' /* 可以通过$route.meta.title 后取当前的描述信息、菜单信息 */ 30 | } 31 | }, 32 | { 33 | path: '/sysSetting', /* 首页 */ 34 | component: sysSetting, 35 | name: 'sysSetting', /* this.$route.matched.filter(item => item.name) */ 36 | meta: { 37 | keepAlive: false, /* 用于在 中使用,判断是否需要进行缓存 */ 38 | auth: true, /* 自定义属性,用于判断是否进行校验,在router.beforeEach中使用 */ 39 | title: '系统设置' /* 可以通过$route.meta.title 后取当前的描述信息、菜单信息 */ 40 | } 41 | }, 42 | { 43 | path: '/companyHelp', 44 | component: companyHelp, 45 | name: 'companyHelp', 46 | meta: { 47 | keepAlive: false, 48 | auth: true 49 | } 50 | }, 51 | { 52 | path: '/userCenter', /* 首页 */ 53 | component: userCenter, 54 | name: 'userCenter', 55 | meta: { 56 | keepAlive: false, 57 | auth: true 58 | } 59 | }, 60 | { 61 | path: '/flowTemplate', /* 404页面 */ 62 | component: flowTemplate, 63 | name: 'flowTemplate' 64 | }, 65 | { 66 | path: '/web404', /* 404页面 */ 67 | component: NotFound, 68 | name: 'web404' 69 | }, 70 | { 71 | path: '*', /* 默认跳转到登录界面 */ 72 | redirect: {path: '/flowTemplate'} 73 | } 74 | ] 75 | // ,scrollBehavior(to, from, savedPosition) {// return 期望滚动到哪个的位置,第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。 76 | // if (savedPosition) { 77 | // return savedPosition; 78 | // } else { 79 | // if (from.meta.keepAlive) { 80 | // from.meta.savedPosition = document.body.scrollTop; 81 | // } 82 | // return { x: 0, y: to.meta.savedPosition || 0 }; 83 | // } 84 | // } 85 | }); 86 | 87 | router.beforeEach((to, from, next) => {// 注册一个全局前置守卫 88 | if (to.matched.some(m => m.meta.auth)) {// 判断是否需要校验 89 | if (store.state.isLogin) {// 获取 90 | next();// 校验通过,正常跳转到你设置好的页面 91 | } else { 92 | next({// 校验失败,跳转至登录界面 93 | path: '/login', 94 | query: { 95 | redirect: to.fullPath 96 | }// 将跳转的路由path作为参数,用于在登录成功后获取并跳转到该路径 97 | }); 98 | } 99 | } else { 100 | next();// 不需要校验,直接跳转 101 | } 102 | }); 103 | 104 | export default router; 105 | 106 | /* 107 | $route.path 字符串,等于当前路由对象的路径,会被解析为绝对路径,如 "/home/news" 。 108 | $route.params 对象,包含路由中的动态片段和全匹配片段的键值对 109 | $route.query 对象,包含路由中查询参数的键值对。例如,对于 /home/news/detail/01?favorite=yes ,会得到$route.query.favorite == 'yes' 。 110 | $route.router 路由规则所属的路由器(以及其所属的组件)。 111 | $route.matched 数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。它会将嵌套它的父路由都匹配出来。 112 | $route.name 当前路径的名字,如果没有使用具名路径,则名字为空。 113 | */ 114 | -------------------------------------------------------------------------------- /src/views/sys-setting/welcome-page/welcome-page.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 138 | -------------------------------------------------------------------------------- /src/scss/iconfont.styl: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'commonicon'; 3 | src: url('./assets/fonts/commonicon.eot?bdoeg4'); 4 | src: url('./assets/fonts/commonicon.eot?bdoeg4#iefix') format('embedded-opentype'), 5 | url('./assets/fonts/commonicon.ttf?bdoeg4') format('truetype'), 6 | url('./assets/fonts/commonicon.woff?bdoeg4') format('woff'), 7 | url('./assets/fonts/commonicon.svg?bdoeg4#commonicon') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | [class^="common-icon"], [class*=" common-icon"] { 12 | /* use !important to prevent issues with browser extensions that change fonts */ 13 | font-family: 'commonicon' !important; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | 25 | &.bk-icon { 26 | font-family: 'commonicon' !important; 27 | } 28 | } 29 | 30 | .common-icon-add:before { 31 | content: "\e900"; 32 | } 33 | .common-icon-arrow-down:before { 34 | content: "\e901"; 35 | } 36 | .common-icon-black-box:before { 37 | content: "\e902"; 38 | } 39 | .common-icon-black-figure:before { 40 | content: "\e903"; 41 | } 42 | .common-icon-black-hook:before { 43 | content: "\e904"; 44 | } 45 | .common-icon-blueking:before { 46 | content: "\e905"; 47 | } 48 | .common-icon-box:before { 49 | content: "\e906"; 50 | } 51 | .common-icon-box-pen:before { 52 | content: "\e907"; 53 | } 54 | .common-icon-circulation:before { 55 | content: "\e908"; 56 | } 57 | .common-icon-clock:before { 58 | content: "\e909"; 59 | } 60 | .common-icon-clock-inversion:before { 61 | content: "\e90a"; 62 | } 63 | .common-icon-clock-reload:before { 64 | content: "\e90b"; 65 | } 66 | .common-icon-close:before { 67 | content: "\e90c"; 68 | } 69 | .common-icon-dark-circle-avatar:before { 70 | content: "\e90d"; 71 | } 72 | .common-icon-dark-circle-close:before { 73 | content: "\e90e"; 74 | } 75 | .common-icon-dark-circle-ellipsis:before { 76 | content: "\e90f"; 77 | } 78 | .common-icon-dark-circle-i:before { 79 | content: "\e910"; 80 | } 81 | .common-icon-dark-circle-pause:before { 82 | content: "\e911"; 83 | } 84 | .common-icon-dark-circle-question:before { 85 | content: "\e912"; 86 | } 87 | .common-icon-dark-circle-r:before { 88 | content: "\e913"; 89 | } 90 | .common-icon-dark-circle-s:before { 91 | content: "\e914"; 92 | } 93 | .common-icon-dark-circle-shape:before { 94 | content: "\e915"; 95 | } 96 | .common-icon-dark-circle-warning:before { 97 | content: "\e916"; 98 | } 99 | .common-icon-dark-paper:before { 100 | content: "\e917"; 101 | } 102 | .common-icon-done-thin:before { 103 | content: "\e918"; 104 | } 105 | .common-icon-double-arrow:before { 106 | content: "\e919"; 107 | } 108 | .common-icon-double-paper:before { 109 | content: "\e91a"; 110 | } 111 | .common-icon-double-vertical-line:before { 112 | content: "\e91b"; 113 | } 114 | .common-icon-drag:before { 115 | content: "\e91c"; 116 | } 117 | .common-icon-edit:before { 118 | content: "\e91d"; 119 | } 120 | .common-icon-eye-close:before { 121 | content: "\e91e"; 122 | } 123 | .common-icon-eye-open:before { 124 | content: "\e91f"; 125 | } 126 | .common-icon-flag-circle:before { 127 | content: "\e920"; 128 | } 129 | .common-icon-four-square:before { 130 | content: "\e921"; 131 | } 132 | .common-icon-gray-edit:before { 133 | content: "\e922"; 134 | } 135 | .common-icon-left-pin:before { 136 | content: "\e923"; 137 | } 138 | .common-icon-loading:before { 139 | content: "\e924"; 140 | } 141 | .common-icon-marquee:before { 142 | content: "\e925"; 143 | } 144 | .common-icon-no-data:before { 145 | content: "\e926"; 146 | } 147 | .common-icon-node-branchgateway:before { 148 | content: "\e927"; 149 | } 150 | .common-icon-node-convergegateway:before { 151 | content: "\e928"; 152 | } 153 | .common-icon-node-parallelgateway:before { 154 | content: "\e929"; 155 | } 156 | .common-icon-node-subflow:before { 157 | content: "\e92a"; 158 | } 159 | .common-icon-node-tasknode:before { 160 | content: "\e92b"; 161 | } 162 | .common-icon-paper:before { 163 | content: "\e92c"; 164 | } 165 | .common-icon-reduction:before { 166 | content: "\e92d"; 167 | } 168 | .common-icon-return-arrow:before { 169 | content: "\e92e"; 170 | } 171 | .common-icon-right-triangle:before { 172 | content: "\e92f"; 173 | } 174 | .common-icon-search:before { 175 | content: "\e930"; 176 | } 177 | .common-icon-source-in:before { 178 | content: "\e931"; 179 | } 180 | .common-icon-source-out:before { 181 | content: "\e932"; 182 | } 183 | .common-icon-square-attribute:before { 184 | content: "\e933"; 185 | } 186 | .common-icon-square-code:before { 187 | content: "\e934"; 188 | } 189 | .common-icon-tooltips:before { 190 | content: "\e935"; 191 | } 192 | .common-icon-zoom-in:before { 193 | content: "\e936"; 194 | } 195 | .common-icon-zoom-out:before { 196 | content: "\e937"; 197 | } 198 | -------------------------------------------------------------------------------- /src/views/header/header.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 102 | 103 | 161 | -------------------------------------------------------------------------------- /src/utils/draft.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | const draft = { 3 | // 添加本地缓存 4 | addDraft (username, ccId, templateId, templateData, message = window.gettext('自动保存')) { 5 | // 防止无法进行存储本地缓存 大约96KB左右的多余空间 6 | const minDraftLength = 100000; 7 | // 本地缓存剩余大小 8 | let remainingLocalStorageSize = 1024 * 1024 * 5 - unescape(encodeURIComponent(JSON.stringify(localStorage))).length; 9 | let index = 0; 10 | 11 | while (remainingLocalStorageSize < minDraftLength) { 12 | // 大小不足96KB时清除一部分本地缓存 从最早创建的本地缓存开始删除 13 | const key = localStorage.key(index++); 14 | const keyArray = key.split('_'); 15 | 16 | if (keyArray.length !== 4) { 17 | continue; 18 | } 19 | localStorage.removeItem(key); 20 | remainingLocalStorageSize = 1024 * 1024 * 5 - unescape(encodeURIComponent(JSON.stringify(localStorage))).length; 21 | } 22 | // 当前时间戳 23 | const timestamp = new Date().getTime(); 24 | // 当前年月日时分秒时间 25 | const localTime = moment.unix(timestamp / 1000).format('YYYY-MM-DD HH:mm:ss'); 26 | // 数据 27 | const descriptionData = {'time': localTime, 'message': message}; 28 | // 存储数据 29 | const key = [username, ccId, templateId, timestamp].join('_'); 30 | // 超过50个本地缓存需要进行删除 31 | let draftNumber = 0; 32 | const localStorageLength = localStorage.length; 33 | const regex = this.getKeyRegex(username, ccId, templateId); 34 | 35 | for (let index = localStorageLength - 1; index >= 0; index--) { 36 | const key = localStorage.key(index); 37 | // 获取key字段的所有切割信息 38 | 39 | if (regex.test(key)) { 40 | draftNumber++; 41 | if (draftNumber >= 50) { 42 | this.deleteDraft(key); 43 | } 44 | } 45 | } 46 | try { 47 | localStorage[key] = JSON.stringify({'template': templateData, 'description': descriptionData}); 48 | } catch (e) { 49 | // 用户浏览器不支持localStorage 50 | } 51 | }, 52 | // 删除本地缓存 53 | deleteDraft (key) { 54 | try { 55 | localStorage.removeItem(key); 56 | return true; 57 | } catch (e) { 58 | return false; 59 | } 60 | }, 61 | // 用于替换第一次创建模板id为 uuid 的id 62 | draftReplace (username, ccId, templateId, templateUUID) { 63 | const regex = this.getKeyRegex(username, ccId, templateUUID); 64 | 65 | for (const key in localStorage) { 66 | // 获取key字段的所有切割信息 67 | const keyArray = key.split('_'); 68 | // 切割完的长度应该为4且原先的模板id uuid 时才需要使用 69 | // username重复的几率比较大放置最后判断 70 | 71 | if (regex.test(key)) { 72 | // 原先的模板数据 73 | const draftData = localStorage[key]; 74 | // 将原先信息删除 75 | 76 | localStorage.removeItem(key); 77 | keyArray[2] = templateId; 78 | // 重新创建key值 79 | const newKey = keyArray.join('_'); 80 | 81 | localStorage[newKey] = draftData; 82 | } 83 | } 84 | }, 85 | // 获取当前本地缓存 86 | getDraftArray (username, ccId, templateId) { 87 | const regex = this.getKeyRegex(username, ccId, templateId); 88 | const draftArray = []; 89 | const localStorageLength = localStorage.length; 90 | 91 | for (let index = localStorageLength - 1; index >= 0; index--) { 92 | const key = localStorage.key(index); 93 | 94 | if (regex.test(key)) { 95 | draftArray.push({'key': key, 'data': JSON.parse(localStorage[key])}); 96 | } 97 | } 98 | return draftArray; 99 | }, 100 | // 删除没有template_id 的模板 101 | deleteAllDraftByUUID (username, ccId, uuid) { 102 | const regex = this.getKeyRegex(username, ccId, uuid); 103 | const localStorageLength = localStorage.length; 104 | 105 | for (let index = localStorageLength - 1; index >= 0; index--) { 106 | const key = localStorage.key(index); 107 | // uuid 需要加上""变为字符串 108 | 109 | if (regex.test(key)) { 110 | localStorage.removeItem(key); 111 | } 112 | } 113 | }, 114 | // 复制并替换本地缓存(模板克隆时使用) 115 | copyAndReplaceDraft (username, ccId, templateId, uuid) { 116 | const regex = this.getKeyRegex(username, ccId, templateId); 117 | 118 | for (const key in localStorage) { 119 | // 获取key字段的所有切割信息 120 | const keyArray = key.split('_'); 121 | 122 | if (regex.test(key)) { 123 | // 原先的模板数据 124 | const draftData = localStorage[key]; 125 | 126 | keyArray[2] = uuid; 127 | // 重新创建key值并进行创建 128 | const newKey = keyArray.join('_'); 129 | 130 | localStorage[newKey] = draftData; 131 | } 132 | } 133 | }, 134 | // 获取最近的一个本地缓存 135 | getLastDraft (username, ccId, templateId) { 136 | const localStorageLength = localStorage.length; 137 | // 动态生成正则表达式 138 | const regex = this.getKeyRegex(username, ccId, templateId); 139 | 140 | for (let index = localStorageLength - 1; index >= 0; index--) { 141 | const key = localStorage.key(index); 142 | 143 | if (regex.test(key)) { 144 | return localStorage[key]; 145 | } 146 | } 147 | }, 148 | // 获得正则表达式 149 | getKeyRegex (username, ccId, templateId) { 150 | return new RegExp('^' + username + '_' + ccId + '_' + templateId + '_'); 151 | } 152 | }; 153 | 154 | export default draft; 155 | -------------------------------------------------------------------------------- /src/views/sys-setting/base-manage/base-manage.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 116 | 117 | 118 | 151 | -------------------------------------------------------------------------------- /src/views/flow-template/flow-template.js: -------------------------------------------------------------------------------- 1 | import {jsPlumb} from 'jsplumb'; 2 | import $ from 'jquery'; 3 | import './utils/flow.js'; 4 | import nodeMenu from './node-menu/node-menu.vue'; 5 | import node from './node/node.vue'; 6 | import draggable from 'vuedraggable'; 7 | 8 | const ENDPOINT_DIRECTION = ['Top', 'Left', 'Right', 'Bottom']; 9 | 10 | export default { 11 | components: { 12 | draggable, 13 | node, 14 | nodeMenu 15 | }, 16 | data () { 17 | return { 18 | dataFlowInstance: null, 19 | data: { 20 | nodeList: [], 21 | lineList: [] 22 | }, 23 | isEdit: true, 24 | opts: { 25 | canvas: '#flow-contain', // 画布 26 | locationConfig: { 27 | start: ENDPOINT_DIRECTION, 28 | end: ENDPOINT_DIRECTION, 29 | task: ENDPOINT_DIRECTION 30 | // startpoint: ENDPOINT_DIRECTION, 31 | // endpoint: ENDPOINT_DIRECTION, 32 | // parallelgateway: ENDPOINT_DIRECTION, 33 | // convergegateway: ENDPOINT_DIRECTION, 34 | // branchgateway: ENDPOINT_DIRECTION, 35 | // tasknode: ENDPOINT_DIRECTION, 36 | // subflow: ENDPOINT_DIRECTION 37 | }, // 节点的类型和端点的位置 38 | lineWidth: 3, // 线的宽度 默认为2 39 | fillColor: '#348af3', // 高亮颜色 40 | defaultColor: '#a9adb6', // 默认颜色 41 | lineRadius: 1, // 线拐弯弧度 42 | pointColor: 'rgba(52, 138, 243, 0.15)', // 端点的颜色 43 | pointWidth: 3, // 连接端点的半径 44 | pointDistance: 0, // 端点与线的距离 45 | data: [], // 渲染的数据源, 46 | id: 'node', // 配置渲染的节点id 47 | isEdit: this.isEdit, // 是否编辑 48 | dropElevent: null // 拖拽的数据源 49 | } 50 | }; 51 | }, 52 | mounted () { 53 | const ths = this; 54 | 55 | $('#flow-main-content').dataflow(this.opts); 56 | this.dataFlowInstance = $('#flow-main-content').data('dataflow'); 57 | }, 58 | computed: { 59 | }, 60 | methods: { 61 | // 返回唯一标识 62 | // getUUID () { 63 | // return Math.random().toString(36).substr(3, 10); 64 | // }, 65 | // 改变节点的位置 66 | changeNodeSite (data) { 67 | console.log('-------------flow-template.js 改变节点的位置changeNodeSite begin---------'); 68 | console.log(data); 69 | for (var i = 0; i < this.data.nodeList.length; i++) { 70 | let node = this.data.nodeList[i]; 71 | 72 | if (node.id === data.nodeId) { 73 | node.left = data.left; 74 | node.top = data.top; 75 | } 76 | } 77 | console.log('-------------flow-template.js 改变节点的位置changeNodeSite end---------'); 78 | }, 79 | 80 | /** 81 | * 拖拽结束后添加新的节点 82 | * @param evt 83 | * @param nodeMenu 被添加的节点对象 84 | * @param mousePosition 鼠标拖拽结束的坐标 85 | */ 86 | addNodeFn (evt, nodeMenu, mousePosition) { 87 | console.log('-------------flow-template.js addNodeFn begin---------'); 88 | console.log(evt, nodeMenu, mousePosition); 89 | // let width = this.$refs.nodeMenu.$el.clientWidth; 90 | const ths = this; 91 | // let nodeId = this.getUUID(); 92 | let nodeId = ths.dataFlowInstance.generateNodeId(); 93 | 94 | let parentX = document.getElementById('flow-contain').getBoundingClientRect().left; 95 | let parentY = document.getElementById('flow-contain').getBoundingClientRect().top; 96 | let left = evt.originalEvent.layerX - mousePosition.left; 97 | let top = evt.originalEvent.layerY - mousePosition.top; 98 | 99 | if (parentX > evt.originalEvent.clientX) { 100 | // left = 0; 101 | return; 102 | } 103 | if (parentY > evt.originalEvent.clientY) { 104 | // top = 0; 105 | return; 106 | } 107 | 108 | let node = { 109 | id: nodeId, 110 | name: nodeId, 111 | type: nodeMenu.type, 112 | ico: nodeMenu.ico, 113 | x: left + 'px', 114 | y: top + 'px' 115 | }; 116 | 117 | console.log('生成新的node:'); 118 | console.log(node); 119 | // 这里可以进行业务判断、是否能够添加该节点 120 | ths.data.nodeList.push(node); 121 | // 给节点增加flow功能 122 | ths.$nextTick(function () { 123 | ths.dataFlowInstance.createLocation(node); 124 | }); 125 | console.log('-------------flow-template.js addNodeFn end---------'); 126 | }, 127 | // 加载流程图 128 | loadEasyFlow () { 129 | console.log('-------------flow-template.js 加载流程图 loadEasyFlow begin---------'); 130 | const tempLineList = this.data.lineList || []; 131 | const tempNodeList = this.data.nodeList || []; 132 | 133 | // 初始化节点 134 | for (let i = 0; i < tempNodeList.length; i++) { 135 | const node = tempNodeList[i]; 136 | 137 | // 设置源点,可以拖出线连接其他节点 138 | this.dataFlowInstance.makeSource(node.id, this.jsplumbSourceOptions); 139 | // // 设置目标点,其他源点拖出的线可以连接该节点 140 | this.dataFlowInstance.makeTarget(node.id, this.jsplumbTargetOptions); 141 | // 限制节点的可拖动区域 142 | // flowUtils.draggable(this.dataFlowInstance, node.id, {containment: $('#flow-contain')}); 143 | } 144 | 145 | // 初始化连线 146 | for (let i = 0; i < tempLineList.length; i++) { 147 | const line = tempLineList[i]; 148 | 149 | this.dataFlowInstance.connect({ 150 | source: line.from, 151 | target: line.to 152 | }, this.jsplumbConnectOptions); 153 | } 154 | console.log('-------------flow-template.js 加载流程图 loadEasyFlow end---------'); 155 | } 156 | } 157 | }; 158 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import modules from './modules/index.js'; 4 | 5 | Vue.use(Vuex); 6 | 7 | const store = new Vuex.Store({ 8 | state: { 9 | isLogin: true, // 判断是否已经登录, 10 | lang: 'zh', 11 | sysSetting: { 12 | homeCurrentTab: 'none', 13 | currentTab: {}, 14 | tabIndex: 0, 15 | homeTabs: [] 16 | }, 17 | userCenter: { 18 | homeCurrentTab: 'none', 19 | currentTab: {}, 20 | tabIndex: 0, 21 | homeTabs: [] 22 | } 23 | }, 24 | mutations: { 25 | saveLogin (state) { 26 | state.isLogin = true; 27 | }, 28 | SYS_ADD_TAB (state, menuNode) { // 增加页签 29 | let hasThisTab = false; 30 | let len = state.sysSetting.homeTabs.length; 31 | 32 | for (var i = 0; i < len; i++) { // 判断页签是否已经打开过 33 | if (state.sysSetting.homeTabs[i].id === menuNode.menuId) { 34 | state.sysSetting.homeCurrentTab = state.sysSetting.homeTabs[i].name; 35 | state.sysSetting.currentTab = state.sysSetting.homeTabs[i]; 36 | state.sysSetting.homeTabs[i].timestamp = new Date().getTime(); 37 | hasThisTab = true; 38 | break; 39 | } 40 | } 41 | 42 | if (!hasThisTab) { 43 | let menuName = state.lang === 'zh' ? menuNode.menuNameCn : menuNode.menuNameEn; 44 | 45 | state.sysSetting.currentTab = { 46 | id: menuNode.menuId, 47 | name: menuNode.menuId, 48 | title: menuName, 49 | component: menuNode.comp, 50 | url: menuNode.url, 51 | timestamp: new Date().getTime() 52 | }; 53 | 54 | state.sysSetting.homeTabs.push(state.sysSetting.currentTab); 55 | state.sysSetting.homeCurrentTab = menuNode.menuId; 56 | ++state.sysSetting.tabIndex; 57 | } 58 | }, 59 | SYS_CLICK_TAB (state, target) {// 点击页签 60 | let tabs = state.sysSetting.homeTabs; 61 | let activeName = state.sysSetting.homeCurrentTab; 62 | 63 | tabs.forEach((tab, index) => { 64 | if (tab.name === target.name) { 65 | activeName = tab.name; 66 | state.sysSetting.currentTab = tab; 67 | tab.timestamp = new Date().getTime(); // 更新该tab的时间戳 68 | } 69 | }); 70 | state.sysSetting.homeCurrentTab = activeName; 71 | }, 72 | SYS_REMOVE_TAB (state, targetName) {// 删除页签 73 | let tabs = state.sysSetting.homeTabs; 74 | let activeName = state.sysSetting.homeCurrentTab; 75 | 76 | tabs.forEach((tab, index) => { 77 | if (activeName === targetName) {// 删除的是当前打开的 78 | if (tab.name === targetName) { 79 | let nextTab = tabs[index + 1] || tabs[index - 1]; 80 | 81 | if (nextTab) { 82 | activeName = nextTab.name; 83 | state.sysSetting.currentTab = nextTab; 84 | } 85 | } 86 | } 87 | }); 88 | state.sysSetting.homeCurrentTab = activeName; 89 | state.sysSetting.homeTabs = tabs.filter(tab => tab.name !== targetName); 90 | }, 91 | USER_ADD_TAB (state, menuNode) { // 增加页签 92 | let hasThisTab = false; 93 | let len = state.userCenter.homeTabs.length; 94 | 95 | for (var i = 0; i < len; i++) { // 判断页签是否已经打开过 96 | if (state.userCenter.homeTabs[i].id === menuNode.menuId) { 97 | state.userCenter.homeCurrentTab = state.userCenter.homeTabs[i].name; 98 | state.userCenter.currentTab = state.userCenter.homeTabs[i]; 99 | state.userCenter.homeTabs[i].timestamp = new Date().getTime(); 100 | hasThisTab = true; 101 | break; 102 | } 103 | } 104 | 105 | if (!hasThisTab) { 106 | let menuName = state.lang === 'zh' ? menuNode.menuNameCn : menuNode.menuNameEn; 107 | 108 | state.userCenter.currentTab = { 109 | id: menuNode.menuId, 110 | name: menuNode.menuId, 111 | title: menuName, 112 | component: menuNode.comp, 113 | url: menuNode.url, 114 | timestamp: new Date().getTime() 115 | }; 116 | 117 | state.userCenter.homeTabs.push(state.userCenter.currentTab); 118 | state.userCenter.homeCurrentTab = menuNode.menuId; 119 | ++state.userCenter.tabIndex; 120 | } 121 | }, 122 | USER_CLICK_TAB (state, target) {// 点击页签 123 | let tabs = state.userCenter.homeTabs; 124 | let activeName = state.userCenter.homeCurrentTab; 125 | 126 | tabs.forEach((tab, index) => { 127 | if (tab.name === target.name) { 128 | activeName = tab.name; 129 | state.userCenter.currentTab = tab; 130 | tab.timestamp = new Date().getTime(); // 更新该tab的时间戳 131 | } 132 | }); 133 | state.userCenter.homeCurrentTab = activeName; 134 | }, 135 | USER_REMOVE_TAB (state, targetName) {// 删除页签 136 | let tabs = state.userCenter.homeTabs; 137 | let activeName = state.userCenter.homeCurrentTab; 138 | 139 | tabs.forEach((tab, index) => { 140 | if (activeName === targetName) {// 删除的是当前打开的 141 | if (tab.name === targetName) { 142 | let nextTab = tabs[index + 1] || tabs[index - 1]; 143 | 144 | if (nextTab) { 145 | activeName = nextTab.name; 146 | state.userCenter.currentTab = nextTab; 147 | } 148 | } 149 | } 150 | }); 151 | state.userCenter.homeCurrentTab = activeName; 152 | state.userCenter.homeTabs = tabs.filter(tab => tab.name !== targetName); 153 | } 154 | }, 155 | getters: { 156 | // 当前tab 157 | getSysHomeCurrentTab (state) { 158 | return state.sysSetting.homeCurrentTab || []; 159 | }, 160 | // 已打开的tabs 161 | getSysHomeTabs (state) { 162 | return state.sysSetting.homeTabs || []; 163 | }, 164 | // 当前tab 165 | getUserHomeCurrentTab (state) { 166 | return state.userCenter.homeCurrentTab || []; 167 | }, 168 | // 已打开的tabs 169 | getUserHomeTabs (state) { 170 | return state.userCenter.homeTabs || []; 171 | } 172 | }, 173 | actions: {// 增加页签 ,参数:{name:'',path:'',comp:''} 174 | sysAddTab ({commit}, target) { 175 | commit('SYS_ADD_TAB', target); 176 | }, 177 | // 删除页签 178 | sysRemoveTab ({commit}, targetName) { 179 | commit('SYS_REMOVE_TAB', targetName); 180 | }, 181 | // 点击页签 182 | sysClickTab ({commit}, targetName) { 183 | commit('SYS_CLICK_TAB', targetName); 184 | }, 185 | // 增加页签 ,参数:{name:'',path:'',comp:''} 186 | userAddTab ({commit}, target) { 187 | commit('USER_ADD_TAB', target); 188 | }, 189 | // 删除页签 190 | userRemoveTab ({commit}, targetName) { 191 | commit('USER_REMOVE_TAB', targetName); 192 | }, 193 | // 点击页签 194 | userClickTab ({commit}, targetName) { 195 | commit('USER_CLICK_TAB', targetName); 196 | } 197 | }, 198 | modules 199 | }); 200 | 201 | export default store; 202 | -------------------------------------------------------------------------------- /src/views/sys-setting/scholl-info/scholl-info.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 173 | 174 | 185 | -------------------------------------------------------------------------------- /static/css/reset.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 3 | * http://cssreset.com 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video, input { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font-weight: normal; 23 | outline: none; 24 | background: none; 25 | vertical-align: baseline; 26 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 27 | font-family: Microsoft YaHei,'Avenir', Helvetica, Arial, sans-serif; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-osx-font-smoothing: grayscale; 30 | } 31 | 32 | /* HTML5 display-role reset for older browsers */ 33 | article, aside, details, figcaption, figure, 34 | footer, header, menu, nav, section { 35 | display: block; 36 | } 37 | 38 | body { 39 | line-height: 1; 40 | } 41 | 42 | blockquote, q { 43 | quotes: none; 44 | } 45 | 46 | blockquote:before, blockquote:after, 47 | q:before, q:after { 48 | content: none; 49 | } 50 | 51 | table { 52 | border-collapse: collapse; 53 | border-spacing: 0; 54 | } 55 | 56 | /* custom */ 57 | a { 58 | color: #7e8c8d; 59 | text-decoration: none; 60 | -webkit-backface-visibility: hidden; 61 | } 62 | 63 | li { 64 | list-style: none; 65 | } 66 | 67 | ::-webkit-scrollbar { 68 | width: 5px; 69 | height: 5px; 70 | } 71 | 72 | ::-webkit-scrollbar-track-piece { 73 | background-color: rgba(0, 0, 0, 0.2); 74 | -webkit-border-radius: 6px; 75 | } 76 | 77 | ::-webkit-scrollbar-thumb:vertical { 78 | height: 5px; 79 | background-color: rgba(125, 125, 125, 0.7); 80 | -webkit-border-radius: 6px; 81 | } 82 | 83 | ::-webkit-scrollbar-thumb:horizontal { 84 | width: 5px; 85 | background-color: rgba(125, 125, 125, 0.7); 86 | -webkit-border-radius: 6px; 87 | } 88 | 89 | html, body { 90 | width: 100%; 91 | } 92 | 93 | body { 94 | -webkit-text-size-adjust: none; 95 | color: #2c3e50; 96 | } 97 | h1, h2, h3, h4, h5, h6 { 98 | font-size: 100%; 99 | font-weight: normal; 100 | } 101 | ul, ol{ 102 | list-style: none; 103 | } 104 | img { 105 | border: none; 106 | } 107 | table { 108 | border-collapse: collapse; 109 | border-spacing: 0; 110 | } 111 | a { 112 | text-decoration: none; 113 | } 114 | a:hover { 115 | text-decoration: underline; 116 | } 117 | 118 | /* ==========css common========== */ 119 | /*relative*/ 120 | .fixed { 121 | position: fixed; 122 | } 123 | .rel { 124 | position: relative; 125 | } 126 | .abs { 127 | position: absolute; 128 | } 129 | 130 | /*display*/ 131 | .db { 132 | display: block; 133 | } 134 | .dn { 135 | display: none; 136 | } 137 | .dib { 138 | display: inline-block; 139 | } 140 | 141 | /*float*/ 142 | .fl { 143 | float: left 144 | } 145 | .fr { 146 | float: right 147 | } 148 | .cf:after{ 149 | content: " "; 150 | visibility: hidden; 151 | display: block; 152 | clear: both; 153 | height: 0; 154 | font-size: 0; 155 | } 156 | .cf { 157 | clear: both; 158 | zoom: 1; 159 | } 160 | 161 | /*margin*/ 162 | .mlr-auto { 163 | margin-left: auto; 164 | margin-right: auto; 165 | } 166 | .mt5 { 167 | margin-top: 5px; 168 | } 169 | .mr5 { 170 | margin-right: 5px; 171 | } 172 | .mb5 { 173 | margin-bottom: 5px; 174 | } 175 | .ml5 { 176 | margin-left: 5px; 177 | } 178 | .mg5 { 179 | margin: 5px; 180 | } 181 | .mt10 { 182 | margin-top: 10px; 183 | } 184 | .mr10 { 185 | margin-right: 10px; 186 | } 187 | .mb10 { 188 | margin-bottom: 10px; 189 | } 190 | .ml10 { 191 | margin-left: 10px; 192 | } 193 | .mg10 { 194 | margin: 10px; 195 | } 196 | .mt15 { 197 | margin-top: 15px; 198 | } 199 | .mr15 { 200 | margin-right: 15px; 201 | } 202 | .mb15 { 203 | margin-bottom: 15px; 204 | } 205 | .ml15 { 206 | margin-left: 15px; 207 | } 208 | .mg15 { 209 | margin: 15px; 210 | } 211 | .mt20 { 212 | margin-top: 20px; 213 | } 214 | .mr20 { 215 | margin-right: 20px; 216 | } 217 | .mb20 { 218 | margin-bottom: 20px; 219 | } 220 | .ml20 { 221 | margin-left: 20px; 222 | } 223 | .mg20 { 224 | margin: 20px; 225 | } 226 | 227 | /*padding*/ 228 | .pt5 { 229 | padding-top: 5px; 230 | } 231 | .pr5 { 232 | padding-right: 5px; 233 | } 234 | .pb5 { 235 | padding-bottom: 5px; 236 | } 237 | .pl5 { 238 | padding-left: 5px; 239 | } 240 | .pd5 { 241 | padding: 5px; 242 | } 243 | .pt10 { 244 | padding-top: 10px; 245 | } 246 | .pr10 { 247 | padding-right: 10px; 248 | } 249 | .pb10 { 250 | padding-bottom: 10px; 251 | } 252 | .pl10 { 253 | padding-left: 10px; 254 | } 255 | .pd10 { 256 | padding: 10px; 257 | } 258 | .pt15 { 259 | padding-top: 15px; 260 | } 261 | .pr15 { 262 | padding-right: 15px; 263 | } 264 | .pb15 { 265 | padding-bottom: 15px; 266 | } 267 | .pl15 { 268 | padding-left: 15px; 269 | } 270 | .pd15 { 271 | padding: 15px; 272 | } 273 | .pt20 { 274 | padding-top: 20px; 275 | } 276 | .pr20 { 277 | padding-right: 20px; 278 | } 279 | .pb20 { 280 | padding-bottom: 20px; 281 | } 282 | .pl20 { 283 | padding-left: 20px; 284 | } 285 | .pd20 { 286 | padding: 20px; 287 | } 288 | 289 | /*text-align*/ 290 | .tl { 291 | text-align: left; 292 | } 293 | .tc { 294 | text-align: center; 295 | } 296 | .tr { 297 | text-align: right; 298 | } 299 | 300 | /* line-height */ 301 | .lh14 { 302 | line-height: 14px; 303 | } 304 | .lh16 { 305 | line-height: 16px; 306 | } 307 | .lh18 { 308 | line-height: 18px; 309 | } 310 | .lh20 { 311 | line-height: 20px; 312 | } 313 | .lh22 { 314 | line-height: 22px; 315 | } 316 | .lh24 { 317 | line-height: 24px; 318 | } 319 | 320 | /* vertical-align */ 321 | .vm { 322 | vertical-align: middle; 323 | } 324 | .vb { 325 | vertical-align: bottom; 326 | } 327 | .vt { 328 | vertical-align: top; 329 | } 330 | 331 | /* font-size */ 332 | .f0 { 333 | font-size: 0; 334 | } 335 | .f12 { 336 | font-size: 12px; 337 | } 338 | .f14 { 339 | font-size: 14px; 340 | } 341 | .f16 { 342 | font-size: 16px; 343 | } 344 | .f18 { 345 | font-size: 18px; 346 | } 347 | .f20 { 348 | font-size: 20px; 349 | } 350 | .f24 { 351 | font-size: 24px; 352 | } 353 | 354 | /* overflow */ 355 | .ovh { 356 | overflow: hidden; 357 | } 358 | .ova { 359 | overflow: auto; 360 | } 361 | 362 | /* visibility */ 363 | .vh { 364 | visibility: hidden; 365 | } 366 | .vv{ 367 | visibility: visible; 368 | } 369 | 370 | /*border-radius*/ 371 | .bd-rs4 { 372 | border-radius: 4px; 373 | } 374 | .bd-rs6 { 375 | border-radius: 6px; 376 | } 377 | .bd-rs8 { 378 | border-radius: 8px; 379 | } 380 | .bd-rs12 { 381 | border-radius: 12px; 382 | } 383 | -------------------------------------------------------------------------------- /src/utils/validatePipeline.js: -------------------------------------------------------------------------------- 1 | import {NODE_DICT} from 'src/constants/index.js'; 2 | 3 | const NODE_RULE = { 4 | 'startpoint': { 5 | min_in: 0, 6 | max_in: 0, 7 | min_out: 1, 8 | max_out: 1, 9 | allowed_out: ['tasknode', 'branchgateway', 'parallelgateway', 'endpoint', 'subflow'], 10 | unique: true 11 | }, 12 | 'endpoint': { 13 | min_in: 1, 14 | max_in: 1, 15 | min_out: 0, 16 | max_out: 0, 17 | allowed_out: [], 18 | unique: true 19 | }, 20 | 'tasknode': { 21 | min_in: 1, 22 | max_in: 1, 23 | min_out: 1, 24 | max_out: 1, 25 | allowed_out: ['tasknode', 'subflow', 'branchgateway', 'parallelgateway', 'convergegateway', 'endpoint'], 26 | unique: false 27 | }, 28 | 'branchgateway': { 29 | min_in: 1, 30 | max_in: 1, 31 | min_out: 2, 32 | max_out: 1000, 33 | allowed_out: ['tasknode', 'subflow', 'branchgateway', 'parallelgateway', 'convergegateway'], 34 | unique: false 35 | }, 36 | 'parallelgateway': { 37 | min_in: 1, 38 | max_in: 1, 39 | min_out: 2, 40 | max_out: 1000, 41 | allowed_out: ['tasknode', 'subflow', 'branchgateway', 'parallelgateway', 'convergegateway'], 42 | unique: false 43 | }, 44 | 'convergegateway': { 45 | min_in: 2, 46 | max_in: 1000, 47 | min_out: 1, 48 | max_out: 1, 49 | allowed_out: ['tasknode', 'subflow', 'branchgateway', 'parallelgateway', 'convergegateway', 'endpoint'], 50 | unique: false 51 | }, 52 | 'subflow': { 53 | min_in: 1, 54 | max_in: 1, 55 | min_out: 1, 56 | max_out: 1, 57 | allowed_out: ['tasknode', 'subflow', 'branchgateway', 'parallelgateway', 'endpoint', 'convergegateway'], 58 | unique: false 59 | } 60 | }; 61 | 62 | const validatePipeline = { 63 | /** 64 | * 判断连线是否合法 65 | * step: 66 | * 1.源节点(输出连线最大条数、输出节点是否允许连接) 67 | * 2.目标节点(输入连线的条数) 68 | * @param {Object} line 69 | * @param {Object} data 70 | */ 71 | isLineValid (line, data) { 72 | if (data) { 73 | return { 74 | result: 'ok' 75 | }; /// ////////////// 76 | } 77 | const {lines, locations} = data; 78 | const {source, target} = line; 79 | const sourceId = source.id; 80 | const targetId = target.id; 81 | const sourceNode = locations.filter(item => item.id === sourceId)[0]; 82 | const targetNode = locations.filter(item => item.id === targetId)[0]; 83 | const sourceRule = NODE_RULE[sourceNode.type]; 84 | const targetRule = NODE_RULE[targetNode.type]; 85 | let sourceLinesLinked = 0; 86 | let targetLinesLinked = 0; 87 | let isLoop = false; 88 | 89 | if (sourceRule.max_out === 0) { 90 | const i18n_text = window.gettext('只能添加输入连线'); 91 | const message = `${NODE_DICT[sourceNode.type]}${i18n_text}`; 92 | 93 | return this.getMessage(false, message); 94 | } 95 | 96 | if (targetRule.max_in === 0) { 97 | const i18n_text = window.gettext('只能添加输出连线'); 98 | const message = `${NODE_DICT[targetNode.type]}${i18n_text}`; 99 | 100 | return this.getMessage(false, message); 101 | } 102 | 103 | if (sourceRule.allowed_out.indexOf(targetNode.type) === -1) { 104 | const i18n_text = window.gettext('不能连接'); 105 | const message = `${NODE_DICT[sourceNode.type]}${i18n_text}${NODE_DICT[targetNode.type]}`; 106 | 107 | return this.getMessage(false, message); 108 | } 109 | const isSameLine = lines.some(item => { 110 | if (item.source.id === sourceId) { 111 | sourceLinesLinked += 1; 112 | if (item.target.id === targetId) { 113 | return true; 114 | } 115 | } 116 | if (item.target.id === targetId) { 117 | targetLinesLinked += 1; 118 | } 119 | if (item.source.id === targetId && item.target.id === sourceId) { 120 | isLoop = true; 121 | } 122 | }); 123 | 124 | if (isLoop) { 125 | const message = window.gettext('相同节点不能回连'); 126 | 127 | return this.getMessage(false, message); 128 | } 129 | 130 | if (isSameLine) { 131 | const message = window.gettext('重复添加连线'); 132 | 133 | return this.getMessage(false, message); 134 | } else { 135 | const i18n_text1 = window.gettext('已达到'); 136 | 137 | if (sourceLinesLinked >= sourceRule.max_out) { 138 | const i18n_text2 = window.gettext('最大输出连线条数'); 139 | const message = `${i18n_text1}${NODE_DICT[sourceNode.type]}${i18n_text2}`; 140 | 141 | return this.getMessage(false, message); 142 | } 143 | if (targetLinesLinked >= targetRule.max_in) { 144 | const i18n_text2 = window.gettext('最大输入连线条数'); 145 | const message = `${i18n_text1}${NODE_DICT[targetNode.type]}${i18n_text2}`; 146 | 147 | return this.getMessage(false, message); 148 | } 149 | } 150 | return this.getMessage(); 151 | }, 152 | isLocationValid (loc, data) { 153 | const rule = NODE_RULE[loc.type]; 154 | 155 | if (rule.unique) { // 节点唯一性 156 | const isLocationOverMount = data.some(item => { 157 | return item.type === loc.type && item.id !== loc.id; 158 | }); 159 | 160 | if (isLocationOverMount) { 161 | const i18n_text = window.gettext('在模板中只能添加一个'); 162 | const message = `${NODE_DICT[loc.type]}${i18n_text}`; 163 | 164 | return this.getMessage(false, message); 165 | } 166 | } 167 | return this.getMessage(); 168 | }, 169 | 170 | /** 171 | * 校验有没有空节点 172 | * @param {Object} data 173 | */ 174 | isDataValid (data) { 175 | let message; 176 | let branchAndParallelGateways = 0; 177 | let convergegateways = 0; 178 | let tasknode = 0; 179 | let subflow = 0; 180 | const isLineNumValid = data.locations.every(loc => { 181 | const rule = NODE_RULE[loc.type]; 182 | const name = loc.name || NODE_DICT[loc.type]; 183 | let sourceLinesLinked = 0; 184 | let targetLinesLinked = 0; 185 | 186 | if (loc.type === 'branchgateway' || loc.type === 'parallelgateway') { 187 | branchAndParallelGateways += 1; 188 | } else if (loc.type === 'convergegateway') { 189 | convergegateways += 1; 190 | } else if (loc.type === 'tasknode') { 191 | tasknode += 1; 192 | } else if (loc.type === 'subflow') { 193 | subflow += 1; 194 | } 195 | data.lines.forEach(line => { 196 | if (line.source.id === loc.id) { 197 | targetLinesLinked += 1; 198 | } 199 | if (line.target.id === loc.id) { 200 | sourceLinesLinked += 1; 201 | } 202 | }); 203 | const i18n_text1 = window.gettext('至少需要'); 204 | 205 | if (sourceLinesLinked < rule.min_in) { 206 | const i18n_text2 = window.gettext('条输入连线'); 207 | 208 | message = `${name}${i18n_text1}${rule.min_in}${i18n_text2}`; 209 | return false; 210 | } 211 | if (targetLinesLinked < rule.min_out) { 212 | const i18n_text2 = window.gettext('条输出连线'); 213 | 214 | message = `${name}${i18n_text1}${rule.min_out}${i18n_text2}`; 215 | return false; 216 | } 217 | return true; 218 | }); 219 | 220 | if (!isLineNumValid) { 221 | return this.getMessage(false, message); 222 | } 223 | 224 | if ((tasknode + subflow) === 0) { 225 | message = window.gettext('请添加任务节点'); 226 | return this.getMessage(false, message); 227 | } 228 | 229 | if (branchAndParallelGateways !== convergegateways) { 230 | message = window.gettext('并行网关、分支网关个数和汇聚网关个数必须一致,并且必须配对使用'); 231 | return this.getMessage(false, message); 232 | } 233 | 234 | return this.getMessage(); 235 | }, 236 | getMessage (result = true, message = '') { 237 | return {result, message}; 238 | } 239 | }; 240 | 241 | export default validatePipeline; 242 | -------------------------------------------------------------------------------- /src/views/login/login.styl: -------------------------------------------------------------------------------- 1 | .login { 2 | width: 100%; 3 | height: 100%; 4 | text-align: center; 5 | background-color: rgb(53, 152, 219); 6 | background-position: left bottom; /* 规定背景图像的位置 */ 7 | // background-origin: content-box; /* 规定背景图片的定位区域 */ 8 | // background-clip:content-box; /* 规定背景的绘制区域 */ 9 | background-attachment: fixed; /* 设置背景图像是否固定或者随着页面的其余部分滚动 */ 10 | background-repeat: no-repeat; 11 | background-size: cover; 12 | &.bg0 { 13 | background-image: url("./img/bg0_0.jpg"); 14 | } 15 | &.bg1 { 16 | background-color: rgb(53, 152, 219); 17 | background-image: url("./img/bg0_1.jpg"); 18 | } 19 | &.bg2 { 20 | background-color: rgb(53, 152, 219); 21 | background-image: url("./img/bg0_2.jpg"); 22 | } 23 | .f-content { 24 | // position: absolute; 25 | // top: 0; 26 | // right: 0; 27 | // bottom: 0; 28 | // text-align: center; 29 | // margin: 0 auto; 30 | position: absolute; 31 | top: 50%; 32 | left: 50%; 33 | padding-top: 30px; 34 | margin-left: -245px; 35 | margin-top: -200px; 36 | width: 36%; 37 | transition: width 0.3s ease-out; 38 | background-repeat: no-repeat; 39 | background-attachment: fixed; 40 | background-size: cover; 41 | background-position: right bottom; 42 | box-shadow: 0px 0px 1px 0px #333; 43 | @media (max-width: 1024px) and (min-width: 481px) { 44 | width: 50%; 45 | margin-left: -180px; 46 | margin-top: -150px; 47 | } 48 | 49 | &.right-bg0 { 50 | background-image: url("./img/bg1_0.png"); 51 | } 52 | &.right-bg1 { 53 | background-image: url("./img/bg1_1.png"); 54 | } 55 | &.right-bg2 { 56 | background-image: url("./img/bg1_2.png"); 57 | } 58 | .detail { 59 | margin: 0 auto; 60 | width: 60%; 61 | @media (max-width: 1024px) and (min-width: 481px) { 62 | width: 70%; 63 | } 64 | .r-form { 65 | // margin-top: 50%; 66 | text-align: left; 67 | .f-title { 68 | position: relative; 69 | height: 40px; 70 | margin-bottom: 8px; 71 | line-height: 40px; 72 | font-size: 14px; 73 | color: #fff; 74 | .user { 75 | display: inline-block; 76 | font-weight: bold; 77 | } 78 | .lang { 79 | position: absolute; 80 | right: 0px; 81 | display: inline-block; 82 | &:hover { 83 | cursor: pointer; 84 | } 85 | .down { 86 | margin-left: 3px; 87 | } 88 | .lang-list { 89 | position: absolute; 90 | right: 0; 91 | display: inline-block; 92 | border: 1px solid #ddd; 93 | background: #fff; 94 | z-index: 1; 95 | line-height: 1.5; 96 | a { 97 | display: block; 98 | margin: -1px 0; 99 | overflow: hidden; 100 | word-wrap: normal; 101 | white-space: nowrap; 102 | text-overflow: ellipsis; 103 | color: #333; 104 | text-decoration: none; 105 | padding: 12px 30px; 106 | &:hover{ 107 | color: #333; 108 | background: #f7f7f7; 109 | text-decoration: none; 110 | } 111 | } 112 | } 113 | } 114 | } 115 | .content-wrapper { 116 | position: relative; 117 | .arrow { 118 | position: absolute; 119 | left: 21px; 120 | top: -7px; 121 | border-right: 7px solid transparent; 122 | border-left: 7px solid transparent; 123 | border-bottom: 7px solid #fff; 124 | display: inline-block; 125 | transition: left 0.5s ease; 126 | } 127 | .f-input { 128 | width: 100%; 129 | background-color: rgba(255,255,255,0.9); 130 | border-radius: 3px; 131 | .icon { 132 | float: left; 133 | color: #ccc; 134 | font-size: 18px; 135 | transition: color ease-in 0.3s; 136 | width: 45px; 137 | height: 46px; 138 | line-height: 46px; 139 | text-align: center; 140 | @media (max-width: 1440px) and (min-width: 481px) { 141 | height: 40px; 142 | line-height: 40px; 143 | } 144 | } 145 | section { 146 | border-left: 1px solid #fff; 147 | border-right: 1px solid #fff; 148 | border-bottom: 1px solid #e3e4e5; 149 | } 150 | .input-content { 151 | .input-wrapper{ 152 | margin-left: 45px; 153 | } 154 | input { 155 | background: none; 156 | border: none; 157 | outline: none; 158 | padding: 5px 0; 159 | width: 100%; 160 | color: #555; 161 | height: 36px; 162 | line-height: 36px; 163 | &:active{ 164 | color: #777; 165 | border-color: #5bc0de; 166 | } 167 | @media (max-width: 1440px) and (min-width: 481px) { 168 | height: 30px; 169 | line-height: 30px; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | .reme { 176 | position: relative; 177 | line-height: 44px; 178 | height: 44px; 179 | color: #fff; 180 | font-size: 14px; 181 | input { 182 | position: absolute !important; 183 | left: -9999px; 184 | box-sizing: border-box; 185 | padding: 0; 186 | } 187 | .saveUsername{ 188 | cursor: pointer; 189 | .checkbox { 190 | display: inline-block; 191 | vertical-align: text-bottom; 192 | background: url(./img/check.png) no-repeat; 193 | width: 16px; 194 | height: 16px; 195 | margin: 1px 3px 0 0; 196 | overflow: hidden; 197 | &.checkbox-checked { 198 | background: url(./img/checked.png) no-repeat; 199 | &:hover { 200 | background: url(./img/check_hover.png) no-repeat; 201 | } 202 | } 203 | } 204 | } 205 | } 206 | .login-btn { 207 | position: relative; 208 | display: inline-block; 209 | box-sizing: border-box; 210 | width: 100%; 211 | height: 46px; 212 | background-color: #3598db; 213 | color: rgb(255, 255, 255); 214 | border-color: #136b9f; 215 | line-height: 46px; 216 | text-align: center; 217 | text-decoration: none; 218 | vertical-align: middle; 219 | cursor: pointer; 220 | font-size: 14px; 221 | border: 1px solid transparent; 222 | border-radius: 2px; 223 | padding: 0 12px; 224 | transition: all ease-out 0.3s; 225 | &:hover{ 226 | color: #fff; 227 | background-color: #136b9f; 228 | border-color: #0f557f; 229 | } 230 | @media (max-width: 1440px) and (min-width: 481px) { 231 | height: 40px; 232 | line-height: 40px; 233 | font-size: 14px; 234 | } 235 | } 236 | .err-message { 237 | font-size: 13px; 238 | color: #ff0000; 239 | height: auto; 240 | min-height: 40px; 241 | line-height: 2 !important; 242 | margin-top: 8px; 243 | } 244 | } 245 | } 246 | } 247 | .r-header { 248 | padding-top: 30px; 249 | padding-right: 50px; 250 | font-size: 13px; 251 | line-height: 36px; 252 | text-align: right; 253 | a { 254 | color: #fff; 255 | margin-left: 5px; 256 | } 257 | } 258 | } 259 | 260 | --------------------------------------------------------------------------------