Инкапсулируйте лучший компонент таблицы на основе фреймворка element-ui.

внешний интерфейс JavaScript Vue.js
Инкапсулируйте лучший компонент таблицы на основе фреймворка element-ui.

Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность

Эта статья приняла участие"Проект "Звезда раскопок"", чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.

предисловие

Когда мы работаем над системой фонового управления, на самом деле компоненты, к которым мы больше всего прикасаемся,表格组件, так что простота использования табличного компонента напрямую связана с нашей системой фонового управления.效率Итак, сегодня мы поговорим о том, какelement-uiКомпонент формы写更少的代码,实现更多并且更稳定的功能. Без дальнейших церемоний, давайте начнем.

Функция

Для начала разберемся, какие функции нам нужно реализовать.

  1. Комбинированная функция пейджингаPaginationКомпонент для реализации пагинации таблицы
  2. Добавьте слоты в таблицу, вы можете настроить стиль и данные для таблицы
  3. Реализовать несколько заголовков
  4. Реализовать данные межстраничной проверки
  5. Используйте функцию запроса[输入框、多选框、单选框、日期框]и другие компоненты для обеспечения взаимодействия запросов
  6. Сортировка накопления порядкового номера
  7. Функция убывания порядка для лучшей работы
  8. Реализовать отображение условий запроса. Возможно, запрос таблицы может быть запрошен на основе многих условий. С отображением условий запроса вы можете сразу увидеть, на каких условиях основан список.
  9. Для реализации динамических заголовков возможно, что таблица имеет много заголовков, и пользователям трудно найти некоторые заголовки, которые они хотят видеть с первого взгляда, поэтому может потребоваться функция, которая может реализовать фильтрацию заголовков.

Базовая реализация

При реализации функций, о которых мы упоминали выше, мы должны сначала добавить функции самого element-ui. Затем мы сначала помещаем эти основные функции в наши собственные компоненты.

<template>
    <el-table v-bind="$attrs" v-on="$listeners">
        <template v-for="(item) in columns">
          <el-table-column
            :key="item.prop"
            v-bind="item"
            show-overflow-tooltip> 
          </el-table-column>
        </template>
    </el-table>
</template>

<script>
 export default {
  name: 'miniTable',
  props: {
    columns: {
      type: Array,
      default: () => [],
    },
  }
}
</script>

<mini-table border :columns="columns"></mini-table>
columns: [
    { label: '姓名', prop: 'name', align: 'center' },
    { label: '年龄', prop: 'age', align: 'center' },
    { label: '爱好', prop: 'hobby', align: 'center' },
    { label: '学历', prop: 'education', align: 'center' },
    { label: '籍贯', prop: 'nativePlace', align: 'center' },
    { label: '备注', prop: 'remark', align: 'left', width: 200 }
],

image.pngВышеприведенное реализует наши основные функции в element-ui.Вышеупомянутое в основном опирается наvueкоторый предоставил$attrа также$listenersфункция достижения.

