diff --git a/Web/src/assets/style/main.less b/Web/src/assets/style/main.less index 3853d6f..7d7dbd6 100644 --- a/Web/src/assets/style/main.less +++ b/Web/src/assets/style/main.less @@ -320,6 +320,99 @@ } } } + .yo-layout-sider { + height: 100%; + + background-color: @nav-background; + .ant-layout-sider-children { + display: flex; + flex-direction: column; + } + .logo { + font-size: @font-size-lg * 1.5; + font-weight: 500; + line-height: @layout-header-height + 10px; + + z-index: 11; + + display: flex; + overflow: hidden; + align-items: center; + flex: 0 0 @layout-header-height + 10px; + + height: @layout-header-height + 10px; + padding: 0 @padding-md 0 @padding-lg; + + color: @logo-color; + box-shadow: @logo-box-shadow; + img { + max-height: 100%; + } + span { + margin-left: @padding-sm; + + transition: @animation-duration-slow; + transition-property: opacity; + } + } + &.ant-layout-sider-collapsed { + .logo { + span { + opacity: 0; + } + } + } + .yo-sider-nav { + position: relative; + z-index: 10; + + flex: 1 1 100%; + + box-shadow: 2px 0 8px @nav-box-shadow-color; + &--app { + font-size: @font-size-sm; + + margin-top: @padding-sm; + padding: 0 @padding-md; + + color: @nav-app-color; + } + } + .swiper-container { + position: absolute; + top: 0; + left: 0; + bottom: 0; + + width: 100%; + .swiper-scrollbar { + transition: @animation-duration-slow; + transition-property: opacity; + + opacity: 0; + } + .swiper-scrollbar-drag { + background-color: @nav-scrollbar-background; + } + &:hover { + .swiper-scrollbar { + opacity: 1; + } + } + } + .swiper-slide { + height: auto; + min-height: 100%; + >.ant-spin-nested-loading { + height: 100%; + .ant-spin-blur { + &::after { + opacity: 0; + } + } + } + } + } .yo-layout--left-menu, .yo-layout--right-menu { position: absolute; @@ -366,99 +459,11 @@ } } } + .layout-sider { + } >section { >.ant-layout-sider { - height: 100%; - - background-color: @nav-background; - .ant-layout-sider-children { - display: flex; - flex-direction: column; - } - .logo { - font-size: @font-size-lg * 1.5; - font-weight: 500; - line-height: @layout-header-height + 10px; - - z-index: 11; - - display: flex; - overflow: hidden; - align-items: center; - flex: 0 0 @layout-header-height + 10px; - - height: @layout-header-height + 10px; - padding: 0 @padding-md 0 @padding-lg; - - color: @logo-color; - box-shadow: @logo-box-shadow; - img { - max-height: 100%; - } - span { - margin-left: @padding-sm; - - transition: @animation-duration-slow; - transition-property: opacity; - } - } - &.ant-layout-sider-collapsed { - .logo { - span { - opacity: 0; - } - } - } - .yo-sider-nav { - position: relative; - z-index: 10; - - flex: 1 1 100%; - - box-shadow: 2px 0 8px @nav-box-shadow-color; - &--app { - font-size: @font-size-sm; - - margin-top: @padding-sm; - padding: 0 @padding-md; - - color: @nav-app-color; - } - } - .swiper-container { - position: absolute; - top: 0; - left: 0; - bottom: 0; - - width: 100%; - .swiper-scrollbar { - transition: @animation-duration-slow; - transition-property: opacity; - - opacity: 0; - } - .swiper-scrollbar-drag { - background-color: @nav-scrollbar-background; - } - &:hover { - .swiper-scrollbar { - opacity: 1; - } - } - } - .swiper-slide { - height: auto; - min-height: 100%; - >.ant-spin-nested-loading { - height: 100%; - .ant-spin-blur { - &::after { - opacity: 0; - } - } - } - } + .yo-layout-sider(); } } } @@ -473,9 +478,16 @@ width: 100%; min-width: @container-width; height: 100%; + + @layout-header-height: 54px; .ant-layout-header { + line-height: @layout-header-height; + + z-index: 11; + flex: 0 0 @layout-header-height; + height: @layout-header-height; padding: 0; background-color: @nav-background; @@ -515,19 +527,19 @@ } } .user-container { - margin: 12px 0; + margin: (@layout-header-height - 40px) / 2 0; } .logo { font-size: @font-size-lg * 1.5; font-weight: 500; - line-height: @layout-header-height; + line-height: @layout-header-height - 10px; display: flex; overflow: hidden; align-items: center; - height: @layout-header-height; - margin-right: @padding-lg; + height: @layout-header-height 10px; + margin: 5px @padding-lg 5px 0; color: @logo-color; img { diff --git a/Web/src/main.js b/Web/src/main.js index ab56557..1e13ac4 100644 --- a/Web/src/main.js +++ b/Web/src/main.js @@ -100,6 +100,11 @@ import './assets/style/app.less' import { SETTING_KEY } from './common/storage' const settings = JSON.parse(window.localStorage.getItem(SETTING_KEY)) +Object.assign(settings, { + layout: 'top-nav', + container: 'container-fluid', + navTheme: 'dark' +}) const app = new Vue({ data: { diff --git a/Web/src/views/main-dynamic/_layout/content.vue b/Web/src/views/main-dynamic/_layout/content.vue new file mode 100644 index 0000000..f049914 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/content.vue @@ -0,0 +1,166 @@ + + \ No newline at end of file diff --git a/Web/src/views/main-dynamic/_layout/header/index.vue b/Web/src/views/main-dynamic/_layout/header/index.vue new file mode 100644 index 0000000..06fd340 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/header/index.vue @@ -0,0 +1,100 @@ + + \ No newline at end of file diff --git a/Web/src/views/main-dynamic/_layout/header/search.js b/Web/src/views/main-dynamic/_layout/header/search.js new file mode 100644 index 0000000..ea452f0 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/header/search.js @@ -0,0 +1,149 @@ +export default { + props: { + menus: { + type: Array, + require: true + } + }, + + data() { + return { + searchText: '', + searchResult: [], + + timer: null + }; + }, + + methods: { + + renderSelect(menu) { + return menu.map((p) => { + return p.children ? this.renderSelectGroup(p) : this.renderSelectOption(p) + }) + }, + + renderSelectGroup(menu) { + return ( + + + {menu.parents} + + {this.renderSelect(menu.children)} + + ) + }, + + renderSelectOption(menu) { + return ( + {menu.meta.icon && } + {menu.meta.title} + {menu.component} + ) + }, + + + onSearch(value) { + clearTimeout(this.timer) + + this.timer = setTimeout(() => { + this.doSearch(value) + }, 300) + }, + + doSearch(value) { + this.searchText = value + + const menus = this.$_.concat.apply(this, this.$_.cloneDeep(this.menus.map(p => p.menu))) + + const search = (m) => { + if (!value) return [] + return m.filter((p) => { + if (p.children) { + p.children = search(p.children) + } else { + return p.meta.title.indexOf(value) > -1 || (p.component || '').toLowerCase().indexOf(value.toLowerCase()) > -1 + } + return p.children.length + }) + } + + /** + * 拆分层级,只留下 [父级-父级-...] [子级] + * *******在更为复杂的目录下会出现父级联动错误的问题 + */ + const unzip = (m) => { + const getSeed = (parent, seed) => { + if (parent.children) { + seed.parents.push(parent.meta.title) + seed.children = parent.children + parent.children.forEach(p => { + getSeed(p, seed) + }) + } + return seed + } + const result = [] + m.forEach(p => { + const r = { parents: [], children: [] } + result.push(getSeed(p, r)) + }) + return result + } + + const result = unzip(search(menus)).filter(p => p.parents.length).map(p => { + return { + parents: p.parents.join('-'), + children: p.children + } + }) + + this.searchResult = result + }, + + onSearchSelect(value, node) { + this.searchText = ''; + this.onSearch(this.searchText); + + const menu = JSON.parse(node.componentOptions.propsData.value) + + this.openContentWindow({ + key: menu.id, + title: menu.meta.title, + icon: menu.meta.icon, + path: menu.component, + }); + }, + + }, + + render() { + + const props = { + dropdownMatchSelectWidth: false, + dropdownStyle: { width: '300px' }, + optionLabelProp: 'value', + placeholder: '请输入检索关键字', + value: this.searchText + } + + const on = { + search: this.onSearch, + select: this.onSearchSelect + } + + return ( + + + + + + + ) + + } +} \ No newline at end of file diff --git a/Web/src/views/main-dynamic/_layout/header/user.js b/Web/src/views/main-dynamic/_layout/header/user.js new file mode 100644 index 0000000..fe89071 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/header/user.js @@ -0,0 +1,91 @@ +let userOpenTimer, userCloseTimer + +let initDropdownHeight + +import { PERVIEW_URL } from '@/util/global'; + +import { doLogout } from '@/common/login' + +export default { + data() { + return { + dropdownHeight: 0 + } + }, + mounted() { + initDropdownHeight = this.$refs.dropdown.scrollHeight + }, + methods: { + onOpen(e) { + clearTimeout(userCloseTimer) + e.target.classList.add('open') + userOpenTimer = setTimeout(() => { + e.target.classList.add('drop') + this.dropdownHeight = initDropdownHeight + }, 300) + }, + + onClose(e) { + clearTimeout(userOpenTimer) + e.target.classList.remove('drop') + this.dropdownHeight = 0 + userCloseTimer = setTimeout(() => { + e.target.classList.remove('open') + }, 300) + }, + + onAccountSetting() { + this.openContentWindow({ + key: 'account-home', + title: '个人中心', + icon: 'user', + path: '/system/account' + }) + }, + + onLogout() { + this.$confirm({ + title: '提示', + content: '是否确定退出登录', + onOk: async () => { + await doLogout() + }, + onCancel() { + } + }) + } + }, + + render() { + return ( +
+
+
+ { + this.$root.global.info && + } + { + this.$root.global.info && + {this.$root.global.info.nickName || this.$root.global.info.name} + } +
+
+
    +
  • + + 个人中心 +
  • +
  • +
  • + + 退出登录 +
  • +
+
+
+
+ ) + } +} \ No newline at end of file diff --git a/Web/src/views/main-dynamic/_layout/logo.vue b/Web/src/views/main-dynamic/_layout/logo.vue new file mode 100644 index 0000000..309fc67 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/logo.vue @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/Web/src/views/main/_layout/nav/index.js b/Web/src/views/main-dynamic/_layout/nav/index.js similarity index 100% rename from Web/src/views/main/_layout/nav/index.js rename to Web/src/views/main-dynamic/_layout/nav/index.js diff --git a/Web/src/views/main-dynamic/_layout/sider/index.vue b/Web/src/views/main-dynamic/_layout/sider/index.vue new file mode 100644 index 0000000..57b9069 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/sider/index.vue @@ -0,0 +1,116 @@ + + \ No newline at end of file diff --git a/Web/src/views/main-dynamic/_layout/sider/menu.js b/Web/src/views/main-dynamic/_layout/sider/menu.js new file mode 100644 index 0000000..a4cccc5 --- /dev/null +++ b/Web/src/views/main-dynamic/_layout/sider/menu.js @@ -0,0 +1,92 @@ +import { HmacMD5 } from "crypto-js" + +export default { + props: { + nav: { + default() { + return { + content: [] + } + }, + type: Object, + }, + menuStyle: { + default() { + return {} + }, + type: Object || String, + }, + mode: { + default: 'inline', + type: String, + }, + }, + + methods: { + renderMenu(menu) { + return menu.map((p) => { + return p.children ? this.renderSubMenu(p) : this.renderMenuItem(p) + }) + }, + + renderSubMenu(menu) { + return ( + + + {menu.meta.icon && } + {menu.meta.title} + + {this.renderMenu(menu.children)} + + ) + }, + + renderMenuItem(menu) { + return ( + this.onOpenContentWindow(menu)}> + {menu.meta.icon && } + {menu.meta.title} + + ) + }, + + onMenuOpenChange() { + this.$emit('openChange') + }, + + onOpenContentWindow(menu) { + this.openContentWindow({ + key: menu.id, + title: menu.meta.title, + icon: menu.meta.icon, + path: menu.component, + }) + }, + }, + + render() { + const props = { + mode: this.mode, + selectable: false, + style: this.menuStyle, + theme: this.$root.global.settings.navTheme, + } + + const on = { + openChange: this.onMenuOpenChange, + } + + return (
+ { + this.nav.content.map(item => { + return ( +
+
{item.app.name}
+ {this.renderMenu(item.menu)} +
+ ) + }) + } +
) + }, +} \ No newline at end of file diff --git a/Web/src/views/main-dynamic/index.vue b/Web/src/views/main-dynamic/index.vue new file mode 100644 index 0000000..b841dde --- /dev/null +++ b/Web/src/views/main-dynamic/index.vue @@ -0,0 +1,239 @@ + + \ No newline at end of file diff --git a/Web/src/views/main/setting.vue b/Web/src/views/main-dynamic/setting.vue similarity index 100% rename from Web/src/views/main/setting.vue rename to Web/src/views/main-dynamic/setting.vue diff --git a/Web/src/views/main/_layout/header/index.vue b/Web/src/views/main/_layout/header/index.vue index 06fd340..62d708b 100644 --- a/Web/src/views/main/_layout/header/index.vue +++ b/Web/src/views/main/_layout/header/index.vue @@ -1,35 +1,11 @@ \ No newline at end of file diff --git a/Web/src/views/main/_layout/sider/index.vue b/Web/src/views/main/_layout/sider/index.vue index 57b9069..5eeede6 100644 --- a/Web/src/views/main/_layout/sider/index.vue +++ b/Web/src/views/main/_layout/sider/index.vue @@ -1,32 +1,29 @@