first commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import Table from './src/Table.vue'
|
||||
import { ElTable } from 'element-plus'
|
||||
import { TableColumn, TableSetProps } from './src/types'
|
||||
|
||||
export type {
|
||||
TableColumn,
|
||||
TableSlotDefault,
|
||||
Pagination,
|
||||
TableSetProps,
|
||||
TableProps
|
||||
} from './src/types'
|
||||
|
||||
export interface TableExpose {
|
||||
setProps: (props: Recordable) => void
|
||||
setColumn: (columnProps: TableSetProps[]) => void
|
||||
addColumn: (column: TableColumn, index?: number) => void
|
||||
delColumn: (field: string) => void
|
||||
elTableRef: ComponentRef<typeof ElTable>
|
||||
}
|
||||
|
||||
export { Table }
|
||||
@@ -0,0 +1,587 @@
|
||||
<script lang="tsx">
|
||||
import {
|
||||
ElTable,
|
||||
ElTableColumn,
|
||||
ElPagination,
|
||||
ComponentSize,
|
||||
ElTooltipProps,
|
||||
ElImage,
|
||||
ElEmpty,
|
||||
ElCard
|
||||
} from 'element-plus'
|
||||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { setIndex } from './helper'
|
||||
import type { TableProps, TableColumn, Pagination, TableSetProps } from './types'
|
||||
import { set, get } from 'lodash-es'
|
||||
import { CSSProperties } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import TableActions from './components/TableActions.vue'
|
||||
import { createVideoViewer } from '@/components/VideoPlayer'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
props: {
|
||||
pageSize: propTypes.number.def(10),
|
||||
currentPage: propTypes.number.def(1),
|
||||
// 是否展示表格的工具栏
|
||||
showAction: propTypes.bool.def(false),
|
||||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||||
showOverflowTooltip: propTypes.bool.def(true),
|
||||
// 表头
|
||||
columns: {
|
||||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否展示分页
|
||||
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('left'),
|
||||
// 表头对齐方式
|
||||
headerAlign: propTypes.string
|
||||
.validate((v: string) => ['left', 'center', 'right'].includes(v))
|
||||
.def('left'),
|
||||
data: {
|
||||
type: Array as PropType<Recordable[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 图片自动预览字段数组
|
||||
imagePreview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 视频自动预览字段数组
|
||||
videoPreview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
height: propTypes.oneOfType([Number, String]),
|
||||
maxHeight: propTypes.oneOfType([Number, String]),
|
||||
stripe: propTypes.bool.def(false),
|
||||
border: propTypes.bool.def(true),
|
||||
size: {
|
||||
type: String as PropType<ComponentSize>,
|
||||
validator: (v: ComponentSize) => ['default', 'small', 'large'].includes(v)
|
||||
},
|
||||
fit: propTypes.bool.def(true),
|
||||
showHeader: propTypes.bool.def(true),
|
||||
highlightCurrentRow: propTypes.bool.def(false),
|
||||
currentRowKey: propTypes.oneOfType([Number, String]),
|
||||
// row-class-name, 类型为 (row: Recordable, rowIndex: number) => string | string
|
||||
rowClassName: {
|
||||
type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
|
||||
default: ''
|
||||
},
|
||||
rowStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
cellClassName: {
|
||||
type: [Function, String] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => string | string
|
||||
>,
|
||||
default: ''
|
||||
},
|
||||
cellStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
headerRowClassName: {
|
||||
type: [Function, String] as PropType<(row: Recordable, rowIndex: number) => string | string>,
|
||||
default: ''
|
||||
},
|
||||
headerRowStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
headerCellClassName: {
|
||||
type: [Function, String] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => string | string
|
||||
>,
|
||||
default: ''
|
||||
},
|
||||
headerCellStyle: {
|
||||
type: [Function, Object] as PropType<
|
||||
(row: Recordable, column: any, rowIndex: number) => Recordable | CSSProperties
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
rowKey: propTypes.string.def('id'),
|
||||
emptyText: propTypes.string.def('No Data'),
|
||||
defaultExpandAll: propTypes.bool.def(false),
|
||||
expandRowKeys: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: undefined
|
||||
},
|
||||
defaultSort: {
|
||||
type: Object as PropType<{ prop: string; order: string }>,
|
||||
default: () => ({})
|
||||
},
|
||||
tooltipEffect: {
|
||||
type: String as PropType<'dark' | 'light'>,
|
||||
default: 'dark'
|
||||
},
|
||||
tooltipOptions: {
|
||||
type: Object as PropType<
|
||||
Pick<
|
||||
ElTooltipProps,
|
||||
| 'effect'
|
||||
| 'enterable'
|
||||
| 'hideAfter'
|
||||
| 'offset'
|
||||
| 'placement'
|
||||
| 'popperClass'
|
||||
| 'popperOptions'
|
||||
| 'showAfter'
|
||||
| 'showArrow'
|
||||
>
|
||||
>,
|
||||
default: () => ({
|
||||
enterable: true,
|
||||
placement: 'top',
|
||||
showArrow: true,
|
||||
hideAfter: 200,
|
||||
popperOptions: { strategy: 'fixed' }
|
||||
})
|
||||
},
|
||||
showSummary: propTypes.bool.def(false),
|
||||
sumText: propTypes.string.def('Sum'),
|
||||
summaryMethod: {
|
||||
type: Function as PropType<(param: { columns: any[]; data: any[] }) => any[]>,
|
||||
default: undefined
|
||||
},
|
||||
spanMethod: {
|
||||
type: Function as PropType<
|
||||
(param: { row: any; column: any; rowIndex: number; columnIndex: number }) => any[]
|
||||
>,
|
||||
default: undefined
|
||||
},
|
||||
selectOnIndeterminate: propTypes.bool.def(true),
|
||||
indent: propTypes.number.def(16),
|
||||
lazy: propTypes.bool.def(false),
|
||||
load: {
|
||||
type: Function as PropType<(row: Recordable, treeNode: any, resolve: Function) => void>,
|
||||
default: undefined
|
||||
},
|
||||
treeProps: {
|
||||
type: Object as PropType<{ hasChildren?: string; children?: string; label?: string }>,
|
||||
default: () => ({ hasChildren: 'hasChildren', children: 'children', label: 'label' })
|
||||
},
|
||||
tableLayout: {
|
||||
type: String as PropType<'auto' | 'fixed'>,
|
||||
default: 'fixed'
|
||||
},
|
||||
scrollbarAlwaysOn: propTypes.bool.def(false),
|
||||
flexible: propTypes.bool.def(false),
|
||||
// 自定义内容
|
||||
customContent: propTypes.bool.def(false),
|
||||
cardBodyStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardBodyClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
cardWrapStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardWrapClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
|
||||
setup(props, { attrs, emit, slots, 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 } as any
|
||||
}
|
||||
|
||||
const setColumn = (columnProps: TableSetProps[], 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 addColumn = (column: TableColumn, index?: number) => {
|
||||
const { columns } = unref(getProps)
|
||||
if (index !== void 0) {
|
||||
columns.splice(index, 0, column)
|
||||
} else {
|
||||
columns.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
const delColumn = (field: string) => {
|
||||
const { columns } = unref(getProps)
|
||||
const index = columns.findIndex((item) => item.field === field)
|
||||
if (index > -1) {
|
||||
columns.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
const changSize = (size: ComponentSize) => {
|
||||
setProps({ size })
|
||||
}
|
||||
|
||||
const confirmSetColumn = (columns: TableColumn[]) => {
|
||||
setProps({ columns })
|
||||
}
|
||||
|
||||
expose({
|
||||
setProps,
|
||||
setColumn,
|
||||
delColumn,
|
||||
addColumn,
|
||||
elTableRef
|
||||
})
|
||||
|
||||
const pagination = computed(() => {
|
||||
return Object.assign(
|
||||
{
|
||||
small: false,
|
||||
background: false,
|
||||
pagerCount: 7,
|
||||
layout: 'sizes, prev, pager, next, jumper, ->, total',
|
||||
pageSizes: [10, 20, 30, 40, 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, ...unref(getProps) }
|
||||
delete bindValue.columns
|
||||
delete bindValue.data
|
||||
delete bindValue.align
|
||||
return bindValue
|
||||
})
|
||||
|
||||
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||||
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
|
||||
unref(getProps)
|
||||
return columnsChildren.map((v) => {
|
||||
if (v.hidden) return null
|
||||
const props = { ...v } as any
|
||||
if (props.children) delete props.children
|
||||
|
||||
const children = v.children
|
||||
|
||||
const slots = {
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
let isPreview = false
|
||||
isPreview =
|
||||
imagePreview.some((item) => (item as string) === v.field) ||
|
||||
videoPreview.some((item) => (item as string) === v.field)
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isPreview
|
||||
? renderPreview(get(data.row, v.field), v.field)
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
slots['header'] = (...args: any[]) => props.slots.header(...args)
|
||||
}
|
||||
|
||||
return (
|
||||
<ElTableColumn
|
||||
showOverflowTooltip={showOverflowTooltip}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
{...props}
|
||||
prop={v.field}
|
||||
>
|
||||
{slots}
|
||||
</ElTableColumn>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const renderPreview = (url: string, field: string) => {
|
||||
const { imagePreview, videoPreview } = unref(getProps)
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
{imagePreview.includes(field) ? (
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%]"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
) : videoPreview.includes(field) ? (
|
||||
<BaseButton
|
||||
type="primary"
|
||||
icon={<Icon icon="vi-ep:video-play" />}
|
||||
onClick={() => {
|
||||
createVideoViewer({
|
||||
url
|
||||
})
|
||||
}}
|
||||
>
|
||||
预览
|
||||
</BaseButton>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderTableColumn = (columnsChildren?: TableColumn[]) => {
|
||||
const {
|
||||
columns,
|
||||
reserveIndex,
|
||||
pageSize,
|
||||
currentPage,
|
||||
align,
|
||||
headerAlign,
|
||||
showOverflowTooltip,
|
||||
reserveSelection,
|
||||
imagePreview,
|
||||
videoPreview
|
||||
} = unref(getProps)
|
||||
|
||||
return (columnsChildren || columns).map((v) => {
|
||||
if (v.hidden) return null
|
||||
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}
|
||||
fixed={v.fixed}
|
||||
width="65px"
|
||||
></ElTableColumn>
|
||||
)
|
||||
} else if (v.type === 'selection') {
|
||||
return (
|
||||
<ElTableColumn
|
||||
type="selection"
|
||||
reserveSelection={reserveSelection}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
selectable={v.selectable}
|
||||
width="50"
|
||||
></ElTableColumn>
|
||||
)
|
||||
} else {
|
||||
const props = { ...v } as any
|
||||
if (props.children) delete props.children
|
||||
|
||||
const children = v.children
|
||||
|
||||
const slots = {
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
|
||||
let isPreview = false
|
||||
isPreview =
|
||||
imagePreview.some((item) => (item as string) === v.field) ||
|
||||
videoPreview.some((item) => (item as string) === v.field)
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isPreview
|
||||
? renderPreview(get(data.row, v.field), v.field)
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
slots['header'] = (...args: any[]) => props.slots.header(...args)
|
||||
}
|
||||
return (
|
||||
<ElTableColumn
|
||||
showOverflowTooltip={showOverflowTooltip}
|
||||
align={align}
|
||||
headerAlign={headerAlign}
|
||||
{...props}
|
||||
prop={v.field}
|
||||
>
|
||||
{slots}
|
||||
</ElTableColumn>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return () => {
|
||||
const tableSlots = {}
|
||||
if (getSlot(slots, 'empty')) {
|
||||
tableSlots['empty'] = (...args: any[]) => getSlot(slots, 'empty', args)
|
||||
}
|
||||
if (getSlot(slots, 'append')) {
|
||||
tableSlots['append'] = (...args: any[]) => getSlot(slots, 'append', args)
|
||||
}
|
||||
|
||||
return (
|
||||
<div v-loading={unref(getProps).loading}>
|
||||
{unref(getProps).customContent ? (
|
||||
<div class="flex flex-wrap">
|
||||
{unref(getProps)?.data?.length ? (
|
||||
unref(getProps)?.data.map((item) => {
|
||||
const cardSlots = {
|
||||
default: () => {
|
||||
return getSlot(slots, 'content', item)
|
||||
}
|
||||
}
|
||||
if (getSlot(slots, 'content-header')) {
|
||||
cardSlots['header'] = () => {
|
||||
return getSlot(slots, 'content-header', item)
|
||||
}
|
||||
}
|
||||
if (getSlot(slots, 'content-footer')) {
|
||||
cardSlots['footer'] = () => {
|
||||
return getSlot(slots, 'content-footer', item)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ElCard
|
||||
shadow="hover"
|
||||
class={unref(getProps).cardWrapClass}
|
||||
style={unref(getProps).cardWrapStyle}
|
||||
bodyClass={unref(getProps).cardBodyClass}
|
||||
bodyStyle={unref(getProps).cardBodyStyle}
|
||||
>
|
||||
{cardSlots}
|
||||
</ElCard>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div class="flex flex-1 justify-center">
|
||||
<ElEmpty description="暂无数据" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{unref(getProps).showAction && !unref(getProps).customContent ? (
|
||||
<TableActions
|
||||
columns={unref(getProps).columns}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
onConfirm={confirmSetColumn}
|
||||
/>
|
||||
) : null}
|
||||
<ElTable ref={elTableRef} data={unref(getProps).data} {...unref(getBindValue)}>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
</>
|
||||
)}
|
||||
{unref(getProps).pagination ? (
|
||||
<ElPagination
|
||||
v-model:pageSize={pageSizeRef.value}
|
||||
v-model:currentPage={currentPageRef.value}
|
||||
class="mt-10px"
|
||||
{...unref(pagination)}
|
||||
></ElPagination>
|
||||
) : undefined}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,166 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ElDrawer,
|
||||
ElCheckbox,
|
||||
ElCheckboxGroup,
|
||||
ElText,
|
||||
ElRadioButton,
|
||||
ElRadioGroup
|
||||
} from 'element-plus'
|
||||
import { TableColumn } from '../types'
|
||||
import { PropType, ref, watch, unref } from 'vue'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { DEFAULT_FILTER_COLUMN } from '@/constants'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
|
||||
const modelValue = defineModel<boolean>()
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
||||
const oldColumns = ref<TableColumn[]>()
|
||||
|
||||
const settingColumns = ref<TableColumn[]>()
|
||||
|
||||
// 存储不要的列
|
||||
const hiddenColumns = ref<TableColumn[]>([])
|
||||
|
||||
const defaultCheckColumns = ref<string[]>([])
|
||||
const checkColumns = ref<string[]>([])
|
||||
|
||||
const checkAll = ref(false)
|
||||
const isIndeterminate = ref(true)
|
||||
const handleCheckAllChange = (val: boolean) => {
|
||||
checkColumns.value = val ? unref(defaultCheckColumns) : []
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
|
||||
const handleCheckedColumnsChange = (value: string[]) => {
|
||||
const checkedCount = value.length
|
||||
checkAll.value = checkedCount === unref(defaultCheckColumns)?.length
|
||||
isIndeterminate.value = checkedCount > 0 && checkedCount < unref(defaultCheckColumns)?.length
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
const newColumns = cloneDeep(unref(settingColumns))?.map((item) => {
|
||||
const fixed = unref(settingColumns)?.find((col) => col.field === item.field)?.fixed
|
||||
item.hidden = !unref(checkColumns)?.includes(item.field)
|
||||
item.fixed = fixed ? fixed : false
|
||||
return item
|
||||
})
|
||||
emit('confirm', [...unref(hiddenColumns), ...(newColumns || [])])
|
||||
modelValue.value = false
|
||||
}
|
||||
|
||||
const restore = () => {
|
||||
initColumns([...unref(hiddenColumns), ...(unref(oldColumns) || [])], true)
|
||||
}
|
||||
|
||||
const initColumns = (columns: TableColumn[], isReStore = false) => {
|
||||
const newColumns = columns?.filter((item) => {
|
||||
if (!isReStore) {
|
||||
item.fixed = item.fixed !== void 0 ? item.fixed : false
|
||||
}
|
||||
return (item.type && !DEFAULT_FILTER_COLUMN.includes(item.type)) || !item.type
|
||||
})
|
||||
if (!unref(oldColumns)?.length) {
|
||||
oldColumns.value = cloneDeep(newColumns)
|
||||
}
|
||||
settingColumns.value = cloneDeep(newColumns)
|
||||
|
||||
hiddenColumns.value = cloneDeep(
|
||||
columns?.filter((item) => item.type && DEFAULT_FILTER_COLUMN.includes(item.type))
|
||||
)
|
||||
|
||||
defaultCheckColumns.value = unref(settingColumns)?.map((item) => item.field) || []
|
||||
checkColumns.value =
|
||||
unref(settingColumns)
|
||||
?.filter((item) => !item.hidden)
|
||||
?.map((item) => item.field) || []
|
||||
|
||||
if (unref(checkColumns)?.length === unref(defaultCheckColumns)?.length) {
|
||||
checkAll.value = true
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.columns,
|
||||
(columns) => {
|
||||
initColumns(columns)
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElDrawer v-model="modelValue" title="列设置" size="350px">
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-between">
|
||||
<ElCheckbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAllChange"
|
||||
/>
|
||||
<ElText class="ml-8px!">{{ checkColumns.length }} / {{ settingColumns?.length }}</ElText>
|
||||
</div>
|
||||
<ElText>固定 / 排序</ElText>
|
||||
</div>
|
||||
<div v-if="settingColumns?.length">
|
||||
<VueDraggable
|
||||
v-model="settingColumns"
|
||||
target=".el-checkbox-group"
|
||||
handle=".handle"
|
||||
:animation="150"
|
||||
>
|
||||
<ElCheckboxGroup
|
||||
ref="draggableWrap"
|
||||
v-model="checkColumns"
|
||||
@change="handleCheckedColumnsChange"
|
||||
>
|
||||
<div
|
||||
v-for="item in settingColumns"
|
||||
:key="item.field"
|
||||
class="flex items-center justify-between mt-12px"
|
||||
>
|
||||
<ElCheckbox :value="item.field">
|
||||
{{ item.label }}
|
||||
</ElCheckbox>
|
||||
<div class="flex items-center">
|
||||
<ElRadioGroup size="small" v-model="item.fixed">
|
||||
<ElRadioButton value="left">
|
||||
<Icon icon="vi-ep:arrow-left" />
|
||||
</ElRadioButton>
|
||||
<ElRadioButton :value="false">
|
||||
<Icon icon="vi-ep:close" />
|
||||
</ElRadioButton>
|
||||
<ElRadioButton value="right">
|
||||
<Icon icon="vi-ep:arrow-right" />
|
||||
</ElRadioButton>
|
||||
</ElRadioGroup>
|
||||
|
||||
<div class="ml-12px cursor-move handle"><Icon icon="vi-ep:rank" /></div>
|
||||
</div>
|
||||
</div>
|
||||
</ElCheckboxGroup>
|
||||
</VueDraggable>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<BaseButton @click="restore">还原</BaseButton>
|
||||
<BaseButton type="primary" @click="confirm">确定</BaseButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDrawer>
|
||||
</template>
|
||||
@@ -0,0 +1,105 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, unref, computed, PropType, ref } from 'vue'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ComponentSize } from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { TableColumn } from '../types'
|
||||
import ColumnSetting from './ColumnSetting.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableActions',
|
||||
components: {
|
||||
ColumnSetting
|
||||
},
|
||||
props: {
|
||||
columns: {
|
||||
type: Array as PropType<TableColumn[]>,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
emits: ['refresh', 'changSize', 'confirm'],
|
||||
setup(props, { emit }) {
|
||||
const appStore = useAppStore()
|
||||
const { t } = useI18n()
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
const showSetting = ref(false)
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
const changSize = (size: ComponentSize) => {
|
||||
emit('changSize', size)
|
||||
}
|
||||
|
||||
const confirm = (columns: TableColumn[]) => {
|
||||
emit('confirm', columns)
|
||||
}
|
||||
|
||||
const showColumnSetting = () => {
|
||||
showSetting.value = true
|
||||
}
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class="text-right h-28px flex items-center justify-end">
|
||||
<div title="刷新" class="w-30px h-20px flex items-center justify-end" onClick={refresh}>
|
||||
<Icon
|
||||
icon="vi-ant-design:sync-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ElDropdown trigger="click" onCommand={changSize}>
|
||||
{{
|
||||
default: () => {
|
||||
return (
|
||||
<div title="尺寸" class="w-30px h-20px flex items-center justify-end">
|
||||
<Icon
|
||||
icon="vi-ant-design:column-height-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
dropdown: () => {
|
||||
return (
|
||||
<ElDropdownMenu>
|
||||
{{
|
||||
default: () => {
|
||||
return unref(sizeMap).map((v) => {
|
||||
return (
|
||||
<ElDropdownItem key={v} command={v}>
|
||||
{t(`size.${v}`)}
|
||||
</ElDropdownItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
</ElDropdownMenu>
|
||||
)
|
||||
}
|
||||
}}
|
||||
</ElDropdown>
|
||||
|
||||
<div
|
||||
title="列设置"
|
||||
class="w-30px h-20px flex items-center justify-end"
|
||||
onClick={showColumnSetting}
|
||||
>
|
||||
<Icon
|
||||
icon="vi-ant-design:setting-outlined"
|
||||
class="cursor-pointer"
|
||||
hover-color="var(--el-color-primary)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ColumnSetting v-model={showSetting.value} columns={props.columns} onConfirm={confirm} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => {
|
||||
const newIndex = index + 1
|
||||
if (reserveIndex) {
|
||||
return size * (current - 1) + newIndex
|
||||
} else {
|
||||
return newIndex
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { TableProps as ElTableProps } from 'element-plus'
|
||||
export interface TableColumn {
|
||||
field: string
|
||||
label?: string
|
||||
type?: string
|
||||
/**
|
||||
* 是否隐藏
|
||||
*/
|
||||
hidden?: boolean
|
||||
children?: TableColumn[]
|
||||
slots?: {
|
||||
default?: (...args: any[]) => JSX.Element | JSX.Element[] | null
|
||||
header?: (...args: any[]) => JSX.Element | null
|
||||
}
|
||||
index?: number | ((index: number) => number)
|
||||
columnKey?: string
|
||||
width?: string | number
|
||||
minWidth?: string | number
|
||||
fixed?: boolean | 'left' | 'right'
|
||||
renderHeader?: (...args: any[]) => JSX.Element | null
|
||||
// sortable?: boolean
|
||||
sortMethod?: (...args: any[]) => number
|
||||
sortBy?: string | string[] | ((...args: any[]) => string | string[])
|
||||
sortOrders?: (string | null)[]
|
||||
resizable?: boolean
|
||||
formatter?: (...args: any[]) => any
|
||||
showOverflowTooltip?: boolean
|
||||
align?: 'left' | 'center' | 'right'
|
||||
headerAlign?: 'left' | 'center' | 'right'
|
||||
className?: string
|
||||
labelClassName?: string
|
||||
selectable?: (...args: any[]) => boolean
|
||||
reserveSelection?: boolean
|
||||
filters?: Array<{ text: string; value: string }>
|
||||
filterPlacement?: string
|
||||
filterMultiple?: boolean
|
||||
filterMethod?: (...args: any[]) => boolean
|
||||
filteredValue?: string[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface TableSlotDefault {
|
||||
row: Recordable
|
||||
column: TableColumn
|
||||
$index: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
small?: boolean
|
||||
background?: boolean
|
||||
pageSize?: number
|
||||
defaultPageSize?: number
|
||||
total?: number
|
||||
pageCount?: number
|
||||
pagerCount?: number
|
||||
currentPage?: number
|
||||
defaultCurrentPage?: number
|
||||
layout?: string
|
||||
pageSizes?: number[]
|
||||
popperClass?: string
|
||||
prevText?: string
|
||||
nextText?: string
|
||||
disabled?: boolean
|
||||
hideOnSinglePage?: boolean
|
||||
}
|
||||
|
||||
export interface TableSetProps {
|
||||
field: string
|
||||
path: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export interface TableProps extends Omit<Partial<ElTableProps<any[]>>, 'data'> {
|
||||
pageSize?: number
|
||||
currentPage?: number
|
||||
showAction?: boolean
|
||||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||||
showOverflowTooltip?: boolean
|
||||
// 表头
|
||||
columns?: TableColumn[]
|
||||
// 是否展示分页
|
||||
pagination?: Pagination | undefined
|
||||
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
|
||||
reserveSelection?: boolean
|
||||
// 加载状态
|
||||
loading?: boolean
|
||||
// 是否叠加索引
|
||||
reserveIndex?: boolean
|
||||
// 对齐方式
|
||||
align?: 'left' | 'center' | 'right'
|
||||
// 表头对齐方式
|
||||
headerAlign?: 'left' | 'center' | 'right'
|
||||
imagePreview?: string[]
|
||||
videoPreview?: string[]
|
||||
sortable?: boolean
|
||||
data?: Recordable
|
||||
}
|
||||
Reference in New Issue
Block a user