el-table——可编辑拖拽转换csv格式的表格
<!--可拖拽的表格:表格内容+行参数+按钮名称(对话框标题)-->
<template>
<div>
<el-button
size="mini"
type="primary"
@click="showDialog">{{ dialogTitle }}</el-button>
<CommonTable
style="marginTop:10px"
:table-data="tableDataBeigin"
:table-column="dropCol"
/>
<el-dialog
:visible.sync="dialogVisible"
@open='openDialog'
:close-on-click-modal="false"
append-to-body
show-close
:before-close="beforeClose"
:title="dialogTitle"
width='40%'
>
<div
v-if="!tabShow"
style="margin-top:-20px;">
<DragTableView
:table-data="tableDataDialog"
:drop-col="dropCol"
:save-disabled='saveDisabled'
@save-call-back="saveCallBack"
/>
</div>
<el-tabs
v-else
@tab-click="handleClickTab"
style="margin-top:-20px;"
v-model="activeName"
type="card">
<el-tab-pane
label="表格编辑模式"
name="table"
>
<DragTableView
:table-data="tableDataDialog"
:drop-col="dropCol"
:save-disabled='saveDisabled'
@save-call-back="saveCallBack"
/>
</el-tab-pane>
<el-tab-pane
label="文本编辑模式"
name="txt"
>
<el-input
v-model="strSplit"
type="textarea"
:rows="6"
placeholder="例:a,int,描述a,类型int。"
spellcheck='false'
/>
<h4 style="margin:5px 0">注意:</h4>
<ul style="text-align:left">
<li>1.所有字段输出后,将自动清除空格;若有数据类型,转换后均为小写(不符合规范的默认设置为string)。</li>
<li>2.手动编辑时,注意第3个逗号(不区分中英文)后面的内容将合并到最后一列,新的一行用Enter键换行。</li>
<li>3.可将导出的csv文件内容,直接复制到文本编辑框进行输出。</li>
</ul>
</el-tab-pane>
</el-tabs>
<!--保存操作 -->
<span
slot="footer"
class="dialog-footer"
>
<el-button
size="mini"
type="primary"
@click="submitDialog"
:disabled="saveDisabled"
>保存</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import CommonTable from './CommonTable' // 展示内容
import DragTableView from './dragTableView' // 展示内容
import Sortable from 'sortablejs' // 拖拽排序
export default {
components: {
CommonTable,
DragTableView
},
props: {
'tableData': {
type: Array,
default () {
return []
}
},
'dropCol': {
type: Array,
default () {
return [
{
default: '',
label: '字段',
prop: 'field_name'
},
{
default: 'string',
label: '类型',
prop: 'field_type'
},
{
default: '',
label: '描述',
prop: 'field_desc'
}
]
}
},
'dialogTitle': {
type: String,
default: ''
},
'tabShow': {
type: Boolean,
default: false
}
}, // 数据与标题
data () {
return {
strSplit: '',
activeName: 'table',
dialogVisible: false, // 对话框
saveDisabled: false, // 禁用保存
tableDataBeigin: [], // 原数据
tableDataDialog: [] // 编辑数据
}
},
watch: {
tableData: {
immediate: true,
handler (arr) {
this.getDialogTableData()
}
}
},
methods: {
showDialog () {
if (this.activeName === 'txt') {
this.strSplitFormat()
}
this.dialogVisible = true
},
deepCopy (source) {
let result;
(source instanceof Array) ? (result = []) : (result = {})
for (let key in source) {
result[key] = (typeof source[key] === 'object') ? this.deepCopy(source[key]) : source[key]
}
return result
},
openDialog () { // 打开对话框
if (this.activeName === 'table') {
this.$nextTick(function () {
this.rowDropDialog()
})
}
},
beforeClose () {
this.getDialogTableData()
this.dialogVisible = false
this.saveDisabled = false
},
findStrIndex (str, cha, num) { // 字符串截取
var x = str.indexOf(cha)
for (var i = 0; i < num; i++) {
x = str.indexOf(cha, x + 1)
}
return x
},
getDialogTableData () {
const tableData = []
this.tableData.forEach((item, index) => {
const obj = {}
obj.id = index
this.dropCol.forEach(e => {
obj[e.prop] = item[e.prop] || ''
})
tableData.push(obj)
})
this.tableDataBeigin = tableData
this.tableDataDialog = this.deepCopy(tableData)
},
strSplitFormat () {
let str = ''
this.tableDataDialog.forEach(item => {
delete item.id // 删除拖拽使用的id
const itemArray = Object.values(item)
const strArray = []
itemArray.forEach(val => { // 去除所有空格
strArray.push(val.replace(/\s/ig, ''))
})
str += strArray + '\n'
})
this.strSplit = str
},
tableDataFormat () {
const array = this.strSplit.split('\n')
if (!array[array.length - 1]) {
array.pop()
}
const tableDataDialog = []
array.forEach((item, index) => {
// 第一步:格式化字符串的内容
const itemNew = item.replace(/\s/ig, '') // 去除空格
const itemSpace = itemNew.replace(/,/g, ',') // 将字符串中中文逗号转化为英文逗号
const itemSplit = itemSpace.split(',') // 将字符串分割成数组
const indexSecond = this.findStrIndex(itemSpace, ',', 1)
let array2 = []
if (itemSplit.length > 3) { // 只转换第二个逗号前的内容,第三个逗号后面的内容进行合并
array2 = itemSpace.substring(0, indexSecond).split(',')
array2.push(item.substring(indexSecond + 1))
} else {
if (itemSplit.length === 1) { // 只有一个字段
array2 = [itemNew, this.dropCol[1].prop === 'field_type' ? 'string' : '', '']
} else { // 其他情况
array2 = itemSplit
}
}
// 第二步:将字符串转成成需要的表格数据
const obj = {}
array2.forEach((eNew, i) => {
obj.id = index
const itemDrop = this.dropCol[i].prop
const itemLower = eNew.toLowerCase() // 转换成小写
if (itemDrop === 'field_type') {
const options = ['tinyint', 'smallint', 'int', 'bigint', 'boolean', 'float', 'double', 'string']
obj[itemDrop] = options.indexOf(itemLower) !== -1 ? itemLower : 'string'
} else if (itemDrop === 'field_key') {
const keyOptions = ['qq', 'area', 'roleid', 'os', 'commid', 'openid', 'null']
obj[itemDrop] = keyOptions.indexOf(itemLower) !== -1 ? itemLower : 'null'
} else { // 其他内容不转换大小写
obj[itemDrop] = eNew || ''
}
})
tableDataDialog.push(obj)
})
this.tableDataDialog = tableDataDialog
},
handleClickTab (tab, event) {
if (tab.name === 'txt') {
this.strSplitFormat()
} else {
this.tableDataFormat()
}
},
saveCallBack (val) {
this.saveDisabled = val
},
rowDropDialog () {
const tbody = document.querySelector('#dragTable_sql tbody')
const _this = this
Sortable.create(tbody, {
handle: '.el-icon-rank',
animation: 150,
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.tableDataDialog.splice(oldIndex, 1)[0]
_this.tableDataDialog.splice(newIndex, 0, currRow)
}
})
},
submitDialog () { // 提交表单
if (this.activeName === 'txt') { // 文本编辑内容进行转换
this.tableDataFormat()
}
const tableData = []
this.tableDataDialog.forEach((item, index) => {
const obj = {}
this.dropCol.forEach(e => {
obj[e.prop] = item[e.prop] || ''
})
tableData.push(obj)
})
this.tableDataBeigin = tableData
const arr = tableData.map(item => item[this.dropCol[0].prop])
console.log(tableData, '提交tableData')
if ((new Set(arr)).size !== arr.length) { // 判断字段是否重名
this.$message.warning(this.dropCol[0].label + '不可重名')
} else {
this.$emit('save-drag-table', tableData)
this.dialogVisible = false
}
}
}
}
</script>
* 通用的table展示 * @param {Array} tableData * @param {Array} tableColumn * @return {Number/String} height(参考element) * @return {String} size(参考element) * @return {Boolean} stripe 默认显示 * @return {Boolean} sortable 默认显示 * @return {Boolean} loading * @return {Function} filterChange * @return {Function / String} tableRowClassName 底色 * @return {String} slot 插入的位置:header、footer、footerDesc * */ <template> <div> <el-table :max-height="height" ref="commonTable" :data="tableData" :size="size" :stripe="stripe" border highlight-current-row v-loading="loading" :row-class-name="tableRowClassName" @filter-change="filterChange" @selection-change="handleSelectionChange" :row-key="rowKey" > <!--自定义插入--> <slot name="header"/> <slot name="filters"/> <el-table-column v-for="(item, index) in tableColumn" :key="`key_${index}`" :prop="item.prop" :label="item.label" show-overflow-tooltip :sortable='sortable' align="center" > <template slot-scope="scope"> <div v-if="tableColumn[index].prop === 'field_key'"> <span>{{ keyOptionsObj[scope.row.field_key] }}</span> </div> <div v-else> <span>{{ scope.row[tableColumn[index].prop] }}</span> </div> </template> </el-table-column> <!--自定义插入--> <slot name="footer"/> <slot name="footerDesc"/> </el-table> </div> </template> <script> export default { props: { tableData: { type: Array, default () { return [] } }, tableColumn: { type: Array, default () { return [ { default: '', label: '字段名称', prop: 'field_name' }, { default: 'string', label: '字段类型', prop: 'field_type' }, { default: '', label: '字段描述', prop: 'field_desc' } ] } }, height: { type: [String, Number], default: '500' }, size: { type: String, default: 'mini' }, sortable: { type: Boolean, default: true }, stripe: { type: Boolean, default: true }, loading: { type: Boolean, default: false }, filterChange: { type: Function, default () { return '' } }, tableRowClassName: { type: Function, default () { return '' } }, rowKey: { type: String, default: '' }, initSelection: { type: Boolean, default: false } }, data () { return { keyOptionsObj: { qq: 'QQ号', area: '大区ID', roleid: '角色ID', os: '手机操作系统', commid: '微信Commid', openid: 'Open ID', null: '不关联' } } }, // computed: { // stripeShow () { // return !this.tableRowClassName() // } // }, watch: { initSelection: { immediate: true, handler (val) { if (val) { this.$nextTick(() => { this.$refs.commonTable.clearSelection() }) } } } }, methods: { handleSelectionChange (val) { this.$emit('handleSelectionChange', val) } } } </script>
<template>
<div>
<div style="margin-bottom: 10px">
<el-button
size="mini"
type="primary"
@click="addRow"
>新增一行</el-button>
</div>
<el-table
:data="tableData"
border
size="mini"
style="width: 100%"
max-height="350"
id="dragTable_sql"
current-row-key="getRowKeys"
:row-key="getRowKeys"
>
<!-- 拖拽图标 -->
<el-table-column
width="40"
align="center"
>
<template >
<i
class="el-icon-rank"
style="cursor:grab"/>
</template>
</el-table-column>
<!-- 数据 -->
<el-table-column
v-for="(item, index) in dropCol"
:key="`dropCol_${index}`"
:prop="dropCol[index].prop"
:label="item.label"
show-overflow-tooltip
align="center"
>
<template slot-scope="scope">
<div v-if="dropCol[index].prop === 'field_type'">
<el-select
v-model="scope.row[dropCol[index].prop]"
:placeholder="'请选择'+dropCol[index].label"
style="width:100%"
>
<el-option
v-for="item in options"
:key="item"
:label="item"
:value="item"
/>
</el-select>
</div>
<div v-else-if="dropCol[index].prop === 'field_key'">
<el-select
v-model="scope.row[dropCol[index].prop]"
:placeholder="'请选择'+dropCol[index].label"
style="width:100%"
>
<el-option
v-for="(item,index) in keyOptions"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div v-else-if="dropCol[index].prop === 'field_name' || dropCol[index].prop === 'name'">
<el-input
v-focus
clearable
v-model="scope.row[dropCol[index].prop]"
:placeholder="'请输入'+dropCol[index].label"
@blur="checkRepeat(dropCol[index].label, dropCol[index].prop,scope.row)"
@clear="checkRepeat(dropCol[index].label, dropCol[index].prop,scope.row)"
/>
</div>
<!-- <div v-else-if="dropCol[index].prop === 'name'">
<el-input
clearable
v-model="scope.row[dropCol[index].prop]"
:placeholder="'请输入'+dropCol[index].label"
@blur="checkRepeat(dropCol[index].label, dropCol[index].prop,scope.row)"
@clear="checkRepeat(dropCol[index].label, dropCol[index].prop,scope.row)"
/>
</div> -->
<div v-else>
<el-input
clearable
v-model="scope.row[dropCol[index].prop]"
:placeholder="'请输入'+dropCol[index].label"
/>
</div>
</template>
</el-table-column>
<!--操作 -->
<el-table-column
width="80"
align="center"
label="操作"
fixed="right"
>
<template slot-scope="scope">
<el-button-group>
<el-button
size="mini"
type="danger"
@click="deleteRow(scope.$index, scope.row)"
>删除</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
import _ from 'lodash' // 数据转换
export default {
props: {
'tableData': {
type: Array,
default () {
return []
}
},
'dropCol': {// 4.
type: Array,
default () {
return [
{
default: '',
label: '字段',
prop: 'field_name'
},
{
default: 'string',
label: '类型',
prop: 'field_type'
},
{
default: '',
label: '描述',
prop: 'field_desc'
}
]
}
},
'saveDisabled': {// 5.
type: Boolean,
default: false
}
}, // 数据与标题
data () {
return {
getRowKeys (row) { // 3.
return row.id
},
options: [
'tinyint',
'smallint',
'int',
'bigint',
'boolean',
'float',
'double',
'string'
],
keyOptions: [
{ value: 'qq', label: 'QQ号' },
{ value: 'area', label: '大区ID' },
{ value: 'roleid', label: '角色ID' },
{ value: 'os', label: '手机操作系统' },
{ value: 'commid', label: '微信Commid' },
{ value: 'openid', label: 'Open ID' },
{ value: 'null', label: '不关联' }
]
// saveDisabled: false// 禁用保存
}
},
directives: {
// 注册一个局部的自定义指令 v-focus
focus: {
// 指令的定义
inserted: function (el) {
// 聚焦元素
el.querySelector('input').focus()
}
}
},
watch: {
tableData () {
if (this.tableData.length > 0 && this.tableData[this.tableData.length - 1][this.dropCol[0].prop]) {
this.$emit('save-call-back', false)
} else if (this.tableData.length === 0) {
this.$emit('save-call-back', false)
} else {
this.$emit('save-call-back', true)
}
}
},
methods: {
checkRepeat (label, prop, row) {
const checkArry = this.tableData.map(item => item[prop])
const regString = /^[a-zA-Z][a-zA-Z0-9_]*$/
if (row[prop] !== '') {
if (new Set(checkArry).size !== checkArry.length) {
this.$message.warning(label + '不可重名')
// this.saveDisabled = true
this.$emit('save-call-back', true)
} else if (!regString.test(row[prop])) {
this.$message.warning(label + '必须是字母开头,只能输入字母,数字,下划线')
// this.saveDisabled = true
this.$emit('save-call-back', true)
} else {
// this.saveDisabled = false
this.$emit('save-call-back', false)
}
} else {
this.$message.warning(label + '不可为空')
// this.saveDisabled = true
this.$emit('save-call-back', true)
}
},
addRow () { // 1.
const tableRowKey = this.dropCol.map(item => item.prop)
const tableRowVal = this.dropCol.map(item => item.default)
const tableRow = _.zipObject(tableRowKey, tableRowVal)
tableRow.id = this.tableData.length > 0 ? this.tableData.length : '0'
if (Array.isArray(this.tableData) && this.tableData.length > 0) {
const lastRow = this.tableData[this.tableData.length - 1]
if (lastRow && this.dropCol && lastRow[this.dropCol[0].prop] !== '') {
this.tableData.push(tableRow)
// this.saveDisabled = true
this.$emit('save-call-back', true)
} else {
this.$message.warning('新增行的' + this.dropCol[0].label + '不可为空')
}
} else {
this.tableData.push(tableRow)
// this.saveDisabled = true
this.$emit('save-call-back', true)
}
},
deleteRow (index, row) { // 5.
console.log(this.tableData.length, 1111)
this.tableData.splice(index, 1)
// if (this.tableData.length > 0 && this.tableData[this.tableData.length - 1][this.dropCol[0].prop]) {
// // this.saveDisabled = false
// this.$emit('save-call-back', false)
// } else if (this.tableData.length === 0) {
// console.log(this.tableData.length, 222)
// this.$emit('save-call-back', false)
// } else {
// console.log(this.tableData.length, 333)
// this.$emit('save-call-back', true)
// }
}
}
}
</script>
<!--可拖拽的表格:表格内容+行参数+按钮名称(对话框标题)-->
<template>
<div>
<el-button
size="mini"
type="primary"
@click="showDialog">{{ dialogTitle }}</el-button>
<CommonTable
style="marginTop:10px"
:table-data="tableDataBeigin"
:table-column="dropCol"
/>
<el-dialog
:visible.sync="dialogVisible"
@open='openDialog'
:close-on-click-modal="false"
append-to-body
show-close
:before-close="beforeClose"
:title="dialogTitle"
width='40%'
>
<div
v-if="!tabShow"
style="margin-top:-20px;">
<DragTableView
:table-data="tableDataDialog"
:drop-col="dropCol"
:save-disabled='saveDisabled'
@save-call-back="saveCallBack"
/>
</div>
<el-tabs
v-else
@tab-click="handleClickTab"
style="margin-top:-20px;"
v-model="activeName"
type="card">
<el-tab-pane
label="表格编辑模式"
name="table"
>
<DragTableView
:table-data="tableDataDialog"
:drop-col="dropCol"
:save-disabled='saveDisabled'
@save-call-back="saveCallBack"
/>
</el-tab-pane>
<el-tab-pane
label="文本编辑模式"
name="txt"
>
<el-input
v-model="strSplit"
type="textarea"
:rows="6"
placeholder="例:a,int,描述a,类型int。"
spellcheck='false'
/>
<h4 style="margin:5px 0">注意:</h4>
<ul style="text-align:left">
<li>1.所有字段输出后,将自动清除空格;若有数据类型,转换后均为小写(不符合规范的默认设置为string)。</li>
<li>2.手动编辑时,注意第3个逗号(不区分中英文)后面的内容将合并到最后一列,新的一行用Enter键换行。</li>
<li>3.可将导出的csv文件内容,直接复制到文本编辑框进行输出。</li>
</ul>
</el-tab-pane>
</el-tabs>
<!--保存操作 -->
<span
slot="footer"
class="dialog-footer"
>
<el-button
size="mini"
type="primary"
@click="submitDialog"
:disabled="saveDisabled"
>保存</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import CommonTable from './CommonTable' // 展示内容
import DragTableView from './dragTableView' // 展示内容
import Sortable from 'sortablejs' // 拖拽排序
export default {
components: {
CommonTable,
DragTableView
},
props: {
'tableData': {
type: Array,
default () {
return []
}
},
'dropCol': {
type: Array,
default () {
return [
{
default: '',
label: '字段',
prop: 'field_name'
},
{
default: 'string',
label: '类型',
prop: 'field_type'
},
{
default: '',
label: '描述',
prop: 'field_desc'
}
]
}
},
'dialogTitle': {
type: String,
default: ''
},
'tabShow': {
type: Boolean,
default: false
}
}, // 数据与标题
data () {
return {
strSplit: '',
activeName: 'table',
dialogVisible: false, // 对话框
saveDisabled: false, // 禁用保存
tableDataBeigin: [], // 原数据
tableDataDialog: [] // 编辑数据
}
},
watch: {
tableData: {
immediate: true,
handler (arr) {
this.getDialogTableData()
}
}
},
methods: {
showDialog () {
if (this.activeName === 'txt') {
this.strSplitFormat()
}
this.dialogVisible = true
},
deepCopy (source) {
let result;
(source instanceof Array) ? (result = []) : (result = {})
for (let key in source) {
result[key] = (typeof source[key] === 'object') ? this.deepCopy(source[key]) : source[key]
}
return result
},
openDialog () { // 打开对话框
if (this.activeName === 'table') {
this.$nextTick(function () {
this.rowDropDialog()
})
}
},
beforeClose () {
this.getDialogTableData()
this.dialogVisible = false
this.saveDisabled = false
},
findStrIndex (str, cha, num) { // 字符串截取
var x = str.indexOf(cha)
for (var i = 0; i < num; i++) {
x = str.indexOf(cha, x + 1)
}
return x
},
getDialogTableData () {
const tableData = []
this.tableData.forEach((item, index) => {
const obj = {}
obj.id = index
this.dropCol.forEach(e => {
obj[e.prop] = item[e.prop] || ''
})
tableData.push(obj)
})
this.tableDataBeigin = tableData
this.tableDataDialog = this.deepCopy(tableData)
},
strSplitFormat () {
let str = ''
this.tableDataDialog.forEach(item => {
delete item.id // 删除拖拽使用的id
const itemArray = Object.values(item)
const strArray = []
itemArray.forEach(val => { // 去除所有空格
strArray.push(val.replace(/\s/ig, ''))
})
str += strArray + '\n'
})
this.strSplit = str
},
tableDataFormat () {
const array = this.strSplit.split('\n')
if (!array[array.length - 1]) {
array.pop()
}
const tableDataDialog = []
array.forEach((item, index) => {
// 第一步:格式化字符串的内容
const itemNew = item.replace(/\s/ig, '') // 去除空格
const itemSpace = itemNew.replace(/,/g, ',') // 将字符串中中文逗号转化为英文逗号
const itemSplit = itemSpace.split(',') // 将字符串分割成数组
const indexSecond = this.findStrIndex(itemSpace, ',', 1)
let array2 = []
if (itemSplit.length > 3) { // 只转换第二个逗号前的内容,第三个逗号后面的内容进行合并
array2 = itemSpace.substring(0, indexSecond).split(',')
array2.push(item.substring(indexSecond + 1))
} else {
if (itemSplit.length === 1) { // 只有一个字段
array2 = [itemNew, this.dropCol[1].prop === 'field_type' ? 'string' : '', '']
} else { // 其他情况
array2 = itemSplit
}
}
// 第二步:将字符串转成成需要的表格数据
const obj = {}
array2.forEach((eNew, i) => {
obj.id = index
const itemDrop = this.dropCol[i].prop
const itemLower = eNew.toLowerCase() // 转换成小写
if (itemDrop === 'field_type') {
const options = ['tinyint', 'smallint', 'int', 'bigint', 'boolean', 'float', 'double', 'string']
obj[itemDrop] = options.indexOf(itemLower) !== -1 ? itemLower : 'string'
} else if (itemDrop === 'field_key') {
const keyOptions = ['qq', 'area', 'roleid', 'os', 'commid', 'openid', 'null']
obj[itemDrop] = keyOptions.indexOf(itemLower) !== -1 ? itemLower : 'null'
} else { // 其他内容不转换大小写
obj[itemDrop] = eNew || ''
}
})
tableDataDialog.push(obj)
})
this.tableDataDialog = tableDataDialog
},
handleClickTab (tab, event) {
if (tab.name === 'txt') {
this.strSplitFormat()
} else {
this.tableDataFormat()
}
},
saveCallBack (val) {
this.saveDisabled = val
},
rowDropDialog () {
const tbody = document.querySelector('#dragTable_sql tbody')
const _this = this
Sortable.create(tbody, {
handle: '.el-icon-rank',
animation: 150,
onEnd ({ newIndex, oldIndex }) {
const currRow = _this.tableDataDialog.splice(oldIndex, 1)[0]
_this.tableDataDialog.splice(newIndex, 0, currRow)
}
})
},
submitDialog () { // 提交表单
if (this.activeName === 'txt') { // 文本编辑内容进行转换
this.tableDataFormat()
}
const tableData = []
this.tableDataDialog.forEach((item, index) => {
const obj = {}
this.dropCol.forEach(e => {
obj[e.prop] = item[e.prop] || ''
})
tableData.push(obj)
})
this.tableDataBeigin = tableData
const arr = tableData.map(item => item[this.dropCol[0].prop])
console.log(tableData, '提交tableData')
if ((new Set(arr)).size !== arr.length) { // 判断字段是否重名
this.$message.warning(this.dropCol[0].label + '不可重名')
} else {
this.$emit('save-drag-table', tableData)
this.dialogVisible = false
}
}
}
}
</script>

浙公网安备 33010602011771号