likes
comments
collection
share

使用tsx封装动态table

作者站长头像
站长
· 阅读数 14

纸上得来终觉浅,绝知此事要躬行。一直以来都没用过vuetsx,最近闲来无事,尝试用tsx封装一个动态表格。

技术栈

  • vue3
  • ts
  • element-plus

实现代码

定义数据类型

  • TableColumn: 描述表格项的配置
export interface TableColumnI {
  label: string;
  prop: string;
  width?: number | string;
  fixed?: string;
  align?: string;
  visible?: boolean;
  methods?: string;
  render?: RenderI;
  formatter?: string | Function;
  dict?: string   // 字典,如果存在的话会根据字典类型和值转成其描述,如
}
  • RenderI: 自定义表格渲染的内容或组件
interface RenderI {
  (row: TableData): VNode | JSX.Element
}
  • TableDataI: 表格行数据类型
export interface TableDataI {
  [key: string]: any;
}
  • OptionColumnI: 操作列配置项
  label: string;
  label: string;
  width?: number | string;
  fixed?: string;
  align?: string;
  visible?: boolean;
  buttons: OptionButtonI[]
  • OptionButtonI: 操作列中按钮配置项
  label: string;
  icon?: string;
  type?: ButtonType;
  method?: Function;
  disabled?: boolean;
  size?: string;
  popconfirm?: boolean;
  group?: OptionButtonI[]

tsx文件

import { defineComponent, ref, type PropType } from 'vue'
import {
  type OptionButtonI,
  type OptionColumnI,
  type TableColumnI,
  type TableDataI
} from '@/types/dtable'
import '@/styles/dtable.scss'
import { formatDate, formatMoney } from '@/utils/format'
import { dictDataLabel } from '@/plugins/DictPlugin'  自定义翻译字典的插件
import Money from '../money'  // 自定义展示金额的组件

