构思:相信用element的小伙伴都有一个烦恼,就是element-puls的表格标签太多了,很容易看错,所以我就魔改了一下。通过vue3的v-for循环el-table中的el-table-column标签,实现一些功能。
需要实现功能: 实现单选功能 element的表格单选选中看着很别扭,没有单选框,我们给加上。 当表单需要一对多的时候,使用表格可以有校验。 可以拖拉位置。
html部分
<template>
<div class="p-table">
<el-button @click="a">开发环境专用(查看是否校验通过)</el-button>
<el-table
class="tableClass"
:row-key="props.keyId"
:height="props.height"
:stripe="props.stripe"
:border="props.border"
:data="list"
@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-form-${columnItem.prop}-${index}`"
>
<!-- `${(80 / tableColumn.length) * (10 + columnItem.label)}` -->
<el-table-column
:fixed="columnItem.fixed"
:label="columnItem.label"
:prop="columnItem.prop"
:rules="columnItem.rules"
:width="
columnItem.width !== undefined
? columnItem.width
: columnItem.label.includes('单号') ||
columnItem.label.includes('物资编码') ||
columnItem.label.includes('物资名称') ||
columnItem.label.includes('规格')
? '120'
: columnItem.label == '操作'
? '300'
: ''
"
:show-overflow-tooltip="columnItem.showText"
>
<template #header>
<div>
<span style="color: red" v-if="columnItem.required">*</span>
<span> {{ columnItem.label }}</span>
</div>
</template>
<!-- -->
<template #default="scope">
<el-form
scroll-to-error
class="formClass"
ref="addFormRef"
:model="scope.row"
label-width="0px"
inline
>
<el-form-item
ref="itemFormRef"
class="itemForm"
label=""
:rules="
columnItem.rules || [
{
required: columnItem.required,
message: `请输入或者选择${columnItem.label}`,
trigger: columnItem.type == '输入框' ? 'blur' : 'change',
},
]
"
:prop="columnItem.required ? columnItem.prop : null"
>
<slot
name="columnCell"
:record="scope.row"
:scope="scope"
:column="{
prop: columnItem.prop,
label: columnItem.label,
width: columnItem.width,
}"
>
<!-- <el-input
v-if="columnItem.type == '输入框'"
clearable
:disabled="columnItem.disabled"
:placeholder="`请填写${columnItem.label}`"
v-model="scope.row[columnItem.prop]"
/>
<el-input-number
v-else-if="columnItem.type == '数字输入框'"
clearable
:min="0"
:max="99999"
:disabled="columnItem.disabled"
:placeholder="`请填写${columnItem.label}`"
v-model="scope.row[columnItem.prop]"
/>
<p-select
v-else-if="columnItem.type == '下拉框'"
clearable
:disabled="columnItem.disabled"
:placeholder="`请选择${columnItem.label}`"
v-model="scope.row[columnItem.prop]"
:option="
columnItem.option || columnItem.opt || columnItem.t
"
/>
<el-date-picker
v-else-if="columnItem.type == '日期'"
:disabled="columnItem.disabled"
:placeholder="`请选择${columnItem.label}`"
v-model="scope.row[columnItem.prop]"
value-format="YYYY-MM-DD"
clearable
/> -->
{{ getData(scope.row, columnItem.prop, columnItem.label) }}
</slot>
</el-form-item>
</el-form>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
js部分
<script setup name="p-table-form">
import { computed } from 'vue'
import Sortable from 'sortablejs'
import { v4 as uuidv4 } from 'uuid'
const { proxy } = getCurrentInstance()
/**
* 使用此组件 外部需要设置宽度 不能100%宽度 必须写固定宽度 否则会出现宽度无法自适应
*/
const props = defineProps({
// 列表主键id(必须设置且自动带出)
keyId: {
required: false,
type: String,
default: 'id',
},
// 数据
data: {
required: true,
type: Array,
default: () => [],
},
/**
* 列表每一行
*/
column: {
required: true,
type: Array,
default: () => [],
},
// 是否展开
defaultExpandAll: {
type: Boolean,
default: false,
},
// 拖动class(需要提供本身或上级calss)
className: {
type: String,
default: '',
required: false,
},
// 加载状态
loading: {
type: Boolean,
default: false,
required: false,
},
// 高度
height: {
type: String,
default: '40rem',
required: false,
},
// 值为空是 显示
empty: {
type: String,
default: '--',
required: false,
},
// 是否需要序号
index: {
type: Boolean,
default: true,
required: false,
},
// 是否需要复选
isSel: {
type: Boolean,
default: false,
},
// 是否需要边框
border: {
type: Boolean,
default: true,
},
stripe: {
type: Boolean,
default: true,
},
// 是否需要单选(单选必须传入keyId,用作判断)
isRadio: {
type: Boolean,
default: false,
},
})
// 注册事件名称
const emit = defineEmits(['current-change', 'change', 'listChange'])
// 异常说明
const errorMsg = reactive({
1: '传入数据类型错误,类型应为数组!',
2: '传入"column"数据错误,prop属性不能重复!',
3: '当isRadio启用时,必须传入keyId作为唯一标识!',
4: `当前keyId为${props.keyId},获取不到主键ID,请传入正确keyId`,
})
// 单选时,数据带出
const tableRadio = ref({
[props.keyId]: '',
})
/**
* 白名单 判断是否需要 show-overflow-tooltip
* 一般 操作按钮 开关 标签不需要show-overflow-tooltip 就设置白名单
* 可设置prop为这些参数,或者设置label同样效果
*/
/**
*
*/
const whiteList = ref(['x', '操作', '状态'])
// 复选勾选
const selectionChange = (selection, a, b) => {
try {
// 判断keyId是否正确
const resultId = selection.every((t) => t[props.keyId])
if (!resultId) {
emit('change', {
ids: [],
uids: selection.map((item) => item['uid']),
index: selection.map((item) => item['index']),
row: selection,
})
throw errorMsg['4']
}
emit('change', {
ids: selection.map((item) => item[props.keyId]),
index: selection.map((item) => item['index']),
uids: selection.map((item) => item['uid']),
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]) {
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 'p-table组件:' + 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 'p-table组件:' + errorMsg['1']
} else {
// 单选时,数据刷新默认选中一项
if (props.data.length > 0 && props.isRadio) {
// 每次置空
tableRadio.value[props.keyId] = ''
const obj = props.data[0]
// 如果为空 抛出异常
if (!obj[props.keyId]) {
throw 'p-table组件:' + errorMsg['4']
}
// 单选时,默认提供一个唯一标识
tableRadio.value[props.keyId] = obj[props.keyId]
}
// 数据参入uuid
props.data.forEach((t, ind) => {
t[`uid`] = uuidv4()
t['index'] = ind
})
return props.data || []
}
})
// 表格拖动
// 创建拖拽实例
const initSort = () => {
if (!props.className) return
console.log(props.className, 'props.className')
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)
}
// 校验表格
const validate = async () => {
const elements = await proxy.$refs.itemFormRef
if (!Array.isArray(elements))
return console.log(
'%cp-table-form组件:列表数据错误 => ' + typeof elements,
'color:red;font-size:14px'
)
elements.forEach((t) => t.validate())
return new Promise(async (resolve, reject) => {
try {
for (let index = 0; index < elements.length; index++) {
const v = await elements[index].validate()
}
resolve(true)
} catch {
proxy.warningMsg('列表校验未通过,请注意查看!')
reject('校验表格err')
}
})
}
onMounted(() => {
if (props.isTree) return
initSort()
})
const a = async () => {
await validate()
}
defineExpose({
validate,
})
</script>
css
<style scoped lang="scss">
.p-table {
width: 100%;
}
.p-table ::v-deep .el-table__cell {
// padding: 0 !important;
}
.formClass {
overflow: hidden;
}
.itemForm {
width: 100% !important;
}
.tableClass {
width: 100% !important;
margin-top: 0.9375rem;
}
</style>