<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<script src="https://unpkg.com/vue@3.2.47"></script>
<link rel="stylesheet" href="https://unpkg.com/element-plus@1.1.0-beta.12/dist/index.css">
<script src="https://unpkg.com/element-plus@1.1.0-beta.12"></script>
<title>Element Plus demo</title>
</head>
<body>
<div id="app">
<div class="tb-container" ref="tbContainerRef">
<h2 style="margin:0;text-align:center;">Element Plus 可编辑表格</h2>
<!-- 表格 -->
<el-table
:data="testDatas"
border
style="width: 100%;margin-top:10px"
@cell-dblclick="cellDblclick"
@header-contextmenu="(column, $event) => rightClick(null, column, $event)"
@row-contextmenu="rightClick"
:row-class-name="tableRowClassName"
>
<el-table-column v-if="columnList.length > 0" type="index" :label="'编号'" :width="50"></el-table-column>
<el-table-column
v-for="(col,idx) in columnList"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:index="idx"
/>
</el-table>
<p style="text-align:left;color:#ccc;">右键菜单,双击编辑</p>
<div>
<h3 style="text-align:center;">实时数据展示</h3>
<label>当前目标:</label>
<p>{{JSON.stringify(curTarget)}}</p>
<label>表头:</label>
<p v-for="col in columnList" :key="col.prop">{{JSON.stringify(col)}}</p>
<label>数据:</label>
<p v-for="(data,idx) in testDatas" :key="idx">{{JSON.stringify(data)}}</p>
</div>
<!-- 右键菜单框 -->
<div v-show="showMenu" id="contextmenu" @mouseleave="showMenu=false">
<i class="el-icon-circle-close hideContextMenu" @click="showMenu=false"></i>
<el-button size="mini" type="primary" @click="addColumn()">前方插入一列</el-button>
<el-button size="mini" type="primary" @click="addColumn(true)">后方插入一列</el-button>
<el-popconfirm title="确定删除该列吗?" @confirm="delColumn">
<template #reference>
<el-button type="primary" size="mini">删除当前列</el-button>
</template>
</el-popconfirm>
<el-button size="mini" type="primary" @click="renameCol($event)">更改列名</el-button>
<div v-show="!curTarget.isHead">
<hr/>
<el-button size="mini" type="primary" @click="addRow()">上方插入一行</el-button>
<el-button size="mini" type="primary" @click="addRow(true)">下方插入一行</el-button>
<el-popconfirm title="确定删除该行吗?" @confirm="delRow">
<template #reference>
<el-button type="primary" size="mini">删除当前行</el-button>
</template>
</el-popconfirm>
</div>
</div>
<!-- 单元格/表头内容编辑框 -->
<div v-show="showEditInput" id="editInput" @mouseleave="showEditInput=false">
<el-input placeholder="请输入内容" v-model="curTarget.val" clearable @change="updTbCellOrHeader">
<template #prepend>{{curColumn.label || curColumn.prop}}</template>
</el-input>
</div>
</div>
<!-- 相关链接 -->
<div style="text-align:left;border-top:1px solid #666">
<p>相关链接: </p>
<a href="https://blog.csdn.net/ymzhaobth/article/details/104716431" target="_blank">博文</a><br/>
<a href="https://github.com/zymbth/editable-table" target="_blank">代码仓库</a><br/>
<a href="https://zymbth.github.io/editable-table/" target="_blank">Github Page</a><br/>
<a href="https://codepen.io/zymbth/pen/BaJpvoO" target="_blank">Editable Table V1 (vue3 & Element Plus)</a><br/>
<!-- <a href="https://codepen.io/zymbth/pen/gOogZMK" target="_blank">Editable Table V2 (vue3 & Element Plus)</a><br/> -->
<span>Editable Table V2 (vue3 & Element Plus)</span><br/>
<a diabled href="https://codepen.io/zymbth/pen/eYWYrmz" target="_blank">Editable Table V2 (vue2 & Element UI)</a>
</div>
</div>
<script type="module">
const App = {
data() {
return {
columnList: [
{ prop: "name", label: '姓名' },
{ prop: "age", label: '年龄' },
{ prop: "city", label: '城市' },
{ prop: "tel", label: '电话' }
],
testDatas: [
{ name: '张三', age: 24, city: '广州', tel: '13312345678' },
{ name: '李四', age: 25, city: '九江', tel: '18899998888' }
],
showMenu: false, // 显示右键菜单
showEditInput: false, // 显示单元格/表头内容编辑输入框
curTarget: { // 当前目标信息
rowIdx: null, // 行下标
colIdx: null, // 列下标
val: null, // 单元格内容/列名
isHead: undefined // 当前目标是表头?
},
countCol: 0, // 新建列计数
}
},
computed: {
curColumn () {
return this.columnList[this.curTarget.colIdx]??{}
}
},
methods: {
// 单元格双击事件 - 更改单元格数值
cellDblclick(row, column, cell, event) {
this.showEditInput = false
if(column.index == null) return
this.locateMenuOrEditInput('editInput', 200, event) // 编辑框定位
this.showEditInput = true
// 当前目标
this.curTarget = {
rowIdx: row.row_index,
colIdx: column.index,
val: row[column.property],
isHead: false
}
},
// 单元格/表头右击事件 - 打开菜单
rightClick(row, column, $event) {
// 阻止浏览器自带的右键菜单弹出
$event.preventDefault()
this.showMenu = false
if(column.index == null) return
this.locateMenuOrEditInput('contextmenu', 140, $event) // 菜单定位
this.showMenu = true
// 当前目标
this.curTarget = {
rowIdx: row ? row.row_index : null, // 目标行下标,表头无 row_index
colIdx: column.index, // 目标项下标
val: row ? row[column.property] : column.label, // 目标值,表头记录名称
isHead: !row
}
},
// 去更改列名
renameCol($event) {
this.showEditInput = false
if(this.curTarget.colIdx === null) return
this.locateMenuOrEditInput('editInput', 200, $event) // 编辑框定位
this.showEditInput = true
},
// 更改单元格内容/列名
updTbCellOrHeader(val) {
if(!this.curTarget.isHead) // 更改单元格内容
this.testDatas[this.curTarget.rowIdx][this.curColumn.prop] = val
else { // 更改列名
if(!val) return
this.columnList[this.curTarget.colIdx].label = val
}
},
// 新增行
addRow(later) {
this.showMenu = false
const idx = later ? this.curTarget.rowIdx + 1 : this.curTarget.rowIdx
let obj = {}
this.columnList.forEach(p => obj[p.prop] = '')
this.testDatas.splice(idx, 0, obj)
},
// 删除行
delRow() {
this.showMenu = false
this.curTarget.rowIdx !== null && this.testDatas.splice(this.curTarget.rowIdx, 1)
},
// 新增列
addColumn(later) {
this.showMenu = false
const idx = later ? this.curTarget.colIdx + 1 : this.curTarget.colIdx
const colStr = { prop: 'col_' + ++this.countCol, label: '' }
this.columnList.splice(idx, 0, colStr)
this.testDatas.forEach(p => p[colStr.prop] = '')
},
// 删除列
delColumn() {
this.showMenu = false
this.testDatas.forEach(p => { delete p[this.curColumn.prop] })
this.columnList.splice(this.curTarget.colIdx, 1)
},
// 添加表格行下标
tableRowClassName({row, rowIndex}) {
row.row_index = rowIndex
},
// 定位菜单/编辑框
locateMenuOrEditInput(eleId, eleWidth, $event) {
// 表格容器的位置
const { x: tbX, y: tbY } = this.$refs.tbContainerRef.getBoundingClientRect()
// 当前鼠标位置
const { x: pX, y: pY } = $event
const ele = document.getElementById(eleId)
ele.style.top = pY - tbY - 6 + 'px'
ele.style.left = pX - tbX - 6 + 'px'
if(window.innerWidth - eleWidth < pX - tbX) {
ele.style.left = 'unset'
ele.style.right = 0
}
},
},
};
const app = Vue.createApp(App);
app.use(ElementPlus);
app.mount("#app");
</script>
</body>
<style>
html,body {margin:0;padding:0;}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
font-smoothing: antialiased;
osx-font-smoothing: grayscale;
color: #2c3e50;
padding: 20px;
}
label {font-weight:bold;}
.tb-container {position: relative;}
#contextmenu {
position:absolute;
top: 0;
left: 0;
height:auto;
width:120px;
border-radius: 3px;
border: 1px solid #999999;
background-color: #f4f4f4;
padding: 10px;
z-index: 12;
}
#contextmenu button {display:block;margin:0 0 5px;}
.hideContextMenu {position:absolute;top:5px;right:5px;}
#editInput,#headereditInput {
position:absolute;
top: 0;
left: 0;
height:auto;
min-width:200px;
max-width: 400px;
padding: 0;
z-index: 12;
}
#editInput .el-input,#headereditInput .el-input {
outline: 0;
border: 1px solid #c0f2f9;
border-radius: 5px;
box-shadow: 0px 0px 10px 0px #c0f2f9;
}
</style>
</html>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.