486 lines
14 KiB
Vue
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>
|