Kaynağa Gözat

😒table组件封装优化

zuohuaijun 3 yıl önce
ebeveyn
işleme
ba2bfaab2d

+ 16 - 27
Web/src/components/table/index.vue

@@ -15,7 +15,7 @@
 						</el-dropdown-menu>
 					</template>
 				</el-dropdown>
-				<el-popover placement="top-end" trigger="click" transition="el-zoom-in-top" popper-class="table-tool-popper" :width="300" :persistent="false" @show="onSetTable" @hide="closePop">
+				<el-popover placement="top-end" trigger="click" transition="el-zoom-in-top" popper-class="table-tool-popper" :width="300" :persistent="false" @show="onSetTable">
 					<template #reference>
 						<SvgIcon name="iconfont icon-quanjushezhi_o" :size="22" title="设置" />
 					</template>
@@ -30,7 +30,7 @@
 						</div>
 						<el-scrollbar>
 							<div ref="toolSetRef" class="tool-sortable">
-								<div class="tool-sortable-item" v-for="v in state.listColumn" :key="v.prop" v-show="!v.hideCheck && !v.fixed" :data-key="v.prop">
+								<div class="tool-sortable-item" v-for="v in columns" :key="v.prop" v-show="!v.hideCheck && !v.fixed" :data-key="v.prop">
 									<i class="fa fa-arrows-alt handle cursor-pointer"></i>
 									<el-checkbox v-model="v.isCheck" size="default" class="ml12 mr8" :label="v.label" @change="onCheckChange" />
 								</div>
@@ -53,8 +53,8 @@
 			@selection-change="onSelectionChange"
 			@sort-change="sortChange"
 		>
-			<el-table-column type="selection" :reserve-selection="true" width="30" v-if="config.isSelection && config.showSelection" />
-			<el-table-column type="index" label="序号" align="center" width="60" v-if="config.isSerialNo" />
+			<el-table-column type="selection" :reserve-selection="true" :width="30" v-if="config.isSelection && config.showSelection" />
+			<el-table-column type="index" label="序号" align="center" :width="60" v-if="config.isSerialNo" />
 			<el-table-column v-for="(item, index) in setHeader" :key="index" v-bind="item">
 				<!-- 自定义列插槽,插槽名为columns属性的prop -->
 				<template #default="scope" v-if="$slots[item.prop]">
@@ -73,9 +73,8 @@
 				<el-empty description="暂无数据" />
 			</template>
 		</el-table>
-		<div class="table-footer mt15">
+		<div v-if="state.showPagination" class="table-footer mt15">
 			<el-pagination
-				small
 				v-model:current-page="state.page.page"
 				v-model:page-size="state.page.pageSize"
 				:pager-count="5"