listenersсодержит родительскую область(Не содержит.nativeмодификатор) vonпрослушиватель событий. это может пройтиvon="listeners содержит прослушиватели событий `v-on` в родительской области (без декоратора .native). Доступ к нему можно получить через `v-on="listeners" 传入内部组件——在创建更高层次的组件时非常有用 $attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs"` передает внутренние компоненты - полезно при создании высокоуровневых компонентов.

ВышеvueОфициальный документ вводит эти два свойства.Общий смысл в том, что вы можете полагаться на эти два свойства, чтобы сделать более высокий уровень инкапсуляции для существующих компонентов. Разрешить пользователям прозрачно передавать свойства или методы нашим компонентам при их использовании.element-uiкомпоненты, чтобы пойти.Конечно, если есть какие-то функции, нам нужноelement-uiЕсли мы выполним некоторую обработку события, мы можем внести некоторые изменения в код. Мы поговорим об этом позже. Тогда есть еще один момент, потому что атрибуты должны быть прозрачно переданы вelement-uiкомпонента, чтобы избежать некоторых ненужных конфликтов позже, мы добавим атрибуты, используемые в наших компонентах, позже перед именем__, чтобы представить свойства, которые являются частными для наших собственных компонентов.

Интегрированная пагинация

код сначала

<el-pagination
  class="pagination"
  background
  v-if="hasPagination"
  @size-change="sizeChange"
  @current-change="currentChange"
  :total="pagination.totalRow"
  :current-page="pagination.pageIndex"
  :page-size="pagination.pageSize"
  :page-sizes="pageSizes"
  :layout="layout">
</el-pagination>

// methods
/**
 * 切换分页数量
 * @param { Number } pageSize 页数
 */
sizeChange (pageSize) {
  this.pagination.pageIndex = 1
  this.pagination.pageSize = pageSize
  this.queryData()
},
/**
 * 切换页码
 * @param { Number } pageIndex 页码
 */
currentChange (pageIndex) {
  this.pagination.pageIndex = pageIndex
  this.queryData(true)
},

По пейджингу сказать особо нечего, логика пейджинга в основном представляет собой функцию, связанную с таблицей. такpaginationобъект непосредственно в компонентеdataхорошо выраженный внутри,pageSizesа такжеlayoutПринимать внешние входящие. Если не пройдено, дайте значение по умолчанию.Есть также случаи, когда номера страниц не требуются. Таким образом, внешнее также может переходить вhasPagination, если вам не нужен номер страницы, введитеfalse. По умолчаниюtrue

пользовательское содержимое таблицы

Таблицы не обязательно представляют собой простые текстовые данные. Возможно, потребуется визуализировать按钮или а图片также может быть百分比进度条Тогда что это такое, неизвестно в нашем компоненте. Поэтому нам нужно предоставить слот для пользователя в компоненте, чтобы пользователь мог настраивать контент. Обеспечьте гибкость компонентов.Так что же нужно сделать? Давайте посмотрим на реализацию кода ниже:

// 添加slot
<el-table-column
  :key="item.prop"
  v-bind="item"
  show-overflow-tooltip> 
    <template v-if="item.__slotName" v-slot="scope">
      <slot :name="item.__slotName" :data="scope"></slot>
    </template>
</el-table-column>

// data
columns: [{ label: '头像', prop: 'avatar', align: 'center', __slotName: 'avatar' }]

// 使用miniTable组件
<mini-table size="small" border :columns="columns">
  <template slot="avatar" slot-scope="scope">
    <img slot="avatar" width="40" :src="scope.data.row.avatar" />
  </template>
</mini-table>

Это не сложно реализовать, добавим__slotNameАтрибут передается компоненту, а затем к компоненту добавляется суждение, если оно передано__slotName, используйте слот и передайтеscopeДля слота используется название слота__slotNameТаким образом, когда мы используем компоненты, мы можем определитьslot="avatar"и получить данные строки, предоставленные компонентом.

image.pngЗатем мы можем записать в таблицу строку с данными аватара.

Несколько заголовков

В реальных проектах мы также столкнемся с проблемой множественных заголовков, это может быть заголовок второго уровня, может быть заголовок третьего уровня, а может быть и больше. Чтобы соответствовать всем случаям, способ его реализации использует способ рекурсии компонентов для реализации нескольких заголовков.

<template>
<el-table-column
v-bind="item"
:key="item.prop"
show-overflow-tooltip> 
  <template v-for="obj in item.__children">
    <my-table-column v-if="obj.__children" :item="obj" v-bind="obj" :key="obj.prop"></my-table-column>
    <el-table-column
      v-else
      :key="obj.prop"
      v-bind="obj"
      show-overflow-tooltip> 
      <template v-if="obj.__slotName" v-slot="scope">
        <slot :name="obj.__slotName" :data="scope"></slot>
      </template>
    </el-table-column>
  </template>
</el-table-column>
</template>

<script>
import MyTableColumn from './tableColumn'

export default {
 name: 'MyTableColumn',
 components: {
   MyTableColumn
 },
 props: {
   item: {
     type: Object,
     default: () => {}
   }
 }
}
</script>

Вышеупомянутое создаетtableColumnфайл, если заголовок существует__childrenто рекурсивный компонент, если он отсутствует, является二级表头, Когда мы его используем, добавьте тест данных, подобный этому

{ label: '地址信息',
  prop: 'address', 
  align: 'center',
  __children: [
    {
      label: '省份',
      prop: 'province',
      align: 'center'
    },
    {
      label: '城市',
      prop: 'city',
      align: 'center',
      __children: [
        {
          label: '区',
          prop: 'area',
          align: 'center',
        },
        {
          label: '县',
          prop: 'county',
          align: 'center',
        }
      ]
    }
  ]
}

image.pngНаконец, по нашимjsonОписание данных, чтобы получить то, что мы хотим.

Проверяйте данные на разных страницах

Проверка по страницам на самом деле является одной из наших наиболее распространенных потребностей. Данные, которые пользователь хочет проверить, находятся в нижнем колонтитуле первой страницы и в верхнем колонтитуле второй страницы.В общем, если данные на первой странице проверяются, а затем щелкают на второй странице, данные обновляются. Нет проверки.Если вам нужно проверить по страницам, это на самом деле очень просто.Элемент-ui на самом деле предоставляет эту функцию.В основном используются два атрибута.

резервный выбор: допустимо только для столбцов с типом = выбор, тип — логический, если true, ранее выбранные данные будут сохранены после обновления данных (необходимо указатьrow-key)

Ключ строки: ключ данных строки, используемый для оптимизации рендеринга таблицы; это свойство требуется при использовании функции резерва. Когда тип строки, поддерживается многоуровневый доступ:user.info.id, но не поддерживаетuser.info[0].id, в этом случае используйтеFunction

// 对组件外部暴露一个 isCheckMemory 属性,默认false, 需要跨页,设置成true
props: {
     /**
     * 是否需要跨页勾选
     */
    isCheckMemory: {
      type: Boolean,
      default: false
    },
    /**
     * 表格行数据的唯一键
     */
    idKey: {
      type: String,
      default: 'id'
    }
}
// 给table设置row-key,给type="selection"的tableColumn设置reserve-selection
<template>
    <el-table 
      ref="__table"
      :data="tableData"
      v-bind="$attrs"
      :row-key="idKey"
      v-on="listeners">
      <el-table-column
        v-if="isCheck"
        align="center"
        width="70"
        type="selection"
        :reserve-selection="isCheckMemory"
      >
      </el-table-column>
      ...
    </el-table>
</template>

Вышеизложенное сделает перекрестную проверку хорошо выполненной. Затем делаем то же самое, что и раньше.selection-changeВы можете проверить все данные. Здесь стоит отметить, что,v-on="listeners", Если компоненту нужно сделать какие-то операции над проверяемыми данными, мы можем написать это так:

  computed:  {
    listeners: function () {
      var vm = this
      return Object.assign(
        {}, 
        this.$listeners, {
          'selection-change': function (val) {
            // dosomething
            vm.selectData = val
            vm.$emit('selection-change', val)
          }
        }
      )
    }
  },

сортировка столбцов

Сортировка столбцов на самом деле является относительно распространенной функцией, потому что таблицы, как правило, представляют собой данные с разбивкой на страницы, и внешний интерфейс не может получить все данные, поэтому большая часть сортировки выполняется бэкэндом, а внешний интерфейс отвечает за передачу некоторых полей бэкэнду. сообщая бэкенду, что нужно следовать за полем, которое нужно сортировать, по убыванию или по возрастанию.

//props 外部传递一个sortArr,告诉组件哪一些字段需要排序
/**
 * 排序
 */
sortArr: {
  type: Array,
  default: () => { return [] }
}

// el-table-column中加入sortable
<el-table-column
  v-else
  :key="item.prop"
  v-bind="item"
  :sortable="sortFun(item.prop) ? 'custom' : false "
  show-overflow-tooltip> 
    <template v-if="item.__slotName" v-slot="scope">
      <slot :name="item.__slotName" :data="scope"></slot>
    </template>
</el-table-column>

// methods
sortFun (prop) {
  if (this.sortArr && this.sortArr.length > 0) {
    return this.sortArr.indexOf(prop) > -1
  } else {
    return false
  }
}

// 监听事件 sort-change
listeners: function () {
  var vm = this
  return Object.assign(
    {}, 
    this.$listeners, {
      'sort-change': function (column) {
        vm.order.sortName = column.prop
        switch (column.order) {
          case 'ascending':
            vm.order.sortOrder = 'asc'
            break
          case 'descending':
            vm.order.sortOrder = 'desc'
            break
          default:
            vm.order.sortOrder = ''
        }
        vm.$emit('sortChange', vm.order)
      }
    }
  )
}

// 用法
// 加入 sortArr 去设置哪一些需要排序
<mini-table size="small" border :sortArr="['age', 'area', 'county']" :columns="columns"></mini-table>

image.pngТогда мы сможем узнать, какие поля нужно отсортировать.Просто передайте sortName и sortOrder в фоновый режим, чтобы выполнить новый запрос, и все будет в порядке.

накопление серийного номера

В списке наших требований нужно накопить порядковый номер. Например, первая страница отображает серийный номер от 1 до 20. Если он не обрабатывается и переключается на вторую страницу, отображаемый серийный номер по-прежнему 1-20. Если мы хотим, чтобы он отображал серийный номер от 21 до 40, нам нужно сделать немного обработки.

// template 加入 :index="typeIndex"
<el-table-column
    v-if="isIndex"
    show-overflow-tooltip
    align="center"
    :index="typeIndex"
    type="index"
    :fixed="fixed">
      <template slot="header">
        <span>序号</span>
      </template>
</el-table-column>
      
// methods
typeIndex (index) {
  const tabIndex = index + (this.pagination.pageIndex - 1) * this.pagination.pageSize + 1
  return tabIndex
}

Здесь мы выполняем некоторую обработку в соответствии с номером страницы, чтобы достичь желаемых ожиданий.

Данные запроса

Поскольку возможно, что для таблицы нет условия запроса, есть только одна таблица, поэтому мы поместили функцию запроса данных в табличный компонент, чтобы сделать это. После этого компонент запроса будет создан самостоятельно.Компонент запроса собирает введенные пользователем условия запроса, а затем передает их табличному компоненту.Для запроса сначала введитеaxios, потому что на нашей стороне нет онлайн-апи для поддержки, поэтому я использую его здесьmockjs, Сначала давайте посмотрим на axios

import axios from 'axios'

const service = axios.create({
  baseURL: '/',
  timeout: 10000
})

export const get = function (url, params) {
  return service.get(url, { params })
}

export const post = function (url, data) {
  return service.post(url, { data })
}

Вот, по ситуации фон компании, добавить перехватчик. Я просто экспортирую сюдаgetа такжеpostметод.mockjsЕсли это так, вы можете пойти в Интернет, чтобы найти информацию и посмотреть, я вставлю код прямо сюда и кратко расскажу об этом.

import Mock from 'mockjs'

const data = Mock.mock({
  "list|60-400": [
    {
      "id": '@increment(1)',  // 生成累加的id
      "name": "@cname()", // 生成名称
      "age|1-50": 1, // 生成1-50的数字
      "avatar": "@image('40x40', '#50B347', '#FFF', 'Mock.js')", // 生成 40*40的头像
      "hobby": "@ctitle(6)", // 生成6字废文
      "education": "@ctitle(6)", // 生成6字废文
      "nativePlace": "@ctitle(6)", // 生成6字废文
      "province": "@ctitle(6)", // 生成6字废文
      "area": "@ctitle(6)", // 生成6字废文
      "county": "@ctitle(6)", // 生成6字废文
      "remark": "@csentence(20)", // 生成20字废文
    }
  ]
})

// 拦截axios发出的getList接口, 返回上边定义的data数据
Mock.mock(/\/getList/, 'get', (options) => {
   // 获取传递的参数pageindex
   const pagenum = getQuery(options.url,'pageOffset')
   // 获取传递的参数pagesize
   const pagesize = getQuery(options.url,'pageSize')
   // 截取数据的起始位置
   const start = (pagenum-1)*pagesize
   // 截取数据的终点位置
   const end = pagenum*pagesize
   // 计算总页数
   const totalPage = Math.ceil(data.list.length/pagesize)
   // 数据的起始位置:(pageindex-1)*pagesize  数据的结束位置:pageindex*pagesize
   const list = pagenum>totalPage?[]:data.list.slice(start,end)

  return {
    status: 200,
    success: true,
    message: '获取新闻列表成功',
    list: list,
    total: data.list.length
  }
})

// 拿到query接口?和&符号后边的参数
const getQuery = (url,name)=>{
  const index = url.indexOf('?')
  if(index !== -1) {
    const queryStrArr = url.substr(index+1).split('&')
    for(var i=0;i<queryStrArr.length;i++) {
      const itemArr = queryStrArr[i].split('=')
      if(itemArr[0] === name) {
        return itemArr[1]
      }
    }
  }
  return null
}

export default Mock

Данные перехвата и определения mockJS кратко объясняются в комментариях к приведенному выше коду. Тогда метод запроса выглядит следующим образом

    async queryData (isReset) {
      if (this.query && this.query.url) {
        this.loading = true
        this.pagination.pageIndex = isReset ? this.pagination.pageIndex : 1
        let param = Object.assign(
          {},
          this.query.queryParam, {
            [this.pageParam.pageOffset]: this.pagination.pageIndex,
            [this.pageParam.pageSize]: this.pagination.pageSize
          })
        if (this.order.sortName && this.order.sortOrder) {
          param.sortName = this.order.sortName
          param.sortOrder = this.order.sortOrder
        }
        let result = null
        try {
          switch(this.query.method) {
            case 'get':
              result = await get(this.query.url, param)
              break
            case 'post':
              result = await post(this.query.url, param)
              break
            default:
              result = await get(this.query.url, param)
              break
          }
        } catch(e) {
          console.warn(e)
        } finally {
          this.loading = false
          const { data } = result
          if (data && data.success) {
            this.pagination.totalRow = data.total
            this.tableData = data.list
            this.$emit('fetchData', data)
          } else {
            this.$message({
              type: 'warning',
              message: data ? data.message : '查询失败!'
            })
            this.tableData = []
            this.pagination.pageIndex = 1
            this.pagination.totalRow = 0
          }
        }
      }
    }

Есть несколько моментов, которые нужно объяснить, интерфейс запроса, для хорошего опыта. Добавить кloading, после завершения запросаloadingУстановите значение false. Еще кое-чтоpageIndexа такжеpageSize, Фон у всех разный, поэтому поле номера страницы у всех может называться по-разному. мы можемpropsВведите название поля номера страницы на своем собственном фоне, после чего оно будет соответствующим образом изменено на нужное вам поле.

/**
 * 分页需要传入后台的字段
 */
pageParam: {
  type: Object,
  default: () => {
    return {
      pageOffset: 'pageOffset',
      pageSize: 'pageSize'
    }
  }
},

Тогда метод поддерживаетpostа такжеgetДва метода запроса, вам нужно только передать компонент при использовании компонента

    <mini-table :isAutoQuery="true" :query="query" size="small" border :sortArr="['age', 'area', 'county']" :columns="columns">...</mini-table>
  
// data
query: {
    url: '/getList',
    method: 'get',
    queryParam: {}
},

хорошо, тогда давайте посмотрим на окончательный эффект:

13.gif

напиши в конце

хорошо, тогда некоторые функции, которые должна иметь часто используемая форма, мы в основном интегрировали в компоненты, которые мы написали сами. Если есть небольшие проблемы в использовании, нам нужно только внести некоторые изменения внутри компонента.После использования его в проекте в течение определенного периода времени компонент будет становиться все более и более стабильным, и в то же время стабильным и гибким время. Чем позже выполнен проект, тем больше он может отражать ценность такого компонента. Позже на этой основе мы добавим функцию поиска по форме и функцию динамического заголовка.Если вы считаете, что статья наиболее полезна для вас, она может вам понравиться и подписаться на нее. Кроме того, после выполнения всех упомянутых мною функций. Я отправлю код в gitee для тех, кто нуждается в его клонировании.Увидимся в следующий раз!