export default defineComponent({
  name: 'dtable',
  props: {
    columns: {
      type: Array as PropType<TableColumnI[]>,
      default: () => []
    },
    datas: {
      type: Array as PropType<TableDataI[]>,
      default: () => []
    },
    options: {
      type: Object as PropType<OptionColumnI>,
      default: () => {}
    },
    loading: {
      type: Boolean,
      default: false
    },
    /**
     * 动态绑定 key 值
     */
    keyId: {
      type: String,
      default: 'id'
    },
    /**
     * 行内自定义样式配置
     */
    rowStyle: {
      type: Object,
      default: () => {
        return {
          height: '40px'
        }
      }
    }
  },
  setup(props, ctx) {
    const { emit, expose, slots } = ctx
    const tableRef = ref(null)
    expose({
      tableRef
    })
    return () => (
      <div v-loading={props.loading}>
        <el-table
          class="d-table"
          ref={tableRef}
          data={props.datas}
          border
          row-style={props.rowStyle}
          header-row-class-name="d-table-header"
          row-key={props.keyId}
        >
          {props.columns.map((item: TableColumnI) => {
            if (!item.formatter) {
              // 格式化时间
              if (item.type === 'dateTime') {
                item.formatter = (row: TableDataI, column: TableColumnI, cellValue: string) =>
                  formatDate(cellValue)
              } else if (item.type === 'date') {
                item.formatter = (row: TableDataI, column: TableColumnI, cellValue: string) =>
                  formatDate(cellValue, 'YYYY-MM-DD')
              } else if (item.type === 'money') {
                // 格式化金额
                item.formatter = (
                  row: TableDataI,
                  column: TableColumnI,
                  cellValue: number | bigint
                ) => formatMoney(cellValue)
              }
            }

            // 根据字典类型翻译数据
            if (item.dict) {
              item.formatter = (
                row: TableDataI,
                column: TableColumnI,
                cellValue: string | number | boolean
              ) => {
                if (item.dict) {
                  return dictDataLabel(item.dict, cellValue).value
                }
              }
            }

            return (
              <el-table-column
                key={item.prop}
                width={item.width ?? ''}
                align={item.align ?? 'center'}
                label={item.label}
                fixed={item.fixed}
                prop={item.prop}
                formatter={item.formatter}
                v-slots={{
                  default: (scope: { row: TableDataI }) => {
                    if (item.render) {
                      return item.render(scope.row)
                    } else {
                      if (item.type === 'money') {
                        // tablecolumn的类型时'money'时使用自定义的组件显示
                        return <Money money={scope.row[item.prop]}></Money>
                      }
                    }
                  }
                }}
              ></el-table-column>
            )
          })}
          {props.options && (
            <el-table-column
              width={props.options.width}
              label={props.options.label}
              fixed={props.options.fixed}
              align={props.options.align ?? 'center'}
              v-slots={{
                default: (scope: { row: TableDataI }) => {
                  return (
                    props.options.buttons && (
                      <div class="flex-box">
                        {props.options.buttons.map((button: OptionButtonI) => {
                          if (button.group) {
                            return (
                              <el-button
                                size={button.size}
                                type={button.type ?? 'default'}
                                icon={button.icon}
                                disabled={button.disabled}
                                v-slots={{
                                  default: () => {
                                    const popRef = ref()
                                    return (
                                      <el-dropdown
                                        trigger="click"
                                        hide-on-click={false}
                                        ref={popRef}
                                        id={scope.row.id}
                                        v-slots={{
                                          dropdown: () => {
                                            return (
                                              <el-dropdown-menu>
                                                {button.group?.map((item1) => {
                                                  if (item1.type === 'danger' && item1.popconfirm) {
                                                    return (
                                                      <el-dropdown-item>
                                                        <el-popconfirm
                                                          title="确定删除这个条数据吗?"
                                                          onConfirm={() => {
                                                            if (item1.method) {
                                                              item1.method(scope.row)
                                                            }
                                                            popRef.value.handleClose()
                                                          }}
                                                          onCancel={() => {
                                                            popRef.value.handleClose()
                                                          }}
                                                          v-slots={{
                                                            reference: () => {
                                                              return (
                                                                <el-button
                                                                  size={item1.size}
                                                                  type={item1.type ?? 'default'}
                                                                  icon={item1.icon}
                                                                  disabled={item1.disabled}
                                                                >
                                                                  {item1.label}
                                                                </el-button>
                                                              )
                                                            }
                                                          }}
                                                        ></el-popconfirm>
                                                      </el-dropdown-item>
                                                    )
                                                  } else {
                                                    return (
                                                      <el-dropdown-item>
                                                        <el-button
                                                          size={item1.size}
                                                          type={item1.type ?? 'default'}
                                                          icon={item1.icon}
                                                          disabled={item1.disabled}
                                                          onClick={() => {
                                                            if (item1.method) {
                                                              return item1.method(scope.row)
                                                            }
                                                          }}
                                                        >
                                                          {item1.label}
                                                        </el-button>
                                                      </el-dropdown-item>
                                                    )
                                                  }
                                                })}
                                              </el-dropdown-menu>
                                            )
                                          }
                                        }}
                                      >
                                        <span class="el-dropdown-link">
                                          {button.label}
                                          <el-icon class="el-icon--right">
                                            <arrow-down />
                                          </el-icon>
                                        </span>
                                      </el-dropdown>
                                    )
                                  }
                                }}
                              ></el-button>
                            )
                          }

                          if (button.type === 'danger' && button.popconfirm) {
                            return (
                              <el-popconfirm
                                title="确定删除这个条数据吗?"
                                onConfirm={() => button.method(scope.row)}
                                v-slots={{
                                  reference: () => {
                                    return (
                                      <el-button
                                        size={button.size}
                                        type={button.type ?? 'default'}
                                        icon={button.icon}
                                        disabled={button.disabled}
                                      >
                                        {button.label}
                                      </el-button>
                                    )
                                  }
                                }}
                              ></el-popconfirm>
                            )
                          } else {
                            return (
                              <el-button
                                size={button.size}
                                type={button.type ?? 'default'}
                                icon={button.icon}
                                disabled={button.disabled}
                                onClick={() => button.method(scope.row)}
                              >
                                {button.label}
                              </el-button>
                            )
                          }
                        })}
                      </div>
                    )
                  )
                }
              }}
            ></el-table-column>
          )}
        </el-table>
      </div>
    )
  }
})

使用方式

<dtable :columns="columns" :datas="tableDatas" :options="options" />
const columns: TableColumn[] = [
  {
    label: '名称',
    prop: 'name'
  },
  {
    label: '类型',
    prop: 'type'
  },
  {
    label: '更新时间',
    prop: 'updateTime',
    type: 'date'
  },
  {
    label: '系统内置',
    prop: 'system',
    dict: 'YesOrNo'
  },
  {
    label: '状态',
    prop: 'status',
    dict: 'AvailableStatus'
  }
]

const options: OptionColumn = {
  label: '操作',
  width: '320px',
  buttons: [
    {
      label: '编辑',
      type: 'primary',
      method: (row: any) => {
        dialogObj.dialogVisible = true
        dialogObj.dialogTitle = '编辑字典'
        dictId.value = row.id
      }
    },
    {
      label: '字典项',
      type: 'warning',
      method: (row: any) => {
        itemDrawerVisible.value = true
        dictId.value = row.id
      }
    },
    {
      label: '更多操作',
      group: [
        {
          label: '删除',
          type: 'danger',
          popconfirm: true,
          method: (row: any) => {
            delDictApi(row.id).then(() => {
              handleQuery()
            })
          }
        }
      ]
    }
  ]
}

let tableDatas = ref([])
const loading = ref(false)

效果 使用tsx封装动态table

遇到的问题

  1. 绑定事件 绑定事件时需要加on, 如给<el-button> 绑定click事件时需要使用onClick, 给<el-popconfirm>组件绑定confirm事件时需要使用onConfirm

使用tsx封装动态table

  1. 向插槽提供内容时使用v-slotdefault插槽提供内容时如下: 使用tsx封装动态table
转载自:https://juejin.cn/post/7272569858977022012
评论
请登录