<!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
<template>
	<!-- 表格主体 -->
	<div class="pro-table-main" :class="[$attrs.class, customClass]">
		<!-- 搜索区域  -->
		<div class="table-search-content">
			<slot name="table-search" />
		</div>
		<!-- 表格头部 操作按钮 -->
		<div class="table-header">
			<div class="header-button-lf">
				<slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" />
			</div>
			<div v-if="toolButton" class="header-button-ri">
				<slot name="toolButton">
					<el-progress :stroke-width="10" :percentage="downloadObj.percentage" v-if="downloadObj.downloading">
						<template #default="{ percentage }">
							<span class="font14">{{ percentage }}%</span>
							<span class="font14">导出进度</span>
						</template>
					</el-progress>
					<el-button v-if="showToolButton('refresh')" circle @click="onRefresh" title="刷新表格" :disabled="loading">
						<SvgIcon name="ele-Refresh" />
					</el-button>
					<el-button v-if="showToolButton('setting') && columns.length" circle @click="openColSetting" title="列设置">
						<SvgIcon name="ele-Setting" />
					</el-button>
					<el-button
						v-if="showToolButton('exportCurrent') && columns.length"
						circle
						@click="exportCurrent"
						title="导出当前页"
						:disabled="exportLoading || loading"
					>
						<SvgIcon name="iconfont icon-daochu" />
					</el-button>
					<el-button
						v-if="showToolButton('exportAll') && columns.length"
						circle
						@click="exportAll"
						title="导出全部"
						:disabled="exportLoading || loading"
					>
						<SvgIcon name="iconfont icon-export" />
					</el-button>
				</slot>
				<slot name="description"> </slot>
				<slot name="tableBtn"></slot>
			</div>
		</div>
		<!-- 表格主体 -->
		<!--    	:row-style="{ height: '24px' }"
			:cell-style="{ padding: '4px 0' }"-->
		<el-table
			ref="tableRef"
			v-bind="$attrs"
			:data="processTableData"
			:border="border"
			:row-key="rowKey"
			@selection-change="selectionChange"
			:scrollbar-always-on="true"
			v-horizontal-scroll
			v-loading="loading"
			:key="processTableData.length"
			:row-style="{ height: '22px' }"
			:cell-style="{ padding: '4px 0' }"
			highlight-current-row
			:current-row-key="currentRowKey"
		>
			<!-- 默认插槽 -->
			<slot />
			<template v-for="item in tableColumns">
				<!-- selection || radio || index || expand || sort -->
				<el-table-column
					v-if="item.type && columnTypes.includes(item.type)"
					v-bind="item"
					:align="item.align ?? 'left'"
					:reserve-selection="item.type === 'selection'"
					:key="item.prop"
				>
					<template #default="scope">
						<!-- expand -->
						<template v-if="item.type === 'expand'">
							<component :is="item.render" v-bind="scope" v-if="item.render" />
							<slot v-else :name="item.type" v-bind="scope" />
						</template>
						<!-- radio -->
						<el-radio v-if="item.type === 'radio'" v-model="radio" :label="scope.row[rowKey]">
							<i></i>
						</el-radio>
					</template>
				</el-table-column>
				<!-- other -->
				<TableColumn v-if="!item.type && item.prop && item.isShow" :column="item" :key="item.prop">
					<template v-for="slot in Object.keys($slots)" #[slot]="scope">
						<slot :name="slot" v-bind="scope" />
					</template>
				</TableColumn>
			</template>
			<!-- 插入表格最后一行之后的插槽 -->
			<template #append>
				<slot name="append" />
			</template>
			<!-- 无数据 -->
			<template #empty>
				<slot name="empty"><el-empty :image-size="120" /></slot>
			</template>
		</el-table>
		<!-- 分页组件 -->
		<slot name="pagination">
			<div class="pagination-container" v-if="pagination">
				<el-pagination
					:background="background"
					v-model:current-page="pageIndex"
					v-model:page-size="pageSize"
					:layout="pageLayout"
					:total="total"
					:page-sizes="pageSizes"
					@size-change="handleSizeChange"
					@current-change="handleCurrentChange"
					:disabled="loading"
					:pager-count="5"
				/>
			</div>
		</slot>
	</div>
	<!-- 列设置 -->
	<col-setting-com v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" @update:colSetting="updateColSetting" @changeRow="changeRow" />
