diff --git a/docs/assets/guide/en/sheet/edit_control.md b/docs/assets/guide/en/sheet/edit_control.md new file mode 100755 index 000000000..0ff977d75 --- /dev/null +++ b/docs/assets/guide/en/sheet/edit_control.md @@ -0,0 +1,109 @@ +# Edit Capability Control + +In `VTableSheet`, the editing capability is enabled by default. However, in some scenarios (such as preview mode or permission control), you may need to disable editing globally or for specific sheets. + +VTableSheet provides flexible configuration options to control the editing capability and keyboard shortcuts at both the global and sheet levels. + +## Configuration Options + +### editable + +- **Type**: `boolean` +- **Default**: `true` (if not configured globally or on the sheet) +- **Description**: Controls whether the table is editable. + - When set to `false`, the table enters **Read-Only Mode**: + - Double-click editing is disabled. + - Editors are not registered. + - Modification shortcuts (Cut, Paste, etc.) are disabled. + - Context menu items related to modification (Insert/Delete rows, Merge cells, etc.) are hidden or disabled. + - The Delete/Backspace key will not clear cell content (unless configured otherwise). + - **Priority**: The configuration on a specific sheet (`ISheetDefine.editable`) takes precedence over the global configuration (`IVTableSheetOptions.editable`). + +### keyboardShortcutPolicy + +- **Type**: `SheetKeyboardShortcutPolicy` +- **Description**: Defines the policy for keyboard shortcuts, allowing fine-grained control over specific actions. +- **Properties**: + - `copySelected` (boolean): Enable copy shortcut (Ctrl+C). Default `true`. + - `cutSelected` (boolean): Enable cut shortcut (Ctrl+X). Default `true` (disabled in Read-Only mode). + - `pasteValueToCell` (boolean): Enable paste shortcut (Ctrl+V). Default `true` (disabled in Read-Only mode). + - `selectAllOnCtrlA` (boolean): Enable select all shortcut (Ctrl+A). Default `true`. + - `deleteRange` (boolean): Enable clearing cell content with Delete/Backspace. Default `true` (disabled in Read-Only mode). + - ... (other navigation shortcuts like `moveFocusCellOnTab`, `editCellOnEnter`, etc.) + +## Usage Examples + +### 1. Global Read-Only Mode + +You can set the entire workbook to read-only by configuring `editable: false` in the initialization options. + +```typescript +import { VTableSheet } from '@visactor/vtable-sheet'; + +const sheet = new VTableSheet(container, { + editable: false, // Global read-only + sheets: [ + { + sheetKey: 'sheet1', + data: data1 + }, + { + sheetKey: 'sheet2', + data: data2 + } + ] +}); +``` + +### 2. Mixed Mode (Global Read-Only with Specific Editable Sheets) + +You can set the global default to read-only, and enable editing for specific sheets. + +```typescript +const sheet = new VTableSheet(container, { + editable: false, // Default read-only + sheets: [ + { + sheetKey: 'read_only_sheet', + sheetTitle: 'Read Only', + data: data1 + // Inherits global editable: false + }, + { + sheetKey: 'editable_sheet', + sheetTitle: 'Editable', + data: data2, + editable: true // Override global setting + } + ] +}); +``` + +### 3. Custom Keyboard Shortcut Policy + +You can customize the keyboard behavior. For example, disable Cut and Paste but allow Copy, or disable clearing content with the Delete key. + +```typescript +const sheet = new VTableSheet(container, { + // Global policy: Allow copy, disable cut/paste + keyboardShortcutPolicy: { + copySelected: true, + cutSelected: false, + pasteValueToCell: false + }, + sheets: [ + { + sheetKey: 'sheet1', + data: data1 + }, + { + sheetKey: 'sheet2', + data: data2, + // Sheet-level policy: Allow delete range + keyboardShortcutPolicy: { + deleteRange: true + } + } + ] +}); +``` diff --git a/docs/assets/guide/zh/sheet/edit_control.md b/docs/assets/guide/zh/sheet/edit_control.md new file mode 100755 index 000000000..0ff977d75 --- /dev/null +++ b/docs/assets/guide/zh/sheet/edit_control.md @@ -0,0 +1,109 @@ +# Edit Capability Control + +In `VTableSheet`, the editing capability is enabled by default. However, in some scenarios (such as preview mode or permission control), you may need to disable editing globally or for specific sheets. + +VTableSheet provides flexible configuration options to control the editing capability and keyboard shortcuts at both the global and sheet levels. + +## Configuration Options + +### editable + +- **Type**: `boolean` +- **Default**: `true` (if not configured globally or on the sheet) +- **Description**: Controls whether the table is editable. + - When set to `false`, the table enters **Read-Only Mode**: + - Double-click editing is disabled. + - Editors are not registered. + - Modification shortcuts (Cut, Paste, etc.) are disabled. + - Context menu items related to modification (Insert/Delete rows, Merge cells, etc.) are hidden or disabled. + - The Delete/Backspace key will not clear cell content (unless configured otherwise). + - **Priority**: The configuration on a specific sheet (`ISheetDefine.editable`) takes precedence over the global configuration (`IVTableSheetOptions.editable`). + +### keyboardShortcutPolicy + +- **Type**: `SheetKeyboardShortcutPolicy` +- **Description**: Defines the policy for keyboard shortcuts, allowing fine-grained control over specific actions. +- **Properties**: + - `copySelected` (boolean): Enable copy shortcut (Ctrl+C). Default `true`. + - `cutSelected` (boolean): Enable cut shortcut (Ctrl+X). Default `true` (disabled in Read-Only mode). + - `pasteValueToCell` (boolean): Enable paste shortcut (Ctrl+V). Default `true` (disabled in Read-Only mode). + - `selectAllOnCtrlA` (boolean): Enable select all shortcut (Ctrl+A). Default `true`. + - `deleteRange` (boolean): Enable clearing cell content with Delete/Backspace. Default `true` (disabled in Read-Only mode). + - ... (other navigation shortcuts like `moveFocusCellOnTab`, `editCellOnEnter`, etc.) + +## Usage Examples + +### 1. Global Read-Only Mode + +You can set the entire workbook to read-only by configuring `editable: false` in the initialization options. + +```typescript +import { VTableSheet } from '@visactor/vtable-sheet'; + +const sheet = new VTableSheet(container, { + editable: false, // Global read-only + sheets: [ + { + sheetKey: 'sheet1', + data: data1 + }, + { + sheetKey: 'sheet2', + data: data2 + } + ] +}); +``` + +### 2. Mixed Mode (Global Read-Only with Specific Editable Sheets) + +You can set the global default to read-only, and enable editing for specific sheets. + +```typescript +const sheet = new VTableSheet(container, { + editable: false, // Default read-only + sheets: [ + { + sheetKey: 'read_only_sheet', + sheetTitle: 'Read Only', + data: data1 + // Inherits global editable: false + }, + { + sheetKey: 'editable_sheet', + sheetTitle: 'Editable', + data: data2, + editable: true // Override global setting + } + ] +}); +``` + +### 3. Custom Keyboard Shortcut Policy + +You can customize the keyboard behavior. For example, disable Cut and Paste but allow Copy, or disable clearing content with the Delete key. + +```typescript +const sheet = new VTableSheet(container, { + // Global policy: Allow copy, disable cut/paste + keyboardShortcutPolicy: { + copySelected: true, + cutSelected: false, + pasteValueToCell: false + }, + sheets: [ + { + sheetKey: 'sheet1', + data: data1 + }, + { + sheetKey: 'sheet2', + data: data2, + // Sheet-level policy: Allow delete range + keyboardShortcutPolicy: { + deleteRange: true + } + } + ] +}); +``` diff --git a/docs/assets/option/zh/table/sheet.md b/docs/assets/option/zh/table/sheet.md index fcd599425..db6648f56 100644 --- a/docs/assets/option/zh/table/sheet.md +++ b/docs/assets/option/zh/table/sheet.md @@ -44,6 +44,18 @@ 是否是当前活动工作表。 +### editable(boolean) + +是否允许编辑该工作表。 +- `true`: 允许编辑(默认)。 +- `false`: 只读模式。 +**优先级**:此配置优先级高于全局 `IVTableSheetOptions.editable`。 + +### keyboardShortcutPolicy(SheetKeyboardShortcutPolicy) + +该工作表的快捷键策略配置。可用于覆盖全局的快捷键策略。 +具体配置项参考全局配置中的 `keyboardShortcutPolicy`。 + ### cellMerge(CustomMergeCellArray) 单元格合并配置,格式为: @@ -184,6 +196,27 @@ rowHeightConfig: { ## defaultColWidth(number) = 100 默认列宽。 +## editable(boolean) = true + +全局编辑能力开关。 +- `true`: 默认所有工作表可编辑(除非工作表单独配置为只读)。 +- `false`: 默认所有工作表只读(除非工作表单独配置为可编辑)。 +只读模式下: +- 双击和按键无法进入编辑状态。 +- 剪切、粘贴等修改性快捷键被禁用。 +- Delete/Backspace 无法清空单元格。 +- 右键菜单中的修改项被隐藏。 + +## keyboardShortcutPolicy(SheetKeyboardShortcutPolicy) + +全局快捷键策略配置。定义了允许或禁用的快捷键行为。 +常用属性: +- `copySelected` (boolean): 是否允许复制 (Ctrl+C)。默认 true。 +- `cutSelected` (boolean): 是否允许剪切 (Ctrl+X)。只读模式下强制 false。 +- `pasteValueToCell` (boolean): 是否允许粘贴 (Ctrl+V)。只读模式下强制 false。 +- `selectAllOnCtrlA` (boolean): 是否允许全选 (Ctrl+A)。默认 true。 +- `deleteRange` (boolean): 是否允许 Delete/Backspace 清空选区。只读模式下强制 false。 + ## dragOrder(Object) 拖拽列顺序和行顺序配置。 diff --git a/packages/vtable-plugins/src/context-menu.ts b/packages/vtable-plugins/src/context-menu.ts index fc9c68fae..56156ad33 100644 --- a/packages/vtable-plugins/src/context-menu.ts +++ b/packages/vtable-plugins/src/context-menu.ts @@ -143,9 +143,10 @@ export class ContextMenuPlugin implements pluginsDefinition.IVTablePlugin { if (this.pluginOptions.beforeShowAdjustMenuItems) { menuItems = this.pluginOptions.beforeShowAdjustMenuItems(menuItems, table as ListTable, col, row); } - - // 显示右键菜单 - this.showContextMenu(menuItems, mouseX, mouseY, col, row); + if (menuItems.length > 0) { + // 显示右键菜单 + this.showContextMenu(menuItems, mouseX, mouseY, col, row); + } } }; @@ -175,9 +176,10 @@ export class ContextMenuPlugin implements pluginsDefinition.IVTablePlugin { if (this.pluginOptions.beforeShowAdjustMenuItems) { menuItems = this.pluginOptions.beforeShowAdjustMenuItems(menuItems, table as ListTable, colIndex, rowIndex); } - - // 显示右键菜单 - this.showContextMenu(menuItems, mouseX, mouseY, colIndex, rowIndex); + if (menuItems.length > 0) { + // 显示右键菜单 + this.showContextMenu(menuItems, mouseX, mouseY, colIndex, rowIndex); + } } }; diff --git a/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts b/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts index 3e296d31d..c95f53384 100644 --- a/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts +++ b/packages/vtable-plugins/src/excel-edit-cell-keyboard.ts @@ -73,7 +73,30 @@ export class ExcelEditCellKeyboardPlugin implements pluginsDefinition.IVTablePlu // this.pluginOptions?.keyDown_before?.(event); if (this.table?.editorManager && this.isExcelShortcutKey(event)) { const eventKey = event.key.toLowerCase() as ExcelEditCellKeyboardResponse; - //判断是键盘触发编辑单元格的情况下,那么在编辑状态中切换方向需要选中下一个继续编辑 + + let editable = true; + let deleteRangeEnabled = true; + + const vtableSheet = (this.table as any).__vtableSheet; + const activeSheet = vtableSheet?.getActiveSheet?.(); + const sheetOptions = activeSheet?.options as + | { editable?: boolean; keyboardShortcutPolicy?: { deleteRange?: boolean } } + | undefined; + + if (sheetOptions) { + editable = sheetOptions.editable !== false; + deleteRangeEnabled = sheetOptions.keyboardShortcutPolicy?.deleteRange ?? true; + } + + const isDeleteKey = + eventKey === ExcelEditCellKeyboardResponse.DELETE || eventKey === ExcelEditCellKeyboardResponse.BACKSPACE; + + if (isDeleteKey && (!editable || !deleteRangeEnabled)) { + event.stopPropagation(); + event.preventDefault(); + return; + } + if (this.table.editorManager.editingEditor && this.table.editorManager.beginTriggerEditCellMode === 'keydown') { const { col, row } = this.table.editorManager.editCell; if (eventKey !== ExcelEditCellKeyboardResponse.BACKSPACE && eventKey !== ExcelEditCellKeyboardResponse.DELETE) { @@ -122,6 +145,9 @@ export class ExcelEditCellKeyboardPlugin implements pluginsDefinition.IVTablePlu !this.table.editorManager.editingEditor && (eventKey === ExcelEditCellKeyboardResponse.DELETE || eventKey === ExcelEditCellKeyboardResponse.BACKSPACE) ) { + if (!deleteRangeEnabled) { + return; + } //响应删除键,删除 const selectCells = this.table.getSelectedCellInfos(); if ( diff --git a/packages/vtable-sheet/src/components/vtable-sheet.ts b/packages/vtable-sheet/src/components/vtable-sheet.ts index 04eec7fe3..31e9b950e 100644 --- a/packages/vtable-sheet/src/components/vtable-sheet.ts +++ b/packages/vtable-sheet/src/components/vtable-sheet.ts @@ -5,7 +5,7 @@ import * as VTable from '@visactor/vtable'; import { getTablePlugins } from '../core/table-plugins'; import { DomEventManager } from '../event/dom-event-manager'; import { showSnackbar } from '../tools/ui/snackbar'; -import type { IVTableSheetOptions, ISheetDefine } from '../ts-types'; +import type { IVTableSheetOptions, ISheetDefine, SheetKeyboardShortcutPolicy } from '../ts-types'; import type { MultiSheetImportResult } from '@visactor/vtable-plugins/src/excel-import/types'; import type { TableEventHandlersEventArgumentMap } from '@visactor/vtable/es/ts-types/events'; import SheetTabDragManager from '../managers/tab-drag-manager'; @@ -478,6 +478,18 @@ export default class VTableSheet { const contentWidth = this.contentElement.clientWidth; const contentHeight = this.contentElement.clientHeight; sheetDefine.dragOrder = sheetDefine.dragOrder ?? this.options.dragOrder; + + const globalEditable = this.options.editable; + const sheetEditable = sheetDefine.editable; + const effectiveEditable = sheetEditable ?? (globalEditable ?? true); + + const effectiveKeyboardPolicy = + (this.options.keyboardShortcutPolicy || sheetDefine.keyboardShortcutPolicy) && + { + ...(this.options.keyboardShortcutPolicy as SheetKeyboardShortcutPolicy), + ...(sheetDefine.keyboardShortcutPolicy as SheetKeyboardShortcutPolicy) + }; + // 创建sheet实例 const sheet = new WorkSheet(this, { ...sheetDefine, @@ -488,8 +500,12 @@ export default class VTableSheet { defaultColWidth: this.options.defaultColWidth, dragOrder: sheetDefine.dragOrder, plugins: getTablePlugins(sheetDefine, this.options, this), - headerEditor: 'formula', - editor: 'formula', + ...(effectiveEditable + ? { + headerEditor: 'formula', + editor: 'formula' + } + : {}), select: { makeSelectCellVisible: false }, @@ -499,9 +515,11 @@ export default class VTableSheet { borderLineDash: [null, null, null, null], padding: [8, 8, 8, 8] }, - editCellTrigger: ['api', 'keydown', 'doubleclick'], + editCellTrigger: effectiveEditable ? ['api', 'keydown', 'doubleclick'] : ['api'], customMergeCell: sheetDefine.cellMerge, - theme: sheetDefine.theme?.tableTheme || this.options.theme?.tableTheme + theme: sheetDefine.theme?.tableTheme || this.options.theme?.tableTheme, + editable: effectiveEditable, + keyboardShortcutPolicy: effectiveKeyboardPolicy as SheetKeyboardShortcutPolicy | undefined } as any); // 事件系统现在通过 TableEventRelay 自动处理,不再需要手动绑定 diff --git a/packages/vtable-sheet/src/core/WorkSheet.ts b/packages/vtable-sheet/src/core/WorkSheet.ts index e548c64cb..7b2c28e78 100644 --- a/packages/vtable-sheet/src/core/WorkSheet.ts +++ b/packages/vtable-sheet/src/core/WorkSheet.ts @@ -1,6 +1,7 @@ import type { ColumnDefine, ListTableConstructorOptions, ColumnsDefine } from '@visactor/vtable'; import { ListTable } from '@visactor/vtable'; import { isValid } from '@visactor/vutils'; +import type { TableKeyboardOptions } from '@visactor/vtable/es/ts-types'; import type { IWorkSheetOptions, IWorkSheetAPI, @@ -230,17 +231,76 @@ export class WorkSheet implements IWorkSheetAPI, IWorksheetEventSource { isShowTableHeader = true; } - const keyboardOptions = { - ...this.options.keyboardOptions, - copySelected: true, - getCopyCellValue: { - html: this.getCellValueConsiderFormula.bind(this) - }, - processFormulaBeforePaste: this.processFormulaPaste.bind(this), - pasteValueToCell: true, - showCopyCellBorder: true, - cutSelected: true + const editable = this.options.editable !== false; + const policy = this.options.keyboardShortcutPolicy; + + const keyboardOptionsBase = this.options.keyboardOptions ?? {}; + + let keyboardOptions: TableKeyboardOptions = { + ...keyboardOptionsBase + }; + + if (editable) { + keyboardOptions = { + ...keyboardOptions, + copySelected: keyboardOptions.copySelected ?? true, + pasteValueToCell: keyboardOptions.pasteValueToCell ?? true, + showCopyCellBorder: keyboardOptions.showCopyCellBorder ?? true, + cutSelected: keyboardOptions.cutSelected ?? true + }; + } + + keyboardOptions.getCopyCellValue = { + ...keyboardOptions.getCopyCellValue, + html: this.getCellValueConsiderFormula.bind(this) }; + keyboardOptions.processFormulaBeforePaste = this.processFormulaPaste.bind(this); + + if (policy) { + if (policy.moveFocusCellOnTab !== undefined) { + keyboardOptions.moveFocusCellOnTab = policy.moveFocusCellOnTab; + } + if (policy.moveFocusCellOnEnter !== undefined) { + keyboardOptions.moveFocusCellOnEnter = policy.moveFocusCellOnEnter; + } + if (policy.editCellOnEnter !== undefined) { + keyboardOptions.editCellOnEnter = policy.editCellOnEnter; + } + if (policy.moveEditCellOnArrowKeys !== undefined) { + keyboardOptions.moveEditCellOnArrowKeys = policy.moveEditCellOnArrowKeys; + } + if (policy.selectAllOnCtrlA !== undefined) { + keyboardOptions.selectAllOnCtrlA = policy.selectAllOnCtrlA; + } + + if (editable) { + if (policy.copySelected !== undefined) { + keyboardOptions.copySelected = policy.copySelected; + } + if (policy.cutSelected !== undefined) { + keyboardOptions.cutSelected = policy.cutSelected; + } + if (policy.pasteValueToCell !== undefined) { + keyboardOptions.pasteValueToCell = policy.pasteValueToCell; + } + if (policy.showCopyCellBorder !== undefined) { + keyboardOptions.showCopyCellBorder = policy.showCopyCellBorder; + } + } + } + + if (!editable) { + keyboardOptions = { + ...keyboardOptions, + // 禁止通过键盘触发编辑 + editCellOnEnter: false, + moveFocusCellOnEnter: false, + moveEditCellOnArrowKeys: false, + // 禁止剪切 / 粘贴等修改性快捷键 + cutSelected: false, + pasteValueToCell: false + }; + } //更改theme 的frameStyle let changedTheme: TYPES.VTableThemes.ITableThemeDefine; diff --git a/packages/vtable-sheet/src/core/table-plugins.ts b/packages/vtable-sheet/src/core/table-plugins.ts index 959036341..e2073098e 100644 --- a/packages/vtable-sheet/src/core/table-plugins.ts +++ b/packages/vtable-sheet/src/core/table-plugins.ts @@ -6,6 +6,7 @@ import { HighlightHeaderWhenSelectCellPlugin, ContextMenuPlugin, ExcelEditCellKeyboardPlugin, + ExcelEditCellKeyboardResponse, AutoFillPlugin, DEFAULT_HEADER_MENU_ITEMS, DEFAULT_BODY_MENU_ITEMS, @@ -20,7 +21,7 @@ import type { IHighlightHeaderWhenSelectCellPluginOptions, ContextMenuOptions } from '@visactor/vtable-plugins'; -import type { ISheetDefine, IColumnDefine, IVTableSheetOptions } from '../ts-types'; +import type { ISheetDefine, IColumnDefine, IVTableSheetOptions, SheetKeyboardShortcutPolicy } from '../ts-types'; import { isValid } from '@visactor/vutils'; /** @@ -39,6 +40,18 @@ export function getTablePlugins( // 结合options.VTablePluginModules,来判断是否禁用插件 const disabledPluginsUserSetted = options?.VTablePluginModules?.filter(module => module.disabled); let enabledPluginsUserSetted = options?.VTablePluginModules?.filter(module => !module.disabled); + + const globalEditable = options?.editable; + const sheetEditable = sheetDefine?.editable; + const effectiveEditable = sheetEditable ?? (globalEditable ?? true); + + let effectiveKeyboardPolicy: SheetKeyboardShortcutPolicy | undefined; + if (options?.keyboardShortcutPolicy || sheetDefine?.keyboardShortcutPolicy) { + effectiveKeyboardPolicy = { + ...(options?.keyboardShortcutPolicy as SheetKeyboardShortcutPolicy), + ...(sheetDefine?.keyboardShortcutPolicy as SheetKeyboardShortcutPolicy) + }; + } if (!disabledPluginsUserSetted?.some(module => module.module === FilterPlugin)) { const userPluginOptions = enabledPluginsUserSetted?.find(module => module.module === FilterPlugin) ?.moduleOptions as FilterOptions; @@ -118,7 +131,12 @@ export function getTablePlugins( if (!ContextMenuPlugin) { console.warn('ContextMenuPlugin is not available in @visactor/vtable-plugins'); } else { - const contextMenuPlugin = createContextMenuItems(sheetDefine, userPluginOptions); + const contextMenuPlugin = createContextMenuItems( + sheetDefine, + userPluginOptions as ContextMenuOptions, + effectiveEditable, + effectiveKeyboardPolicy + ); plugins.push(contextMenuPlugin); } //已经初始化过的插件,从enabledPluginsUserSetted中移除 @@ -132,29 +150,6 @@ export function getTablePlugins( if (!ExcelEditCellKeyboardPlugin) { console.warn('ExcelEditCellKeyboardPlugin is not available in @visactor/vtable-plugins'); } else { - // let currentState_editingEditor: IEditor | null = null; //需要在keyDownBeforeCallback中保存下来,因为插件处理事件中会影响这个值(调用了completeEdit) - // const keyDownBeforeCallback = function (this: ExcelEditCellKeyboardPlugin, event: KeyboardEvent) { - // currentState_editingEditor = sheet.getActiveSheet()?.tableInstance?.editorManager.editingEditor; - // }; - // // 注意:这里使用普通函数而不是箭头函数,这样才能通过 apply 正确绑定 this 为插件实例 - // const keyDownAfterCallback = function (this: ExcelEditCellKeyboardPlugin, event: KeyboardEvent) { - // const eventKey = event.key.toLowerCase() as ExcelEditCellKeyboardResponse; - // if (this.responseKeyboard.includes(eventKey)) { - // if ( - // (currentState_editingEditor && - // eventKey !== ExcelEditCellKeyboardResponse.DELETE && - // eventKey !== ExcelEditCellKeyboardResponse.BACKSPACE) || - // (!currentState_editingEditor && - // (eventKey === ExcelEditCellKeyboardResponse.DELETE || - // eventKey === ExcelEditCellKeyboardResponse.BACKSPACE)) || - // sheet.formulaManager._formulaWorkingOnCell - // ) { - // event.stopPropagation(); - // event.preventDefault(); - // } - // } - // }; - // 创建插件时包含回调 const excelEditCellKeyboardPlugin = new ExcelEditCellKeyboardPlugin(userPluginOptions); plugins.push(excelEditCellKeyboardPlugin); } @@ -252,7 +247,12 @@ function createColumnFilterChecker(sheetDefine: ISheetDefine) { }; } -function createContextMenuItems(sheetDefine: ISheetDefine, userPluginOptions?: ContextMenuOptions) { +function createContextMenuItems( + sheetDefine: ISheetDefine, + userPluginOptions?: ContextMenuOptions, + effectiveEditable?: boolean, + policy?: SheetKeyboardShortcutPolicy +) { return new ContextMenuPlugin({ headerCellMenuItems: [ ...DEFAULT_HEADER_MENU_ITEMS, @@ -343,6 +343,59 @@ function createContextMenuItems(sheetDefine: ISheetDefine, userPluginOptions?: C ); } } + if (effectiveEditable === false) { + const editKeys = new Set([ + 'copy', + 'cut', + 'paste', + 'delete', + 'insert', + 'insert_row_above', + 'insert_row_below', + 'delete_row', + 'insert_column_left', + 'insert_column_right', + 'delete_column', + 'merge_cells', + 'unmerge_cells' + ]); + menuItems = menuItems.filter(item => { + if (typeof item === 'string') { + return true; + } + if (editKeys.has(item.menuKey as string) || editKeys.has(item.iconName as string)) { + return false; + } + if (Array.isArray(item.children) && item.children.length) { + item.children = item.children.filter(child => { + if (typeof child === 'string') { + return true; + } + return !editKeys.has(child.menuKey as string); + }); + } + return true; + }); + } else if (policy) { + menuItems = menuItems.map(item => { + if (typeof item === 'string') { + return item; + } + if (item.menuKey === 'copy' && policy.copySelected === false) { + return { ...item, disabled: true }; + } + if (item.menuKey === 'cut' && policy.cutSelected === false) { + return { ...item, disabled: true }; + } + if (item.menuKey === 'paste' && policy.pasteValueToCell === false) { + return { ...item, disabled: true }; + } + return item; + }); + } + if(menuItems.length===1&&menuItems[0]==='---'){ + menuItems=[]; + } return menuItems; }, menuClickCallback: { diff --git a/packages/vtable-sheet/src/ts-types/index.ts b/packages/vtable-sheet/src/ts-types/index.ts index 05d48687e..180497780 100644 --- a/packages/vtable-sheet/src/ts-types/index.ts +++ b/packages/vtable-sheet/src/ts-types/index.ts @@ -3,8 +3,10 @@ import { TYPES as VTableTypes, themes as VTableThemes } from '@visactor/vtable'; import type { CellValue, IStyle, MainMenuItem } from './base'; import type { IFilterState } from './filter'; import type { TableSeriesNumberOptions, ImportResult } from '@visactor/vtable-plugins'; -import type { SortState } from '@visactor/vtable/es/ts-types'; +import type { SortState, TableKeyboardOptions } from '@visactor/vtable/es/ts-types'; + export { VTableThemes, VTableTypes, ImportResult }; + /** 筛选配置 */ export interface IFilterConfig { /** 指定筛选器支持的筛选模式(按值、按条件、或两者) */ @@ -66,7 +68,21 @@ export interface ISheetDefine { enableDragColumnOrder?: boolean; enableDragRowOrder?: boolean; }; + /** + * sheet 级编辑能力开关: + * - 未配置:继承 IVTableSheetOptions.editable; + * - false:本 sheet 只读; + * - true:仅在全局 editable 未关闭时生效。 + */ + editable?: boolean; + /** + * sheet 级快捷键策略: + * - 未配置:继承 IVTableSheetOptions.keyboardShortcutPolicy; + * - 已配置:对全局策略逐字段覆盖。 + */ + keyboardShortcutPolicy?: SheetKeyboardShortcutPolicy; } + export interface IThemeDefine { rowSeriesNumberCellStyle?: TableSeriesNumberOptions['rowSeriesNumberCellStyle']; colSeriesNumberCellStyle?: TableSeriesNumberOptions['colSeriesNumberCellStyle']; @@ -80,6 +96,28 @@ export interface IThemeDefine { }; tableTheme: VTableThemes.ITableThemeDefine; } + +/** + * VTableSheet 层的快捷键策略: + * - 仅暴露与编辑 / 剪切 / 粘贴 / 全选等直接相关的字段; + * - 其他复杂键盘配置仍通过底层 keyboardOptions 高级用法处理(不在本次 API 范围)。 + */ +export type SheetKeyboardShortcutPolicy = Pick< + TableKeyboardOptions, + | 'moveFocusCellOnTab' + | 'editCellOnEnter' + | 'moveFocusCellOnEnter' + | 'moveEditCellOnArrowKeys' + | 'cutSelected' + | 'copySelected' + | 'pasteValueToCell' + | 'showCopyCellBorder' + | 'selectAllOnCtrlA' +> & { + /** 是否允许 Delete / Backspace 清空选中区域,默认:编辑模式下 true,只读模式下 false */ + deleteRange?: boolean; +}; + /** VTableSheet配置 */ export interface IVTableSheetOptions { /** Sheet列表 */ @@ -116,7 +154,23 @@ export interface IVTableSheetOptions { enableDragColumnOrder?: boolean; enableDragRowOrder?: boolean; }; + + /** + * 全局编辑能力开关,默认值为 true(保持当前行为): + * - true 或未配置:默认可编辑; + * - false:所有 sheet 进入只读模式,禁止通过 UI 修改数据或结构。 + */ + editable?: boolean; + + /** + * 全局快捷键策略: + * - 控制剪切 / 复制 / 粘贴等快捷键行为; + * - 单个 sheet 可通过 ISheetDefine.keyboardShortcutPolicy 覆盖; + * - 当 editable === false(只读模式)时,策略仍可用于控制只读场景下是否允许复制 / 全选等非修改性操作。 + */ + keyboardShortcutPolicy?: SheetKeyboardShortcutPolicy; } + export * from './base'; export * from './formula'; export * from './filter'; diff --git a/packages/vtable-sheet/src/ts-types/sheet.ts b/packages/vtable-sheet/src/ts-types/sheet.ts index 57acf4a7e..067275c8f 100644 --- a/packages/vtable-sheet/src/ts-types/sheet.ts +++ b/packages/vtable-sheet/src/ts-types/sheet.ts @@ -1,4 +1,4 @@ -import type { IColumnDefine, IFilterConfig, ISheetDefine } from './index'; +import type { IColumnDefine, IFilterConfig, ISheetDefine, SheetKeyboardShortcutPolicy } from './index'; import type { CellCoord, CellRange, CellValue } from './base'; import type { ListTableConstructorOptions } from '@visactor/vtable'; @@ -20,6 +20,10 @@ export interface IWorkSheetOptions extends Omit