From 68cd528d84f15f15dcce134904e6686591f8187e Mon Sep 17 00:00:00 2001 From: xuliangzhan Date: Mon, 30 Dec 2024 19:58:50 +0800 Subject: [PATCH] update docs --- .gitee/ISSUE_TEMPLATE/bug_report.yml | 39 ++- .gitee/ISSUE_TEMPLATE/feature_request.yml | 10 +- .github/ISSUE_TEMPLATE/bug_report.yml | 18 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- examples/views/table/TableTest8.vue | 5 +- examples/views/table/TableTest9.vue | 2 +- package.json | 4 +- packages/table/module/custom/panel.ts | 17 +- packages/table/module/edit/hook.ts | 7 +- packages/table/module/export/hook.ts | 155 +++++++--- packages/table/module/validator/hook.ts | 8 +- packages/table/render/index.ts | 4 +- packages/table/src/body.ts | 76 ++++- packages/table/src/footer.ts | 31 +- packages/table/src/header.ts | 43 ++- packages/table/src/table.ts | 320 +++++++++++++-------- packages/table/src/util.ts | 8 +- packages/toolbar/src/toolbar.ts | 30 +- packages/ui/index.ts | 8 +- packages/ui/src/dom.ts | 17 +- packages/ui/src/vn.ts | 3 + styles/components/table.scss | 4 +- 22 files changed, 571 insertions(+), 240 deletions(-) diff --git a/.gitee/ISSUE_TEMPLATE/bug_report.yml b/.gitee/ISSUE_TEMPLATE/bug_report.yml index e722d7f857..70cdcac5c1 100644 --- a/.gitee/ISSUE_TEMPLATE/bug_report.yml +++ b/.gitee/ISSUE_TEMPLATE/bug_report.yml @@ -5,8 +5,8 @@ body: - type: input id: issue_link attributes: - label: "可复现的链接(必须为公开链接,仅包含能复现问题的示例代码):" - description: "一个最小化的重现示例能让我们精确地定位问题,从而快速解决问题。如何创建,点击 v3:[codesandbox](https://codesandbox.io/s/vue-template-916h0)、[jsfiddle](https://jsfiddle.net/86p7Ltny/)、[jsrun](https://jsrun.net/vIyKp/edit) 或 v4:[codesandbox](https://codesandbox.io/s/vxe-table-wentiyanshi-forked-54v2j)、[jsfiddle](https://jsfiddle.net/9qoghkbj/)、[jsrun](https://jsrun.net/K5IKp/edit),将代码示例编辑后保存。" + label: "[可复现的链接](https://vxetable.cn/issues.html)(必须为公开链接,仅包含能复现问题的示例代码):" + description: "一个最小化的重现示例能让我们精确地定位问题,从而快速解决问题。" validations: required: true - type: textarea @@ -17,7 +17,7 @@ body: required: true - type: markdown attributes: - value: "在发布问题之前,请仔细阅读所填写的步骤,以确保是详细和清晰的。" + value: "在发布问题之前,请先查阅[最新文档](https://vxetable.cn/)确保使用的是最新版本,并仔细阅读所填写的步骤,以确保是详细和清晰的。" - type: input id: issue_expect attributes: @@ -26,27 +26,50 @@ body: id: issue_os_version attributes: label: "操作系统:" - placeholder: "例如:window11" + placeholder: "例如:window10" validations: required: true - type: input id: issue_browser_version attributes: label: "浏览器版本:" - placeholder: "例如:chrome 133.0.6876.4" + placeholder: "例如:chrome 95.0.4638.69" validations: required: true - type: input id: issue_vue_version attributes: label: "vue 版本:" - placeholder: "例如:3.5.0" + placeholder: "例如:3.4.27" validations: required: true - type: input - id: issue_vxe_version + id: issue_vxe_ui_version + attributes: + label: "vxe-pc-ui 版本:" + placeholder: "例如:4.3.51" + validations: + required: true +- type: input + id: issue_vxe_table_version attributes: label: "vxe-table 版本:" - placeholder: "例如:3.9.0" + placeholder: "例如:4.9.35" validations: required: true +- type: checkboxes + id: issue_confirm_read_agreement + attributes: + label: "反馈?" + description: "官方只维护最新版本,如果不是最新版本,请先更新到[最新版本](https://vxetable.cn/)之后再提!" + options: + - label: "我已确认要求提供复现链接与示例代码" + required: true +- type: checkboxes + id: issue_confirm_latest_version + attributes: + label: "是否使用当前最新版本?" + description: "我已确认是最新版本,仅支持提交最新版本的问题反馈,非最新版本不予处理,一律关闭" + options: + - label: "我已确认是使用最新版本并已按要求提供复现链接与示例代码" + required: true \ No newline at end of file diff --git a/.gitee/ISSUE_TEMPLATE/feature_request.yml b/.gitee/ISSUE_TEMPLATE/feature_request.yml index 039b9b23ea..53b241c7f1 100644 --- a/.gitee/ISSUE_TEMPLATE/feature_request.yml +++ b/.gitee/ISSUE_TEMPLATE/feature_request.yml @@ -10,7 +10,7 @@ body: required: true - type: markdown attributes: - value: "请先查看[最新文档](https://xuliangzhan_admin.gitee.io/vxe-table/#/table/api),确定该功能是否已有实现" + value: "请先查阅[最新文档](https://vxetable.cn/),确定该功能是否已有实现" - type: textarea id: issue_api_describe attributes: @@ -25,3 +25,11 @@ body: - type: markdown attributes: value: "如果有其他已实现的方案,可以通过链接或截图描述一下" +- type: checkboxes + id: issue_confirm_latest_version + attributes: + label: "是否使用当前最新版本?" + description: "官方只维护最新版本,如果不是最新版本,请先更新到[最新版本](https://vxetable.cn/)之后再反馈问题!" + options: + - label: "我已确认是使用当前的最新版本。" + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d349247693..70cdcac5c1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -5,7 +5,7 @@ body: - type: input id: issue_link attributes: - label: "可复现的链接(必须为公开链接,仅包含能复现问题的示例代码):" + label: "[可复现的链接](https://vxetable.cn/issues.html)(必须为公开链接,仅包含能复现问题的示例代码):" description: "一个最小化的重现示例能让我们精确地定位问题,从而快速解决问题。" validations: required: true @@ -47,21 +47,29 @@ body: id: issue_vxe_ui_version attributes: label: "vxe-pc-ui 版本:" - placeholder: "例如:4.2.2" + placeholder: "例如:4.3.51" validations: required: true - type: input id: issue_vxe_table_version attributes: label: "vxe-table 版本:" - placeholder: "例如:4.7.81" + placeholder: "例如:4.9.35" validations: required: true +- type: checkboxes + id: issue_confirm_read_agreement + attributes: + label: "反馈?" + description: "官方只维护最新版本,如果不是最新版本,请先更新到[最新版本](https://vxetable.cn/)之后再提!" + options: + - label: "我已确认要求提供复现链接与示例代码" + required: true - type: checkboxes id: issue_confirm_latest_version attributes: label: "是否使用当前最新版本?" - description: "官方只维护最新版本,如果不是最新版本,请先更新到[最新版本](https://vxetable.cn/)之后再提!" + description: "我已确认是最新版本,仅支持提交最新版本的问题反馈,非最新版本不予处理,一律关闭" options: - - label: "我已确认是使用当前的最新版本并已按要求提供复现链接与示例代码。" + - label: "我已确认是使用最新版本并已按要求提供复现链接与示例代码" required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 7e63857478..53b241c7f1 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -29,7 +29,7 @@ body: id: issue_confirm_latest_version attributes: label: "是否使用当前最新版本?" - description: "官方只维护最新版本,如果不是最新版本,请先更新到[最新版本](https://vxetable.cn/)之后再提!" + description: "官方只维护最新版本,如果不是最新版本,请先更新到[最新版本](https://vxetable.cn/)之后再反馈问题!" options: - label: "我已确认是使用当前的最新版本。" required: true \ No newline at end of file diff --git a/examples/views/table/TableTest8.vue b/examples/views/table/TableTest8.vue index f201f0a24f..fa9e624cc0 100644 --- a/examples/views/table/TableTest8.vue +++ b/examples/views/table/TableTest8.vue @@ -7,12 +7,13 @@ show-footer height="800" :loading="loading" - :column-config="{resizable: true}" + :column-config="{resizable: true,drag: true}" + :row-config="{drag: true}" :scroll-x="{enabled: true, gt: 0}" :scroll-y="{enabled: true, gt: 0}" :data="tableData" :footer-data="footerData"> - + diff --git a/examples/views/table/TableTest9.vue b/examples/views/table/TableTest9.vue index 5447520eb2..1c3c2a465d 100644 --- a/examples/views/table/TableTest9.vue +++ b/examples/views/table/TableTest9.vue @@ -12,7 +12,7 @@ :scroll-x="{enabled: true, gt: 0}" :scroll-y="{enabled: true, gt: 0}" :data="tableData"> - + diff --git a/package.json b/package.json index 0169145cb5..771b465139 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vxe-table", - "version": "4.9.35", + "version": "4.10.0-beta.13", "description": "一个基于 vue 的 PC 端表格组件,支持增删改查、虚拟树、拖拽排序,懒加载、快捷菜单、数据校验、树形结构、打印、导入导出、自定义模板、渲染器、JSON 配置式...", "scripts": { "update": "npm install --legacy-peer-deps", @@ -28,7 +28,7 @@ "style": "lib/style.css", "typings": "types/index.d.ts", "dependencies": { - "vxe-pc-ui": "^4.3.43" + "vxe-pc-ui": "^4.3.51" }, "devDependencies": { "@types/resize-observer-browser": "^0.1.11", diff --git a/packages/table/module/custom/panel.ts b/packages/table/module/custom/panel.ts index 6d3ce22064..63e615c7cb 100644 --- a/packages/table/module/custom/panel.ts +++ b/packages/table/module/custom/panel.ts @@ -1,7 +1,7 @@ import { defineComponent, h, inject, ref, Ref, VNode, PropType, nextTick, TransitionGroup, createCommentVNode } from 'vue' import { VxeUI } from '../../../ui' import { formatText } from '../../../ui/src/utils' -import { tpImg, addClass, removeClass } from '../../../ui/src/dom' +import { getTpImg, addClass, removeClass } from '../../../ui/src/dom' import { errLog } from '../../../ui/src/log' import XEUtils from 'xe-utils' @@ -251,9 +251,7 @@ export default defineComponent({ const sortDragstartEvent = (evnt: DragEvent) => { if (evnt.dataTransfer) { - const img = new Image() - img.src = tpImg - evnt.dataTransfer.setDragImage(img, 0, 0) + evnt.dataTransfer.setDragImage(getTpImg(), 0, 0) } } @@ -422,11 +420,7 @@ export default defineComponent({ if (immediate) { reactData.customColumnList = collectColumn.slice(0) - $xeTable.refreshColumn(true).then(() => { - $xeTable.updateCellAreas() - $xeTable.handleCustom() - $xeTable.saveCustomStore('update:sort') - }) + $xeTable.handleColDragSwapColumn() } }).catch(() => { }) @@ -474,10 +468,13 @@ export default defineComponent({ const renderDragTip = () => { const dragCol = dragColumnRef.value + const columnDragOpts = computeColumnDragOpts.value return h('div', {}, [ h('div', { ref: refDragLineElem, - class: 'vxe-table-custom-popup--drag-line' + class: ['vxe-table-custom-popup--drag-line', { + 'is--guides': columnDragOpts.showGuidesStatus + }] }), h('div', { ref: refDragTipElem, diff --git a/packages/table/module/edit/hook.ts b/packages/table/module/edit/hook.ts index d245e4d285..bc868d8699 100644 --- a/packages/table/module/edit/hook.ts +++ b/packages/table/module/edit/hook.ts @@ -228,7 +228,12 @@ hooks.add('tableEditModule', { throw new Error(getI18n('vxe.error.unableInsert')) } afterFullData.splice(afIndex, 0, ...newRecords) - tableFullData.splice($xeTable.findRowIndexOf(tableFullData, targetRow), 0, ...newRecords) + const tfIndex = $xeTable.findRowIndexOf(tableFullData, targetRow) + if (tfIndex > -1) { + tableFullData.splice(tfIndex + (isInsertNextRow ? 1 : 0), 0, ...newRecords) + } else { + tableFullData.push(...newRecords) + } // 刷新单元格合并 mergeList.forEach((mergeItem: any) => { const { row: mergeRowIndex, rowspan: mergeRowspan } = mergeItem diff --git a/packages/table/module/export/hook.ts b/packages/table/module/export/hook.ts index c5ae5b45bd..71f9e5ed99 100644 --- a/packages/table/module/export/hook.ts +++ b/packages/table/module/export/hook.ts @@ -6,7 +6,7 @@ import { parseFile, formatText, eqEmptyValue } from '../../../ui/src/utils' import { createHtmlPage, getExportBlobByContent } from './util' import { warnLog, errLog } from '../../../ui/src/log' -import type { VxeGridConstructor, VxeGridPrivateMethods, TableExportMethods, VxeGridPropTypes } from '../../../../types' +import type { VxeGridConstructor, VxeGridPrivateMethods, VxeTablePropTypes, TableExportMethods, VxeGridPropTypes, VxeTableDefines } from '../../../../types' const { getI18n, hooks, renderer } = VxeUI @@ -780,7 +780,7 @@ hooks.add('tableExportModule', { } } - const handleExport = (opts: any) => { + const handleExport = (opts: VxeTablePropTypes.ExportHandleOptions) => { const { remote, columns, colgroups, exportMethod, afterExportMethod } = opts return new Promise(resolve => { if (remote) { @@ -944,7 +944,7 @@ hooks.add('tableExportModule', { }) } - const handleExportAndPrint = (options: any, isPrint?: boolean) => { + const handleExportAndPrint = (options: VxeTablePropTypes.ExportOpts | VxeTablePropTypes.ExportConfig, isPrint?: boolean) => { const { treeConfig, showHeader, showFooter } = props const { initStore, mergeList, isGroup, footerTableData, exportStore, exportParams } = reactData const { collectColumn } = internalData @@ -987,13 +987,13 @@ hooks.add('tableExportModule', { } }) // 默认选中 - XEUtils.eachTree(exportColumns, (column: any, index, items, path, parent) => { + XEUtils.eachTree(exportColumns, (column, index, items, path, parent) => { const isColGroup = column.children && column.children.length if (isColGroup || defaultFilterExportColumn(column)) { column.checked = columns ? columns.some((item: any) => { if (isColumnInfo(item)) { - return column === item + return column.id === item.id } else if (XEUtils.isString(item)) { return column.field === item } else { @@ -1033,10 +1033,33 @@ hooks.add('tableExportModule', { Object.assign(exportParams, { mode: selectRecords.length ? 'selected' : 'current' }, defOpts) - if (!modeList.some(item => item.value === exportParams.mode)) { + const { filename, sheetName, mode, type } = exportParams + if (filename) { + if (XEUtils.isFunction(filename)) { + exportParams.filename = filename({ + options: defOpts, + $table: $xeTable, + $grid: $xeGrid + }) + } else { + exportParams.filename = `${filename}` + } + } + if (sheetName) { + if (XEUtils.isFunction(sheetName)) { + exportParams.sheetName = sheetName({ + options: defOpts, + $table: $xeTable, + $grid: $xeGrid + }) + } else { + exportParams.sheetName = `${sheetName}` + } + } + if (!modeList.some(item => item.value === mode)) { exportParams.mode = modeList[0].value } - if (!typeList.some(item => item.value === exportParams.type)) { + if (!typeList.some(item => item.value === type)) { exportParams.type = typeList[0].value } initStore.export = true @@ -1054,7 +1077,7 @@ hooks.add('tableExportModule', { /** * 导出文件,支持 csv/html/xml/txt * 如果是树表格,则默认是导出所有节点 - * 如果是启用了虚拟滚动,则只能导出数据源,可以配合 dataFilterMethod 函数自行转换数据 + * 如果是启用了虚拟滚动,则只能导出数据源,可以配合 dataFilterMethod 函数转换数据 * @param {Object} options 参数 */ exportData (options) { @@ -1084,16 +1107,14 @@ hooks.add('tableExportModule', { // columnFilterMethod: null, // beforeExportMethod: null, // afterExportMethod: null - }, exportOpts, { - print: false - }, options) - const { type, mode, columns, original, beforeExportMethod, includeFields, excludeFields } = opts + }, exportOpts, options) + const { filename, sheetName, type, mode, columns, original, columnFilterMethod, beforeExportMethod, includeFields, excludeFields } = opts let groups: any[] = [] const customCols = columns && columns.length ? columns : null - let columnFilterMethod = opts.columnFilterMethod + const handleOptions: VxeTablePropTypes.ExportHandleOptions = Object.assign({ }, opts, { filename: '', sheetName: '', colgroups: [], columns: [], data: [] }) // 如果设置源数据,则默认导出设置了字段的列 if (!customCols && !columnFilterMethod) { - columnFilterMethod = ({ column }) => { + handleOptions.columnFilterMethod = ({ column }) => { if (excludeFields) { if (XEUtils.includes(excludeFields, column.field)) { return false @@ -1109,9 +1130,9 @@ hooks.add('tableExportModule', { } } if (customCols) { - (opts as any)._isCustomColumn = true + handleOptions._isCustomColumn = true groups = XEUtils.searchTree( - XEUtils.mapTree(customCols, (item: any) => { + XEUtils.mapTree(customCols, (item) => { let targetColumn if (item) { if (isColumnInfo(item)) { @@ -1125,11 +1146,11 @@ hooks.add('tableExportModule', { if (colid) { targetColumn = $xeTable.getColumnById(colid) } else if (field && type) { - targetColumn = tableFullColumn.find((column: any) => column.field === field && column.type === type) + targetColumn = tableFullColumn.find((column) => column.field === field && column.type === type) } else if (field) { targetColumn = $xeTable.getColumnByField(field) } else if (type) { - targetColumn = tableFullColumn.find((column: any) => column.type === type) + targetColumn = tableFullColumn.find((column) => column.type === type) } } return targetColumn || {} @@ -1146,10 +1167,10 @@ hooks.add('tableExportModule', { } ) } else { - groups = XEUtils.searchTree(isGroup ? tableGroupColumn : tableFullColumn, (column: any, index) => column.visible && (!columnFilterMethod || columnFilterMethod({ column, $columnIndex: index })), { children: 'children', mapChildren: 'childNodes', original: true }) + groups = XEUtils.searchTree(isGroup ? tableGroupColumn : tableFullColumn, (column, index) => column.visible && (!columnFilterMethod || columnFilterMethod({ column, $columnIndex: index })), { children: 'children', mapChildren: 'childNodes', original: true }) } // 获取所有列 - const cols: any = [] + const cols: VxeTableDefines.ColumnInfo[] = [] XEUtils.eachTree(groups, column => { const isColGroup = column.children && column.children.length if (!isColGroup) { @@ -1157,17 +1178,40 @@ hooks.add('tableExportModule', { } }, { children: 'childNodes' }) // 构建分组层级 - opts.columns = cols - ;(opts as any).colgroups = convertToRows(groups) - if (!opts.filename) { - opts.filename = getI18n(opts.original ? 'vxe.table.expOriginFilename' : 'vxe.table.expFilename', [XEUtils.toDateString(Date.now(), 'yyyyMMddHHmmss')]) + handleOptions.columns = cols + handleOptions.colgroups = convertToRows(groups) + if (filename) { + if (XEUtils.isFunction(filename)) { + handleOptions.filename = filename({ + options: opts, + $table: $xeTable, + $grid: $xeGrid + }) + } else { + handleOptions.filename = `${filename}` + } } - if (!opts.sheetName) { - opts.sheetName = document.title + if (!handleOptions.filename) { + handleOptions.filename = getI18n(handleOptions.original ? 'vxe.table.expOriginFilename' : 'vxe.table.expFilename', [XEUtils.toDateString(Date.now(), 'yyyyMMddHHmmss')]) + } + + if (sheetName) { + if (XEUtils.isFunction(sheetName)) { + handleOptions.sheetName = sheetName({ + options: opts, + $table: $xeTable, + $grid: $xeGrid + }) + } else { + handleOptions.sheetName = `${sheetName}` + } + } + if (!handleOptions.sheetName) { + handleOptions.sheetName = document.title || '' } // 检查类型,如果为自定义导出,则不需要校验类型 - if (!opts.exportMethod && !XEUtils.includes(XEUtils.keys(exportOpts._typeMaps), type)) { + if (!handleOptions.exportMethod && !XEUtils.includes(XEUtils.keys(exportOpts._typeMaps), type)) { errLog('vxe.error.notType', [type]) if (process.env.VUE_APP_VXE_ENV === 'development') { if (['xlsx', 'pdf'].includes(type)) { @@ -1178,13 +1222,13 @@ hooks.add('tableExportModule', { return Promise.reject(params) } - if (!opts.print) { + if (!handleOptions.print) { if (beforeExportMethod) { - beforeExportMethod({ options: opts, $table: $xeTable, $grid: $xeGrid } as any) + beforeExportMethod({ options: handleOptions, $table: $xeTable, $grid: $xeGrid }) } } - if (!opts.data) { - opts.data = [] + if (!handleOptions.data) { + handleOptions.data = [] if (mode === 'selected') { const selectRecords = $xeTable.getCheckboxRecords() if (['html', 'pdf'].indexOf(type) > -1 && treeConfig) { @@ -1199,12 +1243,13 @@ hooks.add('tableExportModule', { } } - if ($xeGrid && !opts.remote) { - const { reactData: gridReactData } = $xeGrid + if ($xeGrid && !handleOptions.remote) { + const gridReactData = $xeGrid.reactData const { computeProxyOpts } = $xeGrid.getComputeMaps() - const { sortData } = gridReactData const proxyOpts = computeProxyOpts.value - const { beforeQueryAll, afterQueryAll, ajax = {}, props = {} } = proxyOpts + const { sortData } = gridReactData + const { beforeQueryAll, afterQueryAll, ajax = {} } = proxyOpts + const resConfigs = proxyOpts.response || proxyOpts.props || {} const ajaxMethods = ajax.queryAll const queryAllSuccessMethods = ajax.queryAllSuccess const queryAllErrorMethods = ajax.queryAllError @@ -1223,18 +1268,19 @@ hooks.add('tableExportModule', { sorts: sortData as any[], filters: gridReactData.filterData, form: gridReactData.formData, - options: opts + options: handleOptions } return Promise.resolve((beforeQueryAll || ajaxMethods)(params)) .then(rest => { - opts.data = (props.list ? XEUtils.get(rest, props.list) : rest) || [] + const listProp = resConfigs.list + handleOptions.data = (listProp ? (XEUtils.isFunction(listProp) ? listProp({ data: rest, $grid: $xeGrid }) : XEUtils.get(rest, listProp)) : rest) || [] if (afterQueryAll) { afterQueryAll(params) } if (queryAllSuccessMethods) { queryAllSuccessMethods({ ...params, response: rest }) } - return handleExport(opts) + return handleExport(handleOptions) }) .catch((rest) => { if (queryAllErrorMethods) { @@ -1244,10 +1290,10 @@ hooks.add('tableExportModule', { } } } else if (mode === 'current') { - opts.data = afterFullData + handleOptions.data = afterFullData } } - return handleExport(opts) + return handleExport(handleOptions) }, importByFile (file, options) { const opts = Object.assign({}, options) @@ -1295,9 +1341,24 @@ hooks.add('tableExportModule', { remote: false, print: true }) - if (!opts.sheetName) { - opts.sheetName = document.title + const { sheetName } = opts + let printTitle = '' + + if (sheetName) { + if (XEUtils.isFunction(sheetName)) { + printTitle = sheetName({ + options: opts, + $table: $xeTable, + $grid: $xeGrid + }) + } else { + printTitle = `${sheetName}` + } + } + if (!printTitle) { + printTitle = document.title || '' } + const beforePrintMethod = opts.beforePrintMethod const tableHtml = opts.html || opts.content return new Promise((resolve, reject) => { @@ -1305,7 +1366,7 @@ hooks.add('tableExportModule', { if (tableHtml) { resolve( VxeUI.print({ - title: opts.sheetName, + title: printTitle, html: tableHtml, customStyle: opts.style, beforeMethod: beforePrintMethod @@ -1324,7 +1385,7 @@ hooks.add('tableExportModule', { resolve( exportMethods.exportData(opts).then(({ content }: any) => { return VxeUI.print({ - title: opts.sheetName, + title: printTitle, html: content, customStyle: opts.style, beforeMethod: beforePrintMethod @@ -1358,7 +1419,7 @@ hooks.add('tableExportModule', { remote: false, print: true }) - return exportMethods.exportData(opts).then(({ content }) => { + return $xeTable.exportData(opts).then(({ content }) => { return { html: content } @@ -1428,7 +1489,7 @@ hooks.add('tableExportModule', { initStore.import = true }, closeExport: handleCloseExport, - openExport (options: any) { + openExport (options) { const exportOpts = computeExportOpts.value const defOpts = Object.assign({ message: true, @@ -1442,7 +1503,7 @@ hooks.add('tableExportModule', { handleExportAndPrint(defOpts) }, closePrint: handleCloseExport, - openPrint (options: any) { + openPrint (options) { const printOpts = computePrintOpts.value const defOpts = Object.assign({ message: true diff --git a/packages/table/module/validator/hook.ts b/packages/table/module/validator/hook.ts index a3300bc487..e3b4c1a877 100644 --- a/packages/table/module/validator/hook.ts +++ b/packages/table/module/validator/hook.ts @@ -109,7 +109,7 @@ hooks.add('tableValidatorModule', { const beginValidate = (rows: any, cols: VxeTableDefines.ColumnInfo[] | null, cb: any, isFull?: boolean): Promise => { const validRest: any = {} const { editRules, treeConfig } = props - const { afterFullData, visibleColumn } = internalData + const { afterFullData } = internalData const treeOpts = computeTreeOpts.value const childrenField = treeOpts.children || treeOpts.childrenField const validOpts = computeValidOpts.value @@ -230,11 +230,7 @@ hooks.add('tableValidatorModule', { } else { const row = firstErrParams.row const column = firstErrParams.column - const rowIndex = afterFullData.indexOf(row) - const columnIndex = visibleColumn.indexOf(column) - const targetRow = rowIndex > 0 ? afterFullData[rowIndex - 1] : row - const targetColumn = columnIndex > 0 ? visibleColumn[columnIndex - 1] : column - $xeTable.scrollToRow(targetRow, targetColumn).then(posAndFinish) + $xeTable.scrollToRow(row, column).then(posAndFinish) } }) }) diff --git a/packages/table/render/index.ts b/packages/table/render/index.ts index daf83cb6ce..64726ab7c8 100644 --- a/packages/table/render/index.ts +++ b/packages/table/render/index.ts @@ -694,7 +694,9 @@ renderer.mixin({ const { row, column } = params let cellValue = XEUtils.get(row, column.field) if (cellValue) { - cellValue = getLabelFormatDate(cellValue, props) + if (props.type !== 'time') { + cellValue = getLabelFormatDate(cellValue, props) + } } return getCellLabelVNs(renderOpts, params, cellValue) }, diff --git a/packages/table/src/body.ts b/packages/table/src/body.ts index a2744c3414..0e9c99ad1a 100644 --- a/packages/table/src/body.ts +++ b/packages/table/src/body.ts @@ -197,12 +197,12 @@ export default defineComponent({ data: tableData, items } - let isColDragCell = false + let isRowDragCell = false let isDisabledDrag = false if (rowOpts.drag) { - isColDragCell = rowDragOpts.trigger === 'row' || (column.dragSort && rowDragOpts.trigger === 'cell') + isRowDragCell = rowDragOpts.trigger === 'row' || (column.dragSort && rowDragOpts.trigger === 'cell') } - if (isColDragCell) { + if (isRowDragCell) { isDisabledDrag = !!(dragDisabledMethod && dragDisabledMethod(params)) } // hover 进入事件 @@ -233,11 +233,15 @@ export default defineComponent({ } } // 按下事件处理 - if (isColDragCell || checkboxOpts.range || mouseConfig) { + if (isRowDragCell || checkboxOpts.range || mouseConfig) { tdOns.onMousedown = (evnt: MouseEvent) => { $xeTable.triggerCellMousedownEvent(evnt, params) } } + // 拖拽列事件 + if (isRowDragCell) { + tdOns.onMouseup = $xeTable.triggerCellMouseupEvent + } // 点击事件处理 tdOns.onClick = (evnt: MouseEvent) => { $xeTable.triggerCellClickEvent(evnt, params) @@ -379,7 +383,7 @@ export default defineComponent({ 'col--ellipsis': hasEllipsis, 'fixed--width': !isAutoCellWidth, 'fixed--hidden': fixedHiddenColumn, - 'is--drag-cell': isColDragCell, + 'is--drag-cell': isRowDragCell, 'is--drag-disabled': isDisabledDrag, 'col--dirty': isDirty, 'col--active': editConfig && isEdit && (actived.row === row && (actived.column === column || editOpts.mode === 'row')), @@ -762,18 +766,20 @@ export default defineComponent({ }) const renderVN = () => { + const { slots } = tableContext + const { fixedColumn, fixedType, tableColumn } = props const { showOverflow: allColumnOverflow, spanMethod, footerSpanMethod, mouseConfig } = tableProps - const { tableData, scrollXLoad, scrollYLoad, isAllOverflow, isDragRowMove, expandColumn } = tableReactData - const { visibleColumn } = tableInternalData - const { slots } = tableContext + const { isGroup, tableData, scrollXLoad, scrollYLoad, isAllOverflow, isDragRowMove, expandColumn, dragRow, dragCol } = tableReactData + const { visibleColumn, fullAllDataRowIdData, fullColumnIdData } = tableInternalData const rowOpts = computeRowOpts.value const sYOpts = computeSYOpts.value const emptyOpts = computeEmptyOpts.value const mouseOpts = computeMouseOpts.value const rowDragOpts = computeRowDragOpts.value - let renderColumnList = tableColumn + let renderDataList = tableData + let renderColumnList = tableColumn as VxeTableDefines.ColumnInfo[] if (fixedType) { renderColumnList = visibleColumn @@ -788,6 +794,54 @@ export default defineComponent({ } } + // 行拖拽 + if (scrollYLoad && dragRow) { + if (renderDataList.length > 2) { + const dRowRest = fullAllDataRowIdData[getRowid($xeTable, dragRow)] + if (dRowRest) { + const drIndex = dRowRest._index + const firstRow = renderDataList[0] + const lastRow = renderDataList[renderDataList.length - 1] + const firstRowRest = fullAllDataRowIdData[getRowid($xeTable, firstRow)] + const lastRowRest = fullAllDataRowIdData[getRowid($xeTable, lastRow)] + if (firstRowRest && lastRowRest) { + const frIndex = firstRowRest._index + const lrIndex = lastRowRest._index + if (drIndex < frIndex) { + renderDataList = [dragRow].concat(renderDataList) + } else if (drIndex > lrIndex) { + renderDataList = renderDataList.concat([dragRow]) + } + } + } + } + } + + if (!fixedType && !isGroup) { + // 列拖拽 + if (scrollXLoad && dragCol) { + if (renderColumnList.length > 2) { + const dCowRest = fullColumnIdData[dragCol.id] + if (dCowRest) { + const dcIndex = dCowRest._index + const firstCol = renderColumnList[0] + const lastCol = renderColumnList[renderColumnList.length - 1] + const firstColRest = fullColumnIdData[firstCol.id] + const lastColRest = fullColumnIdData[lastCol.id] + if (firstColRest && lastColRest) { + const fcIndex = firstColRest._index + const lcIndex = lastColRest._index + if (dcIndex < fcIndex) { + renderColumnList = [dragCol].concat(renderColumnList) + } else if (dcIndex > lcIndex) { + renderColumnList = renderColumnList.concat([dragCol]) + } + } + } + } + } + } + let emptyContent: string | VxeComponentSlotType | VxeComponentSlotType[] const emptySlot = slots ? slots.empty : null if (emptySlot) { @@ -853,11 +907,11 @@ export default defineComponent({ name: `vxe-body--row-list${isDragRowMove ? '' : '-disabled'}`, tag: 'tbody' }, { - default: () => renderRows(fixedType, tableData, renderColumnList) + default: () => renderRows(fixedType, renderDataList, renderColumnList) }) : h('tbody', { ref: refBodyTBody - }, renderRows(fixedType, tableData, renderColumnList)) + }, renderRows(fixedType, renderDataList, renderColumnList)) ]), h('div', { class: 'vxe-table--checkbox-range' diff --git a/packages/table/src/footer.ts b/packages/table/src/footer.ts index a004bd99f9..dce04beecc 100644 --- a/packages/table/src/footer.ts +++ b/packages/table/src/footer.ts @@ -267,15 +267,15 @@ export default defineComponent({ const renderVN = () => { const { fixedType, fixedColumn, tableColumn } = props const { spanMethod, footerSpanMethod, showFooterOverflow: allColumnFooterOverflow } = tableProps - const { visibleColumn } = tableInternalData - const { scrollbarWidth } = tableReactData + const { visibleColumn, fullColumnIdData } = tableInternalData + const { isGroup, scrollXLoad, scrollYLoad, scrollbarWidth, dragCol } = tableReactData let renderColumnList = tableColumn if (fixedType) { renderColumnList = visibleColumn // 如果是使用优化模式 - if (allColumnFooterOverflow) { + if (scrollXLoad || scrollYLoad || allColumnFooterOverflow) { // 如果不支持优化模式 if (spanMethod || footerSpanMethod) { renderColumnList = visibleColumn @@ -285,6 +285,31 @@ export default defineComponent({ } } + if (!fixedType && !isGroup) { + // 列拖拽 + if (scrollXLoad && dragCol) { + if (renderColumnList.length > 2) { + const dCowRest = fullColumnIdData[dragCol.id] + if (dCowRest) { + const dcIndex = dCowRest._index + const firstCol = renderColumnList[0] + const lastCol = renderColumnList[renderColumnList.length - 1] + const firstColRest = fullColumnIdData[firstCol.id] + const lastColRest = fullColumnIdData[lastCol.id] + if (firstColRest && lastColRest) { + const fcIndex = firstColRest._index + const lcIndex = lastColRest._index + if (dcIndex < fcIndex) { + renderColumnList = [dragCol].concat(renderColumnList) + } else if (dcIndex > lcIndex) { + renderColumnList = renderColumnList.concat([dragCol]) + } + } + } + } + } + } + const ons: Record = {} if (!fixedType) { ons.onScroll = scrollEvent diff --git a/packages/table/src/header.ts b/packages/table/src/header.ts index a353f2b2ce..30cca6c4f7 100644 --- a/packages/table/src/header.ts +++ b/packages/table/src/header.ts @@ -234,6 +234,7 @@ export default defineComponent({ 'is--sortable': column.sortable, 'col--filter': !!column.filters, 'is--filter-active': hasFilter, + 'is--drag-active': !column.fixed && !isDisabledDrag, 'is--drag-disabled': isDisabledDrag, 'col--current': currentColumn === column }, @@ -312,17 +313,19 @@ export default defineComponent({ const renderVN = () => { const { fixedType, fixedColumn, tableColumn } = props const { showHeaderOverflow: allColumnHeaderOverflow, spanMethod, footerSpanMethod } = tableProps - const { isGroup, scrollbarWidth } = tableReactData - const { visibleColumn } = tableInternalData - let headerGroups: VxeTableDefines.ColumnInfo[][] = headerColumn.value + const { isGroup, scrollXLoad, scrollYLoad, scrollbarWidth, dragCol } = tableReactData + const { visibleColumn, fullColumnIdData } = tableInternalData + + let renderHeaderList = headerColumn.value let renderColumnList = tableColumn as VxeTableDefines.ColumnInfo[] + if (isGroup) { renderColumnList = visibleColumn } else { if (fixedType) { renderColumnList = visibleColumn // 如果是使用优化模式 - if (allColumnHeaderOverflow) { + if (scrollXLoad || scrollYLoad || allColumnHeaderOverflow) { // 如果不支持优化模式 if (spanMethod || footerSpanMethod) { renderColumnList = visibleColumn @@ -331,8 +334,36 @@ export default defineComponent({ } } } - headerGroups = [renderColumnList] + renderHeaderList = [renderColumnList] + } + + if (!fixedType && !isGroup) { + // 列拖拽 + if (scrollXLoad && dragCol) { + if (renderColumnList.length > 2) { + const dCowRest = fullColumnIdData[dragCol.id] + if (dCowRest) { + const dcIndex = dCowRest._index + const firstCol = renderColumnList[0] + const lastCol = renderColumnList[renderColumnList.length - 1] + const firstColRest = fullColumnIdData[firstCol.id] + const lastColRest = fullColumnIdData[lastCol.id] + if (firstColRest && lastColRest) { + const fcIndex = firstColRest._index + const lcIndex = lastColRest._index + if (dcIndex < fcIndex) { + renderColumnList = [dragCol].concat(renderColumnList) + renderHeaderList = [[dragCol].concat(renderHeaderList[0])].concat(renderHeaderList.slice(1)) + } else if (dcIndex > lcIndex) { + renderColumnList = renderColumnList.concat([dragCol]) + renderHeaderList = [renderHeaderList[0].concat([dragCol])].concat(renderHeaderList.slice(1)) + } + } + } + } + } } + return h('div', { ref: refElem, class: ['vxe-table--header-wrapper', fixedType ? `fixed-${fixedType}--wrapper` : 'body--wrapper'], @@ -374,7 +405,7 @@ export default defineComponent({ */ h('thead', { ref: refHeaderTHead - }, renderHeads(headerGroups)) + }, renderHeads(renderHeaderList)) ]), /** * 其他 diff --git a/packages/table/src/table.ts b/packages/table/src/table.ts index 301ed4801a..484d235892 100644 --- a/packages/table/src/table.ts +++ b/packages/table/src/table.ts @@ -1,6 +1,6 @@ import { defineComponent, h, ComponentPublicInstance, reactive, ref, Ref, provide, inject, nextTick, onActivated, onDeactivated, onBeforeUnmount, onUnmounted, watch, computed, ComputedRef, onMounted } from 'vue' import XEUtils from 'xe-utils' -import { tpImg, browse, isPx, isScale, hasClass, addClass, removeClass, getEventTargetNode, getPaddingTopBottomSize, setScrollTop, setScrollLeft, isNodeElement } from '../../ui/src/dom' +import { browse, initTpImg, getTpImg, isPx, isScale, hasClass, addClass, removeClass, getEventTargetNode, getPaddingTopBottomSize, setScrollTop, setScrollLeft, isNodeElement } from '../../ui/src/dom' import { getLastZIndex, nextZIndex, hasChildrenList, getFuncText, isEnableConf, formatText, eqEmptyValue } from '../../ui/src/utils' import { VxeUI } from '../../ui' import Cell from './cell' @@ -18,13 +18,11 @@ import TableImportPanelComponent from '../module/export/import-panel' import TableExportPanelComponent from '../module/export/export-panel' import TableMenuPanelComponent from '../module/menu/panel' -import type { VxeLoadingComponent, VxeTooltipInstance, VxeTooltipComponent, VxeTabsConstructor, VxeTabsPrivateMethods, ValueOf, VxeComponentSlotType, VxeComponentStyleType } from 'vxe-pc-ui' +import type { VxeLoadingComponent, VxeTooltipInstance, VxeTooltipComponent, VxeTabsConstructor, VxeTabsPrivateMethods, ValueOf, VxeComponentSlotType } from 'vxe-pc-ui' import type { VxeGridConstructor, VxeGridPrivateMethods, VxeTableConstructor, TableReactData, TableInternalData, VxeTablePropTypes, VxeToolbarConstructor, TablePrivateMethods, VxeTablePrivateRef, VxeTablePrivateComputed, VxeTablePrivateMethods, TableMethods, VxeTableMethods, VxeTableDefines, VxeTableEmits, VxeTableProps, VxeColumnPropTypes } from '../../../types' const { getConfig, getIcon, getI18n, renderer, formats, createEvent, globalResize, interceptor, hooks, globalEvents, GLOBAL_EVENT_KEYS, useFns, renderEmptyElement } = VxeUI -// const isWebkit = browse['-webkit'] && !browse.edge - const customStorageKey = 'VXE_CUSTOM_STORE' export default defineComponent({ @@ -681,16 +679,6 @@ export default defineComponent({ } }) - const computeTableStyle = computed(() => { - const { tableData } = reactData - const columnOpts = computeColumnOpts.value - const stys: VxeComponentStyleType = {} - if (columnOpts.drag) { - stys['--vxe-ui-table-drag-column-move-delay'] = `${Math.max(0.06, Math.min(0.3, tableData.length / 800))}s` - } - return stys - }) - const refMaps: VxeTablePrivateRef = { refElem, refTooltip, @@ -1442,26 +1430,25 @@ export default defineComponent({ tableData.forEach(row => { const rowid = getRowid($xeTable, row) const rowRest = fullAllDataRowIdData[rowid] - const trList = el.querySelectorAll(`.vxe-body--row[rowid="${rowid}"]`) - if (rowRest && trList.length) { + const cellList = el.querySelectorAll(`.vxe-body--row[rowid="${rowid}"]>.vxe-body--column>.vxe-cell`) + if (rowRest && cellList.length) { let height = 0 - for (let i = 0; i < trList.length; i++) { - const trEl = trList[i] as HTMLTableRowElement - const tdList = trEl.children - for (let j = 0; j < tdList.length; j++) { - const tdEl = tdList[j] - const cellElem = tdEl.querySelector('.vxe-cell') as HTMLDivElement - if (!calcPadding) { - paddingTop = XEUtils.toNumber(getComputedStyle(tdEl).paddingTop) - paddingBottom = XEUtils.toNumber(getComputedStyle(tdEl).paddingBottom) - calcPadding = true - } - let cellHeight = paddingTop + paddingBottom - if (cellElem) { - cellHeight += cellElem.offsetHeight - } - height = Math.max(height, cellHeight) + for (let i = 0; i < cellList.length; i++) { + const cellElem = cellList[i] as HTMLElement + const tdEl = cellElem.parentElement as HTMLTableCellElement + if (!tdEl || !tdEl.clientWidth) { + break + } + if (!calcPadding) { + paddingTop = XEUtils.toNumber(getComputedStyle(tdEl).paddingTop) + paddingBottom = XEUtils.toNumber(getComputedStyle(tdEl).paddingBottom) + calcPadding = true } + let cellHeight = paddingTop + paddingBottom + if (cellElem) { + cellHeight += cellElem.clientHeight + } + height = Math.max(height, cellHeight) } rowRest.height = scrollXLoad ? Math.max(rowRest.height, height) : height } @@ -1799,7 +1786,7 @@ export default defineComponent({ if (fixedType) { renderColumnList = visibleColumn // 如果是使用优化模式 - if (allColumnHeaderOverflow) { + if (scrollXLoad || scrollYLoad || allColumnHeaderOverflow) { // 如果不支持优化模式 if (spanMethod || footerSpanMethod) { renderColumnList = visibleColumn @@ -1920,7 +1907,7 @@ export default defineComponent({ if (fixedType) { renderColumnList = visibleColumn // 如果是使用优化模式 - if (allColumnFooterOverflow) { + if (scrollXLoad || scrollYLoad || allColumnFooterOverflow) { // 如果不支持优化模式 if (spanMethod || footerSpanMethod) { renderColumnList = visibleColumn @@ -2518,6 +2505,7 @@ export default defineComponent({ const handleRecalculateLayout = (reFull: boolean) => { const el = refElem.value + internalData.reRunTime = Date.now() if (!el || !el.clientWidth) { return nextTick() } @@ -2592,7 +2580,6 @@ export default defineComponent({ editStore.insertMaps = {} editStore.removeMaps = {} const sYLoad = updateScrollYStatus(fullData) - reactData.scrollYLoad = sYLoad reactData.isDragRowMove = false // 全量数据 internalData.tableFullData = fullData @@ -2709,6 +2696,14 @@ export default defineComponent({ reactData.tableColumn = tableColumn } + const handleUpdateColumn = () => { + const columnList = XEUtils.orderBy(internalData.collectColumn, 'renderSortNumber') + internalData.collectColumn = columnList + const tableFullColumn = getColumnList(columnList) + internalData.tableFullColumn = tableFullColumn + cacheColumnMap() + } + const loadScrollXData = () => { const { mergeList, mergeFooterList } = reactData const { scrollXStore } = internalData @@ -2739,7 +2734,7 @@ export default defineComponent({ return result } - const parseColumns = () => { + const parseColumns = (isReset: boolean) => { const { showOverflow } = props const rowOpts = computeRowOpts.value const leftList: VxeTableDefines.ColumnInfo[] = [] @@ -2828,10 +2823,12 @@ export default defineComponent({ warnLog('vxe.error.scrollErrProp', ['footer-span-method']) } } - const { visibleSize } = handleVirtualXVisible() - scrollXStore.startIndex = 0 - scrollXStore.endIndex = visibleSize - scrollXStore.visibleSize = visibleSize + if (isReset) { + const { visibleSize } = handleVirtualXVisible() + scrollXStore.startIndex = 0 + scrollXStore.endIndex = visibleSize + scrollXStore.visibleSize = visibleSize + } } // 如果列被显示/隐藏,则清除合并状态 // 如果列被设置为固定,则清除合并状态 @@ -2849,12 +2846,15 @@ export default defineComponent({ }) internalData.visibleColumn = visibleColumn handleTableColumn() - return tableMethods.updateFooter().then(() => { - return tableMethods.recalculate() - }).then(() => { - tableMethods.updateCellAreas() - return tableMethods.recalculate() - }) + if (isReset) { + return tableMethods.updateFooter().then(() => { + return tableMethods.recalculate() + }).then(() => { + tableMethods.updateCellAreas() + return tableMethods.recalculate() + }) + } + return tableMethods.updateFooter() } const initColumnSort = () => { @@ -2878,7 +2878,7 @@ export default defineComponent({ ).then(() => { reactData._isLoading = false cacheColumnMap() - parseColumns().then(() => { + parseColumns(true).then(() => { if (reactData.scrollXLoad) { loadScrollXData() } @@ -3105,12 +3105,14 @@ export default defineComponent({ } const scrollXEvent = (evnt: Event) => { - const { inFooterScroll, inBodyScroll, lastScrollTop } = internalData - if (inFooterScroll) { - return - } - if (inBodyScroll) { - return + const { intoRunScroll, inFooterScroll, inBodyScroll, lastScrollTop } = internalData + if (!intoRunScroll) { + if (inFooterScroll) { + return + } + if (inBodyScroll) { + return + } } const tableHeader = refTableHeader.value const tableBody = refTableBody.value @@ -3141,14 +3143,15 @@ export default defineComponent({ } const scrollYEvent = (evnt: Event) => { - const { inFooterScroll, inBodyScroll, lastScrollLeft } = internalData - if (inFooterScroll) { - return - } - if (inBodyScroll) { - return + const { intoRunScroll, inFooterScroll, inBodyScroll, lastScrollLeft } = internalData + if (!intoRunScroll) { + if (inFooterScroll) { + return + } + if (inBodyScroll) { + return + } } - const tableBody = refTableBody.value const leftBody = refTableLeftBody.value const rightBody = refTableRightBody.value @@ -3187,8 +3190,8 @@ export default defineComponent({ internalData.lcsTimeout = undefined internalData.inVirtualScroll = false internalData.inBodyScroll = false - internalData.bodyScrollType = '' internalData.inFooterScroll = false + internalData.bodyScrollType = '' if (isRollX && scrollXLoad) { tablePrivateMethods.updateScrollXData() } @@ -4066,15 +4069,11 @@ export default defineComponent({ * 将固定的列左边、右边分别靠边 * 如果传 true 则会检查列顺序并排序 */ - refreshColumn (initOrder) { - if (initOrder) { - const columnList = XEUtils.orderBy(internalData.collectColumn, 'renderSortNumber') - internalData.collectColumn = columnList - const tableFullColumn = getColumnList(columnList) - internalData.tableFullColumn = tableFullColumn - cacheColumnMap() + refreshColumn (initSort) { + if (initSort) { + handleUpdateColumn() } - return parseColumns().then(() => { + return parseColumns(true).then(() => { return tableMethods.refreshScroll() }).then(() => { return tableMethods.recalculate() @@ -4116,16 +4115,24 @@ export default defineComponent({ */ recalculate (reFull?: boolean) { return new Promise(resolve => { - const { rceTimeout } = internalData + const { rceTimeout, reRunTime } = internalData + const resizeOpts = computeResizeOpts.value + const refreshDelay = resizeOpts.refreshDelay || 20 const el = refElem.value if (el && el.clientWidth) { autoCellWidth() } if (rceTimeout) { clearTimeout(rceTimeout) - nextTick(() => { - resolve() - }) + if (reRunTime && reRunTime + (refreshDelay - 5) < Date.now()) { + resolve( + handleRecalculateLayout(!!reFull) + ) + } else { + nextTick(() => { + resolve() + }) + } } else { resolve( handleRecalculateLayout(!!reFull) @@ -4134,7 +4141,7 @@ export default defineComponent({ internalData.rceTimeout = setTimeout(() => { internalData.rceTimeout = undefined handleRecalculateLayout(!!reFull) - }, 20) + }, refreshDelay) }) }, openTooltip (target, content) { @@ -5107,6 +5114,7 @@ export default defineComponent({ const rightBodyElem = rightBody ? rightBody.$el as HTMLDivElement : null const tableHeaderElem = tableHeader ? tableHeader.$el as HTMLDivElement : null const tableFooterElem = tableFooter ? tableFooter.$el as HTMLDivElement : null + internalData.intoRunScroll = true if (XEUtils.isNumber(scrollLeft)) { const xHandleEl = refScrollXHandleElem.value if (xHandleEl) { @@ -5128,9 +5136,10 @@ export default defineComponent({ } } if (reactData.scrollXLoad || reactData.scrollYLoad) { - return new Promise(resolve => { + return new Promise(resolve => { setTimeout(() => { nextTick(() => { + internalData.intoRunScroll = false resolve() }) }, 30) @@ -6012,6 +6021,19 @@ export default defineComponent({ } } + const clearDragStatus = () => { + const { dragRow, dragCol } = reactData + if (dragRow || dragCol) { + clearColDropOrigin() + clearRowDropOrigin() + hideDropTip() + reactData.dragRow = null + reactData.dragCol = null + reactData.isDragColMove = false + reactData.isDragRowMove = false + } + } + const clearRowDropOrigin = () => { const el = refElem.value if (el) { @@ -6099,15 +6121,22 @@ export default defineComponent({ const { scrollbarWidth, scrollbarHeight } = reactData const { prevDragToChild } = internalData const wrapperRect = el.getBoundingClientRect() + const tableWidth = el.clientWidth + const tableHeight = el.clientHeight if (trEl) { const rdLineEl = refDragRowLineElem.value if (rdLineEl) { if (showLine) { const trRect = trEl.getBoundingClientRect() + let trHeight = trEl.clientHeight + const offsetTop = Math.max(1, trRect.y - wrapperRect.y) + if (offsetTop + trHeight > tableHeight - scrollbarHeight) { + trHeight = tableHeight - offsetTop - scrollbarHeight + } rdLineEl.style.display = 'block' - rdLineEl.style.top = `${Math.max(1, trRect.y - wrapperRect.y)}px` - rdLineEl.style.height = `${trRect.height}px` - rdLineEl.style.width = `${wrapperRect.width - scrollbarWidth}px` + rdLineEl.style.top = `${offsetTop}px` + rdLineEl.style.height = `${trHeight}px` + rdLineEl.style.width = `${tableWidth - scrollbarWidth}px` rdLineEl.setAttribute('drag-pos', dragPos) rdLineEl.setAttribute('drag-to-child', prevDragToChild ? 'y' : 'n') } else { @@ -6118,16 +6147,31 @@ export default defineComponent({ const cdLineEl = refDragColLineElem.value if (cdLineEl) { if (showLine) { + const leftContainerElem = refLeftContainer.value + const leftContainerWidth = leftContainerElem ? leftContainerElem.clientWidth : 0 + const rightContainerElem = refRightContainer.value + const rightContainerWidth = rightContainerElem ? rightContainerElem.clientWidth : 0 const thRect = thEl.getBoundingClientRect() + let thWidth = thEl.clientWidth const offsetTop = Math.max(0, thRect.y - wrapperRect.y) + const startX = leftContainerWidth + let offsetLeft = thRect.x - wrapperRect.x + if (offsetLeft < startX) { + thWidth -= startX - offsetLeft + offsetLeft = startX + } + const endX = tableWidth - rightContainerWidth - (rightContainerWidth ? 0 : scrollbarWidth) + if (offsetLeft + thWidth > endX) { + thWidth = endX - offsetLeft + } cdLineEl.style.display = 'block' cdLineEl.style.top = `${offsetTop}px` - cdLineEl.style.left = `${Math.max(1, thRect.x - wrapperRect.x)}px` - cdLineEl.style.width = `${thRect.width}px` + cdLineEl.style.left = `${offsetLeft}px` + cdLineEl.style.width = `${thWidth}px` if (prevDragToChild) { cdLineEl.style.height = `${thRect.height}px` } else { - cdLineEl.style.height = `${wrapperRect.height - offsetTop - scrollbarHeight}px` + cdLineEl.style.height = `${tableHeight - offsetTop - scrollbarHeight}px` } cdLineEl.setAttribute('drag-pos', dragPos) cdLineEl.setAttribute('drag-to-child', prevDragToChild ? 'y' : 'n') @@ -6884,7 +6928,7 @@ export default defineComponent({ const isRadioType = type === 'radio' const isCheckboxType = type === 'checkbox' const isExpandType = type === 'expand' - const cell = evnt.currentTarget + const cell = evnt.currentTarget as HTMLDivElement const triggerRadio = isRadioType && getEventTargetNode(evnt, cell, 'vxe-cell--radio').flag const triggerCheckbox = isCheckboxType && getEventTargetNode(evnt, cell, 'vxe-cell--checkbox').flag const triggerTreeNode = treeNode && getEventTargetNode(evnt, cell, 'vxe-tree--btn-wrapper').flag @@ -6965,7 +7009,7 @@ export default defineComponent({ const { editStore } = reactData const editOpts = computeEditOpts.value const { actived } = editStore - const cell = evnt.currentTarget + const cell = evnt.currentTarget as HTMLDivElement params = Object.assign({ cell }, params) if (isEnableConf(editConfig) && editOpts.trigger === 'dblclick') { if (!actived.args || evnt.currentTarget !== actived.args.cell) { @@ -7197,7 +7241,8 @@ export default defineComponent({ const triggerFilter = getEventTargetNode(evnt, cell, 'vxe-cell--filter').flag let triggerDrag = false if (!(triggerInput || triggerCheckbox || triggerSort || triggerFilter)) { - if (columnOpts.drag && trigger === 'cell' && !(disabledMethod && disabledMethod(params))) { + const { column } = params + if (columnOpts.drag && !column.fixed && trigger === 'cell' && !(disabledMethod && disabledMethod(params))) { triggerDrag = true $xeTable.handleHeaderCellDragMousedownEvent(evnt, params) } @@ -7222,7 +7267,7 @@ export default defineComponent({ const rowOpts = computeRowOpts.value const rowDragOpts = computeRowDragOpts.value const { trigger, disabledMethod } = rowDragOpts - const cell = evnt.currentTarget + const cell = evnt.currentTarget as HTMLElement params.cell = cell const triggerInput = cell && cell.tagName && cell.tagName.toLowerCase() === 'input' const triggerRadio = isRadioType && getEventTargetNode(evnt, cell, 'vxe-cell--radio').flag @@ -7249,14 +7294,15 @@ export default defineComponent({ $xeTable.closeMenu() } }, + triggerCellMouseupEvent () { + clearDragStatus() + }, /** * 行拖拽 */ handleRowDragDragstartEvent (evnt) { if (evnt.dataTransfer) { - const img = new Image() - img.src = tpImg - evnt.dataTransfer.setDragImage(img, 0, 0) + evnt.dataTransfer.setDragImage(getTpImg(), 0, 0) } }, handleRowDragDragendEvent (evnt) { @@ -7499,22 +7545,23 @@ export default defineComponent({ dispatchEvent('row-dragstart', params, evnt) }, handleCellDragMouseupEvent () { - clearRowDropOrigin() - hideDropTip() - reactData.dragRow = null - reactData.dragCol = null - reactData.isDragRowMove = false + clearDragStatus() }, /** * 列拖拽 */ handleHeaderCellDragDragstartEvent (evnt) { if (evnt.dataTransfer) { - const img = new Image() - img.src = tpImg - evnt.dataTransfer.setDragImage(img, 0, 0) + evnt.dataTransfer.setDragImage(getTpImg(), 0, 0) } }, + handleColDragSwapColumn () { + handleUpdateColumn() + parseColumns(false).then(() => { + $xeTable.updateCellAreas() + $xeTable.saveCustomStore('update:sort') + }) + }, handleColDragSwapEvent (evnt, isSyncColumn, dragCol, prevDragCol, prevDragPos, prevDragToChild) { const { mouseConfig } = props const columnDragOpts = computeColumnDragOpts.value @@ -7665,10 +7712,7 @@ export default defineComponent({ }, evnt) if (isSyncColumn) { - return tableMethods.refreshColumn(true).then(() => { - $xeTable.updateCellAreas() - return $xeTable.saveCustomStore('update:sort') - }) + $xeTable.handleColDragSwapColumn() } }).catch(() => { }) @@ -7687,6 +7731,9 @@ export default defineComponent({ reactData.dragCol = null setTimeout(() => { reactData.isDragColMove = false + $xeTable.recalculate().then(() => { + loadScrollXData() + }) }, 500) }, handleHeaderCellDragDragoverEvent (evnt) { @@ -7703,10 +7750,10 @@ export default defineComponent({ const column = $xeTable.getColumnById(colid) if (column) { evnt.preventDefault() - const { dragCol } = reactData - const offsetX = evnt.clientX - thEl.getBoundingClientRect().x + const { clientX } = evnt + const offsetX = clientX - thEl.getBoundingClientRect().x const dragPos = offsetX < thEl.clientWidth / 2 ? 'left' : 'right' - if ((dragCol && dragCol.id === column.id) || (!isCrossDrag && column.parentId)) { + if (column.fixed || (dragCol && dragCol.id === column.id) || (!isCrossDrag && column.parentId)) { showDropTip(evnt, null, thEl, false, dragPos) return } @@ -7719,6 +7766,36 @@ export default defineComponent({ targetColumn: column, dragPos }, evnt) + + // 边缘滚动 + const el = refElem.value + if (!el) { + return + } + const xHandleEl = refScrollXHandleElem.value + const tableBody = refTableBody.value + const tableBodyElem = tableBody ? tableBody.$el as HTMLDivElement : null + const scrollTargetEl = xHandleEl || tableBodyElem + if (scrollTargetEl) { + const wrapperRect = el.getBoundingClientRect() + const tableWidth = el.clientWidth + const leftContainerElem = refLeftContainer.value + const leftContainerWidth = leftContainerElem ? leftContainerElem.clientWidth : 0 + const rightContainerElem = refRightContainer.value + const rightContainerWidth = rightContainerElem ? rightContainerElem.clientWidth : 0 + const srartX = wrapperRect.x + leftContainerWidth + const endX = wrapperRect.x + tableWidth - rightContainerWidth + const distSize = 28 + const startDistSize = clientX - srartX + const endDistSize = endX - clientX + if (startDistSize > 0 && startDistSize <= distSize) { + const scrollRatio = Math.floor(tableWidth / (startDistSize > distSize / 2 ? 240 : 120)) + scrollTargetEl.scrollLeft -= scrollRatio * (distSize - startDistSize) + } else if (endDistSize > 0 && endDistSize <= distSize) { + const scrollRatio = Math.floor(tableWidth / (endDistSize > distSize / 2 ? 240 : 120)) + scrollTargetEl.scrollLeft += scrollRatio * (distSize - endDistSize) + } + } } }, handleHeaderCellDragMousedownEvent (evnt, params) { @@ -7992,7 +8069,6 @@ export default defineComponent({ }, updateScrollXData () { const { showOverflow } = props - // reactData.tableColumn = [] return nextTick().then(() => { handleTableColumn() calcCellHeight() @@ -8003,7 +8079,6 @@ export default defineComponent({ }) }, updateScrollYData () { - // reactData.tableData = [] return nextTick().then(() => { tablePrivateMethods.handleTableData() calcCellHeight() @@ -8264,7 +8339,6 @@ export default defineComponent({ const virtualScrollBars = computeVirtualScrollBars.value const resizableOpts = computeResizableOpts.value const isArea = mouseConfig && mouseOpts.area - const tableStyle = computeTableStyle.value const columnDragOpts = computeColumnDragOpts.value return h('div', { ref: refElem, @@ -8300,7 +8374,6 @@ export default defineComponent({ 'is--virtual-x': scrollXLoad, 'is--virtual-y': scrollYLoad }], - style: tableStyle, spellcheck: false, onKeydown: keydownEvent }, [ @@ -8638,6 +8711,12 @@ export default defineComponent({ nextTick(() => tableMethods.recalculate(true)) }) + watch(computeSize, () => { + nextTick(() => { + tableMethods.recalculate(true).then(() => tableMethods.refreshScroll()) + }) + }) + watch(() => props.syncResize, (value) => { if (value) { handleUupdateResize() @@ -8711,6 +8790,14 @@ export default defineComponent({ }) onMounted(() => { + const columnOpts = computeColumnOpts.value + const rowOpts = computeRowOpts.value + const customOpts = computeCustomOpts.value + + if (columnOpts.drag || rowOpts.drag || customOpts.allowSort) { + initTpImg() + } + nextTick(() => { const { data, treeConfig, showOverflow } = props const { scrollXStore, scrollYStore } = internalData @@ -8869,22 +8956,13 @@ export default defineComponent({ }) if (props.autoResize) { - const resizeOpts = computeResizeOpts.value - const { refreshDelay } = resizeOpts const el = refElem.value const parentEl = tablePrivateMethods.getParentElem() - const handleOptimizeResize = refreshDelay ? XEUtils.throttle(() => tableMethods.recalculate(true), refreshDelay, { leading: true, trailing: true }) : null - resizeObserver = globalResize.create(handleOptimizeResize - ? () => { - if (props.autoResize) { - handleOptimizeResize() - } - } - : () => { - if (props.autoResize) { - tableMethods.recalculate(true) - } - }) + resizeObserver = globalResize.create(() => { + if (props.autoResize) { + tableMethods.recalculate(true) + } + }) if (el) { resizeObserver.observe(el) } diff --git a/packages/table/src/util.ts b/packages/table/src/util.ts index 41e2b66564..4a5c75d01b 100644 --- a/packages/table/src/util.ts +++ b/packages/table/src/util.ts @@ -69,12 +69,18 @@ export const convertHeaderColumnToRows = (originColumns: any): any[][] => { } export function restoreScrollLocation ($xeTable: VxeTableConstructor, scrollLeft: number, scrollTop: number) { - const { internalData } = $xeTable + const internalData = $xeTable.internalData + return $xeTable.clearScroll().then(() => { if (scrollLeft || scrollTop) { // 重置最后滚动状态 internalData.lastScrollLeft = 0 internalData.lastScrollTop = 0 + + internalData.inVirtualScroll = false + internalData.inBodyScroll = false + internalData.inFooterScroll = false + internalData.bodyScrollType = '' // 还原滚动状态 return $xeTable.scrollTo(scrollLeft, scrollTop) } diff --git a/packages/toolbar/src/toolbar.ts b/packages/toolbar/src/toolbar.ts index 514cb99956..17986acf14 100644 --- a/packages/toolbar/src/toolbar.ts +++ b/packages/toolbar/src/toolbar.ts @@ -4,7 +4,7 @@ import { VxeUI } from '../../ui' import { getSlotVNs } from '../../ui/src/vn' import { warnLog, errLog } from '../../ui/src/log' -import type { ValueOf, VxeButtonComponent, VxeButtonEvents } from 'vxe-pc-ui' +import type { ValueOf, VxeButtonComponent, VxeButtonEvents, VxeComponentSlotType } from 'vxe-pc-ui' import type { VxeGridConstructor, GridPrivateMethods, ToolbarMethods, ToolbarInternalData, VxeToolbarConstructor, VxeToolbarEmits, VxeToolbarPropTypes, ToolbarPrivateRef, ToolbarReactData } from '../../../types' const { getConfig, getIcon, getI18n, renderer, commands, createEvent, useFns } = VxeUI @@ -326,13 +326,18 @@ export default defineComponent({ /** * 渲染按钮 */ - const renderBtns = () => { + const renderLeftBtns = () => { const { buttons } = props const { connectTable } = internalData const $table = connectTable - const btnVNs: VNode[] = [] + const buttonPrefixSlot = slots.buttonPrefix || slots['button-prefix'] + const buttonSuffixSlot = slots.buttonSuffix || slots['button-suffix'] + const btnVNs: VxeComponentSlotType[] = [] + if (buttonPrefixSlot) { + btnVNs.push(...getSlotVNs(buttonPrefixSlot({ buttons: buttons || [], $grid: $xeGrid, $table: $table }))) + } if (buttons) { - buttons.forEach((item) => { + buttons.forEach((item, index) => { const { dropdowns, buttonRender } = item if (item.visible !== false) { const compConf = buttonRender ? renderer.get(buttonRender.name) : null @@ -341,6 +346,7 @@ export default defineComponent({ const params = { $grid: $xeGrid, $table: $table!, button: item } btnVNs.push( h('span', { + key: `br${item.code || index}`, class: ['vxe-button--item', toolbarButtonClassName ? (XEUtils.isFunction(toolbarButtonClassName) ? toolbarButtonClassName(params) : toolbarButtonClassName) : ''] }, getSlotVNs(compConf.renderToolbarButton(buttonRender, params))) ) @@ -348,6 +354,7 @@ export default defineComponent({ if (VxeUIButtonComponent) { btnVNs.push( h(VxeUIButtonComponent, { + key: `bd${item.code || index}`, disabled: item.disabled, loading: item.loading, type: item.type, @@ -377,6 +384,9 @@ export default defineComponent({ } }) } + if (buttonSuffixSlot) { + btnVNs.push(...getSlotVNs(buttonSuffixSlot({ buttons: buttons || [], $grid: $xeGrid, $table: $table }))) + } return btnVNs } @@ -387,7 +397,12 @@ export default defineComponent({ const { tools } = props const { connectTable } = internalData const $table = connectTable - const btnVNs: VNode[] = [] + const toolPrefixSlot = slots.toolPrefix || slots['tool-prefix'] + const toolSuffixSlot = slots.toolSuffix || slots['tool-suffix'] + const btnVNs: VxeComponentSlotType[] = [] + if (toolPrefixSlot) { + btnVNs.push(...getSlotVNs(toolPrefixSlot({ tools: tools || [], $grid: $xeGrid, $table: $table }))) + } if (tools) { tools.forEach((item, tIndex) => { const { dropdowns, toolRender } = item @@ -437,6 +452,9 @@ export default defineComponent({ } }) } + if (toolSuffixSlot) { + btnVNs.push(...getSlotVNs(toolSuffixSlot({ tools: tools || [], $grid: $xeGrid, $table: $table }))) + } return btnVNs } @@ -553,7 +571,7 @@ export default defineComponent({ }, [ h('div', { class: 'vxe-buttons--wrapper' - }, buttonsSlot ? buttonsSlot({ $grid: $xeGrid, $table: $table }) : renderBtns()), + }, buttonsSlot ? buttonsSlot({ $grid: $xeGrid, $table: $table }) : renderLeftBtns()), h('div', { class: 'vxe-tools--wrapper' }, toolsSlot ? toolsSlot({ $grid: $xeGrid, $table: $table }) : renderRightTools()), diff --git a/packages/ui/index.ts b/packages/ui/index.ts index ea54196137..2f3f46315c 100644 --- a/packages/ui/index.ts +++ b/packages/ui/index.ts @@ -37,7 +37,7 @@ VxeUI.setConfig({ // keyField: '_X_ROW_KEY' // 行数据的唯一主键字段名 // }, resizeConfig: { - // refreshDelay: 250 + // refreshDelay: 20 }, resizableConfig: { dragMode: 'auto', @@ -49,11 +49,13 @@ VxeUI.setConfig({ }, rowDragConfig: { showIcon: true, - animation: true + animation: true, + showGuidesStatus: true }, columnDragConfig: { showIcon: true, - animation: true + animation: true, + showGuidesStatus: true }, checkboxConfig: { // trigger: 'default', diff --git a/packages/ui/src/dom.ts b/packages/ui/src/dom.ts index 198e0a40f9..d41c3b3b82 100644 --- a/packages/ui/src/dom.ts +++ b/packages/ui/src/dom.ts @@ -4,7 +4,22 @@ const reClsMap: { [key: string]: any } = {} export const browse = XEUtils.browse() -export const tpImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' +let tpImgEl: HTMLImageElement | undefined + +export function initTpImg () { + if (!tpImgEl) { + tpImgEl = new Image() + tpImgEl.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' + } + return tpImgEl +} + +export function getTpImg () { + if (!tpImgEl) { + return initTpImg() + } + return tpImgEl +} export function getPropClass (property: any, params: any) { return property ? XEUtils.isFunction(property) ? property(params) : property : '' diff --git a/packages/ui/src/vn.ts b/packages/ui/src/vn.ts index 9342c8794b..07144c9b2c 100644 --- a/packages/ui/src/vn.ts +++ b/packages/ui/src/vn.ts @@ -32,6 +32,9 @@ export function getChangeEvent (renderOpts: any) { } export function getSlotVNs (vns: VxeComponentSlotType | VxeComponentSlotType[]) { + if (vns === null || vns === undefined) { + return [] + } if (XEUtils.isArray(vns)) { return vns } diff --git a/styles/components/table.scss b/styles/components/table.scss index 56f72d10fb..2680e5f5ba 100644 --- a/styles/components/table.scss +++ b/styles/components/table.scss @@ -732,7 +732,7 @@ &.col--drag-cell { .vxe-header--column { user-select: none; - &:not(.is--drag-disabled) { + &.is--drag-active { cursor: grab; &:active { cursor: grabbing; @@ -1377,7 +1377,6 @@ .vxe-header--col-list-move { transition-property: transform; transition-duration: 0.35s; - transition-delay: var(--vxe-ui-table-drag-column-move-delay, 0.3s); } .vxe-table--drag-col-line, @@ -1459,7 +1458,6 @@ .vxe-body--row-list-move { transition-property: transform; transition-duration: 0.35s; - transition-delay: 0.05s; } .vxe-table--drag-sort-tip { display: none;