wms-elevue/src/components/Table/src/Table.vue
2024-07-23 10:17:25 +08:00

486 lines
14 KiB
Vue

<template>
<div
ref="basicTableRef"
:class="{
'table-full-screen': isFullscreen && !getDarkTheme,
}"
>
<div class="table-toolbar">
<!--顶部左侧区域-->
<div class="flex items-center table-toolbar-left">
<template v-if="tableTitle">
<div class="table-toolbar-left-title">
{{ tableTitle }}
<el-tooltip v-if="tableTitleTooltip" :content="tableTitleTooltip">
<el-icon class="ml-1 text-gray-400 cursor-pointer el-input__icon" size="18">
<QuestionCircleOutlined />
</el-icon>
</el-tooltip>
</div>
</template>
<slot name="tableTitle"></slot>
</div>
<div class="flex items-center table-toolbar-right">
<!--顶部右侧区域-->
<slot name="toolbar"></slot>
<template v-if="isTableSetting">
<!--表格斑马纹-->
<el-tooltip content="表格斑马纹" v-if="isShowTableStriped">
<div class="mr-2 table-toolbar-right-icon">
<el-switch v-model="striped" />
</div>
</el-tooltip>
<el-divider direction="vertical" v-if="isShowTableStriped" />
<!--刷新-->
<el-tooltip content="刷新" v-if="isShowTableRedo">
<div class="table-toolbar-right-icon" @click="reloadTable()">
<el-icon class="el-input__icon" :size="18">
<Refresh />
</el-icon>
</div>
</el-tooltip>
<!--密度-->
<el-tooltip content="密度" v-if="isShowTableSize">
<div class="table-toolbar-right-icon">
<el-dropdown @command="densitySelect">
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="small">紧凑</el-dropdown-item>
<el-dropdown-item command="default">默认</el-dropdown-item>
<el-dropdown-item command="large">宽松</el-dropdown-item>
</el-dropdown-menu>
</template>
<el-icon class="el-input__icon" :size="18">
<ColumnHeightOutlined />
</el-icon>
</el-dropdown>
</div>
</el-tooltip>
<!--表格设置单独抽离成组件-->
<ColumnSetting
v-if="isShowTableSetting"
@columns-change="columnsChange"
:tableSetting="props.tableSetting"
/>
<!--全屏-->
<el-tooltip :content="isFullscreen ? '还原' : '全屏'" v-if="isShowTableFullscreen">
<div class="table-toolbar-right-icon" @click="toggleTableFullScreen">
<el-icon class="el-input__icon" :size="18">
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</el-icon>
</div>
</el-tooltip>
</template>
</div>
</div>
<div class="s-table" ref="sTableRef" v-if="isShowTable">
<el-table ref="tableElRef" v-bind="getBindValues" v-loading="getLoading" @selection-change="handleSelectionChange">
<template v-for="item in getTableColumns" :key="item.prop">
<el-table-column
align="center"
v-if="item.type === 'index' || item.type === 'selection'"
:type="item.type"
:width="item.width"
/>
<el-table-column align="center" v-else-if="item.isSlot" v-bind="item">
<template #default="scope">
<slot :name="item.value" :scope="scope"/>
</template>
</el-table-column>
<el-table-column v-else :prop="item.prop" v-bind="item" show-overflow-tooltip align="center">
<template #default="scope" v-if="item.render">
<Render :column="item" :row="scope.row" :render="item.render" :index="scope.$index" />
</template>
</el-table-column>
</template>
<template v-for="item in Object.keys($slots)" :key="item" #[item]="data">
<slot v-bind="data" :name="item"></slot>
</template>
</el-table>
<div class="flex justify-end s-table-pagination" v-if="pagination && isObject(pagination)">
<el-pagination
v-model:current-page="pagination.currentPage"
:page-sizes="pagination.pageSizes"
:page-size="pagination.pageSize"
:layout="pagination.layout"
:total="pagination.total"
@size-change="updatePageSize"
@current-change="updatePage"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
// 使用一个简单的 <script> to declare options
export default {
inheritAttrs: false,
};
</script>
<script lang="ts" setup>
import { ref, unref, toRaw, computed, onMounted, nextTick, useAttrs } from 'vue';
import { createTableContext } from './hooks/useTableContext';
import ColumnSetting from './components/settings/ColumnSetting.vue';
import { Render } from './components/Render';
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 { useDesignSetting } from '@/hooks/setting/useDesignSetting';
import { Refresh } from '@element-plus/icons-vue';
import {
ColumnHeightOutlined,
QuestionCircleOutlined,
FullscreenExitOutlined,
FullscreenOutlined,
} from '@vicons/antd';
import { useFullscreen } from '@vueuse/core';
import { ElIcon } from 'element-plus';
import { isObject } from 'lodash-es';
const props = defineProps({
...basicProps,
});
const attrs = useAttrs();
const emit = defineEmits([
'fetch-success',
'fetch-error',
'checked-row-change',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change',
'columns-change',
'selection-change'
]);
const striped = ref(false);
const isShowTable = ref(true);
const deviceHeight = ref<Number | String>('auto');
const sTableRef = ref<HTMLElement | null>(null);
const tableElRef = ref<HTMLElement | null>(null);
const basicTableRef = ref<HTMLElement | null>(null);
const wrapRef = ref(null);
const checkedRowKeys = ref<any>([]);
let paginationEl: HTMLElement | null;
const tableData = ref<Recordable[]>([]);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const { isFullscreen, toggle } = useFullscreen(basicTableRef);
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
});
const tableTitle = unref(getProps).title || '';
const tableTitleTooltip = unref(getProps).titleTooltip || '';
const { getAppTheme, getDarkTheme } = useDesignSetting();
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, setPagination } = usePagination(getProps);
const {
getDataSourceRef,
getRowKey,
reload,
restReload,
setTableData,
getDataSource,
updateTableData,
updateTableDataRecord,
deleteTableDataRecord,
} = useDataSource(
getProps,
{
getPaginationInfo,
setPagination,
tableData,
setLoading,
},
emit,
);
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
useColumns(getProps);
const tableSize = ref(unref(getProps as any).size || 'medium');
//返回表格 ref 实例
function getTableRef() {
return tableElRef.value;
}
//表格全屏
function toggleTableFullScreen() {
toggle();
}
//table内部刷新
async function reloadTable(opt?) {
!getProps.value.isKeepRowKeys && (await restCheckedRowKeys());
reload(opt);
}
//页码切换
async function updatePage(page) {
!getProps.value.isKeepRowKeys && (await restCheckedRowKeys());
setPagination({ currentPage: page });
reload();
}
//分页数量切换
function updatePageSize(size) {
setPagination({ currentPage: 1, pageSize: size });
reload();
}
//密度切换
function densitySelect(e) {
tableSize.value = e;
}
//获取表格大小
const getTableSize = computed(() => tableSize.value);
//获取斑马纹
const getStriped = computed(() => striped.value);
//表格设置工具
const isTableSetting = computed(() => getProps.value.showTableSetting);
//是否显示刷新按钮
const isShowTableRedo = computed(() => getProps.value.tableSetting?.redo ?? 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 isShowTableStriped = computed(() => getProps.value.tableSetting?.striped ?? true);
//计算高度
const getDeviceHeight = computed(() => {
const tableData = unref(getDataSourceRef);
if (deviceHeight.value === 'auto') return 'auto';
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
return maxHeight;
});
//获取表格
const getTableColumns = computed(() => {
return toRaw(unref(getPageColumns));
});
//组装表格信息
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef);
return {
...attrs,
...unref(getProps),
loading: unref(getLoading),
columns: toRaw(unref(getPageColumns)),
rowKey: unref(getRowKey),
data: tableData,
size: unref(getTableSize),
stripe: unref(getStriped),
'max-height': getDeviceHeight.value,
height:getDeviceHeight.value,
};
});
function handleSelectionChange(val){
emit('selection-change', val);
}
//columns 列变动
function columnsChange(columns) {
emit('columns-change', columns);
}
//清空行
function restCheckedRowKeys() {
console.log('清空行');
checkedRowKeys.value = [];
emit('checked-row-change', checkedRowKeys);
}
//更新选中行
function setCheckedRowKeys(rowKeys) {
checkedRowKeys.value = rowKeys;
emit('checked-row-change', checkedRowKeys);
}
//重新计算表格高度
function redoHeight() {
computeTableHeight();
}
//获取分页信息
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
const tableAction = {
reload,
restReload,
reloadTable,
restCheckedRowKeys,
redoHeight,
setColumns,
setLoading,
setProps,
getColumns,
getTableRef,
getPageColumns,
getCacheColumns,
setCacheColumnsField,
setCheckedRowKeys,
emit,
};
createTableContext({ ...tableAction, wrapRef, getBindValues, isShowTable });
const getCanResize = computed(() => {
const { canResize } = unref(getProps);
return canResize;
});
async function computeTableHeight() {
const table: any = unref(tableElRef);
if (!table) return;
if (!unref(getCanResize)) return;
const tableEl: any = table?.$el;
await nextTick();
const headEl = tableEl.querySelector('.el-table__header-wrapper');
const { bottomIncludeBody } = getViewportOffset(headEl);
let headerH = 64;
let paginationH = 0;
let marginH = 0;
if (!isBoolean(pagination.value)) {
paginationEl = document.querySelector('.s-table-pagination') as HTMLElement;
if (paginationEl) {
const offsetHeight = paginationEl.offsetHeight;
paginationH += offsetHeight || 0;
}
} else {
headerH = 26;
}
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,
reloadTable,
setCheckedRowKeys,
restCheckedRowKeys,
getDataSource,
getColumns,
getTableRef,
setColumns,
setTableData,
updateTableData,
updateTableDataRecord,
deleteTableDataRecord,
redoHeight,
});
</script>
<style lang="scss" 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: 2;
&-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(--n-text-color);
:hover {
color: v-bind(getAppTheme);
}
}
}
}
.table-toolbar-inner-popover-title {
padding: 2px 0;
}
.table-full-screen {
background: #fff;
padding: 20px;
}
.s-table {
width: 100%;
max-width: 100%;
&-pagination {
padding-top: 15px;
}
}
</style>