一个简易的搜索功能解决方案

This commit is contained in:
ronger 2020-11-30 23:56:08 +08:00
parent 0e5ca8cfcd
commit 93c7567676
8 changed files with 265 additions and 210 deletions

View File

@ -6,19 +6,18 @@
<img src="@/assets/rymcu.png" alt="RYMCU" class="navbar-brand-img"> <img src="@/assets/rymcu.png" alt="RYMCU" class="navbar-brand-img">
</a> </a>
</el-col> </el-col>
<el-col :xs="0" :sm="12" :md="8" :xl="8" style="text-align: center;">
<el-row type="flex" justify="center">
<el-col>
<el-menu :default-active="getActiveMenu" style="margin-top: -2px;border: 0;" mode="horizontal"
@select="handleSelectMenu">
<el-menu-item index="index">首页</el-menu-item>
<el-menu-item index="topic">专题</el-menu-item>
</el-menu>
</el-col>
</el-row>
</el-col>
<el-col :xs="16" :sm="8" :md="6" :xl="3" style="padding-top: 1rem;"> <el-col :xs="16" :sm="8" :md="6" :xl="3" style="padding-top: 1rem;">
<!--<el-col :xs="24" :sm="16" :xl="12">-->
<el-col :xs="0" :sm="0" :xl="0">
<el-autocomplete
v-model="state"
size="small"
:fetch-suggestions="querySearchAsync"
placeholder="搜索帖子、标签和用户"
:trigger-on-focus="false"
@select="handleSelect"
/>
</el-col>
<!--<el-col v-if="user" :xs="0" :sm="8" :xl="6">-->
<client-only> <client-only>
<el-col v-if="user" style="text-align: right;"> <el-col v-if="user" style="text-align: right;">
<el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;" href="/article/post">发帖 <el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;" href="/article/post">发帖
@ -127,27 +126,6 @@
} }
}, },
methods: { methods: {
loadAll() {
return [
{"value": "三全鲜食(北新泾店)", "address": "长宁区新渔路144号"},
{"value": "Hot honey 首尔炸鸡(仙霞路)", "address": "上海市长宁区淞虹路661号"},
{"value": "新旺角茶餐厅", "address": "上海市普陀区真北路988号创邑金沙谷6号楼113"}
]
},
querySearchAsync(queryString, cb) {
let restaurants = this.restaurants;
let results = queryString ? restaurants.filter(this.createStateFilter(queryString)) : restaurants;
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
cb(results);
}, 3000 * Math.random());
},
createStateFilter(queryString) {
return (state) => {
return (state.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
};
},
handleSelectMenu(item) { handleSelectMenu(item) {
let _ts = this; let _ts = this;
let activeMenu = _ts.$store.state.activeMenu; let activeMenu = _ts.$store.state.activeMenu;
@ -171,9 +149,6 @@
} }
} }
}, },
handleSelect(item) {
console.log(item)
},
handleCommand(item) { handleCommand(item) {
let _ts = this; let _ts = this;
switch (item) { switch (item) {
@ -211,7 +186,6 @@
} }
}, },
mounted() { mounted() {
// this.restaurants = this.loadAll();
let user = this.user; let user = this.user;
if (user) { if (user) {
this.getUnreadNotifications(); this.getUnreadNotifications();

View File

@ -6,37 +6,50 @@
<img src="@/assets/rymcu.png" alt="RYMCU" class="navbar-brand-img"> <img src="@/assets/rymcu.png" alt="RYMCU" class="navbar-brand-img">
</a> </a>
</el-col> </el-col>
<el-col :xs="0" :sm="12" :md="14" :xl="18" style="text-align: center;"> <el-col :xs="0" :sm="8" :md="8" :xl="12" style="text-align: center;">
<el-row type="flex" justify="center"> <el-row type="flex" justify="center">
<el-col> <el-col>
<el-menu :default-active="getActiveMenu" style="margin-top: -2px;border: 0;" mode="horizontal" <el-menu :default-active="activeMenu" style="margin-top: -2px;border: 0;" mode="horizontal"
@select="handleSelectMenu"> @select="handleSelectMenu">
<el-menu-item index="index">首页</el-menu-item> <el-menu-item index="index">首页</el-menu-item>
<el-menu-item index="topic">专题</el-menu-item> <el-menu-item index="topic">专题</el-menu-item>
<!-- <el-menu-item index="github">开源代码</el-menu-item>-->
<!-- <el-menu-item index="open-source">资料下载</el-menu-item>-->
</el-menu> </el-menu>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
<el-col :xs="16" :sm="8" :md="6" :xl="3" style="padding-top: 1rem;"> <el-col :xs="10" :sm="6" :md="6" :xl="6" style="padding-top: 1rem;text-align: right;">
<!--<el-col :xs="24" :sm="16" :xl="12">--> <el-autocomplete
<el-col :xs="0" :sm="0" :xl="0"> v-model="queryString"
<el-autocomplete size="small"
v-model="state" value-key="label"
size="small" :fetch-suggestions="querySearchAsync"
:fetch-suggestions="querySearchAsync" placeholder="搜索帖子、作品集和用户"
placeholder="搜索帖子、标签和用户" :trigger-on-focus="false"
:trigger-on-focus="false" @select="handleSelect"
@select="handleSelect" style="width: 80%;"
/> popper-class="search-result-box"
</el-col> >
<!--<el-col v-if="user" :xs="0" :sm="8" :xl="6">--> <template slot-scope="{ item }">
<el-col>
<span class="search-result-type">
<small class="text-muted" v-html="getSearchResultType(item.type)"></small>
</span>
<span>{{ item.label }}</span>
</el-col>
</template>
<!-- <template slot="append">-->
<!-- <el-button size="small" icon="el-icon-search" @click="search"></el-button>-->
<!-- </template>-->
</el-autocomplete>
</el-col>
<el-col :xs="6" :sm="6" :md="6" :xl="3" style="padding-top: 1rem;">
<client-only> <client-only>
<el-col v-if="user" style="text-align: right;"> <el-col v-if="user" style="text-align: right;">
<el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;" href="/portfolio/post">创建作品集 <el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;"
href="/portfolio/post">创建作品集
</el-link> </el-link>
<el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;" href="/article/post">发帖 <el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;"
href="/article/post">发帖
</el-link> </el-link>
<el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;"> <el-link rel="nofollow" :underline="false" style="padding-left: 10px;padding-right: 10px;">
<el-dropdown trigger="click" @command="handleCommand"> <el-dropdown trigger="click" @command="handleCommand">
@ -61,7 +74,9 @@
:src="avatarURL"></el-avatar> :src="avatarURL"></el-avatar>
<el-avatar class="mr-3" v-else size="small" style="margin-top: 1rem;" <el-avatar class="mr-3" v-else size="small" style="margin-top: 1rem;"
src="https://static.rymcu.com/article/1578475481946.png"></el-avatar> src="https://static.rymcu.com/article/1578475481946.png"></el-avatar>
<el-link rel="nofollow" :underline="false" style="margin-left: 10px;margin-bottom: 1rem;">{{ nickname }}</el-link> <el-link rel="nofollow" :underline="false" style="margin-left: 10px;margin-bottom: 1rem;">
{{ nickname }}
</el-link>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item v-if="hasPermissions" command="admin-dashboard">系统管理</el-dropdown-item> <el-dropdown-item v-if="hasPermissions" command="admin-dashboard">系统管理</el-dropdown-item>
<el-dropdown-item command="user">个人中心</el-dropdown-item> <el-dropdown-item command="user">个人中心</el-dropdown-item>
@ -88,172 +103,196 @@
</template> </template>
<script> <script>
import { isBrowser } from '~/environment' import {mapState} from 'vuex';
import {isBrowser} from '~/environment';
const Cookie = process.client ? require('js-cookie') : undefined const Cookie = process.client ? require('js-cookie') : undefined
export default { export default {
name: "PcHeader", name: "PcHeader",
computed: { computed: {
getActiveMenu() { ...mapState({
return this.$store.state.activeMenu; activeMenu: state => state.activeMenu,
}, user: state => state.oauth,
user() { initialSearchData: state => state.search.list
return this.$store.state.oauth; }),
}, avatarURL() {
avatarURL() { let _ts = this;
let _ts = this; if (isBrowser) {
if (isBrowser) { if (!_ts.$store.state.userInfo) {
if (!_ts.$store.state.userInfo) { let user = localStorage.getItem('user');
let user = localStorage.getItem('user'); if (user) {
if (user) { _ts.$store.commit('setUser', JSON.parse(user))
_ts.$store.commit('setUser', JSON.parse(user))
}
} }
} }
return _ts.$store.state.userInfo?.avatarURL;
},
nickname() {
let _ts = this;
if (isBrowser) {
if (!_ts.$store.state.userInfo) {
let user = localStorage.getItem('user');
if (user) {
_ts.$store.commit('setUser', JSON.parse(user))
}
}
}
return _ts.$store.state.userInfo?.nickname;
},
hasPermissions() {
return this.$store.getters.hasPermissions('blog_admin');
} }
return _ts.$store.state.userInfo?.avatarURL;
}, },
data() { nickname() {
return { let _ts = this;
restaurants: [], if (isBrowser) {
state: '', if (!_ts.$store.state.userInfo) {
timeout: null, let user = localStorage.getItem('user');
show: false, if (user) {
notifications: [], _ts.$store.commit('setUser', JSON.parse(user))
notificationNumbers: "" }
}
}
return _ts.$store.state.userInfo?.nickname;
},
hasPermissions() {
return this.$store.getters.hasPermissions('blog_admin');
}
},
data() {
return {
queryString: '',
timeout: null,
show: false,
notifications: [],
notificationNumbers: ""
};
},
watch: {
user: function () {
this.getUnreadNotifications();
}
},
methods: {
querySearchAsync(queryString, cb) {
let initialSearchData = this.initialSearchData;
let results = queryString ? initialSearchData.filter(this.createStateFilter(queryString)) : initialSearchData;
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
cb(results);
}, 3000 * Math.random());
},
createStateFilter(queryString) {
return (state) => {
if (state && state.label) {
return (state.label.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
}
}; };
}, },
watch: { handleSelectMenu(item) {
user: function () { let _ts = this;
this.getUnreadNotifications(); let activeMenu = _ts.$store.state.activeMenu;
} if (activeMenu !== item) {
},
methods: {
loadAll() {
return [
{"value": "三全鲜食(北新泾店)", "address": "长宁区新渔路144号"},
{"value": "Hot honey 首尔炸鸡(仙霞路)", "address": "上海市长宁区淞虹路661号"},
{"value": "新旺角茶餐厅", "address": "上海市普陀区真北路988号创邑金沙谷6号楼113"}
]
},
querySearchAsync(queryString, cb) {
let restaurants = this.restaurants;
let results = queryString ? restaurants.filter(this.createStateFilter(queryString)) : restaurants;
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
cb(results);
}, 3000 * Math.random());
},
createStateFilter(queryString) {
return (state) => {
return (state.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
};
},
handleSelectMenu(item) {
let _ts = this;
let activeMenu = _ts.$store.state.activeMenu;
if (activeMenu !== item) {
switch (item) {
case 'topic':
_ts.$router.push({
path: '/topic/news'
})
break;
case 'github':
window.open("https://github.com/rymcu");
return false;
break;
default:
_ts.$router.push(
{
path: '/'
}
)
}
}
},
handleSelect(item) {
console.log(item)
},
handleCommand(item) {
let _ts = this;
switch (item) { switch (item) {
case 'user': case 'topic':
_ts.$router.push({ _ts.$router.push({
path: '/user/' + _ts.nickname path: '/topic/news'
}) })
break; break;
case 'user-info': case 'github':
_ts.$router.push({ window.open("https://github.com/rymcu");
path: '/user/settings/account' return false;
})
break;
case 'logout':
Cookie.remove('auth')
_ts.$store.commit('setAuth', null)
item = 'login';
break; break;
default: default:
_ts.$router.push({ _ts.$router.push(
name: item {
}) path: '/'
} }
}, )
getUnreadNotifications() {
let _ts = this;
if (_ts.user) {
_ts.$axios.$get('/api/notification/unread').then(function (res) {
if (res) {
_ts.$set(_ts, 'notifications', res.notifications);
_ts.$set(_ts, 'notificationNumbers', res.notifications.length == 0 ? "" : res.notifications.length);
}
})
} }
} }
}, },
mounted() { handleSelect(item) {
// this.restaurants = this.loadAll(); console.log(item);
let user = this.user; let _ts = this;
if (user) { if (item) {
this.getUnreadNotifications(); _ts.$router.push({
path: `/${item.type}/${item.value}`
})
} }
},
handleCommand(item) {
let _ts = this;
switch (item) {
case 'user':
_ts.$router.push({
path: '/user/' + _ts.nickname
})
break;
case 'user-info':
_ts.$router.push({
path: '/user/settings/account'
})
break;
case 'logout':
Cookie.remove('auth')
_ts.$store.commit('setAuth', null)
item = 'login';
break;
default:
_ts.$router.push({
name: item
})
}
},
getUnreadNotifications() {
let _ts = this;
if (_ts.user) {
_ts.$axios.$get('/api/notification/unread').then(function (res) {
if (res) {
_ts.$set(_ts, 'notifications', res.notifications);
_ts.$set(_ts, 'notificationNumbers', res.notifications.length == 0 ? "" : res.notifications.length);
}
})
}
},
search() {
console.log(this.queryString)
},
getSearchResultType(type) {
switch (type) {
case 'article':
type = '文章';
break;
case 'portfolio':
type = '作品集';
break;
case 'user':
type = '用户';
break;
}
return type;
}
},
mounted() {
let user = this.user;
if (user) {
this.getUnreadNotifications();
} }
} }
}
</script> </script>
<style scoped> <style scoped>
.navbar-brand { .navbar-brand {
color: inherit; color: inherit;
margin-right: 1rem; margin-right: 1rem;
font-size: 1.25rem; font-size: 1.25rem;
white-space: nowrap; white-space: nowrap;
font-weight: 600; font-weight: 600;
padding: 0; padding: 0;
transition: .3s opacity; transition: .3s opacity;
line-height: 3rem; line-height: 3rem;
} }
.navbar-brand-img { .navbar-brand-img {
height: 3rem; height: 3rem;
line-height: 3rem; line-height: 3rem;
vertical-align: top; vertical-align: top;
width: auto; width: auto;
} }
.search-result-box {
min-width: 20vw !important;
}
.search-result-type {
padding-right: 5px;
}
</style> </style>

View File

@ -81,7 +81,7 @@
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import {mapState} from 'vuex'; import { mapState } from 'vuex';
import ShareBox from '~/components/widget/share'; import ShareBox from '~/components/widget/share';
import PortfoliosWidget from '~/components/widget/portfolios'; import PortfoliosWidget from '~/components/widget/portfolios';
import EditTags from '~/components/widget/tags'; import EditTags from '~/components/widget/tags';

View File

@ -136,6 +136,9 @@
currentChangeArticle(page) { currentChangeArticle(page) {
this.$store.dispatch('portfolio/fetchArticleList', {page: page, portfolio_id: this.routePortfolioId}) this.$store.dispatch('portfolio/fetchArticleList', {page: page, portfolio_id: this.routePortfolioId})
} }
},
mounted() {
this.$store.commit('setActiveMenu', 'portfolioDetail');
} }
} }
</script> </script>

