本文僅講解如何使用 Vue 創(chuàng)建一個(gè)實(shí)現(xiàn)分頁(yè)、搜索、批量操作的列表組件,所以只提供此小組件的代碼及說(shuō)明,不提供其之外的代碼或配置
github 地址: https://github.com/jothy1023/vue-component
使用到的技術(shù)或框架
- Vue.js (2.0) 一套基于 MVVM 的 progressive framework,可以幫助我們迅速搭建用戶界面
- semantic UI 一套漂亮的 UI 框架
- localStorage HTML5 的本地存儲(chǔ) API 之一(另一個(gè)為 sessionStorage),顧名思義, localStorage 保存在當(dāng)前設(shè)備內(nèi)存中,除非主動(dòng)刪除,否則一直存在
準(zhǔn)備工作
- 安裝 Vue 2.0 (rc.6)
- 引入 semantic.js 文件
- 創(chuàng)建 Audit.vue 文件
具體過(guò)程
一個(gè)簡(jiǎn)單的 Vue 組件由三部分組成:html 模板 、javascript 腳本 以及 css 樣式。下面我們一一來(lái)看。
效果圖如下:

用到的數(shù)據(jù)結(jié)構(gòu)是一個(gè)簡(jiǎn)單的 user 對(duì)象數(shù)組,對(duì)象格式為
user {
id: int
name: String,
audit: int
}
其中 audit 用來(lái)標(biāo)記是否審核成功,共 4 個(gè)狀態(tài)從 0 到 3,分別為 未審核、正在審核、已審核、審核不通過(guò)。
html 模板
由一對(duì)<template></template>
標(biāo)簽作為唯一父標(biāo)簽包裹住模板內(nèi)容,模板內(nèi)分為 filter (過(guò)濾)、container (主體)兩部分。
container
container 主體是一個(gè) table 表格,表格內(nèi)容 tbody 部分使用 Vue 的 v-for 指令,基于 users 數(shù)組渲染出列表。主要代碼如下
<tr v-for="user in filteredUsers">
<td class="collapsing">
<div class="ui toggle slider checkbox" v-if="aKey!==''">
<input type="checkbox" :value="user" v-model="selectedUsers"> <label></label>
</div>
</td>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>
<i class="radio black icon" v-if="user.audit==0"></i>
<i class="minus blue icon" v-if="user.audit==1"></i>
<i class="checkmark green icon" v-if="user.audit==2"></i>
<i class="remove red icon" v-if="user.audit==3"></i>
</td>
<td>
<ui-button css="primary" v-if="user.audit==0" @click="user.audit=1">審核</ui-button>
<ui-button css="green" v-if="user.audit==1" @click="user.audit=2">通過(guò)審核</ui-button>
<ui-button css="red" v-if="user.audit==1" @click="user.audit=3">不通過(guò)審核</ui-button>
<ui-button css="grey" v-if="user.audit==2 || user.audit==3" @click="user.audit=1">再次審核</ui-button>
</td>
</tr>
其中,filteredUsers
為計(jì)算屬性 computed 的一個(gè) getter,用于從模板中分理出較復(fù)雜的邏輯,此處返回過(guò)濾后的 users 。
下來(lái)有一個(gè) type 為 checkbox 的 input,當(dāng)按照某一審核要求過(guò)濾時(shí)顯示,其 v-model 指向 selectedUsers 數(shù)組,用來(lái)保存批量操作中選中的 user ,之后可以直接對(duì)此數(shù)組進(jìn)行操作,改動(dòng)會(huì)同步到 users 。
filter
filter 部分由一個(gè) input 輸入框以及一個(gè) select 選擇框組成,分別可以通過(guò)用戶名和審核狀態(tài)過(guò)濾。具體代碼如下:
<div class="filter">
<ui-input css="icon">
<input type="text" v-model="fKey" placeholder="輸入姓名搜索.." />
<i class="search icon"></i>
</ui-input>
<!-- dropdown -->
<ui-dropdown :setting="{allowAdditions: true}" css="selection">
<input name="aKey" type="hidden" v-model.lazy="aKey">
<div class="default text">Select a id</div>
<i class="dropdown icon"></i>
<div class="menu">
<div class="item" data-value="">所有</div>
<div class="item" data-value="0">未審核</div>
<div class="item" data-value="1">正在審核</div>
<div class="item" data-value="2">已審核</div>
<div class="item" data-value="3">審核不通過(guò)</div>
</div>
</ui-dropdown>
</div>
select 使用 semanticUI 的 dropdown 實(shí)現(xiàn),將其 class 設(shè)置為 selection。 dropdown 中包含一個(gè) input 標(biāo)簽,此標(biāo)簽屬性為 <input name="aKey" type="hidden" v-model.lazy="aKey">
,其中,name 對(duì)應(yīng) v-model 的屬性名,負(fù)責(zé)將值從下面的 .item 中的 data-value 取回并傳遞。
此處有一個(gè)地方需要引起注意!!(敲黑板
由于此處的 input 標(biāo)簽其實(shí)算是 select 組件的,改動(dòng)時(shí)默認(rèn)行為是 change 而非 input ,也沒(méi)有輸入框,因此就算添加了 v-model 也無(wú)法觸發(fā)。但是添加 @change 卻可以正常觸發(fā),百思不得姐,經(jīng)過(guò)千難萬(wàn)苦跋山涉水之后,終于發(fā)現(xiàn)!!在 Vue 的源碼中有這么一斷代碼:

我們只關(guān)注首末行,噢.. 大意就是,判斷有無(wú) lazy 屬性(或者在 IE 環(huán)境下并且類型為 range ),true 時(shí)監(jiān)聽(tīng) change 事件,否則監(jiān)聽(tīng) input 事件。真相大白啦,機(jī)智地給 v-model 加上個(gè)小尾巴 .lazy ,hot-loader 馬上刷新,一試果然成功了!贊 b( ̄▽ ̄)d
P.S: 后來(lái)又翻了翻 Vue 的教程,發(fā)現(xiàn)尤大大其實(shí)在教程中已經(jīng)說(shuō)明過(guò)了,惹..附圖

并且.. 1.0 版本與 2.0 版本也有點(diǎn)細(xì)微差別,1.0 是直接寫在 input 標(biāo)簽的末尾,而 2.0 必須緊跟 v-model 之后。
當(dāng)二者的 v-model 即 fKey 或 aKey 發(fā)生改變時(shí),觸發(fā) computed 屬性重新計(jì)算 filteredUsers ,重新渲染 users 列表,還有個(gè) paginate 為分頁(yè)方法。
filteredUsers () {
let fUsers = this.queryFilter('name', this.fKey, this.users)
fUsers = this.queryFilter('audit', this.aKey, fUsers)
return this.paginate(fUsers)
}
javascript 腳本
初始數(shù)據(jù)
定義了 initialUsers 數(shù)組,存進(jìn)去若干個(gè) user 對(duì)象,定義 userStorage 對(duì)象,存放 fetch 與 save 方法,分別調(diào)用 localStorage 的 getItem 與 setItem 方法向 localStorage 獲取及存儲(chǔ)數(shù)據(jù)。
var userStorage
function init() {
var STORAGE_KEY = 'users';
userStorage = {
fetch: function () {
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || initialUsers
},
save: function (users) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(users))
}
};
}
init(window);
接著是 Vue 實(shí)例的各屬性方法。
data () {
return {
users: userStorage.fetch(), // users 數(shù)據(jù)
selectedUsers: [], // 保存選中的 users 數(shù)組
fKey: '', // 過(guò)濾 name 的關(guān)鍵字
name: '', // 上一次過(guò)濾的 name 關(guān)鍵字,初始化為''
aKey: '', // select audit 的關(guān)鍵字
audit: '', // 上一次過(guò)濾的 audit關(guān)鍵字,初始化為''
limit: 10, // 每頁(yè)顯示行數(shù)
totalPage: 0, // 總頁(yè)數(shù)
currentPage: 0, // 當(dāng)前頁(yè)
jPage: 1 // 跳轉(zhuǎn)到某頁(yè)
}
}
此處的 name 與 audit 看起來(lái)有些多余,其實(shí)它是有很重要的作用的,它用于保存上一次過(guò)濾的 key與 本次的對(duì)比。
過(guò)濾方法 queryFilter
queryFilter (prop, key, arr) {
// none query string, return arr
if (!key) {
return arr
}
// filtering
arr = arr.filter((user) => {
if (user[prop].toString().indexOf(key) !== -1) {
return true
}
})
// if it's a new filter query, refilter and turn to page one
if (key !== this[prop]) {
this.currentPage = 0
// save last filter query
this[prop] = key
}
return arr
}
這個(gè)設(shè)置應(yīng)用情境是:未設(shè)置這個(gè)變量時(shí),若用戶在一次搜索之后進(jìn)行翻頁(yè),假設(shè)停留在了第 n 頁(yè),這時(shí)候再重新搜索,頁(yè)面會(huì)停留在本次搜索的結(jié)果的第 n 頁(yè),非常不方便。因此檢測(cè)如果重新搜索,則充值 currentPage 屬性,記錄新的 key 。
分頁(yè)方法 paginate
paginate (arr) {
this.totalPage = Math.ceil(arr.length / this.limit)
let page = this.currentPage
let curLimit = this.limit
// 返回指定條數(shù)的數(shù)組
arr = arr.slice(curLimit * page, curLimit * (page + 1))
return arr
}
此處用到了數(shù)組的 slice 方法進(jìn)行淺復(fù)制。
翻頁(yè)方法 turnPage
接受一個(gè)數(shù)組,1 為向后翻頁(yè),-1 為向前翻頁(yè)
- HTML
<div class="jtp">
<span>跳轉(zhuǎn)到第 </span>
<ui-input css="icon">
<input type="text" v-model="jPage" @keyup.enter="jumpToPage">
</ui-input><span> 頁(yè)</span>
</div>
- Javascript
turnPage (num) {
if (num === 1) {
if (this.currentPage === this.totalPage - 1) {
return
} else {
this.currentPage++
}
} else {
if (this.currentPage === 0) {
return
} else {
this.currentPage--
}
}
}
單個(gè)操作修改 audit
為單獨(dú)操作的 button 添加 @click 事件,直接修改
批量操作方法
由于 selectedUsers 數(shù)組保存了被選中 users 的數(shù)據(jù),因此只要調(diào)用 setAuditId 方法,傳入 selectedUsers 以及要設(shè)置的 audit ,遍歷 selectedUsers 進(jìn)行設(shè)置即可。代碼舉例如下:
- HTML
<ui-button css="green" v-if="aKey==='1'" @click="pass">通過(guò)審核</ui-button>
<ui-button css="red" v-if="aKey==='1'" @click="reject">不通過(guò)審核</ui-button>
<ui-button css="small" v-if="aKey==='0'" @click="approveSel">審核</ui-button>
<ui-button css="small" v-if="aKey==='0'" @click="approveAll">全部審核</ui-button>
<ui-button css="small" v-if="aKey==='2' || aKey==='3'" @click="approveSel">再次審核</ui-button>
<ui-button css="small" v-if="aKey==='2' || aKey==='3'" @click="approveAll">全部再次審核</ui-button>
- Javascript
approveSel () {
this.setAuditId(this.selectedUsers, 1)
},
approveAll () {
this.setAuditId(this.filteredUsers, 1)
},
pass () {
this.setAuditId(this.selectedUsers, 2)
},
reject () {
this.setAuditId(this.selectedUsers, 3)
},
setAuditId (users, aId) {
users.forEach((user) => {
user.audit = aId
})
}
此外,還添加了 watch 對(duì) users 進(jìn)行檢測(cè),每當(dāng)其改變(例如 修改了 audit)時(shí),將新的 users 存入 localStorage 中。
// watch
watch: {
users: {
handler () {
userStorage.save(this.users)
},
deep: true
}
}
CSS
這部分其實(shí)沒(méi)什么好說(shuō)的,基本上都用了 semanticUI 的 css ,只在最后的跳轉(zhuǎn)頁(yè)碼輸入框的地方重設(shè)了 input 的大小而已。
至此,整個(gè)組件就完成啦~~撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。
初試 Vue ,對(duì)一些 api 的使用可能有不準(zhǔn)確的,有寫得不好的地方歡迎指正。