├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ └── 404_images
│ │ ├── 404.png
│ │ └── 404_cloud.png
├── components
│ ├── Breadcrumb
│ │ └── index.vue
│ ├── Hamburger
│ │ └── index.vue
│ └── SvgIcon
│ │ └── index.vue
├── icons
│ ├── index.js
│ ├── svg
│ │ ├── dashboard.svg
│ │ ├── example.svg
│ │ ├── eye-open.svg
│ │ ├── eye.svg
│ │ ├── form.svg
│ │ ├── link.svg
│ │ ├── nested.svg
│ │ ├── password.svg
│ │ ├── table.svg
│ │ ├── tree.svg
│ │ └── user.svg
│ └── svgo.yml
├── layout
│ ├── components
│ │ ├── AppMain.vue
│ │ ├── Navbar.vue
│ │ ├── Sidebar
│ │ │ ├── FixiOSBug.js
│ │ │ ├── Item.vue
│ │ │ ├── Link.vue
│ │ │ ├── Logo.vue
│ │ │ ├── SidebarItem.vue
│ │ │ └── index.vue
│ │ └── index.js
│ ├── index.vue
│ └── mixin
│ │ └── ResizeHandler.js
├── main.js
├── router
│ └── index.js
├── settings.js
├── store
│ ├── getters.js
│ ├── index.js
│ └── modules
│ │ ├── app.js
│ │ └── settings.js
├── styles
│ ├── element-ui.scss
│ ├── index.scss
│ ├── mixin.scss
│ ├── sidebar.scss
│ ├── transition.scss
│ └── variables.scss
├── utils
│ ├── get-page-title.js
│ ├── index.js
│ ├── request.js
│ └── validate.js
└── views
│ ├── 404.vue
│ ├── dashboard
│ └── index.vue
│ ├── login
│ └── index.vue
│ └── tree
│ └── index.vue
└── vue.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Naccl (https://naccl.top/)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # my-vue-admin-template
2 |
3 | 此模板基于[vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)修改
4 |
5 | 去掉了权限控制等我不需要的功能,主要保留**后台管理界面样式**
6 |
7 | 最匹配我个人需要的基础模板
8 |
9 | ## Project setup
10 | ```
11 | npm install
12 | ```
13 |
14 | ### Compiles and hot-reloads for development
15 | ```
16 | npm run serve
17 | ```
18 |
19 | ### Compiles and minifies for production
20 | ```
21 | npm run build
22 | ```
23 |
24 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "my-vue-admin-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "axios": "^0.24.0",
11 | "core-js": "^3.6.5",
12 | "element-ui": "^2.15.6",
13 | "normalize.css": "7.0.0",
14 | "nprogress": "^0.2.0",
15 | "vue": "^2.6.11",
16 | "vue-router": "^3.2.0",
17 | "vuex": "^3.4.0"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "~4.5.0",
21 | "@vue/cli-plugin-router": "~4.5.0",
22 | "@vue/cli-plugin-vuex": "~4.5.0",
23 | "@vue/cli-service": "~4.5.0",
24 | "sass": "1.26.8",
25 | "sass-loader": "8.0.2",
26 | "svg-sprite-loader": "4.1.3",
27 | "svgo": "1.2.2",
28 | "vue-template-compiler": "^2.6.11"
29 | },
30 | "browserslist": [
31 | "> 1%",
32 | "last 2 versions",
33 | "not dead"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Naccl/my-vue-admin-template/21ce2e07978f1f3cbdca524c07048d6a58e382d3/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/src/assets/404_images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Naccl/my-vue-admin-template/21ce2e07978f1f3cbdca524c07048d6a58e382d3/src/assets/404_images/404.png
--------------------------------------------------------------------------------
/src/assets/404_images/404_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Naccl/my-vue-admin-template/21ce2e07978f1f3cbdca524c07048d6a58e382d3/src/assets/404_images/404_cloud.png
--------------------------------------------------------------------------------
/src/components/Breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ item.meta.title }}
6 | {{ item.meta.title }}
7 |
8 |
9 |
10 |
11 |
12 |
65 |
66 |
79 |
--------------------------------------------------------------------------------
/src/components/Hamburger/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
32 |
33 |
45 |
--------------------------------------------------------------------------------
/src/components/SvgIcon/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
47 |
48 |
63 |
--------------------------------------------------------------------------------
/src/icons/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import SvgIcon from '@/components/SvgIcon'// svg component
3 |
4 | // register globally
5 | Vue.component('svg-icon', SvgIcon)
6 |
7 | const req = require.context('./svg', false, /\.svg$/)
8 | const requireAll = requireContext => requireContext.keys().map(requireContext)
9 | requireAll(req)
10 |
--------------------------------------------------------------------------------
/src/icons/svg/dashboard.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/example.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye-open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/eye.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/nested.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/password.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svg/user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/svgo.yml:
--------------------------------------------------------------------------------
1 | # replace default config
2 |
3 | # multipass: true
4 | # full: true
5 |
6 | plugins:
7 |
8 | # - name
9 | #
10 | # or:
11 | # - name: false
12 | # - name: true
13 | #
14 | # or:
15 | # - name:
16 | # param1: 1
17 | # param2: 2
18 |
19 | - removeAttrs:
20 | attrs:
21 | - 'fill'
22 | - 'fill-rule'
23 |
--------------------------------------------------------------------------------
/src/layout/components/AppMain.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
19 |
20 |
32 |
33 |
41 |
--------------------------------------------------------------------------------
/src/layout/components/Navbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
30 |
31 |
58 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/FixiOSBug.js:
--------------------------------------------------------------------------------
1 | export default {
2 | computed: {
3 | device() {
4 | return this.$store.state.app.device
5 | }
6 | },
7 | mounted() {
8 | // In order to fix the click on menu on the ios device will trigger the mouseleave bug
9 | // https://github.com/PanJiaChen/vue-element-admin/issues/1135
10 | this.fixBugIniOS()
11 | },
12 | methods: {
13 | fixBugIniOS() {
14 | const $subMenu = this.$refs.subMenu
15 | if ($subMenu) {
16 | const handleMouseleave = $subMenu.handleMouseleave
17 | $subMenu.handleMouseleave = (e) => {
18 | if (this.device === 'mobile') {
19 | return
20 | }
21 | handleMouseleave(e)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Item.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
42 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Link.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
44 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
33 |
34 |
83 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/SidebarItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 |
96 |
--------------------------------------------------------------------------------
/src/layout/components/Sidebar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
64 |
65 |
--------------------------------------------------------------------------------
/src/layout/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Navbar } from './Navbar'
2 | export { default as Sidebar } from './Sidebar'
3 | export { default as AppMain } from './AppMain'
4 |
--------------------------------------------------------------------------------
/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
52 |
53 |
94 |
--------------------------------------------------------------------------------
/src/layout/mixin/ResizeHandler.js:
--------------------------------------------------------------------------------
1 | import store from '@/store'
2 |
3 | const { body } = document
4 | const WIDTH = 992 // refer to Bootstrap's responsive design
5 |
6 | export default {
7 | watch: {
8 | $route(route) {
9 | if (this.device === 'mobile' && this.sidebar.opened) {
10 | store.dispatch('app/closeSideBar', { withoutAnimation: false })
11 | }
12 | }
13 | },
14 | beforeMount() {
15 | window.addEventListener('resize', this.$_resizeHandler)
16 | },
17 | beforeDestroy() {
18 | window.removeEventListener('resize', this.$_resizeHandler)
19 | },
20 | mounted() {
21 | const isMobile = this.$_isMobile()
22 | if (isMobile) {
23 | store.dispatch('app/toggleDevice', 'mobile')
24 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
25 | }
26 | },
27 | methods: {
28 | // use $_ for mixins properties
29 | // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
30 | $_isMobile() {
31 | const rect = body.getBoundingClientRect()
32 | return rect.width - 1 < WIDTH
33 | },
34 | $_resizeHandler() {
35 | if (!document.hidden) {
36 | const isMobile = this.$_isMobile()
37 | store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
38 |
39 | if (isMobile) {
40 | store.dispatch('app/closeSideBar', { withoutAnimation: true })
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 |
6 | import 'normalize.css/normalize.css' // A modern alternative to CSS resets
7 | import ElementUI from 'element-ui'
8 | import 'element-ui/lib/theme-chalk/index.css'
9 | import '@/styles/index.scss' // global css
10 | import '@/icons' // icon
11 |
12 | Vue.use(ElementUI)
13 |
14 | Vue.config.productionTip = false
15 |
16 | new Vue({
17 | router,
18 | store,
19 | render: h => h(App)
20 | }).$mount('#app')
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import Layout from '@/layout'
4 | import NProgress from 'nprogress'
5 | import 'nprogress/nprogress.css'
6 | import getPageTitle from '@/utils/get-page-title'
7 |
8 | Vue.use(VueRouter)
9 |
10 | const routes = [
11 | {
12 | path: '/login',
13 | component: () => import('@/views/login/index'),
14 | hidden: true
15 | },
16 | {
17 | path: '/404',
18 | component: () => import('@/views/404'),
19 | hidden: true
20 | },
21 | {
22 | path: '/',
23 | component: Layout,
24 | redirect: '/dashboard',
25 | children: [
26 | {
27 | path: 'dashboard',
28 | name: 'Dashboard',
29 | component: () => import('@/views/dashboard/index'),
30 | meta: {title: 'Dashboard', icon: 'dashboard'}
31 | }
32 | ]
33 | },
34 | {
35 | path: '/example',
36 | component: Layout,
37 | redirect: '/example/table',
38 | name: 'Example',
39 | meta: {title: 'Example', icon: 'el-icon-s-help'},
40 | children: [
41 | {
42 | path: 'tree',
43 | name: 'Tree',
44 | component: () => import('@/views/tree/index'),
45 | meta: {title: 'Tree', icon: 'tree'}
46 | },
47 | {
48 | path: 'tree',
49 | name: 'Tree',
50 | component: () => import('@/views/tree/index'),
51 | meta: {title: 'Tree', icon: 'tree'}
52 | }
53 | ]
54 | },
55 |
56 | // 404 page must be placed at the end !!!
57 | {path: '*', redirect: '/404', hidden: true}
58 | ]
59 |
60 | const router = new VueRouter({
61 | mode: 'history',
62 | base: process.env.BASE_URL,
63 | routes
64 | })
65 |
66 | router.beforeEach(async (to, from, next) => {
67 | NProgress.start()
68 | document.title = getPageTitle(to.meta.title)
69 | next()
70 | })
71 |
72 | router.afterEach(() => {
73 | NProgress.done()
74 | })
75 |
76 |
77 | export default router
--------------------------------------------------------------------------------
/src/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * @type {string}
4 | * @description page title
5 | */
6 | title: 'My Vue Admin Template',
7 |
8 | /**
9 | * @type {string}
10 | * @description logo URL
11 | */
12 | logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png',
13 |
14 | /**
15 | * @type {boolean} true | false
16 | * @description Whether fix the header
17 | */
18 | fixedHeader: true,
19 |
20 | /**
21 | * @type {boolean} true | false
22 | * @description Whether show the logo in sidebar
23 | */
24 | sidebarLogo: true,
25 |
26 | /**
27 | * @type {Array}
28 | * @description 默认展开的父级菜单
29 | */
30 | defaultOpeneds: ['/example']
31 | }
--------------------------------------------------------------------------------
/src/store/getters.js:
--------------------------------------------------------------------------------
1 | const getters = {
2 | sidebar: state => state.app.sidebar,
3 | device: state => state.app.device,
4 | }
5 | export default getters
6 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import getters from './getters'
4 | import app from './modules/app'
5 | import settings from './modules/settings'
6 |
7 | Vue.use(Vuex)
8 |
9 | export default new Vuex.Store({
10 | modules: {
11 | app,
12 | settings,
13 | },
14 | getters
15 | })
16 |
--------------------------------------------------------------------------------
/src/store/modules/app.js:
--------------------------------------------------------------------------------
1 | const state = {
2 | sidebar: {
3 | opened: true,
4 | withoutAnimation: false
5 | },
6 | device: 'desktop'
7 | }
8 |
9 | const mutations = {
10 | TOGGLE_SIDEBAR: state => {
11 | state.sidebar.opened = !state.sidebar.opened
12 | state.sidebar.withoutAnimation = false
13 | },
14 | CLOSE_SIDEBAR: (state, withoutAnimation) => {
15 | state.sidebar.opened = false
16 | state.sidebar.withoutAnimation = withoutAnimation
17 | },
18 | TOGGLE_DEVICE: (state, device) => {
19 | state.device = device
20 | }
21 | }
22 |
23 | const actions = {
24 | toggleSideBar({ commit }) {
25 | commit('TOGGLE_SIDEBAR')
26 | },
27 | closeSideBar({ commit }, { withoutAnimation }) {
28 | commit('CLOSE_SIDEBAR', withoutAnimation)
29 | },
30 | toggleDevice({ commit }, device) {
31 | commit('TOGGLE_DEVICE', device)
32 | }
33 | }
34 |
35 | export default {
36 | namespaced: true,
37 | state,
38 | mutations,
39 | actions
40 | }
41 |
--------------------------------------------------------------------------------
/src/store/modules/settings.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const {title, logo, fixedHeader, sidebarLogo, defaultOpeneds} = defaultSettings
4 |
5 | const state = {
6 | title: title,
7 | logo: logo,
8 | fixedHeader: fixedHeader,
9 | sidebarLogo: sidebarLogo,
10 | defaultOpeneds: defaultOpeneds,
11 | }
12 |
13 | const mutations = {
14 | CHANGE_SETTING: (state, {key, value}) => {
15 | // eslint-disable-next-line no-prototype-builtins
16 | if (state.hasOwnProperty(key)) {
17 | state[key] = value
18 | }
19 | }
20 | }
21 |
22 | const actions = {
23 | changeSetting({commit}, data) {
24 | commit('CHANGE_SETTING', data)
25 | }
26 | }
27 |
28 | export default {
29 | namespaced: true,
30 | state,
31 | mutations,
32 | actions
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/styles/element-ui.scss:
--------------------------------------------------------------------------------
1 | // cover some element-ui styles
2 |
3 | .el-breadcrumb__inner,
4 | .el-breadcrumb__inner a {
5 | font-weight: 400 !important;
6 | }
7 |
8 | .el-upload {
9 | input[type="file"] {
10 | display: none !important;
11 | }
12 | }
13 |
14 | .el-upload__input {
15 | display: none;
16 | }
17 |
18 |
19 | // to fixed https://github.com/ElemeFE/element/issues/2461
20 | .el-dialog {
21 | transform: none;
22 | left: 0;
23 | position: relative;
24 | margin: 0 auto;
25 | }
26 |
27 | // refine element ui upload
28 | .upload-container {
29 | .el-upload {
30 | width: 100%;
31 |
32 | .el-upload-dragger {
33 | width: 100%;
34 | height: 200px;
35 | }
36 | }
37 | }
38 |
39 | // dropdown
40 | .el-dropdown-menu {
41 | a {
42 | display: block
43 | }
44 | }
45 |
46 | // to fix el-date-picker css style
47 | .el-range-separator {
48 | box-sizing: content-box;
49 | }
50 |
--------------------------------------------------------------------------------
/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './variables.scss';
2 | @import './mixin.scss';
3 | @import './transition.scss';
4 | @import './element-ui.scss';
5 | @import './sidebar.scss';
6 |
7 | body {
8 | height: 100%;
9 | -moz-osx-font-smoothing: grayscale;
10 | -webkit-font-smoothing: antialiased;
11 | text-rendering: optimizeLegibility;
12 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 | }
14 |
15 | label {
16 | font-weight: 700;
17 | }
18 |
19 | html {
20 | height: 100%;
21 | box-sizing: border-box;
22 | }
23 |
24 | #app {
25 | height: 100%;
26 | }
27 |
28 | *,
29 | *:before,
30 | *:after {
31 | box-sizing: inherit;
32 | }
33 |
34 | a:focus,
35 | a:active {
36 | outline: none;
37 | }
38 |
39 | a,
40 | a:focus,
41 | a:hover {
42 | cursor: pointer;
43 | color: inherit;
44 | text-decoration: none;
45 | }
46 |
47 | div:focus {
48 | outline: none;
49 | }
50 |
51 | .clearfix {
52 | &:after {
53 | visibility: hidden;
54 | display: block;
55 | font-size: 0;
56 | content: " ";
57 | clear: both;
58 | height: 0;
59 | }
60 | }
61 |
62 | // main-container global css
63 | .app-container {
64 | padding: 20px;
65 | }
66 |
--------------------------------------------------------------------------------
/src/styles/mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin clearfix {
2 | &:after {
3 | content: "";
4 | display: table;
5 | clear: both;
6 | }
7 | }
8 |
9 | @mixin scrollBar {
10 | &::-webkit-scrollbar-track-piece {
11 | background: #d3dce6;
12 | }
13 |
14 | &::-webkit-scrollbar {
15 | width: 6px;
16 | }
17 |
18 | &::-webkit-scrollbar-thumb {
19 | background: #99a9bf;
20 | border-radius: 20px;
21 | }
22 | }
23 |
24 | @mixin relative {
25 | position: relative;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
--------------------------------------------------------------------------------
/src/styles/sidebar.scss:
--------------------------------------------------------------------------------
1 | #app {
2 |
3 | .main-container {
4 | min-height: 100%;
5 | transition: margin-left .28s;
6 | margin-left: $sideBarWidth;
7 | position: relative;
8 | }
9 |
10 | .sidebar-container {
11 | transition: width 0.28s;
12 | width: $sideBarWidth !important;
13 | background-color: $menuBg;
14 | height: 100%;
15 | position: fixed;
16 | font-size: 0px;
17 | top: 0;
18 | bottom: 0;
19 | left: 0;
20 | z-index: 1703;//1001 mavon-editor最高z-index: 1600
21 | overflow: hidden;
22 |
23 | // reset element-ui css
24 | .horizontal-collapse-transition {
25 | transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
26 | }
27 |
28 | .scrollbar-wrapper {
29 | overflow-x: hidden !important;
30 | }
31 |
32 | .el-scrollbar__bar.is-vertical {
33 | right: 0px;
34 | }
35 |
36 | .el-scrollbar {
37 | height: 100%;
38 | }
39 |
40 | &.has-logo {
41 | .el-scrollbar {
42 | height: calc(100% - 50px);
43 | }
44 | }
45 |
46 | .is-horizontal {
47 | display: none;
48 | }
49 |
50 | a {
51 | display: inline-block;
52 | width: 100%;
53 | overflow: hidden;
54 | }
55 |
56 | .svg-icon {
57 | margin-left: -2px;
58 | margin-right: 12px;
59 | font-size: 18px;
60 | //margin-right: 16px;
61 | }
62 |
63 | .sub-el-icon {
64 | margin-right: 12px;
65 | margin-left: -2px;
66 | }
67 |
68 | .el-menu {
69 | border: none;
70 | height: 100%;
71 | width: 100% !important;
72 | }
73 |
74 | // menu hover
75 | .submenu-title-noDropdown,
76 | .el-submenu__title {
77 | &:hover {
78 | background-color: $menuHover !important;
79 | }
80 | }
81 |
82 | .is-active>.el-submenu__title {
83 | color: $subMenuActiveText !important;
84 | }
85 |
86 | & .nest-menu .el-submenu>.el-submenu__title,
87 | & .el-submenu .el-menu-item {
88 | min-width: $sideBarWidth !important;
89 | background-color: $subMenuBg !important;
90 |
91 | &:hover {
92 | background-color: $subMenuHover !important;
93 | }
94 | }
95 | }
96 |
97 | .hideSidebar {
98 | .sidebar-container {
99 | width: 54px !important;
100 | }
101 |
102 | .main-container {
103 | margin-left: 54px;
104 | }
105 |
106 | .submenu-title-noDropdown {
107 | padding: 0 !important;
108 | position: relative;
109 |
110 | .el-tooltip {
111 | padding: 0 !important;
112 |
113 | .svg-icon {
114 | margin-left: 20px;
115 | }
116 |
117 | .sub-el-icon {
118 | margin-left: 19px;
119 | }
120 | }
121 | }
122 |
123 | .el-submenu {
124 | overflow: hidden;
125 |
126 | &>.el-submenu__title {
127 | padding: 0 !important;
128 |
129 | .svg-icon {
130 | margin-left: 20px;
131 | }
132 |
133 | .sub-el-icon {
134 | margin-left: 19px;
135 | }
136 |
137 | .el-submenu__icon-arrow {
138 | display: none;
139 | }
140 | }
141 | }
142 |
143 | .el-menu--collapse {
144 | .el-submenu {
145 | &>.el-submenu__title {
146 | &>span {
147 | height: 0;
148 | width: 0;
149 | overflow: hidden;
150 | visibility: hidden;
151 | display: inline-block;
152 | }
153 | }
154 | }
155 | }
156 | }
157 |
158 | .el-menu--collapse .el-menu .el-submenu {
159 | min-width: $sideBarWidth !important;
160 | }
161 |
162 | // mobile responsive
163 | .mobile {
164 | .main-container {
165 | margin-left: 0px;
166 | }
167 |
168 | .sidebar-container {
169 | transition: transform .28s;
170 | width: $sideBarWidth !important;
171 | }
172 |
173 | &.hideSidebar {
174 | .sidebar-container {
175 | pointer-events: none;
176 | transition-duration: 0.3s;
177 | transform: translate3d(-$sideBarWidth, 0, 0);
178 | }
179 | }
180 | }
181 |
182 | .withoutAnimation {
183 |
184 | .main-container,
185 | .sidebar-container {
186 | transition: none;
187 | }
188 | }
189 | }
190 |
191 | // when menu collapsed
192 | .el-menu--vertical {
193 | &>.el-menu {
194 | .svg-icon {
195 | margin-right: 16px;
196 | }
197 | .sub-el-icon {
198 | margin-right: 12px;
199 | margin-left: -2px;
200 | }
201 | }
202 |
203 | .nest-menu .el-submenu>.el-submenu__title,
204 | .el-menu-item {
205 | &:hover {
206 | // you can use $subMenuHover
207 | background-color: $menuHover !important;
208 | }
209 | }
210 |
211 | // the scroll bar appears when the subMenu is too long
212 | >.el-menu--popup {
213 | max-height: 100vh;
214 | overflow-y: auto;
215 |
216 | &::-webkit-scrollbar-track-piece {
217 | background: #d3dce6;
218 | }
219 |
220 | &::-webkit-scrollbar {
221 | width: 6px;
222 | }
223 |
224 | &::-webkit-scrollbar-thumb {
225 | background: #99a9bf;
226 | border-radius: 20px;
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/styles/transition.scss:
--------------------------------------------------------------------------------
1 | // global transition css
2 |
3 | /* fade */
4 | .fade-enter-active,
5 | .fade-leave-active {
6 | transition: opacity 0.28s;
7 | }
8 |
9 | .fade-enter,
10 | .fade-leave-active {
11 | opacity: 0;
12 | }
13 |
14 | /* fade-transform */
15 | .fade-transform-leave-active,
16 | .fade-transform-enter-active {
17 | transition: all .25s;
18 | }
19 |
20 | .fade-transform-enter {
21 | opacity: 0;
22 | transform: translateX(-10px);
23 | }
24 |
25 | .fade-transform-leave-to {
26 | opacity: 0;
27 | transform: translateX(10px);
28 | }
29 |
30 | /* breadcrumb transition */
31 | .breadcrumb-enter-active,
32 | .breadcrumb-leave-active {
33 | transition: all .25s;
34 | }
35 |
36 | .breadcrumb-enter,
37 | .breadcrumb-leave-active {
38 | opacity: 0;
39 | transform: translateX(20px);
40 | }
41 |
42 | .breadcrumb-move {
43 | transition: all .5s;
44 | }
45 |
46 | .breadcrumb-leave-active {
47 | position: absolute;
48 | }
49 |
--------------------------------------------------------------------------------
/src/styles/variables.scss:
--------------------------------------------------------------------------------
1 | // sidebar
2 | $menuText:#bfcbd9;
3 | $menuActiveText:#409EFF;
4 | $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
5 |
6 | $menuBg:#304156;
7 | $menuHover:#263445;
8 |
9 | $subMenuBg:#1f2d3d;
10 | $subMenuHover:#001528;
11 |
12 | $sideBarWidth: 210px;
13 |
14 | // the :export directive is the magic sauce for webpack
15 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
16 | :export {
17 | menuText: $menuText;
18 | menuActiveText: $menuActiveText;
19 | subMenuActiveText: $subMenuActiveText;
20 | menuBg: $menuBg;
21 | menuHover: $menuHover;
22 | subMenuBg: $subMenuBg;
23 | subMenuHover: $subMenuHover;
24 | sideBarWidth: $sideBarWidth;
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/get-page-title.js:
--------------------------------------------------------------------------------
1 | import defaultSettings from '@/settings'
2 |
3 | const title = defaultSettings.title
4 |
5 | export default function getPageTitle(pageTitle) {
6 | if (pageTitle) {
7 | return `${pageTitle} - ${title}`
8 | }
9 | return `${title}`
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * Parse the time to string
7 | * @param {(Object|string|number)} time
8 | * @param {string} cFormat
9 | * @returns {string | null}
10 | */
11 | export function parseTime(time, cFormat) {
12 | if (arguments.length === 0 || !time) {
13 | return null
14 | }
15 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
16 | let date
17 | if (typeof time === 'object') {
18 | date = time
19 | } else {
20 | if ((typeof time === 'string')) {
21 | if ((/^[0-9]+$/.test(time))) {
22 | // support "1548221490638"
23 | time = parseInt(time)
24 | } else {
25 | // support safari
26 | // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
27 | time = time.replace(new RegExp(/-/gm), '/')
28 | }
29 | }
30 |
31 | if ((typeof time === 'number') && (time.toString().length === 10)) {
32 | time = time * 1000
33 | }
34 | date = new Date(time)
35 | }
36 | const formatObj = {
37 | y: date.getFullYear(),
38 | m: date.getMonth() + 1,
39 | d: date.getDate(),
40 | h: date.getHours(),
41 | i: date.getMinutes(),
42 | s: date.getSeconds(),
43 | a: date.getDay()
44 | }
45 | const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
46 | const value = formatObj[key]
47 | // Note: getDay() returns 0 on Sunday
48 | if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
49 | return value.toString().padStart(2, '0')
50 | })
51 | return time_str
52 | }
53 |
54 | /**
55 | * @param {number} time
56 | * @param {string} option
57 | * @returns {string}
58 | */
59 | export function formatTime(time, option) {
60 | if (('' + time).length === 10) {
61 | time = parseInt(time) * 1000
62 | } else {
63 | time = +time
64 | }
65 | const d = new Date(time)
66 | const now = Date.now()
67 |
68 | const diff = (now - d) / 1000
69 |
70 | if (diff < 30) {
71 | return '刚刚'
72 | } else if (diff < 3600) {
73 | // less 1 hour
74 | return Math.ceil(diff / 60) + '分钟前'
75 | } else if (diff < 3600 * 24) {
76 | return Math.ceil(diff / 3600) + '小时前'
77 | } else if (diff < 3600 * 24 * 2) {
78 | return '1天前'
79 | }
80 | if (option) {
81 | return parseTime(time, option)
82 | } else {
83 | return (
84 | d.getMonth() +
85 | 1 +
86 | '月' +
87 | d.getDate() +
88 | '日' +
89 | d.getHours() +
90 | '时' +
91 | d.getMinutes() +
92 | '分'
93 | )
94 | }
95 | }
96 |
97 | /**
98 | * @param {string} url
99 | * @returns {Object}
100 | */
101 | export function param2Obj(url) {
102 | const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
103 | if (!search) {
104 | return {}
105 | }
106 | const obj = {}
107 | const searchArr = search.split('&')
108 | searchArr.forEach(v => {
109 | const index = v.indexOf('=')
110 | if (index !== -1) {
111 | const name = v.substring(0, index)
112 | const val = v.substring(index + 1, v.length)
113 | obj[name] = val
114 | }
115 | })
116 | return obj
117 | }
118 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import {Message} from 'element-ui'
3 | import NProgress from 'nprogress'
4 | import 'nprogress/nprogress.css'
5 |
6 | const request = axios.create({
7 | baseURL: process.env.VUE_APP_BASE_API,
8 | timeout: 5000
9 | })
10 |
11 | // request interceptor
12 | request.interceptors.request.use(config => {
13 | NProgress.start()
14 | return config
15 | },
16 | error => {
17 | console.log(error)
18 | return Promise.reject(error)
19 | }
20 | )
21 |
22 | // response interceptor
23 | request.interceptors.response.use(response => {
24 | NProgress.done()
25 | const res = response.data
26 | if (res.code !== 200) {
27 | Message({
28 | message: res.msg || 'Error',
29 | type: 'error',
30 | duration: 5000
31 | })
32 | return Promise.reject(new Error(res.message || 'Error'))
33 | }
34 | return res
35 | },
36 | error => {
37 | console.error(error)
38 | Message({
39 | message: error.message,
40 | type: 'error',
41 | duration: 5000
42 | })
43 | return Promise.reject(error)
44 | }
45 | )
46 |
47 | export default request
--------------------------------------------------------------------------------
/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by PanJiaChen on 16/11/18.
3 | */
4 |
5 | /**
6 | * @param {string} path
7 | * @returns {Boolean}
8 | */
9 | export function isExternal(path) {
10 | return /^(https?:|mailto:|tel:)/.test(path)
11 | }
--------------------------------------------------------------------------------
/src/views/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
OOPS!
12 |
15 |
{{ message }}
16 |
Please check that the URL you entered is correct, or click the button below to return to the homepage.
17 |
Back to home
18 |
19 |
20 |
21 |
22 |
23 |
34 |
35 |
229 |
--------------------------------------------------------------------------------
/src/views/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
13 |
25 |
--------------------------------------------------------------------------------
/src/views/login/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Login Form
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
38 |
39 |
40 |
41 |
42 | Login
43 |
44 |
45 |
46 |
47 |
103 |
104 |
150 |
151 |
213 |
--------------------------------------------------------------------------------
/src/views/tree/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
78 |
79 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const defaultSettings = require('./src/settings.js')
4 |
5 | function resolve(dir) {
6 | return path.join(__dirname, dir)
7 | }
8 |
9 | const name = defaultSettings.title // page title
10 |
11 | // If your port is set to 80,
12 | // use administrator privileges to execute the command line.
13 | // For example, Mac: sudo npm run
14 | // You can change the port by the following methods:
15 | // port = 9528 npm run dev OR npm run dev --port = 9528
16 | const port = process.env.port || process.env.npm_config_port || 9001 // dev port
17 |
18 | // All configuration item explanations can be find in https://cli.vuejs.org/config/
19 | module.exports = {
20 | /**
21 | * You will need to set publicPath if you plan to deploy your site under a sub path,
22 | * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
23 | * then publicPath should be set to "/bar/".
24 | * In most cases please use '/' !!!
25 | * Detail: https://cli.vuejs.org/config/#publicpath
26 | */
27 | publicPath: '/',
28 | outputDir: 'dist',
29 | assetsDir: 'static',
30 | lintOnSave: process.env.NODE_ENV === 'development',
31 | productionSourceMap: false,
32 | devServer: {
33 | port: port,
34 | open: true,
35 | overlay: {
36 | warnings: false,
37 | errors: true
38 | },
39 | },
40 | configureWebpack: {
41 | // provide the app's title in webpack's name field, so that
42 | // it can be accessed in index.html to inject the correct title.
43 | name: name,
44 | resolve: {
45 | alias: {
46 | '@': resolve('src')
47 | }
48 | }
49 | },
50 | chainWebpack(config) {
51 | // it can improve the speed of the first screen, it is recommended to turn on preload
52 | config.plugin('preload').tap(() => [
53 | {
54 | rel: 'preload',
55 | // to ignore runtime.js
56 | // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
57 | fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
58 | include: 'initial'
59 | }
60 | ])
61 |
62 | // when there are many pages, it will cause too many meaningless requests
63 | config.plugins.delete('prefetch')
64 |
65 | // set svg-sprite-loader
66 | config.module
67 | .rule('svg')
68 | .exclude.add(resolve('src/icons'))
69 | .end()
70 | config.module
71 | .rule('icons')
72 | .test(/\.svg$/)
73 | .include.add(resolve('src/icons'))
74 | .end()
75 | .use('svg-sprite-loader')
76 | .loader('svg-sprite-loader')
77 | .options({
78 | symbolId: 'icon-[name]'
79 | })
80 | .end()
81 |
82 | config
83 | .when(process.env.NODE_ENV !== 'development',
84 | config => {
85 | config
86 | .plugin('ScriptExtHtmlWebpackPlugin')
87 | .after('html')
88 | .use('script-ext-html-webpack-plugin', [{
89 | // `runtime` must same as runtimeChunk name. default is `runtime`
90 | inline: /runtime\..*\.js$/
91 | }])
92 | .end()
93 | config
94 | .optimization.splitChunks({
95 | chunks: 'all',
96 | cacheGroups: {
97 | libs: {
98 | name: 'chunk-libs',
99 | test: /[\\/]node_modules[\\/]/,
100 | priority: 10,
101 | chunks: 'initial' // only package third parties that are initially dependent
102 | },
103 | elementUI: {
104 | name: 'chunk-elementUI', // split elementUI into a single package
105 | priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
106 | test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
107 | },
108 | commons: {
109 | name: 'chunk-commons',
110 | test: resolve('src/components'), // can customize your rules
111 | minChunks: 3, // minimum common number
112 | priority: 5,
113 | reuseExistingChunk: true
114 | }
115 | }
116 | })
117 | // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
118 | config.optimization.runtimeChunk('single')
119 | }
120 | )
121 | }
122 | }
123 |
--------------------------------------------------------------------------------