Table of Contents
Background description:
Development Process:
Detailed development:
Summarize:
Background description:
Many times on the web, we need to develop a list page to display a large amount of data and provide some interactive functions, such as sorting and paging, and export functions. Sometimes we will encapsulate components in order to avoid trouble. >. Let’s use Vue 3 and Element-Plus to develop a list page.
Here, take a system log list page as an example.
Development process:
Create a component named
recordList.vue
, which will be used to display system logs.
Generally, this kind of page consists of three parts:
1.Page name
2. Search box
3. Tables and pagination
The first two parts are often in the same style as other pages. Just reuse the styles. The styles of tables and pagination are almost the same, so most of the entire page is simple and easy to encapsulate.
As shown in the picture:
Detailed development:
1. Subcomponent – template (the search component is a set encapsulated by myself)
<template> <div class="dashboard-container"> <el-card class="card-style"> <div class="mt-1"> <h2 class="fwb-mb-1">{<!-- -->{ listName }}</h2> <el-row> <!---Search component---> <SearchBox v-if="dataReady" :search-options="searchOptions" :showButton="true" :btnLoading="btnLoading" button-name="Export" @search="searchData" /> </el-row> </div> <el-table v-loading="loading" :data="tableData" class="table-small-custom" height="calc(100vh - 240px)" stripe @sort-change="changeTableSort" > <el-table-column type="index" width="70" label="serial number"> <template #default="scope"> <span v-text="getIndex(scope.$index)"></span> </template> </el-table-column> <el-table-column v-for="(col, index) in tableColumn" :key="index" :prop="col.prop" :label="col.label" :min-width="col.minWidth" :sortable="col.sortable" :show-overflow-tooltip="col.showOverflowTooltip" ></el-table-column> <el-table-column v-if="pageName === 'operationRecord'" label="Operation" min-width="80" fixed="right"> <template #default="scope"> <el-button type="primary" size="small" @click="seeDetail(scope.row)">Details</el-button> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="params.page" v-model:page-size="params.pageSize" class="pg-block" layout="total, sizes, prev, pager, next, jumper" :total="pageTotal" background small @current-change="handleCurrentChange" @size-change="handleSizeChange" /> </el-card> </div> </template>
Some of the styles involved above are also common. They control margin and padding. There is nothing special. It depends on the project.
2. Subcomponent–script
<script setup> import { ref, reactive, defineProps, computed, defineEmits, onMounted} from 'vue' import { ElMessage, ElMessageBox } from 'element-plus' import SearchOptionsBox from '@/components/searchOptionsBox.vue' //Search component const emit = defineEmits(['openDialog']) //This is a pop-up window to view details const props = defineProps({ listName: { //The name of the list default: '', type: String }, tableColumn: { // Columns in the table default: [], type: Array }, url: { //URL to request data default: '', type: String }, pageName: { //When used on multiple pages, some pages will have special requirements for differentiation. default: '', type: String } }) let pageTotal = ref(0) let params = reactive({ page: 1, pageSize: 20 }) //Search component options, personally defined let searchOptions = ref([]) let dataReady = ref(false) let btnLoading = ref(false) let tableData = ref([]) let loading = ref(true) onMounted(() => { getOperationRecordData() //... }) //download controls export and is defined by yourself const getOperationRecordData = async (download) => { let tempParams = { ...params, ...searchParams } if (!download) { download = 0 loading.value = true } else { btnLoading.value = true } let res = ( await axios.get(props.url, { params: { ...tempParams, download } }) ).data if (res.code == 200) { if (download) { ElMessage.success(res.message || 'Success!') btnLoading.value = false return } tableData.value = res.data params.page = res.page params.pageSize = res.pageSize pageTotal.value = res.total loading.value = false } else { ElMessage.error(res.message || 'Failed to obtain!') loading.value = false btnLoading.value = false } } //Process the data of the search component let searchParams = reactive({}) const searchData = (searchForm, download) => { params.page = 1 searchParams = JSON.parse(JSON.stringify(searchForm)) if (!download) { download = 0 } getOperationRecordData(download) } //The following is paging and sorting const handleSizeChange = (val) => { params.page = 1 params.pageSize = val getOperationRecordData() } const handleCurrentChange = (val) => { params.page = val getOperationRecordData() } //serial number const getIndex = (index) => { return (params.page - 1) * params.pageSize + index + 1 } //Set the default sort field and positive and negative order const changeTableSort = (column) => { params.sort_key = 'create_at' params.sort_val = 'asc' if (column.order == 'ascending') { params.sort_val = 'asc' } else if (column.order == 'descending') { params.sort_val = 'desc' } else { delete params.sort_key delete params.sort_val } getOperationRecordData() } //The pop-up window to view details opens const seeDetail = (row) => { emit('openDialog', row) }
3. Parent component – tableColumn is a column of the table, and the specific format can be defined according to requirements.
<template> <RecordList list-name="System log" :table-column="tableColumn" url="/log-list" page-name="operationRecord" @openDialog="openDialog" /> <!-- Operation details pop-up window!--> <el-dialog v-model="seeDialogVisible" title="Operation details" width="40%" :close-on-click-modal="false" top="10vh"> <el-descriptions v-if="ready" size="large" column="1" style="margin-left: 20px"> <el-descriptions-item v-for="(item, index) in tableColumn" :key="index" :label="`${item.label} :`"> {<!-- -->{ recordInfo[item.prop] }} </el-descriptions-item> </el-descriptions> <template #footer> <el-button @click="seeDialogVisible = false">Cancel</el-button> </template> </el-dialog> </template> <script setup> import { ref, onMounted } from 'vue' import RecordList from '@/components/recordList.vue' let tableColumn = ref([ { prop: 'user_user', label: 'operator', minWidth: '140', sortable: false, showOverflowTooltip: false }, { prop: 'create_at', label: 'operation time', minWidth: '160', sortable: true, showOverflowTooltip: false }, { prop: 'content_type', label: 'operation type', minWidth: '140', sortable: false, showOverflowTooltip: false }, { prop: 'content', label: 'operation content', minWidth: '330', sortable: false, showOverflowTooltip: true }, { prop: 'user_role', label: 'role', minWidth: '150', sortable: false, showOverflowTooltip: false }, { prop: 'operation_ip', label: 'IP address', minWidth: '120', sortable: false, showOverflowTooltip: false } ]) const seeDialogVisible = ref(false) const ready = ref(false) const recordInfo = ref({}) const openDialog = (row) => { recordInfo.value = row ready.value = true seeDialogVisible.value = true } </script> <style lang="scss" scoped> :deep(.el-dialog__body) { padding-bottom: 0px !important; height: 450px; overflow-y: auto; } .el-descriptions--large { height: 420px; overflow-y: auto; :deep(.el-descriptions__cell) { display: flex; flex-direction: row; justify-content: flex-start; } :deep(.el-descriptions__label) { font-weight: bold !important; width: auto; } :deep(.el-descriptions__content) { width: 70%; display: flex; flex-direction: row; } } </style>
4. There are many ways to export the function in sub-components. If the backend returns a base64, it can be processed directly, as follows:
let res = (await axios.post(props.apiUrl, tempParams)).data if (res.code == 200) { if (download) { let b64 = res.data.res let a = document.createElement('a') a.href = 'data:application/vnd.ms-excel;base64,' + b64 a.download = `filename.xlsx` a.click() ElMessage.success(res.message || 'Success!') changeButtonLoading(false) //Update some data or status of the page in the parent component updateParams() //Update some data or status of the page in the parent component return } tableData.value = res.data.res columns.value = res.data.field_list params.page = res.page params.pageSize = res.pageSize pageTotal.value = res.total loading.value = false updateParams() } else { ElMessage.error(res.message || 'Failed to obtain table data!') loading.value = false changeButtonLoading(false) //Update some data or status of the page in the parent component updateParams() //Update some data or status of the page in the parent component }
Summary:
Although it is simple business logic, it is more convenient to encapsulate it. Here is the way to implement the business requirements of recording this common type of list page.
There is also an implementation of filterable table columns.