<!--可拖拽的表格:表格内容+行参数+按钮名称(对话框标题)--> <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
>