https://procomponents.ant.design/components/table/?current=1&pageSize=5

一般按照我的理解,各部分功能区主要放置的东西如下所示。
高级筛选栏:筛选条件 、重置、搜索
标题栏:例如新建、导入数据、导出数据、批量操作等与表格多行操作相关的按钮等
表格区域:表格数据、表格单行操作等
分页栏:控制pageNo、pageSize等
除了这几部分功能区,一般地,我个人还会再加上一些modal(对话框)、drawer(抽屉)

config.js:整个组件的一些配置
BtnGroup组件:对应”标题栏“
Index.vue:核心,是其他组件的共同父组件,统一处理数据,负责布局和接收并处理子组件传递的事件,总调度
SearchCom:对应“高级筛选栏”
TableCom:对应”表格区域“
index.less:组件中样式统一编写处
AddOrEditNidal:增加或编辑的modal组件
DetailModal.vue:查看详情的modal组件
ExportModal.vue:导出数据的modal组件
还有一个分页的组件,一般放在components共用:
Pageable.vue:负责分页
整体布局大致如下:


<template>
<div>
<SearchCom ref="searchComRef" @search="handleSearch" @refresh="handleRefresh">SearchCom>
<BtnGroup style="margin-top: 15px" @btnEvent="handleBtnEvent">BtnGroup>
<TableCom
style="margin-top: 15px"
:loading="tableLoading"
:pageData="pageData"
@changePage="handleChangePage"
@triggerEvent="handleTriggerEvent"
>TableCom>
<DetailDrawer>DetailDrawer>
<AddOrEditModal
ref="addOrEditModalRef"
@ok="addOrEditOk"
@cancel="addOrEditCancel"
:open-option="addOrEditOpenOption"
>AddOrEditModal>
<BulkAddModal
ref="bulkAddModalRef"
@ok="bulkAddOk"
@cancel="bulkAddCancel"
:open-option="bulkAddOpenOption"
>BulkAddModal>
div>
template>
<script setup>
import BtnGroup from './BtnGroup.vue';
import SearchCom from './SearchCom.vue';
import TableCom from './TableCom.vue';
import AddOrEditModal from './AddOrEditModal.vue';
import DetailDrawer from './DetailDrawer.vue';
import BulkAddModal from './BulkAddModal.vue';
import { ref, provide, unref, onMounted } from 'vue';
import { checkResp } from '@/utils/utils';
import { ElMessage } from 'element-plus';
import {
netDelPackage,
netGetPackageList,
netSavePackage,
netUploadPackage,
netUpdatePackage
} from '@/http/modules/softPackageManage';
// ---------------------------表格相关模块--------------------------
const pageData = ref({
size: 10,
content: [],
number: 0,
totalElements: 2,
totalPages: 1
});
const pageSize = ref(10);
const tableLoading = ref(false);
const searchComRef = ref(null);
const searchFields = ref({});
const handleSearch = fields => {
console.log('父组件接收到fields');
console.log(fields);
searchFields.value = fields;
getResultList();
};
const handleRefresh = () => {
// 清空父子组件的fields
searchComRef['value'].clearForm();
searchFields['value'] = {};
getResultList();
};
// 获取结果列表
const getResultList = async (pageNum, pSize) => {
tableLoading.value = true;
const params = {
pageNo: pageNum || 1,
pageSize: pSize || pageSize.value,
...unref(searchFields)
};
try {
const resp = await netGetPackageList({}, params);
if (checkResp(resp)) {
console.log(resp);
pageData.value = resp.data;
}
} catch (e) {
ElMessage.error(`获取数据失败`);
} finally {
tableLoading.value = false;
}
};
// 翻页
const handleChangePage = (pageNum, pSize) => {
// const { pageNum, pSize } = payload;
console.log('父组件接收到了页面变化');
console.log(pageNum, pSize);
getResultList(pageNum, pSize);
};
// 详情 删除 等
const handleTriggerEvent = (eventType, record) => {
console.log('父组件接收到了事件触发');
console.log(eventType, record);
switch (eventType) {
case 'edit':
handleRecordEdit(record);
break;
case 'delete':
handleRecordDel(record.id);
break;
}
};
// -------------------------------------------弹框相关------------------------------------
// ------------------------------------新增或编辑框------------------------------
const addOrEditModalRef = ref(null);
const addOrEditOpenOption = ref({
openType: 'add',
visible: false,
loading: false,
model: {}
});
const addOrEditOk = async fields => {
console.log('父组件接收到了fields');
console.log(fields);
try {
let resp;
if (addOrEditOpenOption['value'].openType === 'add') {
resp = await netSavePackage(fields);
} else if (addOrEditOpenOption['value'].openType === 'edit') {
resp = await netUpdatePackage(fields);
}
if (checkResp(resp)) {
ElMessage.success(`操作成功`);
addOrEditModalRef['value'].clearForm();
addOrEditCancel();
// 刷新一下
handleRefresh();
}
} catch (e) {
ElMessage.error(`操作失败`);
} finally {
}
};
const addOrEditCancel = () => {
console.log('取消了');
addOrEditOpenOption.value.visible = false;
};
// ----------------------------------批量上传框------------------------------
const bulkAddModalRef = ref(null);
const bulkAddOpenOption = ref({
openType: 'add',
visible: false,
loading: false,
model: {}
});
const bulkAddOk = async fields => {
console.log('父组件接收到了fields');
console.log(fields);
const formData = new FormData();
fields.files.forEach(item => {
formData.append('file', item.raw);
});
try {
const resp = await netUploadPackage(formData);
if (checkResp(resp)) {
console.log(resp);
ElMessage.success(`上传成功`);
bulkAddModalRef['value'].clearForm();
bulkAddCancel();
}
} catch (e) {
ElMessage.error(`上传失败`);
} finally {
}
};
const bulkAddCancel = () => {
console.log('取消了');
bulkAddOpenOption.value.visible = false;
};
// -------------------------------点击button,打开框----------------------------------------
const handleAdd = () => {
addOrEditOpenOption.value.openType = 'add';
addOrEditOpenOption.value.visible = true;
};
const handleEdit = record => {
addOrEditOpenOption.value.openType = 'edit';
addOrEditOpenOption.value.visible = true;
addOrEditOpenOption.value.model = { ...record };
};
const handleBulkAdd = () => {
bulkAddOpenOption.value.openType = 'add';
bulkAddOpenOption.value.visible = true;
};
// -------------------------------------button group----------------------------------
const handleBtnEvent = btnType => {
console.log('父组件收到了btnType');
console.log(btnType);
switch (btnType) {
case 'add':
handleAdd();
break;
case 'edit':
handleEdit();
break;
case 'bulkAdd':
handleBulkAdd();
break;
}
};
// ---------------------------------针对record(一行数据)的操作---------------------------------
const handleRecordEdit = record => {
addOrEditOpenOption.value.openType = 'edit';
addOrEditOpenOption.value.visible = true;
addOrEditOpenOption.value.model = { ...record };
};
const handleRecordDel = async id => {
try {
const resp = await netDelPackage({
id
});
if (checkResp(resp)) {
ElMessage.success(`操作成功`);
// 刷新一下
handleRefresh();
}
} catch (e) {
ElMessage.error(`操作失败`);
} finally {
}
};
// -------------------------------------页面生命周期-------------------------------------
const init = () => {
handleRefresh();
};
onMounted(() => {
init();
});
script>
<style scoped lang="less">
@import './index.less';
style>
<template>
<el-form
style="width: 98%; margin: 0 auto"
ref="searchFormRef"
:model="searchForm"
:rules="searchRules"
label-position="center"
>
<el-row :gutter="50">
<el-col :span="7">
<el-form-item label="产品" prop="productIds">
<el-select
:loading="productLoading"
style="width: 85%"
v-model="searchForm.productIds"
placeholder="请选择"
multiple
clearable
>
<el-option v-for="item in productList" :key="item.id" :value="item.id" :label="item.productName"
>{{ item.productName }}
el-option>
el-select>
<SvgIcon style="margin-left: 10px" icon-class="icon-shuaxin" @click="()=> handleRefresh('product')">SvgIcon>
el-form-item>
el-col>
<el-col :span="8">
<el-form-item label="起止时间" prop="startToEnd">
<el-date-picker
style="width: 100%"
v-model="searchForm.startToEnd"
:shortcuts="timeShortcuts"
type="daterange"
range-separator="到"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
/>
el-form-item>
el-col>
<el-col>
<div style="display: flex; justify-content: flex-end">
<el-button type="primary" @click="handleSearch" icon="el-icon-search">搜索el-button>
<el-button @click="handleRefresh" icon="el-icon-refresh">重置el-button>
div>
el-col>
el-row>
el-form>
template>
<script setup>
import { onMounted, ref, unref } from 'vue';
import SvgIcon from '@/components/SvgIcon.vue';
import { useRoute } from 'vue-router';
import { checkResp } from '@/utils/utils';
import { netGetProductList, netGetProjectList, netGetSoftPackageList } from '@/http/modules/ExecutionStatistic';
import { timeShortcuts } from './config';
import { cloneDeep } from 'loadsh';
const route = useRoute();
const emit = defineEmits(['search', 'reset']);
const searchFormRef = ref(null);
const searchForm = ref({
startToEnd: [],
// 产品id(多个)
productIds: '',
});
const searchRules = ref({
productIds: [
{
required: false
}
]
});
const handleSearch = async () => {
// 校验搜索表单
const form = unref(searchFormRef);
let fields = unref(searchForm);
// 校验表单
if (!form) return;
const valid = await form.validate();
if (!valid) {
console.log('error submit!');
return;
}
// 校验通过后的处理
fields = cloneDeep(fields);
if (fields.startToEnd.length === 2) {
const startFrom = fields.startToEnd[0];
const endTo = fields.startToEnd[1];
fields.startFrom = startFrom;
fields.endTo = endTo;
} else {
fields.startFrom = null;
fields.endTo = null;
}
delete fields.startToEnd;
console.log('ok submit', fields);
emit('search', fields);
};
// ----------------------------表单初始化相关--------------------------------
// 产品
const productList = ref([]);
const productLoading = ref(false);
const queryProductList = () => {
netGetProductList().then(resp => {
if (checkResp(resp)) {
console.log(resp);
productList.value = resp.data;
}
});
};
// 刷新
const handleRefresh = type => {
switch(type){
case 'product':
searchForm.value.productIds = []
queryProductList()
}
}
// ---------------------------------刷新数据---------------------------------
const handleRefresh = () => {
// 重置表单
searchFormRef['value'].resetFields();
emit('reset');
};
// ----------------------------------页面初始化----------------------------------
const init = () => {
if (productList.value.length < 1) {
queryProductList();
}
};
onMounted(() => {
init();
});
script>
<template>
<div>
<el-button @click="emit('btnEvent', 'add')" icon="el-icon-plus">新增el-button>
<el-button @click="emit('btnEvent', 'bulkAdd')" icon="el-icon-upload">批量上传el-button>
div>
template>
<script setup>
const emit = defineEmits(['btnEvent']);
script>
<style scoped>style>
<template>
<div>
<el-table
:data="pageData['content']"
border
v-loading="loading"
:element-loading-svg="svg"
class="custom-loading-svg"
element-loading-svg-view-box="-10, -10, 50, 50"
>
<el-table-column width="80" label="序号" align="center" show-overflow-tooltip>
<template #default="scope">
{{ scope.$index + 1 }}
template>
el-table-column>
<el-table-column label="名称" min-width="100" prop="name" align="center" show-overflow-tooltip>
el-table-column>
<el-table-column fixed="right" label="操作" width="220" align="center">
<template #default="{ row }">
<el-popconfirm
style="margin-left: 10px"
confirmButtonText="确认"
cancelButtonText="取消"
@confirm="handleEventTrigger('delete', row)"
icon="el-icon-warning"
iconColor="red"
title="确定删除吗?"
>
<template #reference>
<el-button type="danger" size="mini" icon="el-icon-delete">删除el-button>
template>
el-popconfirm>
template>
el-table-column>
el-table>
<pageable
:isPageSet="true"
:total="pageData['totalElements']"
:current-page="pageData['number'] + 1"
:page-size="pageData['size']"
@change="handlePageChange"
>pageable>
div>
template>
<script setup>
// 引入部分
import { onMounted, ref, reactive, unref } from 'vue';
import Pageable from '@/components/Pageable.vue';
// 基础声明部分
const svg = `
`;
const props = defineProps({
pageData: {
type: Object,
default: () => ({})
},
pageSize: {
type: Number,
default: 10
},
loading: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['changePage', 'triggerEvent']);
// 表格展示相关
const handlePageChange = (pageNum, pSize) => {
console.log(pageNum, pSize);
emit('changePage', pageNum, pSize);
};
// 事件触发
const handleEventTrigger = (eventType, record) => {
emit('triggerEvent', eventType, record);
};
// 初始化函数
const init = () => {};
// --------------------------生命周期相关-------------------------
onMounted(() => {
init();
});
script>
<style>
.top-helper {
padding: 10px 0;
}
style>
/**
* 获取本月第一天
*/
const getNowMonthFirst = () => {
const date = new Date();
date.setDate(1);
return date;
};
/**
* 获取本月最后一天
*/
const getNowMonthLast = () => {
const date = new Date();
return new Date(date.getFullYear(), date.getMonth() + 1, 0);
};
//本周第一天
function weekFirst() {
return new Date(new Date().getTime() - 3600 * 1000 * 24 * (new Date().getDay() - 1));
}
// 本周最后一天
function weekLast() {
return new Date(new Date().getTime() + 3600 * 1000 * 24 * (7 - new Date().getDay()));
}
export const timeShortcuts = [
{
text: '当前周',
value: () => {
const start = weekFirst();
const end = weekLast();
return [start, end];
}
},
{
text: '当前月',
value: () => {
const start = getNowMonthFirst();
const end = getNowMonthLast();
return [start, end];
}
},
{
text: '最近一周',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
}
},
{
text: '最近一月',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
}
},
{
text: '最近三个月',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
return [start, end];
}
},
{
text: '所有',
value: () => {
return [null, null];
}
}
];
<template>
<el-dialog v-model="openOption.visible" :title="openOption.openType === 'add' ? '新增' : '编辑'" width="60%">
<el-skeleton :loading="openOption.loading" animated>
<el-form ref="formRef" :model="form" :rules="rules" :label-width="formLabelWidth" status-icon>
<el-form-item label="名称" prop="name">
<el-input style="width: 90%" v-model="form.name" />
el-form-item>
el-form>
el-skeleton>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancel">取消el-button>
<el-button type="primary" @click="handleConfirm">确认el-button>
span>
template>
el-dialog>
template>
<script setup>
import { onMounted, ref, unref, watch, toRefs } from 'vue';
import axios from '@/http/axios';
import { checkResp } from '@/utils/utils';
import SvgIcon from '@/components/SvgIcon.vue';
// --------------------------定义部分----------------------------
const props = defineProps({
// 打开类型 新增 编辑
openOption: {
type: Object,
default: () => ({
openType: 'add',
visible: false,
loading: false,
model: {}
})
}
});
const emit = defineEmits(['ok', 'cancel']);
// 基础声明部分
const formLabelWidth = '150px';
const { openOption } = toRefs(props);
// svg图
const svg = `
`;
// 表单
const form = ref({
name: '',
code: '',
version: '',
applicableModel: '',
businessType: ''
});
const formRef = ref(null);
const rules = ref({
name: [
{
required: true,
message: '不能为空',
trigger: 'blur'
}
],
});
// -----------表单查询相关-----------------
// -------示例:单选下拉 产品列表------------
const productionList = ref([]);
const productionLoading = ref(false);
const netGetProductionList = async () => {
productionLoading.value = true;
try {
const resp = await axios.get('/controller/products/list', {
params: {
pageNo: 1,
pageSize: 999
}
});
if (checkResp(resp)) {
productionList.value = resp.data.content;
}
} catch (e) {
console.log('error');
} finally {
productionLoading.value = false;
}
};
const handleProductionRefresh = () => {
console.log('处理产品列表刷新');
form.value.productId = '';
netGetProductionList();
};
// ---------------确认和取消---------------------
const handleCancel = () => {
emit('cancel');
};
// 确认提交
const handleConfirm = () => {
const fields = unref(form);
// 校验表单
if (!formRef) return;
formRef['value'].validate(valid => {
console.log(valid, fields);
if (valid) {
// 校验通过
console.log('ok submit', fields);
emit('ok', fields);
} else {
console.log('error submit!');
}
});
};
// 清空表单
const clearForm = () => {
console.log('clearForm');
formRef['value'].resetFields();
};
// 页面初始化函数
const init = () => {};
watch(
() => props.openOption.visible,
newVal => {
console.log('监控到弹窗的打开和关闭');
console.log(newVal);
if (newVal) {
if (productionList.value.length < 1) {
handleProductionRefresh();
}
setTimeout(() => {
console.log('nextTick');
console.log(openOption);
if (openOption['value'].openType === 'edit') {
console.log('有modal传进来');
console.log(openOption['value'].model);
const model = openOption['value'].model;
form.value = { ...model };
}
});
}
}
);
// ---------------------------生命周期相关-------------------------
onMounted(() => {
init();
});
// 对外暴露
defineExpose({
clearForm
});
script>
<style scoped>style>