update 更换外框架布局
This commit is contained in:
@@ -320,54 +320,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.yo-layout--left-menu,
|
.yo-layout-sider {
|
||||||
.yo-layout--right-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
.ant-layout-header {
|
|
||||||
line-height: @layout-header-height - 20px;
|
|
||||||
|
|
||||||
z-index: 6;
|
|
||||||
|
|
||||||
height: @layout-header-height - 20px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
background-color: @white;
|
|
||||||
>section {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.header-actions {
|
|
||||||
.header-action {
|
|
||||||
line-height: @layout-header-height - 16px;
|
|
||||||
|
|
||||||
height: @layout-header-height - 20px;
|
|
||||||
.anticon {
|
|
||||||
color: fade(@black, 35%);
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
background-color: fade(@black, 5%);
|
|
||||||
.anticon {
|
|
||||||
color: @icon-color-hover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>.ant-input-search,
|
|
||||||
>.ant-select-auto-complete {
|
|
||||||
.ant-input {
|
|
||||||
&:focus {
|
|
||||||
background-color: fade(@black, 5%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>section {
|
|
||||||
>.ant-layout-sider {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
background-color: @nav-background;
|
background-color: @nav-background;
|
||||||
@@ -460,6 +413,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.yo-layout--left-menu,
|
||||||
|
.yo-layout--right-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.ant-layout-header {
|
||||||
|
line-height: @layout-header-height - 20px;
|
||||||
|
|
||||||
|
z-index: 6;
|
||||||
|
|
||||||
|
height: @layout-header-height - 20px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
background-color: @white;
|
||||||
|
>section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.header-actions {
|
||||||
|
.header-action {
|
||||||
|
line-height: @layout-header-height - 16px;
|
||||||
|
|
||||||
|
height: @layout-header-height - 20px;
|
||||||
|
.anticon {
|
||||||
|
color: fade(@black, 35%);
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: fade(@black, 5%);
|
||||||
|
.anticon {
|
||||||
|
color: @icon-color-hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>.ant-input-search,
|
||||||
|
>.ant-select-auto-complete {
|
||||||
|
.ant-input {
|
||||||
|
&:focus {
|
||||||
|
background-color: fade(@black, 5%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.layout-sider {
|
||||||
|
}
|
||||||
|
>section {
|
||||||
|
>.ant-layout-sider {
|
||||||
|
.yo-layout-sider();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.yo-layout--top-nav {
|
.yo-layout--top-nav {
|
||||||
@@ -473,9 +478,16 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: @container-width;
|
min-width: @container-width;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
|
@layout-header-height: 54px;
|
||||||
.ant-layout-header {
|
.ant-layout-header {
|
||||||
|
line-height: @layout-header-height;
|
||||||
|
|
||||||
|
z-index: 11;
|
||||||
|
|
||||||
flex: 0 0 @layout-header-height;
|
flex: 0 0 @layout-header-height;
|
||||||
|
|
||||||
|
height: @layout-header-height;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
background-color: @nav-background;
|
background-color: @nav-background;
|
||||||
@@ -515,19 +527,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.user-container {
|
.user-container {
|
||||||
margin: 12px 0;
|
margin: (@layout-header-height - 40px) / 2 0;
|
||||||
}
|
}
|
||||||
.logo {
|
.logo {
|
||||||
font-size: @font-size-lg * 1.5;
|
font-size: @font-size-lg * 1.5;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: @layout-header-height;
|
line-height: @layout-header-height - 10px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
height: @layout-header-height;
|
height: @layout-header-height 10px;
|
||||||
margin-right: @padding-lg;
|
margin: 5px @padding-lg 5px 0;
|
||||||
|
|
||||||
color: @logo-color;
|
color: @logo-color;
|
||||||
img {
|
img {
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ import './assets/style/app.less'
|
|||||||
import { SETTING_KEY } from './common/storage'
|
import { SETTING_KEY } from './common/storage'
|
||||||
|
|
||||||
const settings = JSON.parse(window.localStorage.getItem(SETTING_KEY))
|
const settings = JSON.parse(window.localStorage.getItem(SETTING_KEY))
|
||||||
|
Object.assign(settings, {
|
||||||
|
layout: 'top-nav',
|
||||||
|
container: 'container-fluid',
|
||||||
|
navTheme: 'dark'
|
||||||
|
})
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
166
Web/src/views/main-dynamic/_layout/content.vue
Normal file
166
Web/src/views/main-dynamic/_layout/content.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<a-layout-content>
|
||||||
|
<div class="yo-tab-external-mount">
|
||||||
|
<a-tabs @change="onChange" @edit="onClose" hide-add type="editable-card" v-model="actived">
|
||||||
|
<a-tab-pane
|
||||||
|
:closable="pane.closable"
|
||||||
|
:force-render="true"
|
||||||
|
:key="pane.key"
|
||||||
|
v-for="pane in panes"
|
||||||
|
>
|
||||||
|
<a-dropdown :trigger="['contextmenu']" slot="tab">
|
||||||
|
<div @click.middle="() => pane.closable && $emit('close', pane.key)">
|
||||||
|
<a-icon :type="pane.icon" v-if="pane.icon" />
|
||||||
|
{{ pane.title }}
|
||||||
|
<a-tooltip :title="pane.subTitle" placement="bottom" v-if="pane.subTitle">
|
||||||
|
<span class="yo-layout-tab-subtitle">{{`- ${pane.subTitle}`}}</span>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<a-menu slot="overlay">
|
||||||
|
<template v-if="mode === 'development'">
|
||||||
|
<a-menu-item @click="onCopyComponent(pane)" key="-1">
|
||||||
|
复制组件地址
|
||||||
|
<a-tag color="red">dev</a-tag>
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
</template>
|
||||||
|
<a-menu-item @click="onLoadContentWindow(pane.key)" key="0">重新加载</a-menu-item>
|
||||||
|
<a-menu-divider />
|
||||||
|
<a-menu-item
|
||||||
|
:disabled="!pane.closable"
|
||||||
|
@click="() => pane.closable && $emit('close', pane.key)"
|
||||||
|
key="1"
|
||||||
|
>关闭</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
|
:disabled="!hasOther(pane)"
|
||||||
|
@click="$emit('close-other', pane.key)"
|
||||||
|
key="2"
|
||||||
|
>关闭其他标签页</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
|
:disabled="!hasRight(pane)"
|
||||||
|
@click="$emit('close-right', pane.key)"
|
||||||
|
key="3"
|
||||||
|
>关闭右侧标签页</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</a-dropdown>
|
||||||
|
<!-- <component
|
||||||
|
:is="pane.component"
|
||||||
|
:key="pane.key"
|
||||||
|
:param="pane.param"
|
||||||
|
ref="panes"
|
||||||
|
v-if="pane.loaded"
|
||||||
|
/>-->
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
|
||||||
|
<div class="yo-tab-external-mount-content">
|
||||||
|
<div
|
||||||
|
:class="pane.key === actived ? 'yo-tab-external-tabpane-active' : 'yo-tab-external-tabpane-inactive'"
|
||||||
|
:key="pane.key"
|
||||||
|
class="yo-tab-external-tabpane"
|
||||||
|
v-for="pane in panes"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:id="pane.key"
|
||||||
|
:is="pane.component"
|
||||||
|
:key="pane.key"
|
||||||
|
:param="pane.param"
|
||||||
|
ref="panes"
|
||||||
|
v-if="pane.loaded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-layout-content>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import NProgress from 'nprogress';
|
||||||
|
import 'nprogress/nprogress.css';
|
||||||
|
|
||||||
|
NProgress.configure({ parent: '.ant-layout-content > .yo-tab-external-mount > .yo-tab-external-mount-content' });
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
panes: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
tabActived: {
|
||||||
|
type: [String, Number],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mode: process.env.VUE_APP_NODE_ENV,
|
||||||
|
actived: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
tabActived() {
|
||||||
|
if (this.tabActived !== this.actived) {
|
||||||
|
this.actived = this.tabActived;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.actived = this.tabActived;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onLoadContentWindow(key) {
|
||||||
|
NProgress.start();
|
||||||
|
const pane = this.panes.find((p) => p.key === key);
|
||||||
|
|
||||||
|
// 打开之前先销毁
|
||||||
|
// 销毁后重新生成的组件会放置到$refs.panes的最后,所以这里直接根据索引取是错误的
|
||||||
|
// const index = this.panes.indexOf(pane);
|
||||||
|
// const component = this.$refs.panes && this.$refs.panes[index];
|
||||||
|
// if (component) {
|
||||||
|
// component.$destroy();
|
||||||
|
// }
|
||||||
|
|
||||||
|
const i = import(`@/pages${pane.path}`);
|
||||||
|
pane.component = () => i;
|
||||||
|
pane.loaded = false;
|
||||||
|
i.then(() => {})
|
||||||
|
.catch(() => {
|
||||||
|
pane.component = () => import('@/views/error/404');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
pane.loaded = true;
|
||||||
|
NProgress.done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onClose(targetKey, action) {
|
||||||
|
if (action === 'remove') {
|
||||||
|
this.$emit('close', targetKey);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChange(activeKey) {
|
||||||
|
this.$emit('change', activeKey);
|
||||||
|
},
|
||||||
|
|
||||||
|
onCopyComponent(pane) {
|
||||||
|
try {
|
||||||
|
const copy = document.createElement('textarea');
|
||||||
|
document.body.append(copy);
|
||||||
|
copy.value = `/pages${pane.path}`;
|
||||||
|
copy.select();
|
||||||
|
setTimeout(() => {
|
||||||
|
document.execCommand('copy');
|
||||||
|
copy.remove();
|
||||||
|
this.$message.success('已复制到剪切板');
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
this.$message.error('复制错误');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hasOther(pane) {
|
||||||
|
return this.panes.filter((p) => p.key !== pane.key && p.closable).length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
hasRight(pane) {
|
||||||
|
return this.panes.length > this.panes.indexOf(pane) + 1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
100
Web/src/views/main-dynamic/_layout/header/index.vue
Normal file
100
Web/src/views/main-dynamic/_layout/header/index.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<a-layout-header class="header">
|
||||||
|
<section
|
||||||
|
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
|
||||||
|
>
|
||||||
|
<div class="header-actions">
|
||||||
|
<a
|
||||||
|
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
|
||||||
|
class="header-action"
|
||||||
|
>
|
||||||
|
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
|
||||||
|
</a>
|
||||||
|
<search :menus="nav.content" />
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<a @click="$emit('reload')" class="header-action">
|
||||||
|
<a-icon type="reload" />
|
||||||
|
</a>
|
||||||
|
<a class="header-action">
|
||||||
|
<a-badge count="5">
|
||||||
|
<a-icon type="bell" />
|
||||||
|
</a-badge>
|
||||||
|
</a>
|
||||||
|
<a @click="$emit('setting')" class="header-action">
|
||||||
|
<a-icon type="setting" />
|
||||||
|
</a>
|
||||||
|
<User />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<container v-else-if="$root.global.settings.layout === 'top-nav'">
|
||||||
|
<div class="header-actions">
|
||||||
|
<a @click="showNav = !showNav" class="header-action mr-md">
|
||||||
|
<a-icon type="menu" />
|
||||||
|
</a>
|
||||||
|
<Logo />
|
||||||
|
<search :menus="nav.content" />
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<a @click="$emit('reload')" class="header-action">
|
||||||
|
<a-icon type="reload" />
|
||||||
|
</a>
|
||||||
|
<a class="header-action">
|
||||||
|
<a-badge count="5">
|
||||||
|
<a-icon type="bell" />
|
||||||
|
</a-badge>
|
||||||
|
</a>
|
||||||
|
<a @click="$emit('setting')" class="header-action">
|
||||||
|
<a-icon type="setting" />
|
||||||
|
</a>
|
||||||
|
<User />
|
||||||
|
</div>
|
||||||
|
<a-drawer
|
||||||
|
:body-style="{ padding: 0 }"
|
||||||
|
:closable="false"
|
||||||
|
:get-container="'.ant-layout-content > .yo-tab-external-mount'"
|
||||||
|
:visible="showNav"
|
||||||
|
:wrap-style="{ position: 'absolute' }"
|
||||||
|
@close="showNav = false"
|
||||||
|
placement="left"
|
||||||
|
width="38.2%"
|
||||||
|
>
|
||||||
|
<div @blur="showNav = false" @mouseleave="showNav = false">
|
||||||
|
<Nav :nav="nav" @open="showNav = false" />
|
||||||
|
</div>
|
||||||
|
</a-drawer>
|
||||||
|
</container>
|
||||||
|
</a-layout-header>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Logo from '../logo';
|
||||||
|
import Nav from '../nav';
|
||||||
|
|
||||||
|
import User from './user';
|
||||||
|
import Search from './search';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Logo,
|
||||||
|
Nav,
|
||||||
|
|
||||||
|
User,
|
||||||
|
Search,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nav: {
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
content: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showNav: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
149
Web/src/views/main-dynamic/_layout/header/search.js
Normal file
149
Web/src/views/main-dynamic/_layout/header/search.js
Normal file
@@ -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 (
|
||||||
|
<a-select-opt-group key={menu.parents}>
|
||||||
|
<span slot="label">
|
||||||
|
{menu.parents}
|
||||||
|
</span>
|
||||||
|
{this.renderSelect(menu.children)}
|
||||||
|
</a-select-opt-group>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderSelectOption(menu) {
|
||||||
|
return (<a-select-option key={menu.id} value={
|
||||||
|
JSON.stringify(menu)
|
||||||
|
}>
|
||||||
|
{menu.meta.icon && <a-icon type={menu.meta.icon} />}
|
||||||
|
{menu.meta.title}
|
||||||
|
<small style={{ display: 'block', color: '#aaa' }}>{menu.component}</small>
|
||||||
|
</a-select-option>)
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<a-auto-complete {...{ props, on }}>
|
||||||
|
<template slot="dataSource">
|
||||||
|
{this.renderSelect(this.searchResult)}
|
||||||
|
</template>
|
||||||
|
<a-input allow-clear>
|
||||||
|
<a-icon slot="suffix" type="search" class="certain-category-icon" />
|
||||||
|
</a-input>
|
||||||
|
</a-auto-complete>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Web/src/views/main-dynamic/_layout/header/user.js
Normal file
91
Web/src/views/main-dynamic/_layout/header/user.js
Normal file
@@ -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 (
|
||||||
|
<div onMouseenter={this.onOpen} onMouseleave={this.onClose} class="user-container" >
|
||||||
|
<div class="user-container-inner">
|
||||||
|
<div class="user--base">
|
||||||
|
{
|
||||||
|
this.$root.global.info && <yo-image width="32" type="avatar" class="user--avatar" icon="user" id={this.$root.global.info.avatar} />
|
||||||
|
}
|
||||||
|
{
|
||||||
|
this.$root.global.info &&
|
||||||
|
<span
|
||||||
|
class="user--name"
|
||||||
|
>{this.$root.global.info.nickName || this.$root.global.info.name}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="user--dropdown" ref="dropdown" style={{ height: `${this.dropdownHeight}px` }}>
|
||||||
|
<ul class="ant-dropdown-menu ant-dropdown-menu-vertical">
|
||||||
|
<li class="ant-dropdown-menu-item" onClick={this.onAccountSetting}>
|
||||||
|
<a-icon type="user" />
|
||||||
|
个人中心
|
||||||
|
</li>
|
||||||
|
<li class="ant-dropdown-menu-item-divider"></li>
|
||||||
|
<li class="ant-dropdown-menu-item" onClick={this.onLogout}>
|
||||||
|
<a-icon type="logout" />
|
||||||
|
退出登录
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Web/src/views/main-dynamic/_layout/logo.vue
Normal file
10
Web/src/views/main-dynamic/_layout/logo.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<div class="logo">
|
||||||
|
<img
|
||||||
|
:src="require('@/assets/image/logo.png')"
|
||||||
|
alt
|
||||||
|
v-if="$root.global.settings.navTheme == 'light'"
|
||||||
|
/>
|
||||||
|
<img :src="require('@/assets/image/logo-w.png')" alt v-else />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
116
Web/src/views/main-dynamic/_layout/sider/index.vue
Normal file
116
Web/src/views/main-dynamic/_layout/sider/index.vue
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<a-layout-sider
|
||||||
|
:collapsed="siderCollapsed === undefined ? $root.global.settings.siderCollapsed : siderCollapsed"
|
||||||
|
@collapse="onCollapse"
|
||||||
|
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
|
||||||
|
width="200"
|
||||||
|
>
|
||||||
|
<Logo />
|
||||||
|
<div class="yo-sider-nav">
|
||||||
|
<div class="swiper-container" id="layout--swiper-container">
|
||||||
|
<div class="swiper-wrapper">
|
||||||
|
<div class="swiper-slide">
|
||||||
|
<a-spin :spinning="nav.loading">
|
||||||
|
<a-icon slot="indicator" spin type="loading" />
|
||||||
|
<Menu
|
||||||
|
:menu-style="{ height: '100%', borderRight: 0 }"
|
||||||
|
:nav="nav"
|
||||||
|
@openChange="onMenuOpenChange"
|
||||||
|
mode="inline"
|
||||||
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="swiper-scrollbar" id="layout--swiper-scrollbar"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-layout-sider>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Logo from '../logo';
|
||||||
|
import Menu from './menu';
|
||||||
|
|
||||||
|
import Swiper from 'swiper';
|
||||||
|
|
||||||
|
let timer,
|
||||||
|
swiper,
|
||||||
|
siderSwiperOptions = {
|
||||||
|
direction: 'vertical',
|
||||||
|
slidesPerView: 'auto',
|
||||||
|
freeMode: true,
|
||||||
|
scrollbar: {
|
||||||
|
el: '#layout--swiper-scrollbar',
|
||||||
|
},
|
||||||
|
mousewheel: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Logo,
|
||||||
|
Menu,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
nav: {
|
||||||
|
default() {
|
||||||
|
return {
|
||||||
|
apps: [],
|
||||||
|
menus: [],
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
siderCollapsed: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// swiper不能使用vue版本的组件.效果相当差
|
||||||
|
swiper = new Swiper('#layout--swiper-container', siderSwiperOptions);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.onUpdateSwiper();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (this.$root.global.settings.layout === 'left-menu' || this.$root.global.settings.layout === 'right-menu') {
|
||||||
|
if (!this.$root.global.settings.siderCollapsed) {
|
||||||
|
if (window.innerWidth < 1000) {
|
||||||
|
this.siderCollapsed = true;
|
||||||
|
} else {
|
||||||
|
this.siderCollapsed = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onUpdateSwiper();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onUpdateSwiper() {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
// 需要更新两次
|
||||||
|
swiper.update();
|
||||||
|
swiper.update();
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
|
||||||
|
onMenuOpenChange() {
|
||||||
|
this.onUpdateSwiper();
|
||||||
|
},
|
||||||
|
|
||||||
|
onCollapse() {
|
||||||
|
this.onUpdateSwiper();
|
||||||
|
},
|
||||||
|
|
||||||
|
windowTriggerResize() {
|
||||||
|
let e = new Event('resize');
|
||||||
|
window.dispatchEvent(e);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
92
Web/src/views/main-dynamic/_layout/sider/menu.js
Normal file
92
Web/src/views/main-dynamic/_layout/sider/menu.js
Normal file
@@ -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 (
|
||||||
|
<a-sub-menu key={menu.id}>
|
||||||
|
<span slot="title">
|
||||||
|
{menu.meta.icon && <a-icon type={menu.meta.icon} />}
|
||||||
|
<span>{menu.meta.title}</span>
|
||||||
|
</span>
|
||||||
|
{this.renderMenu(menu.children)}
|
||||||
|
</a-sub-menu>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMenuItem(menu) {
|
||||||
|
return (
|
||||||
|
<a-menu-item key={menu.id} onClick={() => this.onOpenContentWindow(menu)}>
|
||||||
|
{menu.meta.icon && <a-icon type={menu.meta.icon} />}
|
||||||
|
<span>{menu.meta.title}</span>
|
||||||
|
</a-menu-item>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
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 (<section>
|
||||||
|
{
|
||||||
|
this.nav.content.map(item => {
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div class="yo-sider-nav--app">{item.app.name}</div>
|
||||||
|
<a-menu {...{ props, on }}>{this.renderMenu(item.menu)}</a-menu>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</section>)
|
||||||
|
},
|
||||||
|
}
|
||||||
239
Web/src/views/main-dynamic/index.vue
Normal file
239
Web/src/views/main-dynamic/index.vue
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<a-spin :spinning="nav.loading" class="yo-layout--spin">
|
||||||
|
<a-icon :style="{ fontSize: '24px' }" slot="indicator" spin type="loading" />
|
||||||
|
<a-layout
|
||||||
|
:class="{
|
||||||
|
[`yo-layout--top-nav--${$root.global.settings.container}`]: $root.global.settings.layout === 'top-nav',
|
||||||
|
[`yo-layout--${$root.global.settings.layout}`]: true,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Sider :nav="nav" v-if="$root.global.settings.layout === 'left-menu'" />
|
||||||
|
<a-layout>
|
||||||
|
<Header :nav="nav" @reload="onReloadContentWindow" @setting="setting.visible = true" />
|
||||||
|
<a-layout>
|
||||||
|
<Content
|
||||||
|
:panes="panes"
|
||||||
|
:tabActived="tabActived"
|
||||||
|
@change="onChangeContentWindow"
|
||||||
|
@close="onCloseContentWindow"
|
||||||
|
@close-other="onCloseOtherContentWindow"
|
||||||
|
@close-right="onCloseRightContentWindow"
|
||||||
|
ref="content"
|
||||||
|
/>
|
||||||
|
</a-layout>
|
||||||
|
</a-layout>
|
||||||
|
<Sider :nav="nav" v-if="$root.global.settings.layout === 'right-menu'" />
|
||||||
|
</a-layout>
|
||||||
|
<Setting :visible="setting.visible" @close="setting.visible = false" />
|
||||||
|
</a-spin>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import Header from './_layout/header';
|
||||||
|
import Sider from './_layout/sider/index';
|
||||||
|
import Content from './_layout/content';
|
||||||
|
|
||||||
|
import Setting from './setting';
|
||||||
|
|
||||||
|
import { EMPTY_ID, setGlobal } from '@/util/global';
|
||||||
|
|
||||||
|
const getNewID = () => {
|
||||||
|
return Math.random().toString(16).slice(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Header,
|
||||||
|
Sider,
|
||||||
|
Content,
|
||||||
|
Setting,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
panes: new Array(),
|
||||||
|
tabActived: '',
|
||||||
|
|
||||||
|
setting: {
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
nav: {
|
||||||
|
loading: false,
|
||||||
|
content: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
Vue.prototype.openContentWindow = this.onOpenContentWindow;
|
||||||
|
Vue.prototype.closeContentWindow = this.onCloseContentWindow;
|
||||||
|
|
||||||
|
this.nav.loading = true;
|
||||||
|
|
||||||
|
this.$api.getLoginUser().then(async ({ data }) => {
|
||||||
|
// 去除应用和菜单信息,存储基本信息
|
||||||
|
const info = this.$_.cloneDeep(data);
|
||||||
|
delete info.apps;
|
||||||
|
delete info.menus;
|
||||||
|
setGlobal(info);
|
||||||
|
|
||||||
|
this.nav.content = await this.onSetNav(data);
|
||||||
|
this.nav.loading = false;
|
||||||
|
|
||||||
|
this.$root.global.defaultWindow.map((options) => {
|
||||||
|
this.onOpenContentWindow(options);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开一个新的标签页
|
||||||
|
* settings: {
|
||||||
|
* title: String,
|
||||||
|
* path: String,
|
||||||
|
* icon: String,
|
||||||
|
* param: Object,
|
||||||
|
* closable: Boolean = true,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
onOpenContentWindow(settings) {
|
||||||
|
if (settings.path) {
|
||||||
|
const key = settings.key || getNewID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果当前标签页已打开,则只需要选中
|
||||||
|
*/
|
||||||
|
const pane = this.panes.find((p) => p.key === key);
|
||||||
|
if (pane) {
|
||||||
|
this.onChangeContentWindow(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = settings.path.startsWith('/') ? settings.path : `/${settings.path}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向标签页队列中添加一个新的标签页
|
||||||
|
*/
|
||||||
|
this.panes.push({
|
||||||
|
key,
|
||||||
|
closable: settings.closable === undefined ? true : settings.closable,
|
||||||
|
icon: settings.icon,
|
||||||
|
title: settings.title || '新建窗口',
|
||||||
|
subTitle: settings.subTitle,
|
||||||
|
component: null,
|
||||||
|
path,
|
||||||
|
param: settings.param,
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onChangeContentWindow(key);
|
||||||
|
|
||||||
|
this.$refs.content.onLoadContentWindow(key);
|
||||||
|
} else {
|
||||||
|
console.warn('wrong component path');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseContentWindow(key) {
|
||||||
|
key = key || this.tabActived;
|
||||||
|
const i = this.$_.findIndex(this.panes, (p) => p.key === key);
|
||||||
|
this.panes.splice(i, 1);
|
||||||
|
|
||||||
|
if (this.panes.length) {
|
||||||
|
if (key === this.tabActived) {
|
||||||
|
const pane = this.panes[i];
|
||||||
|
if (pane) {
|
||||||
|
this.onChangeContentWindow(pane.key);
|
||||||
|
} else {
|
||||||
|
this.onChangeContentWindow(this.panes[i - 1].key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseOtherContentWindow(key) {
|
||||||
|
const panes = Object.assign([], this.panes);
|
||||||
|
let flag = false;
|
||||||
|
for (let i = panes.length - 1; i >= 0; i--) {
|
||||||
|
const p = panes[i];
|
||||||
|
if (p.key !== key && p.closable) {
|
||||||
|
if (p.key === this.tabActived) {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
panes.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.panes = panes;
|
||||||
|
if (flag) {
|
||||||
|
this.onChangeContentWindow(this.$_.last(panes).key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseRightContentWindow(key) {
|
||||||
|
const panes = Object.assign([], this.panes);
|
||||||
|
let flag = false;
|
||||||
|
for (let i = panes.length - 1; i >= 0; i--) {
|
||||||
|
const p = panes[i];
|
||||||
|
if (p.key !== key && p.closable) {
|
||||||
|
if (p.key === this.tabActived) {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
panes.splice(i, 1);
|
||||||
|
} else if (p.key === key) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.panes = panes;
|
||||||
|
if (flag) {
|
||||||
|
this.onChangeContentWindow(this.$_.last(panes).key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onChangeContentWindow(key) {
|
||||||
|
this.tabActived = key;
|
||||||
|
},
|
||||||
|
|
||||||
|
onReloadContentWindow(key) {
|
||||||
|
if (!key) key = this.tabActived;
|
||||||
|
this.$refs.content.onLoadContentWindow(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSetNav(nav) {
|
||||||
|
const getNav = [];
|
||||||
|
nav.apps.forEach((app) => {
|
||||||
|
getNav.push({
|
||||||
|
app,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.$api
|
||||||
|
.$queue(getNav.map((p) => this.$api.sysMenuChangeAwait({ application: p.app.code })))
|
||||||
|
.then((menus) => {
|
||||||
|
menus.forEach((menu, i) => {
|
||||||
|
getNav[i].menu = this.serializeMenu(menu.data);
|
||||||
|
});
|
||||||
|
return getNav;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
serializeMenu(menus) {
|
||||||
|
const menu = this.$_.cloneDeep(menus);
|
||||||
|
const children = this.$_.groupBy(menu, 'pid');
|
||||||
|
|
||||||
|
const serialize = (m) => {
|
||||||
|
m.map((p) => {
|
||||||
|
if (children[p.id]) {
|
||||||
|
p.children = serialize(children[p.id]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
return children[EMPTY_ID] ? serialize(children[EMPTY_ID]) : new Array();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,35 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-layout-header class="header">
|
<a-layout-header class="header">
|
||||||
<section
|
<container>
|
||||||
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
|
|
||||||
>
|
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<a
|
<a
|
||||||
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
|
@click="$root.global.settings.siderCollapsed = !$root.global.settings.siderCollapsed"
|
||||||
class="header-action"
|
class="header-action mr-md"
|
||||||
>
|
>
|
||||||
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
|
|
||||||
</a>
|
|
||||||
<search :menus="nav.content" />
|
|
||||||
</div>
|
|
||||||
<div class="header-actions">
|
|
||||||
<a @click="$emit('reload')" class="header-action">
|
|
||||||
<a-icon type="reload" />
|
|
||||||
</a>
|
|
||||||
<a class="header-action">
|
|
||||||
<a-badge count="5">
|
|
||||||
<a-icon type="bell" />
|
|
||||||
</a-badge>
|
|
||||||
</a>
|
|
||||||
<a @click="$emit('setting')" class="header-action">
|
|
||||||
<a-icon type="setting" />
|
|
||||||
</a>
|
|
||||||
<User />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<container v-else-if="$root.global.settings.layout === 'top-nav'">
|
|
||||||
<div class="header-actions">
|
|
||||||
<a @click="showNav = !showNav" class="header-action mr-md">
|
|
||||||
<a-icon type="menu" />
|
<a-icon type="menu" />
|
||||||
</a>
|
</a>
|
||||||
<Logo />
|
<Logo />
|
||||||
@@ -44,31 +20,13 @@
|
|||||||
<a-icon type="bell" />
|
<a-icon type="bell" />
|
||||||
</a-badge>
|
</a-badge>
|
||||||
</a>
|
</a>
|
||||||
<a @click="$emit('setting')" class="header-action">
|
|
||||||
<a-icon type="setting" />
|
|
||||||
</a>
|
|
||||||
<User />
|
<User />
|
||||||
</div>
|
</div>
|
||||||
<a-drawer
|
|
||||||
:body-style="{ padding: 0 }"
|
|
||||||
:closable="false"
|
|
||||||
:get-container="'.ant-layout-content > .yo-tab-external-mount'"
|
|
||||||
:visible="showNav"
|
|
||||||
:wrap-style="{ position: 'absolute' }"
|
|
||||||
@close="showNav = false"
|
|
||||||
placement="left"
|
|
||||||
width="38.2%"
|
|
||||||
>
|
|
||||||
<div @blur="showNav = false" @mouseleave="showNav = false">
|
|
||||||
<Nav :nav="nav" @open="showNav = false" />
|
|
||||||
</div>
|
|
||||||
</a-drawer>
|
|
||||||
</container>
|
</container>
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Logo from '../logo';
|
import Logo from '../logo';
|
||||||
import Nav from '../nav';
|
|
||||||
|
|
||||||
import User from './user';
|
import User from './user';
|
||||||
import Search from './search';
|
import Search from './search';
|
||||||
@@ -76,7 +34,6 @@ import Search from './search';
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Logo,
|
Logo,
|
||||||
Nav,
|
|
||||||
|
|
||||||
User,
|
User,
|
||||||
Search,
|
Search,
|
||||||
@@ -91,10 +48,5 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showNav: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section>
|
|
||||||
<a-layout-sider
|
<a-layout-sider
|
||||||
:collapsed="siderCollapsed === undefined ? $root.global.settings.siderCollapsed : siderCollapsed"
|
:collapsed="siderCollapsed === undefined ? $root.global.settings.siderCollapsed : siderCollapsed"
|
||||||
@collapse="onCollapse"
|
@collapse="onCollapse"
|
||||||
v-if="$root.global.settings.layout === 'left-menu' || $root.global.settings.layout === 'right-menu'"
|
class="yo-layout-sider"
|
||||||
width="200"
|
width="200"
|
||||||
>
|
>
|
||||||
<Logo />
|
|
||||||
<div class="yo-sider-nav">
|
<div class="yo-sider-nav">
|
||||||
<div class="swiper-container" id="layout--swiper-container">
|
<div class="swiper-container" id="layout--swiper-container">
|
||||||
<div class="swiper-wrapper">
|
<div class="swiper-wrapper">
|
||||||
@@ -26,7 +24,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a-layout-sider>
|
</a-layout-sider>
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import Logo from '../logo';
|
import Logo from '../logo';
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export default {
|
|||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
style: this.menuStyle,
|
style: this.menuStyle,
|
||||||
theme: this.$root.global.settings.navTheme,
|
theme: 'light',
|
||||||
}
|
}
|
||||||
|
|
||||||
const on = {
|
const on = {
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
[`yo-layout--${$root.global.settings.layout}`]: true,
|
[`yo-layout--${$root.global.settings.layout}`]: true,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Sider :nav="nav" v-if="$root.global.settings.layout === 'left-menu'" />
|
|
||||||
<a-layout>
|
<a-layout>
|
||||||
<Header :nav="nav" @reload="onReloadContentWindow" @setting="setting.visible = true" />
|
<Header :nav="nav" @reload="onReloadContentWindow" @setting="setting.visible = true" />
|
||||||
<a-layout>
|
<a-layout class="yo-nav-theme--light">
|
||||||
|
<Sider :nav="nav" />
|
||||||
<Content
|
<Content
|
||||||
:panes="panes"
|
:panes="panes"
|
||||||
:tabActived="tabActived"
|
:tabActived="tabActived"
|
||||||
@@ -23,9 +23,7 @@
|
|||||||
/>
|
/>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
<Sider :nav="nav" v-if="$root.global.settings.layout === 'right-menu'" />
|
|
||||||
</a-layout>
|
</a-layout>
|
||||||
<Setting :visible="setting.visible" @close="setting.visible = false" />
|
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -36,8 +34,6 @@ import Header from './_layout/header';
|
|||||||
import Sider from './_layout/sider/index';
|
import Sider from './_layout/sider/index';
|
||||||
import Content from './_layout/content';
|
import Content from './_layout/content';
|
||||||
|
|
||||||
import Setting from './setting';
|
|
||||||
|
|
||||||
import { EMPTY_ID, setGlobal } from '@/util/global';
|
import { EMPTY_ID, setGlobal } from '@/util/global';
|
||||||
|
|
||||||
const getNewID = () => {
|
const getNewID = () => {
|
||||||
@@ -49,16 +45,12 @@ export default {
|
|||||||
Header,
|
Header,
|
||||||
Sider,
|
Sider,
|
||||||
Content,
|
Content,
|
||||||
Setting,
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
panes: new Array(),
|
panes: new Array(),
|
||||||
tabActived: '',
|
tabActived: '',
|
||||||
|
|
||||||
setting: {
|
|
||||||
visible: false,
|
|
||||||
},
|
|
||||||
nav: {
|
nav: {
|
||||||
loading: false,
|
loading: false,
|
||||||
content: [],
|
content: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user