前后端分页,模糊搜索,详细讲解


分页可以分为前端分页和后端分页

1.前端分页

前端分页就是先获取全部数据,数据少了还好,如果数据很大,获取数据的时间就会比较长;所以对于数据量大的操作,一般都采用后端分页的操作更合适。

下面是整个分页的模块

<template>
  <div class="ys-table">
    <YsDialog
      v-model="isShowDialog"
      :title="dialogTitle"
      :close-on-click-mask="false"
      :with-mask="true"
    >
      <template v-slot:content>
        <Form
          :id='id'
          :params='selected'
          @addSubmit='handleAddCallback'
          @editSubmit='handleEditCallback'
        />
      </template>
    </YsDialog>
    <i-row
      type="flex"
      justify="space-between"
      align="bottom"
      class="ys-table-toolbar"
    >
      <i-col class="ys-table-col">
        <div>
          <h4 class='table-title'>
            带宽通道
          </h4>
          <i-button
            @click="handleAdd"
          >
            <VIcon size="small">
              add
            </VIcon>
            新建
          </i-button>
        </div>
      </i-col>
      <i-col v-if="searchAble">
        <i-input
          v-model="searchTemp"
          search
          placeholder="搜索"
          @on-search="searchHandler"
        />
      </i-col>
    </i-row>
    <Table
      :data="data"
      class="ys-table-body"
      style="width: 100%"
    >
      <TableColumn
        width="60"
        label="序号"
        prop="$index"
        align="center"
        fixed="left"
      />
      <TableColumn
        prop="name"
        label="名称"
      />
      <TableColumn label="整体限流">
        <TableColumn
          prop="extend_reference_mode"
          label="引用方式"
        />
        <TableColumn
          label="最大带宽"
        >
          <template slot-scope="scope">
            <div v-if="scope.row.traffic_limiting_mode === 'Stream'">
              <div>
                <VIcon size="small">
                  file_upload
                </VIcon>
                {{ scope.row.global_upstream_bandwidth_max }}
              </div>
              <div>
                <VIcon size="small">
                  file_download
                </VIcon>
                {{ scope.row.global_downstream_bandwidth_max }}
              </div>
            </div>
            <div v-else>
              <span>{{ scope.row.global_total_bandwidth_max }}</span>
            </div>
          </template>
        </TableColumn>
        <TableColumn
          label="保证带宽"
        >
          <template slot-scope="scope">
            <div v-if="scope.row.traffic_limiting_mode === 'Stream'">
              <div>
                <VIcon size="small">
                  file_upload
                </VIcon>
                {{ scope.row.global_upstream_bandwidth_guaranteed }}
              </div>
              <div>
                <VIcon size="small">
                  file_download
                </VIcon>
                {{ scope.row.global_downstream_bandwidth_guaranteed }}
              </div>
            </div>
            <div v-else>
              <span>{{ scope.row.global_total_bandwidth_guaranteed }}</span>
            </div>
          </template>
        </TableColumn>
        <TableColumn
          prop="global_max_connections"
          label="最大连接数"
        />
        <TableColumn
          prop="global_max_connection_rate"
          label="最大连接速率"
        />
      </TableColumn>
      <TableColumn label="每IP限流">
        <TableColumn
          prop="extend_traffic_limiting_object"
          label="限流对象"
        />
        <TableColumn
          label="最大带宽"
        >
          <template slot-scope="scope">
            <div v-if="scope.row.traffic_limiting_mode === 'Stream'">
              <div>
                <VIcon size="small">
                  file_upload
                </VIcon>
                {{ scope.row.per_upstream_bandwidth_max }}
              </div>
              <div>
                <VIcon size="small">
                  file_download
                </VIcon>
                {{ scope.row.per_downstream_bandwidth_max }}
              </div>
            </div>
            <div v-else>
              <span>{{ scope.row.per_total_bandwidth_max }}</span>
            </div>
          </template>
        </TableColumn>
        <TableColumn
          label="保证带宽"
        >
          <template slot-scope="scope">
            <div v-if="scope.row.traffic_limiting_mode === 'Stream'">
              <div>
                <VIcon size="small">
                  file_upload
                </VIcon>
                {{ scope.row.per_upstream_bandwidth_guaranteed }}
              </div>
              <div>
                <VIcon size="small">
                  file_download
                </VIcon>
                {{ scope.row.per_downstream_bandwidth_guaranteed }}
              </div>
            </div>
            <div v-else>
              <span>{{ scope.row.per_total_bandwidth_guaranteed }}</span>
            </div>
          </template>
        </TableColumn>
        <TableColumn
          prop="per_max_connections"
          label="最大连接数"
        />
        <TableColumn
          prop="per_max_connection_rate"
          label="最大连接速率"
        />
      </TableColumn>
      <TableColumn
        label="操作"
        width='100'
      >
        <template slot-scope="scope">
          <i-button-group>
            <i-button
              size="small"
              @click="handleEdit(scope.row)"
            >
              编辑
            </i-button>
            <i-button
              size="small"
              @click="handleDelete(scope.row)"
            >
              删除
            </i-button>
          </i-button-group>
        </template>
      </TableColumn>
      <div
        slot="empty"
        class="status-panel"
        :class="is_error && 'is-error'"
      >
        <template v-if="is_error">
          <IIcon
            type="md-warning"
            class="icon"
          />
          <span>{{ isErrorText }}</span>
        </template>
        <template v-else>
          <IIcon
            type="ios-copy-outline"
            class="icon"
          />
          <span>{{ noDataText }}</span>
        </template>
      </div>
    </Table>
    <i-row
      v-if="total > 10 || pages > 1"
      type="flex"
      justify="end"
    >
      <i-col>
        <i-page
          :current="page"
          :total="total"
          show-total
          show-sizer
          class="ys-table-pagination"
          @on-change="pageChangeHandler"
          @on-page-size-change="sizeChangeHandler"
        />
      </i-col>
    </i-row>
    <i-spin v-if="!!loading" />
  </div>
