描述:
之前實現(xiàn)了自定義分頁組件,但是由于現(xiàn)實業(yè)務需求(需要將過濾項表單、分頁的offset和limit傳遞到路由中,即a?xxx=xxx&xxx=xxx)。因此對分頁組件進行了重新的設計。
分頁組件的重新設計
之前的分頁組件主要是監(jiān)聽每頁顯示數(shù)量limit和偏移量offset的變化來觸發(fā)事件,進行回調(diào),在回調(diào)處發(fā)起網(wǎng)絡請求。
由于上述需求,在新的分頁組件中主要監(jiān)聽了頁面改變的監(jiān)聽事件,并通過頁面改變監(jiān)聽,觸發(fā)編程式導航,從而改變頁面路由,通過對頁面路由的更新來發(fā)起網(wǎng)絡請求,從而實現(xiàn)目標。
<!--
by nickctang
自定義分頁組件
傳入?yún)?shù): pagination
pagination包含偏移量、每頁顯示數(shù)量、和總數(shù)
在進行分頁切換是,觸發(fā)編程式導航,導航到對應位置,并進行服務器請求,請求完畢后,將pagination回傳回來
-->
<template>
<div class="row q-my-md fs-14" >
<span class="self-center col-auto q-pb-sm q-mt-md q-mr-md">共{{paginationModel.count}}條,第{{ page }} /{{max}}頁</span>
<q-select
class="col-auto self-center q-mr-md q-mt-md"
style="width: 90px"
v-model="paginationModel.limit"
:options="perPageNumOptions"
/>
<q-pagination class="col-auto q-mt-md" v-model="page" :max="max" size="sm" boundary-links boundary-numbers :max-pages="10" direction-links ellipses @input="onPageChange"/>
</div>
</template>
<script>
export default {
name: 'CPagination',
data () {
return {
perPageNumOptions: [
{
label: '每頁12條',
value: 12
},
{
label: '每頁24條',
value: 24
},
{
label: '每頁48條',
value: 48
},
{
label: '每頁96條',
value: 96
}
]
}
},
computed: {
page: {
get: function () {
if (this.paginationModel.count === 0) {
return 0
}
// 偏移量/每頁顯示數(shù)量 向下取整 + 1
return Math.floor(this.paginationModel.offset / this.paginationModel.limit) + 1
},
set: function (val) {
// 頁碼改變動態(tài)的更新偏移量
this.paginationModel.offset = (val - 1) * this.paginationModel.limit
}
},
max: function () {
// 最大頁數(shù),總數(shù)/每頁顯示數(shù)量,向上取整
return Math.ceil(this.paginationModel.count / this.paginationModel.limit)
},
paginationModel: {// pagination的雙向綁定,可令外界的pagination來改變page
get: function () {
return this.pagination
},
set: function (val) {
this.$emit('paginationChange', val)
}
}
},
model: {
prop: 'pagination',
event: 'paginationChange'
},
methods: {
onPageChange: function (val) {
console.log('pagechange ' + val)
const query = JSON.parse(JSON.stringify(this.$route.query)) // 深度復制
query.limit = this.paginationModel.limit
query.offset = this.paginationModel.offset
this.$router.push(
{
path: this.$route.path,
query: query
})
}
},
props: {
pagination: {
offset: 0,
limit: 12,
count: 0
}
}
}
</script>
在父組件使用該分頁組件時,只需要傳入pagination即可,pagination是一個JSON對象,包含offset、limit、count。
在頁面進行網(wǎng)絡請求得到結(jié)果后,只需要改變pagination的內(nèi)容,即可動態(tài)的更新分頁組件。
分頁組件 + table + 編程式導航的結(jié)合使用
描述:
在父組件使用Table表格和分頁組件的過程中,一般還會存在過濾項表單,來對數(shù)據(jù)進行過濾請求,這些過濾項和分頁的offset、limit都需要當作路由的query參數(shù)。
實際上,該query參數(shù)模樣,相當于axios進行get請求時的params。因此將表單對應組件的v-model和請求所需的param定義成相同的名稱。
期間遇到的問題:
基本上還是多次請求的問題,在編程式導航過程中,如果沒有對應的更新分頁組件,由于count的變化,容易讓分頁組件響應編程式導航,而且由于沒有更新offset、limit,從而導致編程式導航成功響應,進行了二次網(wǎng)絡請求。
最終的解決:
對原來的分頁組件進行的更新。在每次獲取網(wǎng)絡請求結(jié)束后,動態(tài)的改變pagination中的count、limit、offset,從而基本觸發(fā)了編程式導航,但是由于路由的query沒有改變,而不會響應beforeRouteUpdate,也就不會再次響應網(wǎng)絡請求。
實現(xiàn)過程
- 首先,定義table。
// html
<q-table
no-data-label="沒有更多數(shù)據(jù)"
:data="tableData"
:columns="columns"
:pagination.sync="paginationControl"
:loading="tableLoading"
name="id"
color="secondary" >
...
//vue
tableLoading: false, // 表格加載狀態(tài)
paginationControl: { rowsPerPage: 0 }, // 顯示全部
tableData: [],
columns: [{
name: 'severity',
required: true,
align: 'left',
label: '問題級別',
field: 'severity'
},...
],
- 其次,覆蓋Table默認的分頁,采用新的分頁
// html
<template slot="bottom" slot-scope="props">
<div class="col"></div>
<c-pagination class="no-margin q-pb-sm" :pagination="pagination" />
</template>
// vue
// table+分頁組件所需
pagination: {
offset: 0,
limit: 12,
count: 0
}
- 此外,如果對過濾項進行點擊過濾,是進行的編程式路由導航操作,將過濾項格式化成為規(guī)定的路由query格式(為了保證和get請求params一致)。
// vue methods中
filterSearch: function () {
this.$router.push({
path: this.$route.path,
query: filterToQueryFormat(this.filterForm) // 將過濾項格式化匹配到路由
})
}
// filterToQueryFormat,引用的utils。包含了
/**
* 篩選項格式化到url
* @param {{篩選項表單}} filterForm
* @return {{返回url的query}} query
*/
export function filterToQueryFormat (filterForm) {
console.log('filterToQuery')
let query = {}
for (var key in filterForm) {
if (key.endsWith('__in')) {
query[key] = filterForm[key].toString()
} else {
query[key] = filterForm[key] || filterForm[key] === '' ? filterForm[key] : null
}
}
query.offset = 0
console.log(filterForm)
console.log(query)
console.log('filterToQuery end')
return query
}
/**
* url格式化匹配篩選項
* @param {{url的query}} query
* @param {{篩選項表單}}} filterFrom 由于是對象的引用,所以在此對filterForm進行改變即改變了外層的filterForm
* @return {{返回url的query}} query
*/
export function queryTofilterFormat (query, filterForm) {
console.log('queryToFilter')
let filter = {}
for (var key in filterForm) {
if (key.endsWith('__in')) {
filter[key] = query[key] ? query[key].split(',') : []
} else {
filter[key] = query[key] ? query[key] : null
}
}
filter.limit = query.limit
filter.offset = query.offset
console.log(query)
console.log(filter)
console.log('queryToFilter end')
return filter
}
- 接下來,將過濾項表單定義為filterFrom,表單項所對應的具體組件v-model根據(jù)網(wǎng)絡請求param一一對應。
// html
<q-input :clearable="filterForm.file__icontains!=null" v-model="filterForm.file__icontains" float-label="所屬文件" />
q-btn color="secondary" size="sm" icon="search" label="篩選" @click="filterSearch" />
// vue
filterForm: { // 過濾項表單
state__in: [],
resolution__in: [],
severity__in: [],
is_tapdbug: null,
file__icontains: null,
author: null,
ci_time__lte: null,
ci_time__gte: null
}
- 然后,每次進入頁面,在mounted中進行第一次請求。對頁面路由更新進行監(jiān)聽,從而發(fā)起請求。
beforeRouteUpdate是只有頁面路由的params或query進行改變才會觸發(fā),原頁面復用,不進行頁面刷新。
// vue
mounted () {
// 加載數(shù)據(jù)
this.tableRequest(this.$route.query)
},
beforeRouteUpdate (to, from, next) {
// 路由query更新觸發(fā)
this.tableRequest(to.query)
next()
},
- 最后,對數(shù)據(jù)請求的操作。
///
/**
* 表格數(shù)據(jù)請求
* @param {{路由query參數(shù)}} query
*/
tableRequest: function (query) {
this.tableLoading = true // 表格加載動畫
// 判斷路由是否有l(wèi)imit和offset,如果沒有,默認12、0
query.limit = query.limit ? parseInt(query.limit) : 12
query.offset = query.offset ? parseInt(query.offset) : 0
issues(this.$route.params.project_id, query).then(response => {
// 請求成功后,改變分頁組件顯示
this.pagination = {
limit: query.limit,
offset: query.offset,
count: response.count
}
// 將路由的query匹配到過濾項表單中
this.filterForm = queryTofilterFormat(query, this.filterForm)
// 返回數(shù)據(jù)
this.tableData = response.results
// 關閉table加載狀態(tài)
this.tableLoading = false
}).catch(error => {
this.tableLoading = false
console.log(error)
})
}
整個代碼
// 分頁組件
<!--
by nickctang
自定義分頁組件
傳入?yún)?shù): pagination
pagination包含偏移量、每頁顯示數(shù)量、和總數(shù)
在進行分頁切換是,觸發(fā)編程式導航,導航到對應位置,并進行服務器請求,請求完畢后,將pagination回傳回來
-->
<template>
<div class="row q-my-md fs-14" >
<span class="self-center col-auto q-pb-sm q-mt-md q-mr-md">共{{paginationModel.count}}條,第{{ page }} /{{max}}頁</span>
<q-select
class="col-auto self-center q-mr-md q-mt-md"
style="width: 90px"
v-model="paginationModel.limit"
:options="perPageNumOptions"
/>
<q-pagination class="col-auto q-mt-md" v-model="page" :max="max" size="sm" boundary-links boundary-numbers :max-pages="10" direction-links ellipses @input="onPageChange"/>
</div>
</template>
<script>
export default {
name: 'CPagination',
data () {
return {
perPageNumOptions: [
{
label: '每頁12條',
value: 12
},
{
label: '每頁24條',
value: 24
},
{
label: '每頁48條',
value: 48
},
{
label: '每頁96條',
value: 96
}
]
}
},
computed: {
page: {
get: function () {
if (this.paginationModel.count === 0) {
return 0
}
// 偏移量/每頁顯示數(shù)量 向下取整 + 1
return Math.floor(this.paginationModel.offset / this.paginationModel.limit) + 1
},
set: function (val) {
// 頁碼改變動態(tài)的更新偏移量
this.paginationModel.offset = (val - 1) * this.paginationModel.limit
}
},
max: function () {
// 最大頁數(shù),總數(shù)/每頁顯示數(shù)量,向上取整
return Math.ceil(this.paginationModel.count / this.paginationModel.limit)
},
paginationModel: {// pagination的雙向綁定,可令外界的pagination來改變page
get: function () {
return this.pagination
},
set: function (val) {
this.$emit('paginationChange', val)
}
}
},
model: {
prop: 'pagination',
event: 'paginationChange'
},
methods: {
onPageChange: function (val) {
console.log('pagechange ' + val)
const query = JSON.parse(JSON.stringify(this.$route.query)) // 深度復制
query.limit = this.paginationModel.limit
query.offset = this.paginationModel.offset
this.$router.push(
{
path: this.$route.path,
query: query
})
}
},
props: {
pagination: {
offset: 0,
limit: 12,
count: 0
}
}
}
</script>
// issue_list
<template>
<q-page>
<proj-base-scroll content_class="p-max-w">
<p class="q-title q-mb-md">
<span class="q-mr-md">問題列表</span>
<q-table
no-data-label="沒有更多數(shù)據(jù)"
:data="tableData"
:columns="columns"
selection="multiple"
:selected.sync="selectedSecond"
:pagination.sync="paginationControl"
:loading="tableLoading"
name="id"
color="secondary" >
<q-tr slot="body" slot-scope="props" class="textPrimary" :props="props">
<q-td auto-width>
<q-checkbox color="secondary" v-model="props.selected" />
</q-td>
<q-td key="severity" :props="props">
<q-chip dense square :color="CODELINT_STATUS.SEVERITY_CHOICES[props.row.severity].color">
{{ CODELINT_STATUS.SEVERITY_CHOICES[props.row.severity].label }}
</q-chip>
</q-td>
<q-td key="file" :props="props">
<q-btn flat color="primary" size="sm" no-caps class="no-padding">
{{ props.row.file.slice(props.row.file.lastIndexOf('/')) }}
</q-btn>
<q-tooltip anchor="top middle" self="bottom middle" :offset="[10, 10]">
{{ props.row.file }}
</q-tooltip>
</q-td>
<q-td key="revision" :props="props">
<p class="q-mb-sm">
{{ props.row.revision.substring(0, 8) }}
<q-tooltip anchor="top middle" self="bottom middle" :offset="[10, 10]">
{{ props.row.revision }}
</q-tooltip>
</p>
<p class="no-margin textTertiary">{{ formatDatetime(props.row.ci_time) }}</p>
</q-td>
<q-td key="ruleinfo" :props="props">
<p class="q-mb-sm">{{ props.row.checkrule__display_name }}</p>
<p class="no-margin textTertiary">{{ props.row.msg }}</p>
</q-td>
<q-td key="state" :props="props">
<q-chip dense square :color="CODELINT_STATUS.STATE_CHOICES[props.row.state].color">
{{ CODELINT_STATUS.STATE_CHOICES[props.row.state].label }}
</q-chip>
</q-td>
<q-td key="author" :props="props">{{ props.row.author }}</q-td>
<q-td key="is_tapdbug" :props="props">{{ props.row.is_tapdbug?'已提單':'未提單' }}</q-td>
</q-tr>
<template slot="top" slot-scope="props">
<div class="row col q-mb-md fs-14 gutter-sm">
<div class="col-md-2 col-xs-4">
<q-select
multiple
clearable
v-model="filterForm.state__in"
:options="CODELINT_STATUS.STATE_OPTIONS"
float-label="狀態(tài)"
/>
</div>
<div class="col-md-2 col-xs-4">
<q-select
multiple
clearable
v-model="filterForm.resolution__in"
:options="CODELINT_STATUS.RESOLUTION_OPTIONS"
float-label="解決方法"
/>
</div>
<div class="col-md-2 col-xs-4">
<q-select
multiple
clearable
v-model="filterForm.severity__in"
:options="CODELINT_STATUS.SEVERITY_OPTIONS"
float-label="問題級別"
/>
</div>
<div class="col-md-2 col-xs-4">
<q-input :clearable="filterForm.file__icontains!=null" v-model="filterForm.file__icontains" float-label="所屬文件" />
</div>
<div class="col-md-2 col-xs-4">
<q-input :clearable="filterForm.author!=null" v-model="filterForm.author" float-label="責任人" />
</div>
<div class="col-md-2 col-xs-4">
<q-select
class="col-md-2 col-xs-4"
clearable
v-model="filterForm.is_tapdbug"
:options="tapdOptions"
float-label="xx情況"
/>
</div>
<div class="col-md-2 col-xs-4">
<q-datetime float-label="引入時間>=" v-model="filterForm.ci_time__gte" type="datetime" clearable />
</div>
<div class="col-md-2 col-xs-4">
<q-datetime float-label="引入時間<=" v-model="filterForm.ci_time__lte" type="datetime" clearable />
</div>
<div class="col self-center text-right">
<q-btn color="secondary" :disabled="selectedSecond.length==0" size="sm" label="xxx" class="q-mr-md" />
<q-btn color="secondary" size="sm" icon="search" label="篩選" @click="filterSearch" />
</div>
</div>
</template>
<template slot="bottom" slot-scope="props">
<div class="col"></div>
<c-pagination class="no-margin q-pb-sm" :pagination="pagination" />
</template>
</q-table>
</proj-base-scroll>
</q-page>
</template>
<style lang="stylus">
</style>
<script>
import projBaseScroll from 'src/components/codeproj/projBaseScroll'
import { CODELINT_STATUS } from '@/libs/status'
import { filterToQueryFormat, queryTofilterFormat } from '@/libs/utils'
import { mapState } from 'vuex'
import { date } from 'quasar'
import { issues } from '@/service/xxx'
export default {
components: {
projBaseScroll
},
data () {
return {
filterForm: { // 過濾項表單
state__in: [],
resolution__in: [],
severity__in: [],
is_tapdbug: null,
file__icontains: null,
author: null,
ci_time__lte: null,
ci_time__gte: null
},
// table+分頁組件所需
pagination: {
offset: 0,
limit: 12,
count: 0
},
tableLoading: false, // 表格加載狀態(tài)
paginationControl: { rowsPerPage: 0 }, // 顯示全部
tableData: [],
columns: [
....
],
CODELINT_STATUS: CODELINT_STATUS,
tapdOptions: [
{
label: '是',
value: true
}, {
label: '否',
value: false
}
],
selectedSecond: []
}
},
mounted () {
// 加載數(shù)據(jù)
this.tableRequest(this.$route.query)
},
computed: mapState({
project: state => state.codeproj.project
}),
beforeRouteUpdate (to, from, next) {
// 路由query更新觸發(fā)
this.tableRequest(to.query)
next()
},
methods: {
filterSearch: function () {
this.$router.push({
path: this.$route.path,
query: filterToQueryFormat(this.filterForm) // 將過濾項匹配到路由
})
},
/**
* 表格數(shù)據(jù)請求
* @param {{路由query參數(shù)}} query
*/
tableRequest: function (query) {
this.tableLoading = true // 表格加載動畫
// 判斷路由是否有l(wèi)imit和offset,如果沒有,默認12、0
query.limit = query.limit ? parseInt(query.limit) : 12
query.offset = query.offset ? parseInt(query.offset) : 0
issues(this.$route.params.project_id, query).then(response => {
// 請求成功后,改變分頁組件顯示
this.pagination = {
limit: query.limit,
offset: query.offset,
count: response.count
}
// 將路由的query匹配到過濾項表單中
this.filterForm = queryTofilterFormat(query, this.filterForm)
// 返回數(shù)據(jù)
this.tableData = response.results
// 關閉table加載狀態(tài)
this.tableLoading = false
}).catch(error => {
this.tableLoading = false
console.log(error)
})
},
/**
* 格式化時間
*/
formatDatetime: function (datetime) {
return date.formatDate(datetime, 'YYYY-MM-DD HH:mm')
}
}
}
</script>
// utils
/**
* 篩選項格式化到url
* @param {{篩選項表單}} filterForm
* @return {{返回url的query}} query
*/
export function filterToQueryFormat (filterForm) {
console.log('filterToQuery')
let query = {}
for (var key in filterForm) {
if (key.endsWith('__in')) {
query[key] = filterForm[key].toString()
} else {
query[key] = filterForm[key] || filterForm[key] === '' ? filterForm[key] : null
}
}
query.offset = 0
console.log(filterForm)
console.log(query)
console.log('filterToQuery end')
return query
}
/**
* url格式化匹配篩選項
* @param {{url的query}} query
* @param {{篩選項表單}}} filterFrom 由于是對象的引用,所以在此對filterForm進行改變即改變了外層的filterForm
* @return {{返回url的query}} query
*/
export function queryTofilterFormat (query, filterForm) {
console.log('queryToFilter')
let filter = {}
for (var key in filterForm) {
if (key.endsWith('__in')) {
filter[key] = query[key] ? query[key].split(',') : []
} else {
filter[key] = query[key] ? query[key] : null
}
}
filter.limit = query.limit
filter.offset = query.offset
console.log(query)
console.log(filter)
console.log('queryToFilter end')
return filter
}
最后
代碼感覺還是不夠精簡,雖然實現(xiàn)了一定的流程化,但是感覺還有其它改進過程,望指正。。