This commit is contained in:
ky_sunl
2021-04-16 08:51:15 +00:00
parent 0b2316e9e5
commit 18f1d28d27
11 changed files with 256 additions and 133 deletions

View File

@@ -2,6 +2,7 @@
.dark { .dark {
.main(@nav-background: @layout-header-background; .main(@nav-background: @layout-header-background;
@nav-box-shadow-color: fade(@black, 25%); @nav-box-shadow-color: fade(@black, 25%);
@nav-scrollbar-background: fade(@white, 50%);
@logo-color: @white; @logo-color: @white;
@logo-box-shadow: none; @logo-box-shadow: none;
@header-action-color: fade(@white, 60%); @header-action-color: fade(@white, 60%);

View File

@@ -2,6 +2,7 @@
.light { .light {
.main(@nav-background: @white; .main(@nav-background: @white;
@nav-box-shadow-color: fade(@black, 5%); @nav-box-shadow-color: fade(@black, 5%);
@nav-scrollbar-background: fade(@black, 30%);
@logo-color: @black; @logo-color: @black;
@logo-box-shadow: inset -1px -1px 1px @border-color-split; @logo-box-shadow: inset -1px -1px 1px @border-color-split;
@header-action-color: fade(@black, 35%); @header-action-color: fade(@black, 35%);

View File

@@ -3,6 +3,7 @@
.main(@nav-background: @layout-header-background, .main(@nav-background: @layout-header-background,
@nav-box-shadow-color: fade(@black, 25%), @nav-box-shadow-color: fade(@black, 25%),
@nav-scrollbar-background: fade(@white, 30%),
@logo-color: @white, @logo-color: @white,
@logo-box-shadow: none, @logo-box-shadow: none,
@header-action-color: fade(@white, 60%), @header-action-color: fade(@white, 60%),
@@ -35,12 +36,14 @@
box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05); box-shadow: inset 1px 1px 10px rgba(0, 0, 0, .05);
} }
} }
>.ant-input-search { >.ant-input-search,
>.ant-select-auto-complete {
display: flex; display: flex;
align-items: center; align-items: center;
margin: 5px @padding-md; margin: 5px @padding-md;
.ant-input { .ant-input {
width: 180px;
height: 34px; height: 34px;
padding: 5px 30px 5px 11px; padding: 5px 30px 5px 11px;
@@ -52,6 +55,9 @@
box-shadow: none; 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 { .ant-input {
&:focus { &:focus {
background-color: fade(@black, 5%); background-color: fade(@black, 5%);
@@ -256,9 +263,27 @@
height: 100%; height: 100%;
box-shadow: 2px 0 8px @nav-box-shadow-color; 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 { .swiper-slide {
height: auto; 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 { .ant-input {
color: @header-search-color; color: @header-search-color;
background-color: @header-search-background; background-color: @header-search-background;
@@ -306,7 +332,7 @@
background-color: @header-search-focus-background; background-color: @header-search-focus-background;
} }
} }
.ant-input-search-icon { .anticon-search {
color: @header-search-icon-color; color: @header-search-icon-color;
&:hover { &:hover {
color: @header-search-icon-hover-color; color: @header-search-icon-hover-color;

View File

@@ -81,6 +81,7 @@ export default {
data.forEach((p, i) => { data.forEach((p, i) => {
n[n.length - 1] = i n[n.length - 1] = i
p.key = n.join('-') p.key = n.join('-')
p.scopedSlots = { title: 'title' }
if (p.children) { if (p.children) {
this.generateKey(p.children, Object.assign([], n)) this.generateKey(p.children, Object.assign([], n))
} }
@@ -125,14 +126,20 @@ export default {
mousewheel: true, mousewheel: true,
} }
const slots = { const scopedSlots = {
title: ({ title }) => { title: ({ title }) => {
return ( return (
<div> <div>
<span v-if={title.indexOf(this.searchValue) > -1}> {
{title.replace(this.searchValue, <span style="color: #f50">{this.searchValue}</span>)} title.indexOf(this.searchValue) > -1 ?
</span> <span>
<span v-else>{{ title }}</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> </div>
); );
@@ -150,20 +157,14 @@ export default {
<swiper options={swiperOptions}> <swiper options={swiperOptions}>
<a-spin style={{ height: '100%' }} spinning={this.loading}> <a-spin style={{ height: '100%' }} spinning={this.loading}>
<a-icon slot="indicator" type="loading" spin /> <a-icon slot="indicator" type="loading" spin />
<swiper-slide> <swiper-slide>
<a-alert closable banner type="error">
<template slot="message">
<div>搜索无法高亮</div>
</template>
</a-alert>
<a-tree <a-tree
{...{ slots }}
treeData={this.data} treeData={this.data}
expandedKeys={this.expandedKeys} expandedKeys={this.expandedKeys}
autoExpandParent={this.autoExpandParent} autoExpandParent={this.autoExpandParent}
onExpand={this.onExpand} onExpand={this.onExpand}
onSelect={this.onSelect} onSelect={this.onSelect}
{...{ scopedSlots }}
/> />
</swiper-slide> </swiper-slide>
</a-spin> </a-spin>

View File

@@ -14,10 +14,10 @@
</a-form-model-item> </a-form-model-item>
<template v-if="mode == 'add'"> <template v-if="mode == 'add'">
<a-form-model-item label="密码" prop="password"> <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>
<a-form-model-item label="确认密码" prop="confirm"> <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> </a-form-model-item>
</template> </template>
<a-form-model-item label="昵称"> <a-form-model-item label="昵称">

View File

@@ -138,6 +138,7 @@ export default {
}, },
{ {
title: '性别', title: '性别',
width: '100px',
dataIndex: 'sex', dataIndex: 'sex',
scopedSlots: { scopedSlots: {
customRender: 'sex', customRender: 'sex',
@@ -149,6 +150,7 @@ export default {
}, },
{ {
title: '状态', title: '状态',
width: '200px',
dataIndex: 'status', dataIndex: 'status',
scopedSlots: { scopedSlots: {
customRender: 'status', customRender: 'status',

View File

@@ -10,7 +10,29 @@
> >
<a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" /> <a-icon :type="$root.global.settings.siderCollapsed ? 'menu-unfold' : 'menu-fold'" />
</a> </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>
<div class="header-actions"> <div class="header-actions">
<a @click="$emit('reload')" class="header-action"> <a @click="$emit('reload')" class="header-action">
@@ -32,7 +54,29 @@
<Sider :nav="nav" @open="(nav) => $emit('open', nav)" /> <Sider :nav="nav" @open="(nav) => $emit('open', nav)" />
</div> </div>
<div class="header-actions"> <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 @click="$emit('reload')" class="header-action">
<a-icon type="reload" /> <a-icon type="reload" />
</a> </a>
@@ -68,5 +112,48 @@ export default {
type: Object, 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> </script>

View File

@@ -6,16 +6,19 @@
width="200" width="200"
> >
<Logo /> <Logo />
<swiper :options="siderSwiperOptions" :style="{ height: '100%' }" ref="sider-swiper"> <swiper :options="siderSwiperOptions" ref="sider-swiper">
<swiper-slide :style="{ height: 'auto' }"> <swiper-slide>
<Menu <a-spin :spinning="nav.loading">
:menu-style="{ height: '100%', borderRight: 0 }" <a-icon slot="indicator" spin type="loading" />
:nav="nav" <Menu
@openChange="onMenuOpenChange" :menu-style="{ height: '100%', borderRight: 0 }"
mode="inline" :nav="nav"
/> @openChange="onMenuOpenChange"
mode="inline"
/>
</a-spin>
</swiper-slide> </swiper-slide>
<div class="swiper-scrollbar"></div> <div class="swiper-scrollbar" id="layout--swiper-scrollbar" slot="scrollbar"></div>
</swiper> </swiper>
</a-layout-sider> </a-layout-sider>
<template v-else-if="$root.global.settings.layout === 'top-nav'"> <template v-else-if="$root.global.settings.layout === 'top-nav'">
@@ -43,6 +46,7 @@ export default {
return { return {
modules: [], modules: [],
menus: [], menus: [],
loading: false,
}; };
}, },
type: Object, type: Object,
@@ -55,7 +59,7 @@ export default {
slidesPerView: 'auto', slidesPerView: 'auto',
freeMode: true, freeMode: true,
scrollbar: { scrollbar: {
el: '.swiper-scrollbar', el: '#layout--swiper-scrollbar',
}, },
mousewheel: true, mousewheel: true,
}, },

View File

@@ -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>
},
}

View File

@@ -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>

View File

@@ -79,8 +79,10 @@ export default {
visible: false, visible: false,
}, },
menus: [],
nav: { nav: {
loaded: false, loading: false,
modules: [], modules: [],
menus: [], menus: [],
}, },
@@ -88,6 +90,8 @@ export default {
}, },
created() { created() {
this.nav.loading = true;
this.$api.getLoginUser().then(({ data }) => { this.$api.getLoginUser().then(({ data }) => {
const info = { ...data }; const info = { ...data };
delete info.apps; delete info.apps;
@@ -95,8 +99,9 @@ export default {
setGlobal(info); setGlobal(info);
this.nav.modules = data.apps; this.nav.modules = data.apps;
this.nav.menus = data.menus; this.menus = data.menus;
this.nav.loaded = true; this.serializeMenu();
this.nav.loading = false;
}); });
}, },
@@ -226,6 +231,22 @@ export default {
if (!key) key = this.tabActived; if (!key) key = this.tabActived;
this.$refs.content.onLoadContentWindow(key); 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> </script>