Files
vxe-table/packages/table/src/table.js
xuliangzhan 286f47081e 更新文档
2019-10-24 21:26:49 +08:00

753 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import XEUtils from 'xe-utils/methods/xe-utils'
import GlobalConfig from '../../conf'
import VXETable from '../../v-x-e-table'
import { UtilTools, GlobalEvent } from '../../tools'
import methods from './methods'
/**
* 渲染浮固定列
* 分别渲染左边固定列和右边固定列
* 如果宽度足够情况下,则不需要渲染固定列
* @param {Function} h 创建 VNode 函数
* @param {Object} $table 表格实例
* @param {String} fixedType 固定列类型
*/
function renderFixed (h, $table, fixedType) {
let { tableData, tableColumn, visibleColumn, collectColumn, isGroup, vSize, showHeader, showFooter, columnStore, footerData } = $table
let fixedColumn = columnStore[`${fixedType}List`]
return h('div', {
class: `vxe-table--fixed-${fixedType}-wrapper`,
ref: `${fixedType}Container`
}, [
showHeader ? h('vxe-table-header', {
props: {
fixedType,
tableData,
tableColumn,
visibleColumn,
collectColumn,
size: vSize,
fixedColumn,
isGroup
},
ref: `${fixedType}Header`
}) : null,
h('vxe-table-body', {
props: {
fixedType,
tableData,
tableColumn,
visibleColumn,
collectColumn,
fixedColumn,
size: vSize,
isGroup
},
ref: `${fixedType}Body`
}),
showFooter ? h('vxe-table-footer', {
props: {
fixedType,
footerData,
tableColumn,
visibleColumn,
size: vSize,
fixedColumn
},
ref: `${fixedType}Footer`
}) : null
])
}
export default {
name: 'VxeTable',
props: {
/** 基本属性 */
// 数据
data: Array,
// 初始化绑定动态列
customs: Array,
// 表格的高度
height: [Number, String],
// 表格的最大高度
maxHeight: [Number, String],
// 所有列是否允许拖动列宽调整大小
resizable: { type: Boolean, default: () => GlobalConfig.resizable },
// 是否带有斑马纹
stripe: { type: Boolean, default: () => GlobalConfig.stripe },
// 是否带有纵向边框
border: { type: Boolean, default: () => GlobalConfig.border },
// 表格的尺寸
size: { type: String, default: () => GlobalConfig.size },
// 列的宽度是否自撑开
fit: { type: Boolean, default: () => GlobalConfig.fit },
// 表格是否加载中
loading: Boolean,
// 所有的列对其方式
align: { type: String, default: () => GlobalConfig.align },
// 所有的表头列的对齐方式
headerAlign: { type: String, default: () => GlobalConfig.headerAlign },
// 所有的表尾列的对齐方式
footerAlign: { type: String, default: () => GlobalConfig.footerAlign },
// 是否显示表头
showHeader: { type: Boolean, default: () => GlobalConfig.showHeader },
// 只对 type=index 时有效,自定义序号的起始值
startIndex: { type: Number, default: 0 },
// 是否要高亮当前选中行
highlightCurrentRow: { type: Boolean, default: () => GlobalConfig.highlightCurrentRow },
// 鼠标移到行是否要高亮显示
highlightHoverRow: { type: Boolean, default: () => GlobalConfig.highlightHoverRow },
// 是否要高亮当前选中列
highlightCurrentColumn: { type: Boolean, default: () => GlobalConfig.highlightCurrentColumn },
// 鼠标移到列是否要高亮显示
highlightHoverColumn: { type: Boolean, default: () => GlobalConfig.highlightHoverColumn },
// 激活单元格编辑时是否高亮显示
highlightCell: Boolean,
// 是否显示表尾合计
showFooter: Boolean,
// 表尾合计的计算方法
footerMethod: Function,
// 给行附加 className
rowClassName: [String, Function],
// 给单元格附加 className
cellClassName: [String, Function],
// 给表头的行附加 className
headerRowClassName: [String, Function],
// 给表头的单元格附加 className
headerCellClassName: [String, Function],
// 给表尾的行附加 className
footerRowClassName: [String, Function],
// 给表尾的单元格附加 className
footerCellClassName: [String, Function],
// 合并行或列
spanMethod: Function,
// 表尾合并行或列
footerSpanMethod: Function,
// 设置所有内容过长时显示为省略号
showOverflow: { type: [Boolean, String], default: () => GlobalConfig.showOverflow },
// 设置表头所有内容过长时显示为省略号
showHeaderOverflow: { type: [Boolean, String], default: () => GlobalConfig.showHeaderOverflow },
// 是否所有服务端筛选
remoteFilter: Boolean,
// 是否所有服务端排序
remoteSort: Boolean,
// 自定义所有列的排序方法
sortMethod: Function,
// 所有列宽度
columnWidth: [Number, String],
// 所有列最小宽度,把剩余宽度按比例分配
columnMinWidth: [Number, String],
/** 高级属性 */
// 主键配置
columnKey: Boolean,
rowKey: Boolean,
rowId: { type: String, default: () => GlobalConfig.rowId },
// 是否自动监听父容器变化去更新响应式表格宽高
autoResize: Boolean,
// 是否自动根据状态属性去更新响应式表格宽高
syncResize: Boolean,
// 排序配置项
sortConfig: Object,
// 单选框配置
radioConfig: Object,
// v3.0 废弃)
selectConfig: Object,
// 复选框配置项
checkboxConfig: Object,
// tooltip 配置项
tooltipConfig: Object,
// 展开行配置项
expandConfig: Object,
// 树形结构配置项
treeConfig: Object,
// 快捷菜单配置项
contextMenu: Object,
// 鼠标配置项
mouseConfig: Object,
// 按键配置项
keyboardConfig: Object,
// 编辑配置项
editConfig: Object,
// 校验配置项
validConfig: Object,
// 校验规则配置项
editRules: Object,
// 优化配置项
optimization: Object,
// 额外的参数
params: Object
},
provide () {
return {
$table: this
}
},
inject: {
$grid: {
default: null
}
},
mixins: [],
data () {
return {
id: XEUtils.uniqueId(),
// 列分组配置
collectColumn: [],
// 完整所有列
tableFullColumn: [],
// 渲染的列
tableColumn: [],
// 渲染中的数据
tableData: [],
// 是否启用了横向 X 可视渲染方式加载
scrollXLoad: false,
// 是否启用了纵向 Y 可视渲染方式加载
scrollYLoad: false,
// 是否存在纵向滚动条
overflowY: true,
// 是否存在横向滚动条
overflowX: false,
// 纵向滚动条的宽度
scrollbarWidth: 0,
// 横向滚动条的高度
scrollbarHeight: 0,
// 复选框属性,是否全选
isAllSelected: false,
// 复选框属性,有选中且非全选状态
isIndeterminate: false,
// 复选框属性,已选中的列
selection: [],
// 当前行
currentRow: null,
// 单选框属性,选中行
selectRow: null,
// 表尾合计数据
footerData: [],
// 已展开的行
expandeds: [],
// 已展开树节点
treeExpandeds: [],
// 树节点不确定状态的列表
treeIndeterminates: [],
// 当前选中的筛选列
filterStore: {
isAllSelected: false,
isIndeterminate: false,
style: null,
options: [],
column: null,
multiple: false,
visible: false
},
// 存放列相关的信息
columnStore: {
leftList: [],
centerList: [],
rightList: [],
resizeList: [],
pxList: [],
pxMinList: [],
scaleList: [],
scaleMinList: [],
autoList: []
},
// 存放快捷菜单的信息
ctxMenuStore: {
selected: null,
visible: false,
showChild: false,
selectChild: null,
list: [],
style: null
},
// 存放可编辑相关信息
editStore: {
indexs: {
columns: []
},
titles: {
columns: []
},
// 所有选中
checked: {
rows: [],
columns: [],
tRows: [],
tColumns: []
},
// 选中源
selected: {
row: null,
column: null
},
// 已复制源
copyed: {
cut: false,
rows: [],
columns: []
},
// 激活
actived: {
row: null,
column: null
},
insertList: [],
removeList: []
},
// 存放数据校验相关信息
validStore: {
visible: false,
row: null,
column: null,
content: '',
rule: null,
isArrow: false
}
}
},
computed: {
vSize () {
return this.size || this.$parent.size || this.$parent.vSize
},
validOpts () {
return Object.assign({ message: 'default' }, GlobalConfig.validConfig, this.validConfig)
},
optimizeOpts () {
return Object.assign({}, GlobalConfig.optimization, this.optimization)
},
rowHeightMaps () {
return Object.assign({
default: 48,
medium: 44,
small: 40,
mini: 36
}, this.optimizeOpts.rHeights)
},
vaildTipOpts () {
return Object.assign({ isArrow: false }, this.tooltipConfig)
},
sortOpts () {
return Object.assign({}, GlobalConfig.sortConfig, this.sortConfig)
},
// 是否使用了分组表头
isGroup () {
return this.collectColumn.some(column => UtilTools.hasChildrenList(column))
},
hasTip () {
return VXETable._tooltip
},
visibleColumn () {
return this.tableFullColumn ? this.tableFullColumn.filter(column => column.visible) : []
},
isResizable () {
return this.resizable || this.tableFullColumn.some(column => column.resizable)
},
hasFilter () {
return this.tableColumn.some(column => column.filters && column.filters.length)
},
headerCtxMenu () {
return this.ctxMenuOpts.header && this.ctxMenuOpts.header.options ? this.ctxMenuOpts.header.options : []
},
bodyCtxMenu () {
return this.ctxMenuOpts.body && this.ctxMenuOpts.body.options ? this.ctxMenuOpts.body.options : []
},
isCtxMenu () {
return this.headerCtxMenu.length || this.bodyCtxMenu.length
},
ctxMenuOpts () {
return Object.assign({}, GlobalConfig.menu, this.contextMenu)
},
ctxMenuList () {
let rest = []
this.ctxMenuStore.list.forEach(list => {
list.forEach(item => {
rest.push(item)
})
})
return rest
},
/**
* 判断列全选的复选框是否禁用
*/
isAllCheckboxDisabled () {
let { tableFullData, treeConfig } = this
// 在 v3.0 中废弃 selectConfig
let checkboxConfig = this.checkboxConfig || this.selectConfig || {}
let { strict, checkMethod } = checkboxConfig
if (strict) {
if (tableFullData.length) {
if (checkMethod) {
if (treeConfig) {
// 暂时不支持树形结构
}
// 如果所有行都被禁用
return tableFullData.every((row, rowIndex) => !checkMethod({ row, rowIndex, $rowIndex: rowIndex }))
}
return false
}
return true
}
return false
}
},
watch: {
data (value) {
if (!this._isUpdateData) {
this.loadTableData(value, true).then(this.handleDefault)
}
this._isUpdateData = false
},
customs (value) {
if (!this.isUpdateCustoms) {
this.mergeCustomColumn(value)
}
this.isUpdateCustoms = false
},
collectColumn (value) {
let tableFullColumn = UtilTools.getColumnList(value)
this.tableFullColumn = tableFullColumn
this.cacheColumnMap()
if (this.customs) {
this.mergeCustomColumn(this.customs)
}
this.refreshColumn()
this.handleTableData(true)
if (this.$toolbar) {
this.$toolbar.updateColumn(tableFullColumn)
}
// 在 v3.0 中废弃 prop、label
if (tableFullColumn.length) {
let cIndex = Math.floor((tableFullColumn.length - 1) / 2)
if (tableFullColumn[cIndex].prop) {
UtilTools.warn('vxe.error.delProp', ['prop', 'field'])
}
if (tableFullColumn[cIndex].label) {
UtilTools.warn('vxe.error.delProp', ['label', 'title'])
}
}
if (this.treeConfig && tableFullColumn.some(column => column.fixed) && tableFullColumn.some(column => column.type === 'expand')) {
UtilTools.warn('vxe.error.treeFixedExpand')
}
},
tableColumn () {
this.analyColumnWidth()
},
height () {
this.$nextTick(this.recalculate)
},
loading () {
if (!this._isLoading) {
this._isLoading = true
}
},
syncResize (value) {
if (value) {
this.$nextTick(this.recalculate)
}
}
},
created () {
let { scrollYStore, optimizeOpts, data, loading } = Object.assign(this, {
tZindex: 0,
elemStore: {},
// 存放横向 X 虚拟滚动相关的信息
scrollXStore: {},
// 存放纵向 Y 虚拟滚动相关信息
scrollYStore: {},
// 存放 tooltip 相关信息
tooltipStore: {},
// 表格父容器的高度
parentHeight: 0,
// 表格宽度
tableWidth: 0,
// 表格高度
tableHeight: 0,
// 表头高度
headerHeight: 0,
// 表尾高度
footerHeight: 0,
// 单选框属性,选中列
// currentColumn: null,
// 当前 hover 行
// hoverRow: null,
// 最后滚动位置
lastScrollLeft: 0,
lastScrollTop: 0,
// 完整数据、条件处理后
tableFullData: [],
afterFullData: [],
// 缓存数据集
fullAllDataRowMap: new Map(),
fullAllDataRowIdData: {},
fullDataRowMap: new Map(),
fullDataRowIdData: {},
fullColumnMap: new Map(),
fullColumnIdData: {}
})
let { scrollY } = optimizeOpts
// 是否加载过 Loading 模块
this._isLoading = loading
if (!UtilTools.getRowkey(this)) {
UtilTools.error('vxe.error.emptyProp', ['row-id'])
}
// if (this.selectConfig) {
// UtilTools.warn('vxe.error.delProp', ['select-config', 'checkbox-config'])
// }
// 检查是否有安装需要的模块
let errorModuleName
if (!VXETable._edit && this.editConfig) {
errorModuleName = 'Edit'
} else if (!VXETable._valid && this.editRules) {
errorModuleName = 'Validator'
} else if (!VXETable._keyboard && (this.keyboardConfig || this.mouseConfig)) {
errorModuleName = 'Keyboard'
} else if (!VXETable._resize && this.autoResize) {
errorModuleName = 'Resize'
}
if (errorModuleName) {
throw new Error(UtilTools.getLog('vxe.error.reqModule', ['errorModuleName']))
}
if (scrollY) {
Object.assign(scrollYStore, {
startIndex: 0,
visibleIndex: 0,
adaptive: XEUtils.isBoolean(scrollY.adaptive) ? scrollY.adaptive : true,
renderSize: XEUtils.toNumber(scrollY.rSize),
offsetSize: XEUtils.toNumber(scrollY.oSize)
})
}
this.loadTableData(data, true).then(() => {
this.handleDefault()
this.updateStyle()
})
GlobalEvent.on(this, 'mousedown', this.handleGlobalMousedownEvent)
GlobalEvent.on(this, 'blur', this.handleGlobalBlurEvent)
GlobalEvent.on(this, 'mousewheel', this.handleGlobalMousewheelEvent)
GlobalEvent.on(this, 'keydown', this.handleGlobalKeydownEvent)
GlobalEvent.on(this, 'resize', this.handleGlobalResizeEvent)
GlobalEvent.on(this, 'contextmenu', this.handleGlobalContextmenuEvent)
this.preventEvent(null, 'created', { $table: this })
},
mounted () {
if (this.autoResize && VXETable._resize) {
this.bindResize()
}
document.body.appendChild(this.$refs.tableWrapper)
this.preventEvent(null, 'mounted', { $table: this })
},
activated () {
let { lastScrollLeft, lastScrollTop } = this
if (lastScrollLeft || lastScrollTop) {
this.clearScroll().then(this.recalculate).then(() => this.scrollTo(lastScrollLeft, lastScrollTop))
}
this.preventEvent(null, 'activated', { $table: this })
},
deactivated () {
this.preventEvent(null, 'deactivated', { $table: this })
},
beforeDestroy () {
let tableWrapper = this.$refs.tableWrapper
if (tableWrapper && tableWrapper.parentNode) {
tableWrapper.parentNode.removeChild(tableWrapper)
}
if (VXETable._resize) {
this.unbindResize()
}
this.closeFilter()
this.closeMenu()
this.preventEvent(null, 'beforeDestroy', { $table: this })
},
destroyed () {
GlobalEvent.off(this, 'mousedown')
GlobalEvent.off(this, 'blur')
GlobalEvent.off(this, 'mousewheel')
GlobalEvent.off(this, 'keydown')
GlobalEvent.off(this, 'resize')
GlobalEvent.off(this, 'contextmenu')
this.preventEvent(null, 'destroyed', { $table: this })
},
render (h) {
let {
_e,
id,
tableData,
tableColumn,
visibleColumn,
collectColumn,
isGroup,
hasFilter,
isResizable,
isCtxMenu,
loading,
_isLoading,
showHeader,
border,
stripe,
height,
highlightHoverRow,
highlightHoverColumn,
highlightCell,
vSize,
showOverflow,
showHeaderOverflow,
editConfig,
validOpts,
mouseConfig = {},
editRules,
showFooter,
footerMethod,
overflowX,
overflowY,
scrollbarHeight,
optimizeOpts,
vaildTipOpts,
tooltipConfig,
columnStore,
filterStore,
ctxMenuStore,
footerData,
hasTip
} = this
let { leftList, rightList } = columnStore
return h('div', {
class: {
'vxe-table': 1,
[`size--${vSize}`]: vSize,
'vxe-editable': editConfig,
'show--head': showHeader,
'show--foot': showFooter,
'scroll--y': overflowY,
'scroll--x': overflowX,
'fixed--left': leftList.length,
'fixed--right': rightList.length,
'all-overflow': showOverflow,
'all-head-overflow': showHeaderOverflow,
'c--highlight': highlightCell,
't--animat': optimizeOpts.animat,
't--stripe': stripe,
't--border': border,
't--checked': mouseConfig.checked,
'is--loading': loading,
'row--highlight': highlightHoverRow,
'column--highlight': highlightHoverColumn
}
}, [
/**
* 隐藏列
*/
h('div', {
class: 'vxe-table-hidden-column',
ref: 'hideColumn'
}, this.$slots.default),
/**
* 主头部
*/
showHeader ? h('vxe-table-header', {
ref: 'tableHeader',
props: {
tableData,
tableColumn,
visibleColumn,
collectColumn,
size: vSize,
isGroup
}
}) : _e(),
/**
* 主内容
*/
h('vxe-table-body', {
ref: 'tableBody',
props: {
tableData,
tableColumn,
visibleColumn,
collectColumn,
size: vSize,
isGroup
}
}),
/**
* 底部汇总
*/
showFooter ? h('vxe-table-footer', {
props: {
footerData,
footerMethod,
tableColumn,
visibleColumn,
size: vSize
},
ref: 'tableFooter'
}) : _e(),
/**
* 左侧固定列
*/
leftList && leftList.length && overflowX ? renderFixed(h, this, 'left') : _e(),
/**
* 右侧固定列
*/
rightList && rightList.length && overflowX ? renderFixed(h, this, 'right') : _e(),
/**
* 列宽线
*/
isResizable ? h('div', {
class: 'vxe-table--resizable-bar',
style: overflowX ? {
'padding-bottom': `${scrollbarHeight}px`
} : null,
ref: 'resizeBar'
}) : _e(),
/**
* 加载中
*/
_isLoading ? h('vxe-table-loading', {
props: {
visible: loading
}
}) : _e(),
h('div', {
class: `vxe-table${id}-wrapper ${this.$vnode.data.staticClass || ''}`,
ref: 'tableWrapper'
}, [
/**
* 筛选
*/
hasFilter ? h('vxe-table-filter', {
props: {
optimizeOpts,
filterStore
},
ref: 'filterWrapper'
}) : _e(),
/**
* 快捷菜单
*/
isCtxMenu ? h('vxe-table-context-menu', {
props: {
ctxMenuStore
},
ref: 'ctxWrapper'
}) : _e(),
/**
* 单元格内容溢出的 tooltip
*/
hasTip ? h('vxe-tooltip', {
ref: 'tooltip',
props: tooltipConfig
}) : _e(),
/**
* 校验不通过的 tooltip
*/
hasTip && editRules && (validOpts.message === 'default' ? !height : validOpts.message === 'tooltip') ? h('vxe-tooltip', {
class: 'vxe-table--valid-error',
props: validOpts.message === 'tooltip' || tableData.length === 1 ? vaildTipOpts : null,
ref: 'validTip'
}) : _e()
])
])
},
methods
}