<template>
<div ref="cTableMain" class="cTable" :style="{'height':_height}">
<div :class="_tableHeight" class="is-null-data">
<el-table
:key="toggleIndex"
ref="scTable"
v-loading="_loading"
v-bind="$attrs"
:data="tableData"
:row-key="rowKey"
:tree-props="treeProps"
:height="height==='auto'?null:'100%'"
:size="config.size"
:border="config.border"
:stripe="config.stripe"
:summary-method="remoteSummary?remoteSummaryMethod:summaryMethod"
@sort-change="sortChange"
@select-all="selectionAll"
@selection-change="selectionChange"
@filter-change="filterChange"
>
<slot />
<div v-for="(item, index) in userColumn" :key="index">
<el-table-column
v-if="!item.hide && (item.action ? checkAction(item.action) : true)"
:column-key="item.prop"
:label="item.label"
:prop="item.prop"
:width="item.width"
:sortable="item.sortable"
:fixed="item.fixed"
:align="item.align"
:header-align="item.headerAlign"
:filters="item.filters"
:filter-method="remoteFilter||!item.filters?null:filterHandler"
:show-overflow-tooltip="item.showOverflowTooltip"
>
<template #default="scope">
<slot :name="item.prop" v-bind="scope">
{{ scope.row[item.prop] ? scope.row[item.prop] : '-' }}
</slot>
</template>
</el-table-column>
</div>
<el-table-column min-width="1" />
<template #empty>
<el-empty :description="emptyText" :image-size="100" />
</template>
</el-table>
</div>
<div v-if="!hidePagination" class="cTable-pagination"><slot name="foot" /></div>
</div>
</template>
<script>
import config from './config'
export default {
name: 'CTable',
props: {
loading: { type: Boolean, default: false },
tableName: { type: String, default: '' },
params: { type: Object, default: () => ({}) },
data: { type: [Object, Array], default: () => [] },
height: { type: [String, Number], default: '100%' },
size: { type: String, default: 'default' },
border: { type: Boolean, default: false },
stripe: { type: Boolean, default: false },
rowKey: { type: String, default: '' },
summaryMethod: { type: Function, default: null },
column: { type: [Object, Array], default: () => [] },
treeProps: { type: Object, default: () => ({ hasChildren: 'hasChildren', children: 'children' }) },
remoteSort: { type: Boolean, default: false },
remoteFilter: { type: Boolean, default: false },
remoteSummary: { type: Boolean, default: false },
hideToolbar: { type: Boolean, default: false }, // 工具栏
hidePagination: { type: Boolean, default: false }, // 分页
hideToolbarLeft: { type: Boolean, default: false }, // 工具栏左
hideToolbarRight: { type: Boolean, default: false }, // 工具栏右
hideFullscreen: { type: Boolean, default: false }, // 全屏
hideSetting: { type: Boolean, default: false } // 表格设置
},
data() {
return {
isActivat: true,
emptyText: '暂无数据',
toggleIndex: 0,
tableData: [],
tableHeight: '100%',
tableParams: this.params,
userColumn: [],
prop: null,
customColumnShow: false,
summary: {},
config: {
size: this.size,
border: this.border,
stripe: this.stripe
}
}
},
computed: {
_height() {
return Number(this.height) ? Number(this.height) + 'px' : this.height
},
_tableHeight() {
if (!this.hidePagination && !this.hideToolbar) {
return 'tableHeight-120'
} else if (!this.hidePagination || !this.hideToolbar) {
return 'tableHeight-60'
} else {
return 'tableHeight'
}
},
_loading: {
get() {
return this.loading
}, set(value) {
this.$emit('update:loading', value)
}
}
},
watch: {
// 监听从props里拿到值了
data(valArr) {
this.tableData = this.data
/* const _this = this
_this.userColumn = _this.column.map(function(value) {
const arr = valArr.map(x => x[value.prop]) // 获取每一列的所有数据
arr.push(value.label) // 把每列的表头也加进去算
value.width = _this.getMaxLength(arr) + 20 // 每列内容最大的宽度 + 表格的内间距(依据实际情况而定)
return value
})*/
// this.$refs.scTable.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
}
},
mounted() {
// 判断是否开启自定义列
if (this.column) {
this.getCustomColumn()
} else {
this.userColumn = this.column
}
this.tableData = this.data
},
activated() {
if (!this.isActivat) {
this.$refs.scTable.doLayout()
}
},
deactivated() {
this.isActivat = false
},
methods: {
/**
* 遍历列的所有内容,获取最宽一列的宽度
* @param arr
*/
getMaxLength(arr) {
return arr.reduce((acc, item) => {
if (item) {
const calcLen = this.getTextWidth(item)
console.log('calcLen', calcLen)
console.log('acc', acc)
if (acc < calcLen) {
acc = calcLen
}
}
return acc
}, 0)
},
/**
* 使用span标签包裹内容,然后计算span的宽度 width: px
* @param valArr
*/
getTextWidth(str) {
let width = 0
const html = document.createElement('span')
html.innerText = str
html.className = 'getTextWidth'
document.querySelector('body').appendChild(html)
width = document.querySelector('.getTextWidth').offsetWidth + 50
document.querySelector('.getTextWidth').remove()
return width
},
// 刷新数据
refresh() {
this.$refs.scTable.clearSelection()
},
// 更新数据 合并上一次params
upData(params) {
this.$refs.scTable.clearSelection()
Object.assign(this.tableParams, params || {})
// this.$refs.scTable.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
},
// 重载数据 替换params
reload(params) {
this.tableParams = params || {}
this.$refs.scTable.clearSelection()
this.$refs.scTable.clearSort()
this.$refs.scTable.clearFilter()
// this.$refs.scTable.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
},
// 自定义变化事件
columnSettingChange(userColumn) {
this.userColumn = userColumn
this.toggleIndex += 1
this.doLayout()
},
// 排序事件
sortChange(obj) {
if (!this.remoteSort) {
return false
}
if (obj.column && obj.prop) {
this.prop = obj.prop
this.order = obj.order
} else {
this.prop = null
this.order = null
}
// this.$refs.scTable.$el.querySelector('.el-table__body-wrapper').scrollTop = 0
},
selectionAll(val) {
this.$emit('select-all', val)
},
selectionChange(obj) {
this.$emit('selection-change', obj)
},
// 本地过滤
filterHandler(value, row, column) {
const property = column.property
return row[property] === value
},
// 过滤事件
filterChange(filters) {
if (!this.remoteFilter) {
return false
}
Object.keys(filters).forEach(key => {
filters[key] = filters[key].join(',')
})
this.upData(filters)
},
// 远程合计行处理
remoteSummaryMethod(param) {
const { columns } = param
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '合计'
return
}
const values = this.summary[column.property]
if (values) {
sums[index] = values
} else {
sums[index] = ''
}
})
return sums
},
configSizeChange() {
this.$refs.scTable.doLayout()
},
// 原生方法转发
clearSelection() {
this.$refs.scTable.clearSelection()
},
toggleRowSelection(row, selected) {
this.$refs.scTable.toggleRowSelection(row, selected)
},
toggleAllSelection() {
this.$refs.scTable.toggleAllSelection()
},
toggleRowExpansion(row, expanded) {
this.$refs.scTable.toggleRowExpansion(row, expanded)
},
setCurrentRow(row) {
this.$refs.scTable.setCurrentRow(row)
},
clearSort() {
this.$refs.scTable.clearSort()
},
clearFilter(columnKey) {
this.$refs.scTable.clearFilter(columnKey)
},
doLayout() {
this.$nextTick(() => {
setTimeout(() => {
this.$refs.scTable.doLayout()
}, 200)
})
},
sort(prop, order) {
this.$refs.scTable.sort(prop, order)
}
}
}
</script>
模态
<template>
<el-dialog
v-bind="$attrs"
:top="top"
:close-on-click-modal="closeOnClickModal"
:show-close="showClose"
append-to-body
:close-on-press-escape="closeOnPressEscape"
:width="width"
>
<div slot="title">
<slot name="title">
<div class="title" :style="`background:${titleBg}`">
{{ title }}
</div>
</slot>
</div>
<div v-if="maxHeight" :style="style">
<el-scrollbar :vertical="true" :max-height="maxHeight" style="height:100%">
<div>
<slot />
</div>
</el-scrollbar>
</div>
<div v-else :style="style">
<slot />
</div>
<div slot="footer" :class="[footerClass[footerPosition], 'dialog-footer', dialogFooter]">
<slot name="footer" />
</div>
</el-dialog>
</template>
<script>
export default {
name: 'CustomDialog',
props: {
closeOnPressEscape: {
type: Boolean,
default: true
},
showClose: {
type: Boolean,
default: true
},
closeOnClickModal: {
type: Boolean,
default: false
},
maxHeight: {
type: String,
default: undefined
},
top: {
type: String,
default: '7vh'
},
title: {
type: String,
default: ''
},
footerPosition: {
type: String,
default: 'center'
},
dialogFooter: {
type: String,
default: 'center'
},
width: {
type: String,
default: '585px'
},
titleBg: {
type: String,
default: '#F1F6FF'
}
},
data() {
return {
footerClass: {
'center': 'footer-center',
'left': 'footer-left',
'right': 'footer-right'
}
}
},
computed: {
style() {
const style = {}
if (!this.fullscreen) {
if (this.maxHeight) {
style.height = this.maxHeight
}
}
return style
}
}
}
</script>
<style lang="scss">
.footer-center {
display: flex;
justify-content: center;
}
.footer-left{
display: flex;
justify-content: left;
}
.footer-right{
display: flex;
justify-content: right;
}
</style>
表单
<template>
<div>
<el-form ref="ruleForm" v-loading="formLoading" :model="form" :rules="rules" :label-width="labelWidth" :class="direction==='vertical'?'normal_form':'flex_form'">
<el-form-item
v-for="item in options"
:key="item.propName"
:prop="item.propName"
:label="item.labelName"
:label-width="item.labelWidth || labelWidth"
class="form_item"
>
<!--提供一个插槽,提高扩展性-->
<slot v-if="item.component === 'slot'" :name="item.slotName" />
<el-input v-else-if="item.component === 'textarea'" v-model="form[item.propName]" :class="item.componentClass ? item.componentClass : 'g-input-m'" :autosize="{ minRows:3, maxRows: 3}" type="textarea" placeholder="请输入" />
<template v-else>
<component :is="item.component" v-model="form[item.propName]" :class="item.componentClass ? item.componentClass : 'g-input-m'" v-bind="item.props" :style="{width: item.width || '200px'}" v-on="item.listeners">
<template v-if="item.component === 'el-select'">
<el-option v-for="(subitem,index) in item.data" :key="index" :label="subitem.label" :value="subitem.value" />
</template>
<template v-else-if="item.component === 'el-select-city'">
<el-radio
v-for="(subitem,index) in item.optionsArr"
:key="index"
:label="subitem.value"
>
{{ subitem.label }}
</el-radio>
</template>
<template v-else-if="item.component === 'el-checkbox-group'">
<el-checkbox
v-for="(subitem,index) in item.optionsArr"
:key="index"
:label="subitem.value"
>
{{ subitem.label }}
</el-checkbox>
</template>
<template v-else-if="item.component === 'el-radio-group'">
<el-radio
v-for="(subitem,index) in item.optionsArr"
:key="index"
:label="subitem.value"
>
{{ subitem.label }}
</el-radio>
</template>
</component>
</template>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
props: {
form: { // 表单绑定数据
type: Object,
default: () => {}
},
options: { // 表单项配置数据
type: Array,
default: () => []
},
rules: { // 表单验证规则
type: Object,
default: () => {}
},
labelWidth: { // label宽度
type: String,
default: '100px'
},
direction: { // 表单方向
type: String,
default: 'vertical'
},
formLoading: {
type: Boolean,
default: false
}
},
data() {
return {
}
},
methods: {
validate() {
this.$refs.ruleForm.validate(valid => {
this.$emit('validate-form', valid)
})
},
resetFields() {
this.$refs.ruleForm.resetFields()
}
}
}
</script>
表格