</template>

<script setup lang="tsx" name="ProTable">
import { ref, provide, onMounted, unref, computed, reactive, PropType, watch, h, nextTick } from 'vue';
import { ElMessage, ElMessageBox, ElTable } from 'element-plus';
import { Loading } from '@element-plus/icons-vue';
import { useSelection } from '@/hooks/useSelection';
import { ColumnProps, TypeProps } from '@/components/ProTable/interface';
import ColSettingCom from './components/ColSetting.vue';
import TableColumn from './components/TableColumn.vue';
import { downloadFileByStream } from '@/utils/tools';
import { useResizeObserver, useWindowSize } from '@vueuse/core';

// 接受父组件参数，配置默认值
const props = defineProps({
	customClass: {
		type: String,
		default: '',
	},
	columns: {
		// 列配置项  ==> 必传
		type: Array as PropType<ColumnProps[]>,
		required: true,
	},
	data: {
		// 静态 table data 数据
		type: Array,
		default: () => [],
		required: true,
	},
	pagination: {
		// 是否需要分页组件 ==> 非必传（默认为true）
		type: Boolean,
		default: true,
	},
	border: {
		// 是否带有纵向边框 ==> 非必传（默认为false）
		type: Boolean,
		default: false,
	},
	toolButton: {
		// 是否显示表格功能按钮 ==> 非必传（默认为true）
		type: [Array, Boolean],
		// default: true,
		default: ['refresh', 'setting'],
	},
	rowKey: {
		// 行数据的 Key，用来优化 Table 的渲染，当表格数据多选时，所指定的 id ==> 非必传（默认为 id）
		type: String,
		default: 'id',
	},
	loading: {
		// 是否显示加载中效果
		type: Boolean,
		default: false,
	},
	radio: {
		// 单选框的值
		type: String,
		default: '',
	},
	exportWithParent: {
		// 导出是否带有子父级关系
		type: Boolean,
		default: false,
	},
	exportMethod: {
		// 导出方法
		type: Function,
		default: () => {},
	},
	exportParams: {
		// 导出参数
		type: Object,
		default: () => {},
	},
	isSpecialExport: {
		// 是否特殊导出（导出传递参数不同）
		type: Boolean,
		default: false,
	},
	total: {
		type: Number,
		default: 0,
	},
	pageIndex: {
		type: Number,
		default: 1,
	},
	pageSize: {
		type: Number,
		default: 10,
	},
	pageSizes: {
		type: Array,
		default() {
			return [10, 20, 50, 100];
		},
	},
	pageLayout: {
		type: String,
		default: 'total, sizes, prev, pager, next, jumper',
	},
	background: {
		type: Boolean,
		default: false,
	},
	isRefreshClearSelection: {
		// 是否刷新清空选中项
		type: Boolean,
		default: true,
	},
	selectIds: {
		// 默认选中的id
		type: Array,
		default: () => [],
	},
});
const emit = defineEmits([
	'dargSort',
	'updateColSetting',
	'selectChange',
	'updateTable',
	'update:pagination',
	'exportCurrent',
	'exportAll',
	'update:pageSize',
	'update:pageIndex',
	'update:radio',
	'update:total',
	'pagination',
]);
const pageSize = computed({
	get() {
		return props.pageSize;
	},
	set(val) {
		emit('update:pageSize', val);
	},
});
const pageIndex = computed({
	get() {
		return props.pageIndex;
	},
	set(val) {
		emit('update:pageIndex', val);
	},
});
const handleSizeChange = (val: any) => {
	pageSize.value = val;
	/**
	 * 场景：API返回总数为25条，按照10条每页，一共有3页。选了2的页数之后，然后把size选择成30条，
	 * 这个时候，分页就会同时触发size选择函数以及current选择函数。{page: 2, size: 30}，{page: 1, size: 30}请求两次，会导致列表会有暂无数据的情况
	 * 解决办法：如果当前页大于最大页数，就把当前页设置成最大页数
	 */
	// 获取最大页数
	let pageMax = Math.ceil(props.total / val);
	if (pageIndex.value > pageMax) {
		pageIndex.value = pageMax;
	}
	onRefresh();
	emit('pagination', { page: pageIndex.value, limit: val });
};
const handleCurrentChange = (val: any) => {
	pageIndex.value = val;
	onRefresh();
	emit('pagination', { page: val, limit: pageSize.value });
};
const radio = computed({
	get() {
		return props.radio;
	},
	set(val) {
		emit('update:radio', val);
	},
});
// table 实例
const tableRef = ref<InstanceType<typeof ElTable>>();

