import XEUtils from 'xe-utils' import PropTypes from 'ant-design-vue/es/_util/vue-types' import { JVXETypes } from '@/components/jeecg/JVxeTable/jvxeTypes' import VxeWebSocketMixins from '../mixins/vxe.web.socket.mixins' import { initDictOptions } from '@/components/dict/JDictSelectUtil' import { getRefPromise } from '../utils/vxeUtils' import { getEnhancedMixins, replaceProps } from '../utils/cellUtils' import JVxeToolbar from './JVxeToolbar' import JVxeSubPopover from './JVxeSubPopover' import JVxeDetailsModal from './JVxeDetailsModal' import JVxePagination from './JVxePagination' import { cloneObject, getVmParentByName, pushIfNotExist, randomString, simpleDebounce } from '@/utils/util' import { UtilTools } from 'vxe-table/packages/tools/src/utils' import { getNoAuthCols } from '@/utils/authFilter' export default { name: 'JVxeTable', provide() { return { superTrigger: (name, event) => this.trigger(name, event) } }, mixins: [VxeWebSocketMixins], components: {JVxeToolbar, JVxeSubPopover, JVxeDetailsModal, JVxePagination}, props: { rowKey: PropTypes.string.def('id'), // 列信息 columns: { type: Array, required: true }, // 数据源 dataSource: { type: Array, required: true }, authPre: { type: String, required: false, default: '' }, // 是否显示工具栏 toolbar: PropTypes.bool.def(false), // 工具栏配置 toolbarConfig: PropTypes.object.def(() => ({ // prefix 前缀;suffix 后缀; slots: ['prefix', 'suffix'], // add 新增按钮;remove 删除按钮;clearSelection 清空选择按钮;collapse 展开收起 btns: ['add', 'remove', 'clearSelection'], })), // 是否显示行号 rowNumber: PropTypes.bool.def(false), // 是否可选择行 rowSelection: PropTypes.bool.def(false), // 选择行类型 rowSelectionType: PropTypes.oneOf(['checkbox', 'radio']).def('checkbox'), // 是否可展开行 rowExpand: PropTypes.bool.def(false), // 展开行配置 expandConfig: PropTypes.object.def(() => ({})), // 页面是否在加载中 loading: PropTypes.bool.def(false), height: PropTypes.instanceOf([Number, String]).def('auto'), // 最大高度 maxHeight: { type: Number, default: () => null, }, // 要禁用的行 TODO 未实现 disabledRows: PropTypes.object.def(() => ({})), // 是否禁用全部组件 disabled: PropTypes.bool.def(false), // 是否可拖拽排序 TODO 仅实现上下排序,未实现拖拽排序(可能无法实现或较为困难) dragSort: PropTypes.bool.def(false), // 排序字段保存的Key dragSortKey: PropTypes.string.def('orderNum'), // 大小,可选值有:medium(中)、small(小)、mini(微)、tiny(非常小) size: PropTypes.oneOf(['medium', 'small', 'mini', 'tiny']).def('medium'), // 是否显示边框线 bordered: PropTypes.bool.def(false), // 分页器参数,设置了即可显示分页器 pagination: PropTypes.object.def(() => ({})), // 点击行时是否显示子表单 clickRowShowSubForm: PropTypes.bool.def(false), // 点击行时是否显示主表单 clickRowShowMainForm: PropTypes.bool.def(false), // 是否点击选中行,优先级最低 clickSelectRow: PropTypes.bool.def(false), // 是否开启 reload 数据效果 reloadEffect: PropTypes.bool.def(false), // 校验规则 editRules: PropTypes.object.def(() => ({})), // 是否异步删除行,如果你要实现异步删除,那么需要把这个选项开启, // 在remove事件里调用confirmRemove方法才会真正删除(除非删除的全是新增的行) asyncRemove: PropTypes.bool.def(false), // 是否一直显示组件,如果为false则只有点击的时候才出现组件 // 注:该参数不能动态修改;如果行、列字段多的情况下,会根据机器性能造成不同程度的卡顿。 alwaysEdit: PropTypes.bool.def(false), // 联动配置,数组,详情配置见文档 linkageConfig: PropTypes.array.def(() => []), }, data() { return { isJVXETable: true, // caseId,表格唯一标识 caseId: `_j-vxe-${randomString(8)}_`, // 内置columns _innerColumns: [], // 内置 EditRules _innerEditRules: [], // 记录滚动条位置 scroll: {top: 0, left: 0}, // 当前是否正在滚动 scrolling: false, // vxe 默认配置 defaultVxeProps: { 'row-id': this.rowKey, // 高亮hover的行 'highlight-hover-row': true, // 溢出隐藏并显示tooltip 'show-overflow': true, // 表头溢出隐藏并显示tooltip 'show-header-overflow': true, 'show-footer-overflow': true, // 可编辑配置 'edit-config': {trigger: 'click', mode: 'cell', showStatus: true}, 'expand-config': { iconClose: 'ant-table-row-expand-icon ant-table-row-collapsed', iconOpen: 'ant-table-row-expand-icon ant-table-row-expanded' }, // 虚拟滚动配置,y轴大于30条数据时启用虚拟滚动 // 'scroll-y': { // gt: 30 // }, // 'scroll-x': { // gt: 15 // }, 'radio-config': {highlight: true}, 'checkbox-config': {highlight: true}, }, // 绑定左侧选择框 selectedRows: [], // 绑定左侧选择框已选择的id selectedRowIds: [], // 统计列配置 statistics: { has: false, sum: [], average: [], }, // 允许执行刷新特效的行ID reloadEffectRowKeysMap: {}, //配置了但是没有授权的按钮和列 集合 excludeCode:[], // 联动下拉选项(用于隔离不同的下拉选项) // 内部联动配置,map _innerLinkageConfig: null, } }, computed: { // vxe 最终 columns vxeColumns() { this._innerColumns.forEach(column => { let renderOptions = { caseId: this.caseId, bordered: this.bordered, disabled: this.disabled, scrolling: this.scrolling, reloadEffect: this.reloadEffect, reloadEffectRowKeysMap: this.reloadEffectRowKeysMap, listeners: this.cellListeners, } if (column.$type === JVXETypes.rowDragSort) { renderOptions.dragSortKey = this.dragSortKey } // slot 组件特殊处理 if (column.$type === JVXETypes.slot) { if (this.$scopedSlots.hasOwnProperty(column.slotName)) { renderOptions.slot = this.$scopedSlots[column.slotName] renderOptions.target = this } } // 处理联动列,联动列只能作用于 select 组件 if (column.$type === JVXETypes.select && this._innerLinkageConfig != null) { // 判断当前列是否是联动列 if (this._innerLinkageConfig.has(column.key)) { renderOptions.linkage = { config: this._innerLinkageConfig.get(column.key), getLinkageOptionsSibling: this.getLinkageOptionsSibling, getLinkageOptionsAsync: this.getLinkageOptionsAsync, linkageSelectChange: this.linkageSelectChange, } } } if (column.editRender) { Object.assign(column.editRender, renderOptions) } if (column.cellRender) { Object.assign(column.cellRender, renderOptions) } // update--begin--autor:lvdandan-----date:20201019------for:LOWCOD-882 【新行编辑】列表上带按钮的遮挡问题 if (column.$type === JVXETypes.file || column.$type === JVXETypes.image) { if (column.width && column.width.endsWith('px')) { column.width = Number.parseInt(column.width.substr(0,column.width.length-2))+Number.parseInt(1)+'px'; } } // update--begin--autor:lvdandan-----date:20201019------for:LOWCOD-882 【新行编辑】列表上带按钮的遮挡问题 // update--begin--autor:lvdandan-----date:20201211------for:JT-118 【online】 日期、时间控件长度较小 if (column.$type === JVXETypes.datetime || column.$type === JVXETypes.userSelect || column.$type === JVXETypes.departSelect) { let width = column.width && column.width.endsWith('px')?Number.parseInt(column.width.substr(0,column.width.length-2)):0; if(width <= 190){ column.width = '190px' } } if (column.$type === JVXETypes.date) { let width = column.width && column.width.endsWith('px')?Number.parseInt(column.width.substr(0,column.width.length-2)):0; if(width <= 135){ column.width = '135px' } } // update--end--autor:lvdandan-----date:20201211------for:JT-118 【online】 日期、时间控件长度较小 }) return this._innerColumns }, // vxe 最终 editRules vxeEditRules() { return Object.assign({}, this.editRules, this._innerEditRules) }, // vxe 最终 props vxeProps() { let expandConfig = Object.assign({}, this.defaultVxeProps['expand-config'], this.expandConfig) return Object.assign({}, this.defaultVxeProps, { showFooter: this.statistics.has, }, this.$attrs, { loading: this.loading, columns: this.vxeColumns, editRules: this.vxeEditRules, // data: this.dataSource, height: this.height === 'auto' ? null : this.height, maxHeight: this.maxHeight, border: this.bordered, expandConfig: expandConfig, footerMethod: this.handleFooterMethod, // footerSpanMethod: this.handleFooterSpanMethod, }) }, // vxe 最终 events vxeEvents() { // 内置事件 let events = { 'scroll': this.handleVxeScroll, 'cell-click': this.handleCellClick, 'edit-closed': this.handleEditClosed, 'edit-actived': this.handleEditActived, 'radio-change': this.handleVxeRadioChange, 'checkbox-all': this.handleVxeCheckboxAll, 'checkbox-change': this.handleVxeCheckboxChange, } // 用户传递的事件,进行合并操作 Object.keys(this.$listeners).forEach(key => { let listen = this.$listeners[key] if (events.hasOwnProperty(key)) { if (Array.isArray(listen)) { listen.push(events[key]) } else { listen = [events[key], listen] } } events[key] = listen }) return events }, // 组件监听事件 cellListeners() { return { trigger: (name, event) => this.trigger(name, event), valueChange: event => this.trigger('valueChange', event), /** 当前行向上移一位 */ rowMoveUp: rowIndex => this.rowResort(rowIndex, rowIndex - 1), /** 当前行向下移一位 */ rowMoveDown: rowIndex => this.rowResort(rowIndex, rowIndex + 1), /** 在当前行下面插入一行 */ rowInsertDown: rowIndex => this.insertRows({}, rowIndex + 1), } }, }, watch: { dataSource: { // deep: true, immediate: true, async handler() { let vxe = await getRefPromise(this, 'vxe') this.dataSource.forEach((data, idx) => { // 开启了排序就自动计算排序值 if (this.dragSort) { this.$set(data, this.dragSortKey, idx + 1) } // 处理联动回显数据 if (this._innerLinkageConfig != null) { for (let configItem of this._innerLinkageConfig.values()) { this.autoSetLinkageOptionsByData(data, '', configItem, 0) } } }) // 阻断vue监听大数据,提高性能 vxe.loadData(this.dataSource) // TODO 解析disabledRows // let disabled = false // // let disabledRowIds = (this.disabledRowIds || []) // // 解析disabledRows // Object.keys(this.disabledRows).forEach(disabledColKey => { // // 判断是否有该属性 // if (data.hasOwnProperty(disabledColKey)) { // if (disabled !== true) { // let temp = this.disabledRows[disabledColKey] // // 禁用规则可以是一个数组 // if (Array.isArray(temp)) { // disabled = temp.includes(data[disabledColKey]) // } else { // disabled = (temp === data[disabledColKey]) // } // if (disabled) { // disabledRowIds.push(row.id) // } // } // } // }) }, }, columns: { immediate: true, handler(columns) { //获取不需要显示列 this.loadExcludeCode() let _innerColumns = [] let _innerEditRules = {} let {rowNumber, rowSelection, rowExpand, dragSort} = this let expandColumn, seqColumn, checkboxColumn, radioColumn, dragSortColumn if (Array.isArray(columns)) { this.statistics.has = false this.statistics.sum = [] this.statistics.average = [] // 处理成vxe可识别的columns columns.forEach(column => { if(this.excludeCode.indexOf(column.key)>=0){ return false } let col = {...column} let {type} = col const enhanced = getEnhancedMixins(type) if (type === JVXETypes.rowNumber) { seqColumn = col } else if (type === JVXETypes.rowCheckbox) { checkboxColumn = col } else if (type === JVXETypes.rowRadio) { radioColumn = col } else if (type === JVXETypes.rowExpand) { expandColumn = col } else if (type === JVXETypes.rowDragSort) { dragSortColumn = col } else { col.field = col.key // 防止和vxeTable自带的type起冲突 col.$type = col.type delete col.type let renderName = 'cellRender', renderOptions = {name: JVXETypes._prefix + type} if (type) { // hidden 是特殊的组件 if (type === JVXETypes.hidden) { col.visible = false } else if (enhanced.switches.editRender) { renderName = 'editRender' renderOptions.type = (enhanced.switches.visible || this.alwaysEdit) ? 'visible' : 'default' } } else { renderOptions.name = JVXETypes._prefix + JVXETypes.normal } col[renderName] = renderOptions // 处理字典 if (col.dictCode) { this._loadDictConcatToOptions(col) } // 处理校验 if (col.validateRules) { let rules = [] if (Array.isArray(col.validateRules)) { for (let rule of col.validateRules) { let replace = { message: replaceProps(col, rule.message) } if (rule.unique || rule.pattern === 'only') { // 唯一校验器 rule.validator = uniqueValidator.bind(this) } else if (rule.pattern) { // 非空 if (rule.pattern === fooPatterns[0].value) { rule.required = true delete rule.pattern } else { // 兼容Online表单的特殊规则 for (let foo of fooPatterns) { if (foo.value === rule.pattern) { rule.pattern = foo.pattern break } } } } else if (typeof rule.handler === 'function') { // 自定义函数校验 rule.validator = handlerConvertToValidator.bind(this) } rules.push(Object.assign({}, rule, replace)) } } _innerEditRules[col.key] = rules } // 处理统计列 // sum = 求和、average = 平均值 if (Array.isArray(col.statistics)) { this.statistics.has = true col.statistics.forEach(item => { let arr = this.statistics[item.toLowerCase()] if (Array.isArray(arr)) { pushIfNotExist(arr, col.key) } }) } _innerColumns.push(col) } }) } // 判断是否开启了序号 if (rowNumber) { let col = {type: 'seq', title: '#', width: 60, fixed: 'left', align: 'center'} if (seqColumn) { col = Object.assign(col, seqColumn, {type: 'seq'}) } _innerColumns.unshift(col) } // 判断是否开启了可选择行 if (rowSelection) { let width = 40 if (this.statistics.has && !rowExpand && !dragSort) { width = 60 } let col = {type: this.rowSelectionType, width, fixed: 'left', align: 'center'} // radio if (this.rowSelectionType === 'radio' && radioColumn) { col = Object.assign(col, radioColumn, {type: 'radio'}) } // checkbox if (this.rowSelectionType === 'checkbox' && checkboxColumn) { col = Object.assign(col, checkboxColumn, {type: 'checkbox'}) } _innerColumns.unshift(col) } // 是否可展开行 if (rowExpand) { let width = 40 if (this.statistics.has && !dragSort) { width = 60 } let col = {type: 'expand', title: '', width, fixed: 'left', align: 'center', slots: {content: 'expandContent'}} if (expandColumn) { col = Object.assign(col, expandColumn, {type: 'expand'}) } _innerColumns.unshift(col) } // 是否可拖动排序 if (dragSort) { let width = 40 if (this.statistics.has) { width = 60 } let col = {type: JVXETypes.rowDragSort, title: '', width, fixed: 'left', align: 'center', cellRender: {name: JVXETypes._prefix + JVXETypes.rowDragSort}} if (dragSortColumn) { col = Object.assign(col, dragSortColumn, {type: JVXETypes.rowDragSort}) } _innerColumns.unshift(col) } this._innerColumns = _innerColumns this._innerEditRules = _innerEditRules } }, // watch linkageConfig // 整理多级联动配置 linkageConfig: { immediate: true, handler() { if (Array.isArray(this.linkageConfig) && this.linkageConfig.length > 0) { // 获取联动的key顺序 let getLcKeys = (key, arr) => { let col = this._innerColumns.find(col => col.key === key) if (col) { arr.push(col.key) if (col.linkageKey) { return getLcKeys(col.linkageKey, arr) } } return arr } let configMap = new Map() this.linkageConfig.forEach(lc => { let keys = getLcKeys(lc.key, []) // 多个key共享一个,引用地址 let configItem = { ...lc, keys, optionsMap: new Map() } keys.forEach(k => configMap.set(k, configItem)) }) this._innerLinkageConfig = configMap } else { this._innerLinkageConfig = null } } }, }, created() { }, mounted() { this.handleTabsChange() }, methods: { /** * 自动判断父级是否是 组件,然后添加事件监听,自动重置表格 */ handleTabsChange() { // 获取父级 const tabs = getVmParentByName(this, 'ATabs') const tabPane = getVmParentByName(this, 'ATabPane') if (tabs && tabPane) { // 用户自定义的 key const currentKey = tabPane.$vnode.key // 添加 activeKey 监听 const unwatch = tabs.$children[0].$watch('$data._activeKey', async (key) => { // 切换到自己时重新计算 if (currentKey === key) { await this.$nextTick() await this.refreshScroll() await this.recalculate() } }) // 当前实例销毁时取消监听 this.$on('beforeDestroy', () => unwatch()) } }, handleVxeScroll(event) { let {$refs, scroll} = this // 记录滚动条的位置 scroll.top = event.scrollTop scroll.left = event.scrollLeft $refs.subPopover ? $refs.subPopover.close() : null this.scrolling = true this.closeScrolling() }, // 当手动勾选单选时触发的事件 handleVxeRadioChange(event) { let row = event.$table.getRadioRecord() this.selectedRows = row ? [row] : [] this.handleSelectChange('radio', this.selectedRows, event) }, // 当手动勾选全选时触发的事件 handleVxeCheckboxAll(event) { this.selectedRows = event.$table.getCheckboxRecords() this.handleSelectChange('checkbox-all', this.selectedRows, event) }, // 当手动勾选并且值发生改变时触发的事件 handleVxeCheckboxChange(event) { this.selectedRows = event.$table.getCheckboxRecords() this.handleSelectChange('checkbox', this.selectedRows, event) }, // 行选择change事件 handleSelectChange(type, selectedRows, $event) { let action if (type === 'radio') { action = 'selected' } else if (type === 'checkbox') { action = selectedRows.includes($event.row) ? 'selected' : 'unselected' } else { action = 'selected-all' } this.selectedRowIds = selectedRows.map(row => row.id) this.trigger('selectRowChange', { type: type, action: action, $event: $event, row: $event.row, selectedRows: this.selectedRows, selectedRowIds: this.selectedRowIds }) }, // 点击单元格时触发的事件 handleCellClick(event) { let {row, column, $event, $table} = event let {$refs} = this // 点击了可编辑的 if (column.editRender) { $refs.subPopover ? $refs.subPopover.close() : null return } // 显示详细信息 if (column.own.showDetails) { // 两个如果同时存在的话会出现死循环 $refs.subPopover ? $refs.subPopover.close() : null $refs.detailsModal ? $refs.detailsModal.open(event) : null } else if ($refs.subPopover) { $refs.subPopover.toggle(event) } else if (this.clickSelectRow) { let className = $event.target.className || '' className = typeof className === 'string' ? className : className.toString() // 点击的是expand,不做处理 if (className.includes('vxe-table--expand-btn')) { return } // 点击的是checkbox,不做处理 if (className.includes('vxe-checkbox--icon') || className.includes('vxe-cell--checkbox')) { return } // 点击的是radio,不做处理 if (className.includes('vxe-radio--icon') || className.includes('vxe-cell--radio')) { return } if (this.rowSelectionType === 'radio') { $table.setRadioRow(row) this.handleVxeRadioChange(event) } else { $table.toggleCheckboxRow(row) this.handleVxeCheckboxChange(event) } } }, // 单元格编辑状态下被关闭时会触发该事件 handleEditClosed({column}) { // 执行增强 getEnhancedMixins(column.own.$type, 'aopEvents').editClosed.apply(this, arguments) }, // 单元格被激活编辑时会触发该事件 handleEditActived({column}) { // 执行增强 getEnhancedMixins(column.own.$type, 'aopEvents').editActived.apply(this, arguments) }, /** 表尾数据处理方法,用于显示统计信息 */ handleFooterMethod({columns, data}) { const {statistics} = this let footers = [] if (statistics.has) { if (statistics.sum.length > 0) { footers.push(this.getFooterStatisticsMap({ columns: columns, title: '合计', checks: statistics.sum, method: (column) => XEUtils.sum(data, column.property) })) } if (statistics.average.length > 0) { footers.push(this.getFooterStatisticsMap({ columns: columns, title: '平均', checks: statistics.average, method: (column) => XEUtils.mean(data, column.property) })) } } return footers }, getFooterStatisticsMap({columns, title, checks, method}) { return columns.map((column, columnIndex) => { if (columnIndex === 0) { return title } if (checks.includes(column.property)) { return method(column, columnIndex) } return null }) }, /** 表尾单元格合并方法 */ handleFooterSpanMethod(event) { if (event.columnIndex === 0) { return {colspan: 2} } }, /*--- 外部可调用接口方法 ---*/ /** * 重置滚动条Top位置 * @param top 新top位置,留空则滚动到上次记录的位置,用于解决切换tab选项卡时导致白屏以及自动将滚动条滚动到顶部的问题 */ resetScrollTop(top) { this.scrollTo(null, (top == null || top === '') ? this.scroll.top : top) }, /** * 加载新数据,和 loadData 不同的是,用该方法加载的数据都是相当于点新增按钮新增的数据。 * 适用于不是数据库里查出来的没有id的临时数据 * @param dataSource */ async loadNewData(dataSource) { if (Array.isArray(dataSource)) { let {xTable} = this.$refs.vxe.$refs // issues/2784 // 先清空所有数据 xTable.loadData([]) dataSource.forEach((data, idx) => { // 开启了排序就自动计算排序值 if (this.dragSort) { this.$set(data, this.dragSortKey, idx + 1) } // 处理联动回显数据 if (this._innerLinkageConfig != null) { for (let configItem of this._innerLinkageConfig.values()) { this.autoSetLinkageOptionsByData(data, '', configItem, 0) } } }) // 再新增 return xTable.insertAt(dataSource) } return [] }, // 校验table,失败返回errMap,成功返回null async validateTable() { const errMap = await this.validate().catch(errMap => errMap) return errMap ? errMap : null }, // 完整校验 async fullValidateTable() { const errMap = await this.fullValidate().catch(errMap => errMap) return errMap ? errMap : null }, /** 设置某行某列的值 */ setValues(values) { if (!Array.isArray(values)) { console.warn(`JVxeTable.setValues:必须传递数组`) return } values.forEach((item, idx) => { let {rowKey, values: record} = item let {row} = this.getIfRowById(rowKey) if (!row) { return } Object.keys(record).forEach(colKey => { let column = this.getColumnByKey(colKey) if (column) { let oldValue = row[colKey] let newValue = record[colKey] if (newValue !== oldValue) { this.$set(row, colKey, newValue) // 触发 valueChange 事件 this.trigger('valueChange', { type: column.own.$type, value: newValue, oldValue: oldValue, col: column.own, column: column, isSetValues: true, }) } } else { console.warn(`JVxeTable.setValues:没有找到key为"${colKey}"的列`) } }) }) }, /** 获取所有的数据,包括values、deleteIds */ getAll() { return { tableData: this.getTableData(), deleteData: this.getDeleteData() } }, /** 获取表格表单里的值 */ getValues(callback, rowIds) { let tableData = this.getTableData({rowIds: rowIds}) callback('', tableData) }, /** 获取表格数据 */ getTableData(options = {}) { let {rowIds} = options let tableData // 仅查询指定id的行 if (Array.isArray(rowIds) && rowIds.length > 0) { tableData = [] rowIds.forEach(rowId => { let {row} = this.getIfRowById(rowId) if (row) { tableData.push(row) } }) } else { // 查询所有行 tableData = this.$refs.vxe.getTableData().fullData } return this.filterNewRows(tableData, false) }, /** 仅获取新增的数据 */ getNewData() { let newData = cloneObject(this.$refs.vxe.getInsertRecords()) newData.forEach(row => delete row.id) return newData }, /** 仅获取新增的数据,带有id */ getNewDataWithId() { let newData = cloneObject(this.$refs.vxe.getInsertRecords()) return newData }, /** 根据ID获取行,新增的行也能查出来 */ getIfRowById(id) { let row = this.getRowById(id), isNew = false if (!row) { row = this.getNewRowById(id) if (!row) { console.warn(`JVxeTable.getIfRowById:没有找到id为"${id}"的行`) return {row: null} } isNew = true } return {row, isNew} }, /** 通过临时ID获取新增的行 */ getNewRowById(id) { let records = this.getInsertRecords() for (let record of records) { if (record.id === id) { return record } } return null }, /** 仅获取被删除的数据(新增又被删除的数据不会被获取到) */ getDeleteData() { return cloneObject(this.$refs.vxe.getRemoveRecords()) }, /** * 添加一行或多行 * * @param rows * @param isOnlJs 是否是onlineJS增强触发的 * @return */ async addRows(rows = {}, isOnlJs) { return this._addOrInsert(rows, -1, 'added', isOnlJs) }, /** * 添加一行或多行 * * @param rows * @param index 添加下标,数字,必填 * @return */ async insertRows(rows, index) { if (typeof index !== 'number' || index < 0) { console.warn(`【JVXETable】insertRows:index必须传递数字,且大于-1`) return } return this._addOrInsert(rows, index, 'inserted') }, /** * 添加一行或多行临时数据,不会填充默认值,传什么就添加进去什么 * @param rows * @param options 选项 * @param options.setActive 是否激活最后一行的编辑模式 */ async pushRows(rows = {}, options = {}) { let {xTable} = this.$refs.vxe.$refs let {setActive, index} = options setActive = setActive == null ? false : !!setActive index = index == null ? -1 : index index = index === -1 ? index : xTable.tableFullData[index] // 插入行 let result = await xTable.insertAt(rows, index) if (setActive) { // 激活最后一行的编辑模式 xTable.setActiveRow(result.rows[result.rows.length - 1]) } await this._recalcSortNumber() return result }, /** 清空选择行 */ clearSelection() { let event = {$table: this.$refs.vxe, target: this} if (this.rowSelectionType === JVXETypes.rowRadio) { this.$refs.vxe.clearRadioRow() this.handleVxeRadioChange(event) } else { this.$refs.vxe.clearCheckboxRow() this.handleVxeCheckboxChange(event) } }, /** 删除一行或多行数据 */ async removeRows(rows) { const res = await this._remove(rows) await this._recalcSortNumber() return res }, /** 根据id删除一行或多行 */ removeRowsById(rowId) { let rowIds if (Array.isArray(rowId)) { rowIds = rowId } else { rowIds = [rowId] } let rows = rowIds.map((id) => { let {row} = this.getIfRowById(id) if (!row) { return } if (row) { return row } else { console.warn(`【JVXETable】removeRowsById:${id}不存在`) return null } }).filter((row) => row != null) return this.removeRows(rows) }, getColumnByKey() { return this.$refs.vxe.getColumnByField.apply(this.$refs.vxe, arguments) }, /* --- 辅助方法 ---*/ // 触发事件 trigger(name, event = {}) { event.$target = this event.$table = this.$refs.vxe //online增强参数兼容 event.target = this this.$emit(name, event) }, /** 【多级联动】获取同级联动下拉选项 */ getLinkageOptionsSibling(row, col, config, request) { // 如果当前列不是顶级列 let key = '' if (col.key !== config.key) { // 就找出联动上级列 let idx = config.keys.findIndex(k => col.key === k) let parentKey = config.keys[idx - 1] key = row[parentKey] // 如果联动上级列没有选择数据,就直接返回空数组 if (key === '' || key == null) { return [] } } else { key = 'root' } let options = config.optionsMap.get(key) if (!Array.isArray(options)) { if (request) { let parent = key === 'root' ? '' : key return this.getLinkageOptionsAsync(config, parent) } else { options = [] } } return options }, /** 【多级联动】获取联动下拉选项(异步) */ getLinkageOptionsAsync(config, parent) { return new Promise(resolve => { let key = parent ? parent : 'root' let options if (config.optionsMap.has(key)) { options = config.optionsMap.get(key) if (options instanceof Promise) { options.then(opt => { config.optionsMap.set(key, opt) resolve(opt) }) } else { resolve(options) } } else if (typeof config.requestData === 'function') { // 调用requestData方法,通过传入parent来获取子级 let promise = config.requestData(parent) config.optionsMap.set(key, promise) promise.then(opt => { config.optionsMap.set(key, opt) resolve(opt) }) } else { resolve([]) } }) }, // 【多级联动】 用于回显数据,自动填充 optionsMap autoSetLinkageOptionsByData(data, parent, config, level) { if (level === 0) { this.getLinkageOptionsAsync(config, '') } else { this.getLinkageOptionsAsync(config, parent) } if (config.keys.length - 1 > level) { let value = data[config.keys[level]] if (value) { this.autoSetLinkageOptionsByData(data, value, config, level + 1) } } }, // 【多级联动】联动组件change时,清空下级组件 linkageSelectChange(row, col, config, value) { if (col.linkageKey) { this.getLinkageOptionsAsync(config, value) let idx = config.keys.findIndex(k => k === col.key) let values = {} for (let i = idx; i < config.keys.length; i++) { values[config.keys[i]] = '' } // 清空后几列的数据 this.setValues([{rowKey: row.id, values}]) } }, /** 加载数据字典并合并到 options */ _loadDictConcatToOptions(column) { initDictOptions(column.dictCode).then((res) => { if (res.success) { let newOptions = (column.options || [])// .concat(res.result) res.result.forEach(item => { // 过滤重复数据 for (let option of newOptions) if (option.value === item.value) return newOptions.push(item) }) this.$set(column, 'options', newOptions) } else { console.group(`JVxeTable 查询字典(${column.dictCode})发生异常`) console.warn(res.message) console.groupEnd() } }) }, //options自定义赋值 刷新 virtualRefresh(){ this.scrolling = true this.closeScrolling() }, // 设置 this.scrolling 防抖模式 closeScrolling: simpleDebounce(function () { this.scrolling = false }, 100), /** * 过滤添加的行 * @param rows 要筛选的行数据 * @param remove true = 删除新增,false=只删除id * @param handler function */ filterNewRows(rows, remove = true, handler) { let insertRecords = this.$refs.vxe.getInsertRecords() let records = [] for (let row of rows) { let item = cloneObject(row) if (insertRecords.includes(row)) { handler ? handler({item, row, insertRecords}) : null if (remove) { continue } delete item.id } records.push(item) } return records }, // 删除选中的数据 async removeSelection() { let res = await this._remove(this.selectedRows) this.clearSelection() await this._recalcSortNumber() return res }, /** * 【删除指定行数据】(重写vxeTable的内部方法,添加了从keepSource中删除) * 如果传 row 则删除一行 * 如果传 rows 则删除多行 * 如果为空则删除所有 */ _remove(rows) { const xTable = this.$refs.vxe.$refs.xTable const {afterFullData, tableFullData, tableSourceData, editStore, treeConfig, checkboxOpts, selection, isInsertByRow, scrollYLoad} = xTable const {actived, removeList, insertList} = editStore const {checkField: property} = checkboxOpts let rest = [] const nowData = afterFullData if (treeConfig) { throw new Error(UtilTools.getLog('vxe.error.noTree', ['remove'])) } if (!rows) { rows = tableFullData } else if (!XEUtils.isArray(rows)) { rows = [rows] } // 如果是新增,则保存记录 rows.forEach(row => { if (!isInsertByRow(row)) { removeList.push(row) } }) // 如果绑定了多选属性,则更新状态 if (!property) { XEUtils.remove(selection, row => rows.indexOf(row) > -1) } // 从数据源中移除 if (tableFullData === rows) { rows = rest = tableFullData.slice(0) tableFullData.length = 0 nowData.length = 0 } else { rest = XEUtils.remove(tableFullData, row => rows.indexOf(row) > -1) XEUtils.remove(nowData, row => rows.indexOf(row) > -1) } // 【从keepSource中删除】 if (xTable.keepSource) { let rowIdSet = new Set(rows.map(row => row.id)) XEUtils.remove(tableSourceData, row => rowIdSet.has(row.id)) } // 如果当前行被激活编辑,则清除激活状态 if (actived.row && rows.indexOf(actived.row) > -1) { xTable.clearActived() } // 从新增中移除已删除的数据 XEUtils.remove(insertList, row => rows.indexOf(row) > -1) xTable.handleTableData() xTable.updateFooter() xTable.updateCache() xTable.checkSelectionStatus() if (scrollYLoad) { xTable.updateScrollYSpace() } return xTable.$nextTick().then(() => { xTable.recalculate() return {row: rest.length ? rest[rest.length - 1] : null, rows: rest} }) }, /** 行重新排序 */ async rowResort(oldIndex, newIndex) { const xTable = this.$refs.vxe.$refs.xTable window.xTable = xTable const sort = (array) => { // 存储旧数据,并删除旧项目 let row = array.splice(oldIndex, 1)[0] // 向新项目里添加旧数据 array.splice(newIndex, 0, row) } sort(xTable.tableFullData) if (xTable.keepSource) { sort(xTable.tableSourceData) } await this.$nextTick() await this._recalcSortNumber() }, /** 重新计算排序字段的数值 */ async _recalcSortNumber() { const xTable = this.$refs.vxe.$refs.xTable if (this.dragSort) { xTable.tableFullData.forEach((data, idx) => data[this.dragSortKey] = (idx + 1)) } await xTable.updateCache(true) return await xTable.updateData() }, async _addOrInsert(rows = {}, index, triggerName, isOnlJs) { let {xTable} = this.$refs.vxe.$refs let records if (Array.isArray(rows)) { records = rows } else { records = [rows] } // 遍历添加默认值 records.forEach(record => this._createRow(record)) let result = await this.pushRows(records, {index: index, setActive: true}) // 遍历插入的行 // update--begin--autor:lvdandan-----date:20201117------for:LOWCOD-987 【新行编辑】js增强附表内置方法调用问题 #1819 // online js增强时以传过来值为准,不再赋默认值 if (isOnlJs != true) { for (let i = 0; i < result.rows.length; i++) { let row = result.rows[i] this.trigger(triggerName, { row: row, $table: xTable, target: this, }) } } // update--end--autor:lvdandan-----date:20201117------for:LOWCOD-987 【新行编辑】js增强附表内置方法调用问题 #1819 return result }, // 创建新行,自动添加默认值 _createRow(record = {}) { let {xTable} = this.$refs.vxe.$refs // 添加默认值 xTable.tableFullColumn.forEach(column => { let col = column.own if (col.key && (record[col.key] == null || record[col.key] === '')) { // 设置默认值 let createValue = getEnhancedMixins(col.$type || col.type, 'createValue') record[col.key] = createValue({row: record, column, $table: xTable}) } // update-begin--author:sunjianlei---date:20210819------for: 处理联动列,联动列只能作用于 select 组件 if (col.$type === JVXETypes.select && this._innerLinkageConfig != null) { // 判断当前列是否是联动列 if (this._innerLinkageConfig.has(col.key)) { let configItem = this._innerLinkageConfig.get(col.key) this.getLinkageOptionsAsync(configItem, '') } } // update-end--author:sunjianlei---date:20210819------for: 处理联动列,联动列只能作用于 select 组件 }) return record }, /*--- 渲染函数 ---*/ // 渲染 vxe renderVxeGrid(h) { return h('vxe-grid', { ref: 'vxe', class: ['j-vxe-table'], props: this.vxeProps, on: this.vxeEvents, // 作用域插槽的格式为 scopedSlots: this.$scopedSlots, }) }, // 渲染工具栏 renderToolbar(h) { if (this.toolbar) { return h('j-vxe-toolbar', { props: { toolbarConfig: this.toolbarConfig, excludeCode: this.excludeCode, size: this.size, disabled: this.disabled, disabledRows: this.disabledRows, selectedRowIds: this.selectedRowIds, }, on: { // 新增事件 add: () => this.addRows(), // 保存事件 save: () => this.trigger('save', { $table: this.$refs.vxe, target: this, }), // 删除事件 remove: () => { let $table = this.$refs.vxe let deleteRows = this.filterNewRows(this.selectedRows) // 触发删除事件 if (deleteRows.length > 0) { let removeEvent = {deleteRows, $table, target: this} if (this.asyncRemove) { // 确认删除,只有调用这个方法才会真删除 removeEvent.confirmRemove = () => this.removeSelection() } else { this.removeSelection() } this.trigger('remove', removeEvent) } else { this.removeSelection() } }, // 清除选择事件 clearSelection: this.clearSelection }, scopedSlots: { toolbarPrefix: this.$scopedSlots.toolbarPrefix, toolbarSuffix: this.$scopedSlots.toolbarSuffix, }, }) } return null }, // 渲染 toolbarAfter 插槽 renderToolbarAfterSlot() { if (this.$scopedSlots['toolbarAfter']) { return this.$scopedSlots['toolbarAfter']() } return null }, // 渲染点击时弹出的子表 renderSubPopover(h) { if (this.clickRowShowSubForm && this.$scopedSlots.subForm) { return h('j-vxe-sub-popover', { ref: 'subPopover', scopedSlots: { subForm: this.$scopedSlots.subForm, } }) } return null }, // 渲染点击时弹出的详细信息 renderDetailsModal(h) { if (this.clickRowShowMainForm && this.$scopedSlots.mainForm) { return h('j-vxe-details-modal', { ref: 'detailsModal', scopedSlots: { subForm: this.clickRowShowSubForm ? this.$scopedSlots.subForm : null, mainForm: this.$scopedSlots.mainForm } }) } }, // 渲染分页器 renderPagination(h) { if (this.pagination && Object.keys(this.pagination).length > 0) { return h('j-vxe-pagination', { props: { size: this.size, disabled: this.disabled, pagination: this.pagination }, on: { change: (e) => this.trigger('pageChange', e) }, }) } return null }, loadExcludeCode(){ if(!this.authPre || this.authPre.length==0){ this.excludeCode = [] }else{ let pre = this.authPre if(!pre.endsWith(':')){ pre += ':' } this.excludeCode = getNoAuthCols(pre) } } }, render(h) { return h('div', { class: ['j-vxe-table-box', `size--${this.size}`] }, [ this.renderSubPopover(h), this.renderDetailsModal(h), this.renderToolbar(h), this.renderToolbarAfterSlot(), this.renderVxeGrid(h), this.renderPagination(h), ]) }, beforeDestroy() { this.$emit('beforeDestroy') } } // 兼容 online 的规则 const fooPatterns = [ {title: '非空', value: '*', pattern: /^.+$/}, {title: '6到16位数字', value: 'n6-16', pattern: /^\d{6,16}$/}, {title: '6到16位任意字符', value: '*6-16', pattern: /^.{6,16}$/}, {title: '6到18位字母', value: 's6-18', pattern: /^[a-z|A-Z]{6,18}$/}, {title: '网址', value: 'url', pattern: /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/}, {title: '电子邮件', value: 'e', pattern: /^([\w]+\.*)([\w]+)@[\w]+\.\w{3}(\.\w{2}|)$/}, {title: '手机号码', value: 'm', pattern: /^1[3456789]\d{9}$/}, {title: '邮政编码', value: 'p', pattern: /^[1-9]\d{5}$/}, {title: '字母', value: 's', pattern: /^[A-Z|a-z]+$/}, {title: '数字', value: 'n', pattern: /^-?\d+(\.?\d+|\d?)$/}, {title: '整数', value: 'z', pattern: /^-?\d+$/}, {title: '金额', value: 'money', pattern: /^(([1-9][0-9]*)|([0]\.\d{0,2}|[1-9][0-9]*\.\d{0,2}))$/}, ] /** 旧版handler转为新版Validator */ function handlerConvertToValidator(event) { const {column, rule} = event return new Promise((resolve, reject) => { rule.handler(event, (flag, msg) => { let message = rule.message if (typeof msg === 'string') { message = replaceProps(column.own, msg) } if (flag == null) { resolve(message) } else if (!!flag) { resolve(message) } else { reject(new Error(message)) } }, this, event) }) } /** 唯一校验器 */ function uniqueValidator(event) { const {cellValue, column, rule} = event let tableData = this.getTableData() let findCount = 0 for (let rowData of tableData) { if (rowData[column.own.key] === cellValue) { if (++findCount >= 2) { return Promise.reject(new Error(rule.message)) } } } return Promise.resolve() }