前期回顾
目录
三、📝 TS类型 src\types\global.d.ts
在平时开发中,系统中写的最多的 表格+搜索表单排名No.1,每一次都在Element-Plus中 拷贝一遍 <template> ,显然是个大活,我们将其html封装,每一只写Data数据让其动态渲染,编写速度达达滴!
一、🛠️ newTable.vue 封装Table
<template>
<div class="container">
<div class="container-main">
<!-- 表单搜索区域 -->
<el-scrollbar v-if="isShowSearchRegion" max-height="300px" class="scrollbar-height">
<slot name="search"></slot>
</el-scrollbar>
<!-- 表格上方搜索向下方按钮区域 -->
<slot name="btn"></slot>
<!-- 列表区域 v-bind="xx"放在最下方,父组件传值可以覆盖上面定义的默认值-->
<!-- 父组件传递的属性(通过 $attrs 或显式传递的 prop)能够覆盖子组件内部的默认设置,你应该确保 v-bind 放在最后 -->
<el-table
ref="multipleTableRef"
stripe
style="width: 100%"
:data="filterTableData"
:border="tableBorder"
:style="{
height: tableHeight || excludeSearchAreaAfterTableHeight,
minHeight: minHeight + 'px',
}"
:row-key="(row) => row.id"
@row-click="handleRowClick"
@selection-change="onSelectionChange"
v-bind="$attrs"
>
<template #empty>
<el-empty :image-size="emptyImgSize" description="暂无数据" />
</template>
<el-table-column
v-if="isSelection"
type="selection"
width="60"
:reserve-selection="true"
:selectable="selectableCallback"
/>
<el-table-column v-if="isRadio" width="60" label="单选" align="center">
<template #default="{ row }">
<el-radio :label="row.id" v-model="selectRadioIdVm" />
</template>
</el-table-column>
<el-table-column
type="index"
label="序号"
min-width="60"
:index="orderHandler"
align="center"
/>
<el-table-column
v-for="item in tableHeader"
:key="item.prop"
:fixed="item.label === '操作' ? 'right' : void 0"
align="center"
header-align="center"
min-width="150"
:show-overflow-tooltip="item.label !== '操作'"
v-bind="item"
>
<template #header v-if="item.slotKey?.includes('tableHeaderSearch')">
<el-input
v-model.trim="headerSearch"
size="small"
:placeholder="getSearchInfo.label"
/>
</template>
<template #default="{ row }" v-if="item.slotKey">
<slot
v-for="slot in item.slotKey.split(',')"
:name="slot"
:row="row"
></slot>
<template v-if="item.slotKey.includes('default')">
<el-link type="primary" :underline="false" @click="handleEdit(row)"
>编辑
</el-link>
<el-popconfirm title="确定删除吗?" @confirm="handleDelete(row.id)">
<template #reference>
<el-link type="danger" :underline="false">删除</el-link>
</template>
</el-popconfirm>
</template>
</template>
</el-table-column>
</el-table>
<!-- 分页区域-->
<el-pagination
v-if="paginationFlag"
background
:page-sizes="pageSizesArr"
:current-page="pageNum"
:page-size="pageSize"
:layout="layout"
:total="Number(total)"
popper-class="pagination-popper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch, toRaw, nextTick, computed } from 'vue';
import { ElTable } from 'element-plus';
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
import myEmits from './newTableConfig/emits';
import myProps from './newTableConfig/props';
const emits = defineEmits(myEmits);
const props = defineProps(myProps);
const headerSearch = ref(''); //表头搜索Vm
const selectRadioIdVm = ref(''); // 单选框vm
const selectedRowData = ref({}); // 当前行数据
// 行点击事件-实现点击行也可以选中单选
const handleRowClick = (row: any) => {
if (props.isRadio) {
selectRadioIdVm.value = row.id; // 选中单选按钮
selectedRowData.value = row; // 拿取当前行数据
emits('selectRowData', selectedRowData.value);
}
};
// 搜索过滤
const filterTableData = computed(() =>
props.tableData?.filter(
(data) =>
!headerSearch.value ||
String(data[getSearchInfo.value.prop])
.toLowerCase()
.includes(headerSearch.value.toLowerCase())
)
);
// 计算那列用于展示搜索
const getSearchInfo = computed(() => {
let searchInfo = { label: '', prop: '' };
props.tableHeader?.find((v) => {
if (v.searchFields) {
searchInfo = { label: v.label, prop: v.prop };
return true;
}
});
return searchInfo;
});
// 序号根据数据长度计算
const orderHandler = (index: number) => {
const { pageNum, pageSize } = props;
// 第0条 * 每页条数 + 当前索引+1
return (pageNum - 1) * pageSize + index + 1;
};
// 页数改变
const handleSizeChange = (val: number | string) => emits('handleSizeChange', val);
// 当前页改变
const handleCurrentChange = (val: number | string) => emits('handleCurrentChange', val);
// 编辑、删除
const handleEdit = (row: object) => emits('handleEdit', row);
const handleDelete = (id: number) => emits('handleDelete', id);
// 复选框
const onSelectionChange = (val: any) => emits('selection-table-change', val);
// 根据父组件传递的id字符串,默认选中对应行
const toggleSelection = (rows?: any) => {
if (props.isSelection) {
if (rows) {
rows.forEach((row: any) => {
const idsArr = props.selectionIds?.split(',');
if (idsArr?.includes(row.id.toString())) {
//重要
nextTick(() => multipleTableRef.value?.toggleRowSelection(row, true));
}
});
} else {
multipleTableRef.value!.clearSelection();
}
}
};
// 返回值用来决定这一行的 CheckBox 是否可以勾选
const selectableCallback = (row: any) => {
const idsArr = props.selectionIds?.split(',');
if (props.isDisableSelection && idsArr?.includes(row.id.toString())) return false;
return true;
};
watch(
() => props.tableData,
(newV) => {
if (!!props.selectionIds) {
// console.log('🤺🤺 selectionIds🚀 ==>:', props.selectionIds);
// console.log('🤺🤺 newV ==>:', newV);
toggleSelection(toRaw(newV));
}
}
);
// 搜索区域高度及默认值
const Height = ref();
// 减去搜索区域高度后的table,不能有默认值不然会出现滚动条
const excludeSearchAreaAfterTableHeight = ref();
const minHeight = 500; // 最小高度值
// 获取表格高度-动态计算搜索框高度
const updateHeight = () => {
let wrapEl = document.querySelector('.scrollbar-height');
if (!wrapEl) return;
// 获取搜索区域高度
Height.value = wrapEl.clientHeight;
if (props.isShowSearchRegion) {
const calculatedHeight = `calc(100vh - ${150 + Height.value}px)`;
// 确保元素的高度不会小于一个最小值
excludeSearchAreaAfterTableHeight.value = `max(${minHeight}px, ${calculatedHeight})`;
}
};
onMounted(() => {
// 表格下拉动画
const tableContainer = document.querySelectorAll<HTMLElement>('.container');
setTimeout(() => {
tableContainer.forEach((item) => {
if (item) item.style.transform = 'translateY(0)';
});
updateHeight();
}, 800);
});
window.addEventListener('resize', updateHeight);
defineExpose({
toggleSelection,
});
</script>
<style scoped lang="scss">
.container {
width: 100%;
height: 100%;
padding: 15px;
transform: translateY(-100%);
transition: transform 0.4s ease-in-out;
background-color: #f8f8f8;
// background-color: #870404;
&-main {
position: relative;
padding: 15px;
width: 100%;
// height: 100%; //el-scrollbar有默认高度100%,当页面列表渲前会继承这里高度,导致搜索区域铺满全屏
background-color: #fff;
border: 1px solid #e6e6e6;
border-radius: 5px;
&:hover {
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
}
transition: box-shadow 0.3s ease-in-out;
.scrollbar-height {
min-height: 100px;
}
.el-pagination {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
}
}
}
:deep(.el-table tbody tr .cell) {
padding-top: 10px;
padding-bottom: 10px;
/* 保留换行
el-table-column打开了show-overflow-tooltip,换行不会生效
*/
// white-space: break-spaces;
}
// :deep(.el-popper.is-dark) {
// max-width: 700px !important;
// word-break: break-all;
// }
// 操作按钮间隙
:deep(.el-link) {
padding-left: 10px;
}
/* 单选框隐藏label文字 */
:deep(.el-radio.el-radio--large .el-radio__label) {
display: none;
}
</style>
二、🚩 newForm.vue 封装搜索表单
<template>
<el-form ref="searchFormRef" :model="searchForm" size="default">
<!-- 使用了不稳定的 key,可能会导致一些不可预期的行为,比如输入框失去焦点。 -->
<el-row>
<el-col
:xs="24"
:sm="24"
:md="24"
:lg="12"
:xl="6"
v-for="item in formOptions"
:key="item.vm"
>
<el-form-item :label="item.props.label" :prop="item.vm">
<el-input
v-if="item.type === FormOptionsType.INPUT"
v-model.lazy.trim="searchForm[item.vm]"
v-bind="item.props"
class="ml10 w100"
></el-input>
<el-select
v-if="item.type === FormOptionsType.SELECT"
v-model.lazy="searchForm[item.vm]"
v-bind="item.props"
class="ml10 w100"
fit-input-width
@change="selectChange(item)"
>
<el-option label="全部" value=""></el-option>
<el-option
v-for="option in item.selectOptions"
:key="option.value"
:label="option.label"
:value="option.value"
>
<zw-tooltip-omit :content="option.label"></zw-tooltip-omit>
</el-option>
</el-select>
<el-cascader
v-if="item.type === FormOptionsType.CASCADER"
v-model.lazy="searchForm[item.vm]"
:options="item.cascaderOptions"
v-bind="item.props"
class="ml10 w100"
/>
<el-date-picker
v-if="item.type === FormOptionsType.DATE_PICKER"
v-model.lazy="searchForm[item.vm]"
v-bind="item.props"
class="ml10 w100"
/>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" class="xs-mt">
<el-form-item style="margin-left: 10px">
<el-button @click="onSearch('reset')">
<SvgIcon name="ant-ReloadOutlined"></SvgIcon>
重置
</el-button>
<el-button type="primary" @click="onSearch()">
<SvgIcon name="ant-SearchOutlined"></SvgIcon>
查询
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
<script setup lang="ts" name="newForm">
import { toRefs, onBeforeUnmount, ref } from 'vue';
import type { PropType } from 'vue';
import { type FormInstance } from 'element-plus';
import { debounce } from '/@/utils/debounce';
const searchFormRef = ref<FormInstance>();
enum FormOptionsType {
INPUT = 'input', // 输入框
SELECT = 'select', // 下拉框
CASCADER = 'cascader', // 级联选择器
DATE_PICKER = 'date-picker', // 日期选择器
}
const props = defineProps({
formOptions: {
type: Array as PropType<FormOptions[]>,
required: true,
},
searchForm: {
type: Object as PropType<SearchFormType>,
required: true,
},
});
const { formOptions, searchForm } = toRefs(props);
const emit = defineEmits(['search', 'select-change']);
const debouncedEmitSearch = debounce((type) => emit('search', type));
const onSearch = (type: string) => {
if (type) searchFormRef.value?.resetFields();
debouncedEmitSearch(type);
};
const selectChange = (item: any) => {
emit('select-change', item);
};
onBeforeUnmount(() => searchFormRef.value?.resetFields());
defineExpose({ searchFormRef });
</script>
<style scoped lang="scss">
:deep(.el-form-item__label) {
margin-left: 10px;
}
</style>
<style scoped lang="scss">
:deep(.el-form-item__label) {
margin-left: 10px;
}
</style>
三、📝 TS类型 src\types\global.d.ts
// new-table
//表头数据类型定义
declare interface TableHeader<T = any> {
label: string;
prop: string;
align?: string;
overHidden?: boolean;
minWidth?: string;
sortable?: boolean;
type?: string;
fixed?: string;
width?: string | number;
// isActionColumn?: boolean; // 是否是操作列
// isCustomizeColumn?: boolean; // 是否是自定义列
slotKey?: string; // 自定义列的插槽名称
searchFields?: boolean; // 是否是搜索字段
}
/*
newForm
允许任何字符串作为索引
不然会报错, 使用动态属性名,需要使用索引签名
*/
declare type SearchFormType = {
[key: string]: string;
};
declare type FormOptions = {
type: string;
props: {
label: string;
placeholder: string;
type: string;
clearable: boolean;
};
vm: string;
selectOptions?: {
value: string | number;
label: string;
}[];
cascaderOptions?: any;
};
四、♻️ 页面使用功能 - 静态
<template>
<new-table
:tableHeader="tableHeader"
:tableData="tableData"
:total="100"
@handleSizeChange="onHandleSizeChange"
@handleCurrentChange="onHandleCurrentChange"
@handleDelete="onHandleDelete"
@handleEdit="onHandleEdit"
>
<template #search>
<el-row>
<el-col
:xs="24"
:sm="24"
:md="24"
:lg="12"
:xl="6"
v-for="item in Math.ceil(Math.random() * 10)"
:key="item"
class="scrollbar-demo-item"
>56546</el-col
>
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" class="xs-mt">
<el-form-item>
<el-button> 重置 </el-button>
<el-button type="primary"> 查询 </el-button>
</el-form-item>
</el-col>
</el-row>
</template>
<template #switch="slotProps">
<el-switch
v-model="slotProps.row.status"
active-text="开"
inactive-text="关"
active-color="#13ce66"
inactive-color="#ff4949"
@change="changeSwitchStatus(slotProps.row.id, slotProps.row.status)"
/>
</template>
</new-table>
</template>
<script setup lang="ts" name="algorithmRegistrationQuery">
import { reactive, toRefs } from "vue";
const state = reactive({
//表头数据
tableHeader: <TableHeader[]>[
{ label: "姓名", prop: "uname", width: "100px" },
{ label: "年龄", prop: "age", slotKey: "switch" },
{ label: "性别", prop: "sex" },
{ label: "操作", width: "240px", fixed: "right", slotKey: "default" },
],
//表数据,从接口获取
tableData: [
{ uname: "小帅", age: "18", sex: "男", status: false, id: 1 },
{ uname: "小美", age: "148", sex: "女", status: false, id: 2 },
{ uname: "小明", age: "12", sex: "男", status: true, id: 3 },
{ uname: "小红", age: "12", sex: "女", status: false, id: 4 },
{ uname: "小黑", age: "12", sex: "男", status: true, id: 5 },
{ uname: "小白", age: "12", sex: "女", status: false, id: 6 },
{ uname: "小黑", age: "12", sex: "男", status: true, id: 7 },
{ uname: "小白", age: "12", sex: "女", status: false, id: 8 },
{ uname: "小黑", age: "12", sex: "男", status: true, id: 9 },
{ uname: "小白", age: "12", sex: "女", status: false, id: 10 },
{ uname: "小黑", age: "12", sex: "男", status: true, id: 11 },
],
});
const { tableHeader, tableData } = toRefs(state);
// 修改
const onHandleEdit = (row: object) => {
console.log(row);
};
// 删除
const onHandleDelete = (row: object) => {
console.log(row);
};
// switch
const changeSwitchStatus = (id: number, status: boolean) => {
console.log(id, status);
};
//分页改变
const onHandleSizeChange = (val: number) => {
console.log("!这里输出 🚀 ==>:", val);
};
//分页改变
const onHandleCurrentChange = (val: number) => {
console.log("!这里输出 🚀 ==>:", val);
};
// //页容量改变
// const onHandleSizeChange = (val: number) => {
// // console.log('页容量 ==>:', val);
// pageSize.value = val;
// getTableList(pageNum.value, pageSize.value, tableId.value);
// };
// //当前分页改变
// const onHandleCurrentChange = (val: number) => {
// // console.log('当前页 🚀 ==>:', val);
// pageNum.value = val;
// getTableList(pageNum.value, pageSize.value, tableId.value);
// };
</script>
<style lang="scss" scoped>
.scrollbar-demo-item {
display: flex;
align-items: center;
justify-content: center;
height: 50px;
margin: 10px;
text-align: center;
border-radius: 4px;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.xs-mt {
display: flex;
align-items: flex-end;
}
</style>
五、♻️ 页面使用功能 - 动态
<template>
<div class="container-wrapper">
<!-- 动态 page -->
<new-table
v-bind="state"
:total="pageTotal"
@handleSizeChange="onHandleSizeChange"
@handleCurrentChange="onHandleCurrentChange"
@handleEdit="onHandleEdit"
@handleDelete="onHandleDelete"
>
<template #search>
<new-form :formOptions="formOptions" :searchForm="searchForm" @search="onSearch" />
</template>
<template #btn>
<el-button type="primary" size="default" class="btn-add">
<SvgIcon name="ant-PlusOutlined"></SvgIcon>
新建题目
</el-button>
</template>
<template #switch="{ row }">
<el-switch
v-model="row.fileStatus"
active-text="开"
inactive-text="关"
:active-value="1"
:inactive-value="2"
active-color="#13ce66"
inactive-color="#ff4949"
@change="changeSwitchStatus(row.id, row.fileStatus)"
/>
</template>
</new-table>
</div>
</template>
<script setup lang="ts" name="algorithmRegistrationQuery">
import { onMounted, reactive, toRefs } from 'vue';
import { getTestList } from '/@/api/encryptionAlgorithm/templateDefinition';
import { STATUS_CODE } from '/@/enum/global';
const state = reactive({
//表头数据
// el-table-column有的属性都可以在这传
/*
searchFields:true 搜索字段-写在那个对象上,那个label 就有搜索,搜索展示在那个字段上。
slotKey: 'xxx' 自定义插槽
包含tableHeaderSearch则展示表格搜索框-是否显示搜索框
包含default则展示 编辑删除
其他值可以在父组件中使用插槽 template自定义内容
#search 表单搜索
#btn 列表上方的按钮
*/
tableHeader: <TableHeader[]>[
{ label: '合规规则', prop: 'knowledgeName', searchFields: true },
{ label: '文件数量', prop: 'documentNumber', width: '200px' },
{ label: '文件状态', prop: 'fileStatus', slotKey: 'switch' },
{ label: '操作', fixed: 'right', slotKey: 'default,tableHeaderSearch', width: 200 },
],
//表项数据
tableData: [],
formOptions: <FormOptions[]>[
{
type: 'input',
props: {
label: '合规规则',
placeholder: '请输入合规规则',
type: 'text',
clearable: true,
},
vm: 'knowledgeName',
},
{
type: 'input',
props: {
label: '文件数量',
placeholder: '请输入文件数量',
type: 'text',
clearable: true,
},
vm: 'documentNumber',
},
// 下拉选择器
{
type: 'select',
props: {
label: '所属部门',
placeholder: '请选择',
clearable: true,
},
vm: 'department',
selectOptions: [
{
label: '数据安全',
value: 1,
},
{
label: '研发',
value: 2,
},
{
label: '事业',
value: 3,
},
],
},
// 时间范围选择器
{
type: 'date-picker',
props: {
label: '时间范围',
type: 'datetimerange', // datetimerange范围 datetime日期
clearable: true,
'range-separator': '-',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
'value-format': 'YYYY-MM-DD HH:mm:ss',
},
vm: 'createTime',
},
// 级联选择器
{
type: 'cascader',
props: {
label: '所属部门',
placeholder: '请选择',
clearable: true,
},
vm: 'cascader',
cascaderOptions: [
{
value: 'guide',
label: 'Guide',
children: [
{
value: 'disciplines',
label: 'Disciplines',
children: [
{
value: 'consistency',
label: 'Consistency',
},
],
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'side nav',
label: 'Side Navigation',
},
{
value: 'top nav',
label: 'Top Navigation',
},
],
},
],
},
{
value: 'component',
label: 'Component',
children: [
{
value: 'basic',
label: 'Basic',
children: [
{
value: 'button',
label: 'Button',
},
],
},
{
value: 'form',
label: 'Form',
children: [
{
value: 'radio',
label: 'Radio',
},
{
value: 'checkbox',
label: 'Checkbox',
},
],
},
{
value: 'data',
label: 'Data',
children: [
{
value: 'table',
label: 'Table',
},
],
},
{
value: 'notice',
label: 'Notice',
children: [
{
value: 'alert',
label: 'Alert',
},
],
},
{
value: 'navigation',
label: 'Navigation',
children: [
{
value: 'menu',
label: 'Menu',
},
],
},
{
value: 'others',
label: 'Others',
children: [
{
value: 'dialog',
label: 'Dialog',
},
],
},
],
},
{
value: 'resource',
label: 'Resource',
children: [
{
value: 'axure',
label: 'Axure Components',
},
],
},
],
},
],
//这里允许动态属性所以可为空,如果是下拉选项将vm置为空就会匹配到子组件的'全部'label字段
searchForm: <SearchFormType>{
department: '',
},
pageNum: 1,
pageSize: 10,
pageTotal: 0,
tableHeight: 'calc(100vh - 375px)', //如果开启#btn占位符需要手动设置表格高度
});
const { tableData, formOptions, searchForm, pageNum, pageSize, pageTotal } = toRefs(state);
// 修改
const onHandleEdit = (row: object) => {
console.log(row);
};
// 删除
const onHandleDelete = (row: object) => {
console.log(row);
};
// switch
const changeSwitchStatus = (id: number, status: boolean) => {
console.log(id, status);
};
//页容量改变
const onHandleSizeChange = (val: number) => {
// console.log('页容量 ==>:', val);
pageSize.value = val;
getTableList(pageNum.value, pageSize.value);
};
//当前分页改变
const onHandleCurrentChange = (val: number) => {
// console.log('当前页 🚀 ==>:', val);
pageNum.value = val;
getTableList(pageNum.value, pageSize.value);
};
// 获取表项数据
const getTableList = (pageNum: number, pageSize: number) => {
// 处理searchForm.value createTime
let params = { ...searchForm.value };
if (params.createTime) {
params.createTimeBegin = params.createTime[0];
params.createTimeEnd = params.createTime[1];
}
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
const { createTime, ...paramsWithoutCreateTime } = params;
getTestList({
pageNum,
pageSize,
...paramsWithoutCreateTime,
}).then((res) => {
if (res.code !== STATUS_CODE.SUCCESS) return;
const { list, total } = res.data;
tableData.value = list;
// console.log('🤺🤺 表项 🚀 ==>:', list);
pageTotal.value = total;
});
};
const onSearch = (isReset?: string) => {
pageNum.value = isReset ? 1 : pageNum.value;
getTableList(pageNum.value, pageSize.value);
};
onMounted(() => getTableList(pageNum.value, pageSize.value));
</script>
<style scoped lang="scss">
.btn-add {
float: right;
margin-bottom: 20px;
}
</style>
六、✈️ 在封装(删除、列表请求)
import { ref, Ref } from 'vue';
import { ElMessage } from 'element-plus';
import { STATUS_CODE } from '/@/enum/global';
// 定义时间字段类型
type TimeFieldType = [string, string];
// 定义列表项数据类型
interface ListItem {
[key: string]: any;
}
// 定义列表响应数据类型
interface ListResponse {
code: STATUS_CODE;
data: {
list: ListItem[];
total: string | number;
};
}
// 定义搜索表单类型
interface SearchForm {
[key: string]: any;
}
// 定义页面配置接口
interface PageConfig {
pageNum: number;
pageSize: number;
total: number;
}
// 定义分页配置
const page = ref<PageConfig>({
pageNum: 1,
pageSize: 10,
total: 0,
});
// 定义列表配置
interface UseTableListOptions {
searchForm: Ref<SearchForm>;
tableData: Ref<ListItem[]>;
listAjaxFunction: (params: any) => Promise<any>;
deleteAjaxFunction?: (id: number) => Promise<any>;
extraParams?: Record<string, any>; // 给listAjaxFunction额外传参数
timeFieldType?: TimeFieldType;
}
// 定义返回值类型
interface UseTableListReturns {
onHandleSizeChange: (num: number) => void;
onHandleCurrentChange: (sizes: number) => void;
getTableList: (pageNum?: number, pageSize?: number, timeField?: TimeFieldType) => void;
onSearch: (isReset?: boolean) => void;
onHandleDelete: (id: number) => void;
page: PageConfig;
}
/**
* 抽离的list和del方法
* @function useTableList
* @param { Object } optionsPayload - 配置对象
* @param { Ref<SearchForm> } optionsPayload.searchForm - 搜索表单VM绑定值
* @param { Ref<ListItem[]> } optionsPayload.tableData - 表格数据
* @param { Function } optionsPayload.listAjaxFunction - 列表请求方法
* @param { Function} [optionsPayload.deleteAjaxFunction] - 删除请求方法(可选)
* @param { Record<string, any> } [optionsPayload.extraParams] - 额外的请求参数(可选)
* @param { TimeFieldType } optionsPayload.timeFieldType - 时间字段类型
* @returns { Object } 包含操作函数和响应式状态的对象
* @author zk
* @createDate 2024/01/24 19:10:12
* @lastFixDate 2024/02/26 11:17
**/
export function useTableList({
searchForm,
tableData,
listAjaxFunction,
deleteAjaxFunction,
extraParams = {},
timeFieldType = ['createTimeStart', 'createTimeEnd'],
}: UseTableListOptions): UseTableListReturns {
// 页容量改变
const onHandleSizeChange = (num: number) => {
page.value.pageSize = num;
getTableList(page.value.pageNum, num);
};
// 当前分页改变
const onHandleCurrentChange = (sizes: number) => {
page.value.pageNum = sizes;
getTableList(sizes, page.value.pageSize);
};
// 获取表项数据
const getTableList = (
pageNum: number = 1,
pageSize: number = 10,
timeField: TimeFieldType = timeFieldType
) => {
let params: SearchForm = { ...searchForm.value, ...extraParams };
// console.log('搜索表单VM绑定值', params);
if (params.Time) {
//有替换没有则新增
params[timeField[0]] = params.Time[0];
params[timeField[1]] = params.Time[1];
delete params.Time;
}
listAjaxFunction({
pageNum,
pageSize,
...params,
}).then((res: ListResponse) => {
if (res?.code !== STATUS_CODE.SUCCESS) return;
const { list, total } = res.data;
tableData.value = list;
page.value.total = Number(total);
});
};
// 搜索功能,支持重置
const onSearch = (isReset?: boolean) => {
page.value.pageNum = isReset ? 1 : page.value.pageNum;
getTableList(page.value.pageNum, page.value.pageSize);
};
// 删除表项
const onHandleDelete = (id: number) => {
if (!deleteAjaxFunction) return;
deleteAjaxFunction(id).then((res) => {
if (res.code === STATUS_CODE.SUCCESS) {
ElMessage.success('删除成功');
onSearch();
}
});
};
return {
onHandleSizeChange,
onHandleCurrentChange,
getTableList,
onSearch,
onHandleDelete,
page: page.value,
};
}
七、♻️ 使用 再次封装 的表格
这里再次基于Element-plus Table二次封装后,再次封装列表和删除方法,方便使用、代码更少,编写速度再次推升
只需要更改接口(列表、删除),即可直接使用功能
<template>
<div class="container-wrapper">
<!-- 数据安全威胁情报管理 page -->
<new-table
v-bind="state"
@handleSizeChange="onHandleSizeChange"
@handleCurrentChange="onHandleCurrentChange"
@handleEdit="onEditDetail"
@handleDelete="onHandleDelete"
>
<template #search>
<new-form :formOptions="formOptions" :searchForm="searchForm" @search="onSearch" />
</template>
<template #btn>
<el-button type="primary" @click="onAddDetail(DialogType.Add)" class="btn-add">
<SvgIcon name="ant-PlusOutlined"></SvgIcon>
新 增
</el-button>
</template>
<template #detail="{ row }">
<el-link
type="primary"
:underline="false"
@click="onAddDetail(DialogType.Detail, row)"
>详情</el-link
>
</template>
</new-table>
<Dialog ref="dialogRef" @on-success="onSearch" />
</div>
</template>
<script setup lang="ts" name="intelligenceManagement">
import { onMounted, reactive, toRefs, defineAsyncComponent, ref } from 'vue';
import { querySecurityThreatList, deleteSecurityThreat } from '/@/api/knowledgeBase/backup';
import { useTableList } from '/@/utils/Hooks/tableSearch';
import { DialogType } from '/@/views/operationManage/operationMaintenance/syslogConfig/components/type';
const Dialog = defineAsyncComponent(() => import('./components/Dialog.vue'));
const dialogRef = ref<InstanceType<typeof Dialog> | null>(null);
const state = reactive({
tableHeight: 'calc(100vh - 360px)',
tableHeader: <TableHeader[]>[
{ label: '标识号', prop: 'identifier' },
{ label: '版本', prop: 'intelligenceVersion' },
{ label: '威胁情报名称', prop: 'intelligenceName' },
{ label: '威胁情报描述', prop: 'intelligenceDescription', minWidth: 200 },
{ label: '创建时间', prop: 'createTime' },
{ label: '操作', width: 200, slotKey: 'isEfficacy,detail,default' },
],
tableData: [],
formOptions: <FormOptions[]>[
{
type: 'input',
props: {
label: '标识号',
placeholder: '请输入标识号',
type: 'text',
clearable: true,
},
vm: 'identifier',
},
{
type: 'input',
props: {
label: '版本',
placeholder: '请输入版本',
type: 'text',
clearable: true,
},
vm: 'intelligenceVersion',
},
{
type: 'input',
props: {
label: '威胁情报名称',
placeholder: '请输入威胁情报名称',
type: 'text',
clearable: true,
},
vm: 'intelligenceName',
},
{
type: 'date-picker',
props: {
label: '创建时间',
type: 'datetimerange',
clearable: true,
'range-separator': '-',
'start-placeholder': '开始日期',
'end-placeholder': '结束日期',
'value-format': 'YYYY-MM-DD HH:mm:ss',
},
vm: 'createTime',
},
],
searchForm: <SearchFormType>{},
});
const { tableData, formOptions, searchForm } = toRefs(state);
const { onHandleSizeChange, onHandleCurrentChange, getTableList, onSearch, page, onHandleDelete } =
useTableList({
searchForm,
tableData,
listAjaxFunction: querySecurityThreatList,
deleteAjaxFunction: deleteSecurityThreat,
timeFieldType: ['startTime', 'endTime'],
});
// 编辑
const onEditDetail = (row: any) => dialogRef.value?.open(DialogType.Edit, row);
// 新建、详情
const onAddDetail = (type: DialogType, row?: any) => dialogRef.value?.open(type, row);
onMounted(() => {
getTableList();
Object.assign(state, toRefs(page));
});
</script>
八、🤖 仓库地址、演示地址
仓库地址:
在线演示:
九、📝 结语
封装其他组件在其余博文中也有详细描写,快来抱走把!
_______________________________ 期待再见 _______________________________