diff --git a/README.md b/README.md index 5a63a7210..976f6831b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ## 功能点 * [x] 基础表格 -* [x] 高级表格 +* [x] 配置式表格 * [x] 基础表单 * [x] 配置式表单 * [x] 斑马线条纹 diff --git a/helper/vetur/tags.json b/helper/vetur/tags.json index fc7d39578..faba39c65 100644 --- a/helper/vetur/tags.json +++ b/helper/vetur/tags.json @@ -242,7 +242,7 @@ "proxy-config", "zoom-config" ], - "description": "高级表格" + "description": "配置式表格" }, "vxe-toolbar": { "attributes": [ diff --git a/package.json b/package.json index 82ca40c76..e3a8c5789 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vxe-table", - "version": "3.7.7-beta.3", + "version": "3.7.7-beta.4", "description": "一个基于 vue 的 PC 端表单/表格组件,支持增删改查、虚拟列表、虚拟树、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、弹窗、自定义模板、渲染器、JSON 配置式...", "scripts": { "update": "npm install --legacy-peer-deps", diff --git a/packages/edit/src/mixin.js b/packages/edit/src/mixin.js index a2882ddb4..460a61f91 100644 --- a/packages/edit/src/mixin.js +++ b/packages/edit/src/mixin.js @@ -1,5 +1,6 @@ import XEUtils from 'xe-utils' import VXETable from '../../v-x-e-table' +import GlobalConfig from '../../v-x-e-table/src/conf' import UtilTools, { isEnableConf } from '../../tools/utils' import { getRowid } from '../../table/src/util' import DomTools, { browse } from '../../tools/dom' @@ -370,7 +371,8 @@ export default { return { insertRecords: this.getInsertRecords(), removeRecords: this.getRemoveRecords(), - updateRecords: this.getUpdateRecords() + updateRecords: this.getUpdateRecords(), + pendingRecords: this.getPendingRecords() } }, /** @@ -425,7 +427,7 @@ export default { const { editRender } = column const cell = params.cell = (params.cell || this.getCell(row, column)) const beforeEditMethod = editOpts.beforeEditMethod || editOpts.activeMethod - if (isEnableConf(editConfig) && isEnableConf(editRender) && cell) { + if (isEnableConf(editConfig) && isEnableConf(editRender) && !this.hasPendingByRow(row) && cell) { if (actived.row !== row || (mode === 'cell' ? actived.column !== column : false)) { // 判断是否禁用编辑 let type = 'edit-disabled' @@ -439,7 +441,7 @@ export default { if (actived.column) { this.clearActived(evnt) } - type = 'edit-actived' + type = 'edit-activated' column.renderHeight = cell.offsetHeight actived.args = params actived.row = row @@ -461,6 +463,18 @@ export default { columnIndex: this.getColumnIndex(column), $columnIndex: this.getVMColumnIndex(column) }, evnt) + + // v4已废弃 + if (type === 'edit-activated') { + this.emitEvent('edit-actived', { + row, + rowIndex: this.getRowIndex(row), + $rowIndex: this.getVMRowIndex(row), + column, + columnIndex: this.getColumnIndex(column), + $columnIndex: this.getVMColumnIndex(column) + }, evnt) + } } else { const { column: oldColumn } = actived if (mouseConfig) { @@ -542,7 +556,12 @@ export default { $columnIndex: this.getVMColumnIndex(column) }, evnt) } - return VXETable._valid ? this.clearValidate() : this.$nextTick() + if (GlobalConfig.cellVaildMode === 'obsolete') { + if (this.clearValidate) { + return this.clearValidate() + } + } + return this.$nextTick() }, _getActiveRecord () { // if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { @@ -555,7 +574,7 @@ export default { const { $el, editStore, afterFullData } = this const { actived } = editStore const { args, row } = actived - if (args && this.findRowIndexOf(afterFullData, row) > -1 && $el.querySelectorAll('.vxe-body--column.col--actived').length) { + if (args && this.findRowIndexOf(afterFullData, row) > -1 && $el.querySelectorAll('.vxe-body--column.col--active').length) { return Object.assign({}, args) } return null diff --git a/packages/grid/src/grid.js b/packages/grid/src/grid.js index adb87fe23..1ccf1060a 100644 --- a/packages/grid/src/grid.js +++ b/packages/grid/src/grid.js @@ -167,7 +167,6 @@ export default { tableLoading: false, isZMax: false, tableData: [], - pendingRecords: [], filterData: [], formData: {}, sortData: [], @@ -221,13 +220,12 @@ export default { if (proxyConfig) { tableProps.loading = loading || tableLoading tableProps.data = tableData - tableProps.rowClassName = this.handleRowClassName if (proxyOpts.seq && isEnableConf(pagerConfig)) { tableProps.seqConfig = Object.assign({}, seqConfig, { startIndex: (tablePage.currentPage - 1) * tablePage.pageSize }) } } if (editConfig) { - tableProps.editConfig = Object.assign({}, editConfig, { beforeEditMethod: this.handleBeforeEditMethod }) + tableProps.editConfig = Object.assign({}, editConfig) } return tableProps } @@ -391,23 +389,6 @@ export default { const parentPaddingSize = isZMax || height !== 'auto' ? 0 : getPaddingTopBottomSize($el.parentNode) return parentPaddingSize + getPaddingTopBottomSize($el) + getOffsetHeight(formWrapper) + getOffsetHeight(toolbarWrapper) + getOffsetHeight(topWrapper) + getOffsetHeight(bottomWrapper) + getOffsetHeight(pagerWrapper) }, - handleRowClassName (params) { - const rowClassName = this.rowClassName - const clss = [] - if (this.pendingRecords.some(item => item === params.row)) { - clss.push('row--pending') - } - clss.push(rowClassName ? (XEUtils.isFunction(rowClassName) ? rowClassName(params) : rowClassName) : '') - return clss - }, - handleBeforeEditMethod (params) { - const { editConfig } = this - const beforeEditMethod = editConfig ? (editConfig.beforeEditMethod || editConfig.activeMethod) : null - if (this.pendingRecords.indexOf(params.row) === -1) { - return !beforeEditMethod || beforeEditMethod({ ...params, $grid: this }) - } - return false - }, initToolbar () { this.$nextTick(() => { const { xTable, xToolbar } = this.$refs @@ -486,8 +467,14 @@ export default { switch (code) { case 'insert': return this.insert() + case 'insert_edit': + return this.insert().then(({ row }) => this.setEditRow(row)) + + // 已废弃 case 'insert_actived': - return this.insert().then(({ row }) => this.setActiveRow(row)) + return this.insert().then(({ row }) => this.setEditRow(row)) + // 已废弃 + case 'mark_cancel': this.triggerPendingEvent(code) break @@ -545,7 +532,6 @@ export default { filterList = $xetable.getCheckedFilters() } else { if (isReload) { - this.pendingRecords = [] $xetable.clearAll() } else { sortList = $xetable.getSortColumns() @@ -619,7 +605,7 @@ export default { return Promise.resolve((beforeDelete || ajaxMethods)(...applyArgs)) .then(rest => { this.tableLoading = false - this.pendingRecords = this.pendingRecords.filter(row => removeRecords.indexOf(row) === -1) + $xetable.setPendingRow(removeRecords, false) if (isMsg) { // 检测弹窗模块 if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { @@ -671,7 +657,7 @@ export default { case 'save': { const ajaxMethods = ajax.save if (ajaxMethods) { - const body = Object.assign({ pendingRecords: this.pendingRecords }, this.getRecordset()) + const body = this.getRecordset() const { insertRecords, removeRecords, updateRecords, pendingRecords } = body const applyArgs = [{ $grid: this, code, button, body, form: formData, options: ajaxMethods }].concat(args) // 排除掉新增且标记为删除的数据 @@ -697,7 +683,7 @@ export default { return Promise.resolve((beforeSave || ajaxMethods)(...applyArgs)) .then(rest => { this.tableLoading = false - this.pendingRecords = [] + $xetable.clearPendingRow() if (isMsg) { // 检测弹窗模块 if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') { @@ -802,9 +788,6 @@ export default { }, { children: 'children' }) return XEUtils.isUndefined(itemIndex) ? itemList : itemList[itemIndex] }, - getPendingRecords () { - return this.pendingRecords - }, triggerToolbarCommitEvent (params, evnt) { const { code } = params return this.commitProxy(params, evnt).then((rest) => { @@ -822,23 +805,10 @@ export default { this.$emit('toolbar-tool-click', { code: tool.code, tool, $grid: this, $event: evnt }) }, triggerPendingEvent (code) { - const { pendingRecords, isMsg } = this + const { isMsg } = this const selectRecords = this.getCheckboxRecords() if (selectRecords.length) { - const plus = [] - const minus = [] - selectRecords.forEach(data => { - if (pendingRecords.some(item => data === item)) { - minus.push(data) - } else { - plus.push(data) - } - }) - if (minus.length) { - this.pendingRecords = pendingRecords.filter(item => minus.indexOf(item) === -1).concat(plus) - } else if (plus.length) { - this.pendingRecords = pendingRecords.concat(plus) - } + this.togglePendingRow(selectRecords) this.clearCheckboxRow() } else { if (isMsg) { @@ -946,7 +916,8 @@ export default { return this.$nextTick().then(() => this.recalculate(true)).then(() => this.isZMax) }, getProxyInfo () { - const { sortData, proxyConfig } = this + const { $refs, sortData, proxyConfig } = this + const $xetable = $refs.xTable if (proxyConfig) { return { data: this.tableData, @@ -955,7 +926,7 @@ export default { sort: sortData.length ? sortData[0] : {}, sorts: sortData, pager: this.tablePage, - pendingRecords: this.pendingRecords + pendingRecords: $xetable ? $xetable.getPendingRecords() : [] } } return null diff --git a/packages/table/src/body.js b/packages/table/src/body.js index 3e2d9a21a..42666fe26 100644 --- a/packages/table/src/body.js +++ b/packages/table/src/body.js @@ -82,10 +82,10 @@ function renderColumn (h, _vm, $xetable, seq, rowid, fixedType, rowLevel, row, r editRules, validOpts, editStore, - validStore, tooltipConfig, rowOpts, - columnOpts + columnOpts, + validErrorMaps } = $xetable const { type, cellRender, editRender, align, showOverflow, className, treeNode } = column const { actived } = editStore @@ -108,7 +108,7 @@ function renderColumn (h, _vm, $xetable, seq, rowid, fixedType, rowLevel, row, r let isDirty const tdOns = {} const cellAlign = align || allAlign - const hasValidError = validStore.row === row && validStore.column === column + const errorValidItem = validErrorMaps[`${rowid}:${column.id}`] const showValidTip = editRules && validOpts.showMessage && (validOpts.message === 'default' ? (height || tableData.length > 1) : validOpts.message === 'inline') const attrs = { colid: column.id } const bindMouseenter = tableListeners['cell-mouseenter'] @@ -244,17 +244,17 @@ function renderColumn (h, _vm, $xetable, seq, rowid, fixedType, rowLevel, row, r } }, column.renderCell(h, params)) ) - if (showValidTip && hasValidError) { + if (showValidTip && errorValidItem) { tdVNs.push( h('div', { - class: 'vxe-cell--valid', - style: validStore.rule && validStore.rule.maxWidth ? { - width: `${validStore.rule.maxWidth}px` + class: 'vxe-cell--valid-error-hint', + style: errorValidItem.rule && errorValidItem.rule.maxWidth ? { + width: `${errorValidItem.rule.maxWidth}px` } : null }, [ h('span', { - class: 'vxe-cell--valid-msg' - }, validStore.content) + class: 'vxe-cell--valid-error-msg' + }, errorValidItem.content) ]) ) } @@ -272,8 +272,8 @@ function renderColumn (h, _vm, $xetable, seq, rowid, fixedType, rowLevel, row, r 'col--ellipsis': hasEllipsis, 'fixed--hidden': fixedHiddenColumn, 'col--dirty': isDirty, - 'col--actived': editConfig && isEdit && (actived.row === row && (actived.column === column || editOpts.mode === 'row')), - 'col--valid-error': hasValidError, + 'col--active': editConfig && isEdit && (actived.row === row && (actived.column === column || editOpts.mode === 'row')), + 'col--valid-error': !!errorValidItem, 'col--current': currentColumn === column }, UtilTools.getClass(compCellClassName, params), @@ -310,7 +310,9 @@ function renderRows (h, _vm, $xetable, fixedType, tableData, tableColumn) { expandColumn, hasFixedColumn, fullAllDataRowIdData, - rowOpts + rowOpts, + pendingRowList, + pendingRowMaps } = $xetable const childrenField = treeOpts.children || treeOpts.childrenField const rows = [] @@ -366,7 +368,8 @@ function renderRows (h, _vm, $xetable, fixedType, tableData, tableColumn) { 'is--expand-tree': isExpandTree, 'row--new': isNewRow && (editOpts.showStatus || editOpts.showInsertStatus), 'row--radio': radioOpts.highlight && $xetable.selectRadioRow === row, - 'row--checked': checkboxOpts.highlight && $xetable.isCheckedByCheckboxRow(row) + 'row--checked': checkboxOpts.highlight && $xetable.isCheckedByCheckboxRow(row), + 'row--pending': pendingRowList.length && !!pendingRowMaps[rowid] }, rowClassName ? (XEUtils.isFunction(rowClassName) ? rowClassName(params) : rowClassName) : '' ], diff --git a/packages/table/src/methods.js b/packages/table/src/methods.js index f116ed494..d7b3c1934 100644 --- a/packages/table/src/methods.js +++ b/packages/table/src/methods.js @@ -2389,7 +2389,7 @@ const Methods = { * 全局按下事件处理 */ handleGlobalMousedownEvent (evnt) { - const { $el, $refs, $xegrid, $toolbar, mouseConfig, editStore, ctxMenuStore, editOpts, filterStore, getRowNode } = this + const { $el, $refs, $xegrid, $toolbar, mouseConfig, editStore, ctxMenuStore, editRules, editOpts, validOpts, filterStore, getRowNode } = this const { actived } = editStore const { ctxWrapper, filterWrapper, validTip } = $refs if (filterWrapper) { @@ -2466,8 +2466,13 @@ const Methods = { if (ctxMenuStore.visible && ctxWrapper && !getEventTargetNode(evnt, ctxWrapper.$el).flag) { this.closeMenu() } + const isActivated = getEventTargetNode(evnt, ($xegrid || this).$el).flag + // 如果存在校验,点击了表格之外则清除 + if (!isActivated && editRules && validOpts.autoClear) { + this.validErrorMaps = {} + } // 最后激活的表格 - this.isActivated = getEventTargetNode(evnt, ($xegrid || this).$el).flag + this.isActivated = isActivated }, /** * 窗口失焦事件处理 @@ -3944,6 +3949,74 @@ const Methods = { this.emitEvent('sort-change', params, evnt) } }, + setPendingRow (rows, status) { + const pendingMaps = { ...this.pendingRowMaps } + const pendingList = [...this.pendingRowList] + if (rows && !XEUtils.isArray(rows)) { + rows = [rows] + } + if (status) { + rows.forEach((row) => { + const rowid = getRowid(this, row) + if (rowid && !pendingMaps[rowid]) { + pendingList.push(row) + pendingMaps[rowid] = row + } + }) + } else { + rows.forEach((row) => { + const rowid = getRowid(this, row) + if (rowid && pendingMaps[rowid]) { + const pendingIndex = this.findRowIndexOf(pendingList, row) + if (pendingIndex > -1) { + pendingList.splice(pendingIndex, 1) + } + delete pendingMaps[rowid] + } + }) + } + this.pendingRowMaps = pendingMaps + this.pendingRowList = pendingList + return this.$nextTick() + }, + togglePendingRow (rows) { + const pendingMaps = { ...this.pendingRowMaps } + const pendingList = [...this.pendingRowList] + if (rows && !XEUtils.isArray(rows)) { + rows = [rows] + } + rows.forEach((row) => { + const rowid = getRowid(this, row) + if (rowid) { + if (pendingMaps[rowid]) { + const pendingIndex = this.findRowIndexOf(pendingList, row) + if (pendingIndex > -1) { + pendingList.splice(pendingIndex, 1) + } + delete pendingMaps[rowid] + } else { + pendingList.push(row) + pendingMaps[rowid] = row + } + } + }) + this.pendingRowMaps = pendingMaps + this.pendingRowList = pendingList + return this.$nextTick() + }, + getPendingRecords () { + return this.pendingRowList.slice(0) + }, + hasPendingByRow (row) { + const { pendingRowMaps } = this + const rowid = getRowid(this, row) + return !!pendingRowMaps[rowid] + }, + clearPendingRow () { + this.pendingRowMaps = {} + this.pendingRowList = [] + return this.$nextTick() + }, sort (sortConfs, sortOrder) { const { sortOpts } = this const { multiple, remote, orders } = sortOpts @@ -4951,7 +5024,7 @@ const Methods = { return this.$nextTick() }, /** - * 更新列状态 + * 更新列状态 updateStatus({ row, column }, cellValue) * 如果组件值 v-model 发生 change 时,调用改函数用于更新某一列编辑状态 * 如果单元格配置了校验规则,则会进行校验 */ @@ -4959,25 +5032,28 @@ const Methods = { const customVal = !XEUtils.isUndefined(cellValue) return this.$nextTick().then(() => { const { $refs, editRules, validStore } = this - if (slotParams && $refs.tableBody && editRules) { + const tableBody = $refs.tableBody + if (slotParams && tableBody && editRules) { const { row, column } = slotParams const type = 'change' - if (this.hasCellRules(type, row, column)) { - const cell = this.getCell(row, column) - if (cell) { - return this.validCellRules(type, row, column, cellValue) - .then(() => { - if (customVal && validStore.visible) { - setCellValue(row, column, cellValue) - } - this.clearValidate() - }) - .catch(({ rule }) => { - if (customVal) { - setCellValue(row, column, cellValue) - } - this.showValidTooltip({ rule, row, column, cell }) - }) + if (this.hasCellRules) { + if (this.hasCellRules(type, row, column)) { + const cell = this.getCell(row, column) + if (cell) { + return this.validCellRules(type, row, column, cellValue) + .then(() => { + if (customVal && validStore.visible) { + setCellValue(row, column, cellValue) + } + this.clearValidate(row, column) + }) + .catch(({ rule }) => { + if (customVal) { + setCellValue(row, column, cellValue) + } + this.showValidTooltip({ rule, row, column, cell }) + }) + } } } } diff --git a/packages/table/src/table.js b/packages/table/src/table.js index b18c2aaba..2dbac275a 100644 --- a/packages/table/src/table.js +++ b/packages/table/src/table.js @@ -343,6 +343,10 @@ export default { upDataFlag: 0, // 刷新列标识,当列的特定属性被改变时,触发表格刷新列 reColumnFlag: 0, + // 已标记的对象集 + pendingRowMaps: {}, + // 已标记的行 + pendingRowList: [], // 当前选中的筛选列 filterStore: { isAllSelected: false, @@ -413,13 +417,9 @@ export default { }, // 存放数据校验相关信息 validStore: { - visible: false, - row: null, - column: null, - content: '', - rule: null, - isArrow: false + visible: false }, + validErrorMaps: {}, // 导入相关信息 importStore: { inited: false, @@ -1044,6 +1044,7 @@ export default { class: ['vxe-table', 'vxe-table--render-default', `tid_${tId}`, vSize ? `size--${vSize}` : '', `border--${tableBorder}`, { [`vaild-msg--${validOpts.msgMode}`]: !!editRules, 'vxe-editable': !!editConfig, + 'old-cell-valid': editRules && GlobalConfig.cellVaildMode === 'obsolete', 'cell--highlight': highlightCell, 'cell--selected': mouseConfig && mouseOpts.selected, 'cell--area': mouseConfig && mouseOpts.area, @@ -1227,7 +1228,9 @@ export default { */ hasTip && this.editRules && validOpts.showMessage && (validOpts.message === 'default' ? !height : validOpts.message === 'tooltip') ? h('vxe-tooltip', { ref: 'validTip', - class: 'vxe-table--valid-error', + class: [{ + 'old-cell-valid': editRules && GlobalConfig.cellVaildMode === 'obsolete' + }, 'vxe-table--valid-error'], props: validOpts.message === 'tooltip' || tableData.length === 1 ? validTipOpts : null }) : _e() ]) diff --git a/packages/tooltip/src/tooltip.js b/packages/tooltip/src/tooltip.js index 841f6eb65..af7874679 100644 --- a/packages/tooltip/src/tooltip.js +++ b/packages/tooltip/src/tooltip.js @@ -175,7 +175,7 @@ export default { 'is--enterable': enterable, 'is--visible': visible, 'is--arrow': isArrow, - 'is--actived': tipActive + 'is--active': tipActive }], style: tipStore.style, ref: 'tipWrapper', diff --git a/packages/validator/src/mixin.js b/packages/validator/src/mixin.js index 8cb3680f1..95a2b81e3 100644 --- a/packages/validator/src/mixin.js +++ b/packages/validator/src/mixin.js @@ -1,6 +1,7 @@ import XEUtils from 'xe-utils' import VXETable from '../../v-x-e-table' import GlobalConfig from '../../v-x-e-table/src/conf' +import { getRowid, handleFieldOrColumn } from '../../table/src/util' import { eqEmptyValue, getFuncText } from '../../tools/utils' import { warnLog, errLog } from '../../tools/log' import DomTools from '../../tools/dom' @@ -87,8 +88,9 @@ export default { * 聚焦到校验通过的单元格并弹出校验错误提示 */ handleValidError (params) { + const { validOpts } = this return new Promise(resolve => { - if (this.validOpts.autoPos === false) { + if (validOpts.autoPos === false) { this.emitEvent('valid-error', params) resolve() } else { @@ -100,6 +102,19 @@ export default { } }) }, + handleErrMsgMode (validErrMaps) { + const { validOpts } = this + if (validOpts.msgMode === 'single') { + const keys = Object.keys(validErrMaps) + const resMaps = validErrMaps + if (keys.length) { + const firstKey = keys[0] + resMaps[firstKey] = validErrMaps[firstKey] + } + return resMaps + } + return validErrMaps + }, /** * 对表格数据进行校验 * 如果不指定数据,则默认只校验临时变动的数据,例如新增或修改 @@ -111,7 +126,7 @@ export default { */ beginValidate (rows, cb, isFull) { const validRest = {} - const { editRules, afterFullData, treeConfig, treeOpts } = this + const { editRules, afterFullData, visibleColumn, treeConfig, treeOpts } = this const childrenField = treeOpts.children || treeOpts.childrenField let vaildDatas if (rows === true) { @@ -130,6 +145,7 @@ export default { this.lastCallTime = Date.now() this.validRuleErr = false // 如果为快速校验,当存在某列校验不通过时将终止执行 this.clearValidate() + const validErrMaps = {} if (editRules) { const columns = this.getColumns() const handleVaild = row => { @@ -153,6 +169,12 @@ export default { if (!validRest[column.property]) { validRest[column.property] = [] } + validErrMaps[`${getRowid(this, row)}:${column.id}`] = { + column, + row, + rule, + content: rule.content + } validRest[column.property].push(rest) if (!isFull) { this.validRuleErr = true @@ -172,6 +194,7 @@ export default { } return Promise.all(rowValids).then(() => { const ruleProps = Object.keys(validRest) + this.validErrorMaps = this.handleErrMsgMode(validErrMaps) return this.$nextTick().then(() => { if (ruleProps.length) { return Promise.reject(validRest[ruleProps[0]][0]) @@ -207,20 +230,21 @@ export default { * 将表格滚动到可视区 * 由于提示信息至少需要占一行,定位向上偏移一行 */ - const row = firstErrParams.row - const rowIndex = afterFullData.indexOf(row) - const locatRow = rowIndex > 0 ? afterFullData[rowIndex - 1] : row if (this.validOpts.autoPos === false) { finish() } else { - if (treeConfig) { - this.scrollToTreeRow(locatRow).then(posAndFinish) - } else { - this.scrollToRow(locatRow).then(posAndFinish) - } + const row = firstErrParams.row + const column = firstErrParams.column + const rowIndex = afterFullData.indexOf(row) + const columnIndex = visibleColumn.indexOf(column) + const locatRow = rowIndex > 0 ? afterFullData[rowIndex - 1] : row + const locatColumn = columnIndex > 0 ? visibleColumn[rowIndex - 1] : column + this.scrollToRow(locatRow, locatColumn).then(posAndFinish) } }) }) + } else { + this.validErrorMaps = {} } return this.$nextTick().then(() => { if (cb) { @@ -335,34 +359,66 @@ export default { } }) }, - _clearValidate () { + _clearValidate (rows, fieldOrColumn) { + const { validOpts, validErrorMaps } = this const validTip = this.$refs.validTip - Object.assign(this.validStore, { - visible: false, - row: null, - column: null, - content: '', - rule: null - }) - if (validTip && validTip.visible) { + const rowList = XEUtils.isArray(rows) ? rows : (rows ? [rows] : []) + const colList = (XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : (fieldOrColumn ? [fieldOrColumn] : []).map(column => handleFieldOrColumn(this, column))) + let validErrMaps = {} + if (validTip && validTip.reactData.visible) { validTip.close() } + // 如果是单个提示模式 + if (validOpts.msgMode === 'single') { + this.validErrorMaps = {} + return this.$nextTick() + } + if (rowList.length && colList.length) { + validErrMaps = Object.assign({}, validErrorMaps) + rowList.forEach(row => { + colList.forEach((column) => { + const vaildKey = `${getRowid(this, row)}:${column.id}` + if (validErrMaps[vaildKey]) { + delete validErrMaps[vaildKey] + } + }) + }) + } else if (rowList.length) { + const rowidList = rowList.map(row => `${getRowid(this, row)}`) + XEUtils.each(validErrorMaps, (item, key) => { + if (rowidList.indexOf(key.split(':')[0]) > -1) { + validErrMaps[key] = item + } + }) + } else if (colList.length) { + const colidList = colList.map(column => `${column.id}`) + XEUtils.each(validErrorMaps, (item, key) => { + if (colidList.indexOf(key.split(':')[1]) > -1) { + validErrMaps[key] = item + } + }) + } + this.validErrorMaps = validErrMaps return this.$nextTick() }, /** * 触发校验 */ triggerValidate (type) { - const { editConfig, editStore, editRules, validStore } = this + const { editConfig, editStore, editRules, editOpts, validOpts } = this const { actived } = editStore - if (actived.row && editRules) { + // 检查清除校验消息 + if (editRules && validOpts.msgMode === 'single') { + this.validErrorMaps = {} + } + + // 校验单元格 + if (editConfig && editRules && actived.row) { const { row, column, cell } = actived.args if (this.hasCellRules(type, row, column)) { return this.validCellRules(type, row, column).then(() => { - if (editConfig.mode === 'row') { - if (validStore.visible && validStore.row === row && validStore.column === column) { - this.clearValidate() - } + if (editOpts.mode === 'row') { + this.clearValidate(row, column) } }).catch(({ rule }) => { // 如果校验不通过与触发方式一致,则聚焦提示错误,否则跳过并不作任何处理 @@ -381,23 +437,37 @@ export default { * 弹出校验错误提示 */ showValidTooltip (params) { - const { $refs, height, tableData, validOpts } = this + const { $refs, height, validStore, validErrorMaps, tableData, validOpts } = this const { rule, row, column, cell } = params const validTip = $refs.validTip const content = rule.content - return this.$nextTick(() => { - Object.assign(this.validStore, { - row, - column, - rule, - content, - visible: true + validStore.visible = true + if (validOpts.msgMode === 'single') { + this.validErrorMaps = { + [`${getRowid(this, row)}:${column.id}`]: { + column, + row, + rule, + content + } + } + } else { + this.validErrorMaps = Object.assign({}, validErrorMaps, { + [`${getRowid(this, row)}:${column.id}`]: { + column, + row, + rule, + content + } }) - this.emitEvent('valid-error', params) + } + this.emitEvent('valid-error', params, null) + if (validTip) { if (validTip && (validOpts.message === 'tooltip' || (validOpts.message === 'default' && !height && tableData.length < 2))) { return validTip.open(cell, content) } - }) + } + return this.$nextTick() } } } diff --git a/styles/grid.scss b/styles/grid.scss index 5b3e23cb0..2b084892f 100644 --- a/styles/grid.scss +++ b/styles/grid.scss @@ -29,26 +29,6 @@ padding: 0.5em 1em; background-color: $vxe-grid-maximize-background-color; } - .vxe-body--row { - &.row--pending { - color: $vxe-table-validate-error-color; - text-decoration: line-through; - cursor: no-drop; - .vxe-body--column { - position: relative; - &:after { - content: ""; - position: absolute; - top: 50%; - left: 0; - width: 100%; - height: 0; - border-bottom: 1px solid $vxe-table-validate-error-color; - z-index: 1; - } - } - } - } .vxe-grid--form-wrapper, .vxe-grid--top-wrapper, .vxe-grid--bottom-wrapper { diff --git a/styles/table.scss b/styles/table.scss index c8ce12d96..9b9a71dfe 100644 --- a/styles/table.scss +++ b/styles/table.scss @@ -1067,7 +1067,7 @@ .vxe-footer--column { &.col--ellipsis { @extend %DefaultColumnHeight; - &:not(.col--actived) { + &:not(.col--active) { & > .vxe-cell { @extend %TextEllipsis; } @@ -1173,26 +1173,28 @@ /*校验不通过*/ .vxe-body--column { - &.col--actived, + &.col--active, &.col--selected { position: relative; } &.col--valid-error { - .vxe-cell--valid { - width: 320px; + .vxe-cell--valid-error-hint { + width: 100%; position: absolute; - bottom: calc(100% + 4px); left: 50%; + font-size: 12px; + line-height: 1.2em; transform: translateX(-50%); - text-align: center; + text-align: left; pointer-events: none; z-index: 4; - .vxe-cell--valid-msg { + padding-left: $vxe-table-cell-padding-left; + padding-right: $vxe-table-cell-padding-right; + .vxe-cell--valid-error-msg { display: inline-block; border-radius: $vxe-border-radius; - padding: 8px 12px; - color: #fff; - background-color: #f56c6c; + color: $vxe-table-validate-error-color; + background-color: $vxe-table-validate-error-background-color; pointer-events: auto; } } @@ -1209,20 +1211,90 @@ } } - .vxe-body--row { - &:first-child { - .vxe-cell--valid { - bottom: auto; - top: calc(100% + 4px); + &.vaild-msg--single { + .vxe-body--row { + &:last-child { + .vxe-cell--valid-error-hint { + bottom: calc(100%); + } + &:first-child { + .vxe-cell--valid-error-hint { + bottom: auto; + } + } } } } - .vxe-body--column { - &:first-child { - .vxe-cell--valid { - left: 10px; - transform: translateX(0); - text-align: left; + &.vaild-msg--full { + .vxe-body--row { + &:last-child { + .vxe-cell--valid-error-hint { + top: calc(100% - 1.3em); + } + } + } + } + /*已废弃,旧的校验样式**/ + &.old-cell-valid { + .vxe-body--column { + &.col--valid-error { + .vxe-cell--valid-error-hint { + width: 320px; + position: absolute; + bottom: calc(100% + 4px); + left: 50%; + transform: translateX(-50%); + text-align: center; + pointer-events: none; + z-index: 4; + .vxe-cell--valid-error-msg { + display: inline-block; + border-radius: $vxe-border-radius; + padding: 8px 12px; + color: #fff; + background-color: #f56c6c; + pointer-events: auto; + } + } + } + } + .vxe-body--row { + &:first-child { + .vxe-cell--valid-error-hint { + bottom: auto; + top: calc(100% + 4px); + } + } + } + .vxe-body--column { + &:first-child { + .vxe-cell--valid-error-hint { + left: 10px; + transform: translateX(0); + text-align: left; + } + } + } + } + + /*单元格标记删除状态*/ + .vxe-body--row { + &.row--pending { + color: $vxe-table-validate-error-color; + text-decoration: line-through; + cursor: no-drop; + .vxe-body--column { + position: relative; + &:after { + content: ""; + position: absolute; + top: 50%; + left: 0; + width: 100%; + height: 0; + border-bottom: 1px solid $vxe-table-validate-error-color; + z-index: 1; + } } } } @@ -1265,7 +1337,7 @@ &.vxe-editable { &.cell--highlight { .vxe-body--column { - &.col--actived { + &.col--active { box-shadow: inset 0px 0px 0px 2px $vxe-primary-color; &.col--valid-error { box-shadow: inset 0px 0px 0px 2px $vxe-table-validate-error-color; @@ -1297,7 +1369,7 @@ } .vxe-body--column { padding: 0; - &.col--actived { + &.col--active { padding: 0; } } diff --git a/types/grid.d.ts b/types/grid.d.ts index 1b3bded8e..0694ff3f0 100644 --- a/types/grid.d.ts +++ b/types/grid.d.ts @@ -10,7 +10,7 @@ import { RowInfo } from './component' /* eslint-disable no-use-before-define */ /** - * 高级表格 + * 配置式表格 */ export declare class Grid extends Table { /** diff --git a/types/table.d.ts b/types/table.d.ts index 76d4ccae9..c3a61b682 100644 --- a/types/table.d.ts +++ b/types/table.d.ts @@ -600,6 +600,22 @@ export declare class Table extends VXETableComponent { * @param row 指定行 */ setRadioRow(row: RowInfo): Promise; + /** + * 将指定行设置为取消/标记待删除状态 + */ + setPendingRow(rows: any | any[], status: boolean): Promise + /** + * 切换指定行的取消/标记待删除状态 + */ + togglePendingRow(rows: any | any[]): Promise + /** + * 获取待删除状态的数据 + */ + getPendingRecords(): any[] + /** + * 清除所有标记状态 + */ + clearPendingRow(): Promise /** * 手动清除临时合并的单元格 */ @@ -913,6 +929,7 @@ export declare class Table extends VXETableComponent { insertRecords: RowInfo[]; removeRecords: RowInfo[]; updateRecords: RowInfo[]; + pendingRecords: RowInfo[] }; /** diff --git a/types/v-x-e-table/renderer.d.ts b/types/v-x-e-table/renderer.d.ts index 1f6a2ccd3..081f66a93 100644 --- a/types/v-x-e-table/renderer.d.ts +++ b/types/v-x-e-table/renderer.d.ts @@ -181,7 +181,7 @@ export class TableRenderParams extends RenderParams { export class GridRenderParams extends TableRenderParams { /** - * 高级表格实例对象 + * 配置式表格实例对象 */ $grid: Grid; }