效果
列标签操作:支持增(可批量)、删、恢复,支持右键编辑、拖拽排序
表格行操作:支持增、删、恢复
代码
Vue函数等已通过unplugin-auto-import自动导入,Element Plus已通过unplugin-vue-components自动导入
// src/assets/styles/_mixin.scss
@mixin flex($direction: null, $justify: null, $items: null, $gap: null) {
display: flex;
@if $direction {
flex-direction: $direction;
}
@if $justify {
justify-content: $justify;
}
@if $items {
align-items: $items;
}
@if $gap {
gap: $gap;
}
}
以上为封装flexbox样式
<template>
<div>
<div class="hstack" style="margin-bottom: 12px">
<VueDraggable v-model="tableCols" class="tags">
<el-tag
v-for="col in tableCols"
:key="col"
size="large"
disable-transitions
closable
@close="deleteCol(col)"
@contextmenu.prevent="openColEditor(col)"
>
{{ col }}
</el-tag>
</VueDraggable>
<el-input
v-if="inputVisible"
ref="inputRef"
v-model="newCol"
clearable
@keyup.enter="createCol"
@blur="createCol"
style="width: 100px"
/>
<el-button v-else :icon="Plus" plain @click="showInput" />
<el-button
:icon="RefreshLeft"
plain
@click="recoverCol"
:disabled="deletedCols.length === 0"
/>
</div>
<el-table :data="tableData">
<el-table-column v-for="col in tableCols" :key="col" :prop="col" :label="col">
<template #default="{ row }">
<el-input v-model="row[col]" />
</template>
</el-table-column>
<el-table-column v-if="tableCols.length > 0" align="center" width="160">
<template #header>
<el-button type="primary" link @click="createRow">添加行</el-button>
<el-button type="primary" link @click="recoverRow" :disabled="deletedRows.length === 0">
恢复行
</el-button>
</template>
<template #default="{ $index }">
<el-button type="danger" link @click="deleteRow($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-dialog v-model="colEditParams.dialogVisible" :show-close="false" @closed="closeColEditor">
<el-form label-width="auto">
<el-form-item label="原列名">
<el-input v-model="colEditParams.oldName" disabled />
</el-form-item>
<el-form-item label="新列名">
<el-input v-model="colEditParams.newName" @keyup.enter="updateCol" />
</el-form-item>
</el-form>
<template #footer>
<div>
<el-button @click="closeColEditor">取消</el-button>
<el-button type="primary" @click="updateCol" :disabled="colEditParams.newName === ''">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { VueDraggable } from 'vue-draggable-plus'
import { Plus, RefreshLeft } from '@element-plus/icons-vue'
const tableCols = ref(['列1', '列2'])
const tableData = ref([
{ 列1: '1-1', 列2: '1-2' },
{ 列1: '2-1', 列2: '2-2' }
])
const deletedCols = ref([])
const deleteCol = (col) => {
tableCols.value.splice(tableCols.value.indexOf(col), 1)
deletedCols.value.push(col)
}
const inputVisible = ref(false)
const inputRef = ref(null)
const newCol = ref('')
const showInput = () => {
inputVisible.value = true
nextTick(() => {
inputRef.value.input.focus()
})
}
const createCol = () => {
if (!newCol.value) return
const newCols = newCol.value.split(';')
tableCols.value.push(...newCols)
inputVisible.value = false
newCol.value = ''
}
const recoverCol = () => {
if (deletedCols.value.length === 0) return
const col = deletedCols.value.pop()
tableCols.value.push(col)
}
const colEditParams = reactive({
dialogVisible: false,
oldName: '',
newName: ''
})
const openColEditor = (oldName) => {
Object.assign(colEditParams, { dialogVisible: true, oldName })
}
const closeColEditor = () => {
Object.assign(colEditParams, {
dialogVisible: false,
oldName: '',
newName: ''
})
}
const updateCol = () => {
const { oldName, newName } = toRaw(colEditParams)
if (newName === '') return
const index = tableCols.value.findIndex((col) => col === oldName)
deleteCol(oldName)
tableCols.value.splice(index, 0, newName)
tableData.value.forEach((item) => {
item[newName] = item[oldName]
})
closeColEditor()
}
const createRow = () => {
const newRow = {}
tableCols.value.forEach((col) => {
newRow[col] = null
})
tableData.value.push(newRow)
}
const deletedRows = ref([])
const deleteRow = (index) => {
const row = tableData.value.splice(index, 1)[0]
deletedRows.value.push(row)
}
const recoverRow = () => {
if (deletedRows.value.length === 0) return
const row = deletedRows.value.pop()
tableData.value.push(row)
}
</script>
<style scoped lang="scss">
@use '@/assets/styles/mixin';
.hstack {
@include mixin.flex($items: center, $gap: 8px);
.el-button + .el-button {
margin-left: 0;
}
}
.tags {
flex-wrap: wrap;
@include mixin.flex($items: center, $gap: 8px);
}
.el-tag {
cursor: grab;
&:active {
cursor: grabbing;
}
}
</style>