Vue3指令篇 -- 自定义vue指令,实现基于el-table的行内编辑功能 -- 支持使用element组件/自定义组件

文章介绍了一种在ElementUI或ElementPlus表格中实现行内编辑功能的方法,通过自定义Vue.js指令减少了代码量,并使用store管理编辑状态。指令支持输入类型的编辑,同时提到了扩展到其他组件类型的需求和分页优化的问题。
摘要由CSDN通过智能技术生成

初衷

正常情况下如果我们需要在 element-uielement-plus 的表格中实现行内编辑功能,需要使用 <template #default="{ row }">,然后通过 v-if 去判断显示 span 还是 el-input 。如果行内多处需要进行行内编辑时,就明显的增加了代码量。(当然最原始的初衷就是摸鱼时看看能不能实现)

优缺点

优点:template的代码量减少,每个table自己维护自己的编辑方法,代码逻辑统一封装,使用也很方便;现已支持使用element组件或者自定义的组件。

指令具体实现

代码中提供了注释,目前只支持 input 输入类型。思考过动态插入 element 的表单组件,实力有限试过很多方法后始终有问题。

/**
 * 表格编辑指令
 * @param {Array} value - [editableList, props, save, getList]
 * @param {Array} editableList - 可编辑列的索引
 * @param {Array} props - 可编辑列对应的prop名称列表
 * @param {Function} save - 保存编辑的方法
 * @param {Function} getList - 获取列表的方法
 * @example
 * <el-table v-table-edit="[editableList, props, save, getList]">
 * 	<el-table-column prop="name" label="姓名"></el-table-column>
 * 	<el-table-column prop="age" label="年龄"></el-table-column>
 *  <el-table-column label="操作">
 * 		只需要完成编辑按钮的逻辑
 *  	具体可参考/geneGenieDx/dataset代码
 *  </el-table-column>
 * </el-table>
 * @description
 * 待更新功能:
 * 	a. 支持其他类型的编辑列的输入框,目前只支持文本框
 * 	b. 分页会带着编辑的状态,需要优化;(已解决:应该有些状态需要通过store去管理)
 * 	c. edit-cancel然后分页之后,会导致数据错乱(已解决:设置font-size=0px)
 */
import _ from 'lodash'
import { parse, stringify } from 'flatted'
import modal from '@/plugins/modal'
import store from '@/store'
import i18n from '@/lang'
const { t } = i18n.global

let editableList = []
let targetRow = null

// 连续点击了多行的编辑按钮后,只有最后一行的编辑状态会被保存,multiRowEdit用于区分这种情况下的调用
function updateRowDom(targetRow, editableList, multiRowEdit = true) {
	Array.from(targetRow.children).forEach((item, i) => {
		if (editableList.includes(i)) {
			item.children[0].style.fontSize = '14px'
			item.children[0].lastElementChild?.tagName === 'INPUT' && item.children[0].lastElementChild.remove()
		}
	})

	if (multiRowEdit) {
		targetRow = null
		store.dispatch('table/setEditId', '')
	}
}

export default {
	mounted(el, bind) {
		const { value } = bind
		const [indexList, props, save, getList] = value
		let newRow = {}
		editableList = indexList

		el.addEventListener('click', e => {
			if (['Edit', 'edit', '编辑'].includes(e.target.innerText)) {
				// 多次点击不同行的编辑清空上一次的编辑状态
				store.state.table.editId && targetRow && updateRowDom(targetRow, editableList, false)

				// 编辑
				const rowData = store.state.table.rowData
				newRow = parse(stringify(rowData.row))
				targetRow = e.querySelectorAll('tbody .el-table__row')[rowData.index]

				Array.from(targetRow.children).forEach((item, i) => {
					if (editableList.includes(i)) {
						const input = document.createElement('input')
						input.type = rowData.inputType
						input.value = newRow[props[i]]
						input.style.width = '100%'
						input.style.padding = '4px 6px'
						input.style.boxSizing = 'border-box'
						input.style.outline = 'none'
						input.style.fontSize = '14px'

						input.onblur = e => {
							newRow[props[i]] = e.target.value
						}

						item.children[0].style.fontSize = '0px'
						// item.children[0].innerHTML = ''
						item.children[0].appendChild(input)
					}
				})
			}  else if (['Save', 'save', '保存'].includes(e.target.innerText)) {
				// 保存编辑
				save(newRow).then(() => {
					updateRowDom(targetRow, editableList)
					modal.msgSuccess(t('pub.operationSuc'))
					getList()
				}).catch(() => {
					updateRowDom(targetRow, editableList)
				})
			} else if (['Cancel', 'cancel', '取消'].includes(e.target.innerText)) {
				// 取消编辑
				updateRowDom(targetRow, editableList)
			}
		})
	},

	beforeUpdate() {
		if (store.state.table.pageChange && targetRow) {
			updateRowDom(targetRow, editableList)
			store.dispatch('table/setPageChange', false)
		}
	}
};
通过store管理编辑状态
// store.js
const state = {
	pageChange: false,
	editId: '',
	rowData: {},
}