// column 列类型
const columnTypes: TypeProps[] = ['selection', 'radio', 'index', 'expand', 'sort'];

// 控制 ToolButton 显示
const showToolButton = (key: 'refresh' | 'setting' | 'exportCurrent' | 'exportAll') => {
	return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
};

// 表格多选 Hooks
const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey);

// 清空选中数据列表
const clearSelection = () => tableRef.value!.clearSelection();
const currentRowKey = ref(undefined);
onMounted(() => {
	useResizeObserver(tableRef, () => {
		tableRef.value?.doLayout();
	});
});
// 处理表格数据
const processTableData = computed(() => {
	if (!props.pagination) return props.data;
	return props.data;
});

// 监听 tableData 的变化来更新表格布局
watch(processTableData, () => {
	if (tableRef.value) {
		tableRef.value.doLayout();
	}
});

// 接收 columns 并设置为响应式
const tableColumns = ref(props.columns);

// 扁平化 columns
const flatColumns = computed(() => flatColumnsFunc(tableColumns.value));
// 定义 enumMap 存储 enum 值（避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择）
const enumMap = ref(new Map<string, { [key: string]: any }[]>());
const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
	if (!enumValue) return;

	// 如果当前 enumMap 存在相同的值 return
	if (enumMap.value.has(prop!) && (typeof enumValue === 'function' || enumMap.value.get(prop!) === enumValue)) return;

	// 当前 enum 为静态数据，则直接存储到 enumMap
	if (typeof enumValue !== 'function') return enumMap.value.set(prop!, unref(enumValue!));

	// 为了防止接口执行慢，而存储慢，导致重复请求，所以预先存储为[]，接口返回后再二次存储
	enumMap.value.set(prop!, []);

	// 当前 enum 为后台数据需要请求数据，则调用该请求接口，并存储到 enumMap
	const { data } = await enumValue();
	enumMap.value.set(prop!, data);
};

// 注入 enumMap
provide('enumMap', enumMap);

// 扁平化 columns 的方法
const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
	columns.forEach(async (col: any) => {
		col.name = col.label ?? '';
		if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
		flatArr.push(col);

		// column 添加默认 isShow
		col.isShow = col.isShow ?? true;
		// 设置 enumMap
		await setEnumMap(col);
	});
	return flatArr.filter((item) => !item._children?.length);
};

// 过滤需要搜索的配置项 && 排序
const searchColumns = computed(() => {
	return flatColumns.value?.filter((item: any) => item.search?.el || item.search?.render).sort((a, b) => a.search!.order! - b.search!.order!);
});

// 设置 搜索表单默认排序 && 搜索表单项的默认值
searchColumns.value?.forEach(() => {});

// 列设置 ==> 需要过滤掉不需要设置的列
const colRef = ref();
const colSetting = tableColumns.value;
/*const colSetting: any = tableColumns!.filter((item) => {
	const { type, prop } = item;
	return !columnTypes.includes(type!) && prop !== 'operation';
});*/
const openColSetting = () => colRef.value.openColSetting();

