439 lines
11 KiB
Plaintext
439 lines
11 KiB
Plaintext
<template>
|
|
<div
|
|
ref="basicTableRef"
|
|
:class="{
|
|
'table-full-screen': isFullscreen,
|
|
}"
|
|
>
|
|
<div class="table-toolbar">
|
|
<!--顶部左侧区域-->
|
|
<div class="flex items-center table-toolbar-left">
|
|
<template v-if="leftTitle">
|
|
<div class="table-toolbar-left-title">
|
|
{{ leftTitle }}
|
|
<a-tooltip v-if="leftTitleTooltip">
|
|
<template #title>{{ leftTitleTooltip }}</template>
|
|
<span class="ml-1 text-gray-400 cursor-pointer">
|
|
<QuestionCircleOutlined style="font-size: 18px" />
|
|
</span>
|
|
</a-tooltip>
|
|
</div>
|
|
</template>
|
|
<slot name="tableTitle"></slot>
|
|
</div>
|
|
|
|
<div class="flex items-center table-toolbar-right">
|
|
<!--顶部右侧区域-->
|
|
<slot name="toolbar"></slot>
|
|
|
|
<template v-if="isTableSetting">
|
|
<!--表格斑马纹-->
|
|
<a-tooltip v-if="isShowTableStriped">
|
|
<template #title>表格斑马纹</template>
|
|
<div class="table-toolbar-right-icon">
|
|
<a-switch
|
|
v-model:checked="tableStriped"
|
|
checked-children="开"
|
|
un-checked-children="关"
|
|
/>
|
|
</div>
|
|
</a-tooltip>
|
|
|
|
<a-divider type="vertical" />
|
|
<!--刷新-->
|
|
<a-tooltip v-if="isShowTableRedo">
|
|
<template #title>刷新</template>
|
|
<div
|
|
class="table-toolbar-right-icon"
|
|
:class="{ 'ml-0': isShowTableStriped }"
|
|
@click="reload"
|
|
>
|
|
<ReloadOutlined />
|
|
</div>
|
|
</a-tooltip>
|
|
|
|
<!--密度-->
|
|
<a-tooltip v-if="isShowTableSize">
|
|
<template #title>密度</template>
|
|
<div class="table-toolbar-right-icon">
|
|
<a-dropdown>
|
|
<ColumnHeightOutlined />
|
|
<template #overlay>
|
|
<a-menu v-model:value="tableSize" @click="densitySelect">
|
|
<a-menu-item v-for="item in densityOptions" :key="item.key">
|
|
<span>{{ item.label }}</span>
|
|
</a-menu-item>
|
|
</a-menu>
|
|
</template>
|
|
</a-dropdown>
|
|
</div>
|
|
</a-tooltip>
|
|
|
|
<!--表格设置单独抽离成组件-->
|
|
<ColumnSetting
|
|
v-if="isShowTableSetting"
|
|
@columns-change="columnsChange"
|
|
:tableSetting="props.tableSetting"
|
|
/>
|
|
|
|
<!--全屏-->
|
|
<a-tooltip v-if="isShowTableFullscreen">
|
|
<template #title>{{ isFullscreen ? '还原' : '全屏' }}</template>
|
|
<div class="table-toolbar-right-icon" @click="toggleTableFullScreen">
|
|
<FullscreenExitOutlined v-if="isFullscreen" />
|
|
<FullscreenOutlined v-else />
|
|
</div>
|
|
</a-tooltip>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="s-table" v-if="isShowTable">
|
|
<a-table
|
|
ref="tableElRef"
|
|
v-bind="getBindValues"
|
|
:pagination="pagination"
|
|
@change="handleTableChange"
|
|
:style="{ minHeight: deviceHeight+45+'px' }"
|
|
class="basicTable"
|
|
>
|
|
<template v-for="item in Object.keys($slots)" :key="item" #[item]="data">
|
|
<slot v-bind="data" :name="item"></slot>
|
|
</template>
|
|
</a-table>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref, unref, toRaw, computed, onMounted, nextTick } from 'vue';
|
|
|
|
import { createTableContext } from './hooks/useTableContext';
|
|
import ColumnSetting from './components/settings/ColumnSetting.vue';
|
|
|
|
import { useLoading } from './hooks/useLoading';
|
|
import { useColumns } from './hooks/useColumns';
|
|
import { useDataSource } from './hooks/useDataSource';
|
|
import { usePagination } from './hooks/usePagination';
|
|
|
|
import { basicProps } from './props';
|
|
import { BasicTableProps } from './types/table';
|
|
|
|
import { getViewportOffset } from '@/utils/domUtils';
|
|
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
|
|
import { isBoolean } from '@/utils/is';
|
|
import { useFullscreen } from '@vueuse/core';
|
|
|
|
import {
|
|
ReloadOutlined,
|
|
ColumnHeightOutlined,
|
|
QuestionCircleOutlined,
|
|
FullscreenExitOutlined,
|
|
FullscreenOutlined,
|
|
} from '@ant-design/icons-vue';
|
|
|
|
const props = defineProps({
|
|
...basicProps,
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'fetch-success',
|
|
'fetch-error',
|
|
'update:checked-row-keys',
|
|
'edit-end',
|
|
'edit-cancel',
|
|
'edit-row-end',
|
|
'edit-change',
|
|
'columns-change',
|
|
]);
|
|
|
|
const densityOptions = [
|
|
{
|
|
label: '紧凑',
|
|
key: 'small',
|
|
},
|
|
{
|
|
label: '中间',
|
|
key: 'middle',
|
|
},
|
|
{
|
|
label: '默认',
|
|
key: 'default',
|
|
},
|
|
];
|
|
|
|
const isShowTable = ref(true);
|
|
const tableStriped = ref(false);
|
|
const deviceHeight = ref(150);
|
|
const tableElRef = ref(null);
|
|
const wrapRef = ref(null);
|
|
const basicTableRef = ref<HTMLElement | null>(null);
|
|
let paginationEl: HTMLElement | null;
|
|
|
|
const tableData = ref<Recordable[]>([]);
|
|
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
|
|
|
const { isFullscreen, toggle } = useFullscreen(basicTableRef as any);
|
|
|
|
const getProps = computed(() => {
|
|
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
|
});
|
|
|
|
const { getLoading, setLoading } = useLoading(getProps);
|
|
|
|
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
|
|
|
const {
|
|
getDataSourceRef,
|
|
getRowKey,
|
|
reload,
|
|
restReload,
|
|
setTableData,
|
|
getDataSource,
|
|
updateTableData,
|
|
tableChange,
|
|
} = useDataSource(
|
|
getProps,
|
|
{
|
|
getPaginationInfo,
|
|
setPagination,
|
|
tableData,
|
|
setLoading,
|
|
},
|
|
emit,
|
|
);
|
|
|
|
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
|
|
useColumns(getProps);
|
|
|
|
const tableSize = ref(unref(getProps as any).size || 'medium');
|
|
|
|
//columns 列变动
|
|
function columnsChange(columns) {
|
|
emit('columns-change', columns);
|
|
}
|
|
|
|
//分页、排序、筛选变化时触发
|
|
function handleTableChange(res) {
|
|
tableChange(res);
|
|
}
|
|
|
|
//密度切换
|
|
function densitySelect(e) {
|
|
tableSize.value = e.key;
|
|
}
|
|
|
|
//获取表格大小
|
|
const getTableSize = computed(() => tableSize.value);
|
|
|
|
//表格设置工具
|
|
const isTableSetting = computed(() => getProps.value.showTableSetting);
|
|
|
|
//是否显示刷新按钮
|
|
const isShowTableRedo = computed(() => getProps.value.tableSetting?.redo ?? true);
|
|
|
|
//斑马纹
|
|
const isShowTableStriped = computed(() => getProps.value.tableSetting?.striped ?? true);
|
|
|
|
//是否显示尺寸调整按钮
|
|
const isShowTableSize = computed(() => getProps.value.tableSetting?.size ?? true);
|
|
|
|
//是否显示字段调整按钮
|
|
const isShowTableSetting = computed(() => getProps.value.tableSetting?.setting ?? true);
|
|
|
|
//是否显示表格全屏按钮
|
|
const isShowTableFullscreen = computed(() => getProps.value.tableSetting?.fullscreen ?? true);
|
|
|
|
//计算高度
|
|
const getDeviceHeight = computed(() => {
|
|
const tableData = unref(getDataSourceRef);
|
|
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
|
return maxHeight;
|
|
});
|
|
|
|
const getTableScroll = computed(() => {
|
|
const { scroll, canResize } = getProps.value;
|
|
return {
|
|
x: '100%',
|
|
y: canResize ? getDeviceHeight.value : null,
|
|
...(scroll || {}),
|
|
};
|
|
});
|
|
|
|
//组装表格信息
|
|
const getBindValues = computed(() => {
|
|
const tableData = unref(getDataSourceRef);
|
|
return {
|
|
...unref(getProps),
|
|
loading: unref(getLoading),
|
|
columns: toRaw(unref(getPageColumns)),
|
|
rowKey: unref(getRowKey),
|
|
dataSource: tableData,
|
|
size: unref(getTableSize),
|
|
scroll: unref(getTableScroll),
|
|
rowClassName:
|
|
tableStriped.value && !unref(getProps).rowClassName
|
|
? (_record, index) => (index % 2 === 1 ? 'table-striped' : null)
|
|
: unref(getProps).rowClassName,
|
|
};
|
|
});
|
|
|
|
//返回表格 ref 实例
|
|
function getTableRef() {
|
|
return tableElRef.value;
|
|
}
|
|
|
|
//表格全屏
|
|
function toggleTableFullScreen() {
|
|
toggle();
|
|
}
|
|
|
|
//重新计算表格高度
|
|
function redoHeight() {
|
|
computeTableHeight();
|
|
}
|
|
|
|
//获取分页信息
|
|
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
|
|
|
|
function setProps(props: Partial<BasicTableProps>) {
|
|
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
|
}
|
|
|
|
const tableAction = {
|
|
reload,
|
|
restReload,
|
|
redoHeight,
|
|
setColumns,
|
|
setLoading,
|
|
setProps,
|
|
getTableRef,
|
|
getColumns,
|
|
getPageColumns,
|
|
getCacheColumns,
|
|
setCacheColumnsField,
|
|
emit,
|
|
};
|
|
|
|
createTableContext({ ...tableAction, wrapRef, getBindValues, isShowTable });
|
|
|
|
const getCanResize = computed(() => {
|
|
const { canResize } = unref(getProps);
|
|
return canResize;
|
|
});
|
|
|
|
async function computeTableHeight() {
|
|
const table = unref(tableElRef);
|
|
if (!table) return;
|
|
if (!unref(getCanResize)) return;
|
|
// @ts-ignore
|
|
const tableEl: any = table?.$el;
|
|
await nextTick();
|
|
const headEl = tableEl.querySelector('.ant-table-thead');
|
|
const { bottomIncludeBody } = getViewportOffset(headEl);
|
|
const headerH = 64;
|
|
let paginationH = 2;
|
|
let marginH = 27;
|
|
console.log(pagination)
|
|
if (!isBoolean(pagination)) {
|
|
paginationEl = tableEl.querySelector('.ant-pagination') as HTMLElement;
|
|
if (paginationEl) {
|
|
const offsetHeight = paginationEl.offsetHeight;
|
|
paginationH += offsetHeight || 0;
|
|
} else {
|
|
paginationH += 28;
|
|
}
|
|
}
|
|
let height =
|
|
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
|
const maxHeight = props.maxHeight;
|
|
height = maxHeight && maxHeight < height ? maxHeight : height;
|
|
deviceHeight.value = height;
|
|
}
|
|
|
|
useWindowSizeFn(computeTableHeight, 280);
|
|
|
|
onMounted(() => {
|
|
nextTick(() => {
|
|
computeTableHeight();
|
|
});
|
|
});
|
|
|
|
//导出方法到外部使用
|
|
defineExpose({
|
|
reload,
|
|
restReload,
|
|
getTableRef,
|
|
getDataSource,
|
|
setTableData,
|
|
updateTableData,
|
|
redoHeight,
|
|
});
|
|
</script>
|
|
<style lang="less" scoped>
|
|
.table-toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 0 0 16px 0;
|
|
|
|
&-left {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
flex: 1;
|
|
|
|
&-title {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
&-right {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
flex: 1;
|
|
|
|
&-icon {
|
|
margin-left: 12px;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
//color: var(--text-color);
|
|
|
|
//:hover {
|
|
// color: v-bind(getAppTheme);
|
|
//}
|
|
}
|
|
.ml-0 {
|
|
margin-left: 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
.table-toolbar-inner-popover-title {
|
|
padding: 2px 0;
|
|
}
|
|
|
|
.s-table :deep(.ant-table-pagination.ant-pagination) {
|
|
float: none;
|
|
text-align: center;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.table-full-screen {
|
|
background: #fff;
|
|
padding: 20px;
|
|
}
|
|
|
|
.dark {
|
|
.table-full-screen {
|
|
background: #151515;
|
|
}
|
|
}
|
|
|
|
.basicTable :deep(.table-striped) td {
|
|
background-color: #fafafa;
|
|
}
|
|
</style>
|