vue3+ts封装表格
时间:2024-12-24 19:43 作者:suxiaojun 分类: 无
这里使用了elementplus+vuetify框架封装的
<template>
<v-card class="border" elevation="0">
<v-card-item v-show="showSearch" class="pb-2">
<el-form :model="form" label-width="auto" :inline="true">
<template v-for="(item, index) in TableHeaders" :key="index">
<el-form-item :label="item.label" v-if="item.search?.component === 'Input'">
<el-input v-model="form[item.field]" :placeholder="item.label" />
</el-form-item>
<el-form-item :label="item.label" v-if="item.search?.component === 'Select'">
<el-select v-model="form[item.field]" :placeholder="item.label" style="min-width: 200px;">
<el-option v-for="(optionItem) in item.search.optionApi()" :key="optionItem.label"
:label="optionItem.label" :value="optionItem.value" />
</el-select>
</el-form-item>
<el-form-item :label="item.label" v-if="item.search?.component === 'DatePicker'">
<el-config-provider :locale="locale">
<el-date-picker v-model="form[item.field]" type="daterange" range-separator="-"
:start-placeholder="开始时间" :end-placeholder="结束时间" size="default"
value-format="YYYY-MM-DD" />
</el-config-provider>
</el-form-item>
</template>
<el-form-item style="display: block;">
<el-button type="primary" style="background-color: #5d87ff;color: #ffffff;" @click="submit">
查询
</el-button>
<el-button type="info" @click="handleForm" style="color: #ffffff;">重置</el-button>
<slot name="searchButton" />
<el-dropdown v-show="SiftHeader ? SiftHeader : false" style="margin-left: auto" trigger="click"
:hide-on-click="false">
<span class="el-dropdown-link d-flex align-center ga-2">
<img :src="filterSVG" width="20px" alt="">
<!-- 可选项表头配置 -->
<span>配置</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-checkbox-group v-model="check" @change="changeCheckbox">
<el-dropdown-item v-for="(item, index) in checkList" :key="index">
<el-checkbox :label="item.label" :value="item">
</el-checkbox>
</el-dropdown-item>
</el-checkbox-group>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-form-item>
</el-form>
</v-card-item>
<v-divider v-show="showSearch"></v-divider>
<v-data-table-server :headers="tableHeaderList" :items="TableData" :items-per-page="pagination.itemsPerPage"
:page.sync="pagination.page" items-per-page-text="页码文本" show-current-page
:page-text="页码文本 + props.Total" :items-per-page-options="perPageOptions"
:items-length="props.Total" @update:options="switchPagination" :hide-default-footer="HideFooter">
<template v-slot:headers="{ columns }">
<tr>
<template v-for="column in columns" :key="column.field">
<th>
<div class="mr-2" :style="{ width: column.width ? column.width : '100px' }">
{{ column.label }}
<v-tooltip v-if="column.labelTipTitle" location="end" :text="column.labelTipTitle">
<template v-slot:activator="{ props }">
<v-icon v-bind="props" icon="mdi-help-circle-outline" size="small"></v-icon>
</template>
</v-tooltip>
</div>
</th>
</template>
</tr>
</template>
<template v-slot:item="{ item }">
<tr>
<td v-for="row in tableHeaderList" :key="row.id">
<template v-if="row.field !== 'operate'">
<div class="d-flex ga-2">
<span v-show="!row.type">
{{ handleFormatter(item, row) }}
</span>
<span v-show="row.type === 'time'">
{{ formatDate(handleFormatter(item, row)) }}
</span>
<!-- vue3动态创建DOM 元素 -->
<component v-if="row?.render" :is="row.render(item, onRowClick)" />
</div>
</template>
<template v-else>
<slot name="operate" :item="item" />
</template>
</td>
</tr>
</template>
</v-data-table-server>
</v-card>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch, PropType } from 'vue';
import { ElDropdown, ElDropdownMenu, ElDropdownItem } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { formatDate } from "@/utils/formatDate"
import filterSVG from "@/assets/images/admin-backend/filter.svg"
import { i18n } from "@/utils/i18n";
/**
* @description 可配置相关插槽
* searchButton 搜索框中按钮插槽
* operate 表格中操作列中插槽
*/
/**
* @param {Array} TableHeaders // 表格表头
* @param {Array} TableData // 表格数据
* @param {Boolean} SiftHeader // 是否使用筛选表头
* @param {Number} Total // 分页总数
* @structure
* field: 对应数据的key
* label: 表头文本
* labelTip: 表头是否显示提示图标,默认flase
* labelTipTitle: 表头提示文本
* type: time 后端返回时间戳时配置类型,为time时转为YYYY-MM-DD hh:mm:ss格式
* search: {
* hidden: 此数据是否使用搜索,默认true
* component: Select下拉框 DatePicker时间筛选 Input输入框
* componentProps: {
* type
* unlinkPanels
* valueFormat
* }
* }
* formatter: function // 用来处理需要转化的数据,返回是一个简单类型
* render: function // 用来实现动态创建DOM元素
*/
const props = defineProps({
TableHeaders: Array as () => any,
TableData: Array as () => any,
SiftHeader: Boolean,
Total: Number,
HideFooter: Boolean,
onRowClick: Function as PropType<(row: any, value: any) => void>
})
// 默认分页数和数据量
const pagination = reactive({
page: 1,
itemsPerPage: 10, // 每页显示的行数
})
const perPageOptions = reactive([
{ value: 10, title: '10' },
{ value: 25, title: '25' },
{ value: 50, title: '50' },
{ value: 100, title: '100' }
])
/**
* @description 表格表头
* 为了自定义表头数据,不使用props中的数据而是重新定义变量接收
*/
const tableHeaderList = ref([])
/* 搜索表单 */
const form: any = reactive({})
/**
* 显示搜索表单控件
* 如果表头中存在需要搜索条件则为true
*/
const showSearch = ref(false)
// 筛选默认勾选
const check: any = ref(props.TableHeaders)
// 筛选列表
const checkList: any = ref(props.TableHeaders)
/**
* @description 处理搜索表单和筛选
*/
const handleForm = () => {
// 筛选是否设置了hidden
props.TableHeaders.forEach((item: any) => {
if (!item.search.hidden) {
form[item.field] = ''
showSearch.value = true
}
})
}
/* 多选框发生变化时触发 */
const changeCheckbox = (value: string[]) => {
// 原数组对比组件中返回的数组,然后进行重组
const list = props.TableHeaders.map((item: any) => {
let data = null
value.forEach((row: any) => {
if (row.label === item.label) {
data = item
return
}
})
return data
})
// 剔除掉要隐藏的对象
tableHeaderList.value = list.filter((row: any) => {
return row != null
})
}
// element的国际化
const locale = ref(zhCn)
watch(
[() => props.TableHeaders, () => i18n.global.locale],
([newHeaders, newLocale], [oldHeaders, oldLocale]) => {
check.value = newHeaders
checkList.value = newHeaders
// element的时间选择器切换中英文
locale.value = newLocale.value == 'cn' ? zhCn : en
// 重新调用 changeCheckbox 以应用新的表头
changeCheckbox(check.value)
},
{ immediate: true, deep: true }
)
const emit = defineEmits()
/**
* @description 提交搜索表单
* 父组件使用@onSubmit 绑定事件
*/
const submit = () => {
emit('onSubmit', form);
}
/**
* @description 切换分页
* 父组件使用@pagination 绑定事件
*/
const switchPagination = (options: any) => {
pagination.page = options.page;
pagination.itemsPerPage = options.itemsPerPage;
emit('pagination', options);
}
/**
* @description 处理formatter
* @param item 列表数据
* @param row 表头定义对象
*/
const handleFormatter = (item: any, row: any) => {
if ('formatter' in row) {
return row.formatter(item[row.field])
} else {
return item[row.field]
}
}
onMounted(() => {
handleForm()
})
</script>
<style lang="scss" scoped>
::v-deep(.el-form-item__label-wrap) {
margin-left: 0 !important;
}
</style>
下面是使用这个封装组件的父组件
HTML文件
<OperateTable :TableHeaders="ActivationTableHeaderList" :TableData="ActivationTableDataList"
:Total="total" @onSubmit="(childParams: any) => subSearch('Activation')(childParams)"
@pagination="(childParams: any) => switchPagination('Activation')(childParams)"
:on-row-click="handleRowClick">
<template v-slot:operate="{ item }">
<v-menu open-on-hover :v-model="selectOperate">
<template v-slot:activator="{ props }">
<v-icon icon="mdi-dots-horizontal" start v-bind="props" />
</template>
<v-list class="theme-list">
<v-list-item v-for="(operation, index) in operationList" :key="index" color="primary"
:ripple="true" @click="handleAction(operation, item)">
<v-list-item-title v-if="operation.label !== '冻结'" class="text-subtitle-1 font-weight-regular">
{{ operation.label }}
</v-list-item-title>
<v-list-item-title v-else class="text-subtitle-1 font-weight-regular">
{{ operation.label === '冻结' && item.status === 1 ? '冻结' : '解冻' }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</template>
</OperateTable>
JS部分
import { ref, reactive, onMounted, computed, provide, h } from 'vue';
/* 为了使用动态使用icon引入组件 */
import { VIcon } from 'vuetify/components';
/* 列表数据 */
const TableDataList: any = ref([])
/* 搜索查询 */
const getUserList = async (value: string) => {
const data: any = {
page: tablePage.value,
size: itemsPerPage.value,
...searchFrom.value
}
const res: any = await getUserListApi(data)
if (res.code !== 0) return
TableDataList.value = res.data.list
total.value = res.data.total
}
/* 动态DOM元素的事件 */
const handleRowClick = async (row: any, value: string) => {
const params = {
key: row[value]
}
const res: any = await decryptApi(params)
if (res.code !== 0) return
};
/* 定义表头 */
const TableHeaderList = computed(() => {
return ([
{
field: 'id',
label: '卡ID',
width: '60px',
search: {
component: 'Input',
}
},
{
field: 'card_no',
label: '卡号',
search: {
component: 'Input',
}
},
{
field: 'cardholder',
label: '持卡人',
width: '130px',
search: {
hidden: true
}
},
{
field: 'expire',
label: '有效期(MM/YY)',
width: '130px',
search: {
hidden: true
}
},
{
field: 'safe_code',
label: '安全码',
width: '160px',
search: {
hidden: true
},
// Vue3特性--动态插入DOM元素
render: (row: any) => h(VIcon, { icon: 'mdi-eye-outline', style: 'color: #939a9d; cursor: pointer;', onClick: () => handleRowClick(row, 'safe_code') })
},
{
field: 'tag',
label: '标签',
search: {
component: 'Input',
}
},
{
field: 'created_at',
label: '建卡时间',
width: '130px',
search: {
hidden: true
}
},
{
field: 'status',
label: '卡状态',
width: '130px',
search: {
component: 'Select',
optionApi: () => {
const res = [
{
value: 0,
label: '冻结'
},
{
value: 1,
label: '激活'
}
]
return res
}
},
formatter: (cellValue: number) => {
let typeText: string | number = ''
switch (cellValue) {
case 0:
typeText = '冻结'
break
case 1:
typeText = '激活'
break
default:
typeText = cellValue
break
}
return typeText
}
},
{
field: 'balance',
label: '可用余额',
width: '130px',
search: {
hidden: true
},
formatter: (cellValue: any) => {
const number = Math.floor(cellValue * 100 / 100)
return number.toFixed(2)
}
},
{
field: 'operate',
label: t("operate"),
width: '50px',
search: {
hidden: true
}
}
])
});