Repository: iview/iview-admin Branch: master Commit: dd86e386a854 Files: 182 Total size: 246.7 KB Directory structure: gitextract_kkvwwkjv/ ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .travis.yml ├── LICENSE ├── README.md ├── cypress.json ├── package.json ├── public/ │ └── index.html ├── src/ │ ├── App.vue │ ├── api/ │ │ ├── data.js │ │ ├── routers.js │ │ └── user.js │ ├── assets/ │ │ └── icons/ │ │ └── iconfont.css │ ├── components/ │ │ ├── charts/ │ │ │ ├── bar.vue │ │ │ ├── index.js │ │ │ ├── pie.vue │ │ │ └── theme.json │ │ ├── common/ │ │ │ ├── common.less │ │ │ └── util.js │ │ ├── common-icon/ │ │ │ ├── common-icon.vue │ │ │ └── index.js │ │ ├── count-to/ │ │ │ ├── count-to.vue │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── cropper/ │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── index.vue │ │ ├── drag-drawer/ │ │ │ ├── drag-drawer-trigger.vue │ │ │ ├── drag-drawer.vue │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── mixin.js │ │ ├── drag-list/ │ │ │ ├── drag-list.vue │ │ │ └── index.js │ │ ├── editor/ │ │ │ ├── editor.vue │ │ │ └── index.js │ │ ├── icons/ │ │ │ ├── icons.vue │ │ │ └── index.js │ │ ├── info-card/ │ │ │ ├── index.js │ │ │ └── infor-card.vue │ │ ├── login-form/ │ │ │ ├── index.js │ │ │ └── login-form.vue │ │ ├── main/ │ │ │ ├── components/ │ │ │ │ ├── a-back-top/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.vue │ │ │ │ ├── error-store/ │ │ │ │ │ ├── error-store.vue │ │ │ │ │ └── index.js │ │ │ │ ├── fullscreen/ │ │ │ │ │ ├── fullscreen.vue │ │ │ │ │ └── index.js │ │ │ │ ├── header-bar/ │ │ │ │ │ ├── custom-bread-crumb/ │ │ │ │ │ │ ├── custom-bread-crumb.less │ │ │ │ │ │ ├── custom-bread-crumb.vue │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── header-bar.less │ │ │ │ │ ├── header-bar.vue │ │ │ │ │ ├── index.js │ │ │ │ │ └── sider-trigger/ │ │ │ │ │ ├── index.js │ │ │ │ │ ├── sider-trigger.less │ │ │ │ │ └── sider-trigger.vue │ │ │ │ ├── language/ │ │ │ │ │ ├── index.js │ │ │ │ │ └── language.vue │ │ │ │ ├── side-menu/ │ │ │ │ │ ├── collapsed-menu.vue │ │ │ │ │ ├── index.js │ │ │ │ │ ├── item-mixin.js │ │ │ │ │ ├── mixin.js │ │ │ │ │ ├── side-menu-item.vue │ │ │ │ │ ├── side-menu.less │ │ │ │ │ └── side-menu.vue │ │ │ │ ├── tags-nav/ │ │ │ │ │ ├── index.js │ │ │ │ │ ├── tags-nav.less │ │ │ │ │ └── tags-nav.vue │ │ │ │ └── user/ │ │ │ │ ├── index.js │ │ │ │ ├── user.less │ │ │ │ └── user.vue │ │ │ ├── index.js │ │ │ ├── main.less │ │ │ └── main.vue │ │ ├── markdown/ │ │ │ ├── index.js │ │ │ └── markdown.vue │ │ ├── parent-view/ │ │ │ ├── index.js │ │ │ └── parent-view.vue │ │ ├── paste-editor/ │ │ │ ├── index.js │ │ │ ├── paste-editor.less │ │ │ ├── paste-editor.vue │ │ │ └── plugins/ │ │ │ └── placeholder.js │ │ ├── split-pane/ │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── split.vue │ │ │ └── trigger.vue │ │ ├── tables/ │ │ │ ├── edit.vue │ │ │ ├── handle-btns.js │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── tables.vue │ │ └── tree-select/ │ │ ├── index.js │ │ ├── tree-select-tree.vue │ │ └── tree-select.vue │ ├── config/ │ │ └── index.js │ ├── directive/ │ │ ├── directives.js │ │ ├── index.js │ │ └── module/ │ │ ├── clipboard.js │ │ └── draggable.js │ ├── index.less │ ├── libs/ │ │ ├── api.request.js │ │ ├── axios.js │ │ ├── excel.js │ │ ├── render-dom.js │ │ ├── tools.js │ │ └── util.js │ ├── locale/ │ │ ├── index.js │ │ └── lang/ │ │ ├── en-US.js │ │ ├── zh-CN.js │ │ └── zh-TW.js │ ├── main.js │ ├── mock/ │ │ ├── data/ │ │ │ ├── org-data.js │ │ │ └── tree-select.js │ │ ├── data.js │ │ ├── index.js │ │ ├── login.js │ │ └── user.js │ ├── plugin/ │ │ ├── error-store/ │ │ │ └── index.js │ │ └── index.js │ ├── router/ │ │ ├── before-close.js │ │ ├── index.js │ │ └── routers.js │ ├── store/ │ │ ├── index.js │ │ └── module/ │ │ ├── app.js │ │ └── user.js │ └── view/ │ ├── argu-page/ │ │ ├── params.vue │ │ └── query.vue │ ├── components/ │ │ ├── count-to/ │ │ │ └── count-to.vue │ │ ├── cropper/ │ │ │ └── cropper.vue │ │ ├── drag-drawer/ │ │ │ └── index.vue │ │ ├── drag-list/ │ │ │ └── drag-list.vue │ │ ├── editor/ │ │ │ └── editor.vue │ │ ├── icons/ │ │ │ └── icons.vue │ │ ├── markdown/ │ │ │ └── markdown.vue │ │ ├── org-tree/ │ │ │ ├── components/ │ │ │ │ ├── org-view.vue │ │ │ │ └── zoom-controller.vue │ │ │ ├── index.less │ │ │ └── index.vue │ │ ├── split-pane/ │ │ │ └── split-pane.vue │ │ ├── tables/ │ │ │ └── tables.vue │ │ ├── tree-select/ │ │ │ └── index.vue │ │ └── tree-table/ │ │ └── index.vue │ ├── directive/ │ │ └── directive.vue │ ├── error-page/ │ │ ├── 401.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ ├── back-btn-group.vue │ │ ├── error-content.vue │ │ └── error.less │ ├── error-store/ │ │ └── error-store.vue │ ├── excel/ │ │ ├── common.less │ │ ├── export-excel.vue │ │ └── upload-excel.vue │ ├── i18n/ │ │ └── i18n-page.vue │ ├── join-page.vue │ ├── login/ │ │ ├── login.less │ │ └── login.vue │ ├── multilevel/ │ │ ├── level-2-1.vue │ │ ├── level-2-2/ │ │ │ ├── level-2-2-1.vue │ │ │ └── level-2-2-2.vue │ │ └── level-2-3.vue │ ├── single-page/ │ │ ├── error-logger.vue │ │ ├── home/ │ │ │ ├── example.vue │ │ │ ├── home.vue │ │ │ └── index.js │ │ └── message/ │ │ └── index.vue │ ├── tools-methods/ │ │ └── tools-methods.vue │ └── update/ │ ├── update-paste.vue │ └── update-table.vue ├── tests/ │ ├── e2e/ │ │ ├── .eslintrc │ │ ├── plugins/ │ │ │ └── index.js │ │ ├── specs/ │ │ │ └── test.js │ │ └── support/ │ │ ├── commands.js │ │ └── index.js │ └── unit/ │ ├── .eslintrc.js │ └── HelloWorld.spec.js └── vue.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "@vue/app" ] } ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 indent_style = space indent_size = 2 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .eslintignore ================================================ ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, 'extends': [ 'plugin:vue/essential', '@vue/standard' ], rules: { // allow async-await 'generator-star-spacing': 'off', // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }], 'no-undef': 'off', 'camelcase': 'off' }, parserOptions: { parser: 'babel-eslint' } } ================================================ FILE: .gitignore ================================================ .DS_Store node_modules /dist package-lock.json /tests/e2e/videos/ /tests/e2e/screenshots/ # local env files .env.local .env.*.local # Log files npm-debug.log* yarn-debug.log* yarn-error.log* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw* build/env.js ================================================ FILE: .postcssrc.js ================================================ module.exports = { plugins: { autoprefixer: {} } } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: stable script: npm run lint notifications: email: false ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 TalkingData Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

iView Admin

Vue.js 2.0 admin management system template based on iView.

[![](https://img.shields.io/github/release/iview/iview-admin.svg)](https://github.com/iview/iview-admin/releases) [![](https://img.shields.io/travis/iview/iview-admin.svg?style=flat-square)](https://travis-ci.org/iview/iview-admin) [![vue](https://img.shields.io/badge/vue-2.5.17-brightgreen.svg?style=flat-square)](https://github.com/vuejs/vue) [![iview ui](https://img.shields.io/badge/iview-3.2.2-brightgreen.svg?style=flat-square)](https://github.com/iview/iview) [![npm](https://img.shields.io/npm/l/express.svg)]() ## Introduction iView Admin is a front-end management background integration solution. It based on [Vue.js](https://github.com/vuejs/vue) and use the UI Toolkit [iView](https://github.com/iview/iview). - [Document](https://lison16.github.io/iview-admin-doc/) - [Preview](https://admin.iviewui.com/) - [Base template recommends using](https://github.com/iview/iview-admin/tree/template) ![image](https://file.iviewui.com/admin-dist/admin-preview.png) ## Features - Login / Logout - Permission Authentication - A list of filters - Permission to switch - i18n - Components - Rich Text Editor - Markdown Editor - City Cascader - Photos preview and edit - Draggable list - File upload - Digital gradient - split-pane - Form - The article published - Workflow - Table - Drag-and-drop sort - Searchable form - Table export data - Export to Csv file - Export to Xls file - Table to picture - Error Page - 403 - 404 - 500 - Router - Dynamic routing - With reference page - Theme - Shrink the sidebar - Tag navigation - Breadcrumb navigation - Full screen / exit full screen - Lock screen - The message center - Personal center ## Getting started ```bush # clone the project git clone https://github.com/iview/iview-admin.git // install dependencies npm install // develop npm run dev ``` ## Build ```bush npm run build ``` ## License [MIT](http://opensource.org/licenses/MIT) Copyright (c) 2016-present, TalkingData ================================================ FILE: cypress.json ================================================ { "pluginsFile": "tests/e2e/plugins/index.js" } ================================================ FILE: package.json ================================================ { "name": "iview-admin", "version": "2.0.0", "author": "Lison", "private": false, "scripts": { "dev": "vue-cli-service serve --open", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "test:unit": "vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e" }, "dependencies": { "axios": "^0.18.0", "clipboard": "^2.0.0", "codemirror": "^5.38.0", "countup": "^1.8.2", "cropperjs": "^1.2.2", "dayjs": "^1.7.7", "echarts": "^4.0.4", "html2canvas": "^1.0.0-alpha.12", "iview": "^3.2.2", "iview-area": "^1.5.17", "js-cookie": "^2.2.0", "simplemde": "^1.11.2", "sortablejs": "^1.7.0", "tree-table-vue": "^1.1.0", "v-org-tree": "^1.0.6", "vue": "^2.5.10", "vue-i18n": "^7.8.0", "vue-router": "^3.0.1", "vuedraggable": "^2.16.0", "vuex": "^3.0.1", "wangeditor": "^3.1.1", "xlsx": "^0.13.3" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.0.1", "@vue/cli-plugin-eslint": "^3.0.1", "@vue/cli-plugin-unit-mocha": "^3.0.1", "@vue/cli-service": "^3.0.1", "@vue/eslint-config-standard": "^3.0.0-beta.10", "@vue/test-utils": "^1.0.0-beta.10", "chai": "^4.1.2", "eslint-plugin-cypress": "^2.0.1", "less": "^2.7.3", "less-loader": "^4.0.5", "lint-staged": "^6.0.0", "mockjs": "^1.0.1-beta3", "vue-template-compiler": "^2.5.13" }, "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ], "gitHooks": { "pre-commit": "lint-staged" }, "lint-staged": { "*.js": [ "vue-cli-service lint", "git add" ], "*.vue": [ "vue-cli-service lint", "git add" ] } } ================================================ FILE: public/index.html ================================================
================================================ FILE: src/App.vue ================================================ ================================================ FILE: src/api/data.js ================================================ import axios from '@/libs/api.request' export const getTableData = () => { return axios.request({ url: 'get_table_data', method: 'get' }) } export const getDragList = () => { return axios.request({ url: 'get_drag_list', method: 'get' }) } export const errorReq = () => { return axios.request({ url: 'error_url', method: 'post' }) } export const saveErrorLogger = info => { return axios.request({ url: 'save_error_logger', data: info, method: 'post' }) } export const uploadImg = formData => { return axios.request({ url: 'image/upload', data: formData }) } export const getOrgData = () => { return axios.request({ url: 'get_org_data', method: 'get' }) } export const getTreeSelectData = () => { return axios.request({ url: 'get_tree_select_data', method: 'get' }) } ================================================ FILE: src/api/routers.js ================================================ import axios from '@/libs/api.request' export const getRouterReq = (access) => { return axios.request({ url: 'get_router', params: { access }, method: 'get' }) } ================================================ FILE: src/api/user.js ================================================ import axios from '@/libs/api.request' export const login = ({ userName, password }) => { const data = { userName, password } return axios.request({ url: 'login', data, method: 'post' }) } export const getUserInfo = (token) => { return axios.request({ url: 'get_info', params: { token }, method: 'get' }) } export const logout = (token) => { return axios.request({ url: 'logout', method: 'post' }) } export const getUnreadCount = () => { return axios.request({ url: 'message/count', method: 'get' }) } export const getMessage = () => { return axios.request({ url: 'message/init', method: 'get' }) } export const getContentByMsgId = msg_id => { return axios.request({ url: 'message/content', method: 'get', params: { msg_id } }) } export const hasRead = msg_id => { return axios.request({ url: 'message/has_read', method: 'post', data: { msg_id } }) } export const removeReaded = msg_id => { return axios.request({ url: 'message/remove_readed', method: 'post', data: { msg_id } }) } export const restoreTrash = msg_id => { return axios.request({ url: 'message/restore', method: 'post', data: { msg_id } }) } ================================================ FILE: src/assets/icons/iconfont.css ================================================ @font-face {font-family: "iconfont"; src: url('iconfont.eot?t=1541579316141'); /* IE9*/ src: url('iconfont.eot?t=1541579316141#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAiEAAsAAAAADmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8eUnXY21hcAAAAYAAAACjAAACLi+YJuBnbHlmAAACJAAABAgAAAcg4dRWHmhlYWQAAAYsAAAAMQAAADYTL8piaGhlYQAABmAAAAAgAAAAJAfdA4xobXR4AAAGgAAAABQAAAAsLAD//2xvY2EAAAaUAAAAGAAAABgImgpGbWF4cAAABqwAAAAfAAAAIAEcAG5uYW1lAAAGzAAAAUUAAAJtPlT+fXBvc3QAAAgUAAAAbgAAAI54roygeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMTx/ytzwv4EhhrmBoRkozAiSAwDuUwzMeJzlkUEKwkAMRd/YabXFhQvxFF6qPYPrUujGY7jyIr1JoZNjtMnEhag3MOEN5MMk8D9QAoVyVSKEJwGrh6oh6wVN1iM3nc+cVImJVKdOehlklElmWdYVstp+ql8VdIv15a1NLW0zFXsO7Kjz3erH/3+rY37vr6kxnx1LKNWOJZlaxxJNnWOpSu+ot8jgqMvI6KjfyOSo88jsaAbI4tBsig89rQB4nLVUTWwbRRSeNzO767i2g7N/FP9s7MRrE5ON4/V6rSZyU0PiINSSNImES4IUoapWz6hEiqiMBDQqEojkAkiFStyKRC+9VSoFCeUEyqESVUAqEkcu3OAQb3hrJxAXwSGI3X0/szPz5vvm2x0i7O/vf8IJe5VkSJnUyUtklRBQJE1VIjRtUafkmk6pSu2ipleh4+xikkKxSksWTUeo8m8NoagpYtoslTmxrLl37z64e33esuJjU8P5Wd262LxoPVnPZ06Pxfe+C0YjkhSJygPhQCA8ABPOykwuN7NyuRvgUnAgLEnhATkaCQQiUe/7XKUyV6nQz+t2o7l66+rs7NVbq82GXTdrdjxjRGU5amTids2bUDMFtzCsqsMYMqr3IDY6OT05GjsI8Exv/6CSkOWEQigh+y3clxY5QVTcEZFIGtHLxDUJs6WsHR1y9SFKdr1HggCp3V1ICYL36OOpVmvKN9bC1u6R3vZ0qwWtVovgJfqOfUvfIYxIWL+fyETHNVJqSkIT1JTjW8ZWh3yDJDz0ctvsyt51etvrg9/QHhqGlzMM+vbmizPnDWPLMNbW19e7tffvsBzL99aWEfBRY46t+tbe3PypXv/IMDYN43WsQBe9HL2NC33RuxABrPsG+xH3o4bVRE2KgCRqulbWNf8W/UYVHM129aKra24VshZkq+CWD/Oy6Xt8cGYEthgHVlVliCfynAlqjo6oysTKlYUAD4docMI5/1ZioN+GwZNBcTwWUmTdBUqhTwX29QebXzF4An4JJMzwfMl+WQ01+IlQZVR4yhie53ycA16pOI/ODiYNGK4MChdCgXNnX5gIJXPCSYnf2OF850aQ+zJIyOs+u8+mMO8jQdwtg1TIWVRjKAnFcslMi8KfGUPoSUCergUyUk77dMyS69Ms6tijKZKYwUGKbpfdzu+iYeZYAHMFiOVi+MD7h9mb99qC0L7X8c+XatMfTj97KZ5IxJt/pd43tYYQKEjAnXMOB6kQEBrwg+LPjindAPOHNdC3q3ait0I3/ZIunZEARLNYNEUA6czSP3N/7j9wz6ZESdX0VNl1zGNS/szbQaQSIGk4DtVPcZf8AgXpf9A2OyTit5s2syZmand46bhEe2WtodLHkvaoqtTXuXN2/c42WADP9HGfbUcUW7JgqHss4xHtlMys679FqUomdP9VJBQBdnlPABBubpuNwqnmQj6/0HwNQzKxDUJFgKiXurBG6dqFjmeBzsvtRPJgGIZThYa5fdOvsReOticPh6JHHXxsv7ItJpOniYPYsmZ/x0QD/o5P105DeQwF6MH33ogoLi+KQp7zpY3HQV5bFMURzheXeds7gpP+jKNXljjHuYvXHke7cdCxLLZf6YX7B63UcCV4nGNgZGBgAOKAN2ZR8fw2Xxm4WRhA4AbHYRMY/f///1oWBuYGIJeDgQkkCgAvWgs2AAAAeJxjYGRgYG7438AQw8Lw/z8DAwsDA1AEBXADAHXiBHJ4nGNhYGBgYfj/nwVM48cATwECKwAAAAAAjAC6AOgBFAGAAf4CbgLqAzgDkHicY2BkYGDgZkhiYGcAASYg5gJCBob/YD4DABOmAYsAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbYhdDoIwEAb3a6k/YIIX8VArWewmdJFWJOnpJTG+OQ+TzJCjLy39p4ODR4OAA4444YwWHS7U3IVzn6Voldtb8ksHnvohrlqjjmw1rmzXsvdT7fEbblnCmOfNfJIYStJJfGIL27yb6AOCGR89AAA=') format('woff'), url('iconfont.ttf?t=1541579316141') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ url('iconfont.svg?t=1541579316141#iconfont') format('svg'); /* iOS 4.1- */ } .iconfont { font-family:"iconfont" !important; font-size:16px; font-style:normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-bear:before { content: "\e600"; } .icon-resize-vertical:before { content: "\e7c3"; } .icon-chuizhifanzhuan:before { content: "\e661"; } .icon-shuipingfanzhuan:before { content: "\e662"; } .icon-qq:before { content: "\e609"; } .icon-frown:before { content: "\e77e"; } .icon-meh:before { content: "\e780"; } .icon-smile:before { content: "\e783"; } .icon-man:before { content: "\e7e2"; } .icon-woman:before { content: "\e7e5"; } ================================================ FILE: src/components/charts/bar.vue ================================================ ================================================ FILE: src/components/charts/index.js ================================================ import ChartPie from './pie.vue' import ChartBar from './bar.vue' export { ChartPie, ChartBar } ================================================ FILE: src/components/charts/pie.vue ================================================ ================================================ FILE: src/components/charts/theme.json ================================================ { "color": [ "#2d8cf0", "#19be6b", "#ff9900", "#E46CBB", "#9A66E4", "#ed3f14" ], "backgroundColor": "rgba(0,0,0,0)", "textStyle": {}, "title": { "textStyle": { "color": "#516b91" }, "subtextStyle": { "color": "#93b7e3" } }, "line": { "itemStyle": { "normal": { "borderWidth": "2" } }, "lineStyle": { "normal": { "width": "2" } }, "symbolSize": "6", "symbol": "emptyCircle", "smooth": true }, "radar": { "itemStyle": { "normal": { "borderWidth": "2" } }, "lineStyle": { "normal": { "width": "2" } }, "symbolSize": "6", "symbol": "emptyCircle", "smooth": true }, "bar": { "itemStyle": { "normal": { "barBorderWidth": 0, "barBorderColor": "#ccc" }, "emphasis": { "barBorderWidth": 0, "barBorderColor": "#ccc" } } }, "pie": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "scatter": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "boxplot": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "parallel": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "sankey": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "funnel": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "gauge": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" }, "emphasis": { "borderWidth": 0, "borderColor": "#ccc" } } }, "candlestick": { "itemStyle": { "normal": { "color": "#edafda", "color0": "transparent", "borderColor": "#d680bc", "borderColor0": "#8fd3e8", "borderWidth": "2" } } }, "graph": { "itemStyle": { "normal": { "borderWidth": 0, "borderColor": "#ccc" } }, "lineStyle": { "normal": { "width": 1, "color": "#aaa" } }, "symbolSize": "6", "symbol": "emptyCircle", "smooth": true, "color": [ "#2d8cf0", "#19be6b", "#f5ae4a", "#9189d5", "#56cae2", "#cbb0e3" ], "label": { "normal": { "textStyle": { "color": "#eee" } } } }, "map": { "itemStyle": { "normal": { "areaColor": "#f3f3f3", "borderColor": "#516b91", "borderWidth": 0.5 }, "emphasis": { "areaColor": "rgba(165,231,240,1)", "borderColor": "#516b91", "borderWidth": 1 } }, "label": { "normal": { "textStyle": { "color": "#000" } }, "emphasis": { "textStyle": { "color": "rgb(81,107,145)" } } } }, "geo": { "itemStyle": { "normal": { "areaColor": "#f3f3f3", "borderColor": "#516b91", "borderWidth": 0.5 }, "emphasis": { "areaColor": "rgba(165,231,240,1)", "borderColor": "#516b91", "borderWidth": 1 } }, "label": { "normal": { "textStyle": { "color": "#000" } }, "emphasis": { "textStyle": { "color": "rgb(81,107,145)" } } } }, "categoryAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "valueAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "logAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "timeAxis": { "axisLine": { "show": true, "lineStyle": { "color": "#cccccc" } }, "axisTick": { "show": false, "lineStyle": { "color": "#333" } }, "axisLabel": { "show": true, "textStyle": { "color": "#999999" } }, "splitLine": { "show": true, "lineStyle": { "color": [ "#eeeeee" ] } }, "splitArea": { "show": false, "areaStyle": { "color": [ "rgba(250,250,250,0.05)", "rgba(200,200,200,0.02)" ] } } }, "toolbox": { "iconStyle": { "normal": { "borderColor": "#999" }, "emphasis": { "borderColor": "#666" } } }, "legend": { "textStyle": { "color": "#999999" } }, "tooltip": { "axisPointer": { "lineStyle": { "color": "#ccc", "width": 1 }, "crossStyle": { "color": "#ccc", "width": 1 } } }, "timeline": { "lineStyle": { "color": "#8fd3e8", "width": 1 }, "itemStyle": { "normal": { "color": "#8fd3e8", "borderWidth": 1 }, "emphasis": { "color": "#8fd3e8" } }, "controlStyle": { "normal": { "color": "#8fd3e8", "borderColor": "#8fd3e8", "borderWidth": 0.5 }, "emphasis": { "color": "#8fd3e8", "borderColor": "#8fd3e8", "borderWidth": 0.5 } }, "checkpointStyle": { "color": "#8fd3e8", "borderColor": "rgba(138,124,168,0.37)" }, "label": { "normal": { "textStyle": { "color": "#8fd3e8" } }, "emphasis": { "textStyle": { "color": "#8fd3e8" } } } }, "visualMap": { "color": [ "#516b91", "#59c4e6", "#a5e7f0" ] }, "dataZoom": { "backgroundColor": "rgba(0,0,0,0)", "dataBackgroundColor": "rgba(255,255,255,0.3)", "fillerColor": "rgba(167,183,204,0.4)", "handleColor": "#a7b7cc", "handleSize": "100%", "textStyle": { "color": "#333" } }, "markPoint": { "label": { "normal": { "textStyle": { "color": "#eee" } }, "emphasis": { "textStyle": { "color": "#eee" } } } } } ================================================ FILE: src/components/common/common.less ================================================ .no-select{ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } ================================================ FILE: src/components/common/util.js ================================================ export const showTitle = (item, vm) => { return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) } ================================================ FILE: src/components/common-icon/common-icon.vue ================================================ ================================================ FILE: src/components/common-icon/index.js ================================================ import CommonIcon from './common-icon.vue' export default CommonIcon ================================================ FILE: src/components/count-to/count-to.vue ================================================ ================================================ FILE: src/components/count-to/index.js ================================================ import countTo from './count-to.vue' export default countTo ================================================ FILE: src/components/count-to/index.less ================================================ @prefix: ~"count-to"; .@{prefix}-wrapper{ .content-outer{ display: inline-block; .@{prefix}-unit-text{ font-style: normal; } } } ================================================ FILE: src/components/cropper/index.js ================================================ import Cropper from './index.vue' export default Cropper ================================================ FILE: src/components/cropper/index.less ================================================ .bg{ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC") } .cropper-wrapper{ width: 600px; height: 340px; .img-box{ height: 340px; width: 430px; border: 1px solid #ebebeb; display: inline-block; .bg; img{ max-width: 100%; display: block; } } .right-con{ display: inline-block; width: 170px; vertical-align: top; box-sizing: border-box; padding: 0 10px; .preview-box{ height: 150px !important; width: 100% !important; overflow: hidden; border: 1px solid #ebebeb; .bg; } .button-box{ padding: 10px 0 0; } } } ================================================ FILE: src/components/cropper/index.vue ================================================ ================================================ FILE: src/components/drag-drawer/drag-drawer-trigger.vue ================================================ ================================================ FILE: src/components/drag-drawer/drag-drawer.vue ================================================ ================================================ FILE: src/components/drag-drawer/index.js ================================================ import DragDrawer from './drag-drawer.vue' export default DragDrawer ================================================ FILE: src/components/drag-drawer/index.less ================================================ @prefix: ~"drag-drawer"; @drag-drawer-trigger-height: 100px; @drag-drawer-trigger-width: 8px; .@{prefix}-wrapper{ &.no-select{ user-select: none; } &.pointer-events-none{ pointer-events: none; & .@{prefix}-trigger-wrapper{ pointer-events: all; } } .ivu-drawer{ &-header{ overflow: hidden !important; box-sizing: border-box; } &-body{ padding: 0; overflow: visible; position: static; display: flex; flex-direction: column; } } .@{prefix}-body-wrapper{ width: 100%; height: 100%; padding: 16px; overflow: auto; } .@{prefix}-trigger-wrapper{ top: 0; height: 100%; width: 0; .@{prefix}-move-trigger{ position: absolute; top: 50%; height: @drag-drawer-trigger-height; width: @drag-drawer-trigger-width; background: rgb(243, 243, 243); transform: translate(-50%, -50%); border-radius: ~"4px / 6px"; box-shadow: 0 0 1px 1px rgba(0, 0, 0, .2); line-height: @drag-drawer-trigger-height; cursor: col-resize; &-point{ display: inline-block; width: 50%; transform: translateX(50%); i{ display: block; border-bottom: 1px solid rgb(192, 192, 192); padding-bottom: 2px; } } } } .@{prefix}-footer{ flex-grow: 1; width: 100%; bottom: 0; left: 0; border-top: 1px solid #e8e8e8; padding: 10px 16px; background: #fff; } } ================================================ FILE: src/components/drag-drawer/mixin.js ================================================ export default { data () { return { prefix: 'drag-drawer' } } } ================================================ FILE: src/components/drag-list/drag-list.vue ================================================ ================================================ FILE: src/components/drag-list/index.js ================================================ import DragList from './drag-list.vue' export default DragList ================================================ FILE: src/components/editor/editor.vue ================================================ ================================================ FILE: src/components/editor/index.js ================================================ import Editor from './editor.vue' export default Editor ================================================ FILE: src/components/icons/icons.vue ================================================ ================================================ FILE: src/components/icons/index.js ================================================ import Icons from './icons.vue' export default Icons ================================================ FILE: src/components/info-card/index.js ================================================ import InforCard from './infor-card.vue' export default InforCard ================================================ FILE: src/components/info-card/infor-card.vue ================================================ ================================================ FILE: src/components/login-form/index.js ================================================ import LoginForm from './login-form.vue' export default LoginForm ================================================ FILE: src/components/login-form/login-form.vue ================================================ ================================================ FILE: src/components/main/components/a-back-top/index.js ================================================ import ABackTop from './index.vue' export default ABackTop ================================================ FILE: src/components/main/components/a-back-top/index.vue ================================================ ================================================ FILE: src/components/main/components/error-store/error-store.vue ================================================ ================================================ FILE: src/components/main/components/error-store/index.js ================================================ import ErrorStore from './error-store.vue' export default ErrorStore ================================================ FILE: src/components/main/components/fullscreen/fullscreen.vue ================================================ ================================================ FILE: src/components/main/components/fullscreen/index.js ================================================ import Fullscreen from './fullscreen.vue' export default Fullscreen ================================================ FILE: src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less ================================================ .custom-bread-crumb{ display: inline-block; vertical-align: top; } ================================================ FILE: src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue ================================================ ================================================ FILE: src/components/main/components/header-bar/custom-bread-crumb/index.js ================================================ import customBreadCrumb from './custom-bread-crumb.vue' export default customBreadCrumb ================================================ FILE: src/components/main/components/header-bar/header-bar.less ================================================ .header-bar{ width: 100%; height: 100%; position: relative; .custom-content-con{ float: right; height: auto; padding-right: 20px; line-height: 64px; & > *{ float: right; } } } ================================================ FILE: src/components/main/components/header-bar/header-bar.vue ================================================ ================================================ FILE: src/components/main/components/header-bar/index.js ================================================ import HeaderBar from './header-bar' export default HeaderBar ================================================ FILE: src/components/main/components/header-bar/sider-trigger/index.js ================================================ import siderTrigger from './sider-trigger.vue' export default siderTrigger ================================================ FILE: src/components/main/components/header-bar/sider-trigger/sider-trigger.less ================================================ .trans{ transition: transform .2s ease; } @size: 40px; .sider-trigger-a{ padding: 6px; width: @size; height: @size; display: inline-block; text-align: center; color: #5c6b77; margin-top: 12px; i{ .trans; vertical-align: top; } &.collapsed i{ transform: rotateZ(90deg); .trans; } } ================================================ FILE: src/components/main/components/header-bar/sider-trigger/sider-trigger.vue ================================================ ================================================ FILE: src/components/main/components/language/index.js ================================================ import Language from './language.vue' export default Language ================================================ FILE: src/components/main/components/language/language.vue ================================================ ================================================ FILE: src/components/main/components/side-menu/collapsed-menu.vue ================================================ ================================================ FILE: src/components/main/components/side-menu/index.js ================================================ import SideMenu from './side-menu.vue' export default SideMenu ================================================ FILE: src/components/main/components/side-menu/item-mixin.js ================================================ export default { props: { parentItem: { type: Object, default: () => {} }, theme: String, iconSize: Number }, computed: { parentName () { return this.parentItem.name }, children () { return this.parentItem.children }, textColor () { return this.theme === 'dark' ? '#fff' : '#495060' } } } ================================================ FILE: src/components/main/components/side-menu/mixin.js ================================================ import CommonIcon from '_c/common-icon' import { showTitle } from '@/libs/util' export default { components: { CommonIcon }, methods: { showTitle (item) { return showTitle(item, this) }, showChildren (item) { return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways)) }, getNameOrHref (item, children0) { return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name) } } } ================================================ FILE: src/components/main/components/side-menu/side-menu-item.vue ================================================ ================================================ FILE: src/components/main/components/side-menu/side-menu.less ================================================ .side-menu-wrapper{ user-select: none; .menu-collapsed{ padding-top: 10px; .ivu-dropdown{ width: 100%; .ivu-dropdown-rel a{ width: 100%; } } .ivu-tooltip{ width: 100%; .ivu-tooltip-rel{ width: 100%; } .ivu-tooltip-popper .ivu-tooltip-content{ .ivu-tooltip-arrow{ border-right-color: #fff; } .ivu-tooltip-inner{ background: #fff; color: #495060; } } } } a.drop-menu-a{ display: inline-block; padding: 6px 15px; width: 100%; text-align: center; color: #495060; } } .menu-title{ padding-left: 6px; } ================================================ FILE: src/components/main/components/side-menu/side-menu.vue ================================================ ================================================ FILE: src/components/main/components/tags-nav/index.js ================================================ import TagsNav from './tags-nav.vue' export default TagsNav ================================================ FILE: src/components/main/components/tags-nav/tags-nav.less ================================================ .no-select{ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .size{ width: 100%; height: 100%; } .tags-nav{ position: relative; border-top: 1px solid #F0F0F0; border-bottom: 1px solid #F0F0F0; .no-select; .size; .close-con{ position: absolute; right: 0; top: 0; height: 100%; width: 32px; background: #fff; text-align: center; z-index: 10; } .btn-con{ position: absolute; top: 0px; height: 100%; background: #fff; padding-top: 3px; z-index: 10; button{ padding: 6px 4px; line-height: 14px; text-align: center; } &.left-btn{ left: 0px; } &.right-btn{ right: 32px; border-right: 1px solid #F0F0F0; } } .scroll-outer{ position: absolute; left: 28px; right: 61px; top: 0; bottom: 0; box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset; .scroll-body{ height: ~"calc(100% - 1px)"; display: inline-block; padding: 1px 4px 0; position: absolute; overflow: visible; white-space: nowrap; transition: left .3s ease; .ivu-tag-dot-inner{ transition: background .2s ease; } } } .contextmenu { position: absolute; margin: 0; padding: 5px 0; background: #fff; z-index: 1000; list-style-type: none; border-radius: 4px; box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .1); li { margin: 0; padding: 5px 15px; cursor: pointer; &:hover { background: #eee; } } } } ================================================ FILE: src/components/main/components/tags-nav/tags-nav.vue ================================================ ================================================ FILE: src/components/main/components/user/index.js ================================================ import User from './user.vue' export default User ================================================ FILE: src/components/main/components/user/user.less ================================================ .user{ &-avatar-dropdown{ cursor: pointer; display: inline-block; // height: 64px; vertical-align: middle; // line-height: 64px; .ivu-badge-dot{ top: 16px; } } } ================================================ FILE: src/components/main/components/user/user.vue ================================================ ================================================ FILE: src/components/main/index.js ================================================ import Main from './main.vue' export default Main ================================================ FILE: src/components/main/main.less ================================================ .main{ .logo-con{ height: 64px; padding: 10px; img{ height: 44px; width: auto; display: block; margin: 0 auto; } } .header-con{ background: #fff; padding: 0 20px; width: 100%; } .main-layout-con{ height: 100%; overflow: hidden; } .main-content-con{ height: ~"calc(100% - 60px)"; overflow: hidden; } .tag-nav-wrapper{ padding: 0; height:40px; background:#F0F0F0; } .content-wrapper{ padding: 18px; height: ~"calc(100% - 80px)"; overflow: auto; } .left-sider{ .ivu-layout-sider-children{ overflow-y: scroll; margin-right: -18px; } } } .ivu-menu-item > i{ margin-right: 12px !important; } .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { margin-right: 8px !important; } .collased-menu-dropdown{ width: 100%; margin: 0; line-height: normal; padding: 7px 0 6px 16px; clear: both; font-size: 12px !important; white-space: nowrap; list-style: none; cursor: pointer; transition: background 0.2s ease-in-out; &:hover{ background: rgba(100, 100, 100, 0.1); } & * { color: #515a6e; } .ivu-menu-item > i{ margin-right: 12px !important; } .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { margin-right: 8px !important; } } .ivu-select-dropdown.ivu-dropdown-transfer{ max-height: 400px; } ================================================ FILE: src/components/main/main.vue ================================================ ================================================ FILE: src/components/markdown/index.js ================================================ import MarkdownEditor from './markdown.vue' export default MarkdownEditor ================================================ FILE: src/components/markdown/markdown.vue ================================================ ================================================ FILE: src/components/parent-view/index.js ================================================ import ParentView from './parent-view.vue' export default ParentView ================================================ FILE: src/components/parent-view/parent-view.vue ================================================ ================================================ FILE: src/components/paste-editor/index.js ================================================ import PasteEditor from './paste-editor.vue' export default PasteEditor ================================================ FILE: src/components/paste-editor/paste-editor.less ================================================ .paste-editor-wrapper{ width: 100%; height: 100%; border: 1px dashed gainsboro; textarea.textarea-el{ width: 100%; height: 100%; } .CodeMirror{ height: 100%; padding: 0; .CodeMirror-code div .CodeMirror-line > span > span.cm-tab{ &::after{ content: '→'; color: #BFBFBF; } } } .first-row{ font-weight: 700; font-size: 14px; } .incorrect-row{ background: #F5CBD1; } } ================================================ FILE: src/components/paste-editor/paste-editor.vue ================================================ ================================================ FILE: src/components/paste-editor/plugins/placeholder.js ================================================ export default (codemirror) => { (function (mod) { mod(codemirror) })(function (CodeMirror) { CodeMirror.defineOption('placeholder', '', function (cm, val, old) { var prev = old && old !== CodeMirror.Init if (val && !prev) { cm.on('blur', onBlur) cm.on('change', onChange) cm.on('swapDoc', onChange) onChange(cm) } else if (!val && prev) { cm.off('blur', onBlur) cm.off('change', onChange) cm.off('swapDoc', onChange) clearPlaceholder(cm) var wrapper = cm.getWrapperElement() wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') } if (val && !cm.hasFocus()) onBlur(cm) }) function clearPlaceholder (cm) { if (cm.state.placeholder) { cm.state.placeholder.parentNode.removeChild(cm.state.placeholder) cm.state.placeholder = null } } function setPlaceholder (cm) { clearPlaceholder(cm) var elt = cm.state.placeholder = document.createElement('pre') elt.style.cssText = 'height: 0; overflow: visible; color: #80848f;' elt.style.direction = cm.getOption('direction') elt.className = 'CodeMirror-placeholder' var placeHolder = cm.getOption('placeholder') if (typeof placeHolder === 'string') placeHolder = document.createTextNode(placeHolder) elt.appendChild(placeHolder) cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild) } function onBlur (cm) { if (isEmpty(cm)) setPlaceholder(cm) } function onChange (cm) { let wrapper = cm.getWrapperElement() let empty = isEmpty(cm) wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') + (empty ? ' CodeMirror-empty' : '') if (empty) setPlaceholder(cm) else clearPlaceholder(cm) } function isEmpty (cm) { return (cm.lineCount() === 1) && (cm.getLine(0) === '') } }) } ================================================ FILE: src/components/split-pane/index.js ================================================ import Split from './split.vue' export default Split ================================================ FILE: src/components/split-pane/index.less ================================================ @split-prefix-cls: ~"ivu-split"; @box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.4); @trigger-bar-background: rgba(23, 35, 61, 0.25); @trigger-background: #F8F8F9; @trigger-width: 6px; @trigger-bar-width: 4px; @trigger-bar-offset: (@trigger-width - @trigger-bar-width) / 2; @trigger-bar-interval: 3px; @trigger-bar-weight: 1px; @trigger-bar-con-height: (@trigger-bar-weight + @trigger-bar-interval) * 8; .@{split-prefix-cls}{ &-wrapper{ position: relative; width: 100%; height: 100%; } &-pane{ position: absolute; &.left-pane, &.right-pane{ top: 0px; bottom: 0px; } &.left-pane{ left: 0px; } &.right-pane{ right: 0px; } &.top-pane, &.bottom-pane{ left: 0px; right: 0px; } &.top-pane{ top: 0px; } &.bottom-pane{ bottom: 0px; } } &-trigger{ &-con{ position: absolute; transform: translate(-50%, -50%); z-index: 10; } &-bar-con{ position: absolute; overflow: hidden; &.vertical{ left: @trigger-bar-offset; top: 50%; height: @trigger-bar-con-height; transform: translate(0, -50%); } &.horizontal{ left: 50%; top: @trigger-bar-offset; width: @trigger-bar-con-height; transform: translate(-50%, 0); } } &-vertical{ width: @trigger-width; height: 100%; background: @trigger-background; box-shadow: @box-shadow; cursor: col-resize; .@{split-prefix-cls}-trigger-bar{ width: @trigger-bar-width; height: 1px; background: @trigger-bar-background; float: left; margin-top: @trigger-bar-interval; } } &-horizontal{ height: @trigger-width; width: 100%; background: @trigger-background; box-shadow: @box-shadow; cursor: row-resize; .@{split-prefix-cls}-trigger-bar{ height: @trigger-bar-width; width: 1px; background: @trigger-bar-background; float: left; margin-right: @trigger-bar-interval; } } } &-horizontal{ .@{split-prefix-cls}-trigger-con{ top: 50%; height: 100%; width: 0; } } &-vertical{ .@{split-prefix-cls}-trigger-con{ left: 50%; height: 0; width: 100%; } } .no-select{ -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } } ================================================ FILE: src/components/split-pane/split.vue ================================================ ================================================ FILE: src/components/split-pane/trigger.vue ================================================ ================================================ FILE: src/components/tables/edit.vue ================================================ ================================================ FILE: src/components/tables/handle-btns.js ================================================ const btns = { delete: (h, params, vm) => { return h('Poptip', { props: { confirm: true, title: '你确定要删除吗?' }, on: { 'on-ok': () => { vm.$emit('on-delete', params) vm.$emit('input', params.tableData.filter((item, index) => index !== params.row.initRowIndex)) } } }, [ h('Button', { props: { type: 'text', ghost: true } }, [ h('Icon', { props: { type: 'md-trash', size: 18, color: '#000000' } }) ]) ]) } } export default btns ================================================ FILE: src/components/tables/index.js ================================================ import Tables from './tables.vue' export default Tables ================================================ FILE: src/components/tables/index.less ================================================ .search-con{ padding: 10px 0; .search{ &-col{ display: inline-block; width: 200px; } &-input{ display: inline-block; width: 200px; margin-left: 2px; } &-btn{ margin-left: 2px; } } } ================================================ FILE: src/components/tables/tables.vue ================================================ ================================================ FILE: src/components/tree-select/index.js ================================================ export { default } from './tree-select.vue' ================================================ FILE: src/components/tree-select/tree-select-tree.vue ================================================ ================================================ FILE: src/components/tree-select/tree-select.vue ================================================ ================================================ FILE: src/config/index.js ================================================ export default { /** * @description 配置显示在浏览器标签的title */ title: 'iView-admin', /** * @description token在Cookie中存储的天数,默认1天 */ cookieExpires: 1, /** * @description 是否使用国际化,默认为false * 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'} * 用来在菜单中显示文字 */ useI18n: true, /** * @description api请求基础路径 */ baseUrl: { dev: 'https://www.easy-mock.com/mock/5add9213ce4d0e69998a6f51/iview-admin/', pro: 'https://produce.com' }, /** * @description 默认打开的首页的路由name值,默认为home */ homeName: 'home', /** * @description 需要加载的插件 */ plugin: { 'error-store': { showInHeader: true, // 设为false后不会在顶部显示错误日志徽标 developmentOff: true // 设为true后在开发环境不会收集错误信息,方便开发中排查错误 } } } ================================================ FILE: src/directive/directives.js ================================================ import draggable from './module/draggable' import clipboard from './module/clipboard' const directives = { draggable, clipboard } export default directives ================================================ FILE: src/directive/index.js ================================================ import directive from './directives' const importDirective = Vue => { /** * 拖拽指令 v-draggable="options" * options = { * trigger: /这里传入作为拖拽触发器的CSS选择器/, * body: /这里传入需要移动容器的CSS选择器/, * recover: /拖动结束之后是否恢复到原来的位置/ * } */ Vue.directive('draggable', directive.draggable) /** * clipboard指令 v-draggable="options" * options = { * value: /在输入框中使用v-model绑定的值/, * success: /复制成功后的回调/, * error: /复制失败后的回调/ * } */ Vue.directive('clipboard', directive.clipboard) } export default importDirective ================================================ FILE: src/directive/module/clipboard.js ================================================ import Clipboard from 'clipboard' export default { bind: (el, binding) => { const clipboard = new Clipboard(el, { text: () => binding.value.value }) el.__success_callback__ = binding.value.success el.__error_callback__ = binding.value.error clipboard.on('success', e => { const callback = el.__success_callback__ callback && callback(e) }) clipboard.on('error', e => { const callback = el.__error_callback__ callback && callback(e) }) el.__clipboard__ = clipboard }, update: (el, binding) => { el.__clipboard__.text = () => binding.value.value el.__success_callback__ = binding.value.success el.__error_callback__ = binding.value.error }, unbind: (el, binding) => { delete el.__success_callback__ delete el.__error_callback__ el.__clipboard__.destroy() delete el.__clipboard__ } } ================================================ FILE: src/directive/module/draggable.js ================================================ import { on } from '@/libs/tools' export default { inserted: (el, binding, vnode) => { let triggerDom = document.querySelector(binding.value.trigger) triggerDom.style.cursor = 'move' let bodyDom = document.querySelector(binding.value.body) let pageX = 0 let pageY = 0 let transformX = 0 let transformY = 0 let canMove = false const handleMousedown = e => { let transform = /\(.*\)/.exec(bodyDom.style.transform) if (transform) { transform = transform[0].slice(1, transform[0].length - 1) let splitxy = transform.split('px, ') transformX = parseFloat(splitxy[0]) transformY = parseFloat(splitxy[1].split('px')[0]) } pageX = e.pageX pageY = e.pageY canMove = true } const handleMousemove = e => { let xOffset = e.pageX - pageX + transformX let yOffset = e.pageY - pageY + transformY if (canMove) bodyDom.style.transform = `translate(${xOffset}px, ${yOffset}px)` } const handleMouseup = e => { canMove = false } on(triggerDom, 'mousedown', handleMousedown) on(document, 'mousemove', handleMousemove) on(document, 'mouseup', handleMouseup) }, update: (el, binding, vnode) => { if (!binding.value.recover) return let bodyDom = document.querySelector(binding.value.body) bodyDom.style.transform = '' } } ================================================ FILE: src/index.less ================================================ @import '~iview/src/styles/index.less'; @menu-dark-title: #001529; @menu-dark-active-bg: #000c17; @layout-sider-background: #001529; ================================================ FILE: src/libs/api.request.js ================================================ import HttpRequest from '@/libs/axios' import config from '@/config' const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro const axios = new HttpRequest(baseUrl) export default axios ================================================ FILE: src/libs/axios.js ================================================ import axios from 'axios' import store from '@/store' // import { Spin } from 'iview' const addErrorLog = errorInfo => { const { statusText, status, request: { responseURL } } = errorInfo let info = { type: 'ajax', code: status, mes: statusText, url: responseURL } if (!responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info) } class HttpRequest { constructor (baseUrl = baseURL) { this.baseUrl = baseUrl this.queue = {} } getInsideConfig () { const config = { baseURL: this.baseUrl, headers: { // } } return config } destroy (url) { delete this.queue[url] if (!Object.keys(this.queue).length) { // Spin.hide() } } interceptors (instance, url) { // 请求拦截 instance.interceptors.request.use(config => { // 添加全局的loading... if (!Object.keys(this.queue).length) { // Spin.show() // 不建议开启,因为界面不友好 } this.queue[url] = true return config }, error => { return Promise.reject(error) }) // 响应拦截 instance.interceptors.response.use(res => { this.destroy(url) const { data, status } = res return { data, status } }, error => { this.destroy(url) let errorInfo = error.response if (!errorInfo) { const { request: { statusText, status }, config } = JSON.parse(JSON.stringify(error)) errorInfo = { statusText, status, request: { responseURL: config.url } } } addErrorLog(errorInfo) return Promise.reject(error) }) } request (options) { const instance = axios.create() options = Object.assign(this.getInsideConfig(), options) this.interceptors(instance, options.url) return instance(options) } } export default HttpRequest ================================================ FILE: src/libs/excel.js ================================================ /* eslint-disable */ import XLSX from 'xlsx'; function auto_width(ws, data){ /*set worksheet max width per col*/ const colWidth = data.map(row => row.map(val => { /*if null/undefined*/ if (val == null) { return {'wch': 10}; } /*if chinese*/ else if (val.toString().charCodeAt(0) > 255) { return {'wch': val.toString().length * 2}; } else { return {'wch': val.toString().length}; } })) /*start in the first row*/ let result = colWidth[0]; for (let i = 1; i < colWidth.length; i++) { for (let j = 0; j < colWidth[i].length; j++) { if (result[j]['wch'] < colWidth[i][j]['wch']) { result[j]['wch'] = colWidth[i][j]['wch']; } } } ws['!cols'] = result; } function json_to_array(key, jsonData){ return jsonData.map(v => key.map(j => { return v[j] })); } // fix data,return string function fixdata(data) { let o = '' let l = 0 const w = 10240 for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w))) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w))) return o } // get head from excel file,return array function get_header_row(sheet) { const headers = [] const range = XLSX.utils.decode_range(sheet['!ref']) let C const R = range.s.r /* start in the first row */ for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */ var cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })] /* find the cell in the first row */ var hdr = 'UNKNOWN ' + C // <-- replace with your desired default if (cell && cell.t) hdr = XLSX.utils.format_cell(cell) headers.push(hdr) } return headers } export const export_table_to_excel= (id, filename) => { const table = document.getElementById(id); const wb = XLSX.utils.table_to_book(table); XLSX.writeFile(wb, filename); /* the second way */ // const table = document.getElementById(id); // const wb = XLSX.utils.book_new(); // const ws = XLSX.utils.table_to_sheet(table); // XLSX.utils.book_append_sheet(wb, ws, filename); // XLSX.writeFile(wb, filename); } export const export_json_to_excel = ({data, key, title, filename, autoWidth}) => { const wb = XLSX.utils.book_new(); data.unshift(title); const ws = XLSX.utils.json_to_sheet(data, {header: key, skipHeader: true}); if(autoWidth){ const arr = json_to_array(key, data); auto_width(ws, arr); } XLSX.utils.book_append_sheet(wb, ws, filename); XLSX.writeFile(wb, filename + '.xlsx'); } export const export_array_to_excel = ({key, data, title, filename, autoWidth}) => { const wb = XLSX.utils.book_new(); const arr = json_to_array(key, data); arr.unshift(title); const ws = XLSX.utils.aoa_to_sheet(arr); if(autoWidth){ auto_width(ws, arr); } XLSX.utils.book_append_sheet(wb, ws, filename); XLSX.writeFile(wb, filename + '.xlsx'); } export const read = (data, type) => { /* if type == 'base64' must fix data first */ // const fixedData = fixdata(data) // const workbook = XLSX.read(btoa(fixedData), { type: 'base64' }) const workbook = XLSX.read(data, { type: type }); const firstSheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[firstSheetName]; const header = get_header_row(worksheet); const results = XLSX.utils.sheet_to_json(worksheet); return {header, results}; } export default { export_table_to_excel, export_array_to_excel, export_json_to_excel, read } ================================================ FILE: src/libs/render-dom.js ================================================ export default { name: 'RenderDom', functional: true, props: { render: Function }, render: (h, ctx) => { return ctx.props.render(h) } } ================================================ FILE: src/libs/tools.js ================================================ export const forEach = (arr, fn) => { if (!arr.length || !fn) return let i = -1 let len = arr.length while (++i < len) { let item = arr[i] fn(item, i, arr) } } /** * @param {Array} arr1 * @param {Array} arr2 * @description 得到两个数组的交集, 两个数组的元素为数值或字符串 */ export const getIntersection = (arr1, arr2) => { let len = Math.min(arr1.length, arr2.length) let i = -1 let res = [] while (++i < len) { const item = arr2[i] if (arr1.indexOf(item) > -1) res.push(item) } return res } /** * @param {Array} arr1 * @param {Array} arr2 * @description 得到两个数组的并集, 两个数组的元素为数值或字符串 */ export const getUnion = (arr1, arr2) => { return Array.from(new Set([...arr1, ...arr2])) } /** * @param {Array} target 目标数组 * @param {Array} arr 需要查询的数组 * @description 判断要查询的数组是否至少有一个元素包含在目标数组中 */ export const hasOneOf = (targetarr, arr) => { return targetarr.some(_ => arr.indexOf(_) > -1) } /** * @param {String|Number} value 要验证的字符串或数值 * @param {*} validList 用来验证的列表 */ export function oneOf (value, validList) { for (let i = 0; i < validList.length; i++) { if (value === validList[i]) { return true } } return false } /** * @param {Number} timeStamp 判断时间戳格式是否是毫秒 * @returns {Boolean} */ const isMillisecond = timeStamp => { const timeStr = String(timeStamp) return timeStr.length > 10 } /** * @param {Number} timeStamp 传入的时间戳 * @param {Number} currentTime 当前时间时间戳 * @returns {Boolean} 传入的时间戳是否早于当前时间戳 */ const isEarly = (timeStamp, currentTime) => { return timeStamp < currentTime } /** * @param {Number} num 数值 * @returns {String} 处理后的字符串 * @description 如果传入的数值小于10,即位数只有1位,则在前面补充0 */ const getHandledValue = num => { return num < 10 ? '0' + num : num } /** * @param {Number} timeStamp 传入的时间戳 * @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间 */ const getDate = (timeStamp, startType) => { const d = new Date(timeStamp * 1000) const year = d.getFullYear() const month = getHandledValue(d.getMonth() + 1) const date = getHandledValue(d.getDate()) const hours = getHandledValue(d.getHours()) const minutes = getHandledValue(d.getMinutes()) const second = getHandledValue(d.getSeconds()) let resStr = '' if (startType === 'year') resStr = year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + second else resStr = month + '-' + date + ' ' + hours + ':' + minutes return resStr } /** * @param {String|Number} timeStamp 时间戳 * @returns {String} 相对时间字符串 */ export const getRelativeTime = timeStamp => { // 判断当前传入的时间戳是秒格式还是毫秒 const IS_MILLISECOND = isMillisecond(timeStamp) // 如果是毫秒格式则转为秒格式 if (IS_MILLISECOND) Math.floor(timeStamp /= 1000) // 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型 timeStamp = Number(timeStamp) // 获取当前时间时间戳 const currentTime = Math.floor(Date.parse(new Date()) / 1000) // 判断传入时间戳是否早于当前时间戳 const IS_EARLY = isEarly(timeStamp, currentTime) // 获取两个时间戳差值 let diff = currentTime - timeStamp // 如果IS_EARLY为false则差值取反 if (!IS_EARLY) diff = -diff let resStr = '' const dirStr = IS_EARLY ? '前' : '后' // 少于等于59秒 if (diff <= 59) resStr = diff + '秒' + dirStr // 多于59秒,少于等于59分钟59秒 else if (diff > 59 && diff <= 3599) resStr = Math.floor(diff / 60) + '分钟' + dirStr // 多于59分钟59秒,少于等于23小时59分钟59秒 else if (diff > 3599 && diff <= 86399) resStr = Math.floor(diff / 3600) + '小时' + dirStr // 多于23小时59分钟59秒,少于等于29天59分钟59秒 else if (diff > 86399 && diff <= 2623859) resStr = Math.floor(diff / 86400) + '天' + dirStr // 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前 else if (diff > 2623859 && diff <= 31567859 && IS_EARLY) resStr = getDate(timeStamp) else resStr = getDate(timeStamp, 'year') return resStr } /** * @returns {String} 当前浏览器名称 */ export const getExplorer = () => { const ua = window.navigator.userAgent const isExplorer = (exp) => { return ua.indexOf(exp) > -1 } if (isExplorer('MSIE')) return 'IE' else if (isExplorer('Firefox')) return 'Firefox' else if (isExplorer('Chrome')) return 'Chrome' else if (isExplorer('Opera')) return 'Opera' else if (isExplorer('Safari')) return 'Safari' } /** * @description 绑定事件 on(element, event, handler) */ export const on = (function () { if (document.addEventListener) { return function (element, event, handler) { if (element && event && handler) { element.addEventListener(event, handler, false) } } } else { return function (element, event, handler) { if (element && event && handler) { element.attachEvent('on' + event, handler) } } } })() /** * @description 解绑事件 off(element, event, handler) */ export const off = (function () { if (document.removeEventListener) { return function (element, event, handler) { if (element && event) { element.removeEventListener(event, handler, false) } } } else { return function (element, event, handler) { if (element && event) { element.detachEvent('on' + event, handler) } } } })() /** * 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性 * 如果没有传入key这个参数,则判断obj对象是否有键值对 */ export const hasKey = (obj, key) => { if (key) return key in obj else { let keysArr = Object.keys(obj) return keysArr.length } } /** * @param {*} obj1 对象 * @param {*} obj2 对象 * @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串 */ export const objEqual = (obj1, obj2) => { const keysArr1 = Object.keys(obj1) const keysArr2 = Object.keys(obj2) if (keysArr1.length !== keysArr2.length) return false else if (keysArr1.length === 0 && keysArr2.length === 0) return true /* eslint-disable-next-line */ else return !keysArr1.some(key => obj1[key] != obj2[key]) } ================================================ FILE: src/libs/util.js ================================================ import Cookies from 'js-cookie' // cookie保存的天数 import config from '@/config' import { forEach, hasOneOf, objEqual } from '@/libs/tools' const { title, cookieExpires, useI18n } = config export const TOKEN_KEY = 'token' export const setToken = (token) => { Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 }) } export const getToken = () => { const token = Cookies.get(TOKEN_KEY) if (token) return token else return false } export const hasChild = (item) => { return item.children && item.children.length !== 0 } const showThisMenuEle = (item, access) => { if (item.meta && item.meta.access && item.meta.access.length) { if (hasOneOf(item.meta.access, access)) return true else return false } else return true } /** * @param {Array} list 通过路由列表得到菜单列表 * @returns {Array} */ export const getMenuByRouter = (list, access) => { let res = [] forEach(list, item => { if (!item.meta || (item.meta && !item.meta.hideInMenu)) { let obj = { icon: (item.meta && item.meta.icon) || '', name: item.name, meta: item.meta } if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) { obj.children = getMenuByRouter(item.children, access) } if (item.meta && item.meta.href) obj.href = item.meta.href if (showThisMenuEle(item, access)) res.push(obj) } }) return res } /** * @param {Array} routeMetched 当前路由metched * @returns {Array} */ export const getBreadCrumbList = (route, homeRoute) => { let homeItem = { ...homeRoute, icon: homeRoute.meta.icon } let routeMetched = route.matched if (routeMetched.some(item => item.name === homeRoute.name)) return [homeItem] let res = routeMetched.filter(item => { return item.meta === undefined || !item.meta.hideInBread }).map(item => { let meta = { ...item.meta } if (meta.title && typeof meta.title === 'function') { meta.__titleIsFunction__ = true meta.title = meta.title(route) } let obj = { icon: (item.meta && item.meta.icon) || '', name: item.name, meta: meta } return obj }) res = res.filter(item => { return !item.meta.hideInMenu }) return [{ ...homeItem, to: homeRoute.path }, ...res] } export const getRouteTitleHandled = (route) => { let router = { ...route } let meta = { ...route.meta } let title = '' if (meta.title) { if (typeof meta.title === 'function') { meta.__titleIsFunction__ = true title = meta.title(router) } else title = meta.title } meta.title = title router.meta = meta return router } export const showTitle = (item, vm) => { let { title, __titleIsFunction__ } = item.meta if (!title) return if (useI18n) { if (title.includes('{{') && title.includes('}}') && useI18n) title = title.replace(/({{[\s\S]+?}})/, (m, str) => str.replace(/{{([\s\S]*)}}/, (m, _) => vm.$t(_.trim()))) else if (__titleIsFunction__) title = item.meta.title else title = vm.$t(item.name) } else title = (item.meta && item.meta.title) || item.name return title } /** * @description 本地存储和获取标签导航列表 */ export const setTagNavListInLocalstorage = list => { localStorage.tagNaveList = JSON.stringify(list) } /** * @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项 */ export const getTagNavListFromLocalstorage = () => { const list = localStorage.tagNaveList return list ? JSON.parse(list) : [] } /** * @param {Array} routers 路由列表数组 * @description 用于找到路由列表中name为home的对象 */ export const getHomeRoute = (routers, homeName = 'home') => { let i = -1 let len = routers.length let homeRoute = {} while (++i < len) { let item = routers[i] if (item.children && item.children.length) { let res = getHomeRoute(item.children, homeName) if (res.name) return res } else { if (item.name === homeName) homeRoute = item } } return homeRoute } /** * @param {*} list 现有标签导航列表 * @param {*} newRoute 新添加的路由原信息对象 * @description 如果该newRoute已经存在则不再添加 */ export const getNewTagList = (list, newRoute) => { const { name, path, meta } = newRoute let newList = [...list] if (newList.findIndex(item => item.name === name) >= 0) return newList else newList.push({ name, path, meta }) return newList } /** * @param {*} access 用户权限数组,如 ['super_admin', 'admin'] * @param {*} route 路由列表 */ const hasAccess = (access, route) => { if (route.meta && route.meta.access) return hasOneOf(access, route.meta.access) else return true } /** * 权鉴 * @param {*} name 即将跳转的路由name * @param {*} access 用户权限数组 * @param {*} routes 路由列表 * @description 用户是否可跳转到该页 */ export const canTurnTo = (name, access, routes) => { const routePermissionJudge = (list) => { return list.some(item => { if (item.children && item.children.length) { return routePermissionJudge(item.children) } else if (item.name === name) { return hasAccess(access, item) } }) } return routePermissionJudge(routes) } /** * @param {String} url * @description 从URL中解析参数 */ export const getParams = url => { const keyValueArr = url.split('?')[1].split('&') let paramObj = {} keyValueArr.forEach(item => { const keyValue = item.split('=') paramObj[keyValue[0]] = keyValue[1] }) return paramObj } /** * @param {Array} list 标签列表 * @param {String} name 当前关闭的标签的name */ export const getNextRoute = (list, route) => { let res = {} if (list.length === 2) { res = getHomeRoute(list) } else { const index = list.findIndex(item => routeEqual(item, route)) if (index === list.length - 1) res = list[list.length - 2] else res = list[index + 1] } return res } /** * @param {Number} times 回调函数需要执行的次数 * @param {Function} callback 回调函数 */ export const doCustomTimes = (times, callback) => { let i = -1 while (++i < times) { callback(i) } } /** * @param {Object} file 从上传组件得到的文件对象 * @returns {Promise} resolve参数是解析后的二维数组 * @description 从Csv文件中解析出表格,解析成二维数组 */ export const getArrayFromFile = (file) => { let nameSplit = file.name.split('.') let format = nameSplit[nameSplit.length - 1] return new Promise((resolve, reject) => { let reader = new FileReader() reader.readAsText(file) // 以文本格式读取 let arr = [] reader.onload = function (evt) { let data = evt.target.result // 读到的数据 let pasteData = data.trim() arr = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => { return row.split('\t') }).map(item => { return item[0].split(',') }) if (format === 'csv') resolve(arr) else reject(new Error('[Format Error]:你上传的不是Csv文件')) } }) } /** * @param {Array} array 表格数据二维数组 * @returns {Object} { columns, tableData } * @description 从二维数组中获取表头和表格数据,将第一行作为表头,用于在iView的表格中展示数据 */ export const getTableDataFromArray = (array) => { let columns = [] let tableData = [] if (array.length > 1) { let titles = array.shift() columns = titles.map(item => { return { title: item, key: item } }) tableData = array.map(item => { let res = {} item.forEach((col, i) => { res[titles[i]] = col }) return res }) } return { columns, tableData } } export const findNodeUpper = (ele, tag) => { if (ele.parentNode) { if (ele.parentNode.tagName === tag.toUpperCase()) { return ele.parentNode } else { return findNodeUpper(ele.parentNode, tag) } } } export const findNodeUpperByClasses = (ele, classes) => { let parentNode = ele.parentNode if (parentNode) { let classList = parentNode.classList if (classList && classes.every(className => classList.contains(className))) { return parentNode } else { return findNodeUpperByClasses(parentNode, classes) } } } export const findNodeDownward = (ele, tag) => { const tagName = tag.toUpperCase() if (ele.childNodes.length) { let i = -1 let len = ele.childNodes.length while (++i < len) { let child = ele.childNodes[i] if (child.tagName === tagName) return child else return findNodeDownward(child, tag) } } } export const showByAccess = (access, canViewAccess) => { return hasOneOf(canViewAccess, access) } /** * @description 根据name/params/query判断两个路由对象是否相等 * @param {*} route1 路由对象 * @param {*} route2 路由对象 */ export const routeEqual = (route1, route2) => { const params1 = route1.params || {} const params2 = route2.params || {} const query1 = route1.query || {} const query2 = route2.query || {} return (route1.name === route2.name) && objEqual(params1, params2) && objEqual(query1, query2) } /** * 判断打开的标签列表里是否已存在这个新添加的路由对象 */ export const routeHasExist = (tagNavList, routeItem) => { let len = tagNavList.length let res = false doCustomTimes(len, (index) => { if (routeEqual(tagNavList[index], routeItem)) res = true }) return res } export const localSave = (key, value) => { localStorage.setItem(key, value) } export const localRead = (key) => { return localStorage.getItem(key) || '' } // scrollTop animation export const scrollTop = (el, from = 0, to, duration = 500, endCallback) => { if (!window.requestAnimationFrame) { window.requestAnimationFrame = ( window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { return window.setTimeout(callback, 1000 / 60) } ) } const difference = Math.abs(from - to) const step = Math.ceil(difference / duration * 50) const scroll = (start, end, step) => { if (start === end) { endCallback && endCallback() return } let d = (start + step > end) ? end : start + step if (start > end) { d = (start - step < end) ? end : start - step } if (el === window) { window.scrollTo(d, d) } else { el.scrollTop = d } window.requestAnimationFrame(() => scroll(d, end, step)) } scroll(from, to, step) } /** * @description 根据当前跳转的路由设置显示在浏览器标签的title * @param {Object} routeItem 路由对象 * @param {Object} vm Vue实例 */ export const setTitle = (routeItem, vm) => { const handledRoute = getRouteTitleHandled(routeItem) const pageTitle = showTitle(handledRoute, vm) const resTitle = pageTitle ? `${title} - ${pageTitle}` : title window.document.title = resTitle } ================================================ FILE: src/locale/index.js ================================================ import Vue from 'vue' import VueI18n from 'vue-i18n' import { localRead } from '@/libs/util' import customZhCn from './lang/zh-CN' import customZhTw from './lang/zh-TW' import customEnUs from './lang/en-US' import zhCnLocale from 'iview/src/locale/lang/zh-CN' import enUsLocale from 'iview/src/locale/lang/en-US' import zhTwLocale from 'iview/src/locale/lang/zh-TW' Vue.use(VueI18n) // 自动根据浏览器系统语言设置语言 const navLang = navigator.language const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false let lang = localLang || localRead('local') || 'zh-CN' Vue.config.lang = lang // vue-i18n 6.x+写法 Vue.locale = () => {} const messages = { 'zh-CN': Object.assign(zhCnLocale, customZhCn), 'zh-TW': Object.assign(zhTwLocale, customZhTw), 'en-US': Object.assign(enUsLocale, customEnUs) } const i18n = new VueI18n({ locale: lang, messages }) export default i18n // vue-i18n 5.x写法 // Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn)) // Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw)) // Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs)) ================================================ FILE: src/locale/lang/en-US.js ================================================ export default { home: 'Home', login: 'Login', components: 'Components', count_to_page: 'Count-to', tables_page: 'Table', split_pane_page: 'Split-pane', markdown_page: 'Markdown-editor', editor_page: 'Rich-Text-Editor', icons_page: 'Custom-icon', img_cropper_page: 'Image-editor', update: 'Update', doc: 'Document', join_page: 'QQ Group', update_table_page: 'Update .CSV', update_paste_page: 'Paste Table Data', multilevel: 'multilevel', directive_page: 'Directive', level_1: 'Level-1', level_2: 'Level-2', level_2_1: 'Level-2-1', level_2_3: 'Level-2-3', level_2_2: 'Level-2-2', level_2_2_1: 'Level-2-2-1', level_2_2_2: 'Level-2-2-2', excel: 'Excel', 'upload-excel': 'Upload Excel', 'export-excel': 'Export Excel', tools_methods_page: 'Tools Methods', drag_list_page: 'Drag-list', i18n_page: 'Internationalization', modalTitle: 'Modal Title', content: 'This is the modal box content.', buttonText: 'Show Modal', 'i18n-tip': 'Note: Only this page is multi-language, other pages do not add language content to the multi-language package.', error_store_page: 'Error Collection', error_logger_page: 'Error Logger', query: 'Query', params: 'Params', cropper_page: 'Cropper', message_page: 'Message Center', tree_table_page: 'Tree Table', org_tree_page: 'Org Tree', drag_drawer_page: 'Draggable Drawer', tree_select_page: 'Tree Selector' } ================================================ FILE: src/locale/lang/zh-CN.js ================================================ export default { home: '首页', login: '登录', components: '组件', count_to_page: '数字渐变', tables_page: '多功能表格', split_pane_page: '分割窗口', markdown_page: 'Markdown编辑器', editor_page: '富文本编辑器', icons_page: '自定义图标', img_cropper_page: '图片编辑器', update: '上传数据', join_page: 'QQ群', doc: '文档', update_table_page: '上传CSV文件', update_paste_page: '粘贴表格数据', multilevel: '多级菜单', directive_page: '指令', level_1: 'Level-1', level_2: 'Level-2', level_2_1: 'Level-2-1', level_2_3: 'Level-2-3', level_2_2: 'Level-2-2', level_2_2_1: 'Level-2-2-1', level_2_2_2: 'Level-2-2-2', excel: 'Excel', 'upload-excel': '上传excel', 'export-excel': '导出excel', tools_methods_page: '工具函数', drag_list_page: '拖拽列表', i18n_page: '多语言', modalTitle: '模态框题目', content: '这是模态框内容', buttonText: '显示模态框', 'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容', error_store_page: '错误收集', error_logger_page: '错误日志', query: '带参路由', params: '动态路由', cropper_page: '图片裁剪', message_page: '消息中心', tree_table_page: '树状表格', org_tree_page: '组织结构树', drag_drawer_page: '可拖动抽屉', tree_select_page: '树状下拉选择器' } ================================================ FILE: src/locale/lang/zh-TW.js ================================================ export default { home: '首頁', login: '登錄', components: '组件', count_to_page: '数字渐变', tables_page: '多功能表格', split_pane_page: '分割窗口', markdown_page: 'Markdown編輯器', editor_page: '富文本編輯器', icons_page: '自定義圖標', img_cropper_page: '圖片編輯器', update: '上傳數據', join_page: 'QQ群', doc: '文檔', update_table_page: '上傳CSV文件', update_paste_page: '粘貼表格數據', multilevel: '多级菜单', directive_page: '指令', level_1: 'Level-1', level_2: 'Level-2', level_2_1: 'Level-2-1', level_2_3: 'Level-2-3', level_2_2: 'Level-2-2', level_2_2_1: 'Level-2-2-1', level_2_2_2: 'Level-2-2-2', excel: 'Excel', 'upload-excel': '上傳excel', 'export-excel': '導出excel', tools_methods_page: '工具函數', drag_list_page: '拖拽列表', i18n_page: '多語言', modalTitle: '模態框題目', content: '這是模態框內容', buttonText: '顯示模態框', 'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容', error_store_page: '錯誤收集', error_logger_page: '錯誤日誌', query: '帶參路由', params: '動態路由', cropper_page: '圖片裁剪', message_page: '消息中心', tree_table_page: '樹狀表格', org_tree_page: '組織結構樹', drag_drawer_page: '可拖動抽屜', tree_select_page: '樹狀下拉選擇器' } ================================================ FILE: src/main.js ================================================ // The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import store from './store' import iView from 'iview' import i18n from '@/locale' import config from '@/config' import importDirective from '@/directive' import { directive as clickOutside } from 'v-click-outside-x' import installPlugin from '@/plugin' import './index.less' import '@/assets/icons/iconfont.css' import TreeTable from 'tree-table-vue' import VOrgTree from 'v-org-tree' import 'v-org-tree/dist/v-org-tree.css' // 实际打包时应该不引入mock /* eslint-disable */ if (process.env.NODE_ENV !== 'production') require('@/mock') Vue.use(iView, { i18n: (key, value) => i18n.t(key, value) }) Vue.use(TreeTable) Vue.use(VOrgTree) /** * @description 注册admin内置插件 */ installPlugin(Vue) /** * @description 生产环境关掉提示 */ Vue.config.productionTip = false /** * @description 全局注册应用配置 */ Vue.prototype.$config = config /** * 注册指令 */ importDirective(Vue) Vue.directive('clickOutside', clickOutside) /* eslint-disable no-new */ new Vue({ el: '#app', router, i18n, store, render: h => h(App) }) ================================================ FILE: src/mock/data/org-data.js ================================================ export default { id: 0, label: 'XXX科技有限公司', children: [ { id: 2, label: '产品研发部', children: [ { id: 5, label: '研发-前端' }, { id: 6, label: '研发-后端' }, { id: 9, label: 'UI设计' }, { id: 10, label: '产品经理' } ] }, { id: 3, label: '销售部', children: [ { id: 7, label: '销售一部' }, { id: 8, label: '销售二部' } ] }, { id: 4, label: '财务部' }, { id: 11, label: 'HR人事' } ] } ================================================ FILE: src/mock/data/tree-select.js ================================================ export const treeData = [ { id: 1, title: '1', children: [ { id: 11, title: '1-1', loading: false, children: [ // { // id: 111, // title: '1-1-1' // }, // { // id: 112, // title: '1-1-2' // }, // { // id: 113, // title: '1-1-3' // }, // { // id: 114, // title: '1-1-4' // } ] }, { id: 12, title: '1-2', children: [ { id: 121, title: '1-2-1' } ] } ] } ] export const newTreeData = [ { id: 'a', title: 'a', children: [ { id: 'a1', title: 'a-1', children: [ { id: 112, title: '1-1-2' }, { id: 'a12', title: 'a-1-2' }, { id: 'a13', title: 'a-1-3' }, { id: 'a14', title: 'a-1-4' } ] }, { id: 'a2', title: 'a-2', children: [ { id: 'a21', title: 'b-2-1' } ] } ] } ] ================================================ FILE: src/mock/data.js ================================================ import Mock from 'mockjs' import { doCustomTimes } from '@/libs/util' import orgData from './data/org-data' import { treeData } from './data/tree-select' const Random = Mock.Random export const getTableData = req => { let tableData = [] doCustomTimes(5, () => { tableData.push(Mock.mock({ name: '@name', email: '@email', createTime: '@date' })) }) return tableData } export const getDragList = req => { let dragList = [] doCustomTimes(5, () => { dragList.push(Mock.mock({ name: Random.csentence(10, 13), id: Random.increment(10) })) }) return dragList } export const uploadImage = req => { return Promise.resolve() } export const getOrgData = req => { return orgData } export const getTreeSelectData = req => { return treeData } ================================================ FILE: src/mock/index.js ================================================ import Mock from 'mockjs' import { login, logout, getUserInfo } from './login' import { getTableData, getDragList, uploadImage, getOrgData, getTreeSelectData } from './data' import { getMessageInit, getContentByMsgId, hasRead, removeReaded, restoreTrash, messageCount } from './user' // 配置Ajax请求延时,可用来测试网络延迟大时项目中一些效果 Mock.setup({ timeout: 1000 }) // 登录相关和获取用户信息 Mock.mock(/\/login/, login) Mock.mock(/\/get_info/, getUserInfo) Mock.mock(/\/logout/, logout) Mock.mock(/\/get_table_data/, getTableData) Mock.mock(/\/get_drag_list/, getDragList) Mock.mock(/\/save_error_logger/, 'success') Mock.mock(/\/image\/upload/, uploadImage) Mock.mock(/\/message\/init/, getMessageInit) Mock.mock(/\/message\/content/, getContentByMsgId) Mock.mock(/\/message\/has_read/, hasRead) Mock.mock(/\/message\/remove_readed/, removeReaded) Mock.mock(/\/message\/restore/, restoreTrash) Mock.mock(/\/message\/count/, messageCount) Mock.mock(/\/get_org_data/, getOrgData) Mock.mock(/\/get_tree_select_data/, getTreeSelectData) export default Mock ================================================ FILE: src/mock/login.js ================================================ import { getParams } from '@/libs/util' const USER_MAP = { super_admin: { name: 'super_admin', user_id: '1', access: ['super_admin', 'admin'], token: 'super_admin', avatar: 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png' }, admin: { name: 'admin', user_id: '2', access: ['admin'], token: 'admin', avatar: 'https://avatars0.githubusercontent.com/u/20942571?s=460&v=4' } } export const login = req => { req = JSON.parse(req.body) return { token: USER_MAP[req.userName].token } } export const getUserInfo = req => { const params = getParams(req.url) return USER_MAP[params.token] } export const logout = req => { return null } ================================================ FILE: src/mock/user.js ================================================ import Mock from 'mockjs' import { doCustomTimes } from '@/libs/util' const Random = Mock.Random export const getMessageInit = () => { let unreadList = [] doCustomTimes(3, () => { unreadList.push(Mock.mock({ title: Random.cword(10, 15), create_time: '@date', msg_id: Random.increment(100) })) }) let readedList = [] doCustomTimes(4, () => { readedList.push(Mock.mock({ title: Random.cword(10, 15), create_time: '@date', msg_id: Random.increment(100) })) }) let trashList = [] doCustomTimes(2, () => { trashList.push(Mock.mock({ title: Random.cword(10, 15), create_time: '@date', msg_id: Random.increment(100) })) }) return { unread: unreadList, readed: readedList, trash: trashList } } export const getContentByMsgId = () => { return `
        这是消息内容,这个内容是使用富文本编辑器编辑的,所以你可以看到一些格式
  1. 你可以查看Mock返回的数据格式,和api请求的接口,来确定你的后端接口的开发
  2. 使用你的真实接口后,前端页面基本不需要修改即可满足基本需求
  3. 快来试试吧

${Random.csentence(100, 200)}

` } export const hasRead = () => { return true } export const removeReaded = () => { return true } export const restoreTrash = () => { return true } export const messageCount = () => { return 3 } ================================================ FILE: src/plugin/error-store/index.js ================================================ import store from '@/store' export default { install (Vue, options) { if (options.developmentOff && process.env.NODE_ENV === 'development') return Vue.config.errorHandler = (error, vm, mes) => { let info = { type: 'script', code: 0, mes: error.message, url: window.location.href } Vue.nextTick(() => { store.dispatch('addErrorLog', info) }) } } } ================================================ FILE: src/plugin/index.js ================================================ import config from '@/config' const { plugin } = config export default (Vue) => { for (let name in plugin) { const value = plugin[name] Vue.use(require(`./${name}`).default, typeof value === 'object' ? value : undefined) } } ================================================ FILE: src/router/before-close.js ================================================ import { Modal } from 'iview' const beforeClose = { before_close_normal: (resolve) => { Modal.confirm({ title: '确定要关闭这一页吗', onOk: () => { resolve(true) }, onCancel: () => { resolve(false) } }) } } export default beforeClose ================================================ FILE: src/router/index.js ================================================ import Vue from 'vue' import Router from 'vue-router' import routes from './routers' import store from '@/store' import iView from 'iview' import { setToken, getToken, canTurnTo, setTitle } from '@/libs/util' import config from '@/config' const { homeName } = config Vue.use(Router) const router = new Router({ routes, mode: 'history' }) const LOGIN_PAGE_NAME = 'login' const turnTo = (to, access, next) => { if (canTurnTo(to.name, access, routes)) next() // 有权限,可访问 else next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面 } router.beforeEach((to, from, next) => { iView.LoadingBar.start() const token = getToken() if (!token && to.name !== LOGIN_PAGE_NAME) { // 未登录且要跳转的页面不是登录页 next({ name: LOGIN_PAGE_NAME // 跳转到登录页 }) } else if (!token && to.name === LOGIN_PAGE_NAME) { // 未登陆且要跳转的页面是登录页 next() // 跳转 } else if (token && to.name === LOGIN_PAGE_NAME) { // 已登录且要跳转的页面是登录页 next({ name: homeName // 跳转到homeName页 }) } else { if (store.state.user.hasGetInfo) { turnTo(to, store.state.user.access, next) } else { store.dispatch('getUserInfo').then(user => { // 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin'] turnTo(to, user.access, next) }).catch(() => { setToken('') next({ name: 'login' }) }) } } }) router.afterEach(to => { setTitle(to, router.app) iView.LoadingBar.finish() window.scrollTo(0, 0) }) export default router ================================================ FILE: src/router/routers.js ================================================ import Main from '@/components/main' import parentView from '@/components/parent-view' /** * iview-admin中meta除了原生参数外可配置的参数: * meta: { * title: { String|Number|Function } * 显示在侧边栏、面包屑和标签栏的文字 * 使用'{{ 多语言字段 }}'形式结合多语言使用,例子看多语言的路由配置; * 可以传入一个回调函数,参数是当前路由对象,例子看动态路由和带参路由 * hideInBread: (false) 设为true后此级路由将不会出现在面包屑中,示例看QQ群路由配置 * hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项 * notCache: (false) 设为true后页面在切换标签后不会缓存,如果需要缓存,无需设置这个字段,而且需要设置页面组件name属性和路由配置的name一致 * access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由 * icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_' * beforeCloseName: (-) 设置该字段,则在关闭当前tab页时会去'@/router/before-close.js'里寻找该字段名对应的方法,作为关闭前的钩子函数 * } */ export default [ { path: '/login', name: 'login', meta: { title: 'Login - 登录', hideInMenu: true }, component: () => import('@/view/login/login.vue') }, { path: '/', name: '_home', redirect: '/home', component: Main, meta: { hideInMenu: true, notCache: true }, children: [ { path: '/home', name: 'home', meta: { hideInMenu: true, title: '首页', notCache: true, icon: 'md-home' }, component: () => import('@/view/single-page/home') } ] }, { path: '', name: 'doc', meta: { title: '文档', href: 'https://lison16.github.io/iview-admin-doc/#/', icon: 'ios-book' } }, { path: '/join', name: 'join', component: Main, meta: { hideInBread: true }, children: [ { path: 'join_page', name: 'join_page', meta: { icon: '_qq', title: 'QQ群' }, component: () => import('@/view/join-page.vue') } ] }, { path: '/message', name: 'message', component: Main, meta: { hideInBread: true, hideInMenu: true }, children: [ { path: 'message_page', name: 'message_page', meta: { icon: 'md-notifications', title: '消息中心' }, component: () => import('@/view/single-page/message/index.vue') } ] }, { path: '/components', name: 'components', meta: { icon: 'logo-buffer', title: '组件' }, component: Main, children: [ { path: 'tree_select_page', name: 'tree_select_page', meta: { icon: 'md-arrow-dropdown-circle', title: '树状下拉选择器' }, component: () => import('@/view/components/tree-select/index.vue') }, { path: 'count_to_page', name: 'count_to_page', meta: { icon: 'md-trending-up', title: '数字渐变' }, component: () => import('@/view/components/count-to/count-to.vue') }, { path: 'drag_list_page', name: 'drag_list_page', meta: { icon: 'ios-infinite', title: '拖拽列表' }, component: () => import('@/view/components/drag-list/drag-list.vue') }, { path: 'drag_drawer_page', name: 'drag_drawer_page', meta: { icon: 'md-list', title: '可拖拽抽屉' }, component: () => import('@/view/components/drag-drawer') }, { path: 'org_tree_page', name: 'org_tree_page', meta: { icon: 'ios-people', title: '组织结构树' }, component: () => import('@/view/components/org-tree') }, { path: 'tree_table_page', name: 'tree_table_page', meta: { icon: 'md-git-branch', title: '树状表格' }, component: () => import('@/view/components/tree-table/index.vue') }, { path: 'cropper_page', name: 'cropper_page', meta: { icon: 'md-crop', title: '图片裁剪' }, component: () => import('@/view/components/cropper/cropper.vue') }, { path: 'tables_page', name: 'tables_page', meta: { icon: 'md-grid', title: '多功能表格' }, component: () => import('@/view/components/tables/tables.vue') }, { path: 'split_pane_page', name: 'split_pane_page', meta: { icon: 'md-pause', title: '分割窗口' }, component: () => import('@/view/components/split-pane/split-pane.vue') }, { path: 'markdown_page', name: 'markdown_page', meta: { icon: 'logo-markdown', title: 'Markdown编辑器' }, component: () => import('@/view/components/markdown/markdown.vue') }, { path: 'editor_page', name: 'editor_page', meta: { icon: 'ios-create', title: '富文本编辑器' }, component: () => import('@/view/components/editor/editor.vue') }, { path: 'icons_page', name: 'icons_page', meta: { icon: '_bear', title: '自定义图标' }, component: () => import('@/view/components/icons/icons.vue') } ] }, { path: '/update', name: 'update', meta: { icon: 'md-cloud-upload', title: '数据上传' }, component: Main, children: [ { path: 'update_table_page', name: 'update_table_page', meta: { icon: 'ios-document', title: '上传Csv' }, component: () => import('@/view/update/update-table.vue') }, { path: 'update_paste_page', name: 'update_paste_page', meta: { icon: 'md-clipboard', title: '粘贴表格数据' }, component: () => import('@/view/update/update-paste.vue') } ] }, { path: '/excel', name: 'excel', meta: { icon: 'ios-stats', title: 'EXCEL导入导出' }, component: Main, children: [ { path: 'upload-excel', name: 'upload-excel', meta: { icon: 'md-add', title: '导入EXCEL' }, component: () => import('@/view/excel/upload-excel.vue') }, { path: 'export-excel', name: 'export-excel', meta: { icon: 'md-download', title: '导出EXCEL' }, component: () => import('@/view/excel/export-excel.vue') } ] }, { path: '/tools_methods', name: 'tools_methods', meta: { hideInBread: true }, component: Main, children: [ { path: 'tools_methods_page', name: 'tools_methods_page', meta: { icon: 'ios-hammer', title: '工具方法', beforeCloseName: 'before_close_normal' }, component: () => import('@/view/tools-methods/tools-methods.vue') } ] }, { path: '/i18n', name: 'i18n', meta: { hideInBread: true }, component: Main, children: [ { path: 'i18n_page', name: 'i18n_page', meta: { icon: 'md-planet', title: 'i18n - {{ i18n_page }}' }, component: () => import('@/view/i18n/i18n-page.vue') } ] }, { path: '/error_store', name: 'error_store', meta: { hideInBread: true }, component: Main, children: [ { path: 'error_store_page', name: 'error_store_page', meta: { icon: 'ios-bug', title: '错误收集' }, component: () => import('@/view/error-store/error-store.vue') } ] }, { path: '/error_logger', name: 'error_logger', meta: { hideInBread: true, hideInMenu: true }, component: Main, children: [ { path: 'error_logger_page', name: 'error_logger_page', meta: { icon: 'ios-bug', title: '错误收集' }, component: () => import('@/view/single-page/error-logger.vue') } ] }, { path: '/directive', name: 'directive', meta: { hideInBread: true }, component: Main, children: [ { path: 'directive_page', name: 'directive_page', meta: { icon: 'ios-navigate', title: '指令' }, component: () => import('@/view/directive/directive.vue') } ] }, { path: '/multilevel', name: 'multilevel', meta: { icon: 'md-menu', title: '多级菜单' }, component: Main, children: [ { path: 'level_2_1', name: 'level_2_1', meta: { icon: 'md-funnel', title: '二级-1' }, component: () => import('@/view/multilevel/level-2-1.vue') }, { path: 'level_2_2', name: 'level_2_2', meta: { access: ['super_admin'], icon: 'md-funnel', showAlways: true, title: '二级-2' }, component: parentView, children: [ { path: 'level_2_2_1', name: 'level_2_2_1', meta: { icon: 'md-funnel', title: '三级' }, component: () => import('@/view/multilevel/level-2-2/level-2-2-1.vue') }, { path: 'level_2_2_2', name: 'level_2_2_2', meta: { icon: 'md-funnel', title: '三级' }, component: () => import('@/view/multilevel/level-2-2/level-2-2-2.vue') } ] }, { path: 'level_2_3', name: 'level_2_3', meta: { icon: 'md-funnel', title: '二级-3' }, component: () => import('@/view/multilevel/level-2-3.vue') } ] }, { path: '/argu', name: 'argu', meta: { hideInMenu: true }, component: Main, children: [ { path: 'params/:id', name: 'params', meta: { icon: 'md-flower', title: route => `{{ params }}-${route.params.id}`, notCache: true, beforeCloseName: 'before_close_normal' }, component: () => import('@/view/argu-page/params.vue') }, { path: 'query', name: 'query', meta: { icon: 'md-flower', title: route => `{{ query }}-${route.query.id}`, notCache: true }, component: () => import('@/view/argu-page/query.vue') } ] }, { path: '/401', name: 'error_401', meta: { hideInMenu: true }, component: () => import('@/view/error-page/401.vue') }, { path: '/500', name: 'error_500', meta: { hideInMenu: true }, component: () => import('@/view/error-page/500.vue') }, { path: '*', name: 'error_404', meta: { hideInMenu: true }, component: () => import('@/view/error-page/404.vue') } ] ================================================ FILE: src/store/index.js ================================================ import Vue from 'vue' import Vuex from 'vuex' import user from './module/user' import app from './module/app' Vue.use(Vuex) export default new Vuex.Store({ state: { // }, mutations: { // }, actions: { // }, modules: { user, app } }) ================================================ FILE: src/store/module/app.js ================================================ import { getBreadCrumbList, setTagNavListInLocalstorage, getMenuByRouter, getTagNavListFromLocalstorage, getHomeRoute, getNextRoute, routeHasExist, routeEqual, getRouteTitleHandled, localSave, localRead } from '@/libs/util' import { saveErrorLogger } from '@/api/data' import router from '@/router' import routers from '@/router/routers' import config from '@/config' const { homeName } = config const closePage = (state, route) => { const nextRoute = getNextRoute(state.tagNavList, route) state.tagNavList = state.tagNavList.filter(item => { return !routeEqual(item, route) }) router.push(nextRoute) } export default { state: { breadCrumbList: [], tagNavList: [], homeRoute: {}, local: localRead('local'), errorList: [], hasReadErrorPage: false }, getters: { menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access), errorCount: state => state.errorList.length }, mutations: { setBreadCrumb (state, route) { state.breadCrumbList = getBreadCrumbList(route, state.homeRoute) }, setHomeRoute (state, routes) { state.homeRoute = getHomeRoute(routes, homeName) }, setTagNavList (state, list) { let tagList = [] if (list) { tagList = [...list] } else tagList = getTagNavListFromLocalstorage() || [] if (tagList[0] && tagList[0].name !== homeName) tagList.shift() let homeTagIndex = tagList.findIndex(item => item.name === homeName) if (homeTagIndex > 0) { let homeTag = tagList.splice(homeTagIndex, 1)[0] tagList.unshift(homeTag) } state.tagNavList = tagList setTagNavListInLocalstorage([...tagList]) }, closeTag (state, route) { let tag = state.tagNavList.filter(item => routeEqual(item, route)) route = tag[0] ? tag[0] : null if (!route) return closePage(state, route) }, addTag (state, { route, type = 'unshift' }) { let router = getRouteTitleHandled(route) if (!routeHasExist(state.tagNavList, router)) { if (type === 'push') state.tagNavList.push(router) else { if (router.name === homeName) state.tagNavList.unshift(router) else state.tagNavList.splice(1, 0, router) } setTagNavListInLocalstorage([...state.tagNavList]) } }, setLocal (state, lang) { localSave('local', lang) state.local = lang }, addError (state, error) { state.errorList.push(error) }, setHasReadErrorLoggerStatus (state, status = true) { state.hasReadErrorPage = status } }, actions: { addErrorLog ({ commit, rootState }, info) { if (!window.location.href.includes('error_logger_page')) commit('setHasReadErrorLoggerStatus', false) const { user: { token, userId, userName } } = rootState let data = { ...info, time: Date.parse(new Date()), token, userId, userName } saveErrorLogger(info).then(() => { commit('addError', data) }) } } } ================================================ FILE: src/store/module/user.js ================================================ import { login, logout, getUserInfo, getMessage, getContentByMsgId, hasRead, removeReaded, restoreTrash, getUnreadCount } from '@/api/user' import { setToken, getToken } from '@/libs/util' export default { state: { userName: '', userId: '', avatarImgPath: '', token: getToken(), access: '', hasGetInfo: false, unreadCount: 0, messageUnreadList: [], messageReadedList: [], messageTrashList: [], messageContentStore: {} }, mutations: { setAvatar (state, avatarPath) { state.avatarImgPath = avatarPath }, setUserId (state, id) { state.userId = id }, setUserName (state, name) { state.userName = name }, setAccess (state, access) { state.access = access }, setToken (state, token) { state.token = token setToken(token) }, setHasGetInfo (state, status) { state.hasGetInfo = status }, setMessageCount (state, count) { state.unreadCount = count }, setMessageUnreadList (state, list) { state.messageUnreadList = list }, setMessageReadedList (state, list) { state.messageReadedList = list }, setMessageTrashList (state, list) { state.messageTrashList = list }, updateMessageContentStore (state, { msg_id, content }) { state.messageContentStore[msg_id] = content }, moveMsg (state, { from, to, msg_id }) { const index = state[from].findIndex(_ => _.msg_id === msg_id) const msgItem = state[from].splice(index, 1)[0] msgItem.loading = false state[to].unshift(msgItem) } }, getters: { messageUnreadCount: state => state.messageUnreadList.length, messageReadedCount: state => state.messageReadedList.length, messageTrashCount: state => state.messageTrashList.length }, actions: { // 登录 handleLogin ({ commit }, { userName, password }) { userName = userName.trim() return new Promise((resolve, reject) => { login({ userName, password }).then(res => { const data = res.data commit('setToken', data.token) resolve() }).catch(err => { reject(err) }) }) }, // 退出登录 handleLogOut ({ state, commit }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { commit('setToken', '') commit('setAccess', []) resolve() }).catch(err => { reject(err) }) // 如果你的退出登录无需请求接口,则可以直接使用下面三行代码而无需使用logout调用接口 // commit('setToken', '') // commit('setAccess', []) // resolve() }) }, // 获取用户相关信息 getUserInfo ({ state, commit }) { return new Promise((resolve, reject) => { try { getUserInfo(state.token).then(res => { const data = res.data commit('setAvatar', data.avatar) commit('setUserName', data.name) commit('setUserId', data.user_id) commit('setAccess', data.access) commit('setHasGetInfo', true) resolve(data) }).catch(err => { reject(err) }) } catch (error) { reject(error) } }) }, // 此方法用来获取未读消息条数,接口只返回数值,不返回消息列表 getUnreadMessageCount ({ state, commit }) { getUnreadCount().then(res => { const { data } = res commit('setMessageCount', data) }) }, // 获取消息列表,其中包含未读、已读、回收站三个列表 getMessageList ({ state, commit }) { return new Promise((resolve, reject) => { getMessage().then(res => { const { unread, readed, trash } = res.data commit('setMessageUnreadList', unread.sort((a, b) => new Date(b.create_time) - new Date(a.create_time))) commit('setMessageReadedList', readed.map(_ => { _.loading = false return _ }).sort((a, b) => new Date(b.create_time) - new Date(a.create_time))) commit('setMessageTrashList', trash.map(_ => { _.loading = false return _ }).sort((a, b) => new Date(b.create_time) - new Date(a.create_time))) resolve() }).catch(error => { reject(error) }) }) }, // 根据当前点击的消息的id获取内容 getContentByMsgId ({ state, commit }, { msg_id }) { return new Promise((resolve, reject) => { let contentItem = state.messageContentStore[msg_id] if (contentItem) { resolve(contentItem) } else { getContentByMsgId(msg_id).then(res => { const content = res.data commit('updateMessageContentStore', { msg_id, content }) resolve(content) }) } }) }, // 把一个未读消息标记为已读 hasRead ({ state, commit }, { msg_id }) { return new Promise((resolve, reject) => { hasRead(msg_id).then(() => { commit('moveMsg', { from: 'messageUnreadList', to: 'messageReadedList', msg_id }) commit('setMessageCount', state.unreadCount - 1) resolve() }).catch(error => { reject(error) }) }) }, // 删除一个已读消息到回收站 removeReaded ({ commit }, { msg_id }) { return new Promise((resolve, reject) => { removeReaded(msg_id).then(() => { commit('moveMsg', { from: 'messageReadedList', to: 'messageTrashList', msg_id }) resolve() }).catch(error => { reject(error) }) }) }, // 还原一个已删除消息到已读消息 restoreTrash ({ commit }, { msg_id }) { return new Promise((resolve, reject) => { restoreTrash(msg_id).then(() => { commit('moveMsg', { from: 'messageTrashList', to: 'messageReadedList', msg_id }) resolve() }).catch(error => { reject(error) }) }) } } } ================================================ FILE: src/view/argu-page/params.vue ================================================ ================================================ FILE: src/view/argu-page/query.vue ================================================ ================================================ FILE: src/view/components/count-to/count-to.vue ================================================ ================================================ FILE: src/view/components/cropper/cropper.vue ================================================ ================================================ FILE: src/view/components/drag-drawer/index.vue ================================================ ================================================ FILE: src/view/components/drag-list/drag-list.vue ================================================ ================================================ FILE: src/view/components/editor/editor.vue ================================================ ================================================ FILE: src/view/components/icons/icons.vue ================================================ ================================================ FILE: src/view/components/markdown/markdown.vue ================================================ ================================================ FILE: src/view/components/org-tree/components/org-view.vue ================================================ ================================================ FILE: src/view/components/org-tree/components/zoom-controller.vue ================================================ ================================================ FILE: src/view/components/org-tree/index.less ================================================ @wrapper: ~'department'; .percent-100 { width: 100%; height: 100%; } .@{wrapper}-outer { .percent-100; overflow: hidden; .tip-box{ position: absolute; left: 20px; top: 20px; z-index: 12; } .zoom-box { position: absolute; right: 30px; bottom: 30px; z-index: 2; } .view-box { position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: 1; cursor: move; .org-tree-drag-wrapper { width: 100%; height: 100%; } .org-tree-wrapper { display: inline-block; position: absolute; left: 50%; top: 50%; transition: transform 0.2s ease-out; .org-tree-node-label { box-shadow: 0px 2px 12px 0px rgba(143, 154, 165, 0.4); border-radius: 4px; .org-tree-node-label-inner { padding: 0; .custom-org-node { padding: 14px 41px; background: #738699; user-select: none; word-wrap: none; white-space: nowrap; border-radius: 4px; color: #ffffff; font-size: 14px; font-weight: 500; line-height: 20px; transition: background 0.1s ease-in; cursor: default; &:hover { background: #5d6c7b; transition: background 0.1s ease-in; } &.has-children-label { cursor: pointer; } .context-menu{ position: absolute; right: -10px; bottom: 20px; z-index: 10; } } } } } } } ================================================ FILE: src/view/components/org-tree/index.vue ================================================ ================================================ FILE: src/view/components/split-pane/split-pane.vue ================================================ ================================================ FILE: src/view/components/tables/tables.vue ================================================ ================================================ FILE: src/view/components/tree-select/index.vue ================================================ ================================================ FILE: src/view/components/tree-table/index.vue ================================================ ================================================ FILE: src/view/directive/directive.vue ================================================ ================================================ FILE: src/view/error-page/401.vue ================================================ ================================================ FILE: src/view/error-page/404.vue ================================================ ================================================ FILE: src/view/error-page/500.vue ================================================ ================================================ FILE: src/view/error-page/back-btn-group.vue ================================================ ================================================ FILE: src/view/error-page/error-content.vue ================================================ ================================================ FILE: src/view/error-page/error.less ================================================ .error-page{ width: 100%; height: 100%; position: relative; background: #f8f8f9; .content-con{ width: 700px; height: 600px; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -60%); img{ display: block; width: 100%; height: 100%; } .text-con{ position: absolute; left: 0px; top: 0px; h4{ position: absolute; left: 0px; top: 0px; font-size: 80px; font-weight: 700; color: #348EED; } h5{ position: absolute; width: 700px; left: 0px; top: 100px; font-size: 20px; font-weight: 700; color: #67647D; } } .back-btn-group{ position: absolute; right: 0px; bottom: 20px; } } } ================================================ FILE: src/view/error-store/error-store.vue ================================================ ================================================ FILE: src/view/excel/common.less ================================================ .margin-top-8{ margin-top: 8px; } .margin-top-10{ margin-top: 10px; } .margin-top-20{ margin-top: 20px; } .margin-left-10{ margin-left: 10px; } .margin-bottom-10{ margin-bottom: 10px; } .margin-bottom-100{ margin-bottom: 100px; } .margin-right-10{ margin-right: 10px; } .padding-left-6{ padding-left: 6px; } .padding-left-8{ padding-left: 5px; } .padding-left-10{ padding-left: 10px; } .padding-left-20{ padding-left: 20px; } .height-100{ height: 100%; } .height-120px{ height: 100px; } .height-200px{ height: 200px; } .height-492px{ height: 492px; } .height-460px{ height: 460px; } .line-gray{ height: 0; border-bottom: 2px solid #dcdcdc; } .notwrap{ word-break:keep-all; white-space:nowrap; overflow: hidden; text-overflow: ellipsis; } .padding-left-5{ padding-left: 10px; } [v-cloak]{ display: none; } ================================================ FILE: src/view/excel/export-excel.vue ================================================ ================================================ FILE: src/view/excel/upload-excel.vue ================================================ ================================================ FILE: src/view/i18n/i18n-page.vue ================================================ ================================================ FILE: src/view/join-page.vue ================================================ ================================================ FILE: src/view/login/login.less ================================================ .login{ width: 100%; height: 100%; background-image: url('../../assets/images/login-bg.jpg'); background-size: cover; background-position: center; position: relative; &-con{ position: absolute; right: 160px; top: 50%; transform: translateY(-60%); width: 300px; &-header{ font-size: 16px; font-weight: 300; text-align: center; padding: 30px 0; } .form-con{ padding: 10px 0 0; } .login-tip{ font-size: 10px; text-align: center; color: #c3c3c3; } } } ================================================ FILE: src/view/login/login.vue ================================================ ================================================ FILE: src/view/multilevel/level-2-1.vue ================================================ ================================================ FILE: src/view/multilevel/level-2-2/level-2-2-1.vue ================================================ ================================================ FILE: src/view/multilevel/level-2-2/level-2-2-2.vue ================================================ ================================================ FILE: src/view/multilevel/level-2-3.vue ================================================ ================================================ FILE: src/view/single-page/error-logger.vue ================================================ ================================================ FILE: src/view/single-page/home/example.vue ================================================ ================================================ FILE: src/view/single-page/home/home.vue ================================================ ================================================ FILE: src/view/single-page/home/index.js ================================================ import home from './home.vue' export default home ================================================ FILE: src/view/single-page/message/index.vue ================================================ ================================================ FILE: src/view/tools-methods/tools-methods.vue ================================================ ================================================ FILE: src/view/update/update-paste.vue ================================================ ================================================ FILE: src/view/update/update-table.vue ================================================ ================================================ FILE: tests/e2e/.eslintrc ================================================ { "plugins": [ "cypress" ], "env": { "mocha": true, "cypress/globals": true }, "rules": { "strict": "off" } } ================================================ FILE: tests/e2e/plugins/index.js ================================================ // https://docs.cypress.io/guides/guides/plugins-guide.html module.exports = (on, config) => Object.assign({}, config, { fixturesFolder: 'tests/e2e/fixtures', integrationFolder: 'tests/e2e/specs', screenshotsFolder: 'tests/e2e/screenshots', videosFolder: 'tests/e2e/videos', supportFile: 'tests/e2e/support/index.js' }) ================================================ FILE: tests/e2e/specs/test.js ================================================ // https://docs.cypress.io/api/introduction/api.html describe('My First Test', () => { it('Visits the app root url', () => { cy.visit('/') cy.contains('h1', 'Welcome to Your Vue.js App') }) }) ================================================ FILE: tests/e2e/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** // // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) ================================================ FILE: tests/e2e/support/index.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands' // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: tests/unit/.eslintrc.js ================================================ module.exports = { env: { mocha: true }, rules: { 'import/no-extraneous-dependencies': 'off' } } ================================================ FILE: tests/unit/HelloWorld.spec.js ================================================ import { expect } from 'chai' import { shallow } from '@vue/test-utils' import HelloWorld from '@/components/HelloWorld.vue' describe('HelloWorld.vue', () => { it('renders props.msg when passed', () => { const msg = 'new message' const wrapper = shallow(HelloWorld, { propsData: { msg } }) expect(wrapper.text()).to.include(msg) }) }) ================================================ FILE: vue.config.js ================================================ const path = require('path') const resolve = dir => { return path.join(__dirname, dir) } // 项目部署基础 // 默认情况下,我们假设你的应用将被部署在域的根目录下, // 例如:https://www.my-app.com/ // 默认:'/' // 如果您的应用程序部署在子路径中,则需要在这指定子路径 // 例如:https://www.foobar.com/my-app/ // 需要将它改为'/my-app/' // iview-admin线上演示打包路径: https://file.iviewui.com/admin-dist/ const BASE_URL = process.env.NODE_ENV === 'production' ? '/' : '/' module.exports = { // Project deployment base // By default we assume your app will be deployed at the root of a domain, // e.g. https://www.my-app.com/ // If your app is deployed at a sub-path, you will need to specify that // sub-path here. For example, if your app is deployed at // https://www.foobar.com/my-app/ // then change this to '/my-app/' baseUrl: BASE_URL, // tweak internal webpack configuration. // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md // 如果你不需要使用eslint,把lintOnSave设为false即可 lintOnSave: true, chainWebpack: config => { config.resolve.alias .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components')) .set('_c', resolve('src/components')) }, // 设为false打包时不生成.map文件 productionSourceMap: false // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串 // devServer: { // proxy: 'localhost:3000' // } }