vue3+element-plus 表格组件封装

二次封装 element-plus 的 table 组件,支持头部查询表单和分页显示

vue 复制代码
<template>
  <query-filter
    v-if="searchable"
    :fields="fields"
    @submit="onSearch"
    @reset="onReset"
  ></query-filter>
  <el-table v-loading="loading" :data="tableData">
    <template v-for="column in columns">
      <el-table-column v-bind="column" v-if="column.slot">
        <slot :name="column.slot" :column="column"></slot>
      </el-table-column>
      <component
        v-else-if="column.component"
        :is="column.component"
        :config="column"
      ></component>
      <el-table-column
        v-else
        v-bind="column"
        align="center"
        :show-overflow-tooltip="true"
      ></el-table-column>
    </template>
  </el-table>
  <el-pagination
    v-if="pageConfig"
    class="mt20 flexEnd"
    v-bind="pageConfig"
    :current-page="params.page"
    :page-size="params.pageSize"
    :total="total"
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
  />
</template>

<script setup lang="ts" generic="T">
import { IResponse } from '@/api/type'
import { watch, ref, onMounted, computed } from 'vue'
import QueryFilter from '../QueryFilter/index.vue'
import { IField } from '../ProForm/type'

interface ITableColumn extends IField {
  prop: string
  label: string
  hideInSearch?: boolean
  [x: string]: any
}

interface IPageConfig {
  background: boolean
  pageSizes: number[]
  layout: string
}

const props = withDefaults(
  defineProps<{
    columns: ITableColumn[]
    pageConfig?: IPageConfig | false
    searchable?: boolean
  }>(),
  {
    searchable: true,
    pageConfig: {
      background: true,
      pageSizes: [10, 20, 50, 100],
      layout: 'total, sizes, prev, pager, next, jumper',
    },
  }
)

const emit = defineEmits(['request'])

const tableData = ref<T[]>([])
const loading = ref(false)
const total = ref(0)

const initialParams = props.pageConfig
  ? {
      pageSize: 10,
      page: 1,
    }
  : {}

let params = ref<Record<string, any>>(initialParams)

const fields = computed(() => {
  return props.columns.filter((item) => !item.hideInSearch && item.prop)
})

const fetchList = async () => {
  loading.value = true
  emit(
    'request',
    params.value,
    (resp: IResponse<{ list: T[]; total: number }>) => {
      if (resp.code === 200) {
        tableData.value = resp.data?.list || []
        total.value = resp?.data?.total
        loading.value = false
      } else {
        loading.value = false
      }
    }
  )
}

const handleSizeChange = (pageSize: number) => {
  params.value = {
    pageSize,
    page: 1,
  }
}

const handleCurrentChange = (page: number) => {
  params.value.page = page
}

const onSearch = (values = {}) => {
  params.value = {
    ...params.value,
    ...values,
    page: initialParams?.page,
  }
}

const onReset = () => {
  params.value = {
    page: initialParams?.page,
    pageSize: params.value.pageSize,
  }
}

watch(
  params,
  () => {
    fetchList()
  },
  {
    deep: true,
  }
)

onMounted(() => {
  fetchList()
})
</script>
<style scoped lang="scss">
.flexEnd {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
</style>

关于 ref 和 reactive 的响应式问题

vue 文档说 ref 对象是可更改的响应式的,对 value 的所有操作都将被追踪,并且写操作会触发与之相关的副作用。 reactive 的响应式是深层次的,会影响所有的嵌套

因此我想 params 是使用 reactive 实现响应式会比较好,但是在实际应用中

对 params 进行整体赋值的修改,没有触发到 watch 变化,需要对 params 的属性值一个一个进行修改

查询之后得到原因是,因为把对象直接赋值给 reactive 创建的对象,会导致 reactive 创建的对象被新赋值的对象直接代理,而 vue3 中都是 proxy 代理对象,就失去了响应式

可以看这个博客的说明vue3 中 reactive 赋值,不能响应式变化

使用

vue 复制代码
<template>
  <pro-table :columns="columns" @request="request">
    <template #inspectionType="{ row }">
      {{ row?.inspectionType === 'ELEVATOR' ? '升降梯' : '扶梯' }}
    </template>
  </pro-table>
</template>

<script setup lang="ts">
import { ProTable } from '@/components'
import { getTableList } from '@/api/components'
import { readonly } from 'vue'

const columns = readonly([
  { type: 'selection' },
  {
    prop: 'inspectionId',
    label: '工单ID',
    hideInSearch: true,
  },
  {
    prop: 'eleContractNo',
    label: '产品合同号',
    type: 'input',
  },
  {
    prop: 'mntContractNo',
    label: '保养合同号',
    type: 'input',
  },
  {
    prop: 'customerName',
    label: '客户名称',
    type: 'input',
  },
  {
    prop: 'inspectionType',
    label: '作业类型',
    slot: 'inspectionType',
    type: 'select',
    fieldProps: {
      options: [
        { label: '升降梯', value: 'ELEVATOR' },
        { label: '扶梯', value: 'ESCALATOR' },
      ],
    },
  },
  {
    prop: 'userName',
    label: '保养巡视人员',
    type: 'input',
  },
  {
    prop: 'inspectionDate',
    label: '巡视时间',
    type: 'datePicker',
  },
  {
    prop: 'orderCode',
    label: '订单状态',
    type: 'select',
    fieldProps: {
      options: [
        'INITIATED',
        'CHANGE_REQUEST',
        'INSPECTION_AUDIT',
        'COMPLETE',
      ].map((item) => ({ label: item, value: item })),
    },
  },
])

const request = async (params, cb) => {
  const resp = await getTableList(params)
  cb?.(resp)
}
</script>
<style scoped></style>

效果

github 地址

预览地址

相关推荐
持续前行5 小时前
vscode 中找settings.json 配置
前端·javascript·vue.js
JosieBook5 小时前
【Vue】11 Vue技术——Vue 中的事件处理详解
前端·javascript·vue.js
安逸点5 小时前
Vue项目中使用xlsx库解析Excel文件
vue.js
一只小阿乐5 小时前
vue 改变查询参数的值
前端·javascript·vue.js·路由·router·网文·未花中文网
小酒星小杜6 小时前
在AI时代下,技术人应该学会构建自己的反Demo地狱系统
前端·vue.js·ai编程
Code知行合壹6 小时前
Pinia入门
vue.js
今天也要晒太阳4736 小时前
element表单和vxe表单联动校验的实现
vue.js
依赖_赖8 小时前
前端实现token无感刷新
前端·javascript·vue.js
hhcccchh8 小时前
学习vue第十三天 Vue3组件深入指南:组件的艺术与科学
javascript·vue.js·学习
zhengxianyi5159 小时前
ruoyi-vue-pro本地环境搭建(超级详细,带异常处理)
前端·vue.js·前后端分离·ruoyi-vue-pro