工作中需要一个表格可以编辑,其中包含下拉框等各种操作,所以就想到了使用extend进行修改table的cell
具体思路:
- 先做一个input组件用来修改数据,并将修改后的数据保留
- 再做一个span标签用来替代原始表格的cell 准备起来:
- input.vue
<template>
<div class="cell">
<el-input ref="elInputRef" v-model.trim="cellValue" :placeholder="`请输入${placeholder}`" clearable @clear="handleInputClear" @blur="handleChangePicker" />
</div>
</template>
<script>
export default {
name: 'FilterInput',
props: {
cellDom: Node,
cellValue: { type: [String, Number], default: '' },
placeholder: { type: [String, Number], default: '' },
saveRowData: { type: Function, default: () => {} },
row: { type: Object, default: () => {} },
property: { type: String, default: '' }
},
data() {
return {
searchData: null
}
},
mounted() {
this.$refs['elInputRef'].focus()
},
methods: {
handleChangePicker(val) {
if (this.saveRowData) {
return this.saveRowData({
cellValue: this.cellValue,
cellDom: this.cellDom,
row: this.row,
property: this.property
})
}
this.$emit('changeModel', this.cellValue)
},
handleInputClear(val) {
this.$emit('clear', this.cellValue)
}
}
}
</script>
<style lang="scss" scoped>
.cell {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
padding: 0 8px;
}
</style>
- span.vue
<script>
export default {
props: {
cellValue: {
type: [String, Number],
default: ''
}
},
render(h) {
return h('span', { class: 'cell', domProps: { innerHTML: this.cellValue ?? '' }})
}
}
</script>
如果是下拉框的话那就需要一个select组件
- select.vue
<template>
<el-select v-model="cellValue" :placeholder="`请选择${placeholder}`" clearable @change="handleChangePicker" @clear="handleSelectClear">
<el-option v-for="item in selectList" :key="item.key " :label="item.label" :value="item.label" />
</el-select>
</template>
<script>
export default {
name: 'FilterSelect',
props: {
cellDom: Node,
cellValue: { type: [String, Number], default: '' },
placeholder: { type: [String, Number], default: '' },
saveRowData: { type: Function, default: () => {} },
row: { type: Object, default: () => {} },
property: { type: String, default: '' },
selectList: { type: Array, default: () => {} },
handleChange: { type: Function, default: () => {} }
},
data() {
return {
searchData: null
}
},
methods: {
handleChangePicker(val) {
if (this.saveRowData) {
return this.saveRowData({
cellValue: this.cellValue,
cellDom: this.cellDom,
row: this.row,
property: this.property
})
}
this.$emit('changeModel', this.cellValue)
},
handleSelectClear(val) {
this.$emit('clear', this.searchData)
}
}
}
</script>
当然不能忘记了推荐搜索功能
- input-auto.vue
<template>
<div class="cell">
<el-autocomplete ref="refAuto" v-model.trim="cellValue" :fetch-suggestions="handleQuerySearch" :debounce="1200" :placeholder="`请输入${placeholder}`" clearable @clear="handleInputClear" @blur="handleChangePicker" @select="handleAutoSel" />
</div>
</template>
<script>
export default {
name: 'FilterInput',
props: {
cellDom: Node,
cellValue: { type: [String, Number], default: '' },
placeholder: { type: [String, Number], default: '' },
saveRowData: { type: Function, default: () => {} },
row: { type: Object, default: () => {} },
property: { type: String, default: '' },
fetchSuggestions: { type: Function, default: () => {} },
autoSelect: { type: Function, default: () => {} }
},
data() {
return {
searchData: null
}
},
mounted() {
this.$nextTick(_ => this.$refs['refAuto'].focus())
},
methods: {
handleChangePicker(val) {
if (this.saveRowData) {
return this.saveRowData({
cellValue: this.cellValue,
cellDom: this.cellDom,
row: this.row,
property: this.property,
params: val
})
}
this.$emit('changeModel', this.cellValue)
},
handleInputClear(val) {
this.$emit('clear', this.cellValue)
},
handleQuerySearch(str, cb) {
if (!str) return
this.fetchSuggestions(str, cb)
},
handleAutoSel(val) {
this.autoSelect(val)
}
}
}
</script>
<style lang="scss" scoped>
.cell {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
padding: 0 8px;
}
</style>
- 还需要准备用来继承的js,extend.js
<script>
import Vue from 'vue'
import FilterInput from '@/components/common/children/table-filter-input'
import definedSpan from '@/components/common/children/table-extend-span'
import InputAuto from '@/components/common/children/table-input-auto'
import defSelect from '@/components/common/children/table-filter-select'
const StarkInput = Vue.extend(FilterInput)
const StarkSpan = Vue.extend(definedSpan)
const StarkAuto = Vue.extend(InputAuto)
const StarkSel = Vue.extend(defSelect)
export default {
StarkInput,
StarkSpan,
StarkAuto,
StarkSel
}
一切准备就绪,我们开始组合
- stark-three.vue
<script>
import ExtendComponents from './extend-data'
import tablePrivate from '../children/table-private'
import { guid } from '@/utils/utils'
export default {
components: { tablePrivate },
props: {
tableColumn: { type: Array, default: () => [] },
tableDataList: { type: Array, default: () => [] },
tableHeight: { type: Number, default: 90 },
valueChange: { type: Function, default: () => {} },
editDic: { type: [Array, String, Boolean], default: false },
headDic: { type: [Array, String], default: '' },
headQuery: { type: Function, default: void 0 },
selection: { type: Boolean, default: false },
sortable: { type: Boolean, default: false },
private: { type: Boolean, default: false },
cellStyle: { type: Function, default: () => {} },
fetchSuggestions: { type: Function, default: () => {} },
autoSelect: { type: Function, default: () => {} },
selectList: { type: Function, default: () => {} }
},
data() {
return {
oldCellValue: null,
oldPopover: null,
dataForm: {}
}
},
methods: {
handleReset() {
this.dataForm = this.$options.data.call(this).dataForm
},
handleCellClassName({ row, rowIndex }) {
row._index = rowIndex
},
handleDblclick(row, column, cell, event) {
if (!this.editDic) return
if (column.type === 'index' || column.type === 'selection') return
this.oldCellValue = row[column.property]
const cellValue = row[column.property]
const dom = this.handleEditDic(column)
if (dom === false) return
new ExtendComponents[dom]({
propsData: {
cellValue: cellValue,
saveRowData: this.handleSaveRowData,
cellDom: cell, row: row, property: column.property,
selectList: this.selectList(column) ?? void 0,
fetchSuggestions: (val, cb) => this.fetchSuggestions(val, cb, row, column) ?? void 0,
autoSelect: (val) => this.autoSelect(val, row, column) ?? void 0
}
}).$mount(cell.children[0])
},
handleSaveRowData(params) {
if (params.cellValue === this.oldCellValue) {
console.log()
} else {
params.row[params.property] = params.cellValue
this.valueChange(params)
this.$set(this.tableDataList, params.row._index, params.row)
}
new ExtendComponents.StarkSpan({
propsData: {
cellValue: params?.cellValue
}
}).$mount(params.cellDom.children[0])
},
handleRowContext(row, column, event) {
event.preventDefault()
this.$emit('contextmenu', this.$contextmenu, event, row)
},
handleSelectionChange(val) {
this.$emit('selChange', val)
},
handleEditDic({ property }) {
if (property === this.editDic || (Array.isArray(this.editDic) && this.editDic.includes(property))) return 'StarkInput'
if (Array.isArray(this.editDic) && this.editDic?.[0]?.['type']) {
const ar = this.editDic.find(item => { if (item.property === property) return item })
return ar?.['type'] ?? false
}
return false
},
handleSift(down, $event) {
if (this.oldPopover === down.remark) return (this.oldPopover = null)
this.oldPopover = down.remark
const sibling = $event.target.parentNode.childNodes?.[0]
const xy = document.body.offsetWidth - $event.x
this.$nextTick(_ => {
let x = -(sibling.offsetWidth / 2)
if (xy < 200) { x = -200 }
sibling.style.left = `${$event.x + x}px`
sibling.style.top = `${$event.y + (sibling.offsetHeight / 4)}px`
})
},
handleInput(event, column) {
event?.constructor === String ? event = event.replace(/^\s+|\s+$/g, '') : null
this.$set(this.dataForm, column.property, event)
},
handleDatePicker(item) {
if (!item?.domType.includes('date')) return {}
const agr = {
type: item?.dateType,
defaultTime: item?.dateType === 'daterange' ? ['00:00:00', '23:59:59'] : '',
valueFormat: item?.valueFormat ?? 'yyyy-MM-dd HH:mm:ss',
...item?.place
}
return agr
},
handleCancel(val) { this.oldPopover = null },
handleQuery(val) {
if (this.headQuery) return this.headQuery({ data: this.dataForm })
},
handleTh(h, { column }) {
console.error(this.headDic)
},
handleDomTh(h, { column }, down) {
const labelLong = column.label.length
const size = 32
column.minWidth = labelLong * size + 32
return h('div', [
h('span', column.label),
h('el-popover', {
id: `ref-${down.remark}`,
ref: `ref-${down.remark}`,
attrs: { 'popper-class': 'table-popover', 'transition': 'el-zoom-in-top', value: down.remark === this.oldPopover },
scopedSlots: { reference: () => h('i', {
class: ['iconfont', 'icon-loudou', 'icon-cursor', `${this.dataForm[column.property] ? 'selected' : ''}`],
on: { click: (e) => this.handleSift(down, e) }})
}
},
[
h('el-row', { class: 'popover-body flex-justify-start' }, [
h('span', `${column.label}:`),
h(down.domType, {
attrs: {
placeholder: `请搜索${column.label}`,
clearable: true,
value: this.dataForm?.[column.property],
...this.handleDatePicker(down)
},
on: { input: (event) => this.handleInput(event, column) }
})
]),
h('el-row', { class: 'popover-foot' }, [
h('el-button', { domProps: { innerHTML: '取消' }, on: { click: () => this.handleCancel(column) }}),
h('el-button', { attrs: { type: 'primary' }, domProps: { innerHTML: '查询' }, on: { click: () => this.handleQuery(column) }})
])
])
])
}
},
render(h) {
const fc = () => {
if (!Array.isArray(this.tableColumn)) return []
return this.tableColumn.map(item => {
return h('el-table-column', {
attrs: {
prop: item.identification, label: item.remark, align: 'center', sortable: item?.sortable ?? false,
showOverflowTooltip: true, key: item?.remark || item?.id || guid(),
'render-header': item?.domType ? (h, { column, $index }) => this.handleDomTh(h, { column, $index }, item) : void 0
},
scopedSlots: {
default: (this.private && item.identification?.includes('mobile')) ? ({ row }) => h('table-private', { attrs: { cellValue: row[item.identification] }}) : void 0
}
})
})
}
const sel = () => {
if (!this.selection) return
return h('el-table-column', { attrs: { type: 'selection' }})
}
const sort = () => {
if (!this.sortable) return
return h('el-table-column', { attrs: { type: 'index' }})
}
return h('div', { attrs: { id: 'stark' }}, [
h('el-table', {
ref: 'refTable',
attrs: {
cellClassName: this.cellClassName, stripe: true, border: true, lazy: true,
data: this.tableDataList, height: '100%', 'cell-style': this.cellStyle,
'row-class-name': this.handleCellClassName
},
on: {
'cell-dblclick': this.handleDblclick,
'row-contextmenu': this.handleRowContext,
'selection-change': this.handleSelectionChange
},
directives: [{
name: 'tableHeight', value: { bottomOffset: this.tableHeight }, arg: { bottomOffset: this.tableHeight }
}]
},
[sel(), sort(), ...fc()])
])
}
}
</script>
搞定,这样就可以直接使用了,当然选项必须选择改变才可以,留到下次更新吧,这次累了