</template>

<script>
  import { bus, REFRESH_FW_SPEED_LIMIT } from 'common/bus'
  import { Table, TableColumn } from 'element-ui'
  import 'element-ui/lib/theme-chalk/table.css'
  import adapter from 'base/components/Table/AsyncAdapter'
  import { SPEED_LIMIT_REFERENCE_MODE } from 'common/enum'
  import Form from './form'
  export default {
    name: 'Channel',
    components: {
      Table,
      TableColumn,
      Form
    },
    props: {
      id: {
        type: String,
        default: ''
      },
      noDataText: {
        type: String,
        default: '暂无数据'
      },
      isErrorText: {
        type: String,
        default: '请求错误'
      }
    },
    data() {
      return {
        searchTemp: '',
        searchStr: '',
        searchAble: true,
        api: () => [],
        selected: {},
        dialogTitle: '',
        isShowDialog: false,
        loading: true,
        is_error: false,
        oriData: [],
        data: [],
        total: 0,
        page: 1,
        size: 10,
      }
    },
    computed: {
      pages() {
        return Math.ceil(this.total / this.size)
      },
    },
    watch: {
      api: {
        handler() {
          this.fetchData()
        },
        immediate: true,
      },
      oriData: {
        handler: _.throttle(function() {
          this.flushTable()
        }, 300),
        deep: true
      }
    },
    created() {
      this.fetchChannel()
      bus.$on(REFRESH_FW_SPEED_LIMIT, this.fetchChannel)
    },
    destroyed() {
      bus.$off(REFRESH_FW_SPEED_LIMIT, this.fetchChannel)
    },
    methods: {
      handleAddCallback() {
        ys.success('新建成功')
        this.isShowDialog = false
        this.fetchChannel()
      },
      handleEditCallback() {
        ys.success('修改成功')
        this.isShowDialog = false
        this.fetchChannel()
      },
      fetchChannel() {
        this.api = adapter(this.$request('/fw/traffic-profile/get', {
          urlQuery: {
            resource_uuid: this.id,
          }
        }).then(data => {
          return _.map(_.get(data, 'DATA', []), channel => {
            const mode = _.find(SPEED_LIMIT_REFERENCE_MODE, mode => {
              return mode.value === channel.reference_mode
            })
            channel.extend_reference_mode = _.get(mode, 'name') || '未知'
            channel.extend_traffic_limiting_object =
              channel.traffic_limiting_object === 'Ip' ? '每IP' : '未知'
            return channel
          })
        }))
      },
      searchHandler() {
        this.searchStr = this.searchTemp
        this.fetchData()
      },
      handleAdd() {
        this.selected = {}
        this.isShowDialog = true
        this.dialogTitle = '新增带宽通道'
      },
      handleEdit(row) {
        this.selected = row
        this.isShowDialog = true
        this.dialogTitle = '修改带宽通道'
      },
      handleDelete(row) {
        let self = this
        self.$YsDialog.confirm({
          title: '删除带宽通道',
          content: '您确认要删除带宽通道 ' + row.name + ' ?',
          onConfirm() {
            return self.$request('/fw/traffic-profile/delete', {
              urlQuery: {
                uuid: row.uuid,
              },
            }).then(() => {
              ys.success('删除成功');
              self.fetchChannel();
            })
          }
        })
      },
      pageChangeHandler(page) {
        this.page = page
        this.fetchData()
        this.$emit('on-page-change', page)
      },
      sizeChangeHandler(size) {
        this.page = 1
        this.size = size
        this.fetchData()
        this.$emit('on-page-size-change', 1, size)
      },
      genQuery() {
        const params = {
          pagination: {
            page: this.page,
            size: this.size,
          },
          search: {
            key: undefined,
            value: this.searchStr
          },
        }
        return params
      },
      fetchData() {
        const fetchId = Math.random().toString()
        this.loading = fetchId
        this.is_error = false
        const query = this.genQuery()
        return Promise.resolve(this.api(query)).then(res => {
          if (fetchId === this.loading) {
            this.page = _.get(res, 'PAGE.INDEX', 1)
            this.total = _.get(res, 'PAGE.TOTAL_ITEM', 0)
            this.oriData = _.map(_.get(res, 'DATA', []), (d, i) => {
              d.$index = (this.page - 1) * this.size + i + 1
              return d
            })
            this.$emit('on-data-change', this.data)
            this.flushTable()
          }
        }).catch(e => {
          this.is_error = true
          throw e
        }).finally(() => {
          if (this.loading === fetchId) {
            this.loading = false
          }
        })
      },
      flushTable() {
        this.data = this.oriData
      },
    }
  }
