This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
.dark {
|
||||
.main(@nav-background: @layout-header-background;
|
||||
@nav-box-shadow-color: fade(@black, 25%);
|
||||
@nav-scrollbar-background: fade(@white, 50%);
|
||||
@logo-color: @white;
|
||||
@logo-box-shadow: none;
|
||||
@header-action-color: fade(@white, 60%);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
.light {
|
||||
.main(@nav-background: @white;
|
||||
@nav-box-shadow-color: fade(@black, 5%);
|
||||
@nav-scrollbar-background: fade(@black, 30%);
|
||||
@logo-color: @black;
|
||||
@logo-box-shadow: inset -1px -1px 1px @border-color-split;
|
||||
@header-action-color: fade(@black, 35%);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
.main(@nav-background: @layout-header-background,
|
||||
@nav-box-shadow-color: fade(@black, 25%),
|
||||
@nav-scrollbar-background: fade(@white, 30%),
|
||||
@logo-color: @white,
|
||||
@logo-box-shadow: none,
|
||||
@header-action-color: fade(@white, 60%),
|
||||
@@ -35,12 +36,14 @@
|
||||
box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05);
|
||||
}
|
||||
}
|
||||
>.ant-input-search {
|
||||
>.ant-input-search,
|
||||
>.ant-select-auto-complete {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
margin: 5px @padding-md;
|
||||
.ant-input {
|
||||
width: 180px;
|
||||
height: 34px;
|
||||
padding: 5px 30px 5px 11px;
|
||||
|
||||
@@ -52,6 +55,9 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.ant-select-selection {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +200,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
>.ant-input-search {
|
||||
>.ant-input-search,
|
||||
>.ant-select-auto-complete {
|
||||
.ant-input {
|
||||
&:focus {
|
||||
background-color: fade(@black, 5%);
|
||||
@@ -256,9 +263,27 @@
|
||||
height: 100%;
|
||||
|
||||
box-shadow: 2px 0 8px @nav-box-shadow-color;
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,7 +323,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
>.ant-input-search {
|
||||
>.ant-input-search,
|
||||
>.ant-select-auto-complete {
|
||||
.ant-input {
|
||||
color: @header-search-color;
|
||||
background-color: @header-search-background;
|
||||
@@ -306,7 +332,7 @@
|
||||
background-color: @header-search-focus-background;
|
||||
}
|
||||
}
|
||||
.ant-input-search-icon {
|
||||
.anticon-search {
|
||||
color: @header-search-icon-color;
|
||||
&:hover {
|
||||
color: @header-search-icon-hover-color;
|
||||
|
||||
@@ -81,6 +81,7 @@ export default {
|
||||
data.forEach((p, i) => {
|
||||
n[n.length - 1] = i
|
||||
p.key = n.join('-')
|
||||
p.scopedSlots = { title: 'title' }
|
||||
if (p.children) {
|
||||
this.generateKey(p.children, Object.assign([], n))
|
||||
}
|
||||
@@ -125,14 +126,20 @@ export default {
|
||||
mousewheel: true,
|
||||
}
|
||||
|
||||
const slots = {
|
||||
const scopedSlots = {
|
||||
title: ({ title }) => {
|
||||
return (
|
||||
<div>
|
||||
<span v-if={title.indexOf(this.searchValue) > -1}>
|
||||
{title.replace(this.searchValue, <span style="color: #f50">{this.searchValue}</span>)}
|
||||
</span>
|
||||
<span v-else>{{ title }}</span>
|
||||
{
|
||||
title.indexOf(this.searchValue) > -1 ?
|
||||
<span>
|
||||
{title.substr(0, title.indexOf(this.searchValue))}
|
||||
<span style="color: #f50">{this.searchValue}</span>
|
||||
{title.substr(title.indexOf(this.searchValue) + this.searchValue.length)}
|
||||
</span>
|
||||
:
|
||||
<span>{title}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
);
|
||||
@@ -150,20 +157,14 @@ export default {
|
||||
<swiper options={swiperOptions}>
|
||||
<a-spin style={{ height: '100%' }} spinning={this.loading}>
|
||||
<a-icon slot="indicator" type="loading" spin />
|
||||
|
||||
<swiper-slide>
|
||||
<a-alert closable banner type="error">
|
||||
<template slot="message">
|
||||
<div>搜索无法高亮</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
<a-tree
|
||||
{...{ slots }}
|
||||
treeData={this.data}
|
||||
expandedKeys={this.expandedKeys}
|
||||
autoExpandParent={this.autoExpandParent}
|
||||
onExpand={this.onExpand}
|
||||
onSelect={this.onSelect}
|
||||
{...{ scopedSlots }}
|
||||
/>
|
||||
</swiper-slide>
|
||||
</a-spin>
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
</a-form-model-item>
|
||||
<template v-if="mode == 'add'">
|
||||
<a-form-model-item label="密码" prop="password">
|
||||
<a-input placeholder="请输入密码" type="password" v-model="form.password" />
|
||||
<a-input-password placeholder="请输入密码" v-model="form.password" />
|
||||
</a-form-model-item>
|
||||
<a-form-model-item label="确认密码" prop="confirm">
|
||||
<a-input placeholder="请确认密码" type="password" v-model="form.confirm" />
|
||||
<a-input-password placeholder="请确认密码" v-model="form.confirm" />
|
||||
</a-form-model-item>
|
||||
</template>
|
||||
<a-form-model-item label="昵称">
|
||||
|
||||
@@ -138,6 +138,7 @@ export default {
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
width: '100px',
|
||||
dataIndex: 'sex',
|
||||
scopedSlots: {
|
||||
customRender: 'sex',
|
||||
@@ -149,6 +150,7 @@ export default {
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: '200px',
|
||||
dataIndex: 'status',
|
||||
scopedSlots: {
|
||||
customRender: 'status',
|
||||
|
||||
@@ -10,7 +10,29 @@
|
||||
>
|
||||
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
|
||||
</a>
|
||||
<a-input-search placeholder="请输入检索关键字" />
|
||||
<!-- <a-input-search placeholder="请输入检索关键字" /> -->
|
||||
<a-auto-complete
|
||||
:dropdown-match-select-width="false"
|
||||
@search="onSearch"
|
||||
@select="onSearchSelect"
|
||||
option-label-prop="value"
|
||||
placeholder="请输入检索关键字"
|
||||
v-model="searchText"
|
||||
>
|
||||
<template slot="dataSource">
|
||||
<a-select-opt-group :key="menu.id" v-for="menu in searchResult">
|
||||
<span slot="label">{{ menu.meta.title }}</span>
|
||||
<a-select-option
|
||||
:key="opt.id"
|
||||
:value="opt.id"
|
||||
v-for="opt in menu.children"
|
||||
>{{ opt.meta.title }}</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</template>
|
||||
<a-input>
|
||||
<a-icon class="certain-category-icon" slot="suffix" type="search" />
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a @click="$emit('reload')" class="header-action">
|
||||
@@ -32,7 +54,29 @@
|
||||
<Sider :nav="nav" @open="(nav) => $emit('open', nav)" />
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<a-input-search placeholder="请输入检索关键字" />
|
||||
<!-- <a-input-search placeholder="请输入检索关键字" /> -->
|
||||
<a-auto-complete
|
||||
:dropdown-match-select-width="false"
|
||||
@search="onSearch"
|
||||
@select="onSearchSelect"
|
||||
option-label-prop="value"
|
||||
placeholder="请输入检索关键字"
|
||||
v-model="searchText"
|
||||
>
|
||||
<template slot="dataSource">
|
||||
<a-select-opt-group :key="menu.id" v-for="menu in searchResult">
|
||||
<span slot="label">{{ menu.meta.title }}</span>
|
||||
<a-select-option
|
||||
:key="opt.id"
|
||||
:value="opt.component"
|
||||
v-for="opt in menu.children"
|
||||
>{{ opt.meta.title }}</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</template>
|
||||
<a-input>
|
||||
<a-icon class="certain-category-icon" slot="suffix" type="search" />
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
<a @click="$emit('reload')" class="header-action">
|
||||
<a-icon type="reload" />
|
||||
</a>
|
||||
@@ -68,5 +112,48 @@ export default {
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchText: '',
|
||||
searchResult: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
window._ = this.$_;
|
||||
},
|
||||
methods: {
|
||||
onSearch(value) {
|
||||
const menus = this.$_.cloneDeep(this.nav.menus);
|
||||
|
||||
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;
|
||||
}
|
||||
return p.children.length;
|
||||
});
|
||||
};
|
||||
|
||||
this.searchResult = search(menus);
|
||||
},
|
||||
|
||||
onSearchSelect(value, node) {
|
||||
this.searchText = '';
|
||||
this.onSearch(this.searchText);
|
||||
|
||||
const id = node.componentOptions.propsData.value;
|
||||
//const menu = this.nav.menus.
|
||||
|
||||
// this.openContentWindow({
|
||||
// key: menu.id,
|
||||
// title: menu.meta.title,
|
||||
// icon: menu.meta.icon,
|
||||
// path: menu.component,
|
||||
// });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -6,16 +6,19 @@
|
||||
width="200"
|
||||
>
|
||||
<Logo />
|
||||
<swiper :options="siderSwiperOptions" :style="{ height: '100%' }" ref="sider-swiper">
|
||||
<swiper-slide :style="{ height: 'auto' }">
|
||||
<Menu
|
||||
:menu-style="{ height: '100%', borderRight: 0 }"
|
||||
:nav="nav"
|
||||
@openChange="onMenuOpenChange"
|
||||
mode="inline"
|
||||
/>
|
||||
<swiper :options="siderSwiperOptions" ref="sider-swiper">
|
||||
<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>
|
||||
</swiper-slide>
|
||||
<div class="swiper-scrollbar"></div>
|
||||
<div class="swiper-scrollbar" id="layout--swiper-scrollbar" slot="scrollbar"></div>
|
||||
</swiper>
|
||||
</a-layout-sider>
|
||||
<template v-else-if="$root.global.settings.layout === 'top-nav'">
|
||||
@@ -43,6 +46,7 @@ export default {
|
||||
return {
|
||||
modules: [],
|
||||
menus: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
type: Object,
|
||||
@@ -55,7 +59,7 @@ export default {
|
||||
slidesPerView: 'auto',
|
||||
freeMode: true,
|
||||
scrollbar: {
|
||||
el: '.swiper-scrollbar',
|
||||
el: '#layout--swiper-scrollbar',
|
||||
},
|
||||
mousewheel: true,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
export default {
|
||||
props: {
|
||||
nav: {
|
||||
default() {
|
||||
return {
|
||||
modules: [],
|
||||
menus: [],
|
||||
}
|
||||
},
|
||||
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} /> : null}
|
||||
<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} /> : null}
|
||||
<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 <a-menu {...{ props, on }}>{this.renderMenu(this.nav.menus)}</a-menu>
|
||||
},
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
<template>
|
||||
<a-menu
|
||||
:mode="mode"
|
||||
:selectable="false"
|
||||
:style="menuStyle"
|
||||
:theme="$root.global.settings.navTheme"
|
||||
@openChange="onMenuOpenChange"
|
||||
>
|
||||
<template v-for="c1 in menu">
|
||||
<a-sub-menu :key="c1.id" v-if="c1.children && c1.children.length">
|
||||
<span slot="title">
|
||||
<a-icon :type="c1.meta.icon" v-if="c1.meta.icon" />
|
||||
<span>{{ c1.meta.title }}</span>
|
||||
</span>
|
||||
<template v-for="c2 in c1.children">
|
||||
<a-menu-item :key="c2.id" @click="onOpenContentWindow(c2)">
|
||||
<a-icon :type="c2.meta.icon" v-if="c2.meta.icon" />
|
||||
<span>{{ c2.meta.title }}</span>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</a-sub-menu>
|
||||
<a-menu-item :key="c1.id" @click="onOpenContentWindow(c1)" v-else>
|
||||
<a-icon :type="c1.meta.icon" v-if="c1.meta.icon" />
|
||||
<span>{{ c1.meta.title }}</span>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</a-menu>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
nav: {
|
||||
default() {
|
||||
return {
|
||||
modules: [],
|
||||
menus: [],
|
||||
};
|
||||
},
|
||||
type: Object,
|
||||
},
|
||||
menuStyle: {
|
||||
default() {
|
||||
return {};
|
||||
},
|
||||
type: Object || String,
|
||||
},
|
||||
mode: {
|
||||
default: 'inline',
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
menu: [],
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.serializeMenu();
|
||||
},
|
||||
|
||||
watch: {
|
||||
nav: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.serializeMenu();
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
serializeMenu() {
|
||||
const menu = this.$_.cloneDeep(this.nav.menus);
|
||||
const children = this.$_.groupBy(menu, 'pid');
|
||||
this.menu = menu
|
||||
.filter((p) => !p.pid)
|
||||
.map((p) => {
|
||||
return {
|
||||
...p,
|
||||
children: children[p.id],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onMenuOpenChange() {
|
||||
this.$emit('openChange');
|
||||
},
|
||||
|
||||
onOpenContentWindow(menu) {
|
||||
this.openContentWindow({
|
||||
key: menu.id,
|
||||
title: menu.meta.title,
|
||||
icon: menu.meta.icon,
|
||||
path: menu.component,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -79,8 +79,10 @@ export default {
|
||||
visible: false,
|
||||
},
|
||||
|
||||
menus: [],
|
||||
|
||||
nav: {
|
||||
loaded: false,
|
||||
loading: false,
|
||||
modules: [],
|
||||
menus: [],
|
||||
},
|
||||
@@ -88,6 +90,8 @@ export default {
|
||||
},
|
||||
|
||||
created() {
|
||||
this.nav.loading = true;
|
||||
|
||||
this.$api.getLoginUser().then(({ data }) => {
|
||||
const info = { ...data };
|
||||
delete info.apps;
|
||||
@@ -95,8 +99,9 @@ export default {
|
||||
setGlobal(info);
|
||||
|
||||
this.nav.modules = data.apps;
|
||||
this.nav.menus = data.menus;
|
||||
this.nav.loaded = true;
|
||||
this.menus = data.menus;
|
||||
this.serializeMenu();
|
||||
this.nav.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -226,6 +231,22 @@ export default {
|
||||
if (!key) key = this.tabActived;
|
||||
this.$refs.content.onLoadContentWindow(key);
|
||||
},
|
||||
|
||||
serializeMenu() {
|
||||
const menu = this.$_.cloneDeep(this.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;
|
||||
};
|
||||
|
||||
this.nav.menus = serialize(children[0]);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user