312 lines
9.1 KiB
Vue
312 lines
9.1 KiB
Vue
<script lang="tsx">
|
||
import { ElTable, ElTableColumn, ElPagination } from 'element-plus'
|
||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||
import { propTypes } from '@/utils/propTypes'
|
||
import { setIndex } from './helper'
|
||
import { getSlot } from '@/utils/tsxHelper'
|
||
import type { TableProps } from './types'
|
||
import { set } from 'lodash-es'
|
||
import { Pagination, TableColumn, TableSetPropsType, TableSlotDefault } from '@/types/table'
|
||
|
||
export default defineComponent({
|
||
// eslint-disable-next-line vue/no-reserved-component-names
|
||
name: 'Table',
|
||
props: {
|
||
pageSize: propTypes.number.def(10),
|
||
currentPage: propTypes.number.def(1),
|
||
// 是否多选
|
||
selection: propTypes.bool.def(false),
|
||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||
showOverflowTooltip: propTypes.bool.def(true),
|
||
// 表头
|
||
columns: {
|
||
type: Array as PropType<TableColumn[]>,
|
||
default: () => []
|
||
},
|
||
// 展开行
|
||
expand: propTypes.bool.def(false),
|
||
// 是否展示分页
|
||
pagination: {
|
||
type: Object as PropType<Pagination>,
|
||
default: (): Pagination | undefined => undefined
|
||
},
|
||
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
|
||
reserveSelection: propTypes.bool.def(false),
|
||
// 加载状态
|
||
loading: propTypes.bool.def(false),
|
||
// 是否叠加索引
|
||
reserveIndex: propTypes.bool.def(false),
|
||
// 对齐方式
|
||
align: propTypes.string
|
||
.validate((v: string) => ['left', 'center', 'right'].includes(v))
|
||
.def('center'),
|
||
// 表头对齐方式
|
||
headerAlign: propTypes.string
|
||
.validate((v: string) => ['left', 'center', 'right'].includes(v))
|
||
.def('center'),
|
||
data: {
|
||
type: Array as PropType<Recordable[]>,
|
||
default: () => []
|
||
}
|
||
},
|
||
emits: ['update:pageSize', 'update:currentPage', 'register'],
|
||
setup(props, { attrs, slots, emit, expose }) {
|
||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||
|
||
// 注册
|
||
onMounted(() => {
|
||
const tableRef = unref(elTableRef)
|
||
emit('register', tableRef?.$parent, elTableRef)
|
||
})
|
||
|
||
const pageSizeRef = ref(props.pageSize)
|
||
|
||
const currentPageRef = ref(props.currentPage)
|
||
|
||
// useTable传入的props
|
||
const outsideProps = ref<TableProps>({})
|
||
|
||
const mergeProps = ref<TableProps>({})
|
||
|
||
const getProps = computed(() => {
|
||
const propsObj = { ...props }
|
||
Object.assign(propsObj, unref(mergeProps))
|
||
return propsObj
|
||
})
|
||
|
||
const setProps = (props: TableProps = {}) => {
|
||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||
outsideProps.value = props
|
||
}
|
||
|
||
const setColumn = (columnProps: TableSetPropsType[], columnsChildren?: TableColumn[]) => {
|
||
const { columns } = unref(getProps)
|
||
for (const v of columnsChildren || columns) {
|
||
for (const item of columnProps) {
|
||
if (v.field === item.field) {
|
||
set(v, item.path, item.value)
|
||
} else if (v.children?.length) {
|
||
setColumn(columnProps, v.children)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
const selections = ref<Recordable[]>([])
|
||
|
||
const selectionChange = (selection: Recordable[]) => {
|
||
selections.value = selection
|
||
}
|
||
|
||
expose({
|
||
setProps,
|
||
setColumn,
|
||
selections
|
||
})
|
||
|
||
const pagination = computed(() => {
|
||
// update by 芋艿:保持和 Pagination 组件的逻辑一致
|
||
return Object.assign(
|
||
{
|
||
small: false,
|
||
background: true,
|
||
pagerCount: document.body.clientWidth < 992 ? 5 : 7,
|
||
layout: 'total, sizes, prev, pager, next, jumper',
|
||
pageSizes: [10, 20, 30, 50, 100],
|
||
disabled: false,
|
||
hideOnSinglePage: false,
|
||
total: 10
|
||
},
|
||
unref(getProps).pagination
|
||
)
|
||
})
|
||
|
||
watch(
|
||
() => unref(getProps).pageSize,
|
||
(val: number) => {
|
||
pageSizeRef.value = val
|
||
}
|
||
)
|
||
|
||
watch(
|
||
() => unref(getProps).currentPage,
|
||
(val: number) => {
|
||
currentPageRef.value = val
|
||
}
|
||
)
|
||
|
||
watch(
|
||
() => pageSizeRef.value,
|
||
(val: number) => {
|
||
emit('update:pageSize', val)
|
||
}
|
||
)
|
||
|
||
watch(
|
||
() => currentPageRef.value,
|
||
(val: number) => {
|
||
emit('update:currentPage', val)
|
||
}
|
||
)
|
||
|
||
const getBindValue = computed(() => {
|
||
const bindValue: Recordable = { ...attrs, ...props }
|
||
delete bindValue.columns
|
||
delete bindValue.data
|
||
return bindValue
|
||
})
|
||
|
||
const renderTableSelection = () => {
|
||
const { selection, reserveSelection, align, headerAlign } = unref(getProps)
|
||
// 渲染多选
|
||
return selection ? (
|
||
<ElTableColumn
|
||
type="selection"
|
||
reserveSelection={reserveSelection}
|
||
align={align}
|
||
headerAlign={headerAlign}
|
||
width="50"
|
||
></ElTableColumn>
|
||
) : undefined
|
||
}
|
||
|
||
const renderTableExpand = () => {
|
||
const { align, headerAlign, expand } = unref(getProps)
|
||
// 渲染展开行
|
||
return expand ? (
|
||
<ElTableColumn type="expand" align={align} headerAlign={headerAlign}>
|
||
{{
|
||
// @ts-ignore
|
||
default: (data: TableSlotDefault) => getSlot(slots, 'expand', data)
|
||
}}
|
||
</ElTableColumn>
|
||
) : undefined
|
||
}
|
||
|
||
const rnderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||
const { align, headerAlign, showOverflowTooltip } = unref(getProps)
|
||
return columnsChildren.map((v) => {
|
||
const props = { ...v }
|
||
if (props.children) delete props.children
|
||
return (
|
||
<ElTableColumn
|
||
showOverflowTooltip={showOverflowTooltip}
|
||
align={align}
|
||
headerAlign={headerAlign}
|
||
{...props}
|
||
prop={v.field}
|
||
>
|
||
{{
|
||
default: (data: TableSlotDefault) =>
|
||
v.children && v.children.length
|
||
? rnderTableColumn(v.children)
|
||
: // @ts-ignore
|
||
getSlot(slots, v.field, data) ||
|
||
v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
|
||
data.row[v.field],
|
||
// @ts-ignore
|
||
header: getSlot(slots, `${v.field}-header`)
|
||
}}
|
||
</ElTableColumn>
|
||
)
|
||
})
|
||
}
|
||
|
||
const rnderTableColumn = (columnsChildren?: TableColumn[]) => {
|
||
const {
|
||
columns,
|
||
reserveIndex,
|
||
pageSize,
|
||
currentPage,
|
||
align,
|
||
headerAlign,
|
||
showOverflowTooltip
|
||
} = unref(getProps)
|
||
return [...[renderTableExpand()], ...[renderTableSelection()]].concat(
|
||
(columnsChildren || columns).map((v) => {
|
||
// 自定生成序号
|
||
if (v.type === 'index') {
|
||
return (
|
||
<ElTableColumn
|
||
type="index"
|
||
index={
|
||
v.index
|
||
? v.index
|
||
: (index) => setIndex(reserveIndex, index, pageSize, currentPage)
|
||
}
|
||
align={v.align || align}
|
||
headerAlign={v.headerAlign || headerAlign}
|
||
label={v.label}
|
||
width="65px"
|
||
></ElTableColumn>
|
||
)
|
||
} else {
|
||
const props = { ...v }
|
||
if (props.children) delete props.children
|
||
return (
|
||
<ElTableColumn
|
||
showOverflowTooltip={showOverflowTooltip}
|
||
align={align}
|
||
headerAlign={headerAlign}
|
||
{...props}
|
||
prop={v.field}
|
||
>
|
||
{{
|
||
default: (data: TableSlotDefault) =>
|
||
v.children && v.children.length
|
||
? rnderTreeTableColumn(v.children)
|
||
: // @ts-ignore
|
||
getSlot(slots, v.field, data) ||
|
||
v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||
|
||
data.row[v.field],
|
||
// @ts-ignore
|
||
header: () => getSlot(slots, `${v.field}-header`) || v.label
|
||
}}
|
||
</ElTableColumn>
|
||
)
|
||
}
|
||
})
|
||
)
|
||
}
|
||
|
||
return () => (
|
||
<div v-loading={unref(getProps).loading}>
|
||
<ElTable
|
||
// @ts-ignore
|
||
ref={elTableRef}
|
||
data={unref(getProps).data}
|
||
onSelection-change={selectionChange}
|
||
{...unref(getBindValue)}
|
||
>
|
||
{{
|
||
default: () => rnderTableColumn(),
|
||
// @ts-ignore
|
||
append: () => getSlot(slots, 'append')
|
||
}}
|
||
</ElTable>
|
||
{unref(getProps).pagination ? (
|
||
// update by 芋艿:保持和 Pagination 组件一致
|
||
<ElPagination
|
||
v-model:pageSize={pageSizeRef.value}
|
||
v-model:currentPage={currentPageRef.value}
|
||
class="float-right mt-15px mb-15px"
|
||
{...unref(pagination)}
|
||
></ElPagination>
|
||
) : undefined}
|
||
</div>
|
||
)
|
||
}
|
||
})
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
:deep(.el-button.is-text) {
|
||
padding: 8px 4px;
|
||
margin-left: 0;
|
||
}
|
||
|
||
:deep(.el-button.is-link) {
|
||
padding: 8px 4px;
|
||
margin-left: 0;
|
||
}
|
||
</style>
|