美文网首页
分页组件+Table表格+编程式导航

分页组件+Table表格+编程式导航

作者: 忘尘无憾 | 来源:发表于2018-08-22 20:26 被阅读112次

    描述:

    之前实现了自定义分页组件,但是由于现实业务需求(需要将过滤项表单、分页的offset和limit传递到路由中,即a?xxx=xxx&xxx=xxx)。因此对分页组件进行了重新的设计。

    分页组件的重新设计

    上一篇:Quasar的自定义分页组件

    之前的分页组件主要是监听每页显示数量limit和偏移量offset的变化来触发事件,进行回调,在回调处发起网络请求。

    由于上述需求,在新的分页组件中主要监听了页面改变的监听事件,并通过页面改变监听,触发编程式导航,从而改变页面路由,通过对页面路由的更新来发起网络请求,从而实现目标。

    <!--
      by nickctang
      自定义分页组件
      传入参数: pagination
      pagination包含偏移量、每页显示数量、和总数
      在进行分页切换是,触发编程式导航,导航到对应位置,并进行服务器请求,请求完毕后,将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
            }
            // 偏移量/每页显示数量 向下取整 + 1
            return Math.floor(this.paginationModel.offset / this.paginationModel.limit) + 1
          },
          set: function (val) {
            // 页码改变动态的更新偏移量
            this.paginationModel.offset = (val - 1) * this.paginationModel.limit
          }
        },
        max: function () {
          // 最大页数,总数/每页显示数量,向上取整
          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。

    在页面进行网络请求得到结果后,只需要改变pagination的内容,即可动态的更新分页组件。

    分页组件 + table + 编程式导航的结合使用

    描述:
    在父组件使用Table表格和分页组件的过程中,一般还会存在过滤项表单,来对数据进行过滤请求,这些过滤项和分页的offset、limit都需要当作路由的query参数。

    实际上,该query参数模样,相当于axios进行get请求时的params。因此将表单对应组件的v-model和请求所需的param定义成相同的名称。

    期间遇到的问题:
    基本上还是多次请求的问题,在编程式导航过程中,如果没有对应的更新分页组件,由于count的变化,容易让分页组件响应编程式导航,而且由于没有更新offset、limit,从而导致编程式导航成功响应,进行了二次网络请求。

    最终的解决:
    对原来的分页组件进行的更新。在每次获取网络请求结束后,动态的改变pagination中的count、limit、offset,从而基本触发了编程式导航,但是由于路由的query没有改变,而不会响应beforeRouteUpdate,也就不会再次响应网络请求。

    实现过程

    • 首先,定义table。
    // html
    <q-table
        no-data-label="没有更多数据"
        :data="tableData"
        :columns="columns"
        :pagination.sync="paginationControl"
        :loading="tableLoading"
        name="id"
        color="secondary" >
        ...
        //vue
        tableLoading: false, // 表格加载状态
        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
    }
    
    • 此外,如果对过滤项进行点击过滤,是进行的编程式路由导航操作,将过滤项格式化成为规定的路由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根据网络请求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中进行第一次请求。对页面路由更新进行监听,从而发起请求。beforeRouteUpdate是只有页面路由的params或query进行改变才会触发,原页面复用,不进行页面刷新。
    // vue
    mounted () {
        // 加载数据
        this.tableRequest(this.$route.query)
     },
     beforeRouteUpdate (to, from, next) {
        // 路由query更新触发
        this.tableRequest(to.query)
        next()
      },
    
    • 最后,对数据请求的操作。
    ///
        /**
        * 表格数据请求
        * @param {{路由query参数}} query
        */
    tableRequest: function (query) {
        this.tableLoading = true // 表格加载动画
        // 判断路由是否有limit和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)
        // 返回数据
        this.tableData = response.results
        // 关闭table加载状态
        this.tableLoading = false
        }).catch(error => {
        this.tableLoading = false
        console.log(error)
        })
    }
    

    整个代码

    // 分页组件
    <!--
      by nickctang
      自定义分页组件
      传入参数: pagination
      pagination包含偏移量、每页显示数量、和总数
      在进行分页切换是,触发编程式导航,导航到对应位置,并进行服务器请求,请求完毕后,将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
            }
            // 偏移量/每页显示数量 向下取整 + 1
            return Math.floor(this.paginationModel.offset / this.paginationModel.limit) + 1
          },
          set: function (val) {
            // 页码改变动态的更新偏移量
            this.paginationModel.offset = (val - 1) * this.paginationModel.limit
          }
        },
        max: function () {
          // 最大页数,总数/每页显示数量,向上取整
          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="没有更多数据"
            :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="状态"
                  />
                </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, // 表格加载状态
          paginationControl: { rowsPerPage: 0 }, // 显示全部
          tableData: [],
          columns: [
           ....
          ],
          CODELINT_STATUS: CODELINT_STATUS,
          tapdOptions: [
            {
              label: '是',
              value: true
            }, {
              label: '否',
              value: false
            }
          ],
          selectedSecond: []
        }
      },
      mounted () {
        // 加载数据
        this.tableRequest(this.$route.query)
      },
      computed: mapState({
        project: state => state.codeproj.project
      }),
      beforeRouteUpdate (to, from, next) {
        // 路由query更新触发
        this.tableRequest(to.query)
        next()
      },
      methods: {
        filterSearch: function () {
          this.$router.push({
            path: this.$route.path,
            query: filterToQueryFormat(this.filterForm) // 将过滤项匹配到路由
          })
        },
        /**
         * 表格数据请求
         * @param {{路由query参数}} query
         */
        tableRequest: function (query) {
          this.tableLoading = true // 表格加载动画
          // 判断路由是否有limit和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)
            // 返回数据
            this.tableData = response.results
            // 关闭table加载状态
            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
    }
    

    最后

    代码感觉还是不够精简,虽然实现了一定的流程化,但是感觉还有其它改进过程,望指正。。

    相关文章

      网友评论

          本文标题:分页组件+Table表格+编程式导航

          本文链接:https://www.haomeiwen.com/subject/arfuiftx.html