View File

@ -1,5 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import Element from 'element-ui' import Element from 'element-ui'
import locale from 'element-ui/lib/locale/lang/zh-CN' import language from 'element-ui/lib/locale/lang/zh-CN'
Vue.use(Element, { locale }) Vue.use(Element, { language })

View File

@ -4,7 +4,7 @@ export const state = () => ({
// ua // ua
userAgent: '', userAgent: '',
// 默认语言 // 默认语言
language: '', language: 'zh-CN',
}) })

View File

@ -1,6 +1,6 @@
import { isServer } from '~/environment' import { isServer } from '~/environment'
const cookieparser = process.server ? require('cookieparser') : undefined const cookieParser = isServer ? require('cookieparser') : undefined
export const state = () => { export const state = () => {
return { return {
@ -52,7 +52,7 @@ export const actions = {
android: userAgent.indexOf('Android') > -1 || userAgent.indexOf('Linux') > -1, //android终端或uc浏览器 android: userAgent.indexOf('Android') > -1 || userAgent.indexOf('Linux') > -1, //android终端或uc浏览器
iPhone: userAgent.indexOf('iPhone') > -1, //是否为iPhone或者QQHD浏览器 iPhone: userAgent.indexOf('iPhone') > -1, //是否为iPhone或者QQHD浏览器
iPad: userAgent.indexOf('iPad') > -1, //是否iPad iPad: userAgent.indexOf('iPad') > -1, //是否iPad
webApp: userAgent.indexOf('Safari') == -1 //是否web应程序,没有头部与底部 webApp: userAgent.indexOf('Safari') == -1 //是否web应程序,没有头部与底部
}; };
} () } ()
} }
@ -62,7 +62,7 @@ export const actions = {
} }
let auth = null let auth = null
if (req.headers.cookie) { if (req.headers.cookie) {
const parsed = cookieparser.parse(req.headers.cookie) const parsed = cookieParser.parse(req.headers.cookie)
try { try {
auth = JSON.parse(parsed.auth) auth = JSON.parse(parsed.auth)
} catch (err) { } catch (err) {
@ -75,7 +75,8 @@ export const actions = {
const initFetchAppData = [ const initFetchAppData = [
// 内容数据 // 内容数据
store.dispatch('topic/fetchNavList'), store.dispatch('topic/fetchNavList'),
store.dispatch('article/fetchList') store.dispatch('article/fetchList'),
store.dispatch('search/fetchList'),
] ]
return Promise.all(initFetchAppData) return Promise.all(initFetchAppData)

38
store/search.js Normal file
View File

@ -0,0 +1,38 @@
export const BASE_API_PATH = '/api/console';
export const state = () => {
return {
fetching: false,
list: []
}
}
export const mutations = {
// 数据列表
updateListFetching(state, action) {
state.fetching = action
},
updateListData(state, action) {
state.list = action
}
}
export const actions = {
// 获取文章列表
fetchList({commit}, params = {}) {
// 清空已有数据
commit('updateListData', []);
commit('updateListFetching', true);
return this.$axios
.$get(`${BASE_API_PATH}/initial-search`)
.then(response => {
commit('updateListFetching', false);
commit('updateListData', response);
})
.catch(error => {
console.log(error);
commit('updateListFetching', false);
});
}
}