diff --git a/vue/template/element-ui-personal/component/Menu/MenuItem.vue b/vue/template/element-ui-personal/component/Menu/MenuItem.vue deleted file mode 100644 index ef8835b..0000000 --- a/vue/template/element-ui-personal/component/Menu/MenuItem.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - diff --git a/vue/template/element-ui-personal/component/Menu/Submenu.vue b/vue/template/element-ui-personal/component/Menu/Submenu.vue deleted file mode 100644 index f83a589..0000000 --- a/vue/template/element-ui-personal/component/Menu/Submenu.vue +++ /dev/null @@ -1,250 +0,0 @@ - diff --git a/vue/template/element-ui-personal/component/Menu/mixin.js b/vue/template/element-ui-personal/component/Menu/mixin.js deleted file mode 100644 index 71201c5..0000000 --- a/vue/template/element-ui-personal/component/Menu/mixin.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 在原基础上增加了 inlineIndent 属性,用于确定每级节点的单位缩进距离 - */ -export default { - inject: ['rootMenu'], - - props: { - inlineIndent: {type: Number, default: 20} - }, - - computed: { - indexPath() { - const path = [this.index] - let parent = this.$parent - while (parent.$options.componentName !== 'ElMenu') { - if (parent.index) { - path.unshift(parent.index) - } - parent = parent.$parent - } - return path - }, - - parentMenu() { - let parent = this.$parent - while ( - parent && - ['ElMenu', 'ElSubmenu'].indexOf(parent.$options.componentName) === -1 - ) { - parent = parent.$parent - } - return parent - }, - - paddingStyle() { - if (this.rootMenu.mode !== 'vertical') return {} - - let padding = this.inlineIndent - - if (!this.rootMenu.collapse) { - let parent = this.$parent - while (parent && parent.$options.componentName !== 'ElMenu') { - if (parent.$options.componentName === 'ElSubmenu') { - padding += this.inlineIndent - } - parent = parent.$parent - } - } - - return {'padding-left': `${padding}px`} - } - } -} diff --git a/vue/template/element-ui-personal/index.js b/vue/template/element-ui-personal/index.js index 1845aea..ac08fcf 100644 --- a/vue/template/element-ui-personal/index.js +++ b/vue/template/element-ui-personal/index.js @@ -1,13 +1,6 @@ -import MenuItem from "./component/Menu/MenuItem" -import Submenu from "./component/Menu/Submenu" import Message from "./component/Message" -const components = [MenuItem, Submenu] - export default function (Vue) { - components.forEach(component => { - Vue.component(component.name, component) - }) Vue.prototype.$message = Message } diff --git a/vue/template/package-lock.json b/vue/template/package-lock.json index ac1cabf..dbbf509 100644 --- a/vue/template/package-lock.json +++ b/vue/template/package-lock.json @@ -4464,6 +4464,11 @@ "integrity": "sha1-SGYSh1c9zFPjZsehrlLDoSDuybo=", "dev": true }, + "el-admin-layout": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/el-admin-layout/-/el-admin-layout-0.0.2.tgz", + "integrity": "sha512-F+5n9HzlbGyD3EbQi4Uk7RdMlZbSLmPAsw5aoK5pYmEtLJktZCOC70bGMNRESc38bGpADa7Cv1+8bwCruG1/uw==" + }, "electron-to-chromium": { "version": "1.3.613", "resolved": "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.613.tgz?cache=0&sync_timestamp=1606876399469&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.613.tgz", @@ -7105,11 +7110,6 @@ "resolved": "https://registry.npm.taobao.org/normalize-wheel/download/normalize-wheel-1.0.1.tgz", "integrity": "sha1-rsiGr/2wRQcNhWRH32Ls+GFG7EU=" }, - "normalize.css": { - "version": "8.0.1", - "resolved": "https://registry.npm.taobao.org/normalize.css/download/normalize.css-8.0.1.tgz", - "integrity": "sha1-m5iiCHOLnMJjTKrLxC0THJdIe/M=" - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npm.taobao.org/npm-run-path/download/npm-run-path-2.0.2.tgz?cache=0&sync_timestamp=1577053500910&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnpm-run-path%2Fdownload%2Fnpm-run-path-2.0.2.tgz", diff --git a/vue/template/package.json b/vue/template/package.json index f25af64..770c527 100644 --- a/vue/template/package.json +++ b/vue/template/package.json @@ -9,8 +9,8 @@ }, "dependencies": { "core-js": "^3.6.5", + "el-admin-layout": "0.0.2", "element-ui": "^2.13.2", - "normalize.css": "^8.0.1", "nprogress": "^0.2.0", "pako": "^1.0.11", "vue": "^2.6.11", diff --git a/vue/template/src/asset/style/cover/element-ui.scss b/vue/template/src/asset/style/cover/element-ui.scss index be01c89..9b5cfdb 100644 --- a/vue/template/src/asset/style/cover/element-ui.scss +++ b/vue/template/src/asset/style/cover/element-ui.scss @@ -9,54 +9,6 @@ } } -.el-menu { - width: 100%; - border: none !important; - overflow: hidden; - - &::-webkit-scrollbar { - display: none; - } - - //子级菜单激活时,父级同样高亮 - .el-menu-item.is-active, - .is-active > .el-submenu__title { - color: $--color-primary !important; - } - - //箭头图标与字体同色 - .el-submenu__title i { - color: inherit; - } - - //原先垂直时右侧展开箭头偏上 - .el-submenu__icon-arrow { - margin-top: -4px; - } - - //弹出菜单 - &.el-menu--popup { - max-height: 88vh; - min-width: 160px; - padding: 0; - margin-bottom: 0.5vh; - overflow-y: auto; - - .el-menu-item, - .el-submenu__title { - padding: 0 $menu-padding; - } - } - - &--horizontal > .el-menu-item:not(.is-disabled):hover, - &--horizontal > .el-menu-item:not(.is-disabled):focus, - &--horizontal > .el-submenu .el-submenu__title:hover, - &--horizontal .el-menu .el-menu-item, - &--horizontal .el-menu .el-submenu__title { - background-color: inherit; - } -} - .el-message { @include deep-shadow; @include max-width-on-mobile; diff --git a/vue/template/src/asset/style/cover/var.scss b/vue/template/src/asset/style/cover/var.scss index 1fa2751..808cf79 100644 --- a/vue/template/src/asset/style/cover/var.scss +++ b/vue/template/src/asset/style/cover/var.scss @@ -1,7 +1,4 @@ /*仅覆盖element-ui中的预设变量,不会被使用*/ -//el-menu的hover背景颜色 -$--menu-item-hover-fill: none; - //引入element的scss时需要覆盖字体路径 $--font-path: '~element-ui/lib/theme-chalk/fonts'; diff --git a/vue/template/src/asset/style/display.scss b/vue/template/src/asset/style/display.scss deleted file mode 100644 index a71f2f1..0000000 --- a/vue/template/src/asset/style/display.scss +++ /dev/null @@ -1,5 +0,0 @@ -@media only screen and (max-width: $--sm) { - .hidden-xs { - display: none !important - } -} diff --git a/vue/template/src/asset/style/index.scss b/vue/template/src/asset/style/index.scss index aeb17fc..4da7730 100644 --- a/vue/template/src/asset/style/index.scss +++ b/vue/template/src/asset/style/index.scss @@ -1,27 +1,27 @@ -@import "~normalize.css/normalize.css"; @import "./var.scss"; @import "./cover/var.scss"; @import "./mixin.scss"; @import "~element-ui/packages/theme-chalk/src/index.scss"; @import "~@ele/style/index.scss"; +//layout中的样式需要覆盖element-ui原定义,所以放element-ui的后面 +@import "~@/layout/style.scss"; @import "./cover/element-ui.scss"; -@import './display.scss'; -@import "./transition.scss"; @import "./nprogress.scss"; +html { + height: 100%; + box-sizing: border-box; +} + body { height: 100%; + margin: 0; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; -webkit-tap-highlight-color: transparent; } -html { - height: 100%; - box-sizing: border-box; -} - #app { height: 100%; } @@ -41,7 +41,6 @@ div:focus { a, a:focus, a:hover { - cursor: pointer; color: inherit; text-decoration: none; } @@ -57,7 +56,7 @@ a:hover { &-thumb { width: 6px; - //border-radius: 5px; + border-radius: 5px; background-color: rgba($--color-primary, 0.8); &:hover { diff --git a/vue/template/src/asset/style/transition.scss b/vue/template/src/asset/style/transition.scss deleted file mode 100644 index 7fdb04e..0000000 --- a/vue/template/src/asset/style/transition.scss +++ /dev/null @@ -1,31 +0,0 @@ -.right-out, -.left-out { - &-leave-active, - &-enter-active { - transition: all .2s; - } -} - -.right-out { - &-enter { - opacity: 0; - transform: translateX(-30px); - } - - &-leave-to { - opacity: 0; - transform: translateX(30px); - } -} - -.left-out { - &-enter { - opacity: 0; - transform: translateX(30px); - } - - &-leave-to { - opacity: 0; - transform: translateX(-30px); - } -} diff --git a/vue/template/src/asset/style/var.scss b/vue/template/src/asset/style/var.scss index 3335c78..947a849 100644 --- a/vue/template/src/asset/style/var.scss +++ b/vue/template/src/asset/style/var.scss @@ -1,7 +1,7 @@ @import "~element-ui/packages/theme-chalk/src/common/var"; //设定移动端的最大宽度,只能是number类型 -$max-mobile-width: 500px; +$max-mobile-width: $--sm; //覆盖element颜色 $--color-primary: #1890ff; @@ -9,76 +9,3 @@ $--color-success: #21BA45; $--color-warning: #FF5722; $--color-danger: #F44336; $--color-info: #009688; - -//菜单中icon的大小 -$menu-icon-size: 14px; -//菜单中icon和文字的距离 -$menu-icon-text-gap: 10px; -//菜单的padding -$menu-padding: $menu-icon-size + $menu-icon-text-gap; -//暗色菜单的背景色 -$menu-background-dark: #304156; -//暗色子级菜单的背景色 -$sub-menu-background-dark: darken($menu-background-dark, 5%); -//暗色主菜单的背景色(用于双层侧边栏的主菜单和顶部导航栏) -$root-menu-background-dark: #1F293D; -//亮色菜单的文字颜色 -$menu-text-color-light: $--color-text-primary; -//暗色菜单的文字颜色 -$menu-text-color-dark: rgba(255, 255, 255, .65); -//亮色菜单的文字hover颜色 -$menu-text-hover-color-light: $--color-primary; -//暗色菜单的文字hover颜色 -$menu-text-hover-color-dark: rgba(255, 255, 255, 1); - -//侧边栏宽度 -$aside-width: 208px; -//侧边栏折叠时的宽度 -$aside-collapse-width: $menu-icon-size + $menu-padding * 2; - -//导航栏高度 -$nav-height: 48px; -//导航栏的padding-left -$navbar-padding-left: 12px; -//亮色导航栏的功能项颜色 -$navbar-item-color-light: $menu-text-color-light; -//暗色导航栏的功能项颜色 -$navbar-item-color-dark: rgba(255, 255, 255, .85); -//亮色导航栏的功能项hover背景颜色 -$navbar-item-hover-color-light: rgba(255, 255, 255, .85); -//暗色导航栏的功能项hover背景颜色 -$navbar-item-hover-color-dark: lighten($root-menu-background-dark, 5%); - -//多页签栏高度 -$tags-view-height: 32px; - -//页头高度,用于max-view-height -$page-header-height: 44px; -//页头的上下padding -$page-header-padding: 12px; - -//路由页面的margin -$page-view-margin: 24px; - -//页脚高度,用于max-view-height -$page-footer-height: 64px; - -//暗色边框的颜色,用于nav-menu和侧边栏 -$--border-color-dark: darken($root-menu-background-dark, 5%); - -//dialog距离顶部的距离,用于的top属性、中滚动区域的最大高度 -$dialog-top: 100px; - -// the :export directive is the magic sauce for webpack -// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass -:export { - maxMobileWidth: $max-mobile-width; - primary: $--color-primary; - info: $--color-info; - success: $--color-success; - danger: $--color-danger; - warning: $--color-warning; - pageViewMargin: $page-view-margin; - dialogTop: $dialog-top; - menuPadding: $menu-padding; -} diff --git a/vue/template/src/component/checkbox/ColorCheckbox.vue b/vue/template/src/component/checkbox/ColorCheckbox.vue deleted file mode 100644 index d9c796b..0000000 --- a/vue/template/src/component/checkbox/ColorCheckbox.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/vue/template/src/component/checkbox/Group.vue b/vue/template/src/component/checkbox/Group.vue deleted file mode 100644 index 3e3c065..0000000 --- a/vue/template/src/component/checkbox/Group.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/vue/template/src/component/checkbox/ImgCheckbox.vue b/vue/template/src/component/checkbox/ImgCheckbox.vue deleted file mode 100644 index 38dff1d..0000000 --- a/vue/template/src/component/checkbox/ImgCheckbox.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/vue/template/src/component/menu/ContextMenu/index.vue b/vue/template/src/component/menu/ContextMenu/index.vue deleted file mode 100644 index ea23e8c..0000000 --- a/vue/template/src/component/menu/ContextMenu/index.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - diff --git a/vue/template/src/component/menu/NavMenu/index.vue b/vue/template/src/component/menu/NavMenu/index.vue deleted file mode 100644 index f3f6c1d..0000000 --- a/vue/template/src/component/menu/NavMenu/index.vue +++ /dev/null @@ -1,175 +0,0 @@ - - - diff --git a/vue/template/src/component/menu/NavMenu/item.vue b/vue/template/src/component/menu/NavMenu/item.vue deleted file mode 100644 index 781e37b..0000000 --- a/vue/template/src/component/menu/NavMenu/item.vue +++ /dev/null @@ -1,154 +0,0 @@ - diff --git a/vue/template/src/component/menu/NavMenu/style.scss b/vue/template/src/component/menu/NavMenu/style.scss deleted file mode 100644 index a1130cf..0000000 --- a/vue/template/src/component/menu/NavMenu/style.scss +++ /dev/null @@ -1,109 +0,0 @@ -@import "~@/asset/style/var"; - -.el-menu { - //图标的大小 - .icon { - font-size: $menu-icon-size; - } - - //图标与文字的间隔 - .icon + .menu-item-content { - margin-left: $menu-icon-text-gap; - } - - //菜单的高亮关键字 - .menu-highlight-result { - vertical-align: unset; - color: $--color-success; - } -} - -//原始样式使用 '>' 可能导致侧边栏折叠时还会显示右侧箭头图标 -//此样式只对垂直状态生效 -.el-menu--collapse .el-submenu .el-submenu__title .el-submenu__icon-arrow { - display: none; -} - -//hover时,菜单左侧图标放大(弹出菜单的父级不放大) -.el-menu-item:not(.popover-menu__title), -.el-submenu__title { - > .icon, - > .el-tooltip > .icon { - transition: transform .1s; - } - - &:hover > .icon, - &:hover > .el-tooltip > .icon { - transform: scale(1.2); - } -} - -//侧边栏垂直菜单 -.el-menu--vertical { - &.el-menu { - overflow-y: auto; - } - - &.el-menu, - .el-menu { - //菜单文字 - .el-menu-item, - .el-submenu__title { - > .menu-item-content { - text-overflow: ellipsis; - overflow: hidden; - //侧边栏宽度、菜单左右padding、左侧图标宽度、距离左右的距离、右侧图标宽度 - width: $aside-width - $menu-padding * 2 - $menu-icon-size - $menu-icon-text-gap * 2 - 12px; - } - } - - //弹出菜单的父级 - &.el-menu--popup .popover-menu__title { - cursor: auto; - border-bottom-width: 1px; - border-bottom-style: solid; - } - } -} - -//顶部水平菜单 -.el-menu--horizontal { - //顶部菜单的顶级节点 - &.el-menu { - //不显示折叠展开按钮 - > .el-submenu > .el-submenu__title > .el-submenu__icon-arrow { - display: none; - } - - //顶级节点的高度以及hover颜色 - > .el-menu-item, - > .el-submenu > .el-submenu__title { - height: $nav-height; - line-height: $nav-height; - - &:hover { - border-bottom: 2px solid $--color-primary; - } - } - } - - &.el-menu, - .el-menu { - //菜单的文字以及hover颜色 - .el-menu-item, - .el-submenu__title { - > span { - max-width: 100px; - } - } - - //submenu的右侧箭头会稍微偏上,不懂为啥 - .el-submenu__icon-arrow { - margin-top: -4px; - } - } -} - - -@import "./theme-light"; -@import "./theme-dark"; diff --git a/vue/template/src/component/menu/NavMenu/theme-dark.scss b/vue/template/src/component/menu/NavMenu/theme-dark.scss deleted file mode 100644 index 0f6d3b8..0000000 --- a/vue/template/src/component/menu/NavMenu/theme-dark.scss +++ /dev/null @@ -1,60 +0,0 @@ -.el-menu--dark { - //侧边栏垂直菜单 - &.el-menu--vertical { - &.el-menu, - .el-menu { - background-color: $menu-background-dark; - - //菜单的文字以及hover颜色 - .el-menu-item, - .el-submenu__title { - color: $menu-text-color-dark; - - &:hover { - color: $menu-text-hover-color-dark; - } - } - - //子级菜单的背景颜色 - &.el-menu--inline { - background-color: $sub-menu-background-dark; - } - - //子级菜单内阴影 - &.el-menu--inline { - box-shadow: inset 0 2px 8px darken($sub-menu-background-dark, 10%) - } - - //设置了显示弹出菜单的父级 - &.el-menu--popup .popover-menu__title { - color: $menu-text-hover-color-dark; - border-bottom-color: $--border-color-dark; - } - } - } - - //顶部水平菜单 - &.el-menu--horizontal { - &.el-menu, - .el-menu { - background-color: $root-menu-background-dark; - - //菜单的文字以及hover颜色 - .el-menu-item, - .el-submenu__title { - color: $menu-text-color-dark; - - &:hover { - color: $menu-text-hover-color-dark; - } - } - } - - &.el-menu > .el-menu-item, - &.el-menu > .el-submenu > .el-submenu__title, - &.el-menu > .el-menu-item:hover, - &.el-menu > .el-submenu > .el-submenu__title:hover { - border-bottom: none; - } - } -} diff --git a/vue/template/src/component/menu/NavMenu/theme-light.scss b/vue/template/src/component/menu/NavMenu/theme-light.scss deleted file mode 100644 index b3349ac..0000000 --- a/vue/template/src/component/menu/NavMenu/theme-light.scss +++ /dev/null @@ -1,39 +0,0 @@ -.el-menu--light { - //侧边栏垂直菜单 - &.el-menu--vertical { - &.el-menu, - .el-menu { - //菜单的文字以及hover颜色 - .el-menu-item, - .el-submenu__title { - color: $menu-text-color-light; - - &:hover { - color: $menu-text-hover-color-light; - } - } - - //弹出菜单的父级 - &.el-menu--popup .popover-menu__title { - color: $menu-text-hover-color-light; - border-bottom-color: $--border-color-light; - } - } - } - - //顶部水平菜单 - &.el-menu--horizontal { - &.el-menu, - .el-menu { - //菜单的文字以及hover颜色 - .el-menu-item, - .el-submenu__title { - color: $menu-text-color-light; - - &:hover { - color: $menu-text-hover-color-light; - } - } - } - } -} diff --git a/vue/template/src/config/index.js b/vue/template/src/config/index.js index f119c92..428cc30 100644 --- a/vue/template/src/config/index.js +++ b/vue/template/src/config/index.js @@ -9,6 +9,9 @@ module.exports = { //页面标题、登录注册页的标题 title: 'BiuBiuBiu~', + //logo地址 + logo: `/static/img/logo.svg`, + //路由配置 route: { //路由模式,['hash','history'] diff --git a/vue/template/src/filter/index.js b/vue/template/src/filter/index.js deleted file mode 100644 index d11e353..0000000 --- a/vue/template/src/filter/index.js +++ /dev/null @@ -1,68 +0,0 @@ -import {isEmpty, timeFormat} from '@/util' - -//时间戳格式化 -export function timestamp2Date(timestamp) { - if (isEmpty(timestamp)) return '' - return timeFormat(null, new Date(timestamp)) -} - -//秒数格式化,最低时间为分钟 -export function secondFormat(time) { - if (!time || isNaN(time)) return ' NaN' - if (time < 3600) return ~~(time / 60) + ' 分' - if (time < 86400) return ~~(time / 3600) + ' 小时' - return ~~(time / 86400) + ' 天' -} - -//格式化当前和给定的时间戳之间的秒数差 -export function secondBetweenFormat(time) { - const between = Date.now() / 1000 - Number(time) - return secondFormat(between) -} - -/** - * 数字转存储单位,1024 => KB - * - * @param num {number} - * @param digits {number} 保留几位小数 - * @return {string} - */ -export function number2StorageUnit(num, digits = 2) { - if (isEmpty(num)) return '' - const si = [ - {value: 1024 * 1024 * 1024 * 1024, symbol: 'TB'}, - {value: 1024 * 1024 * 1024, symbol: 'GB'}, - {value: 1024 * 1024, symbol: 'MB'}, - {value: 1024, symbol: 'KB'} - ] - for (let i = 0; i < si.length; i++) { - if (num >= si[i].value) { - return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol - } - } - return num.toString() -} - -/** - * 10000 => "10,000" - * @param num {number} - */ -export function toThousandFilter(num) { - if (isEmpty(num)) return '' - return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')) -} - -/** - * Upper case first char - * @param string {string} - */ -export function uppercaseFirst(string) { - if (isEmpty(string)) return '' - return string.charAt(0).toUpperCase() + string.slice(1) -} - -export default function (Vue) { - Object - .entries({timestamp2Date, secondFormat, secondBetweenFormat, number2StorageUnit, toThousandFilter, uppercaseFirst}) - .forEach(([key, value]) => Vue.filter(key, value)) -} diff --git a/vue/template/src/layout/component/Aside/component/MenuSearch.vue b/vue/template/src/layout/component/Aside/component/MenuSearch.vue deleted file mode 100644 index 57c3b33..0000000 --- a/vue/template/src/layout/component/Aside/component/MenuSearch.vue +++ /dev/null @@ -1,56 +0,0 @@ - - - - - diff --git a/vue/template/src/layout/component/Aside/component/OnePartRoot.vue b/vue/template/src/layout/component/Aside/component/OnePartRoot.vue deleted file mode 100644 index 8cd661f..0000000 --- a/vue/template/src/layout/component/Aside/component/OnePartRoot.vue +++ /dev/null @@ -1,251 +0,0 @@ - diff --git a/vue/template/src/layout/component/Aside/component/TwoPartRoot.vue b/vue/template/src/layout/component/Aside/component/TwoPartRoot.vue deleted file mode 100644 index 761135a..0000000 --- a/vue/template/src/layout/component/Aside/component/TwoPartRoot.vue +++ /dev/null @@ -1,134 +0,0 @@ - diff --git a/vue/template/src/layout/component/Aside/component/TwoPartSub.vue b/vue/template/src/layout/component/Aside/component/TwoPartSub.vue deleted file mode 100644 index c7cc450..0000000 --- a/vue/template/src/layout/component/Aside/component/TwoPartSub.vue +++ /dev/null @@ -1,110 +0,0 @@ - diff --git a/vue/template/src/layout/component/Aside/index.vue b/vue/template/src/layout/component/Aside/index.vue deleted file mode 100644 index 43d4420..0000000 --- a/vue/template/src/layout/component/Aside/index.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Aside/style.scss b/vue/template/src/layout/component/Aside/style.scss deleted file mode 100644 index f5603b5..0000000 --- a/vue/template/src/layout/component/Aside/style.scss +++ /dev/null @@ -1,101 +0,0 @@ -@import "~@/asset/style/var"; -@import "~@/asset/style/mixin.scss"; - -.aside { - display: flex; - z-index: 11; - - //移动端下侧边栏抽屉不能设置z-index,否则会被el-drawer的遮罩遮住 - @include mobile { - z-index: auto !important; - } - - //单层和双层侧边栏 - .sidebar, - .root-sidebar, - .sub-sidebar { - display: flex; - flex-direction: column; - height: 100%; - width: $aside-width; - box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35); - transition: all 0.2s; - - //折叠状态 - &.collapse { - width: $aside-collapse-width; - - //隐藏菜单文字 - .menu-item-content, - .sub-sidebar-title { - display: none; - } - } - } - - //垂直菜单占满剩余空间 - .el-menu--vertical { - flex: 1; - } - - //底部汉堡包 - .hamburger { - border-top-width: 1px; - border-top-style: solid; - } -} - -//页面为上下结构时,侧边栏的抽屉模式需要扣除导航栏的高度 -.el-drawer.drawer-behind-header { - top: $nav-height; -} - - -/* 双侧侧边栏开始 */ - -.root-sidebar-container { - position: relative; - width: $aside-collapse-width; - height: 100%; -} - -.root-sidebar { - position: absolute; - overflow: hidden; - z-index: 1; - box-shadow: none !important; - border-right-width: 1px; - border-right-style: solid; -} - -.sub-sidebar-title { - height: $nav-height; - line-height: $nav-height; - padding-left: $menu-padding; - font-size: 16px; -} - -/* 双侧侧边栏结束 */ - - -/* 侧边栏切换动画开始 */ - -.sidebar-enter-active { - transition: all 0.2s; -} - -.sidebar-enter, -.sidebar-leave-active { - opacity: 0; - transform: translateY(30px) skewY(10deg) -} - -.sidebar-leave-active { - position: absolute; -} - -/* 侧边栏切换动画结束 */ - - -@import "theme-light"; -@import "theme-dark"; diff --git a/vue/template/src/layout/component/Aside/theme-dark.scss b/vue/template/src/layout/component/Aside/theme-dark.scss deleted file mode 100644 index c2b181c..0000000 --- a/vue/template/src/layout/component/Aside/theme-dark.scss +++ /dev/null @@ -1,27 +0,0 @@ -.aside.dark { - &, - > .el-drawer__wrapper .el-drawer__body { - background-color: $menu-background-dark; - } - - .root-sidebar, - .root-sidebar > .el-menu--vertical.el-menu { - background-color: $root-menu-background-dark; - } - - .root-sidebar { - border-right-color: $--border-color-dark; - } - - .sub-sidebar-title { - color: $--color-white; - } - - .hamburger { - border-top-color: $--border-color-dark; - - i { - color: $menu-text-color-dark; - } - } -} diff --git a/vue/template/src/layout/component/Aside/theme-light.scss b/vue/template/src/layout/component/Aside/theme-light.scss deleted file mode 100644 index a6e0e78..0000000 --- a/vue/template/src/layout/component/Aside/theme-light.scss +++ /dev/null @@ -1,14 +0,0 @@ -.aside.light { - .root-sidebar { - background-color: $--color-white; - border-right-color: $--border-color-light; - } - - .hamburger { - border-top-color: $--border-color-light; - - i { - color: $menu-text-color-light; - } - } -} diff --git a/vue/template/src/layout/component/Footer/index.vue b/vue/template/src/layout/component/Footer/index.vue new file mode 100644 index 0000000..74a2ab5 --- /dev/null +++ b/vue/template/src/layout/component/Footer/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/vue/template/src/layout/component/Footer/style.scss b/vue/template/src/layout/component/Footer/style.scss new file mode 100644 index 0000000..37f8167 --- /dev/null +++ b/vue/template/src/layout/component/Footer/style.scss @@ -0,0 +1,9 @@ +.page-footer > .copyright { + color: $--color-text-secondary; + font-size: 14px; + + > a:hover { + color: $--color-primary; + text-decoration: underline; + } +} diff --git a/vue/template/src/layout/component/Hamburger.vue b/vue/template/src/layout/component/Hamburger.vue deleted file mode 100644 index 6812e6e..0000000 --- a/vue/template/src/layout/component/Hamburger.vue +++ /dev/null @@ -1,30 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Logo.vue b/vue/template/src/layout/component/Logo.vue deleted file mode 100644 index 2f55314..0000000 --- a/vue/template/src/layout/component/Logo.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Navbar/component/Bell.vue b/vue/template/src/layout/component/Navbar/component/Bell.vue deleted file mode 100644 index 026b407..0000000 --- a/vue/template/src/layout/component/Navbar/component/Bell.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/vue/template/src/layout/component/Navbar/component/HeadMenu.vue b/vue/template/src/layout/component/Navbar/component/HeadMenu.vue deleted file mode 100644 index c3e9cb1..0000000 --- a/vue/template/src/layout/component/Navbar/component/HeadMenu.vue +++ /dev/null @@ -1,210 +0,0 @@ - diff --git a/vue/template/src/layout/component/Navbar/index.vue b/vue/template/src/layout/component/Navbar/index.vue deleted file mode 100644 index baa8681..0000000 --- a/vue/template/src/layout/component/Navbar/index.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Navbar/style.scss b/vue/template/src/layout/component/Navbar/style.scss deleted file mode 100644 index f77d547..0000000 --- a/vue/template/src/layout/component/Navbar/style.scss +++ /dev/null @@ -1,50 +0,0 @@ -@import "~@/asset/style/var"; - -.navbar { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: nowrap; - height: $nav-height; - padding-left: $navbar-padding-left; - z-index: 10; - box-shadow: 0 1px 4px rgba(0, 21, 41, .08); - transition: all .2s; - - > div { - display: flex; - flex-wrap: nowrap; - height: 100%; - } - - .navbar-icon { - font-size: 18px; - } - - .navbar-item { - display: flex; - align-items: center; - padding: 0 8px; - cursor: pointer; - } - - .logo-container { - width: auto; - min-width: $aside-width - $navbar-padding-left; - padding-left: $menu-padding - $navbar-padding-left; - } - - .avatar-wrapper { - display: flex; - align-items: center; - height: 100%; - - span { - margin-right: 5px; - font-size: 18px; - } - } -} - -@import "theme-light"; -@import "theme-dark"; diff --git a/vue/template/src/layout/component/Navbar/theme-dark.scss b/vue/template/src/layout/component/Navbar/theme-dark.scss deleted file mode 100644 index 4cd9a22..0000000 --- a/vue/template/src/layout/component/Navbar/theme-dark.scss +++ /dev/null @@ -1,27 +0,0 @@ -.navbar.dark { - background-color: $root-menu-background-dark; - - .navbar-item { - color: $navbar-item-color-dark; - - &:hover { - background-color: $navbar-item-hover-color-dark; - } - } -} - -//暗色主题的下拉菜单 -.dark.el-dropdown-menu { - background-color: $root-menu-background-dark; - border: none; - - .el-dropdown-menu__item { - color: $menu-text-color-dark; - - &:hover, - &:focus { - background-color: unset; - color: $menu-text-hover-color-dark; - } - } -} diff --git a/vue/template/src/layout/component/Navbar/theme-light.scss b/vue/template/src/layout/component/Navbar/theme-light.scss deleted file mode 100644 index 26b9c63..0000000 --- a/vue/template/src/layout/component/Navbar/theme-light.scss +++ /dev/null @@ -1,11 +0,0 @@ -.navbar.light { - background-color: $--color-white; - - .navbar-item { - color: $navbar-item-color-light; - - &:hover { - background-color: $navbar-item-hover-color-light; - } - } -} diff --git a/vue/template/src/layout/component/Page/component/BackToTop.vue b/vue/template/src/layout/component/Page/component/BackToTop.vue deleted file mode 100644 index 08f411d..0000000 --- a/vue/template/src/layout/component/Page/component/BackToTop.vue +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/vue/template/src/layout/component/Page/component/KeepRouterViewAlive.vue b/vue/template/src/layout/component/Page/component/KeepRouterViewAlive.vue deleted file mode 100644 index d454420..0000000 --- a/vue/template/src/layout/component/Page/component/KeepRouterViewAlive.vue +++ /dev/null @@ -1,92 +0,0 @@ - diff --git a/vue/template/src/layout/component/Page/component/PageFooter.vue b/vue/template/src/layout/component/Page/component/PageFooter.vue deleted file mode 100644 index ad019b2..0000000 --- a/vue/template/src/layout/component/Page/component/PageFooter.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - - - diff --git a/vue/template/src/layout/component/Page/component/PageHeader.vue b/vue/template/src/layout/component/Page/component/PageHeader.vue deleted file mode 100644 index 0872fad..0000000 --- a/vue/template/src/layout/component/Page/component/PageHeader.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - - - diff --git a/vue/template/src/layout/component/Page/component/PageView.vue b/vue/template/src/layout/component/Page/component/PageView.vue deleted file mode 100644 index 68fe63d..0000000 --- a/vue/template/src/layout/component/Page/component/PageView.vue +++ /dev/null @@ -1,27 +0,0 @@ - diff --git a/vue/template/src/layout/component/Page/component/TagsView/ScrollPanel.vue b/vue/template/src/layout/component/Page/component/TagsView/ScrollPanel.vue deleted file mode 100644 index 7c14ce7..0000000 --- a/vue/template/src/layout/component/Page/component/TagsView/ScrollPanel.vue +++ /dev/null @@ -1,82 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Page/component/TagsView/index.vue b/vue/template/src/layout/component/Page/component/TagsView/index.vue deleted file mode 100644 index 5ebe93a..0000000 --- a/vue/template/src/layout/component/Page/component/TagsView/index.vue +++ /dev/null @@ -1,207 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Page/component/TagsView/style.scss b/vue/template/src/layout/component/Page/component/TagsView/style.scss deleted file mode 100644 index eb3c3ef..0000000 --- a/vue/template/src/layout/component/Page/component/TagsView/style.scss +++ /dev/null @@ -1,68 +0,0 @@ -@import "~@/asset/style/var"; - -.tags-view-container { - height: $tags-view-height; - box-shadow: inset 0 0 3px 2px hsla(0, 0%, 39.2%, .1); - - .tags-view-wrapper { - height: 100%; - white-space: nowrap; - overflow-x: auto; - - &::-webkit-scrollbar { - display: none; - } - - .tags-view-item { - $margin: 4px; - $border-width: 1px; - - display: inline-block; - cursor: pointer; - height: #{$tags-view-height - $margin * 2}; - line-height: #{$tags-view-height - $margin * 2 - $border-width * 2}; - color: $--color-text-secondary; - background-color: #ffffff; - border-radius: 3px; - border: $border-width solid $--border-color-base; - padding: 0 12px; - font-size: 12px; - margin: $margin; - user-select: none; - - &:first-of-type { - margin-left: 15px; - } - - &:last-of-type { - margin-right: 15px; - } - - &::before { - content: ''; - position: relative; - background-color: #e8eaec; - display: inline-block; - width: 12px; - height: 12px; - border-radius: 50%; - top: 1px; - margin-right: 5px; - } - - &.active { - color: $--color-primary; - - &::before { - background-color: $--color-primary; - } - } - - .el-icon-close { - border-radius: 50%; - font-size: 14px; - margin-left: 10px; - } - } - } -} diff --git a/vue/template/src/layout/component/Page/index.vue b/vue/template/src/layout/component/Page/index.vue deleted file mode 100644 index 810b7ba..0000000 --- a/vue/template/src/layout/component/Page/index.vue +++ /dev/null @@ -1,67 +0,0 @@ - - - diff --git a/vue/template/src/layout/component/Page/style.scss b/vue/template/src/layout/component/Page/style.scss deleted file mode 100644 index e7c743d..0000000 --- a/vue/template/src/layout/component/Page/style.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import "~@/asset/style/var"; - -.page-main { - display: flex; - flex-direction: column; - flex: 1; - position: relative; - overflow: hidden; - background-color: #f0f2f5; - - > .scroll-container { - display: flex; - flex: 1; - flex-direction: column; - overflow-y: overlay; - overflow-x: inherit; - - > .page-view { - margin: $page-view-margin; - flex: 1; - } - } -} diff --git a/vue/template/src/layout/component/SettingDrawer/component/checkbox/ColorCheckbox.vue b/vue/template/src/layout/component/SettingDrawer/component/checkbox/ColorCheckbox.vue new file mode 100644 index 0000000..6b02081 --- /dev/null +++ b/vue/template/src/layout/component/SettingDrawer/component/checkbox/ColorCheckbox.vue @@ -0,0 +1,15 @@ + + + diff --git a/vue/template/src/layout/component/SettingDrawer/component/checkbox/Group.vue b/vue/template/src/layout/component/SettingDrawer/component/checkbox/Group.vue new file mode 100644 index 0000000..2292365 --- /dev/null +++ b/vue/template/src/layout/component/SettingDrawer/component/checkbox/Group.vue @@ -0,0 +1,15 @@ + + + diff --git a/vue/template/src/layout/component/SettingDrawer/component/checkbox/ImgCheckbox.vue b/vue/template/src/layout/component/SettingDrawer/component/checkbox/ImgCheckbox.vue new file mode 100644 index 0000000..9f5963e --- /dev/null +++ b/vue/template/src/layout/component/SettingDrawer/component/checkbox/ImgCheckbox.vue @@ -0,0 +1,21 @@ + + + diff --git a/vue/template/src/component/checkbox/common.js b/vue/template/src/layout/component/SettingDrawer/component/checkbox/mixin.js similarity index 100% rename from vue/template/src/component/checkbox/common.js rename to vue/template/src/layout/component/SettingDrawer/component/checkbox/mixin.js diff --git a/vue/template/src/layout/component/SettingDrawer/component/checkbox/style.scss b/vue/template/src/layout/component/SettingDrawer/component/checkbox/style.scss new file mode 100644 index 0000000..5c33195 --- /dev/null +++ b/vue/template/src/layout/component/SettingDrawer/component/checkbox/style.scss @@ -0,0 +1,34 @@ +@import "~@/asset/style/var"; + +.checkbox-group { + display: flex; + flex-wrap: wrap; + width: 100%; +} + +.color-checkbox { + width: 20px; + height: 20px; + margin: 5px; + border-radius: 2px; + cursor: pointer; + text-align: center; + font-weight: bold; + color: #ffffff; +} + +.img-checkbox { + width: 55px; + margin: 10px; + border-radius: 2px; + cursor: pointer; + position: relative; + + .el-icon-check { + position: absolute; + color: $--color-primary; + font-weight: bold; + top: 25px; + right: 10px; + } +} diff --git a/vue/template/src/layout/component/SettingDrawer.vue b/vue/template/src/layout/component/SettingDrawer/index.vue similarity index 90% rename from vue/template/src/layout/component/SettingDrawer.vue rename to vue/template/src/layout/component/SettingDrawer/index.vue index 612f55a..28ee53e 100644 --- a/vue/template/src/layout/component/SettingDrawer.vue +++ b/vue/template/src/layout/component/SettingDrawer/index.vue @@ -1,10 +1,10 @@ - - diff --git a/vue/template/src/layout/component/SettingDrawer/style.scss b/vue/template/src/layout/component/SettingDrawer/style.scss new file mode 100644 index 0000000..70eef94 --- /dev/null +++ b/vue/template/src/layout/component/SettingDrawer/style.scss @@ -0,0 +1,17 @@ +@import "~@/asset/style/var"; +@import "./component/checkbox/style"; + +.drawer-container { + padding: 24px; + font-size: 14px; + line-height: 1.5; + word-wrap: break-word; + + .drawer-item { + display: flex; + justify-content: space-between; + color: $--color-text-regular; + font-size: 14px; + padding: 12px 0; + } +} diff --git a/vue/template/src/layout/index.vue b/vue/template/src/layout/index.vue index 093989c..5866b0c 100644 --- a/vue/template/src/layout/index.vue +++ b/vue/template/src/layout/index.vue @@ -1,90 +1,101 @@ + + + openSettingDrawer() { + appMutations.showSettingDrawer(true) + }, - + diff --git a/vue/template/src/layout/mixin/decideRouterTransition.js b/vue/template/src/layout/mixin/decideRouterTransition.js deleted file mode 100644 index e0666b7..0000000 --- a/vue/template/src/layout/mixin/decideRouterTransition.js +++ /dev/null @@ -1,29 +0,0 @@ -import {getters as pageGetters, mutations as pageViewMutations} from "@/layout/store/page" - -export default { - watch: { - $route(to, from) { - this.decideRouteTransition(to, from) - } - }, - - methods: { - //根据访问的tab页的左右顺序来确定路由动画 - decideRouteTransition(to, from) { - const {next, prev} = pageGetters.transition - - let transitionName = prev - - //这里认为页签数量不会太多,所以为了可读性使用两次循环查找 - const fromIndex = this.visitedViews.findIndex(i => i.path === from.path) - const toIndex = this.visitedViews.findIndex(i => i.path === to.path) - - //新开tab也认为顺序高于上一个tab - if (toIndex === -1 || fromIndex < toIndex) { - transitionName = next - } - - pageViewMutations.transition({curr: transitionName}) - }, - } -} diff --git a/vue/template/src/layout/mixin/hamburger.js b/vue/template/src/layout/mixin/hamburger.js deleted file mode 100644 index f798b00..0000000 --- a/vue/template/src/layout/mixin/hamburger.js +++ /dev/null @@ -1,33 +0,0 @@ -import Hamburger from '@/layout/component/Hamburger' -import {getters as appGetters} from "@/layout/store/app" -import {getters as asideGetters} from "@/layout/store/aside" -import {getSidebarMenus} from "@/layout/util" - -/** - * 汉堡包的渲染条件混入 - */ - -export default { - components: {Hamburger}, - - computed: { - //渲染汉堡包的条件 - //①侧边栏有菜单 - //②移动端 或 - //③桌面端且汉堡包位置正确(侧边栏引入则需要是aside,反之需要是head) - //④桌面端且是双层侧边栏导航 - //⑤桌面端且是侧边栏导航或混合导航时,未设置侧边栏自动隐藏 - renderHamburger() { - if (getSidebarMenus().length <= 0) return false - - const isMobile = appGetters.isMobile, - correctPosition = - asideGetters.hamburgerPosition === (this.$options.name === 'navbar' ? 'head' : 'aside'), - correctMode = - ['aside', 'mix'].includes(appGetters.navMode) && !asideGetters.autoHide - || appGetters.navMode === 'aside-two-part' - - return isMobile || correctPosition && correctMode - } - } -} diff --git a/vue/template/src/layout/mixin/menu.js b/vue/template/src/layout/mixin/menu.js deleted file mode 100644 index a811c23..0000000 --- a/vue/template/src/layout/mixin/menu.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 顶部菜单和侧边栏菜单的公共混入 - */ -import {mutations as tagsViewMutations} from "@/layout/store/tagsView" -import {refreshPage} from "@/util/route" -import {isExternal} from "@/util/validate" -import {findComponentByTag} from "@/util/vue" - -export default { - data() { - return { - //当前激活的菜单的fullPath - //之所以手动维护是因为el-menu在点击后就会设置activeIndex - activeMenu: '', - - //用于判断鼠标是否在弹出菜单内 - openedMenuNum: 0 - } - }, - - methods: { - //点击菜单后的动作 - actionOnSelectMenu(fullPath, refreshWhenSame = true) { - //外部链接时打开新窗口 - if (isExternal(fullPath)) { - window.open(fullPath) - return this.resetActiveMenu() - } - - //触发的菜单路径是当前路由时,根据参数判断是否进行刷新 - if (this.$route.path === fullPath) { - if (!refreshWhenSame) return - tagsViewMutations.delCacheOnly(this.$route) - this.$nextTick(() => refreshPage()) - } - else this.$router.push(fullPath) - }, - - //由于侧边栏菜单数组更新后,el-menu不一定会更新(当数组中不存在单级菜单时) - //所以手动更新el-menu的当前高亮菜单 - resetActiveMenu() { - const menu = this.$_getElMenuInstance() - menu && menu.updateActiveIndex(this.activeMenu) - }, - - //获取el-menu实例 - //目前被侧边栏和双层侧边栏的主菜单使用 - $_getElMenuInstance() { - if (!this.$_elMenuInstance) { - this.$_elMenuInstance = findComponentByTag(this, 'el-menu') - } - - return this.$_elMenuInstance - } - } -} diff --git a/vue/template/src/layout/mixin/menuSearch.js b/vue/template/src/layout/mixin/menuSearch.js deleted file mode 100644 index ccdf1ba..0000000 --- a/vue/template/src/layout/mixin/menuSearch.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 侧边栏搜索框混入 - */ -import MenuSearch from '@/layout/component/Aside/component/MenuSearch' -import {trim} from "@/util" -import {findComponentByTag} from "@/util/vue" - -export default { - components: {MenuSearch}, - - methods: { - $_getNavMenuInstance() { - if (!this.$_navMenuInstance) { - this.$_navMenuInstance = findComponentByTag(this, 'nav-menu') - } - - return this.$_navMenuInstance - }, - - handlerSearch(v) { - this.$_getNavMenuInstance().realSearchWord = trim(v) - } - } -} diff --git a/vue/template/src/layout/mixin/tagsViewPersistent.js b/vue/template/src/layout/mixin/tagsViewPersistent.js index 2ddc36c..492f108 100644 --- a/vue/template/src/layout/mixin/tagsViewPersistent.js +++ b/vue/template/src/layout/mixin/tagsViewPersistent.js @@ -4,7 +4,7 @@ * 页签变化时写入本地存储 */ -import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView" +import {tagsViewGetters, tagsViewMutations} from "el-admin-layout" import {debounce} from "@/util" import {getTagsView, setTagsView} from "@/util/storage" @@ -27,7 +27,7 @@ export default { if (!v) return setTagsView() //启用时先存储一次(仅在mounted后,否则此时页签数据不完整) - this._isMounted && this.persistentTagsView(this.visitedViews) + this._isMounted && this.persistentTagsView(tagsViewGetters.visitedViews) this.watchVisitedViewsCallback = this.$watch('visitedViews', this.persistentTagsView) } diff --git a/vue/template/src/layout/mixin/tagsViewShortcut.js b/vue/template/src/layout/mixin/tagsViewShortcut.js index f6b0a5d..358d414 100644 --- a/vue/template/src/layout/mixin/tagsViewShortcut.js +++ b/vue/template/src/layout/mixin/tagsViewShortcut.js @@ -4,7 +4,7 @@ * ctrl + ←,上一个页签 */ -import {getters as tagsViewGetters} from "@/layout/store/tagsView" +import {tagsViewGetters} from "el-admin-layout" export default { computed: { @@ -28,20 +28,36 @@ export default { methods: { //上一个页签 gotoViewFront() { - if (this.visitedViews.length <= 1) return - let index = this.visitedViews.findIndex(view => view.path === this.$route.path) + const views = tagsViewGetters.visitedViews + + if (views.length <= 1) return + + let index = views.findIndex(view => view.path === this.$route.path) + if (index < 0) return - if (index === 0) index = this.visitedViews.length - return this.$router.push({path: this.visitedViews[index - 1].path}) + + if (index === 0) { + index = views.length + } + + return this.$router.push({path: views[index - 1].path}) }, //下一个页签 gotoViewBehind() { - if (this.visitedViews.length <= 1) return - let index = this.visitedViews.findIndex(view => view.path === this.$route.path) + const views = tagsViewGetters.visitedViews + + if (views.length <= 1) return + + let index = views.findIndex(view => view.path === this.$route.path) + if (index < 0) return - if (index + 1 > this.visitedViews.length - 1) index = -1 - return this.$router.push({path: this.visitedViews[index + 1].path}) + + if (index + 1 > views.length - 1) { + index = -1 + } + + return this.$router.push({path: views[index + 1].path}) }, //快捷键监听 diff --git a/vue/template/src/layout/store/app.js b/vue/template/src/layout/store/app.js deleted file mode 100644 index 28a1d94..0000000 --- a/vue/template/src/layout/store/app.js +++ /dev/null @@ -1,83 +0,0 @@ -import Vue from 'vue' -import {debounce, isEmpty} from "@/util" -import {isMobile} from "@/util/browser" -import {createGetters, createMutations} from "@/util/observable" - -const state = { - //区分pc和移动端 - isMobile: isMobile(), - - //设置抽屉的显隐 - //放这里的原因是navbar可能会重新渲染,从而导致抽屉的重新渲染 - showSettingDrawer: false, - - //当前激活的顶部菜单的fullPath - activeRootMenu: '', - - //所有的树形菜单,每个元素为顶部菜单,顶部菜单的子级(如果有)为侧边栏菜单 - menus: [], - - //导航模式,'aside'、'aside-two-part'、'head'、'mix' - navMode: 'mix' -} - -const store = Vue.observable(state) - -export const getters = createGetters(store) - -export const mutations = { - ...createMutations(store), - - menus(v) { - sort(v) - store.menus = v - } -} - -//菜单排序 -function sort(routes) { - if (!Array.isArray(routes) || routes.length === 0) { - return - } - - //菜单排序值的空值处理 - const getSortValue = item => { - const sort = deepGetSortValue(item) - return isEmpty(sort) ? 10000 : sort - } - - //获取菜单的排序值 - const deepGetSortValue = item => { - const {children = [], meta: {hidden, sort} = {}} = item - - if (hidden) return null - - if (!isEmpty(sort)) return sort - - //如果只有一个子节点,那么取子节点的排序值 - if (children.length === 1) { - return deepGetSortValue(children[0]) - } - - return null - } - - //对根节点排序 - routes.sort((pre, next) => { - pre = getSortValue(pre) - next = getSortValue(next) - if (pre < next) return -1 - if (pre === next) return 0 - return 1 - }) - - //对每一个根节点的子级排序 - routes.forEach(route => { - const {children} = route - children && children.length && sort(children) - }) -} - -window.addEventListener('resize', debounce(() => { - !document.hidden && mutations.isMobile(isMobile()) -})) diff --git a/vue/template/src/layout/store/aside.js b/vue/template/src/layout/store/aside.js deleted file mode 100644 index b39f84f..0000000 --- a/vue/template/src/layout/store/aside.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 侧边栏的响应式数据 - */ -import Vue from 'vue' -import {bindThis} from "@/util" -import {createGetters, createMutations} from "@/util/observable" -import {getters as appGetters} from "./app" - -const state = { - //抽屉模式时的显隐 - show: false, - - //主题,light 或 dark - theme: 'light', - - //手风琴效果 - uniqueOpen: true, - - //是否折叠 - collapse: false, - - //折叠时显示上级 - showParentOnCollapse: false, - - //自动隐藏 - autoHide: false, - - //汉堡包的位置,aside 或 head - hamburgerPosition: 'aside', - - //是否显示搜索框 - search: true -} - -const store = Vue.observable(state) - -export const getters = createGetters(store) - -export const mutations = bindThis({ - ...createMutations(store), - - /*移动端或设置了侧边栏自动隐藏时打开关闭抽屉,否则展开折叠*/ - open() { - if (appGetters.isMobile || store.autoHide) { - store.show = true - } - else store.collapse = false - }, - close() { - if (appGetters.isMobile || store.autoHide) { - store.show = false - } - else store.collapse = true - }, - //切换侧边栏的状态 - switch(action) { - switch (action) { - case 'open': - return this.open() - case 'close': - return this.close() - default : - let open = true - if (appGetters.isMobile) { - open = !store.show - } - else open = store.collapse - return open ? this.open() : this.close() - } - } -}) diff --git a/vue/template/src/layout/store/navbar.js b/vue/template/src/layout/store/navbar.js deleted file mode 100644 index c804ee4..0000000 --- a/vue/template/src/layout/store/navbar.js +++ /dev/null @@ -1,13 +0,0 @@ -import Vue from 'vue' -import {createGetters, createMutations} from "@/util/observable" - -const state = { - //主题,light 或 dark - theme: 'light', -} - -const store = Vue.observable(state) - -export const getters = createGetters(store) - -export const mutations = createMutations(store) diff --git a/vue/template/src/layout/store/page.js b/vue/template/src/layout/store/page.js deleted file mode 100644 index 97a61d8..0000000 --- a/vue/template/src/layout/store/page.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * 路由页面的响应式数据 - */ -import Vue from 'vue' -import {bindThis} from "@/util" -import {createGetters, createMutations} from "@/util/observable" - -const state = { - //路由过渡动画 - transition: { - //当未启用多页签时的路由动画 - default: 'el-fade-in-linear', - //要访问的tab顺序高于上一个访问的tab时的路由动画 - next: 'left-out', - //要访问的tab顺序不高于上一个访问的tab时的路由动画 - prev: 'right-out', - //当前使用的路由动画 - curr: 'el-fade-in-linear' - }, - - /*iframe的控制*/ - showIframe: false, - currentIframe: '', - iframeList: [], - - //是否显示侧边栏或顶部导航栏的logo - showLogo: true, - //分层结构,上下('top-bottom')、左右('left-right') - position: 'left-right', - //是否显示页头 - showPageHeader: true, - //是否显示返回顶部按钮 - showBackToTop: true -} - -const store = Vue.observable(state) - -export const getters = createGetters(store) - -export const mutations = bindThis({ - ...createMutations(store), - - //修改transition时使用Object.assign - transition(obj) { - Object.assign(store.transition, obj) - }, - - addIframe(src) { - !store.iframeList.includes(src) && store.iframeList.push(src) - }, - delIframe(src) { - const index = store.iframeList.findIndex(i => i === src) - index > -1 && store.iframeList.splice(index, 1) - }, - openIframe({src}) { - store.showIframe = true - store.currentIframe = src - this.addIframe(src) - }, - closeIframe({src, del}) { - store.showIframe = false - store.currentIframe = '' - del && this.delIframe(src) - } -}) diff --git a/vue/template/src/layout/store/tagsView.js b/vue/template/src/layout/store/tagsView.js deleted file mode 100644 index de31a2d..0000000 --- a/vue/template/src/layout/store/tagsView.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * 多页签的响应式数据 - */ -import Vue from 'vue' -import {getters as pageGetters, mutations as pageMutations} from "@/layout/store/page" -import {getRouterViewCacheKey} from "@/layout/util" -import {bindThis} from "@/util" -import {createGetters, createMutations} from "@/util/observable" - -const state = { - //是否启用 - enabled: true, - //是否启用快捷键切换功能 - shortcut: true, - //是否将页签持久化到sessionStorage - persistent: true, - - //显示的页签,vue-router的routeConfig对象数组 - visitedViews: [], - - //缓存的页签,用于:include - cachedViews: [] -} - -const store = Vue.observable(state) - -export const getters = createGetters(store) - -export const mutations = bindThis({ - ...createMutations(store), - - /** - * 多页签的启用/停用 - * 停用时会移除所有缓存,并且重置路由过渡动画 - * @param v 启用为true,停用为false - */ - enabled(v) { - store.enabled = v - - if (!v) { - pageMutations.transition({curr: pageGetters.transition.default}) - this.delAllTagAndCache() - } - }, - - /** - * 在页签栏上添加一个页签,path已存在的不会重复添加,调用时需要保证meta.title有值 - * @param view {routeConfig} - */ - addTagOnly(view) { - const {name, path, fullPath, meta} = view - - if (store.visitedViews.some(v => v.path === path)) { - return - } - - store.visitedViews.push({name, path, fullPath, meta: {...meta}}) - }, - - /** - * 将传入的routeConfig加入的缓存中 - * 以下调用无效:设置了不缓存、是iframe页、未设置唯一标识、已缓存 - * @param view {routeConfig} - */ - addCacheOnly(view) { - const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {} - - if (noCache || iframe || !view.name && !usePathKey && !useFullPathKey) { - return - } - - const key = getRouterViewCacheKey(view) - - if (store.cachedViews.includes(key)) { - return - } - - store.cachedViews.push(key) - }, - - /** - * 同时调用{@link #addTagOnly}、{@link #addCacheOnly} - * @param view {routeConfig} - */ - addTagAndCache(view) { - this.addTagOnly(view) - this.addCacheOnly(view) - }, - - /** - * 根据path从页签栏中移除一个页签 - * @param view {path},routeConfig - */ - delTagOnly(view) { - const index = store.visitedViews.findIndex(i => i.path === view.path) - index > -1 && store.visitedViews.splice(index, 1) - }, - - /** - * 删除对应的缓存 - * @param view {routeConfig} - */ - delCacheOnly(view) { - const key = getRouterViewCacheKey(view) - const index = store.cachedViews.indexOf(key) - index > -1 && store.cachedViews.splice(index, 1) - }, - - /** - * 同时调用{@link #delTagOnly}、{@link #delCacheOnly},移除iframe页 - * @param view {routeConfig} - */ - delTagAndCache(view) { - this.delTagOnly(view) - this.delCacheOnly(view) - - const iframe = view.meta && view.meta.iframe - iframe && pageMutations.delIframe(iframe) - }, - - /** - * 从页签栏上移除除了routeConfig以外的所有非固定页签 - * 并且从中移除除了routeConfig以外的所有缓存 - * @param view {routeConfig} - */ - delOtherTagAndCache(view) { - const visitedViews = store.visitedViews.filter(v => v.meta.affix || v.path === view.path) - const key = store.cachedViews.find(key => key === getRouterViewCacheKey(view)) - - store.visitedViews = visitedViews - store.cachedViews = key ? [key] : [] - }, - - /** - * 从页签栏上移除所有非固定页签,并且移除的所有缓存 - */ - delAllTagAndCache() { - store.visitedViews = store.visitedViews.filter(tag => tag.meta && tag.meta.affix) - store.cachedViews = [] - } -}) diff --git a/vue/template/src/layout/style.scss b/vue/template/src/layout/style.scss new file mode 100644 index 0000000..00f200e --- /dev/null +++ b/vue/template/src/layout/style.scss @@ -0,0 +1,6 @@ +@import "~el-admin-layout/src/style"; + +@import "./component/Footer/style"; +@import "./component/SettingDrawer/style"; + + diff --git a/vue/template/src/layout/util.js b/vue/template/src/layout/util.js deleted file mode 100644 index e50283a..0000000 --- a/vue/template/src/layout/util.js +++ /dev/null @@ -1,39 +0,0 @@ -import {getters as appGetters} from "@/layout/store/app" - -//获取路由页面缓存所需的key -export function getRouterViewCacheKey({name, path, fullPath, meta = {}}) { - const {usePathKey, useFullPathKey} = meta - return usePathKey ? path : useFullPathKey ? fullPath : name -} - -//获取侧边栏的菜单,如果是双层侧边栏导航时,获取的是子菜单 -export function getSidebarMenus() { - const menus = appGetters.menus - - if (!Array.isArray(menus)) { - return [] - } - - //移动端时,侧边栏只会按侧边栏导航模式渲染 - if (appGetters.isMobile) { - return menus - } - - switch (appGetters.navMode) { - case 'aside': - return menus - case 'head': - return [] - case 'aside-two-part': - case 'mix': - const root = menus.find(i => i.path === appGetters.activeRootMenu) - return root ? root.children || [] : [] - default: - return [] - } -} - -//根据路由获取当前激活的菜单 -export function getActiveMenuByRoute({path, meta}) { - return meta.activeMenu || path -} diff --git a/vue/template/src/main.js b/vue/template/src/main.js index 815a2b6..921f6a8 100644 --- a/vue/template/src/main.js +++ b/vue/template/src/main.js @@ -6,11 +6,9 @@ import App from '@/App' import store from '@/store' import router from '@/router' import '@/asset/icon' -import filters from './filter' Vue.use(Element) Vue.use(ElementPersonal) -Vue.use(filters) Vue.config.productionTip = false diff --git a/vue/template/src/router/guardian/avoidReuse.js b/vue/template/src/router/guardian/avoidReuse.js index d7c6c67..b87e2bd 100644 --- a/vue/template/src/router/guardian/avoidReuse.js +++ b/vue/template/src/router/guardian/avoidReuse.js @@ -16,7 +16,7 @@ export default function (router) { } window.addEventListener('unhandledrejection', event => { - if (event.reason.stack.startsWith('Error: Redirected when going from')) { + if (event.reason.stack && event.reason.stack.startsWith('Error: Redirected when going from')) { event.preventDefault() } }) diff --git a/vue/template/src/router/guardian/iframe.js b/vue/template/src/router/guardian/iframe.js index 0bacebd..13df8a6 100644 --- a/vue/template/src/router/guardian/iframe.js +++ b/vue/template/src/router/guardian/iframe.js @@ -1,4 +1,4 @@ -import {mutations as pageMutations} from "@/layout/store/page" +import {pageMutations} from "el-admin-layout" const beforeEach = (to, from, next) => { //从iframe页面离开时,判断是否需要删除iframe diff --git a/vue/template/src/router/util.js b/vue/template/src/router/util.js index d81b344..6024fe9 100644 --- a/vue/template/src/router/util.js +++ b/vue/template/src/router/util.js @@ -4,17 +4,6 @@ import Page500 from '@/view/_app/500' import {isEmpty} from "@/util" import {isExternal, isString} from "@/util/validate" -//json字符串转路由配置 -export function str2routeConfig(str) { - const parseOpt = (key, value) => { - if (key === 'dynamicTitle' && isString(value)) { - return isEmpty(value) ? undefined : eval(`(${value})`) - } - return value - } - return JSON.parse(str, parseOpt) -} - //根据json数组生成路由 export function generateRoutes(jsonTree) { //将深度为2的节点合并为由其最末子节点组成的数组 diff --git a/vue/template/src/store/module/resource.js b/vue/template/src/store/module/resource.js index 8f5aa1d..0b5e631 100644 --- a/vue/template/src/store/module/resource.js +++ b/vue/template/src/store/module/resource.js @@ -1,5 +1,5 @@ import path from 'path' -import {mutations as appMutations} from "@/layout/store/app" +import {appMutations} from "el-admin-layout" import {addDynamicRoutes} from '@/router' import {getDynamicRoutes} from '@/router/define' import {metaExtend} from "@/router/util" diff --git a/vue/template/src/store/module/user.js b/vue/template/src/store/module/user.js index 0868184..4551931 100644 --- a/vue/template/src/store/module/user.js +++ b/vue/template/src/store/module/user.js @@ -1,8 +1,7 @@ import {createMutations} from "@/store/util" import {emptyOrDefault} from "@/util" import {getUser, setUser} from "@/util/storage" -import {mutations as tagsViewMutations} from "@/layout/store/tagsView" -import {elError} from "@/util/message" +import {tagsViewMutations} from "el-admin-layout" //刷新时从本地存储中获取用户信息 const user = getUser() diff --git a/vue/template/src/util/auth.js b/vue/template/src/util/auth.js index c317257..800a4c6 100644 --- a/vue/template/src/util/auth.js +++ b/vue/template/src/util/auth.js @@ -1,5 +1,4 @@ import store from '@/store' -import {uppercaseFirst} from "@/filter" import {isEmpty} from "@/util" /** @@ -30,24 +29,6 @@ export function auth(path) { return true } -/** - * what i can - * 批量生成canXxx的计算属性 - * - * @param apiMap {object} - * @return {object} - */ -export function wic(apiMap) { - const attrs = {} - - Object.entries(apiMap).forEach(([key, value]) => { - const attrKey = `can${uppercaseFirst(key)}` - attrs[attrKey] = () => auth(value.url) - }) - - return attrs -} - /** * 判断是否已登录,已登录则返回true * diff --git a/vue/template/src/util/browser.js b/vue/template/src/util/browser.js deleted file mode 100644 index f23c986..0000000 --- a/vue/template/src/util/browser.js +++ /dev/null @@ -1,223 +0,0 @@ -import cssVar from '@/asset/style/var.scss' - -const maxMobileWidth = parseFloat(cssVar.maxMobileWidth) - -/** - * 根据body宽度判断是否为移动端,是则返回true - * - * @return {boolean} - */ -export function isMobile() { - const rect = document.body.getBoundingClientRect() - return rect.width <= maxMobileWidth -} - -/** - * 判断是否为dom元素,是则返回true - * - * @param obj - * @return {boolean} - */ -export function isDom(obj) { - return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string' -} - -/** - * 获取元素的内宽(扣除左右padding后) - * - * @param el {HTMLElement} - * @return {number} - */ -export function getElementInnerWidth(el) { - if (!el) return 0 - - const style = window.getComputedStyle(el) - - return parseFloat(style.width) - (parseFloat(style.paddingLeft) + parseFloat(style.paddingRight)) -} - -/** - * 获取元素的真实高度 - * - * @param el {HTMLElement} - * @param style 元素的style键值对 - * @return {number} - */ -export function getElementHeight(el, style) { - if (!el) return 0 - - const node = el.cloneNode(true) - node.style.opacity = '0' - - if (style) { - Object.keys(style).forEach(item => { - node.style[item] = style[item] - }) - } - - const id = 'temp-id-' + Date.now() - node.setAttribute('id', id) - document.body.append(node) - - const height = node.offsetHeight - document.body.removeChild(node) - - return height -} - -/** - * 获取元素距离其容器的顶部距离 - * - * @param el {HTMLElement} 目标元素 - * @param container {HTMLElement|Window} 容器元素 - * @return {number} - */ -export function getTopDistance(el, container) { - if (!el) return 0 - - if (!el.getClientRects().length) { - return 0 - } - - const rect = el.getBoundingClientRect() - - if (rect.width || rect.height) { - if (container === window) { - container = el.ownerDocument.documentElement - return rect.top - container.clientTop - } - return rect.top - container.getBoundingClientRect().top - } - - return rect.top -} - -/** - * 加载js或css - * - * @param url {string} 资源地址 - * @param type {string} 'js'或'css' - * @return {Promise} 加载成功返回url(若资源此前已加载,返回undefined) - */ -export function loadExternalResource(url, type = 'js') { - return new Promise((resolve, reject) => { - let tag - - if (type === "css") { - const links = Array.from(document.getElementsByTagName('link')) - if (links.some(link => link.getAttribute('href') === url)) { - return resolve() - } - - tag = document.createElement("link") - tag.rel = "stylesheet" - tag.href = url - } - else if (type === "js") { - const scripts = Array.from(document.getElementsByTagName('script')) - if (scripts.some(script => script.getAttribute('src') === url)) { - return resolve() - } - - tag = document.createElement("script") - tag.src = url - } - - if (tag) { - tag.onload = () => resolve(url) - tag.onerror = () => reject(url) - document.head.appendChild(tag) - } - else reject(`没有这个东东,url:${url},type:${type}`) - }) -} - -/** - * 将dom按最小距离垂直地滚动至视窗内 - * 比如dom在视窗下,那么会滚动到视窗底部 - * - * @param child {HTMLElement} 需要滚动的dom - * @param parent {HTMLElement|Window} 包含child的容器 - */ -export function scrollIntoViewVertically(child, parent = window) { - const {scrollTop, scrollHeight, offsetHeight: containerHeight} = parent - - //当菜单高度不足以滚动时跳过 - if (scrollHeight <= containerHeight) return - - const elHeight = child.offsetHeight, between = getTopDistance(child, parent) - - //计算需要滚动的距离,undefined说明不需要滚动 - let distance - - if (between < 0) distance = between - else if (between + elHeight > containerHeight) { - distance = between + elHeight - containerHeight - } - - if (distance !== undefined) { - parent.scrollTo({top: scrollTop + distance, behavior: 'smooth'}) - } -} - -/** - * 平滑滚动至指定的位置 - * - * @param el {Window|HTMLElement|string|function} 滚动容器,或可用于querySelector的字符串,或一个返回DOM的函数 - * @param position {number} 滚动的目的地 - * @param options 配置项 - * @param options.callback {function} 滚动完成的回调 - * @param options.duration {number} 滚动耗时 - * @param options.direction {string} 滚动方向,top滚动至距元素顶部distance的位置,left滚动至距元素左边distance的位置 - */ -export function scrollTo(el, position, options) { - const {callback, duration = 300, direction = 'top'} = options || {} - - if (typeof el === 'string') { - el = document.querySelector(el) - } - else if (typeof el === 'function') { - el = el() - } - - if (!isDom(el)) return - - const toTop = direction === 'top' - const elPosition = getScroll(el, toTop) - const scrollFunc = (el => { - if (el === window) { - return toTop - ? y => el.scrollTo(window.pageXOffset, window.pageYOffset + y) - : x => el.scrollTo(window.pageXOffset + x, window.pageYOffset) - } - return toTop - ? y => el.scrollTop += y - : x => el.scrollLeft += x - })(el) - - let times = duration / 16, distance = (position - elPosition) / (times + 1) - - const frameFunc = () => { - if (times > 0) { - scrollFunc(distance) - times-- - return window.requestAnimationFrame(frameFunc) - } - typeof callback === 'function' && callback() - } - - return window.requestAnimationFrame(frameFunc) -} - -/** - * 获取元素的滚动距离 - * - * @param el {Window|HTMLElement} - * @param top {boolean} 是否获取垂直的滚动距离 - * @return {number} - */ -export function getScroll(el, top) { - return el === window - ? el[top ? 'pageYOffset' : 'pageXOffset'] - : el[top ? 'scrollTop' : 'scrollLeft'] -} diff --git a/vue/template/src/util/element-ui/elForm.js b/vue/template/src/util/element-ui/elForm.js deleted file mode 100644 index e69de29..0000000 diff --git a/vue/template/src/util/element-ui/elMenu.js b/vue/template/src/util/element-ui/elMenu.js deleted file mode 100644 index 29eaa6e..0000000 --- a/vue/template/src/util/element-ui/elMenu.js +++ /dev/null @@ -1,35 +0,0 @@ -import {scrollIntoViewVertically} from "@/util/browser" - -/** - * 将当前激活的菜单移动到视窗中(仅限垂直菜单) - * - * @param menu el-menu实例 - */ -export function moveToActiveMenuVertically(menu) { - if (!menu) return - - const cur = menu.activeIndex - if (!cur) return - - const curInstance = menu.items[cur] - if (!curInstance) return - - let el = curInstance.$el - - //当侧边栏折叠时,需要滚动至可视区域的元素是激活菜单的最顶层父节点 - if (menu.collapse) { - let rootParent = curInstance - while (rootParent.$parent.$options.componentName !== 'ElMenu') { - rootParent = rootParent.$parent - } - el = rootParent.$el - } - - /* - * 这里考虑了菜单展开时的200ms动画时间 - * 为什么不分情况讨论?比如当subMenu已经是展开状态时,无需延时滚动 - * 但这种情况无法判断,因为这时menu.openedMenus已经包含了subMenu,无论subMenu之前是否展开 - * 所以统一延时300ms - * */ - window.setTimeout(() => scrollIntoViewVertically(el, menu.$el), 300) -} diff --git a/vue/template/src/util/element-ui/elTree.js b/vue/template/src/util/element-ui/elTree.js deleted file mode 100644 index d43f7dc..0000000 --- a/vue/template/src/util/element-ui/elTree.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * el-tree展开折叠控制 - * - * @param ref el-tree实例 - * @param action 'expand' | 'collapse' - * @param level 展开的节点最大深度,为0时匹配全部节点 - * @param func 自定义展开的函数,传入一个node,返回boolean,优先使用 - */ -export function expandControl(ref, action = 'expand', level = 1, func) { - const handler = function () { - if (typeof func === 'function') { - return node => func(node) - } - - const expand = action === 'expand' - const forAll = level === 0 - - return node => node.expanded = forAll || node.level <= level ? expand : !expand - }() - - ref.store._getAllNodes().forEach(handler) -} diff --git a/vue/template/src/util/index.js b/vue/template/src/util/index.js index 56773e6..2034d42 100644 --- a/vue/template/src/util/index.js +++ b/vue/template/src/util/index.js @@ -1,12 +1,6 @@ -/** - * 判断是否为空值,undefined、null、'' 都视为空值 - * - * @param str 不定参数 - * @return {boolean} 若为空值,返回true,否则返回false - */ -export function isEmpty(...str) { - return str.some(i => i === undefined || i === null || i === '') -} +import {isEmpty, getInitialValue, debounce, deepClone} from "el-admin-layout/src/util" + +export {isEmpty, getInitialValue, debounce, deepClone} /** * 当传入空值时,返回默认值 @@ -33,37 +27,6 @@ export function replaceAll(str, substr, replacement) { return str.replace(new RegExp(substr, 'gm'), replacement) } -/** - * 根据传入值的类型,返回基础起始值 - * - * @param v - * @return {boolean|{}|string|*[]|number|null} - */ -export function getInitialValue(v) { - if (v === undefined || v === null) return null - if (typeof v === 'string') return '' - if (typeof v === 'boolean') return false - if (typeof v === 'number') return 0 - if (typeof v === 'object') return {} - if (Array.isArray(v)) return [] -} - -/** - * 简单重置对象属性,遇到对象时会递归重置 - * 重置方法使用{@link #getInitialValue} - * - * @param obj 需要重置的对象 - */ -export function resetObj(obj) { - if (isEmpty(obj)) return - Object.keys(obj).forEach(key => { - if (obj[key] !== null && typeof obj[key] === 'object') { - resetObj(obj[key]) - } - else obj[key] = getInitialValue(obj[key]) - }) -} - /** * 将source合并到target中 * 仅对target中存在的键进行合并 @@ -99,171 +62,3 @@ export function mergeObj(target, source) { target[key] = source[key] } } - -/** - * 日期格式化 - * - * @param fmt {*|string} 格式,y(+..y)-年、M(+M)-月、d(+d)-天、H(+H)-时、m(+m)-分、s(+s)-秒、S-毫秒 - * @param date {Date} 可选,被格式化的日期 - * @return {string} 格式化后的日期字符串 - */ -export function timeFormat(fmt, date = new Date()) { - if (isEmpty(fmt)) fmt = 'yyyy-MM-dd HH:mm:ss' - - const o = { - "M+": date.getMonth() + 1, //月份 - "d+": date.getDate(), //日 - "H+": date.getHours(), //小时 - "m+": date.getMinutes(), //分 - "s+": date.getSeconds(), //秒 - "S": date.getMilliseconds() //毫秒 - } - - if (/(y+)/.test(fmt)) { - const replace = (date.getFullYear() + "").substring(4 - RegExp.$1.length) - fmt = fmt.replace(RegExp.$1, [...replace].join('')) - } - - for (const k in o) { - if (new RegExp(`(${k})`).test(fmt)) { - const firstMatch = RegExp.$1 - const replace = firstMatch.length === 1 ? o[k] + "" : ("00" + o[k]).substring(("" + o[k]).length) - fmt = fmt.replace(firstMatch, [...replace].join('')) - } - } - - return fmt -} - -/** - * 防抖 - * - * @param func {function} 原函数 - * @param wait {number} 防抖间隔,单位毫秒 - * @param immediate {boolean} 是否立即执行一次 - * @return {function} 经过防抖包装后的函数 - */ -export function debounce(func, wait = 100, immediate = false) { - let timeout, args, context, timestamp, result - - const later = function () { - // 据上一次触发时间间隔 - const last = new Date().getTime() - timestamp - - // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait - if (last < wait && last > 0) { - timeout = window.setTimeout(later, wait - last) - } - else { - timeout = null - // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 - if (!immediate) { - result = func.apply(context, args) - if (!timeout) context = args = null - } - } - } - - return function () { - context = this - args = arguments - timestamp = new Date().getTime() - const callNow = immediate && !timeout - // 如果延时不存在,重新设定延时 - if (!timeout) timeout = window.setTimeout(later, wait) - if (callNow) { - result = func.apply(context, args) - context = args = null - } - - return result - } -} - -export function throttle(func, delay = 100) { - let timeoutID - let lastExec = 0 - - function wrapper() { - const self = this - const elapsed = Date.now() - lastExec - const args = arguments - - function exec() { - lastExec = Date.now() - func.apply(self, args) - } - - window.clearTimeout(timeoutID) - - if (elapsed > delay) exec() - else timeoutID = window.setTimeout(exec, delay - elapsed) - } - - return wrapper -} - -/** - * 循环等待成功事件 - * - * @param success {function} 返回true时说明成功 - * @param callback {function} 成功后的回调 - * @param interval {number} 循环间隔,毫秒 - * @param maxTryTime {number} 最大循环次数,超出reject,小于1视为Infinity - * @return {Promise} - */ -export function waitUntilSuccess(success, callback, interval = 1000, maxTryTime = 0) { - return new Promise((resolve, reject) => { - let fun, count = 0 - - const check = () => { - if (success()) { - window.clearInterval(fun) - typeof callback === 'function' && callback() - return resolve() - } - if (maxTryTime >= 1 && ++count >= maxTryTime) { - return reject() - } - } - - if (success()) return check() - - fun = window.setInterval(check, interval) - }) -} - -//将传入对象的所有函数的this绑定为其自身 -export function bindThis(obj, root = obj) { - if (!obj || typeof obj !== 'object') return - - Object.entries(obj).forEach(([k, v]) => { - if (typeof v === 'function') { - obj[k] = v.bind(root) - } - bindThis(v, root) - }) - - return obj -} - -export function deepClone(source) { - if (source === null || typeof source !== 'object' || source instanceof Promise) { - return source - } - - if (Array.isArray(source)) { - return source.map(i => deepClone(i)) - } - else { - return Object.keys(source).reduce((obj, key) => { - obj[key] = deepClone(source[key]) - return obj - }, {}) - } -} - -//有空值判断的trim -export function trim(str) { - return isEmpty(str) ? str : str.trim() -} diff --git a/vue/template/src/util/message.js b/vue/template/src/util/message.js index 39285cc..d4c5df4 100644 --- a/vue/template/src/util/message.js +++ b/vue/template/src/util/message.js @@ -1,10 +1,5 @@ import Message from '@ele/component/Message' import {MessageBox} from 'element-ui' -import {isEmpty} from "@/util" - -export function elError(msg = '操作失败') { - Message.error(msg) -} export function elSuccess(message = '操作成功') { Message.success({ @@ -13,14 +8,6 @@ export function elSuccess(message = '操作成功') { }) } -export function elAlert(msg, callback = () => ({})) { - return MessageBox.alert(msg, { - type: 'warning', - cancelButtonClass: 'is-plain', - callback - }) -} - export function elConfirm(msg = '确认进行该操作?', ignoreReject = true) { const promise = MessageBox.confirm(msg, { type: 'warning', @@ -33,20 +20,3 @@ export function elConfirm(msg = '确认进行该操作?', ignoreReject = true) return promise } - -export function elPrompt(msg) { - return new Promise(resolve => { - MessageBox.prompt(msg, { - type: 'warning', - cancelButtonClass: 'is-plain', - inputType: 'textarea', - inputValidator: str => { - if (isEmpty(str)) return '请输入内容' - if (str.length > 200) return '长度最多200' - return true - } - }) - .then(({value}) => resolve(value)) - .catch(() => ({})) - }) -} diff --git a/vue/template/src/util/observable.js b/vue/template/src/util/observable.js deleted file mode 100644 index 1f133bb..0000000 --- a/vue/template/src/util/observable.js +++ /dev/null @@ -1,36 +0,0 @@ -import {getInitialValue} from "@/util" - -//为Vue.observer返回的对象设置getter -export function createGetters(store) { - const getters = Object.create({}) - Object.defineProperties( - getters, - Object.keys(store).reduce((obj, key) => { - obj[key] = { - enumerable: true, - get() { - return store[key] - } - } - return obj - }, {}) - ) - return getters -} - -//设置mutation -export function createMutations(store, all = false) { - const keys = Object.keys(store) - const obj = {} - keys.forEach(key => { - obj[key] = v => store[key] = v - }) - if (all) { - obj['$all'] = v => { - keys.forEach(key => { - store[key] = v && v[key] || getInitialValue(store[key]) - }) - } - } - return obj -} diff --git a/vue/template/src/util/route.js b/vue/template/src/util/route.js deleted file mode 100644 index 4e94bba..0000000 --- a/vue/template/src/util/route.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 路由控制工具类 - */ - -import {isEmpty} from "@/util" -import {isString} from "@/util/validate" -import router from '@/router' -import {mutations as tagsViewMutations} from "@/layout/store/tagsView" - -/** - * 路由刷新 - * - * @param route {string|route} 需要刷新的路由,不传时为当前路由,如果是字符串时请确保以'/'开头 - * @param replace {boolean} 是否使用replace进行跳转 - * @return {Promise} 返回vue-router跳转的结果 - */ -export function refreshPage(route = router.currentRoute, replace = true) { - const target = `/redirect${isString(route) ? route : route.fullPath}` - return router[replace ? 'replace' : 'push'](target) -} - -/** - * 关闭当前页,如果传入next则跳转到next页面 - * - * @param next {string|route} 跳转的目标页面,作为第一个参数传入vue-router.replace - * @return {undefined|Promise} 仅在next有值时,返回vue-router.replace的结果 - */ -export function closeCurrentPage(next) { - tagsViewMutations.delTagAndCache(router.currentRoute) - if (!isEmpty(next)) { - return router.replace(next) - } -} diff --git a/vue/template/src/util/storage.js b/vue/template/src/util/storage.js index 7475c05..e74758d 100644 --- a/vue/template/src/util/storage.js +++ b/vue/template/src/util/storage.js @@ -39,16 +39,6 @@ export function set(key, obj, storage = window.sessionStorage, useZip = defaultU storage.setItem(`${keyPrefix}${key}`, item) } -/** - * 判断本地存储中是否存在指定键名 - * @param key {string} 键名,自动加上统一前缀 - * @param storage {Storage} window.sessionStorage或window.localStorage - * @return {boolean} 存在则返回true - */ -export function exist(key, storage = window.sessionStorage) { - return !isEmpty(storage.getItem(`${keyPrefix}${key}`)) -} - /** * 根据指定键名,移除本地存储对应项 * @param key {string} 键名,自动加上统一前缀 diff --git a/vue/template/src/util/tree.js b/vue/template/src/util/tree.js index 2cd0d40..29e0797 100644 --- a/vue/template/src/util/tree.js +++ b/vue/template/src/util/tree.js @@ -1,127 +1,3 @@ -import {deepClone} from "@/util" - -const DEFAULT_PROPS = { - id: 'id', - pid: 'pid', - children: 'children', - rootPredicate: 0, - leafHasChildren: false -} - -/** - * 列表转树形结构 - * - * @param list {array} - * @param props 树的配置项 - * @param props.id {string} 节点的id属性名称 - * @param props.pid {string} 节点的父级id属性名称 - * @param props.children {string} 节点的子级节点属性名称 - * @param props.rootPredicate {*|function} 根节点的判定方法,传入函数时参数为节点对象 - * @param props.leafHasChildren {boolean} 叶子节点是否能有children属性 - * @return {array} - */ -export function createTree(list, props = {}) { - if (!list || list.length <= 0) return [] - - const { - id = DEFAULT_PROPS.id, - pid = DEFAULT_PROPS.pid, - children = DEFAULT_PROPS.children, - rootPredicate = DEFAULT_PROPS.rootPredicate, - leafHasChildren = DEFAULT_PROPS.leafHasChildren - } = props - - const info = {} - - list.forEach(i => { - info[i[id]] = i - if (leafHasChildren) i[children] = [] - }) - - const predicate = (() => { - return typeof rootPredicate === 'function' - ? rootPredicate - : key => key === rootPredicate - })() - - return list.filter(node => { - const key = node[pid] - const parent = info[key] - if (parent) { - if (!parent[children]) parent[children] = [] - parent[children].push(node) - } - return predicate(key, node) - }) -} - -/** - * 完全树full,拿到某些带value的节点数组limit,获得裁剪后的树 - * 只用于裁剪客户和供应商的行政区域树 - * - * @param full {array} - * @param limit {array} - */ -export function createLimitTree(full, limit) { - const map = limit.reduce((m, n) => { - m[n.id] = n.value - return m - }, {}) - - full = deepClone(full) - - const result = shakeTree(full, node => { - const value = map[node.id] - if (value !== undefined) { - node.value = value - return true - } - return false - }) - - result.forEach(i => calc(i)) - - return result -} - -/** - * createLimitTree的另一版本 - * - * @param fullMap 完全树的节点map - * @param limit {array} - */ -export function createLimitTreeByMap(fullMap, limit) { - const resultNodes = {} - - //从该节点往上查找父节点 - function findParent(node) { - //如果该节点已存在于结果集中,说明包含该节点的上级分支也全部存在,所以跳过 - if (resultNodes[node.id]) return - - //使用full中的数据,仅保留node的value - const fullNode = fullMap[node.id] - if (!fullNode) return - resultNodes[fullNode.id] = {...node, ...fullNode} - - //查找父节点 - const parent = fullMap[fullNode.pid] - if (!parent) return - - return findParent(parent) - } - - for (const node of limit) { - if (!fullMap[node.id]) continue - findParent(node) - } - - const result = createTree(Object.values(resultNodes), {rootPredicate: '0'}) - - result.forEach(i => calc(i)) - - return result -} - /** * 根据判断函数裁剪树,当节点不满足predicate且无下级节点时将被裁剪 * 此方法会改变原数组的children属性! @@ -144,84 +20,3 @@ export function shakeTree(tree, predicate = () => true, childrenKey = 'children' return predicate(data) || children && children.length > 0 }) } - -/** - * 自底向上计算每个节点的value - * - * @param node - * @param valueKey {string} - * @param childrenKey {string} - * @return {number} - */ -function calc(node, valueKey = 'value', childrenKey = 'children') { - if (!node) return 0 - - const children = node[childrenKey] - const childValue = node[valueKey] || 0 - - if (!children) return childValue - - const value = childValue + children.reduce((v, child) => v + calc(child), 0) - - node[valueKey] = value - - return value -} - -//树转一维id数组 -export function getNodeId(arr) { - if (!arr) return [] - - const res = [] - - arr.forEach(i => { - res.push(i.id) - if (i.children && i.children.length > 0) { - res.push(...getNodeId(i.children)) - } - }) - - return res -} - -//树转一维数组 -export function flatTree(tree, childrenKey = 'children') { - const result = [] - tree.forEach(node => { - if (node[childrenKey]) { - result.push(node) - result.push.apply(result, flatTree(node[childrenKey], childrenKey)) - } - else result.push(node) - }) - return result -} - -export function getNodesByDfs(node) { - const nodes = [] - const stack = [node] - - while (stack.length > 0) { - const item = stack.pop() - nodes.push(item) - const children = item.children - for (let i = children.length - 1; i >= 0; i--) { - stack.push(children[i]) - } - } - - return nodes -} - -export function getNodesByBfs(node) { - const nodes = [] - const queue = [node] - - while (queue.length > 0) { - const item = queue.shift() - nodes.push(item) - queue.push(...item.children) - } - - return nodes -} diff --git a/vue/template/src/util/validate.js b/vue/template/src/util/validate.js index 2bcd9df..eb14b00 100644 --- a/vue/template/src/util/validate.js +++ b/vue/template/src/util/validate.js @@ -2,49 +2,6 @@ export function isString(str) { return typeof str === 'string' || str instanceof String } -export function isInteger(v) { - const t = parseFloat(v) - return t.toString() !== 'NaN' && t < 2147483647 && t > -2147483648 -} - export function isExternal(path) { return /^(https?:|mailto:|tel:)/.test(path) } - -export function isEmail(email) { - const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - return reg.test(email) -} - -export function isImage(suffix) { - const reg = /\.(png|jpg|gif|jpeg|webp|bmp)$/ - return reg.test(suffix.toLowerCase()) -} - -export function isDoc(suffix) { - return /\.(doc|docx)$/.test(suffix.toLowerCase()) -} - -export function isPdf(suffix) { - return /\.pdf$/.test(suffix.toLowerCase()) -} - -export function isPpt(suffix) { - return /\.(ppt|pptx)$/.test(suffix.toLowerCase()) -} - -export function isRar(suffix) { - return /\.rar$/.test(suffix.toLowerCase()) -} - -export function isXls(suffix) { - return /\.(xls|xlsx)$/.test(suffix.toLowerCase()) -} - -export function isTxt(suffix) { - return /\.txt$/.test(suffix.toLowerCase()) -} - -export function isZip(suffix) { - return /\.zip$/.test(suffix.toLowerCase()) -} diff --git a/vue/template/src/util/vue.js b/vue/template/src/util/vue.js deleted file mode 100644 index bcb6d1b..0000000 --- a/vue/template/src/util/vue.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * 根据组件标签名称获取组件实例 - * 使用广度优先搜索 - * - * @param instance 从哪个组件实例开始查找,一般是this - * @param tag 要查找的组件的标签名称 - */ -export function findComponentByTag(instance, tag) { - if (!instance || !tag) return - - const queue = [instance] - - while (queue.length > 0) { - const item = queue.shift() - const {$options, $children} = item - - if ($options._componentTag === tag) return item - - if (Array.isArray($children)) { - queue.push(...$children) - } - } -} diff --git a/vue/template/src/view/_common/SkeletonPage/style.scss b/vue/template/src/view/_common/SkeletonPage/style.scss index aaa2b99..59b2b9e 100644 --- a/vue/template/src/view/_common/SkeletonPage/style.scss +++ b/vue/template/src/view/_common/SkeletonPage/style.scss @@ -1,4 +1,4 @@ -@import "~@/asset/style/var"; +@import "~el-admin-layout/src/style/var"; .skeleton-page-wrapper { margin: $page-view-margin; diff --git a/vue/template/vue.config.js b/vue/template/vue.config.js index e429e3e..e724b9b 100644 --- a/vue/template/vue.config.js +++ b/vue/template/vue.config.js @@ -14,7 +14,7 @@ module.exports = { runtimeCompiler: true, lintOnSave: false, productionSourceMap: settings.isDev, - parallel: true, + transpileDependencies: ['el-admin-layout'], devServer: { port: process.env.port || 8079, contentBasePublicPath: settings.contextPath,