</script>
<style lang="scss" scoped>
  @import "~assets/css/variables.scss";
  @include themify() {
    .el-table ::v-deep .el-table__header th {
      background-color: themed('table-header-bg-color');
    }

    .el-table--border ::v-deep .el-table__header {
      @if $theme-name == dark {
        th {
          border-bottom: themed('border-base');
        }
      }
      tr:last-child th:nth-last-child(2).is-leaf {
        border-right: themed('border-base');
        @if $theme-name != dark {
          border-right-color: themed('background-color-darken');
        }
      }
    }

    .status-panel.is-error {
      color: themed('error-color');
    }
  }

  .ys-table {
    position: relative;

    .ys-table-toolbar {
      padding: 0 0 10px;

      .table-title {
        padding: 0 0 10px;
      }
    }

    .ys-table-pagination {
      margin-top: 10px;
    }
  }
  .status-panel {
    display: flex;
    justify-content: center;
    align-items: center;

    .icon {
      font-size: 1.4em;
    }
  }
</style>
<style>
.ys-table-col .ivu-btn+.ivu-btn {
  margin-left: 10px;
}
</style>

前端请求api接口拿到全部数据,做分页,详情如图

 

 

 

pageChangeHandler改变当前页码,重新展现数据

sizeChangeHandler每页展示多少条数据,并重新回到第一页展示