// 刷新事件
const onRefresh = () => {
	if (props.isRefreshClearSelection) clearSelection();
	emit('updateTable');
};
// 更新列设置
const updateColSetting = (test: ColumnProps[]) => {
	//  平铺数组后获取isShow为true的对象
	const flatColumnsArr = flatColumnsFunc(test);
	const exportNewColumns = flatColumnsArr.filter((item) => item.isShow);
	// 获取prop
	const newColumnsProp = exportNewColumns.map((item) => item.prop);
	emit('updateColSetting', exportNewColumns, newColumnsProp);
};
// 递归获取isShow的列保持子父级关系
const findItems = (tree: any, conditionFn: (item: any) => boolean) => {
	let results: EmptyArrayType = [];
	for (let node of tree) {
		// 如果当前节点满足条件，则将其添加到结果中，并保留父级关系
		if (conditionFn(node)) {
			results.push(node);
		}
		// 递归处理当前节点的子节点
		if (node.children && node.children.length > 0) {
			results = results.concat(findItems(node.children, conditionFn));
		}
	}
	return results;
};
// 导出当前页
const exportLoading = ref(false);
const exportCurrent = () => {
	let exportNewColumns: any = [];
	if (props.exportWithParent) {
		// 导出带子父级关系
		exportNewColumns = findItems(tableColumns.value, (item) => item.isShow && !columnTypes.includes(item.type!) && item.prop !== 'operation');
	} else {
		exportNewColumns = flatColumnsFunc(colSetting).filter(
			(item: any) => item.isShow && !columnTypes.includes(item.type!) && item.prop !== 'operation'
		);
	}
	console.log(exportNewColumns,'导出1231')
	ElMessageBox.confirm(`您确定要导出当前页数据，是否继续?`, '提示', {
		confirmButtonText: '确认',
		cancelButtonText: '取消',
		type: 'warning',
		draggable: true,
		cancelButtonClass: 'default-button',
		autofocus: false,
	})
		.then(async () => {
			exportLoading.value = true;
			const request = {
				queryDto: props.exportParams,
				columnInfos: exportNewColumns,
				isExportAll: false,
			};
			props.exportMethod &&
				props
					.exportMethod(request)
					.then((res: any) => {
						downloadFileByStream(res);
						emit('exportCurrent', exportNewColumns);
						exportLoading.value = false;
					})
					.catch((e: any) => {
						console.log(`导出失败:${e}`);
						exportLoading.value = false;
					});
		})
		.catch(() => {});
};
// 导出全部
const exportAll = () => {
	let exportNewColumns: any = [];
	if (props.exportWithParent) {
		// 导出带子父级关系
		exportNewColumns = findItems(tableColumns.value, (item) => item.isShow && !columnTypes.includes(item.type!) && item.prop !== 'operation');
	} else {
		exportNewColumns = flatColumnsFunc(colSetting).filter(
			(item: any) => item.isShow && !columnTypes.includes(item.type!) && item.prop !== 'operation'
		);
	}
	ElMessageBox.confirm(`您确定要导出全部数据，是否继续?`, '提示', {
		confirmButtonText: '确认',
		cancelButtonText: '取消',
		type: 'warning',
		draggable: true,
		cancelButtonClass: 'default-button',
		autofocus: false,
	})
		.then(() => {
			exportLoading.value = true;
			const request = {
				queryDto: props.exportParams,
				columnInfos: exportNewColumns,
				isExportAll: true,
			};
			const message = ElMessage({
				message: (
					<div class="flex-center-center">
						正在导出中，请稍后
						<Loading class="el-icon is-loading ml5" />
					</div>
				),
				type: 'info',
				duration: 0,
				plain: true,
			});
			if (props.isSpecialExport) {
				const specialRequest = {
					...props.exportParams,
					AddColumnName: exportNewColumns.map((item: any) => item.label),
				};
				props.exportMethod &&
					props
						.exportMethod(specialRequest)
						.then((res: any) => {
							downloadFileByStream(res);
							emit('exportAll', exportNewColumns);
							exportLoading.value = false;
							message.close();
						})
						.catch((e: any) => {
							console.log(`导出失败:${e}`);
							exportLoading.value = false;
							message.close();
						});
			} else {
				props.exportMethod &&
					props
						.exportMethod(request)
						.then((res: any) => {
							downloadFileByStream(res);
							emit('exportAll', exportNewColumns);
							exportLoading.value = false;
							message.close();
						})
						.catch((e: any) => {
							console.log(`导出失败:${e}`);
							exportLoading.value = false;
							message.close();
						});
			}
		})
		.catch(() => {});
};
// 改变行排序
const changeRow = ({ oldIndex, newIndex }) => {
	// 改变表头的顺序
	const draggedItem = tableColumns.value.splice(oldIndex, 1)[0];
	if (draggedItem) tableColumns.value.splice(newIndex, 0, draggedItem);
};
watch(
	// 监听table数据改变后重置选择
	() => props.data,
	() => {
		if (props.isRefreshClearSelection) clearSelection();
		if (props.selectIds && props.selectIds.length) {
			// 监听已经选择的数组ID
			nextTick(() => {
				tableRef.value!.clearSelection();
				for (let i of props.selectIds) {
					for (let j of props.data) {
						if (i === j.id) {
							setTimeout(() => {
								tableRef.value!.toggleRowSelection(j, true);
							}, 100);
						}
					}
				}
			});
		}
	}
);
//下载逻辑
const downloadObj = reactive<any>({
	fileName: '',
	downloading: false,
	range: 0,
	fileBlob: [],
	percentage: 0,
});