@@ -102,12 +101,12 @@ import { exportExcel } from '/@/utils/exportExcel';
 
 // 定义父组件传过来的值
 const props = defineProps({
-	// 获取数据的方法,由父组件传递
+	//获取数据的方法,由父组件传递
 	getData: {
 		type: Function,
 		required: true,
 	},
-	// 列属性,和elementUI的Table-column 属性相同,附加属性:isCheck-是否默认勾选展示,hideCheck-是否隐藏该列的可勾选和拖拽
+	//列属性,和elementUI的Table-column 属性相同,附加属性:isCheck-是否默认勾选展示,hideCheck-是否隐藏该列的可勾选和拖拽
 	columns: {
 		type: Array<any>,
 		default: () => [],
@@ -152,10 +151,10 @@ const state = reactive({
 		field: '',
 		order: '',
 	},
+	showPagination: true,
 	selectlist: [] as EmptyObjectType[],
 	checkListAll: true,
 	checkListIndeterminate: false,
-	listColumn: [] as Array<any>,
 });
 
 // 设置边框显示/隐藏
@@ -172,23 +171,15 @@ const setHeader = computed(() => {
 });
 // tool 列显示全选改变时
 const onCheckAllChange = <T>(val: T) => {
-	if (val)
-		state.listColumn.forEach((v) => {
-			if (!v.hideCheck) v.isCheck = true;
-		});
-	else
-		state.listColumn.forEach((v) => {
-			if (!v.hideCheck) v.isCheck = false;
-		});
+	if (val) props.columns.forEach((v) => (v.isCheck = true));
+	else props.columns.forEach((v) => (v.isCheck = false));
 	state.checkListIndeterminate = false;
-	emit('sortHeader', state.listColumn);
 };
 // tool 列显示当前项改变时
 const onCheckChange = () => {
-	const headers = state.listColumn.filter((v) => v.isCheck).length;
+	const headers = props.columns.filter((v) => v.isCheck).length;
 	state.checkListAll = headers === props.columns.length;
 	state.checkListIndeterminate = headers > 0 && headers < props.columns.length;
-	emit('sortHeader', state.listColumn);
 };
 // 表格多选改变时
 const onSelectionChange = (val: EmptyObjectType[]) => {
@@ -267,20 +258,17 @@ const onSetTable = () => {
 			onEnd: () => {
 				const headerList: EmptyObjectType[] = [];
 				sortable.toArray().forEach((val: any) => {
-					state.listColumn.forEach((v) => {
+					props.columns.forEach((v) => {
 						if (v.prop === val) headerList.push({ ...v });
 					});
 				});
+				console.log(headerList);
 				emit('sortHeader', headerList);
 			},
 		});
 	});
 };
 
-const closePop = () => {
-	state.listColumn = JSON.parse(JSON.stringify(props.columns));
-};
-
 const handleList = async () => {
 	state.loading = true;
 	let param = Object.assign({}, props.param, { ...state.page });
@@ -288,9 +276,11 @@ const handleList = async () => {
 	const res = await props.getData(param);
 	state.loading = false;
 	if (res.result.items) {
+		state.showPagination = true;
 		state.data = res.result?.items ?? [];
 		state.total = res.result?.total ?? 0;
 	} else {
+		state.showPagination = false;
 		state.data = res.result ?? [];
 	}
 };
@@ -305,11 +295,10 @@ onMounted(() => {
 		state.page.order = props.defaultSort.order;
 	}
 	state.page.pageSize = props.config.pageSize;
-	state.listColumn = JSON.parse(JSON.stringify(props.columns));
 	handleList();
 });
 
-// 导出对象
+// 暴露变量
 defineExpose({
 	pageReset,
 	handleList,

+ 20 - 21
Web/src/components/table/search.vue

@@ -2,28 +2,26 @@
 	<div class="table-search-container" v-if="props.search.length > 0">
 		<el-form ref="tableSearchRef" :model="state.form" size="default" label-width="100px" class="table-form">
 			<el-row :gutter="20">
-				<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb20" v-for="(val, key) in search" :key="key" v-show="key < 3 || state.isToggle">
+				<!-- <el-col :xs="12" :sm="8" :md="8" :lg="6" :xl="4" class="mb20"></el-col> -->
+				<el-col :xs="12" :sm="5" :md="5" :lg="6" :xl="4" class="mb20" v-for="(val, key) in search" :key="key" v-show="key < 3 || state.isToggle">
 					<template v-if="val.type !== ''">
-						<el-form-item :label="val.label" :prop="val.prop" :rules="[{ required: val.required, message: `${val.label}不能为空`, trigger: val.type === 'input' ? 'blur' : 'change' }]">
+						<el-form-item
+							label-width="auto"
+							:label="val.label"
+							:prop="val.prop"
+							:rules="[{ required: val.required, message: `${val.label}不能为空`, trigger: val.type === 'input' ? 'blur' : 'change' }]"
+						>
 							<el-input
 								v-model="state.form[val.prop]"
-								v-bind="$attrs"
+								v-bind="val.comProps"
 								:placeholder="val.placeholder"
 								:clearable="!val.required"
 								v-if="val.type === 'input'"
 								@keyup.enter="onSearch(tableSearchRef)"
-								style="width: 100%"
-							/>
-							<el-date-picker
-								v-model="state.form[val.prop]"
-								v-bind="$attrs"
-								type="date"
-								:placeholder="val.placeholder"
-								:clearable="!val.required"
-								v-else-if="val.type === 'date'"
-								style="width: 100%"
+								class="w100"
 							/>
-							<el-select v-model="state.form[val.prop]" v-bind="$attrs" :clearable="!val.required" :placeholder="val.placeholder" v-else-if="val.type === 'select'" style="width: 100%">
+							<el-date-picker v-model="state.form[val.prop]" v-bind="val.comProps" type="date" :placeholder="val.placeholder" :clearable="!val.required" v-else-if="val.type === 'date'" class="w100" />
+							<el-select v-model="state.form[val.prop]" v-bind="val.comProps" :clearable="!val.required" :placeholder="val.placeholder" v-else-if="val.type === 'select'" class="w100">
 								<el-option v-for="item in val.options" :key="item.value" :label="item.label" :value="item.value"> </el-option>
 							</el-select>
 							<el-cascader
@@ -34,26 +32,26 @@
 								:props="val.cascaderProps ? val.cascaderProps : state.cascaderProps"
 								:placeholder="val.placeholder"
 								class="w100"
-								v-bind="$attrs"
+								v-bind="val.comProps"
 								v-model="state.form[val.prop]"
 							>
 							</el-cascader>
 						</el-form-item>
 					</template>
 				</el-col>
-				<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="6" class="mb20">
-					<el-form-item class="table-form-btn" :label-width="search.length <= 3 ? '10px' : '100px'">
+				<el-col :xs="12" :sm="9" :md="9" :lg="6" :xl="4" class="mb20">
+					<el-form-item class="table-form-btn" label-width="auto">
 						<template #label>
 							<div v-if="search.length > 3">
-								<div class="table-form-btn-toggle ml10" @click="state.isToggle = !state.isToggle">
+								<div class="table-form-btn-toggle" @click="state.isToggle = !state.isToggle">
 									<span>{{ state.isToggle ? '收起' : '展开' }}</span>
 									<SvgIcon :name="state.isToggle ? 'ele-ArrowUp' : 'ele-ArrowDown'" />
 								</div>
 							</div>
 						</template>
 						<div>
-							<el-button size="default" type="primary" icon="ele-Search" @click="onSearch(tableSearchRef)" plain>查询 </el-button>
-							<el-button size="default" icon="ele-Refresh" class="ml10" @click="onReset(tableSearchRef)"> 重置 </el-button>
+							<el-button size="default" type="primary" @click="onSearch(tableSearchRef)" plain>查询 </el-button>
+							<el-button size="default" type="info" class="ml10" @click="onReset(tableSearchRef)"> 重置 </el-button>
 						</div>
 					</el-form-item>
 				</el-col>
@@ -68,7 +66,8 @@ import type { FormInstance } from 'element-plus';
 
 // 定义父组件传过来的值
 const props = defineProps({
-	// 搜索表单,type-控件类型(input,select,cascader,date),options-type为selct时需传值,cascaderData,cascaderProps-type为cascader时需传值,属性同elementUI,cascaderProps不传则使用state默认
+	// 搜索表单,type-控件类型(input,select,cascader,date),options-type为selct时需传值,cascaderData,cascaderProps-type为cascader时需传值,属性同elementUI,cascaderProps不传则使用state默认。
+	// 可带入comProps属性,和使用的控件属性对应
 	search: {
 		type: Array<TableSearchType>,
 		default: () => [],

+ 10 - 9
Web/src/types/views.d.ts

@@ -293,17 +293,17 @@ declare type TableDemoPageType = {
 
 declare type TableHeaderType = {
 	key: string;
-	//width: string;
+	width: string;
 	title: string;
-	type?: string | number;
-	colWidth?: string;
+	type: string | number;
+	colWidth: string;
 	width?: string | number;
 	height?: string | number;
-	isCheck?: boolean;
-	align?: string;
-	headerAlign?: string;
-	toolTip?: boolean;
-	sortable?: boolean;
+	isCheck: boolean;
+	align: string;
+	headerAlign: string;
+	toolTip: boolean;
+	sortable: boolean;
 };
 
 declare type TableSearchType = {
@@ -315,13 +315,14 @@ declare type TableSearchType = {
 	options?: SelectOptionType[];
 	cascaderData?: object[];
 	cascaderProps?: object;
+	comProps?: object;
 };
 
 declare type TableDemoState = {
 	tableData: {
 		// data: EmptyObjectType[];
 		// header: TableHeaderType[];
-		columns: Object[]; // 列设置,同ELTable-Column属性
+		columns: Object[]; //列设置,同ELTable-Column属性
 		config: {
 			// total: number;
 			// loading: boolean;

+ 83 - 82
Web/src/views/system/config/index.vue

@@ -7,6 +7,7 @@
 			<Table ref="tableRef" v-bind="tb.tableData" :getData="getData" :exportChangeData="exportChangeData" @sortHeader="onSortHeader" @selectionChange="tableSelection">
 				<template #command>
 					<el-button type="primary" icon="ele-Plus" @click="openAddConfig" v-auth="'sysConfig:add'"> 新增 </el-button>
+
 					<el-button v-if="state.selectlist.length > 0" type="danger" icon="ele-Delete" @click="bacthDelete" v-auth="'sysConfig:batchDelete'"> 批量删除 </el-button>
 				</template>
 				<template #sysFlag="scope">
@@ -33,6 +34,7 @@ import { getAPI } from '/@/utils/axios-utils';
 import { SysConfigApi } from '/@/api-services/api';
 import { auth } from '/@/utils/authFunction';
 
+// 引入组件
 const Table = defineAsyncComponent(() => import('/@/components/table/index.vue'));
 const TableSearch = defineAsyncComponent(() => import('/@/components/table/search.vue'));
 const editConfigRef = ref<InstanceType<typeof EditConfig>>();
@@ -46,28 +48,31 @@ const state = reactive({
 
 const tb = reactive<TableDemoState>({
 	tableData: {
+		// 表头内容(必传,注意格式)
 		columns: [
-			{ prop: 'name', label: '配置名称', width: '180', align: 'center', sortable: 'custom', isCheck: true, hideCheck: true },
-			{ prop: 'code', label: '配置编码', width: '180', align: 'center', toolTip: true, sortable: 'custom', isCheck: true },
-			{ prop: 'value', label: '属性值', width: '120', align: 'center', isCheck: true },
-			{ prop: 'sysFlag', label: '内置参数', width: '120', align: 'center', isCheck: true },
-			{ prop: 'groupCode', label: '分组编码', width: '120', align: 'center', isCheck: true },
-			{ prop: 'orderNo', label: '排序', width: '80', align: 'center', isCheck: true },
-			{ prop: 'remark', label: '备注', width: '', align: '', headerAlign: 'center', showOverflowTooltip: true, isCheck: true },
-			{ prop: 'action', label: '操作', width: '140', align: 'center', type: 'action', fixed: 'right', isCheck: true, hideCheck: true },
+			{ prop: 'name', width: 160, label: '配置名称', align: 'center', sortable: 'custom', isCheck: true, hideCheck: true },
+			{ prop: 'code', width: 120, label: '配置编码', align: 'center', toolTip: true, sortable: 'custom', isCheck: true },
+			{ prop: 'value', width: 120, label: '属性值', align: 'center', sortable: 'custom', isCheck: true },
+			{ prop: 'sysFlag', width: 120, label: '内置参数', align: 'center', sortable: 'custom', isCheck: true },
+			{ prop: 'groupCode', width: 120, label: '分组编码', align: 'center', sortable: 'custom', isCheck: true },
+			{ prop: 'orderNo', width: 80, label: '排序', align: 'center', sortable: 'custom', isCheck: true },
+			{ prop: 'remark', label: '备注', align: '', headerAlign: 'center', sortable: 'custom', showOverflowTooltip: true, isCheck: true },
+			{ prop: 'action', width: 150, label: '操作', type: 'action', align: 'center', isCheck: true, fixed: 'right', hideCheck: true },
 		],
+		// 配置项(必传)
 		config: {
-			isBorder: true, // 表格边框
-			isSerialNo: true, // 表格序号
-			isSelection: true, // 勾选多选
-			showSelection: auth('sysConfig:batchDelete'), // 显示多选
+			isBorder: false, // 是否显示表格边框
+			isSerialNo: true, // 是否显示表格序号
+			isSelection: true, // 是否勾选表格多选
+			showSelection: auth('sysConfig:batchDelete'), //是否显示表格多选
 			pageSize: 10, // 每页条数
-			hideExport: false, // 隐藏导出按钮
-			exportFileName: '参数配置', // 导出报表的文件名(不填写取应用名称)
+			hideExport: false, //是否隐藏导出按钮
+			exportFileName: '系统参数', //导出报表的文件名,不填写取应用名称
 		},
+		// 搜索表单,动态生成(传空数组时,将不显示搜索,type有3种类型:input,date,select)
 		search: [
-			{ label: '配置名称', prop: 'name', placeholder: '配置名称', required: false, type: 'input' },
-			{ label: '配置编码', prop: 'code', placeholder: '配置编码', required: false, type: 'input' },
+			{ label: '配置名称', prop: 'name', placeholder: '搜索配置名称', required: false, type: 'input' },
+			{ label: '配置编码', prop: 'code', placeholder: '搜索配置编码', required: false, type: 'input' },
 			// { label: '创建时间', prop: 'time', placeholder: '请选择', required: false, type: 'date' },
 		],
 		param: {},
@@ -77,38 +82,24 @@ const tb = reactive<TableDemoState>({
 		},
 	},
 });
-
-onMounted(async () => {
-	getGroupList();
-	mittBus.on('submitRefresh', () => {
-		tableRef.value.handleList();
-		getGroupList();
-	});
-});
-
-onUnmounted(() => {
-	mittBus.off('submitRefresh');
-});
-
-const getData = async (param: any) => {
-	var res = await getAPI(SysConfigApi).apiSysConfigPagePost(param);
-	return res.data;
+const getData = (param: any) => {
+	return getAPI(SysConfigApi)
+		.apiSysConfigPagePost(param)
+		.then((res) => {
+			return res.data;
+		});
 };
-
-// 导出处理
 const exportChangeData = (data: Array<EmptyObjectType>) => {
 	data.forEach((item) => {
 		item.sysFlag = item.sysFlag == 1 ? '是' : '否';
 	});
 	return data;
 };
-
-// 拖动列排序
-const onSortHeader = (data: TableHeaderType[]) => {
+// 拖动显示列排序回调
+const onSortHeader = (data: object[]) => {
 	tb.tableData.columns = data;
 };
-
-// 搜索查询
+// 搜索点击时表单回调
 const onSearch = (data: EmptyObjectType) => {
 	tb.tableData.param = Object.assign({}, tb.tableData.param, { ...data });
 	nextTick(() => {
@@ -116,11 +107,47 @@ const onSearch = (data: EmptyObjectType) => {
 	});
 };
 
-// 表格行多选
+const getGroupList = async () => {
+	const res = await getAPI(SysConfigApi).apiSysConfigGroupListGet();
+	const groupSearch = {
+		label: '分组编码',
+		prop: 'groupCode',
+		placeholder: '请选择',
+		required: false,
+		type: 'select',
+		options: [],
+	} as TableSearchType;
+	state.groupList = res.data.result ?? [];
+	res.data.result?.forEach((item) => {
+		groupSearch.options?.push({ label: item, value: item });
+	});
+	let group = tb.tableData.search.filter((item) => {
+		return item.prop == 'groupCode';
+	});
+	if (group.length == 0) {
+		tb.tableData.search.push(groupSearch);
+	} else {
+		group[0] = groupSearch;
+	}
+};
+//表格多选事件
 const tableSelection = (data: EmptyObjectType[]) => {
+	// console.log('表格多选事件', data)
 	state.selectlist = data;
 };
 
+onMounted(async () => {
+	getGroupList();
+	mittBus.on('submitRefresh', () => {
+		tableRef.value.handleList();
+		getGroupList();
+	});
+});
+
+onUnmounted(() => {
+	mittBus.off('submitRefresh');
+});
+
 // 打开新增页面
 const openAddConfig = () => {
 	state.editConfigTitle = '添加配置';
@@ -147,48 +174,22 @@ const delConfig = (row: any) => {
 		})
 		.catch(() => {});
 };
-
-// 批量删除
+//批量删除
 const bacthDelete = () => {
-	// if (state.selectlist.length == 0) return false;
-	// ElMessageBox.confirm(`确定批量删除【${state.selectlist[0].name}】等${state.selectlist.length}个配置?`, '提示', {
-	// 	confirmButtonText: '确定',
-	// 	cancelButtonText: '取消',
-	// 	type: 'warning',
-	// })
-	// 	.then(async () => {
-	// 		const ids = state.selectlist.map((item) => {
-	// 			return item.id;
-	// 		});
-	// 		var res = await getAPI(SysConfigApi).apiSysConfigBatchDeletePost({ ids: ids });
-	// 		tableRef.value.pageReset();
-	// 		ElMessage.success(res.data.result?.toString());
-	// 	})
-	// 	.catch(() => {});
-};
-
-// 获取分组列表
-const getGroupList = async () => {
-	var res = await getAPI(SysConfigApi).apiSysConfigGroupListGet();
-	var groupSearch = {
-		label: '分组编码',
-		prop: 'groupCode',
-		placeholder: '请选择',
-		required: false,
-		type: 'select',
-		options: [],
-	} as TableSearchType;
-	state.groupList = res.data.result ?? [];
-	res.data.result?.forEach((item) => {
-		groupSearch.options?.push({ label: item, value: item });
-	});
-	let group = tb.tableData.search.filter((item) => {
-		return item.prop == 'groupCode';
-	});
-	if (group.length == 0) {
-		tb.tableData.search.push(groupSearch);
-	} else {
-		group[0] = groupSearch;
-	}
+	if (state.selectlist.length == 0) return false;
+	ElMessageBox.confirm(`确定批量删除【${state.selectlist[0].name}】等${state.selectlist.length}个配置?`, '提示', {
+		confirmButtonText: '确定',
+		cancelButtonText: '取消',
+		type: 'warning',
+	})
+		.then(async () => {
+			const ids = state.selectlist.map((item) => {
+				return item.id;
+			});
+			var res = await getAPI(SysConfigApi).apiSysConfigBatchDeletePost({ ids: ids });
+			tableRef.value.pageReset();
+			ElMessage.success(res.data.result?.toString());
+		})
+		.catch(() => {});
 };
 </script>