genQuery获取page,size,和search参数传给后端做搜索

2后端分页

下面这个是封装好的分页组件

<template>
  <div class="ys-table">
    <i-row
      type="flex"
      justify="space-between"
      align="bottom"
      class="ys-table-toolbar"
    >
      <i-col class="ys-table-col">
        <slot name="operation" />
      </i-col>
      <i-col v-if="searchAble">
        <i-input
          v-model="search_tmp"
          search
          placeholder="搜索"
          @on-search="searchHandler"
        >
          <YsSelect
            v-if="searched_columns.length"
            slot="prepend"
            v-model="search_key"
            :disabled="searched_columns.length === 1"
            placeholder="全部"
            clearable
            class="search-column-select"
            @on-clear="searchColClearHandler"
          >
            <i-option
              v-for="col in searched_columns"
              :key="col.key"
              :value="col.key"
            >
              {{ col.title }}
            </i-option>
          </YsSelect>
        </i-input>
      </i-col>
    </i-row>
    <el-table
      :columns='columns_reshaped'
      :data='data'
      :row-key="rowKey"
      class="ys-table-body"
      @sort-change="sortChangeHandler"
    >
      <div
        slot="empty"
        class="status-panel"
        :class="is_error && 'is-error'"
      >
        <template v-if="is_error">
          <IIcon
            type="md-warning"
            class="icon"
          />
          <span>{{ isErrorText }}</span>
        </template>
        <template v-else>
          <IIcon
            type="ios-copy-outline"
            class="icon"
          />
          <span>{{ noDataText }}</span>
        </template>
      </div>
    </el-table>
    <i-row
      v-if="pagination_total > 10 || pagination_pages > 1"
      type="flex"
      justify="end"
    >
      <i-col>
        <i-page
          :current="pagination_page"
          :total="pagination_total"
          :page-size="pagination_size"
          show-total
          show-sizer
          class="ys-table-pagination"
          @on-change="pageChangeHandler"
          @on-page-size-change="sizeChangeHandler"
        />
      </i-col>
    </i-row>
    <i-spin v-if="!!loading" />
  </div>