const mutations = {
	SET_PAGE_CHANGE: (state, view) => {
		state.pageChange = view
	},
	SET_EDIT_ID: (state, view) => {
		state.editId = view
	},
	SET_ROW_DATA: (state, view) => {
		state.rowData = view
	},
}

const actions = {
	setPageChange({ commit }, view) {
		commit('SET_PAGE_CHANGE', view)
	},
	setEditId({ commit }, view) {
		commit('SET_EDIT_ID', view)
	},
	setRowData({ commit }, view) {
		commit('SET_ROW_DATA', view)
	}
}
通过hook(composable)对store的操作进行封装
export const useTableEdit = (keyName) => {
	const store = useStore()

	const editId = computed(() => store.state.table.editId)

	const handleEdit = (row, index, inputType = 'text') => {
		store.dispatch('table/setEditId', row[keyName])
		store.dispatch('table/setRowData', { row, index, inputType })
	}

	return {
		editId,
		handleEdit,
	}
}
使用例子

参数说明见指令的注释

// template
<el-table v-tableEdit="[[0, 1], ['caseSetName', 'descInfo'], saveRow, getCases]" :data="tableData">
			<el-table-column :label="t('task.dataset')" prop="caseSetName" min-width="100">
			</el-table-column>
			<el-table-column :label="t('sample.description')" prop="descInfo" min-width="200">
				<template #default="{ row }">
					<span>{{ row.descInfo || '-' }}</span>
				</template>
			</el-table-column>
			<el-table-column :label="t('sample.number')" prop="caseNum" />
			<el-table-column :label="t('sample.creator')" prop="createBy" />
			<el-table-column :label="t('pub.operates')" width="200">
				<template #default="{ row, $index }">
					<el-button link class="del-text" @click="delCase(row.caseSetId, $index)">{{
						t('pub.delete')
					}}</el-button>
					<el-button
						v-if="editId !== row.caseSetId"
						link
						@click="handleEdit(row, $index)"
					>{{t('pub.edit')}}</el-button>
					<template v-else>
						<el-button type="primary" link>{{ t('pub.save') }}</el-button>
						<el-button link>{{ t('pub.cancel') }}</el-button>
					</template>
				</template>
			</el-table-column>
		</el-table>

// script
// 使用hooks提供的useTableEdit
const { editId, handleEdit } = useTableEdit('caseSetId')
支持使用element组件、自定义组件等

document.createElement('input') 开始的一段创建input的逻辑都可以使用 render 函数去使用 element-plus 的组件。还可以在 render 中添加一个 type 参数用于判断编辑时的表单类型,具体实现可以自己尝试一下

import { h, createApp } from 'vue'
import { ElInput } from 'element-plus'

function render(target, value) {
	const app = createApp({
		render: () => h(
			ElInput,
			{
				modelValue: value,
        		'onUpdate:modelValue': (nVal) => emit('update:modelValue', nVal)
			},
		),
	})

	const div = document.createElement('div')
	app.mount(div)
	target.appendChild(div)
}
展望与未来

save 的按钮其实可以通过 input 的回车事件实现,如果有其他表单可能就不行了,比如说 select 之类的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值