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>