Skip to content

feature: add header-search component #1591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Feb 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"echarts": "4.1.0",
"element-ui": "2.4.11",
"file-saver": "1.3.8",
"fuse.js": "3.4.2",
"js-cookie": "2.2.0",
"jsonlint": "1.6.3",
"jszip": "3.1.5",
Expand Down
187 changes: 187 additions & 0 deletions src/components/HeaderSearch/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
<template>
<div :class="{'show':show}" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click="click" />
<el-select
ref="headerSearchSelect"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change">
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')"/>
</el-select>
</div>
</template>

<script>
import Fuse from 'fuse.js'
import path from 'path'
import i18n from '@/lang'

export default {
name: 'HeaderSearch',
data() {
return {
search: '',
options: [],
searchPool: [],
show: false,
fuse: undefined
}
},
computed: {
routers() {
return this.$store.getters.permission_routers
},
lang() {
return this.$store.getters.language
}
},
watch: {
lang() {
this.searchPool = this.generateRouters(this.routers)
},
routers() {
this.searchPool = this.generateRouters(this.routers)
},
searchPool(list) {
this.initFuse(list)
},
show(value) {
if (value) {
document.body.addEventListener('click', this.close)
} else {
document.body.removeEventListener('click', this.close)
}
}
},
mounted() {
this.searchPool = this.generateRouters(this.routers)
},
methods: {
click() {
this.show = !this.show
if (this.show) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
this.options = []
this.show = false
},
change(val) {
this.$router.push(val.path)
this.search = ''
this.options = []
this.$nextTick(() => {
this.show = false
})
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
},
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
generateRouters(routers, basePath = '/', prefixTitle = []) {
let res = []

for (const router of routers) {
// skip hidden router
if (router.hidden) { continue }

const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
}

if (router.meta && router.meta.title) {
// generate internationalized title
const i18ntitle = i18n.t(`route.${router.meta.title}`)

data.title = [...data.title, i18ntitle]

if (router.redirect !== 'noredirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}

// recursive child routers
if (router.children) {
const tempRouters = this.generateRouters(router.children, data.path, data.title)
if (tempRouters.length >= 1) {
res = [...res, ...tempRouters]
}
}
}
return res
},
querySearch(query) {
if (query !== '') {
this.options = this.fuse.search(query)
} else {
this.options = []
}
}
}
}
</script>

<style lang="scss" scoped>
.header-search {
font-size: 0 !important;

.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}

.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;

/deep/ .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}

&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>
1 change: 1 addition & 0 deletions src/icons/svg/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 23 additions & 12 deletions src/views/layout/components/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,26 @@

<div class="right-menu">
<template v-if="device!=='mobile'">
<error-log class="errLog-container right-menu-item"/>
<search class="right-menu-item" />

<error-log class="errLog-container right-menu-item hover-effect"/>

<el-tooltip :content="$t('navbar.screenfull')" effect="dark" placement="bottom">
<screenfull class="right-menu-item"/>
<screenfull class="right-menu-item hover-effect"/>
</el-tooltip>

<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
<size-select class="right-menu-item"/>
<size-select class="right-menu-item hover-effect"/>
</el-tooltip>

<lang-select class="right-menu-item"/>
<lang-select class="right-menu-item hover-effect"/>

<el-tooltip :content="$t('navbar.theme')" effect="dark" placement="bottom">
<theme-picker class="theme-picker right-menu-item"/>
<theme-picker class="right-menu-item hover-effect"/>
</el-tooltip>
</template>

<el-dropdown class="avatar-container right-menu-item" trigger="click">
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<i class="el-icon-caret-bottom"/>
Expand Down Expand Up @@ -57,6 +59,7 @@ import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import LangSelect from '@/components/LangSelect'
import ThemePicker from '@/components/ThemePicker'
import Search from '@/components/HeaderSearch'

export default {
components: {
Expand All @@ -66,7 +69,8 @@ export default {
Screenfull,
SizeSelect,
LangSelect,
ThemePicker
ThemePicker,
Search
},
computed: {
...mapGetters([
Expand Down Expand Up @@ -100,6 +104,7 @@ export default {
float: left;
cursor: pointer;
transition: background .3s;

&:hover {
background: rgba(0, 0, 0, .025)
}
Expand All @@ -124,24 +129,30 @@ export default {
}

.right-menu-item {
cursor: pointer;
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 20px;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)

&.hover-effect {
cursor: pointer;
transition: background .3s;

&:hover {
background: rgba(0, 0, 0, .025)
}
}
}

.avatar-container {
margin-right: 30px;

.avatar-wrapper {
margin-top: 5px;
position: relative;

.user-avatar {
cursor: pointer;
width: 40px;
Expand Down