因为项目需求需要自定义的表单表头,项目本身框架是element。只能去基于element封装改变成ant思路。
里面做了一些处理:相信有很多前端朋友遇到字段和后端没有对齐 或者后端字段返回空还要说是前端的问题。
我做了一些处理 可以带出字段变量 字段名称以及值
HTML:
<template>
<div class="p-table">
<el-table
class="tableClass"
:tree-props="{
children: treeProp.children || 'children',
hasChildren: treeProp.hasChildren || 'hasChildren',
}"
:row-key="props.keyId"
v-loading="props.loading"
:height="props.height"
:stripe="props.stripe"
:border="props.border"
:show-summary="props.summary"
:data="list"
:summary-method="
(val) => {
return props.summaryMethod(val)
}
"
@selection-change="selectionChange"
@current-change="currentChange"
>
<el-table-column
v-if="isSel && !isRadio"
type="selection"
width="55"
align="center"
fixed="left"
/>
<el-table-column v-if="isRadio" width="70" fixed="left" align="center">
<template #default="{ row }">
<el-radio-group
v-if="row[props.keyId]"
v-model="tableRadio[props.keyId]"
>
<el-radio :label="row[props.keyId]"> </el-radio>
</el-radio-group>
</template>
</el-table-column>
<el-table-column
v-if="index"
label="序号"
type="index"
width="70"
fixed="left"
align="center"
/>
<template
v-for="(columnItem, index) in tableColumn"
:key="`p-table-${columnItem.prop}-${index}`"
>
<!-- `${(80 / tableColumn.length) * (10 + columnItem.label)}` -->
<el-table-column
:fixed="getFixed(columnItem)"
:label="columnItem.label"
:prop="columnItem.prop"
:width="
columnItem.width !== undefined
? columnItem.width
: columnItem.label == '操作'
? '180'
: ''
"
:show-overflow-tooltip="
columnItem.showText === undefined
? isShowText(columnItem)
: columnItem.showText
"
>
<!-- -->
<template #default="scope">
<slot
name="columnCell"
:record="scope.row"
:scope="scope"
:column="{
prop: columnItem.prop,
label: columnItem.label,
width: columnItem.width,
}"
>{{ getData(scope.row, columnItem.prop, columnItem.label) }}</slot
>
</template>
</el-table-column>
</template>
</el-table>
<div
v-if="isPage && total > 0"
class="pagination-container"
:class="{ hidden: hidden }"
id="paginationId"
>
<el-pagination
:background="background"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:pager-count="pagerCount"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
JS
<script setup name="p-table">
import { computed } from 'vue'
import Sortable from 'sortablejs'
const { proxy } = getCurrentInstance()
const props = defineProps({
// 列表主键id(必须设置且自动带出)
keyId: {
required: true,
type: String,
default: 'id',
},
// 数据
data: {
required: true,
type: Array,
default: () => [],
},
/**
* 列表每一行
* @param {
label:string, 必填
prop:string, 必填
showText:boolean,
width:string, 非必填
fixed:string, 非必填
} column
*/
column: {
required: true,
type: Array,
default: () => [],
},
// 拖动class(需要提供本身或上级calss)
className: {
type: String,
default: '',
},
// 加载状态
loading: {
type: Boolean,
default: false,
},
// 分页总量
total: {
type: String || Number,
default: 0,
},
// 高度
height: {
type: String,
default: null,
},
// 值为空是 显示
empty: {
type: String,
default: '',
},
// 是否需要序号
index: {
type: Boolean,
default: true,
},
// 是否需要复选
isSel: {
type: Boolean,
default: true,
},
// 是否需要边框
border: {
type: Boolean,
default: true,
},
// 是否需要合计
summary: {
type: Boolean,
default: false,
},
// 合计计算 (自调用 带出一个对象值)
summaryMethod: {
type: Function,
default: () => [],
},
stripe: {
type: Boolean,
default: true,
},
// 是否需要树结构
isTree: {
type: Boolean,
default: false,
},
// 树结构列表时 子集
treeProp: {
type: Object,
default: {
children: 'children',
hasChildren: 'hasChildren',
},
},
// 是否需要单选(单选必须传入keyId,用作判断)
isRadio: {
type: Boolean,
default: false,
},
// 是否需要分页
isPage: {
type: Boolean,
default: true,
},
// 数据总量
total: {
required: true,
type: Number,
},
// 页数
page: {
type: Number,
default: 1,
},
// 每页数量
limit: {
type: Number,
default: 20,
},
// 默认给出选项每页页数
pageSizes: {
type: Array,
default() {
return [10, 20, 20, 50]
},
},
// 移动端页码按钮的数量端默认值5
pagerCount: {
type: Number,
default: document.body.clientWidth < 992 ? 5 : 7,
},
// element 官方配置
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper',
},
// 是否需要背景色
background: {
type: Boolean,
default: true,
},
// 是否分页后触发回滚
autoScroll: {
type: Boolean,
default: true,
},
// 是否隐藏
hidden: {
type: Boolean,
default: false,
},
})
// 注册事件名称
const emit = defineEmits(['current-change', 'change', 'listChange'])
// 异常说明
const errorMsg = reactive({
1: 'p-table组件:传入数据类型错误,类型应为数组!',
2: 'p-table组件:传入"column"数据错误,prop属性不能重复!',
3: 'p-table组件:当isRadio启用时,必须传入keyId作为唯一标识!',
4: `p-table组件:当前keyId为${props.keyId},获取不到主键ID,请传入正确keyId`,
})
// 单选时,数据带出
const tableRadio = ref({
[props.keyId]: '',
})
/**
* 白名单 判断是否需要 show-overflow-tooltip
* 一般 操作按钮 开关 标签不需要show-overflow-tooltip 就设置白名单
* 可设置prop为这些参数,或者设置label同样效果
*/
const whiteList = ref(['x', 's', 'sw', 'tag', '操作', '状态'])
// 复选勾选
const selectionChange = (selection) => {
try {
// 判断keyId是否正确
const resultId = selection.every((t) => t[props.keyId])
if (!resultId) {
throw errorMsg['4']
}
emit('change', {
ids: selection.map((item) => item[props.keyId]),
row: selection,
})
} catch (err) {
throw 'p-table组件:' + err
}
}
// 单选勾选
const currentChange = (selection) => {
try {
// 点击一行时触发
emit('current-change', { ids: selection[props.keyId], row: selection })
// 如果不是单选 禁止 以下操作
if (!props.isRadio) return
// 判断keyId是否正确
if (props.isRadio) {
if (!selection[props.keyId]) {
// proxy.errorMsg(errorMsg['3'])
throw errorMsg['3']
}
}
// 给单选框一个值
tableRadio.value[props.keyId] = selection[props.keyId]
emit('change', { ids: selection[props.keyId], row: selection })
} catch (err) {
throw 'p-table组件:' + err
}
}
// 筛选 是否有重复 prop
const tableColumn = computed(() => {
const newData = Array.from(new Set(props.column.map((t) => t.prop)))
if (props.column.length > newData.length) {
throw errorMsg['2']
} else {
return props.column
}
})
// 做一些东西 数据为空的时候做出显示 给予提示
const getData = (val, key, label) => {
try {
if (val && key && val[key] != null && val[key] != undefined) {
return val[key]
} else {
if (props.empty) {
return props.empty
} else if (!key) {
return `" ${label} " 的 "prop" 为空`
} else {
return `" ${label} "为空, ${key} -> " ${val[key]} "`
}
}
} catch (err) {
throw 'p-table组件:' + err
}
}
// 判断是否类型错误
const list = computed(() => {
if (!Array.isArray(props.data)) {
throw errorMsg['1']
} else {
// 单选时,数据刷新默认选中一项
if (props.data.length > 0 && props.isRadio) {
// 每次置空
tableRadio.value[props.keyId] = ''
const obj = props.data[0]
// 如果为空 抛出异常
if (!obj[props.keyId]) {
throw errorMsg['4']
}
// 单选时,默认提供一个唯一标识
tableRadio.value[props.keyId] = obj[props.keyId]
}
return props.data || []
}
})
// 判断是否需要 show-overflow-tooltip
const isShowText = (val) => {
if (val.label == '备注') {
return true
} else if (val.label == '辅助属性') {
return false
} else if (
whiteList.value.includes(val.label) ||
whiteList.value.includes(val.prop) ||
val.prop.length < 2
) {
return false
} else {
return true
}
}
// 判断是否需要 fixed
const getFixed = (columnItem) => {
if (columnItem.fixed) {
return columnItem.fixed
} else if (columnItem.label == '操作') {
return 'right'
} else {
return false
}
}
// 提取和赋值分页 页数
const currentPage = computed({
get() {
return props.page
},
set(val) {
emit('update:page', val)
},
})
// 提取和赋值每页数量 每页数量
const pageSize = computed({
get() {
return props.limit
},
set(val) {
emit('update:limit', val)
},
})
// 分页器变化时
function handleSizeChange(val) {
if (currentPage.value * val > props.total) {
currentPage.value = 1
}
emit('pagination', { page: currentPage.value, limit: val })
if (props.autoScroll) {
scrollTo(0, 800)
}
}
// 分页器变化时
function handleCurrentChange(val) {
emit('pagination', { page: val, limit: pageSize.value })
if (props.autoScroll) {
scrollTo(0, 800)
}
}
// 表格拖动
// 创建拖拽实例
const initSort = (className) => {
if (!props.className) return
const table = document.querySelector(
`.${props.className} .el-table__body-wrapper tbody`
)
Sortable.create(table, {
group: 'shared',
animation: 150,
easing: 'cubic-bezier(1, 0, 0, 1)',
onStart: () => {},
// 结束拖动事件
onEnd: async ({ newIndex, oldIndex }) => {
setNodeSort(list.value, oldIndex, newIndex)
},
})
}
// 拖拽完成修改数据排序
const setNodeSort = (data, oldIndex, newIndex) => {
const currRow = data.splice(oldIndex, 1)[0]
data.splice(newIndex, 0, currRow)
emit('listChange', list.value)
}
onMounted(() => {
if (props.isTree) return
initSort()
})
</script>
css
<style scoped lang="scss">
.p-table {
width: 100%;
}
.pagination-container {
z-index: 1;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
}
.fixed {
position: absolute;
bottom: 0px;
height: 2rem;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.pagination-container.hidden {
display: none;
}
#paginationId ::v-deep .el-input__inner {
width: 100px !important;
}
#paginationId ::v-deep .el-input {
width: 100px !important;
}
#paginationId ::v-deep .el-select--default {
width: 100px !important;
}
.tableClass {
width: 100% !important;
margin-top: 0.9375rem;
}
</style>