el-table——可编辑、拖拽排序与校验的formTableDrag
背景:
1.利用form进行校验输入;
2.利用sortable操作Dom替换表格数据顺序;
3.利用lodash实现数据深拷贝与参数替换等
一:最外层的数组校验
<template>
<el-form :rules="rules" :model="form" ref="rulesForm">
<el-form-item prop="table">
<formTableDrag
:table-data="form.table"
:drop-col="column"
tab-show
dialog-title="编辑"
@save-drag-table="saveDragTable"
/>
</el-form-item>
</el-form>
</template>
<script>
import formTableDrag from './formTableDrag'
export default {
components: {
dragTableDialog
},
data () {
return {
rules: {
table: { type: 'array', required: true, message: '输出列表不可为空', trigger: 'blur' }
},
form: {
table: []
},
column: [
{
default: '',
label: '字段',
prop: 'field_name'
},
{
default: 'string',
label: '类型',
prop: 'field_type'
},
{
default: '',
label: '描述',
prop: 'field_desc'
}
]
}
},
methods: {
saveDragTable (val) {this.form.table= val
if(val.length===0){
this.$refs['rulesForm'].validateField('schema') //校验某个字段
}else{
// this.$refs['copyForm'].resetFields()// 清空表单内容
this.$refs['rulesForm'].clearValidate()// 清空报错
}
},
}
}
</script>
二、表格与文本之间 的转换:
<!--可拖拽的表格:表格内容+显示切换+文本输入 --> <template> <div> <el-button type="primary" @click="showDialog">{{ dialogTitle }}</el-button> <CommonTable style="marginTop:10px" :table-data="tableDataBeigin" :table-column="dropCol" /> <el-dialog :visible.sync="dialogVisible" :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;"> <dragTableForm :table-data="tableDataDialog" :table-column="dropCol" :save-disabled="saveDisabled" @save-call-back="saveCallBack" @save-data-back="saveDataBack" /> </div> <el-tabs v-else @tab-click="handleClickTab" style="margin-top:-20px;" v-model="activeName" type="card" > <el-tab-pane label="表格编辑模式" name="table"> <dragTableForm :size="size" :table-data="tableDataDialog" :drop-col="dropCol" :save-disabled="saveDisabled" @save-call-back="saveCallBack" @save-data-back="saveDataBack" /> </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、可将导出的csv文件内容,直接复制过来使用,若有数据类型且不符合规范,转换后默认为string;</li> <li>2、手动编辑时,注意分隔符为英文逗号(第3个逗号后面的内容合并到最后一列),新的一行用Enter键换行。</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 _ from 'lodash' import CommonTable from './commonTable' import dragTableForm from './dragTableForm' export default { components: { CommonTable, dragTableForm }, props: { 'size': { type: String, default: 'mini' }, '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: [] } }, created () { 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 = _.cloneDeep(tableData) }, watch: { tableData () { 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 = _.cloneDeep(tableData) } }, methods: { showDialog () { if (this.activeName === 'txt') { let str = '' this.tableDataDialog.forEach(item => { delete item.id str += Object.values(item) + '\n' }) this.strSplit = str } this.dialogVisible = true }, beforeClose () { 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.tableDataDialog = _.cloneDeep(tableData) 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 }, handleClickTab (tab, event) { if (tab.name === 'txt') { let str = '' this.tableDataDialog.forEach(item => { delete item.id str += Object.values(item) + '\n' }) this.strSplit = str } else { const array = this.strSplit.split('\n') if (!array[array.length - 1]) { array.pop() } const tableDataDialog = [] array.forEach((item, index) => { const allIndex = this.findStrIndex(item, ',', 1) let array2 = [] if (item.split(',').length > 3) { array2 = item.substring(0, allIndex).split(',') array2.push(item.substring(allIndex + 1)) } else { if (item.split(',').length === 1) { array2 = [item, this.dropCol[1].prop === 'field_type' ? 'string' : '', ''] } else { array2 = item.split(',') } } const obj = {} array2.forEach((e, i) => { obj.id = index if (this.dropCol[i].prop === 'field_type') { const options = ['tinyint', 'smallint', 'int', 'bigint', 'boolean', 'float', 'double', 'string'] obj[this.dropCol[i].prop] = options.indexOf(array2[i]) !== -1 ? array2[i] : 'string' } else if (this.dropCol[i].prop === 'field_key') { const keyOptions = ['qq', 'area', 'roleid', 'os', 'commid', 'openid', 'null'] obj[this.dropCol[i].prop] = keyOptions.indexOf(array2[i]) !== -1 ? array2[i] : 'null' } else { obj[this.dropCol[i].prop] = array2[i] ? array2[i] : '' } }) tableDataDialog.push(obj) }) this.tableDataDialog = tableDataDialog } }, saveCallBack (disabled) { this.saveDisabled = disabled }, saveDataBack (data) { this.tableDataDialog = data }, submitDialog () { if (this.activeName === 'txt') { const array = this.strSplit.split('\n') if (!array[array.length - 1]) { array.pop() } const tableDataDialog = [] array.forEach((item, index) => { const allIndex = this.findStrIndex(item, ',', 1) let array2 = [] if (item.split(',').length > 3) { array2 = item.substring(0, allIndex).split(',') array2.push(item.substring(allIndex + 1)) } else { if (item.split(',').length === 1) { array2 = [item, this.dropCol[1].prop === 'field_type' ? 'string' : '', ''] } else { array2 = item.split(',') } } const obj = {} array2.forEach((e, i) => { obj.id = index if (this.dropCol[i].prop === 'field_type') { const options = ['tinyint', 'smallint', 'int', 'bigint', 'boolean', 'float', 'double', 'string'] obj[this.dropCol[i].prop] = options.indexOf(array2[i]) !== -1 ? array2[i] : 'string' } else if (this.dropCol[i].prop === 'field_key') { const keyOptions = ['qq', 'area', 'roleid', 'os', 'commid', 'openid', 'null'] obj[this.dropCol[i].prop] = keyOptions.indexOf(array2[i]) !== -1 ? array2[i] : 'null' } else { obj[this.dropCol[i].prop] = array2[i] ? array2[i] : '' } }) tableDataDialog.push(obj) }) this.tableDataDialog = tableDataDialog } 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]) 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 * */ <template> <div> <el-table id="kp_but_2982" 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" /> <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" /> </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' } ] } }, 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: '不关联' } } }, watch: { initSelection: { immediate: true, handler (val) { if (val) { this.$nextTick(() => { this.$refs.commonTable.clearSelection() }) } } } }, methods: { handleSelectionChange (val) { this.$emit('handleSelectionChange', val) } } } </script>
三、表单嵌套的表格:
<template>
<div>
<el-button
:size="size"
type="primary"
@click="addRow"
style="margin-bottom: 10px"
:disabled="disabledAdd"
>新增一行</el-button>
<el-form :model="form" :rules="rules" ref="form">
<el-table
border
:size="size"
id="dragTable_sql"
:row-key="getRowKeys"
:data="form.tableData"
style="width: 100%;"
>
<!-- 拖拽图标 -->
<el-table-column width="40" align="center">
<template>
<i class="el-icon-rank" style="font-size:large;cursor:grab" />
</template>
</el-table-column>
<!-- 输入选择 -->
<el-table-column
v-for="(item, index) in tableColumn"
:key="index"
:prop="item.prop"
:label="item.label"
align="center"
>
<template slot-scope="scope">
<el-form-item
v-if="index===0"
:size="size"
:prop="`tableData.${scope.$index}.${item.prop}`"
:rules="rules[item.prop]"
>
<el-input
v-focus
clearable
v-model="scope.row[item.prop]"
:placeholder="`请输入${item.label}`"
@change="inputChange"
@clear="inputChange"
/>
</el-form-item>
<el-form-item v-else-if="item.prop === 'field_type'" :size="size">
<el-select
@change="saveChange"
:size="size"
v-model="scope.row[item.prop]"
:placeholder="'请选择'+item.label"
>
<el-option
v-for="item in options"
:key="item"
:label="item"
:value="item"
style="text-align: center;"
/>
</el-select>
</el-form-item>
<el-form-item v-else-if="item.prop === 'field_key'" :size="size">
<el-select
clearable
v-model="scope.row[item.prop]"
:placeholder="'请选择'+item.label"
@change="saveChange"
>
<el-option
style="text-align: center;"
v-for="(item,index) in keyOptions"
:key="index"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-else :size="size">
<el-input
clearable
v-model="scope.row[item.prop]"
:placeholder="`请输入${item.label}`"
@change="saveChange"
@clear="saveChange"
/>
</el-form-item>
</template>
</el-table-column>
<!--操作 -->
<el-table-column width="80" align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-button :size="size" type="danger" @click="deleteRow(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<el-link type="danger" v-show="isRepeatName">{{tableColumn[0].label}}命名已存在!</el-link>
</div>
</template>
<script>
import _ from 'lodash'
import Sortable from 'sortablejs'
export default {
directives: {
focus: {
inserted: function (el) {
el.querySelector('input').focus()
}
}
},
props: {
'size': {
type: String,
default: 'mini'
},
'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'
}
]
}
}
},
watch: {
'form.tableData': {
immediate: false,
handler (val) {
this.$emit('save-data-back', val)
if (val.length > 0) {
const fieldName = val.map(item => item[this.tableColumn[0].prop])
this.isRepeatName = this.isRepeat(fieldName)
val.forEach(item => {
if (!item[this.tableColumn[0].prop]) {// 只有有空就禁止提交
this.disabledAdd = true
this.$emit('save-call-back', true)
} else {
this.disabledAdd = false
this.$emit('save-call-back', false)
}
})
if (this.isRepeatName) { // 有重复值
this.disabledAdd = true
this.$emit('save-call-back', true)
}
}
}
},
'tableData': {
immediate: true,
handler (val) {
this.$nextTick(function () {
this.rowDropDialog()
})
if(val.length > 0){
this.form.tableData = val
}
}
}
},
computed: {
rules () {
const rules = {}
this.tableColumn.forEach((item, index) => {
rules[item.prop] = [
{ required: true, message: '请输入' + item.label, trigger: 'blur' },
{ pattern: /^[a-zA-Z][a-zA-Z0-9_]*$/, message: '须字母开头,不含特殊符号', trigger: 'blur' },
]
})
return rules
}
},
data () {
return {
getRowKeys (row) {
return row.id
},
form: {
tableData: []
},
fieldName: [],
disabledAdd: false,
isRepeatName: false,
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: '不关联' }
]
}
},
methods: {
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.form.tableData.splice(oldIndex, 1)[0]
_this.form.tableData.splice(newIndex, 0, currRow)
}
})
},
inputChange (val) {
if (val) { //必要字段更新
this.disabledAdd = this.fieldName.indexOf(val) !== -1
this.isRepeatName = this.fieldName.indexOf(val) !== -1
this.$emit('save-call-back', this.disabledAdd)
this.$emit('save-data-back', this.form.tableData)
} else {
this.$refs['form'].validate(valid => {
if (valid) {
//清除不计重复
this.$emit('save-data-back', this.form.tableData)
} else {
this.disabledAdd = true
this.$emit('save-call-back', true)
return valid
}
});
}
},
saveChange () {
this.$emit('save-data-back', this.form.tableData)
},
addRow () {
this.$refs['form'].validate((valid) => {
if (valid) {
// 1.读取已有命名
if (this.form.tableData.length > 0) {
this.fieldName = this.form.tableData.map(item => item[this.tableColumn[0].prop])
}
// 2.添加一行:id++1
const tableRowKey = this.tableColumn.map(item => item.prop)
const tableRowVal = this.tableColumn.map(item => item.default)
const tableRow = _.zipObject(tableRowKey, tableRowVal) // 映射
tableRow.id = _.uniqueId() // 拖拽
this.form.tableData.push(tableRow)
this.disabledAdd = true
this.$emit('save-call-back', true)
} else {
return false;
}
});
},
deleteRow (index, row) {
//1.删除
this.form.tableData.splice(index, 1)
// 2.去重
this.fieldName = this.fieldName.filter(item => item !== row[this.tableColumn[0].prop])
},
isRepeat (arr) {
return _.uniq(arr).length !== arr.length;
}
}
}
</script>

浙公网安备 33010602011771号