很多伙伴在自己用vue學(xué)習(xí)開發(fā)的過程中耸峭,不像真正的工作開發(fā)桩蓉,有后端支持,可以從后端那里調(diào)接口拿到真實的數(shù)據(jù)進行展示劳闹。但為了模擬這個過程院究,我們經(jīng)常使用自己在本地編寫模擬的數(shù)據(jù)進行使用,然后通過發(fā)送ajax請求得到我們本地的數(shù)據(jù)本涕。
這次业汰,超人鴨就使用本地編寫的json數(shù)據(jù),通過axios發(fā)送ajax請求菩颖,來獲取本地數(shù)據(jù)样漆,然后實現(xiàn)搜索功能,并在這個功能上加以拓展晦闰,實現(xiàn)搜索關(guān)鍵字的高亮放祟。
我們一步一步來鳍怨,首先使用axios
axios是現(xiàn)在vue官方推薦使用一個插件,用來發(fā)送http請求跪妥,如果只是簡單的調(diào)用接口鞋喇,那用法也非常簡單。
//通過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ǔ)用法骗奖,下面也會看到具體的使用
然后編寫我們的json數(shù)據(jù)
因為是本地的模擬數(shù)據(jù)确徙,所以推薦放在項目中static文件夾里面,下面是超人鴨本次編寫的json文件device.json,大家可以不用關(guān)心數(shù)據(jù)本身有什么含義执桌,看一下json數(shù)據(jù)的編寫格式
{
"data":{
"deviceList":[
{"name":"設(shè)備一","date":"2019-03-24","adress":"深圳","type":"電動"},
{"name":"設(shè)備二","date":"2019-03-24","adress":"上海","type":"汽油"},
{"name":"設(shè)備三","date":"2019-03-24","adress":"北京","type":"電動"},
{"name":"設(shè)備四","date":"2019-03-24","adress":"廣州","type":"汽油"}
......
]
}
}
這就寫好一個json文件鄙皇,我們來看看在組件中如何通過axios調(diào)用到這個json文件:
//在組件的created鉤子函數(shù)中調(diào)用
created(){
//寫上文件的路徑
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
console.log(res)
})
}
看看打印出來的結(jié)果:
可以看到已經(jīng)調(diào)用成功,我們要的數(shù)據(jù)在res.data.data.deviceList里面
回到這次的主題仰挣,實現(xiàn)搜索且關(guān)鍵字高亮
先看看最終效果:
有一個輸入框伴逸,當(dāng)我輸入 '設(shè)備' 兩個字后:
當(dāng)我輸入 '廣州' 兩個字后:
可以看到完成了超人鴨所描述的效果,下面我將通過代碼和代碼中的注釋來介紹功能的實現(xiàn),樣式部分就不寫出來了膘壶,沒什么影響错蝴。
現(xiàn)在簡單的html結(jié)構(gòu)和data里面的變量:
<template>
<div class="bright-index">
<div class="search-box">
<input type="text" v-model="keyword" class="input" placeholder="請輸入搜索內(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">沒有搜索到匹配結(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ù)時為true
}
}
</script>
我們先實現(xiàn)搜索功能颓芭,高亮下一步再實現(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
})
}
//然后我們給按鈕綁定了一個點擊事件search,在這個事件中做處理
methods:{
search() {
if (this.keyword == '') { //如果沒有輸入內(nèi)容亡问,不讓往下執(zhí)行
return
}
this.resultList = [] //每次搜索對結(jié)果數(shù)組做初始化
this.deviceList.forEach((item) => { //把初始數(shù)據(jù)進行遍歷
/**
下面是把初始數(shù)據(jù)中的每一條數(shù)據(jù)的四個字段分別去和輸入的內(nèi)容進行匹配官紫,
如果某一字段中包含輸入的內(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) { //如果沒有匹配結(jié)果州藕,就顯示提示信息
this.isShowTip = true
}
}
}
因為是本地的模擬數(shù)據(jù)束世,所以采用最原始的方式,去遍歷判斷篩選床玻。
這樣就完成了搜索功能毁涉,接下來超人鴨來實現(xiàn)關(guān)鍵字高亮
注意到上面我放的html結(jié)構(gòu)嗎,里面展示數(shù)據(jù)用的不是平常的插值表達式锈死,也就是兩個大括號贫堰,而是用v-html,可以用來展示html代碼的而不是簡單的字符串馅精。
說到這严嗜,那實現(xiàn)高亮的思路就是將關(guān)鍵字也就是你輸入的內(nèi)容,在每一條數(shù)據(jù)中替換成html字符串
接上上面的代碼洲敢,還是在search方法里面
//將得到的每一條數(shù)據(jù)中的每一個字段進行處理,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) { //判斷這個字段中是否包含keyword
//如果包含的話漫玄,就把這個字段中的那一部分進行替換成html字符
return val.replace(keyword, `<font color='#42cccc'>${keyword}</font>`)
} else {
return val
}
}
將每一個字段都進行一次處理,來實現(xiàn)關(guān)鍵字高亮的效果
但是,如果按照超人鴨上面的代碼去實現(xiàn)的話睦优,會出現(xiàn)一個問題渗常,也是本地用模擬數(shù)據(jù)開發(fā)才會出現(xiàn)的問題,下面給大家模擬一下:
當(dāng)我輸入 '設(shè)備' 兩個字:
看起來沒問題汗盘,當(dāng)我再輸入 '深圳' 兩個字時:
發(fā)現(xiàn)沒有皱碘,數(shù)據(jù)是對的,但高亮是錯的隐孽,上一次搜索的關(guān)鍵字高亮在第二次搜索還是高亮的癌椿。
為什么會出現(xiàn)這種情況呢?
這里要說到一個概念菱阵,對象的淺拷貝踢俄,看看這種情況:
let obj1 = { foo: 1 }
let obj2 = obj1
obj2.foo = 3
console.log(obj1.foo) //打印出來之后是3
這就是對象的淺拷貝,數(shù)組也會出現(xiàn)這種情況晴及。這是因為js中對象和數(shù)組是引用數(shù)據(jù)類型都办,存儲方式和基本數(shù)據(jù)類型(字符串、數(shù)字等)不一樣虑稼,直接賦值給另一個對象的話琳钉,另一個對象改變屬性自己的屬性也會跟著改變。(如果這塊不明白的話蛛倦,可以先知道這么一回事就好歌懒,等超人鴨再出一篇詳細介紹js存儲對象的方式)
回到我們的例子中,為什么例子會出現(xiàn)對象的淺拷貝呢?
我們的原始數(shù)據(jù),也就是從json文件中的獲取到的數(shù)據(jù)溯壶,賦值給我們組件中data的deviceList歼培,而這個過程,只執(zhí)行了一次茸塞,也就是說deviceList這個原始數(shù)組在之后的執(zhí)行中都沒有重新去獲取。然后在點擊搜索后遍歷匹配的過程中查剖,resultList去push deviceList中的某一項钾虐,而deviceList的每一項都是一個對象,而這些過程都是對象的淺拷貝笋庄,所以在之后的高亮處理中效扫,改變了某一個對象里面的屬性,deviceList也會跟著受影響直砂。
如何解決
如果在真正與后端配合開發(fā)的過程中菌仁,每次點擊搜索,都會再去調(diào)用一次接口静暂,重新獲取數(shù)據(jù)济丘。那我們也模擬這個過程,每次點擊搜索時都重新去獲取數(shù)據(jù),保證每次原始數(shù)據(jù)deviceList都是新的摹迷。這個實現(xiàn)也非常簡單疟赊,不要在created中用axios調(diào)用json文件,而是search方法一開始調(diào)用:
search() {
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
//將json文件中的數(shù)據(jù)賦值給data里面的deviceList
this.deviceList = res.data.data.deviceList
})
....
}
看起來似乎沒問題了峡碉?
熟悉axios插件的伙伴都知道近哟,axios是用promise封裝的,而promise是用來處理異步操作的鲫寄。異步就是不能確定它什么時候能執(zhí)行完的操作吉执,所以這樣寫的話,不能確定它是什么時候執(zhí)行完成地来,所以不能確定deviceList什么時候才能獲取到數(shù)據(jù)戳玫,當(dāng)方法繼續(xù)往后走而deviceList 沒有數(shù)據(jù)就會報錯。
正確的寫法應(yīng)該在獲取數(shù)據(jù)成功后靠抑,才去執(zhí)行下面的操作量九,也就是獲取數(shù)據(jù)成功后的回調(diào)函數(shù)里,promise支持這樣的寫法:
search() {
this.$axios.get('../../../static/mock/device.json')
.then((res) => {
//將json文件中的數(shù)據(jù)賦值給data里面的deviceList
this.deviceList = res.data.data.deviceList
}).then(() => {
//把上面search寫的代碼放在這里面
})
}
到這里就完成了颂碧,確保了每次都是獲取新的數(shù)據(jù)而且在獲取數(shù)據(jù)成功后才執(zhí)行下一步操作荠列。實現(xiàn)了用模擬數(shù)據(jù)進行搜索,關(guān)鍵字高亮的效果载城。下面超人鴨放上完整代碼肌似,不足的地方請大家多多指教。
<template>
<div class="bright-index">
<div class="search-box">
<input type="text" v-model="keyword" class="input" placeholder="請輸入搜索內(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">沒有搜索到匹配結(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('請輸入搜索內(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>
項目中的html代碼
<div class="search">
<div class="search_box">
<el-input
type="text"
class="input-search"
placeholder="請輸入搜索內(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">沒有搜索到匹配結(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>
項目中的代碼如下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ù)時為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 = [] // 每次搜索對結(jié)果數(shù)組做初始化
this.deviceList.forEach(item => { // 把初始數(shù)據(jù)進行遍歷
// 下面是把初始數(shù)據(jù)中的每一條數(shù)據(jù)的四個字段分別去和輸入的內(nèi)容進行匹配川队,如果某一字段中包含輸入的內(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) { // 如果沒有匹配結(jié)果睬澡,就顯示提示信息
this.isShowTip = true
}
// 將得到的每一條數(shù)據(jù)中的每一個字段進行處理,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) { // 判斷這個字段中是否包含keyword
return val.replace(keyword, `<font color="red">${keyword}</font>`)
} else {
return val
}
},
// 跳轉(zhuǎn)到詳情頁面
searchDetail (announceId) {
this.$router.push({
path: 'searchDetail',
query: {
announceId: announceId
}
})
},
// 改變每頁顯示數(shù)量
handleSizeChange (val) {
this.pageSize = val
this.searchList()
},
// 改變當(dāng)前頁
handleCurrentChange (val) {
this.pageNo = val
this.searchList()
}
}
}