const download = async () => {
	downloadObj.fileBlob = [];
	downloadObj.downloading = true;
	downloadObj.range = 0; //文件总大小
	downloadObj.percentage = 0; //下载进度
	const params = {
		md5: '7343784583fsdufhusdfgsudfe8934',
	};
	const chunkSize = 5 * 1024 * 1024;

	//第一次调接口获取到响应头的content-range，文件总大小，用于计算下载切割
	const config = {
		headers: {
			Range: `bytes=0-${chunkSize}`,
		},
	};

	const data: any = await props.exportMethod(params, config);

	//获取文件总大小
	const arr = data.headers['content-range'].split('/');
	downloadObj.range = Number(arr[1]);
	//存储每片文件流
	downloadObj.fileBlob.push(data.data);
	//获取文件名称
	let fileName = '';
	let cd = data.headers['content-disposition'];
	if (cd) {
		let index = cd.lastIndexOf('=');
		fileName = decodeURI(cd.substring(index + 1, cd.length));
	}

	await chunkUpload(params, fileName, chunkSize);
};

//拿到文件总大小downloadObj.range，计算分为多少都段下载
const chunkUpload = async (params: any, fileName: string, chunkSize: number) => {
	//获取分段下载的数组
	let chunkList = [];
	function chunkPush(page = 1) {
		chunkList.push((page - 1) * chunkSize);
		if (page * chunkSize < downloadObj.range) {
			chunkPush(page + 1);
		}
	}
	chunkPush();

	//加上文件大小在最后一位
	chunkList.push(downloadObj.range);
	console.log(chunkList, 'chunkList');
	//分段组合传参格式处理 0-1024 1024-2048
	let uploadRange: EmptyArrayType = [];
	chunkList.forEach((item, i) => {
		if (i == chunkList.length - 1) return;

		uploadRange.push(`${chunkList[i]}-${chunkList[i + 1]}`);
	});
	console.log(uploadRange, 'uploadRang');
	for (let index = 0; index < uploadRange.length; index++) {
		//第一次调接口已经传过了第一组，从第二位开始
		if (index > 0) {
			const config = {
				headers: {
					Range: `bytes=${uploadRange[index]}`,
				},
			};
			const data = await props.exportMethod(params, config);
			//计算下载进度
			downloadObj.percentage = Math.floor(((index + 1) / uploadRange.length) * 100);
			emit('getDownloadpercent', downloadObj.percentage);
			//存储每一片文件流
			downloadObj.fileBlob.push(data.data);
		}
	}
	//合并
	const blob = new Blob(downloadObj.fileBlob, {
		type: downloadObj.fileBlob[0].type,
	});
	downloadObj.downloading = false;
	//下载
	const link = document.createElement('a');
	const URL = window.URL || window.webkitURL;
	link.href = URL.createObjectURL(blob);
	link.download = fileName;
	link.click();
	URL.revokeObjectURL(link.href);
};
// 暴露给父组件的参数和方法 (外部需要什么，都可以从这里暴露出去)
defineExpose({
	element: tableRef,
	tableData: processTableData,
	radio,
	clearSelection,
	enumMap,
	isSelected,
	selectedList,
	selectedListIds,
});
// 计算表格高度
const height = computed(() => {
	const paginationHeight = props.pagination ? 42 : 0;
	const showSummaryHeight = tableRef.value?.showSummary ? 42 : 0;
	return `calc(100% - ${paginationHeight + showSummaryHeight}px)`;
});
</script>
<style scoped lang="scss">
.pro-table-main {
	display: flex;
	flex-direction: column;
	height: v-bind(height);
	flex: 1;

	.table-header {
		margin-bottom: 10px;
		display: flex;
		justify-content: space-between;

		.header-button-lf {
			flex: 1;
		}
	}

	.el-table {
		flex: 1;
	}

	.table-search-content {
		flex-shrink: 0;
	}
	.pagination-container {
		display: flex;
		justify-content: flex-end;
		align-items: center;
	}
}
</style>
