Use Vue to develop the project (dark horse headline project) - day 6

Use Vue to develop the project (dark horse headline project) - day 6

The main functions that need to be implemented are as follows:

Information list, tab switching, article reporting, channel management, article details, reading memory, follow function, like function, comment function, comment reply, search function, login function, personal center, edit data, Xiaozhi classmates...

The function to be realized today is mainly: search function

in

layout/layout.vue
in

0 Realize the function of displaying mine and not logged in according to whether the user is logged in or not

<van-tabbar route> <van-tabbar-item to="/" icon="home-o"> Home page </van-tabbar-item> <van-tabbar-item to="/question" icon="chat-o"> Q&A </van-tabbar-item> <van-tabbar-item to="/video" icon="video-o"> video </van-tabbar-item> <van-tabbar-item to="/user" icon="search"> + {{$store.state.tokenInfo.token?'My':'Not logged in' }} </van-tabbar-item> </van-tabbar> Copy code

1 Search page

1.1 New
src/views/search/search.vue
page

<template> <div>Search page</div> </template> <script> export default { } </script> <style lang="less" scoped> </style> Copy code

1.2 Supplementary routing configuration

const routes = [ { path:'/login', name:'login', component: Login }, { path:'/', name:'layout', component: Layout, //.... }, { + path:'/search', + name:'search', + component: () => import(/* webpackChunkName: "search" */'../views/search/search.vue') } ] Copy code

1.3 Set routing jump

in

src\views\layout\layout.vue
In, click the button to realize the route jump.

<!-- Top logo search navigation area --> <van-nav-bar fixed > <div slot="left" class="logo"></div> <van-button slot="right" class="search-btn" round type="info" size="small" icon="search" + @click="$router.push('/search')" > search for </van-button> </van-nav-bar> Copy code

1.4 Test directly in the address bar http://localhost:8080/#/search

2 search page-component layout

2.1 Analysis structure

From top to bottom, the page structure can be divided into four parts

2.2 Layout

$router.back() means back

<template> <div> <!-- nav-bar this.$router.push(): route jump this.$router.back(): route back ===== back button in the page --> <van-nav-bar title="Search Center" left-arrow @click-left="$router.back()"></van-nav-bar> <!-- 1. Search area input box --> <van-search v-model.trim="keyword" show-action placeholder="Please enter search keywords" > <!-- #action ==== slot="action" --> <!-- <template slot="action"> <div>Search</div> </template> --> <div slot="action">Search</div> </van-search> <!-- 2. Search suggestions--> <van-cell-group> <van-cell title="cell" icon="search"/> <van-cell title="cell" icon="search"/> <van-cell title="cell" icon="search"/> </van-cell-group> <!-- 3. History Record--> <van-cell-group> <van-cell title="History"/> <van-cell title="cell"> <van-icon name="close"></van-icon> </van-cell> <van-cell title="cell"> <van-icon name="close"></van-icon> </van-cell> </van-cell-group> </div> </template> <script> export default { name:'Search', data () { return { keyword:'' } } } </script> Copy code

2.3 The effect is shown in the figure

3 Search page-realize Lenovo suggestion function

When the user writes the content in the input box, the suggestion content is displayed below the input box

3.1 package api

create

api/search.js
And write

//Used to encapsulate all business related to search operations import request from'../utils/request' /** * Get search suggestions * @param {*} keyword search keyword * @returns */ export const getSuggestion = (keyword) => { return request({ url:'v1_0/suggestion', method:'GET', params: { q: keyword } }) } Copy code

3.2 Request for data when the search content changes

<!-- 1. Search area input box --> <van-search + @input="hInput" v-model.trim="keyword" show-action placeholder="Please enter search keywords" > Copy code
import {getSuggestion} from '@/ api/search.js' duplicated code
return { keyword:'', + suggestions: []//Lenovo suggestions } Copy code
//The content entered by the user in the search box has changed async hInput () { console.log(this.keyword) //If the user does not enter anything, clear the suggestion and do not send a request if (this.keyword ==='') { this.suggestions = [] return } try { const {data: {data}} = await getSuggestion(this.keyword) this.suggestions = data.options } catch (err) { console.log(err) } }, Copy code

3.3 Data rendering

<!-- 2. Search suggestions According to the keywords you enter above, the backend will return suggestions --> <van-cell-group> <van-cell v-for="(suggestion,idx) in suggestions" :key="idx" :title="suggestion" icon="search" /> </van-cell-group> Copy code

4 Search page-highlight search keywords

Since the original data will be used in subsequent operations, you cannot modify the original data directly, but should generate a copy

Use a calculated attribute to save the suggested items after highlighting.

  • In the calculation attribute, the original data needs to be processed: use regular + replace to replace the content with a string to achieve the goal of highlighting.

  • Use v-html to display data.

4.1 Package auxiliary highlight function

//source string, the segment to be highlighted export const heightLight = (str, key) => { const reg = new RegExp(key,'ig') return str.replace(reg, (val) => { return `<span style="color:red">${val}</span>` }) } Copy code

4.2 Add calculation attributes

computed: { cSuggestions () { //Replace every item in this.suggestions //each item in suggestions ====> heightLight(each item in suggestions, this.keyword) return this.suggestions.map(item => { return heightLight(item, this.keyword) }) } }, Copy code

4.3 Rendering

<!-- 2. Search suggestions According to the keywords you enter above, the backend will return suggestions --> <van-cell-group> <van-cell v-for="(item,idx) in cSuggestions" :key="idx" icon="search"> <div v-html="item"></div> </van-cell> </van-cell-group> Copy code

4.4 Check the effect

5 Search page-display search history

After the user searches for a certain keyword, record it for quick search later

1. The format and location of the record

Format: array . For example: ['a','mobile phone','javascript']

2. To save records in the following two cases:

  1. Save when you click search on the search box

  2. Save when clicking on the Lenovo suggestion item given by the system

5.1 Search page-add search records

data () { return { keyword:'', + historys: ['Tianjin','Wanda'], suggestions: [] } }, Copy code
<!-- Search history--> <van-cell-group> <van-cell title="History"></van-cell> <van-cell v-for="(item,idx) in historys" :key="idx" :title="item"> <van-icon name="close"/> </van-cell> </van-cell-group> <!--/Search History--> Copy code

Encapsulate the method of adding history records, convenient to call later

//add a history addHistory (str) { this.historys.push(str) //todo: }, Copy code

5.2 When clicking on Lenovo suggestions

<van-cell-group> <van-cell v-for="(item,idx) in cSuggestions" :key="idx" icon="search" + @click="hClickSuggetion(idx)"> <div v-html="item"></div> </van-cell> </van-cell-group> Copy code
//Case 2: The user clicks on the search suggestion hClickSuggetion (idx) { //1. Add a history this.addHistory(this.suggestions[idx]) //2. Jump to the search results page } Copy code

5.3 When clicking the search button

<div slot="action" @click="hSearch">Search</div> copy code
//Case 1: The user clicks on the search hSearch () { //1. Add a history this.addHistory(this.keyword) //2. Jump to the search results page //todo }, Copy code

5.4 Check whether the bound event is valid

Figure

5.5 Search page-optimize the method of adding history records

Improve the function of adding history records

When adding history records, there are two considerations as follows:

  • No duplicates
  • The last join should be placed at the top of the array
//add a history //1. No duplicates //2. After adding to the front of the array addHistory (str) { //(1) Find out if there is a duplicate, if there is, delete it const idx = this.history.findIndex(item => item === str) //idx !== -1 && this.historys.splice(idx, 1) if (idx> -1) { this.history.splice(idx, 1) } //(2) add to the front of the array this.history.unshift(str) } Copy code

6 Search page-delete history

Provide users with the function of deleting historical records: there is a close button behind each record, click this button to delete this record

6.1 Add a click event to the X icon

<!-- Search history--> <van-cell-group> <van-cell title="History"></van-cell> <van-cell v-for="(item,idx) in historys" :key="idx" :title="item"> + <van-icon name="close" @click="hDelHistory(idx)"/> </van-cell> </van-cell-group> <!--/Search History--> Copy code
//User clicked to delete history hDelHistory (idx) { this.historys.splice(idx, 1) } Copy code

6.2 Search page-search history persistence

Save history to localstorage

  • Encapsulate a module used to persist history records (setup, delete)
  • Save once when the search history changes (add, delete)
  • Use to import local data at the beginning (to take out data from localstorage)

6.2.1 Create
utils/storageHistory.js

//Eliminate the magic string const HISTORY_STR ='HistoryInfo' export const getHistory = () => { return JSON.parse(localStorage.getItem(HISTORY_STR)) } export const setHistory = HistoryInfo => { localStorage.setItem(HISTORY_STR, JSON.stringify(HistoryInfo)) } export const removeHistory = () => { localStorage.removeItem(HISTORY_STR) } Copy code

6.2.2 Import and use

`import {setHistory, getHistory} from '@/utils/storageHistory.js'` duplicated code
data () { return { keyword:'',//search keyword //Initialization, first get the value from the local storage, if not, use [] historys: getHistory() || [],//save history ['regular','javascript'] suggestions: []//current search suggestions } } Copy code
//add a history //1. No duplicates //2. After adding to the front of the array //3. Persistence addHistory (str) { //(1) Find out if there is a duplicate, if there is, delete it const idx = this.history.findIndex(item => item === str) if (idx> -1) { this.historys.splice(idx, 1) } //(2) add to the front of the array this.historys.unshift(str) //(3) Persistence + setHistory(this.history) }, //User clicked to delete history hDelHistory (idx) { this.historys.splice(idx, 1) //Persistence + setHistory(this.history) } Copy code

6.3 Search page-switch display of Lenovo suggestions and history

The two areas of Lenovo suggestion and search history are mutually exclusive:

  • If you are currently searching for content, the search history will not be displayed, but Lenovo suggestions will be displayed.
  • If there is no search content currently, the search history will be displayed instead of the Lenovo suggestion.
<!-- Lenovo suggestion v-html to display the html string effect normally --> <!-- 2. Search suggestions According to the keywords you enter above, the backend will return suggestions v-if="suggestion.length": If there are search suggestions --> <van-cell-group v-if="suggestions.length"> ... </van-cell-group> <!--/Lenovo suggestion--> <!-- Search history--> <van-cell-group v-else> ... </van-cell-group> <!--/Search History--> Copy code

7 Anti-shake and throttling function

In the character input box will change immediately to send a request to obtain search suggestions.

  • For users, although they can receive search suggestions in time, in the search process, the words you entered are not finished, and most of the search suggestions you get are useless.
  • For the server, the frequency of calling this interface is too high, which adds a burden to the server.

7.1 Realize the anti-shake function

//The user's input has changed hInput () { clearTimeout(this.timer) console.log(this.keyword) this.timer = setTimeout(() => { this.doAjax() }, 200) }, async doAjax () { if (this.keyword ==='') { this.suggestions = [] return } try { const res = await getSuggestion(this.keyword) //console.log(res) this.suggestions = res.data.data.options } catch (err) { console.log(err) } }, Copy code

7.2 Realize the throttling function

//Throttle hInput () { const dt = Date.now()//Get the current timestamp in ms if (dt-this.startTime> 500) { this.doAjax() this.startTime = dt } else { console.log('The current timestamp is', dt,'It is not 500ms since the last execution, so it is not executed') } }, async doAjax () { if (this.keyword ==='') { this.suggestions = [] return } try { const res = await getSuggestion(this.keyword) //console.log(res) this.suggestions = res.data.data.options } catch (err) { console.log(err) } } Copy code

7.3 Just choose one of throttling and anti-shake

8 search results

The search results are displayed on another page separately :

  • Route jump, and pass in the keywords you want to search
  • After receiving the keyword, adjust the interface
  • Retrieve the query results and display them.

8.1 Create components

views/search/result.vue

<template> <div class="serach-result"> <!-- Navigation bar--> <van-nav-bar title="xxx search results" left-arrow fixed @click-left="$router.back()" /> <!--/Navigation bar--> <!-- Article list--> <van-list class="article-list" v-model="loading" :finished="finished" finished-text="no more" @load="onLoad" > <van-cell v-for="item in list" :key="item" :title="item" /> </van-list> <!--/Article List--> </div> </template> <script> export default { name:'SearchResult', data () { return { list: [], loading: false, finished: false } }, methods: { onLoad () { //Update data asynchronously setTimeout(() => { for (let i = 0; i <10; i++) { this.list.push(this.list.length + 1) } //End of loading state this.loading = false //All data is loaded if (this.list.length >= 40) { this.finished = true } }, 500) } } } </script> <style lang="less" scoped> .serach-result { height: 100%; overflow: auto; .article-list { margin-top: 39px; } } </style> Copy code

8.2 Create Route

{ path:'/searchResult', name:'searchResult', component: () => import('../views/search/searchResult.vue') }, Copy code

8.3 Use routing to pass parameters and jump pages

//Case 1: The user clicks on the search hSearch () { if (this.keyword ==='') { return } //1. Add a history this.addHistory(this.keyword) //2. Jump to the search results page + this.$router.push('/search/result?keyword=' + this.keyword) } Copy code
<!-- 2. History Record--> <van-cell-group v-else> <van-cell title="History"/> <van-cell v-for="(item,idx) in history" :key="idx" :title="item" + @click="$router.push('/search/result?keyword=' + item)"> <van-icon name="close" @click.stop="hDelHistory(idx)"></van-icon> </van-cell> </van-cell-group> Copy code
//Case 3: The user clicks on the search suggestion hClickSuggetion (idx) { //1. Add a history this.addHistory(this.suggestion[idx]) //2. Jump to the search results page + this.$router.push('/search/result?keyword=' + this.suggestions[idx]) } Copy code

9 Search results-get query results and display

search/result.vue
In, we can get the incoming query keyword through this.$route.query.keyword

created () { var keyword = this.$route.query.keyword alrt(keyword) } Copy code

9.1 Package interface

in

api/serach.js
Package request method

/** * Get query results * @param {*} keyword keyword * @param {*} page page number */ export const getSearchResult = (keyword, page) => { return request({ method:'GET', url:'v1_0/search', params: { q: keyword, page: page } }) } Copy code

9.2 Call the interface to get data

import {getSearchResult} from'@/api/search.js' export default { name:'SearchResult', data () { return { list: [], page: 1,//current page number loading: false, finished: false } } //--- async onLoad () { console.log(this.$route.query.keyword) //1. Make a request const res = await getSearchResult(this.$route.query.keyword, this.page) const arr = res.data.data.results //2. After the data comes back, fill it into the list this.list.push(...arr) //3. Manually end the loading state this.loading = false //4. Determine if there is more data this.finished = !arr.length //5. Page +1 this.page++ } Copy code

9.3 Data rendering

<template> <div class="serach-result"> <!-- Navigation bar--> <van-nav-bar :title="`${$route.query.keyword} search results`" left-arrow fixed @click-left="$router.back()" /> <!--/Navigation bar--> <!-- Article list--> <van-list class="article-list" v-model="loading" :finished="finished" finished-text="no more" @load="onLoad" > <van-cell v-for="item in list" @click="$router.push('/article/' + item.art_id)" :key="item.art_id" :title="item.title" /> </van-list> <!--/Results found in article list--> </div> </template> Copy code

9.4 Checking the effect