UseList组件
import { onMounted, ref, unref, watch } from "vue";
import fileDownload from "js-file-download";
const DEFAULT_MESSAGE = {
GET_DATA_IF_FAILED: "获取列表数据失败",
EXPORT_DATA_IF_FAILED: "导出数据失败",
};
export default function useList(listRequestFn, options = {}) {
const {
immediate = true,
preRequest,
message = DEFAULT_MESSAGE,
filterOption = ref(),
exportRequestFn = undefined,
exportFileName = '导出.xls',
transformFn = undefined,
} = options;
const { GET_DATA_IF_FAILED, EXPORT_DATA_IF_FAILED } = message;
// 加载态
const loading = ref(false);
// 当前页
const curPage = ref(1);
// 分页大小
const pageSize = ref(10);
// 总
const total = ref(0);
// 数据
const list = ref([]);
// 重置
const reset = () => {
if (!filterOption.value) return;
const keys = Reflect.ownKeys(filterOption.value);
keys.forEach((key) => {
Reflect.set(filterOption.value, key, undefined);
});
options?.setRefresh?.();
loadData();
};
// 筛选
const filter = () => {
loadData(curPage.value);
}
const loadData = (pageNum = curPage.value) => {
// 兼容page可能是event
if (typeof pageNum === 'object') {
pageNum = unref(curPage);
}
return new Promise(async (resolve, reject) => {
loading.value = true;
try {
preRequest?.();
const result = await listRequestFn({pageSize: pageSize.value, pageNum, ...filterOption.value});
const transformResult = transformFn ? transformFn(result) : result;
let data = Array.isArray(transformResult.data)
? transformResult.data
: "records" in transformResult.data
? transformResult.data.records
: "list" in transformResult.data
? transformResult.data.list
: [];
let count =
"total" in transformResult
? transformResult.total
: "total" in transformResult.data
? transformResult.data.total
: 0;
list.value = data;
total.value = count;
options?.requestSuccess?.();
resolve({
list: data,
total: count,
});
} catch (error) {
GET_DATA_IF_FAILED && console.log(GET_DATA_IF_FAILED);
options?.requestError?.();
} finally {
loading.value = false;
}
});
};
// 导出
const exportFile = async () => {
if (!exportRequestFn && typeof exportRequestFn !== "function") {
throw new Error("当前没有提供exportRequest函数");
}
try {
const res = await exportRequestFn(filterOption.value);
fileDownload(res, exportFileName);
// window.open(link);
options?.exportSuccess?.();
} catch (error) {
EXPORT_DATA_IF_FAILED && console.log(EXPORT_DATA_IF_FAILED);
options?.exportError?.();
}
};
// 监听分页数据改变
watch([curPage, pageSize], () => {
loadData(curPage.value);
});
onMounted(() => {
if (immediate) {
loadData(curPage.value);
}
});
return {
loading,
curPage,
pageSize,
total,
list,
filterOption,
filter,
reset,
exportFile,
loadData,
};
}
引入后使用
const {
list, //请求到的数据列表
filter, //用于绑定模糊查询的按钮
reset, //用于绑定重置的按钮
curPage,
pageSize,
total, //这三个值为分页所需,下方会细说
exportFile, //用于绑定导出按钮
loadData //用于前端改动数据后更新页面
} = useList(此处为请求数据的list接口, {
filterOption: searchForm, //searchForm为绑定模糊查询的数据
exportRequestFn: 此处为导出的接口,
exportFileName: '文件.xls', //该名称可自行定义
exportParams: () => { //此处可以设置导出时需要的参数
const params = ref({})
params.value = searchForm.value
return params.value
}
preRequest: () => {searchForm.value.State = null}, //该方法内可改变请求接口的参数,如将State值改成null
setRefresh: () => {
searchForm.value.xxx = xxx //重置按钮带参
}
});
Table组件
<template>
<el-table
ref="myTableRef"
:data="tableData"
class="mytable"
:border="isBorder"
:stripe="isStripe"
:max-height="heightVal"
v-loading="loading"
v-bind="$attrs"
>
<template #empty>
<div>暂无数据</div>
</template>
<slot></slot>
<template v-for="(item, index) in tableColumns" :key="index">
<!-- 复选框 -->
<el-table-column
v-if="item.type === 'selection'"
type="selection"
reserve-selection
:width="item.width || 60"
:fixed="item.isFixed"
v-bind="item.data"
></el-table-column>
<!-- 序号 -->
<el-table-column
v-else-if="item.type === 'index'"
type="index"
:label="item.title || '序号'"
:min-width="item.width || 60"
:fixed="item.isFixed"
:align="item.align || 'center'"
v-bind="item.data"
></el-table-column>
<!-- 自定义slot-->
<slot v-else-if="item.slot" :name="item.slot" :item="item" :index="index"></slot>
<el-table-column
v-else
:align="item.align || 'left'"
:fixed="item.isFixed"
:sortable="item?.isSortable"
:min-width="item.width"
:label="item.title"
:prop="item.prop"
v-bind="item.data"
>
<template #header v-if="item.header">
<Texpand :column="item" :render="item.header" :colIndex="index" />
</template>
<template #default="scope">
<template v-if="item.render">
<Texpand :column="item" :row="scope.row" :render="item.render" :index="scope.$index" :colIndex="index" />
</template>
<template v-else>
{{ scope.row[item.prop] }}
</template>
</template>
</el-table-column>
</template>
</el-table>
</template>
<script>
export default {
name: 'MyTable',
};
</script>
<script setup>
import { ref, getCurrentInstance } from 'vue';
import Texpand from './expand.vue';
const props = defineProps({
tableData: {
type: Array,
default: () => {
return [];
}
},
tableColumns: {
type: Array,
default: () => {
return [];
}
},
objectSpanMethod: {
type: Object,
default: () => {
return {};
}
},
isBorder: {
type: Boolean,
default: false
},
isStripe: {
type: Boolean,
default: true
},
heightVal: {
type: String
},
loading: {
type: Boolean,
default: false
},
});
const { proxy } = getCurrentInstance();
const myTableRef = ref();
defineExpose({ myTableRef });
</script>
<style lang="scss" scoped>
</style>
Texpand组件
<script >
import { h, getCurrentInstance, defineComponent } from 'vue';
export default defineComponent({
name: 'Expand',
props: {
row: Object,
render: Function,
index: Number,
colIndex: Number,
column: {
type: Object,
default: null
}
},
// proxy: getCurrentInstance(),
render() {
return this.render(h, { row: this.row , index: this.index, colIndex: this.colIndex, column: this.column })
}
})
</script>
分页组件
<script setup>
const props = defineProps({
layout: {
type: String,
default: "prev, pager, next,sizes,jumper",
},
sizes: {
type: Array,
default() {
return [10, 20, 30, 50];
},
},
total: {
required: true,
type: Number,
}
})
const emit = defineEmits(['pagination'])
const currentPage = ref(1)
const pageSize = ref(10)
const handleSizeChange = (val) => {
emit("pagination", { page: currentPage.value, size: val });
}
const handleCurrentChange = (val) => {
emit("pagination", { page: val, size: pageSize.value });
}
</script>
<template>
<el-pagination class="pagination" :layout="layout" v-model:current-page="currentPage" v-model:page-size="pageSize" :page-sizes="sizes"
:total="total" @size-change="handleSizeChange" @current-change="handleCurrentChange">
<template #default></template>
</el-pagination>
</template>
<style lang='scss' scoped>
.pagination {
position: fixed;
bottom: 30px;
right: 50px;
--el-pagination-button-width: 23px !important;
--el-pagination-button-height: 23px !important;
:deep(.btn-prev) {
width: 23px;
height: 23px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid #AAAAAA;
}
.el-pager {
margin: 0px 5px;
}
:deep(.el-input) {
width: 26px;
height: 23px;
.el-input__inner {
font-size: 12px;
}
.el-input__wrapper {
padding: 0px 5px;
}
}
:deep(.el-select .el-input) {
width: 80px;
}
:deep(.el-pagination__jump),
:deep(.el-pagination__classifier) {
margin-left: 5px !important;
}
:deep(.el-pagination__goto) {
margin-right: 5px !important;
}
}
</style>
分页组件(Pagination.vue)和useList.js需同时引入文件才能使用分页功能,引入后使用:
<MyTable :tableData="list" :tableColumns="tableColumns" />
<Pagination v-show="total > 0" :total="total" v-model:page="curPage" v-model:size="pageSize" />