</template>
<script>
  import { Checkbox } from 'element-ui'
  import ElTable from './ElTable'

  export default {
    name: 'YsTable',
    components: {
      ElTable
    },
    props: {
      api: {
        type: Function,
        default: () => {}
      },
      columns: { // 列名,必须含 { key: '', title: ''} 的对象
        type: Array,
        required: true
      },
      rowKey: {
        type: Boolean,
        default: false
      },
      searchAble: {
        type: Boolean,
        default: true
      },
      defaultSearchCondition: {
        type: Object,
        default() {
          return {
            defaultSearchCol: '',
            defaultSearchTemp: '',
          }
        }
      },
      noDataText: {
        type: String,
        default: '暂无数据'
      },
      isErrorText: {
        type: String,
        default: '请求错误'
      },
      namespace: {
        type: String,
        default: ''
      }
    },
    data() {
      return {
        loading: true,
        is_error: false,

        ori_data: [],
        data: [],

        search_tmp: '',
        search_value: '',
        search_key: '',

        pagination_total: 0,
        pagination_page: 1,
        pagination_size: 10,

        order: {},

        selection: [],
        isSelectedAll: false,
        isIndeterminated: false
      }
    },
    computed: {
      columns_reshaped() {
        let hasIndex = false
        const defaultIndex = {
          key: '$index',
          title: '序号',
          width: 60,
          align: 'center',
          fixed: 'left'
        }
        const result = _.map(this.columns, col => {
          if (col.type === 'index') {
            hasIndex = true
            return {
              ...defaultIndex,
              ...col
            }
          }
          if (col.type === 'selection') {
            return {
              render: this.selectionTdRender,
              headerRender: this.selectionThRender,
              align: 'center',
              width: 50,
              ...col,
              type: null
            }
          }
          return {
            sortMethod: () => 0,
            tooltip: true,
            sortable: true,
            ...col,
          }
        })
        return [].concat(hasIndex ? [] : defaultIndex, result)
      },
      searched_columns() {
        return _.reduce(this.columns, (acc, col) => {
          if (!('key' in col)) return acc

          if (col.key.startsWith('$')) return acc

          if (col.searchable) acc.push(col)
          return acc
        }, [])
      },
      pagination_pages() {
        return Math.ceil(this.pagination_total / this.pagination_size)
      },
    },
    watch: {
      api() {
        this.fetchData()
      },
      defaultSearchCondition: {
        handler(val) {
          if (val.defaultSearchCol && val.defaultSearchTemp) {
            this.search_key = val.defaultSearchCol
            this.search_tmp = val.defaultSearchTemp
            this.searchHandler()
          }
        },
        immediate: true,
        deep: true
      },
      searched_columns: {
        handler(v) {
          if (v.length === 1) {
            this.search_key = _.get(v, [0, 'key'], '')
          } else {
            this.search_key = ''
          }
        },
        immediate: true
      },
      ori_data: {
        handler: _.throttle(function() {
          this.flushTable()
        }, 300),
        deep: true
      },
    },
    created() {
      if (this.$router) {
        this.initPaginationFromQuery()
        this.$watch('pagination_page', v => this.syncPaginationToQuery('page', v))
        this.$watch('pagination_size', v => this.syncPaginationToQuery('size', v))
      }
      this.fetchData()
    },
    methods: {
      searchHandler() {
        this.search_value = this.search_tmp
        this.fetchData()
      },
      searchColClearHandler() {
        this.search_tmp = ''
        this.searchHandler()
      },
      pageChangeHandler(page) {
        if (page !== this.pagination_page) {
          this.pagination_page = page
          this.fetchData()
        }
        this.$emit('on-page-change', page)
      },
      sizeChangeHandler(size) {
        if (size !== this.pagination_size) {
          this.pagination_size = size
          this.fetchData()
        }
        this.$emit('on-page-size-change', 1, size)
      },
      getFetchQuery() {
        const params = {
          pagination: {
            page: this.pagination_page,
            size: this.pagination_size,
          },
          order: this.order
        }

        if (this.searchAble) {
          params.search = {
            key: this.search_key,
            value: this.search_value
          }

          if (!params.search.key) {
            const searchCols = this.searched_columns.length ? this.searched_columns : this.columns
            params.search = _.reduce(searchCols, (acc, col) => {
              if ('key' in col) {
                acc.push({
                  key: col.key,
                  value: this.search_value
                })
              }
              return acc
            }, [])
            if (params.search.length === 1) {
              params.search = params.search.pop()
            }
          }
        }

        return params
      },
      async fetchData() {
        const fetchId = Math.random().toString()
        this.loading = fetchId
        this.is_error = false
        const fetch_query = this.getFetchQuery()
        try {
          const res = await this.api(fetch_query)

          if (fetchId !== this.loading) return
          if (!res) return

          this.pagination_page = _.get(res, 'PAGE.INDEX', 1)
          this.pagination_total = _.get(res, 'PAGE.TOTAL_ITEM', 0)
          this.ori_data = _.map(_.get(res, 'DATA', []), (d, i) => {
            d.$index = (this.pagination_page - 1) * this.pagination_size + i + 1
            return d
          })
          this.selection = _.reduce(this.ori_data, (acc, d) => {
            if (d.$selected) {
              acc.push(d.$index)
            }
            return acc
          }, [])
          this.$emit('on-data-change', this.data)
          this.flushTable()
        } catch (err) {
          this.is_error = true
          throw err
        }
        if (this.loading === fetchId) {
          this.loading = false
        }
      },
      flushTable() {
        this.data = _.map(this.ori_data, d => {
          d = { ...d }
          d.$selected = this.selection.includes(d.$index)
          return d
        })
        this.isSelectedAll = !_.isEmpty(this.data) && _.every(this.data, d => d.$selected)
        this.isIndeterminated = !this.isSelectedAll && !_.isEmpty(this.selection)
      },
      sortChangeHandler({ key, order }) {
        if (order === "asc") {
          this.order = { key }
        } else if (order === 'desc') {
          this.order = {
            key,
            desc: true
          }
        } else {
          this.order = {}
        }
        this.fetchData()
      },
      selectionClickHandler(selected, scoped) {
        if (selected) {
          this.selection.push(scoped.row.$index)
        } else {
          this.selection = _.filter(this.selection, s => {
            return s !== scoped.row.$index
          })
        }
        this.flushTable()
        const selection = _.filter(this.ori_data, d => this.selection.includes(d.$index))
        this.$emit('on-selection-change', selection)
        const item = _.find(this.ori_data, d => d.$index === scoped.row.$index)
        this.$emit('on-select', selection, item)
      },
      selectionAllClickHandler(scoped) {
        if (this.isSelectedAll) {
          this.selection = []
        } else {
          const selectable = _.get(scoped, ['column', 'selectable'], () => true)
          this.selection = _.reduce(this.ori_data, (acc, d) => {
            if (selectable(d)) {
              acc.push(d.$index)
            }
            return acc
          }, [])
        }
        this.flushTable()
        const selection = _.filter(this.ori_data, d => this.selection.includes(d.$index))
        this.$emit('on-selection-change', selection)
        this.$emit('on-select-all', selection)
      },
      selectionTdRender(h, scoped) {
        return h(Checkbox, {
          props: {
            value: _.get(scoped, ['row', '$selected'], false),
            disabled: !_.get(scoped, ['column', 'selectable'], () => true)(scoped.row)
          },
          on: {
            change: v => this.selectionClickHandler(v, scoped)
          }
        })
      },
      selectionThRender(h, scoped) {
        return h(Checkbox, {
          props: {
            value: this.isSelectedAll,
            indeterminate: this.isIndeterminated
          },
          on: {
            change: () => this.selectionAllClickHandler(scoped)
          }
        })
      },
      initPaginationFromQuery() {
        this.pagination_page = _.get(
          this.$route?.query,
          `${this.namespace}_page`,
          this.pagination_page
        ) * 1
        this.pagination_size = _.get(
          this.$route?.query,
          `${this.namespace}_size`,
          this.pagination_size
        ) * 1
      },
      syncPaginationToQuery(key, value) {
        this.$router.push({
          query: {
            ...this.$route.query,
            [`${this.namespace}_${key}`]: value
          }
        })
      }
    },
  }
</script>
<style lang="scss" scoped>
  @import "~assets/css/variables.scss";
  @include themify() {
    .status-panel.is-error {
      color: themed(error-color);
    }
  }

  .ys-table {
    position: relative;

    .ys-table-toolbar {
      padding: 0 0 10px;

      .search-column-select {
        width: auto;
      }
    }

    .ys-table-pagination {
      margin-top: 10px;
    }
  }

  .status-panel {
    display: flex;
    justify-content: center;
    align-items: center;

    .icon {
      font-size: 1.4em;
    }
  }
</style>
<style>
.ys-table-col .ivu-btn+.ivu-btn {
  margin-left: 10px;
}
</style>

 

 

 后端做分页的话前端就只需要将页码page_index,每页数量page_size,做搜索的话将用户输入的数据传给后端,然后后端根据你传的值返回固定条数返回数据,前端展示到页面上。

菜鸟程序员,有错误欢迎指教

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值