很多伙伴在自己用vue學(xué)習(xí)開(kāi)發(fā)的過(guò)程中,不像真正的工作開(kāi)發(fā),有后端支持,可以從后端那里調(diào)接口拿到真實(shí)的數(shù)據(jù)進(jìn)行展示。但為了模擬這個(gè)過(guò)程,我們經(jīng)常使用自己在本地編寫(xiě)模擬的數(shù)據(jù)進(jìn)行使用,然后通過(guò)發(fā)送ajax請(qǐng)求得到我們本地的數(shù)據(jù)。
這次,超人鴨就使用本地編寫(xiě)的json數(shù)據(jù),通過(guò)axios發(fā)送ajax請(qǐng)求,來(lái)獲取本地?cái)?shù)據(jù),然后實(shí)現(xiàn)搜索功能,并在這個(gè)功能上加以拓展,實(shí)現(xiàn)搜索關(guān)鍵字的高亮。
我們一步一步來(lái),首先使用axios
axios是現(xiàn)在vue官方推薦使用一個(gè)插件,用來(lái)發(fā)送http請(qǐng)求,如果只是簡(jiǎn)單的調(diào)用接口,那用法也非常簡(jiǎn)單。
//通過(guò)npm安裝
npm install axios --save
//引入和掛載,在main.js里面
import axios from 'axios'
Vue.prototype.$axios = axios
//在組件中使用
this.$axios.get(url)
.then((res) => {
//成功的回調(diào)
})
上面是axios的基礎(chǔ)用法,下面也會(huì)看到具體的使用
然后編寫(xiě)我們的json數(shù)據(jù)
因?yàn)槭潜镜氐哪M數(shù)據(jù),所以推薦放在項(xiàng)目中static文件夾里面,下面是超人鴨本次編寫(xiě)的json文件device.json,大家可以不用關(guān)心數(shù)據(jù)本身有什么含義,看一下json數(shù)據(jù)的編寫(xiě)格式
{
"data":{
"deviceList":[
{"name":"設(shè)備一","date":"2019-03-24","adress":"深圳","type":"電動(dòng)"},
{"name":"設(shè)備二","date":"2019-03-24","adress":"上海","type":"汽油"},
{"name":"設(shè)備三","date":"2019-03-24","adress":"北京","type":"電動(dòng)"},
{"name":"設(shè)備四","date":"2019-03-24","adress":"廣州","type":"汽油"}
......
]
}
}
這就寫(xiě)好一個(gè)json文件,我們來(lái)看看在組件中如何通過(guò)axios調(diào)用到這個(gè)json文件:
//在組件的created鉤子函數(shù)中調(diào)用
created(){
//寫(xiě)上文件的路徑
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
console.log(res)
})
}
看看打印出來(lái)的結(jié)果:
可以看到已經(jīng)調(diào)用成功,我們要的數(shù)據(jù)在res.data.data.deviceList里面
回到這次的主題,實(shí)現(xiàn)搜索且關(guān)鍵字高亮
先看看最終效果:
有一個(gè)輸入框,當(dāng)我輸入 '設(shè)備' 兩個(gè)字后:
當(dāng)我輸入 '廣州' 兩個(gè)字后:
可以看到完成了超人鴨所描述的效果,下面我將通過(guò)代碼和代碼中的注釋來(lái)介紹功能的實(shí)現(xiàn),樣式部分就不寫(xiě)出來(lái)了,沒(méi)什么影響。
現(xiàn)在簡(jiǎn)單的html結(jié)構(gòu)和data里面的變量:
<template>
<div class="bright-index">
<div class="search-box">
<input type="text" v-model="keyword" class="input" placeholder="請(qǐng)輸入搜索內(nèi)容, 提示:搜索設(shè)備">
<button class="btn" @click="search">搜索</button>
</div>
<div class="content-box">
<div class="content-card" v-for="(item ,index) in resultList" :key="index">
設(shè)備名稱:<span v-html="item.name"></span>
<span>日期:<span v-html="item.date"></span></span>
<span>地址:<span v-html="item.adress"></span></span>
<span>類型:<span v-html="item.type"></span></span>
</div>
<h2 v-if="isShowTip">沒(méi)有搜索到匹配結(jié)果</h2>
</div>
</div>
</template>
<script>
export default {
data () {
return {
keyword: '', //與輸入框綁定的變量
deviceList: [], // 調(diào)用json文件獲取的全部數(shù)據(jù)
resultList: [], //真正展示的數(shù)據(jù),也就是篩選后的數(shù)據(jù)
isShowTip: false //當(dāng)搜索不到數(shù)據(jù)時(shí)為true
}
}
</script>
我們先實(shí)現(xiàn)搜索功能,高亮下一步再實(shí)現(xiàn)
//首先得到數(shù)據(jù),在created里面使用axios
created() {
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
//將json文件中的數(shù)據(jù)賦值給data里面的deviceList
this.deviceList = res.data.data.deviceList
})
}
//然后我們給按鈕綁定了一個(gè)點(diǎn)擊事件search,在這個(gè)事件中做處理
methods:{
search() {
if (this.keyword == '') { //如果沒(méi)有輸入內(nèi)容,不讓往下執(zhí)行
return
}
this.resultList = [] //每次搜索對(duì)結(jié)果數(shù)組做初始化
this.deviceList.forEach((item) => { //把初始數(shù)據(jù)進(jìn)行遍歷
/**
下面是把初始數(shù)據(jù)中的每一條數(shù)據(jù)的四個(gè)字段分別去和輸入的內(nèi)容進(jìn)行匹配,
如果某一字段中包含輸入的內(nèi)容,就往resultList中加
**/
if (item.name.indexOf(this.keyword) > -1 ||
item.date.indexOf(this.keyword) > -1 ||
item.adress.indexOf(this.keyword) > -1 ||
item.type.indexOf(this.keyword) > -1) {
this.resultList.push(item)
}
})
if (this.resultList.length == 0) { //如果沒(méi)有匹配結(jié)果,就顯示提示信息
this.isShowTip = true
}
}
}
因?yàn)槭潜镜氐哪M數(shù)據(jù),所以采用最原始的方式,去遍歷判斷篩選。
這樣就完成了搜索功能,接下來(lái)超人鴨來(lái)實(shí)現(xiàn)關(guān)鍵字高亮
注意到上面我放的html結(jié)構(gòu)嗎,里面展示數(shù)據(jù)用的不是平常的插值表達(dá)式,也就是兩個(gè)大括號(hào),而是用v-html,可以用來(lái)展示html代碼的而不是簡(jiǎn)單的字符串。
說(shuō)到這,那實(shí)現(xiàn)高亮的思路就是將關(guān)鍵字也就是你輸入的內(nèi)容,在每一條數(shù)據(jù)中替換成html字符串
接上上面的代碼,還是在search方法里面
//將得到的每一條數(shù)據(jù)中的每一個(gè)字段進(jìn)行處理,brightKeyword就是變高亮的方法
this.resultList.map((item) => { //遍歷
item.name = this.brightKeyword(item.name)
item.date = this.brightKeyword(item.date)
item.adress = this.brightKeyword(item.adress)
item.type = this.brightKeyword(item.type)
}) //到這里search方法結(jié)束
---------------------------------
brightKeyword(val) {
let keyword = this.keyword //獲取輸入框輸入的內(nèi)容
if (val.indexOf(keyword) !== -1) { //判斷這個(gè)字段中是否包含keyword
//如果包含的話,就把這個(gè)字段中的那一部分進(jìn)行替換成html字符
return val.replace(keyword, `<font color='#42cccc'>${keyword}</font>`)
} else {
return val
}
}
將每一個(gè)字段都進(jìn)行一次處理,來(lái)實(shí)現(xiàn)關(guān)鍵字高亮的效果
但是,如果按照超人鴨上面的代碼去實(shí)現(xiàn)的話,會(huì)出現(xiàn)一個(gè)問(wèn)題,也是本地用模擬數(shù)據(jù)開(kāi)發(fā)才會(huì)出現(xiàn)的問(wèn)題,下面給大家模擬一下:
當(dāng)我輸入 '設(shè)備' 兩個(gè)字:
看起來(lái)沒(méi)問(wèn)題,當(dāng)我再輸入 '深圳' 兩個(gè)字時(shí):
發(fā)現(xiàn)沒(méi)有,數(shù)據(jù)是對(duì)的,但高亮是錯(cuò)的,上一次搜索的關(guān)鍵字高亮在第二次搜索還是高亮的。
為什么會(huì)出現(xiàn)這種情況呢?
這里要說(shuō)到一個(gè)概念,對(duì)象的淺拷貝,看看這種情況:
let obj1 = { foo: 1 }
let obj2 = obj1
obj2.foo = 3
console.log(obj1.foo) //打印出來(lái)之后是3
這就是對(duì)象的淺拷貝,數(shù)組也會(huì)出現(xiàn)這種情況。這是因?yàn)閖s中對(duì)象和數(shù)組是引用數(shù)據(jù)類型,存儲(chǔ)方式和基本數(shù)據(jù)類型(字符串、數(shù)字等)不一樣,直接賦值給另一個(gè)對(duì)象的話,另一個(gè)對(duì)象改變屬性自己的屬性也會(huì)跟著改變。(如果這塊不明白的話,可以先知道這么一回事就好,等超人鴨再出一篇詳細(xì)介紹js存儲(chǔ)對(duì)象的方式)
回到我們的例子中,為什么例子會(huì)出現(xiàn)對(duì)象的淺拷貝呢?
我們的原始數(shù)據(jù),也就是從json文件中的獲取到的數(shù)據(jù),賦值給我們組件中data的deviceList,而這個(gè)過(guò)程,只執(zhí)行了一次,也就是說(shuō)deviceList這個(gè)原始數(shù)組在之后的執(zhí)行中都沒(méi)有重新去獲取。然后在點(diǎn)擊搜索后遍歷匹配的過(guò)程中,resultList去push deviceList中的某一項(xiàng),而deviceList的每一項(xiàng)都是一個(gè)對(duì)象,而這些過(guò)程都是對(duì)象的淺拷貝,所以在之后的高亮處理中,改變了某一個(gè)對(duì)象里面的屬性,deviceList也會(huì)跟著受影響。
如何解決
如果在真正與后端配合開(kāi)發(fā)的過(guò)程中,每次點(diǎn)擊搜索,都會(huì)再去調(diào)用一次接口,重新獲取數(shù)據(jù)。那我們也模擬這個(gè)過(guò)程,每次點(diǎn)擊搜索時(shí)都重新去獲取數(shù)據(jù),保證每次原始數(shù)據(jù)deviceList都是新的。這個(gè)實(shí)現(xiàn)也非常簡(jiǎn)單,不要在created中用axios調(diào)用json文件,而是search方法一開(kāi)始調(diào)用:
search() {
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
//將json文件中的數(shù)據(jù)賦值給data里面的deviceList
this.deviceList = res.data.data.deviceList
})
....
}
看起來(lái)似乎沒(méi)問(wèn)題了?
熟悉axios插件的伙伴都知道,axios是用promise封裝的,而promise是用來(lái)處理異步操作的。異步就是不能確定它什么時(shí)候能執(zhí)行完的操作,所以這樣寫(xiě)的話,不能確定它是什么時(shí)候執(zhí)行完成,所以不能確定deviceList什么時(shí)候才能獲取到數(shù)據(jù),當(dāng)方法繼續(xù)往后走而deviceList 沒(méi)有數(shù)據(jù)就會(huì)報(bào)錯(cuò)。
正確的寫(xiě)法應(yīng)該在獲取數(shù)據(jù)成功后,才去執(zhí)行下面的操作,也就是獲取數(shù)據(jù)成功后的回調(diào)函數(shù)里,promise支持這樣的寫(xiě)法:
search() {
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
//將json文件中的數(shù)據(jù)賦值給data里面的deviceList
this.deviceList = res.data.data.deviceList
}).then(() => {
//把上面search寫(xiě)的代碼放在這里面
})
}
到這里就完成了,確保了每次都是獲取新的數(shù)據(jù)而且在獲取數(shù)據(jù)成功后才執(zhí)行下一步操作。實(shí)現(xiàn)了用模擬數(shù)據(jù)進(jìn)行搜索,關(guān)鍵字高亮的效果。下面超人鴨放上完整代碼,不足的地方請(qǐng)大家多多指教。
<template>
<div class="bright-index">
<div class="search-box">
<input type="text" v-model="keyword" class="input" placeholder="請(qǐng)輸入搜索內(nèi)容, 提示:搜索設(shè)備">
<button class="btn" @click="search">搜索</button>
</div>
<div class="content-box">
<div class="content-card" v-for="(item ,index) in resultList" :key="index">
設(shè)備名稱:<span v-html="item.name" style="color:#000;"></span>
<span>日期:<span v-html="item.date"></span></span>
<span>地址:<span v-html="item.adress"></span></span>
<span>類型:<span v-html="item.type"></span></span>
</div>
<h2 v-if="isShowTip">沒(méi)有搜索到匹配結(jié)果</h2>
</div>
</div>
</template>
<script>
export default {
data () {
return {
keyword: '',
deviceList: [],
resultList: [],
isShowTip: false
}
},
methods: {
search () {
this.isShowTip = false
if (this.keyword == '') {
this.$message.warning('請(qǐng)輸入搜索內(nèi)容')
return
}
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
this.deviceList = res.data.data.deviceList
}).then(() => {
this.resultList = []
this.deviceList.forEach((item) => {
if (item.name.indexOf(this.keyword) > -1 ||
item.date.indexOf(this.keyword) > -1 ||
item.adress.indexOf(this.keyword) > -1 ||
item.type.indexOf(this.keyword) > -1) {
this.resultList.push(item)
}
})
if (this.resultList.length == 0) {
this.isShowTip = true
}
this.resultList.map((item) => {
item.name = this.brightKeyword(item.name)
item.date = this.brightKeyword(item.date)
item.adress = this.brightKeyword(item.adress)
item.type = this.brightKeyword(item.type)
})
})
},
brightKeyword (val) {
let keyword = this.keyword
if (val.indexOf(keyword) !== -1) {
return val.replace(keyword, `<font color='#42cccc'>${keyword}</font>`)
} else {
return val
}
}
}
}
</script>
項(xiàng)目中的html代碼
<div class="search">
<div class="search_box">
<el-input
type="text"
class="input-search"
placeholder="請(qǐng)輸入搜索內(nèi)容"
v-model.trim="searchKey "
@input="searchList"
ref="searchInput"
autocomplete="off"
autocapitalize="off"
autocorrect="off"
autofocus
></el-input>
<el-button type="primary" class="search_btn" @click="searchList">搜索</el-button>
</div>
<div>
<ul>
<li v-for="item in resultList"
:key="item.announceId"
>
<span v-html="item.contentKey1" @click="searchDetail(item.announceId)"></span>
<br/>
<span v-html="item.contentKey2" @click="searchDetail(item.announceId)"></span>
<br/>
<span v-html="item.contentKey3" @click="searchDetail(item.announceId)"></span>
<br/>
<span v-html="item.contentKey4" @click="searchDetail(item.announceId)"></span>
<br/>
<span v-html="item.contentKey5" @click="searchDetail(item.announceId)"></span>
<br/>
<span v-html="item.announceName" @click="searchDetail(item.announceId)"></span>
</li>
</ul>
<h2 v-if="isShowTip">沒(méi)有搜索到匹配結(jié)果</h2>
</div>
<div class="page-footer">
<el-pagination
v-if="total>0"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNo"
:page-sizes="[10,15, 30, 45, 60]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</div>
項(xiàng)目中的代碼如下js
export default {
data () {
return {
searchKey: '',
pageNo: 1,
pageSize: 10,
total: 0,
deviceList: [], // 調(diào)用接口獲取的全部數(shù)據(jù)
resultList: [], // 真正展示的數(shù)據(jù),也就是篩選后的數(shù)據(jù)
isShowTip: false // 當(dāng)搜索不到數(shù)據(jù)時(shí)為true
}
},
created () {
if (this.$route.query.ytoSearchKey) {
this.searchKey = this.$route.query.ytoSearchKey
}
this.searchList()
},
methods: {
searchList () {
this.isShowTip = false
if (this.searchKey === '') {
return
}
if (this.searchKey && this.searchKey.length > 0) {
return new Promise((resolve, reject) => {
const params = {
searchKey: this.searchKey,
pageNo: this.pageNo,
limit: this.pageSize
}
announceList(params).then(res => {
this.pageNo = res.data.data.pageNo
this.deviceList = res.data.data.resultData
this.total = res.data.data.total
}).then(() => {
this.resultList = [] // 每次搜索對(duì)結(jié)果數(shù)組做初始化
this.deviceList.forEach(item => { // 把初始數(shù)據(jù)進(jìn)行遍歷
// 下面是把初始數(shù)據(jù)中的每一條數(shù)據(jù)的四個(gè)字段分別去和輸入的內(nèi)容進(jìn)行匹配,如果某一字段中包含輸入的內(nèi)容,就往resultList中加
if (item.announceName.indexOf(this.searchKey) > -1 ||
item.contentKey1.indexOf(this.searchKey) > -1 ||
item.contentKey2.indexOf(this.searchKey) > -1 ||
item.contentKey3.indexOf(this.searchKey) > -1 ||
item.contentKey4.indexOf(this.searchKey) > -1 ||
item.contentKey5.indexOf(this.searchKey) > -1) {
this.resultList.push(item)
}
})
if (this.resultList.length === 0) { // 如果沒(méi)有匹配結(jié)果,就顯示提示信息
this.isShowTip = true
}
// 將得到的每一條數(shù)據(jù)中的每一個(gè)字段進(jìn)行處理,brightKeyword就是變高亮的方法
this.resultList.map(item => {
item.announceName = this.brightKeyword(item.announceName)
item.contentKey1 = this.brightKeyword(item.contentKey1)
item.contentKey2 = this.brightKeyword(item.contentKey2)
item.contentKey3 = this.brightKeyword(item.contentKey3)
item.contentKey4 = this.brightKeyword(item.contentKey4)
item.contentKey5 = this.brightKeyword(item.contentKey5)
})
}).catch((err) => {
reject(err)
})
})
}
},
brightKeyword (val) {
const keyword = this.searchKey // 獲取輸入框輸入的內(nèi)容
if (val.indexOf(keyword) !== -1) { // 判斷這個(gè)字段中是否包含keyword
return val.replace(keyword, `<font color="red">${keyword}</font>`)
} else {
return val
}
},
// 跳轉(zhuǎn)到詳情頁(yè)面
searchDetail (announceId) {
this.$router.push({
path: 'searchDetail',
query: {
announceId: announceId
}
})
},
// 改變每頁(yè)顯示數(shù)量
handleSizeChange (val) {
this.pageSize = val
this.searchList()
},
// 改變當(dāng)前頁(yè)
handleCurrentChange (val) {
this.pageNo = val
this.searchList()
}
}
}