<template>
<div class=”table-box”>
<div>
<div style=”padding: 0 15px” v-if=”!showAnimate”>
<vxe-form ref=”formRef”>
<slot name=”form-items”></slot>
<vxe-form-item>
<vxe-button-group>
<vxe-button type=”submit” status=”primary” @click=”search”>查询</vxe-button>
<vxe-button type=”reset” @click=”reset”>重置</vxe-button>
</vxe-button-group>
</vxe-form-item>
</vxe-form>
</div>
</div>
<div class=”table-container”>
<div class=”flex flx-j-between” style=”margin-bottom: 10px”>
<div>
<slot name=”header-button-le” />
</div>
<div>
<slot name=”header-button-ri” />
<slot name=”toolButton”>
<el-tooltip :content=”showAnimate ? ‘展开’ : ‘收起'” placement=”top”>
<el-button :icon=”Filter” @click=”showSearch” />
</el-tooltip>
<el-tooltip :content=”‘刷新'” placement=”top”>
<el-button :icon=”Refresh” @click=”search” />
</el-tooltip>
<el-tooltip :content=”‘列设置'” placement=”top”>
<el-button :icon=”Operation” @click=”openColSetting” />
</el-tooltip>
<el-button :icon=”Download” @click=”exportEvent” v-show=”DownloadBarShow” />
</slot>
</div>
</div>
<div class=”table-wrapper”>
<vxe-table
ref=”tableRef”
:id=”id”
:data=”tableData”
:border=”border”
v-bind=”tableProps”
height=”89%”
@checkbox-all=”selectChange”
@checkbox-change=”selectChange”
@radio-change=”radioChange”
:show-footer=”showFooter”
:show-header-overflow=”showHeaderOverflow”
:show-footer-overflow=”showFooterOverflow”
:footer-data=”footerData”
:row-config=”{
useKey: true,
keyField: Key,
isHover: true,
isCurrent: true,
resizable: true,
}”
:column-config=”{ useKey: true, resizable: true }”
:custom-config=”customConfig”
:tooltip-config=”tooltipConfig”
:export-config=”downLoadType”
:scroll-y=”virtualConfig.y”
:scroll-x=”virtualConfig.x”
>
<vxe-column type=”radio” width=”60″ v-if=”radio” :fixed=”‘left'”></vxe-column>
<vxe-column type=”checkbox” width=”60″ v-if=”checkbox” :fixed=”‘left'”></vxe-column>
<vxe-column type=”expand” width=”60″ v-if=”expand”>
<template #content=”{ row }”>
<div>
<slot name=”expand” :row=”row”></slot>
</div>
</template>
</vxe-column>
<vxe-column type=”seq” width=”70″ v-if=”seq” :fixed=”‘left'”></vxe-column>
<template v-for=”item in colSetting” :key=”item[Key]”>
<vxe-column
v-bind=”item”
:align=”item.align ?? ‘left'”
:show-overflow=”item.showOverflow ?? true”
:show-header-overflow=”item.showHeaderOverflow ?? false”
:show-footer-overflow=”item.showFooterOverflow ?? false”
:title-prefix=”item[‘title-prefix’]”
:title-suffix=”item[‘title-suffix’]”
row-resize
>
<template #header=”scope”>
<slot v-if=”item.header” :scope=”scope” :name=”item.header” />
<span v-else>{{ scope.column.title }}</span>
</template>
<template #default=”scope”>
<slot v-if=”item.slot” :scope=”scope” :name=”item.slot” />
<div v-else>
{{ formatData(scope.row, item.field) }}
</div>
</template>
</vxe-column>
</template>
<!– 空数据时显示图片 –>
<template #empty>
<div class=”emptyStyle”>
<img
src=”@/assets/images/notData.png”
style=”width: 200px; opacity: 0.6″
alt=”暂无数据”
/>
<div style=”margin-top: 10px; color: #909399″>暂无数据</div>
</div>
</template>
</vxe-table>
<div>
<vxe-pager
style=”padding-right: 10px”
v-model:current-page=”current”
v-model:page-size=”size”
:page-size=”size”
:total=”total”
:layouts=”[‘Total’, ‘Sizes’, ‘PrevPage’, ‘Number’, ‘NextPage’, ‘Jump’, ‘PageCount’]”
:page-sizes=”pageSizes”
@page-change=”handlePageChange”
>
<template #pageCount=”{ pageCount }”>
<span>共{{ pageCount }}页</span>
</template>
</vxe-pager>
</div>
</div>
</div>
</div>
</template>
<script setup lang=”js”>
import { onMounted, ref, computed, reactive } from ‘vue’;
import { ElLoading, ElMessage } from ‘element-plus’;
import { Filter, Operation, Refresh, Download } from ‘@element-plus/icons-vue’;
import { cloneDeep } from ‘lodash-es’;
const props = defineProps({
// 表格id
id: {
type: String,
required: true,
},
// 查询参数对象
searchParams: {
type: Object,
default: () => ({})
},
// 查询参数对象自定义
apiParams: {
type: Object,
default: () => ({})
},
// 是否显示序号列
seq: {
type: Boolean,
default: false
},
// 是否显示单选列
radio: {
type: Boolean,
default: false
},
// 是否显示多选列
checkbox: {
type: Boolean,
default: true
},
// 是否显示展开行
expand: {
type: Boolean,
default: false
},
// 是否显示表格边框
border: {
type: Boolean,
default: false
},
// 获取数据的API函数
apiName: {
type: Function,
required: true,
},
// 表格行的唯一标识字段名
Key: {
type: String,
default: ‘id’
},
// 导出文件类型
downLoadType: {
type: [String, Object],
default: () => ({ type: ‘xlsx’ })
},
// 是否显示导出按钮
DownloadBarShow: {
type: Boolean,
default: false
},
// 是否显示表尾
showFooter: {
type: Boolean,
default: false
},
// 表尾内容是否溢出显示
showFooterOverflow: {
type: Boolean,
default: true
},
// 表头内容是否溢出显示
showHeaderOverflow: {
type: Boolean,
default: true
},
// 表尾数据
footerData: {
type: Array,
default: () => ([])
},
// 提示配置
tooltipConfig: {
type: Object,
default: () => ({})
},
// 列配置
columns: Array,
// 表格属性配置
tableProps: Object,
// 分页大小选项
pageSizes: {
type: Array,
default: () => [10, 20, 30, 50, 100]
},
// 虚拟滚动是否开启
enabled : {
type: Boolean,
default: true
}
});
const emits = defineEmits([‘reset’,’error’]);
const loading = ref(false)
const colSetting = ref(props.columns);
const tableRef = ref();
const formRef = ref(null);
const showAnimate = ref(true);
const current = ref(1);
const size = ref(10);
const total = ref(0);
const tableData = ref([]);
// 动态虚拟滚动配置
const virtualConfig = computed(() => ({
y: {
enabled: props.enabled,
gt: 80,
scrollY: {
mode: ‘wheel’ // 推荐配置:滚轮模式更流畅
}
},
x: {
enabled: tableData.value.some(row => {
try {
return (Object.keys(row).length > 8 || JSON.stringify(row).length > 300 )&& props.enabled
} catch {
return false
}
}),
gt: 5,
scrollX: {
mode: ‘wheel’
}
}
}))
// 其余方法保持不变
const formatData = (row, field) => {
return field && row[field] !== undefined ? row[field] : ‘-‘;
};
const selectIdLists = ref([]);
const selectLists = ref([]);
const selectChange = () => {
const records = tableRef.value?.getCheckboxRecords() || [];
selectLists.value = records;
selectIdLists.value = records.map((i) => { return i[props.Key] });
};
const selectList = ref([]);
const radioChange = ({ row }) => {
selectList.value = row;
};
const showSearch = () => {
showAnimate.value = !showAnimate.value;
// formRef.value.toggleCollapse();
};
const model = ref({})
const search = () => {
getTableData();
};
const reset = () => {
emits(‘reset’);
current.value = 1
tableRef.value.clearCheckboxRow();
tableRef.value.clearRadioRow();
selectLists.value = [];
selectIdLists.value = [];
selectList.value = [];
setTimeout(() => {
getTableData();
console.log(‘model.value’, model.value);
}, 0);
};
const getTableData = () => {
// const loadingInstance = ElLoading.service({
// text: ‘加载中…’
// })
model.value = cloneDeep(props.searchParams)
const apiParams = cloneDeep(props.apiParams)
const query = {
…model.value,
…apiParams,
current: current.value,
size: size.value
}
if (!props.apiName) return;
props.apiName(current.value,size.value,Object.assign({},query))
.then(({ data:res }) => {
// loadingInstance.close()
tableData.value = res.data.records || []
total.value = res.data.total || 0
})
.catch((err) => {
// loadingInstance.close()
emits(‘error’, err)
})
}
const handleSizeChange = (val) => {
size.value = val;
};
const handleCurrentChange = (val) => {
current.value = val;
};
const handlePageChange = ({ currentPage, pageSize }) => {
handleSizeChange(pageSize)
handleCurrentChange(currentPage)
getTableData();
}
onMounted(() => {
getTableData();
});
const findCustomSetting = (id) => {
return new Promise(resolve => {
setTimeout(() => {
try {
if (sessionStorage.getItem(id)) {
resolve(JSON.parse(sessionStorage.getItem(id) || ”))
// ElMessage.success(‘异步还原用户个性化数据成功’)
} else {
resolve({})
}
} catch (e) {
resolve({})
}
}, 300)
})
}
const saveCustomSetting = (id, storeData) => {
return new Promise(resolve => {
setTimeout(() => {
sessionStorage.setItem(id, JSON.stringify(storeData))
ElMessage.success(‘保存用户个性化数据成功’)
resolve()
}, 200)
})
}
const customConfig = reactive({
storage: true,
restoreStore ({ id }) {
return findCustomSetting(id)
},
updateStore ({ id, storeData }) {
return saveCustomSetting(id, storeData)
}
})
const openColSetting = () => {
tableRef.value.openCustom();
};
const exportEvent = () => {
tableRef.value.openExport();
};
defineExpose({
getTableData,
tableRef,
selectLists,
selectIdLists,
selectList
});
</script>
<style scoped lang=”scss”>
@import ‘./style.scss’;
</style>