需求分析:
1、表格每一行为input组件,可以输入
2、操作栏可以进行表格行的新增下一行和删除该行,
3、参数名称和参数别名是必填的,未填写时给出input框标红,并且不让进行新增下一行
实现代码如下:
<template>
<a-table :columns="columns" :data-source="dataSource" bordered :pagination="false" :scroll="{ y: 300 }" class="edit-table">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'paramsName'">
<a-input :placeholder="`${column.title}`" v-model:value="editTable[record.id][column.dataIndex]" :class="`name${record.id}`"></a-input>
</template>
<template v-if="column.dataIndex === 'paramsAlia'">
<a-input :placeholder="`${column.title}`" v-model:value="editTable[record.id][column.dataIndex]" :class="'alias' + record.id"></a-input>
</template>
<template v-if="column.dataIndex === 'paramsValue'">
<a-input :placeholder="`${column.title}`" v-model:value="editTable[record.id][column.dataIndex]"></a-input>
</template>
<template v-if="column.dataIndex === 'operation'">
<plus-circle-filled style="color: var(--ant-success-color);font-size: 16px;margin-right: 5px;" @click="handleField('add', record)" />
<minus-circle-filled style="color: var(--ant-error-color); font-size: 16px;" @click="handleField('delete', record)" />
</template>
</template>
</a-table>
</template>
<script lang="ts">
import { cloneDeep } from 'lodash-es'
import { defineComponent, ref, reactive, watch, onMounted } from 'vue'
import { PlusCircleFilled, MinusCircleFilled } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue/es'
const columns = ref<any[]>([
{
title: '参数名称',
dataIndex: 'paramsName',
width: '20%',
align: 'center'
},
{
title: '参数别名',
dataIndex: 'paramsAlia',
width: '20%',
align: 'center'
},
{
title: '参数值',
dataIndex: 'paramsValue',
align: 'center'
},
{
title: '操作',
width: '12%',
dataIndex: 'operation',
align: 'center'
}
])
interface DataItem {
id?: string
paramsName?: string
paramsAlia?: string
paramsValue?: string
}
export default defineComponent({
emits: ['customField'],
setup(_props, _context) {
//字段保存的数据
const editTable = reactive<any>({
'1': {
id: '1',
paramsName: '',
paramsAlia: '',
paramsValue: ''
}
})
// 表格默认数据
const dataSource = ref<DataItem[]>([
{
id: '1',
paramsName: '',
paramsAlia: '',
paramsValue: ''
}
])
// 参数名称下一个dom
const currNextName = ref<HTMLElement>()
// 参数别名下一个dom
const currNextAlias = ref<HTMLElement>()
// 监听input框的focus事件,消除未填写提示
watch(currName, nv => {
nv?.addEventListener('focus', function () {
nv.style.borderColor = ''
nv.style.boxShadow = ''
})
})
watch(currAlias, nv => {
nv?.addEventListener('focus', function () {
nv.style.borderColor = ''
nv.style.boxShadow = ''
})
})
// 发送父组件实时数据
watch(
() => editTable,
nv => {
_context.emit('customField', nv)
},
{ deep: true }
)
const handleField = (val: string, record: any) => {
// 新增行
if (val === 'add') {
// 控制当前行字段是否填写
if (editTable[record.id].paramsName === '') {
nextTick(() => {
let currName = document.querySelector(`.name${record.id}`) as HTMLInputElement
currName.style.borderColor = 'var(--polar-error-color-hover)'
currName.style.boxShadow = '0 0 0 1px #F59B9B'
})
return message.error('参数名称不能为空!')
}
if (editTable[record.id].paramsAlia === '') {
nextTick(() => {
let currAlias = document.querySelector(`.alias${record.id}`) as HTMLInputElement
currAlias.style.borderColor = 'var(--polar-error-color-hover)'
currAlias.style.boxShadow = '0 0 0 1px #F59B9B'
})
return message.error('参数别名不能为空!')
}
let obj = {
id: new Date().getTime().toString(),
paramsName: '',
paramsAlia: '',
paramsValue: ''
}
editTable[obj.id] = cloneDeep(obj)
dataSource.value.push({ id: obj.id })
} else {
// 删除行
if (record.id === '1') return
let currIndex = dataSource.value.findIndex(item => item.id === record.id)
if (currIndex !== -1) {
dataSource.value.splice(currIndex, 1)
}
delete editTable[record.id]
}
}
return {
dataSource,
columns,
editTable,
handleField
}
}
})
</script>
以上代码可以实现逻辑功能,但是有个bug是点击时点击必填字段不为空的情况下,新增一行后,再次点击①这个新增按钮,还会新增一行,原因是每次判断是否为空的时候,我都是根据点击的这样的id去找 editTable对象中的值进行判空,只要这一行的字段不为空就增加一行,但是下面已经有一行为空的了,理论上不应该在新增一行空,而是提示下一行不能为空,
所以思路不能仅仅根据当前行是否字段为空进行判断,而应该根据保存数据得到editTable这个整体对象循环判断那个子对象为空进行判空处理:
逻辑如下:
// 控制非当前行字段是否填写
for (let item in editTable) {
if (editTable[item].paramsName === '') {
nextTick(() => {
if (currNextName.value) {
currNextName.value.style.borderColor = 'var(--polar-error-color-hover)'
currNextName.value.style.boxShadow = '0 0 0 1px #F59B9B'
}
})
return message.error('参数名称不能为空!')
}
if (editTable[item].paramsAlia === '') {
nextTick(() => {
if (currNextAlias.value) {
currNextAlias.value.style.borderColor = 'var(--polar-error-color-hover)'
currNextAlias.value.style.boxShadow = '0 0 0 1px #F59B9B'
}
})
return message.error('参数别名不能为空!')
}
}
为了首行未填写出现提示后,消除未填写提示,必须赋值,否则监听input中focus拿不到currNextName,为空值
onMounted(() => {
// 初始化默认值
currNextName.value = document.querySelector(`.name${dataSource.value[0].id}`) as HTMLElement
currNextAlias.value = document.querySelector(`.alias${dataSource.value[0].id}`) as HTMLElement
})
并且为了解决上面bug的问题,必须控制点击新增下一行的时候,把当前保存 dom结点更新为最新下一行的dom数据,这样每次点击都可以找到新一行的字段,提示未填写
nextTick(() => {
currNextName.value = document.querySelector(`.name${obj.id}`) as HTMLInputElement
currNextAlias.value = document.querySelector(`.alias${obj.id}`) as HTMLInputElement
})
完整代码如下:
<script lang="ts">
import { cloneDeep } from 'lodash-es'
import { defineComponent, ref, reactive, watch, onMounted } from 'vue'
import { PlusCircleFilled, MinusCircleFilled } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue/es'
const columns = ref<any[]>([
{
title: '参数名称',
dataIndex: 'paramsName',
width: '20%',
align: 'center'
},
{
title: '参数别名',
dataIndex: 'paramsAlia',
width: '20%',
align: 'center'
},
{
title: '参数值',
dataIndex: 'paramsValue',
align: 'center'
},
{
title: '操作',
width: '12%',
dataIndex: 'operation',
align: 'center'
}
])
interface DataItem {
id?: string
paramsName?: string
paramsAlia?: string
paramsValue?: string
}
export default defineComponent({
emits: ['customField'],
setup(_props, _context) {
//字段保存的数据
const editTable = reactive<any>({
'1': {
id: '1',
paramsName: '',
paramsAlia: '',
paramsValue: ''
}
})
// 表格默认数据
const dataSource = ref<DataItem[]>([
{
id: '1',
paramsName: '',
paramsAlia: '',
paramsValue: ''
}
])
// 参数名称下一个dom
const currNextName = ref<HTMLElement>()
// 参数别名下一个dom
const currNextAlias = ref<HTMLElement>()
onMounted(() => {
// 初始化默认值
currNextName.value = document.querySelector(`.name${dataSource.value[0].id}`) as HTMLElement
currNextAlias.value = document.querySelector(`.alias${dataSource.value[0].id}`) as HTMLElement
})
// 监听input框的focus事件,消除未填写提示
watch(currNextName, nv => {
nv?.addEventListener('focus', function () {
nv.style.borderColor = ''
nv.style.boxShadow = ''
})
})
watch(currNextAlias, nv => {
nv?.addEventListener('focus', function () {
nv.style.borderColor = ''
nv.style.boxShadow = ''
})
})
// 发送父组件实时数据
watch(
() => editTable,
nv => {
_context.emit('customField', nv)
},
{ deep: true }
)
const handleField = (val: string, record: any) => {
// 新增行
if (val === 'add') {
// 控制当前行字段是否填写
if (editTable[record.id].paramsName === '') {
nextTick(() => {
let currName = document.querySelector(`.name${record.id}`) as HTMLInputElement
currName.style.borderColor = 'var(--polar-error-color-hover)'
currName.style.boxShadow = '0 0 0 1px #F59B9B'
})
return message.error('参数名称不能为空!')
}
if (editTable[record.id].paramsAlia === '') {
nextTick(() => {
let currAlias = document.querySelector(`.alias${record.id}`) as HTMLInputElement
currAlias.style.borderColor = 'var(--polar-error-color-hover)'
currAlias.style.boxShadow = '0 0 0 1px #F59B9B'
})
return message.error('参数别名不能为空!')
}
// 控制非当前行字段是否填写
for (let item in editTable) {
if (editTable[item].paramsName === '') {
nextTick(() => {
if (currNextName.value) {
currNextName.value.style.borderColor = 'var(--polar-error-color-hover)'
currNextName.value.style.boxShadow = '0 0 0 1px #F59B9B'
}
})
return message.error('参数名称不能为空!')
}
if (editTable[item].paramsAlia === '') {
nextTick(() => {
if (currNextAlias.value) {
currNextAlias.value.style.borderColor = 'var(--polar-error-color-hover)'
currNextAlias.value.style.boxShadow = '0 0 0 1px #F59B9B'
}
})
return message.error('参数别名不能为空!')
}
}
let obj = {
id: new Date().getTime().toString(),
paramsName: '',
paramsAlia: '',
paramsValue: ''
}
editTable[obj.id] = cloneDeep(obj)
dataSource.value.push({ id: obj.id })
nextTick(() => {
currNextName.value = document.querySelector(`.name${obj.id}`) as HTMLInputElement
currNextAlias.value = document.querySelector(`.alias${obj.id}`) as HTMLInputElement
})
} else {
// 删除行
if (record.id === '1') return
let currIndex = dataSource.value.findIndex(item => item.id === record.id)
if (currIndex !== -1) {
dataSource.value.splice(currIndex, 1)
}
delete editTable[record.id]
}
}
return {
dataSource,
columns,
editTable,
handleField
}
}
})
</script>