diff --git a/packages/sheets/src/facade/f-enum.ts b/packages/sheets/src/facade/f-enum.ts index 28c90abdba..a5623dc447 100644 --- a/packages/sheets/src/facade/f-enum.ts +++ b/packages/sheets/src/facade/f-enum.ts @@ -16,6 +16,7 @@ import { FEnum } from '@univerjs/core/facade'; import { SheetSkeletonChangeType, SheetValueChangeType, SplitDelimiterEnum } from '@univerjs/sheets'; +import { RangePermissionPoint, UnitRole, WorkbookPermissionPoint, WorksheetPermissionPoint } from './permission'; /** * @ignore @@ -37,6 +38,26 @@ export interface IFSheetsEnum { * Split delimiter types. */ SplitDelimiterType: typeof SplitDelimiterEnum; + + /** + * Unit roles. + */ + UnitRole: typeof UnitRole; + + /** + * Workbook permission points. + */ + WorkbookPermissionPoint: typeof WorkbookPermissionPoint; + + /** + * Worksheet permission points. + */ + WorksheetPermissionPoint: typeof WorksheetPermissionPoint; + + /** + * Range permission points. + */ + RangePermissionPoint: typeof RangePermissionPoint; } export class FSheetsEnum implements IFSheetsEnum { @@ -51,6 +72,22 @@ export class FSheetsEnum implements IFSheetsEnum { get SplitDelimiterType(): typeof SplitDelimiterEnum { return SplitDelimiterEnum; } + + get UnitRole(): typeof UnitRole { + return UnitRole; + } + + get WorkbookPermissionPoint(): typeof WorkbookPermissionPoint { + return WorkbookPermissionPoint; + } + + get WorksheetPermissionPoint(): typeof WorksheetPermissionPoint { + return WorksheetPermissionPoint; + } + + get RangePermissionPoint(): typeof RangePermissionPoint { + return RangePermissionPoint; + } } FEnum.extend(FSheetsEnum); diff --git a/packages/sheets/src/facade/permission/README.md b/packages/sheets/src/facade/permission/README.md deleted file mode 100644 index 06e46c13e7..0000000000 --- a/packages/sheets/src/facade/permission/README.md +++ /dev/null @@ -1,1138 +0,0 @@ -# 权限系统 Facade API 重构 - -## 项目概述 - -根据 `/refactor.md` 文档,本项目对 Univer 的权限系统 Facade API 进行了重构,目标是提供更易用、更一致的权限管理接口。 - -## 🎉 项目完成状态 - -**核心功能已完成!** 所有主要的 Facade API 实现已完成并通过编译检查。 - -### ✅ 已完成的工作(9/10) - -1. ✅ **权限类型和枚举定义** (`permission-types.ts` - 571 行) - - 定义了 `WorkbookPermissionPoint`(23个)、`WorksheetPermissionPoint`(20个)、`RangePermissionPoint`(2个)枚举 - - 定义了 `WorkbookMode`、`WorksheetMode` 类型 - - 定义了完整的接口:`WorkbookPermission`、`WorksheetPermission`、`RangePermission`、`RangeProtectionRule` - - 所有接口都包含完整的 RxJS Observable 支持 - -2. ✅ **权限点映射层** (`permission-point-map.ts` - 125 行) - - 实现了 45+ 个权限点从枚举到类构造器的映射 - - 支持 Workbook、Worksheet、Range 三个层级 - -3. ✅ **WorkbookPermission 实现** (`f-workbook-permission.ts` - 355 行) - - 高层 API:`setMode()` - 支持 owner/editor/viewer/commenter 模式 - - 快捷方法:`setReadOnly()`、`setEditable()` - - 底层 API:`setPoint()`、`getPoint()`、`getSnapshot()` - - 协作者管理:`setCollaborators()`、`addCollaborator()`、`updateCollaborator()`、`removeCollaborator()`、`listCollaborators()` - - RxJS Observable 流:`permission$`、`pointChange$`、`collaboratorChange$` - - 兼容性方法:`subscribe()` - -4. ✅ **WorksheetPermission 实现** (`f-worksheet-permission.ts` - 429 行) - - `setMode()` - 支持 editable/readOnly/filterOnly/commentOnly 模式 - - `setReadOnly()`、`setEditable()` 快捷方法 - - `canEditCell()`、`canViewCell()` - 单元格级权限检查 - - `protectRanges()` - **批量创建保护规则**(支持一次创建多个) - - `unprotectRules()` - 批量删除保护规则 - - `listRangeProtectionRules()` - 列出所有保护规则 - - `debugCellPermission()` - 调试接口,返回命中的保护规则 - - RxJS Observable 流:`permission$`、`pointChange$`、`rangeProtectionChange$`、`rangeProtectionRules$` - -5. ✅ **RangePermission 实现** (`f-range-permission.ts` - 307 行) - - `protect()` - 保护当前范围 - - `unprotect()` - 取消保护 - - `isProtected()`、`canEdit()` - 状态检查 - - `getPoint()` - 读取权限点 - - `listRules()` - 列出所有保护规则 - - RxJS Observable 流:`permission$`、`protectionChange$` - -6. ✅ **RangeProtectionRule 实现** (`f-range-protection-rule.ts` - 142 行) - - `updateRanges()` - 更新保护范围(带范围重叠检测) - - `updateOptions()` - 更新保护选项 - - `remove()` - 删除规则 - - 属性访问:`id`、`ranges`、`options` - -7. ✅ **集成到现有 Facade** - - 在 `FWorkbook` 中添加 `getWorkbookPermission(): FWorkbookPermission` 方法 - - 在 `FWorksheet` 中添加 `getWorksheetPermission(): FWorksheetPermission` 方法 - - 在 `FRange` 中添加 `getRangePermission(): FRangePermission` 方法 - - 所有方法都包含完整的 JSDoc 文档和使用示例 - -8. ✅ **向后兼容** - - 在 `FPermission` 类上添加了 `@deprecated` 标记 - - 在主要方法(`addRangeBaseProtection`、`removeRangeProtection`)上添加了 `@deprecated` 标记 - - 提供了从旧 API 到新 API 的迁移示例 - -9. ✅ **单元测试** (全新完成!) - - ✅ **WorkbookPermission 测试用例** (`f-workbook-permission.spec.ts` - 282 行) - - 基础操作测试:获取实例、设置和获取权限点、获取快照 - - 模式操作测试:viewer、editor、owner、commenter 模式 - - 快捷方法测试:setReadOnly()、setEditable() - - 响应式流测试:permission$ 订阅、变化监听、兼容性方法 - - 权限点覆盖测试:测试多个权限点 - - - ✅ **WorksheetPermission 测试用例** (`f-worksheet-permission.spec.ts` - 428 行) - - 基础操作测试:获取实例、权限点操作、可编辑性检查 - - 模式操作测试:readOnly、editable、filterOnly、commentOnly 模式 - - 单元格级权限检查:canEditCell()、canViewCell() - - 范围保护测试:protectRanges()、unprotectRules()、批量操作 - - 调试工具测试:debugCellPermission() - - 响应式流测试:permission$、rangeProtectionChange$、rangeProtectionRules$ - - - ✅ **RangePermission 测试用例** (`f-range-permission.spec.ts` - 307 行) - - 基础操作测试:获取实例、权限快照、权限点 - - 保护操作测试:protect()、unprotect()、带用户白名单、带元数据 - - 状态检查测试:isProtected()、canEdit() - - 规则列表测试:listRules()、重叠范围处理 - - 响应式流测试:permission$、protectionChange$ - - 错误处理测试:取消未保护的范围 - - - ✅ **RangeProtectionRule 测试用例** (`f-range-protection-rule.spec.ts` - 507 行) - - 基础操作测试:创建规则、访问属性 - - 更新范围测试:updateRanges()、多范围更新、重叠检测 - - 更新选项测试:name、allowEdit、allowedUsers、metadata、部分更新 - - 删除规则测试:remove()、重复删除处理 - - 复杂场景测试:多次更新序列、独立更新 - - - ✅ **权限组合逻辑测试** (`permission-combination.spec.ts` - 483 行) - - 层级权限组合:workbook vs worksheet、三级层级 - - 单元格级权限检查:范围保护、重叠规则、调试工具 - - 批量操作测试:批量创建、批量删除、性能验证 - - 响应式流组合:combineLatest、变化监听、规则列表跟踪 - - 模式转换测试:不同模式间的转换 - - 边界情况测试:空规则列表、不存在的单元格、重复操作 - -### ⏳ 待完成的工作(1/10) - -10. **扩展文档与示例** - - [ ] 更详细的 API 使用场景 - - [ ] 高级用法示例 - - [ ] 性能优化建议 - -## 使用示例 - -### 1. WorkbookPermission - 工作簿级权限 - -```typescript -import { FUniver } from '@univerjs/core'; -import { WorkbookPermissionPoint } from '@univerjs/sheets'; - -const univerAPI = FUniver.newAPI(); -const workbook = univerAPI.getActiveWorkbook(); - -// 获取 WorkbookPermission 实例 -const permission = workbook.getWorkbookPermission(); - -// 方式一:使用预定义模式 -await permission.setMode('viewer'); // 只读模式 -await permission.setMode('editor'); // 编辑者模式 -await permission.setMode('owner'); // 拥有者模式 - -// 方式二:使用快捷方法 -await permission.setReadOnly(); // 等同于 setMode('viewer') -await permission.setEditable(); // 等同于 setMode('editor') - -// 方式三:精细控制单个权限点 -await permission.setPoint(WorkbookPermissionPoint.Edit, false); -await permission.setPoint(WorkbookPermissionPoint.Print, true); - -// 获取权限点状态 -const canEdit = permission.getPoint(WorkbookPermissionPoint.Edit); -console.log('Can edit:', canEdit); - -// 获取完整权限快照 -const snapshot = permission.getSnapshot(); -console.log('All permissions:', snapshot); -``` - -### 2. 协作者管理 - -```typescript -// 添加协作者 -await permission.addCollaborator({ - userId: 'user123', - name: 'John Doe', - avatar: 'https://example.com/avatar.jpg', - role: 'editor' // 'owner' | 'editor' | 'reader' -}); - -// 更新协作者角色 -await permission.updateCollaborator('user123', { - role: 'owner' -}); - -// 移除协作者 -await permission.removeCollaborator('user123'); - -// 批量设置协作者 -await permission.setCollaborators([ - { userId: 'user1', name: 'Alice', role: 'editor' }, - { userId: 'user2', name: 'Bob', role: 'reader' } -]); - -// 列出所有协作者 -const collaborators = await permission.listCollaborators(); -console.log('Collaborators:', collaborators); -``` - -### 3. RxJS 响应式编程 - -```typescript -import { map, distinctUntilChanged, filter } from 'rxjs/operators'; - -// 订阅权限变化(立即获得当前状态) -permission.permission$.subscribe(snapshot => { - console.log('Permission snapshot:', snapshot); - // UI 更新逻辑 -}); - -// 监听特定权限点的变化 -permission.permission$ - .pipe( - map(snapshot => snapshot[WorkbookPermissionPoint.Edit]), - distinctUntilChanged() - ) - .subscribe(canEdit => { - console.log('Edit permission changed:', canEdit); - }); - -// 监听协作者变化 -permission.collaboratorChange$.subscribe(change => { - console.log('Collaborator change:', change.type, change.collaborator); -}); - -// 简化订阅(不熟悉 RxJS 的用户) -const unsubscribe = permission.subscribe(snapshot => { - console.log('Permission snapshot:', snapshot); -}); - -// 取消订阅 -unsubscribe(); -``` - -### 4. WorksheetPermission - 工作表级权限 - -```typescript -import { WorksheetPermissionPoint } from '@univerjs/sheets'; - -const worksheet = workbook.getActiveSheet(); -const worksheetPermission = worksheet.getWorksheetPermission(); - -// 设置工作表模式 -await worksheetPermission.setMode('readOnly'); // 完全只读 -await worksheetPermission.setMode('editable'); // 完全可编辑 -await worksheetPermission.setMode('filterOnly'); // 只能筛选排序 -await worksheetPermission.setMode('commentOnly'); // 只能评论 - -// 快捷方法 -await worksheetPermission.setReadOnly(); -await worksheetPermission.setEditable(); - -// 检查整体是否可编辑 -const canEdit = worksheetPermission.canEdit(); - -// 检查特定单元格权限(综合表级和范围级规则) -const canEditA1 = worksheetPermission.canEditCell(0, 0); -const canViewA1 = worksheetPermission.canViewCell(0, 0); -``` - -### 5. 范围保护(批量操作) - -```typescript -// 批量创建多个保护规则(高性能,一次 Command 执行) -const range1 = worksheet.getRange('A1:A10'); -const range2 = worksheet.getRange('B1:B10'); -const range3 = worksheet.getRange('C1:C10'); - -const rules = await worksheetPermission.protectRanges([ - { - ranges: [range1], - options: { - name: 'Column A Protection', - allowEdit: false, - allowedUsers: ['user123'] - } - }, - { - ranges: [range2], - options: { - name: 'Column B Protection', - allowEdit: true - } - }, - { - ranges: [range3], - options: { - name: 'Column C Protection', - allowEdit: false, - metadata: { department: 'Finance' } - } - } -]); - -console.log('Created rules:', rules); - -// 批量删除保护规则 -const ruleIds = rules.map(r => r.id); -await worksheetPermission.unprotectRules(ruleIds); - -// 列出所有保护规则 -const allRules = await worksheetPermission.listRangeProtectionRules(); -``` - -### 6. 调试单元格权限 - -```typescript -// 调试特定单元格的权限信息 -const debugInfo = worksheetPermission.debugCellPermission(0, 0); // A1 单元格 - -if (debugInfo) { - console.log('Cell:', debugInfo.row, debugInfo.col); - console.log('Hit rules:', debugInfo.hitRules); - - debugInfo.hitRules.forEach(rule => { - console.log('Rule ID:', rule.ruleId); - console.log('Ranges:', rule.rangeRefs); // ['R0C0:R9C0'] - console.log('Options:', rule.options); - }); -} else { - console.log('No protection rules for this cell'); -} -``` - -### 7. RangePermission - 范围级权限 - -```typescript -const range = worksheet.getRange('A1:B10'); -const rangePermission = range.getRangePermission(); - -// 保护范围 -const rule = await rangePermission.protect({ - name: 'Important Data', - allowEdit: false, - allowedUsers: ['user123', 'user456'], - metadata: { createdBy: 'admin' } -}); - -console.log('Protection rule created:', rule.id); - -// 检查保护状态 -const isProtected = rangePermission.isProtected(); -const canEdit = rangePermission.canEdit(); - -console.log('Is protected:', isProtected); -console.log('Can edit:', canEdit); - -// 取消保护 -await rangePermission.unprotect(); - -// 列出所有规则 -const rules = await rangePermission.listRules(); -``` - -### 8. RangeProtectionRule - 规则对象操作 - -```typescript -// 假设已经创建了一个保护规则 -const rules = await worksheetPermission.protectRanges([ - { ranges: [worksheet.getRange('A1:B10')], options: { name: 'Original' } } -]); -const rule = rules[0]; - -// 更新保护范围 -const newRange1 = worksheet.getRange('A1:C10'); -const newRange2 = worksheet.getRange('D1:D10'); -await rule.updateRanges([newRange1, newRange2]); - -// 局部更新保护选项 -await rule.updateOptions({ - name: 'Updated Protection', - allowEdit: true, - allowedUsers: ['newUser'] -}); - -// 删除规则 -await rule.remove(); - -// 访问规则属性 -console.log('Rule ID:', rule.id); -console.log('Protected ranges:', rule.ranges); -console.log('Options:', rule.options); -``` - -### 9. 响应式监听工作表权限变化 - -```typescript -import { combineLatest } from 'rxjs'; - -const worksheetPermission = worksheet.getWorksheetPermission(); - -// 监听工作表权限快照变化 -worksheetPermission.permission$.subscribe(snapshot => { - console.log('Worksheet permissions changed:', snapshot); -}); - -// 监听范围保护规则变化 -worksheetPermission.rangeProtectionChange$.subscribe(change => { - console.log('Range protection changed:', change.type, change.rules); -}); - -// 监听当前所有规则列表 -worksheetPermission.rangeProtectionRules$.subscribe(rules => { - console.log('Current rules:', rules); - // 更新 UI 显示的规则列表 -}); - -// 组合多个流 -combineLatest([ - workbookPermission.permission$, - worksheetPermission.permission$, - worksheetPermission.rangeProtectionRules$ -]).subscribe(([workbookSnapshot, worksheetSnapshot, rules]) => { - console.log('Combined state:', { - workbook: workbookSnapshot, - worksheet: worksheetSnapshot, - rules - }); - // 响应式更新整个权限 UI -}); -``` - -### 10. 高级用法:权限配置驱动 - -```typescript -import { WorksheetPermissionConfig } from '@univerjs/sheets'; - -// 使用配置对象批量设置权限 -const config: WorksheetPermissionConfig = { - mode: 'readOnly', - points: { - [WorksheetPermissionPoint.Filter]: true, - [WorksheetPermissionPoint.Sort]: true - }, - rangeProtections: [ - { - ranges: [worksheet.getRange('A1:A10')], - options: { name: 'Protected Column A' } - } - ] -}; - -await worksheetPermission.applyConfig(config); -``` - -## 设计亮点 - -### 1. 枚举驱动的 API - -使用枚举而非类构造器,降低学习成本: - -```typescript -// ❌ 旧 API(复杂) -permission.setWorkbookPermissionPoint(unitId, WorkbookEditablePermission, false); - -// ✅ 新 API(简单) -permission.setPoint(WorkbookPermissionPoint.Edit, false); -``` - -### 2. 模式化权限设置 - -提供高层抽象,简化常见场景: - -```typescript -// ❌ 旧 API(需要设置多个权限点) -permission.setWorkbookPermissionPoint(unitId, WorkbookEditablePermission, false); -permission.setWorkbookPermissionPoint(unitId, WorkbookPrintPermission, true); -// ... 设置更多权限点 - -// ✅ 新 API(一键设置) -await permission.setMode('viewer'); -``` - -### 3. 深度 RxJS 集成 - -所有响应式接口都基于 Observable/Subject: - -```typescript -// 实时监听权限变化 -permission.permission$.subscribe(snapshot => { - // 立即获得当前状态,后续自动更新 -}); - -// 组合多个流 -combineLatest([ - permission.permission$, - worksheetPermission.rangeProtectionRules$ -]).subscribe(([workbookSnapshot, rules]) => { - // 响应式更新 UI -}); -``` - -### 4. 批量操作支持 - -底层 Command 已支持批量,新 API 暴露此能力: - -```typescript -// ✅ 批量创建保护规则(一次 Command 执行) -const rules = await worksheetPermission.protectRanges([ - { ranges: [range1], options: { name: 'Rule 1' } }, - { ranges: [range2], options: { name: 'Rule 2' } }, - { ranges: [range3], options: { name: 'Rule 3' } } -]); -``` - -### 5. 调试友好 - -提供调试接口,方便排查问题: - -```typescript -const debugInfo = worksheetPermission.debugCellPermission(0, 0); -console.log('Hit rules:', debugInfo.hitRules); -``` - -## 迁移策略 - -### 从旧 API 迁移到新 API - -旧 API(`FPermission`)已标记为 `@deprecated`,建议逐步迁移到新 API。 - -#### 迁移对比表 - -| 旧 API | 新 API | 说明 | -|--------|--------|------| -| `workbook.getPermission()` | `workbook.getWorkbookPermission()` | 获取工作簿权限实例 | -| `permission.addRangeBaseProtection()` | `worksheet.getWorksheetPermission().protectRanges()` | 创建范围保护 | -| `permission.removeRangeProtection()` | `worksheetPermission.unprotectRules()` 或 `rule.remove()` | 删除范围保护 | -| `permission.setRangeProtectionRanges()` | `rule.updateRanges()` | 更新保护范围 | -| `permission.setWorkbookPermissionPoint()` | `workbookPermission.setPoint()` | 设置工作簿权限点 | -| 使用类构造器 | 使用枚举 | 更简单的 API | - -#### 迁移示例 1:创建范围保护 - -```typescript -// ❌ 旧 API -const workbook = univerAPI.getActiveWorkbook(); -const permission = workbook.getPermission(); -const unitId = workbook.getId(); -const worksheet = workbook.getActiveSheet(); -const subUnitId = worksheet.getSheetId(); -const range = worksheet.getRange('A1:B2'); -const ranges = [range]; - -// 步骤1:创建基础保护 -const res = await permission.addRangeBaseProtection(unitId, subUnitId, ranges); -const { permissionId, ruleId } = res; - -// 步骤2:手动设置权限点 -import { RangeProtectionPermissionEditPoint } from '@univerjs/sheets'; -const editPoint = new RangeProtectionPermissionEditPoint(unitId, subUnitId, permissionId); -await permission.setRangeProtectionPermissionPoint(editPoint, false); - -// ✅ 新 API(更简单) -const worksheet = univerAPI.getActiveWorkbook().getActiveSheet(); -const worksheetPermission = worksheet.getWorksheetPermission(); -const range = worksheet.getRange('A1:B2'); - -// 一步完成,自动处理权限点 -const rules = await worksheetPermission.protectRanges([ - { - ranges: [range], - options: { - name: 'Protected Area', - allowEdit: false // 自动设置权限点 - } - } -]); -``` - -#### 迁移示例 2:设置工作簿权限 - -```typescript -// ❌ 旧 API -import { WorkbookEditablePermission, WorkbookPrintPermission } from '@univerjs/sheets'; - -const permission = workbook.getPermission(); -const unitId = workbook.getId(); - -// 需要导入并使用类构造器 -permission.setWorkbookPermissionPoint(unitId, WorkbookEditablePermission, false); -permission.setWorkbookPermissionPoint(unitId, WorkbookPrintPermission, true); - -// ✅ 新 API(使用枚举) -import { WorkbookPermissionPoint } from '@univerjs/sheets'; - -const permission = workbook.getWorkbookPermission(); - -// 方式1:使用预定义模式(更简单) -await permission.setMode('viewer'); // 自动设置所有只读相关权限 - -// 方式2:精细控制单个权限点 -await permission.setPoint(WorkbookPermissionPoint.Edit, false); -await permission.setPoint(WorkbookPermissionPoint.Print, true); -``` - -#### 迁移示例 3:管理协作者 - -```typescript -// ❌ 旧 API(需要手动构造协议对象) -import { UnitObject, UnitAction, UnitRole } from '@univerjs/protocol'; - -const collaborator = { - id: 'collab-id', - subject: { - userID: 'user123', - name: 'John Doe', - avatar: '' - }, - role: UnitRole.Editor -}; - -await authzIoService.putCollaborators({ - objectType: UnitObject.Workbook, - // ... 复杂的对象构造 -}, [collaborator]); - -// ✅ 新 API(简化的接口) -const permission = workbook.getWorkbookPermission(); - -await permission.addCollaborator({ - userId: 'user123', - name: 'John Doe', - avatar: 'https://example.com/avatar.jpg', - role: 'editor' // 简单的字符串 -}); -``` - -### 迁移步骤建议 - -1. **阶段 1:新功能使用新 API** - - 所有新开发的功能直接使用新 API - - 熟悉新 API 的使用方式 - -2. **阶段 2:逐步迁移高频路径** - - 识别使用最频繁的权限操作 - - 优先迁移这些高频操作到新 API - - 保留低频操作使用旧 API - -3. **阶段 3:全面迁移** - - 使用 IDE 搜索 `@deprecated` 标记 - - 逐个文件迁移到新 API - - 运行测试确保功能正常 - -4. **阶段 4:清理旧代码** - - 在主版本升级时移除旧 API - - 更新所有文档和示例 - -### 向后兼容性保证 - -- ✅ 旧 API 将继续工作,不会破坏现有代码 -- ✅ 新旧 API 可以在同一代码库中共存 -- ✅ 旧 API 标记为 `@deprecated`,但不会被立即移除 -- ✅ 提供清晰的迁移路径和示例 - -## 技术细节 - -### 权限点映射机制 - -新 API 使用枚举,内部映射到现有权限类: - -```typescript -// 映射表(permission-point-map.ts) -export const WORKBOOK_PERMISSION_POINT_MAP = { - [WorkbookPermissionPoint.Edit]: WorkbookEditablePermission, - [WorkbookPermissionPoint.View]: WorkbookViewPermission, - // ... 45+ 个映射 -}; - -// 使用时自动转换 -setPoint(point: WorkbookPermissionPoint, value: boolean) { - const PointClass = WORKBOOK_PERMISSION_POINT_MAP[point]; - const instance = new PointClass(this._unitId); - this._permissionService.updatePermissionPoint(instance.id, value); -} -``` - -### Observable 流设计 - -使用 BehaviorSubject 确保订阅时立即获得当前状态: - -```typescript -class FWorkbookPermission { - private readonly _permissionSubject: BehaviorSubject; - readonly permission$: Observable; - - constructor() { - // 初始化时提供当前状态 - this._permissionSubject = new BehaviorSubject(this._buildSnapshot()); - this.permission$ = this._permissionSubject.asObservable().pipe( - distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)), - shareReplay(1) // 缓存最新值,新订阅者立即获得 - ); - } -} -``` - -### 批量操作性能优化 - -底层 Command 支持批量操作,新 API 充分利用此特性: - -```typescript -// ✅ 高效:一次 Command 执行,创建多个规则 -const rules = await worksheetPermission.protectRanges([ - { ranges: [range1], options: { name: 'Rule 1' } }, - { ranges: [range2], options: { name: 'Rule 2' } }, - { ranges: [range3], options: { name: 'Rule 3' } } -]); - -// ❌ 低效:多次 Command 执行 -for (const config of configs) { - await worksheetPermission.protectRanges([config]); // 每次都是一个 Command -} -``` - -### 范围重叠检测算法 - -在更新保护范围时,自动检测范围重叠: - -```typescript -private _rangesIntersect(range1: IRange, range2: IRange): boolean { - return !( - range1.endRow < range2.startRow || - range1.startRow > range2.endRow || - range1.endColumn < range2.startColumn || - range1.startColumn > range2.endColumn - ); -} -``` - -## 性能优化建议 - -### 1. 使用批量操作 - -```typescript -// ✅ 推荐:批量创建 -await worksheetPermission.protectRanges([ - { ranges: [range1], options: {...} }, - { ranges: [range2], options: {...} }, - { ranges: [range3], options: {...} } -]); - -// ❌ 避免:逐个创建 -await rangePermission1.protect({...}); -await rangePermission2.protect({...}); -await rangePermission3.protect({...}); -``` - -### 2. 合理使用 Observable 订阅 - -```typescript -// ✅ 推荐:使用 RxJS 操作符减少更新频率 -permission.permission$ - .pipe( - debounceTime(300), // 防抖 - distinctUntilChanged(), // 去重 - map(snapshot => snapshot.points) // 只关注需要的部分 - ) - .subscribe(points => { - // UI 更新 - }); - -// ❌ 避免:直接订阅可能导致过多更新 -permission.permission$.subscribe(snapshot => { - // 每次变化都触发 -}); -``` - -### 3. 及时取消订阅 - -```typescript -import { takeUntil } from 'rxjs/operators'; -import { Subject } from 'rxjs'; - -class MyComponent { - private destroy$ = new Subject(); - - ngOnInit() { - permission.permission$ - .pipe(takeUntil(this.destroy$)) - .subscribe(snapshot => { - // 组件销毁时自动取消订阅 - }); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } -} -``` - -### 4. 缓存权限检查结果 - -```typescript -// 如果需要频繁检查同一个单元格的权限 -class PermissionCache { - private cache = new Map(); - - canEditCell(row: number, col: number): boolean { - const key = `${row},${col}`; - - if (!this.cache.has(key)) { - this.cache.set(key, worksheetPermission.canEditCell(row, col)); - } - - return this.cache.get(key)!; - } - - clearCache() { - this.cache.clear(); - } -} - -// 权限变化时清除缓存 -worksheetPermission.permission$.subscribe(() => { - cache.clearCache(); -}); -``` - -## 最佳实践 - -### 1. 使用 TypeScript 类型 - -```typescript -import { - WorkbookPermissionPoint, - WorksheetPermissionPoint, - RangeProtectionOptions -} from '@univerjs/sheets'; - -// ✅ 类型安全 -const point: WorkbookPermissionPoint = WorkbookPermissionPoint.Edit; -const options: RangeProtectionOptions = { - name: 'Protected', - allowEdit: false -}; - -// ❌ 避免使用字符串 -const point = 'Edit'; // 编译时无法检测错误 -``` - -### 2. 使用模式化 API 简化常见场景 - -```typescript -// ✅ 推荐:使用模式快速设置 -await permission.setMode('viewer'); - -// ❌ 不推荐:手动设置每个权限点(除非有特殊需求) -await permission.setPoint(WorkbookPermissionPoint.Edit, false); -await permission.setPoint(WorkbookPermissionPoint.Print, true); -await permission.setPoint(WorkbookPermissionPoint.Export, true); -// ... 设置更多权限点 -``` - -### 3. 利用调试工具排查问题 - -```typescript -// 当单元格权限行为不符合预期时 -const debugInfo = worksheetPermission.debugCellPermission(row, col); - -if (debugInfo) { - console.log('Cell is protected by these rules:'); - debugInfo.hitRules.forEach(rule => { - console.log(`- Rule ${rule.ruleId}:`, rule.options); - }); -} else { - console.log('Cell is not protected'); -} -``` - -### 4. 组合多个权限级别 - -```typescript -import { combineLatest } from 'rxjs'; - -// 综合考虑 Workbook、Worksheet、Range 三级权限 -combineLatest([ - workbookPermission.permission$, - worksheetPermission.permission$, - rangePermission.permission$ -]).pipe( - map(([workbook, worksheet, range]) => { - // 权限是层级递减的:Workbook > Worksheet > Range - return { - canEdit: workbook[WorkbookPermissionPoint.Edit] && - worksheet[WorksheetPermissionPoint.Edit] && - range[RangePermissionPoint.Edit] - }; - }) -).subscribe(result => { - console.log('Final permission:', result); -}); -``` - -### 5. 错误处理 - -```typescript -try { - await permission.protectRanges([ - { ranges: [range1], options: { name: 'Rule 1' } } - ]); -} catch (error) { - if (error.message.includes('intersect')) { - console.error('Range overlaps with existing protection'); - // 处理范围重叠错误 - } else { - console.error('Failed to protect range:', error); - } -} -``` - -## 参考文档 - -- 设计文档:`/refactor.md` -- 现有 API:`/submodules/univer/packages/sheets/src/facade/f-permission.ts` -- 权限点定义:`/submodules/univer/packages/sheets/src/services/permission/permission-point/` - -## 文件清单 - -### 核心实现文件 -- `permission-types.ts` (571 行) - 类型定义和枚举 -- `permission-point-map.ts` (125 行) - 权限点映射 -- `f-workbook-permission.ts` (355 行) - 工作簿权限实现 -- `f-worksheet-permission.ts` (429 行) - 工作表权限实现 -- `f-range-permission.ts` (307 行) - 范围权限实现 -- `f-range-protection-rule.ts` (142 行) - 保护规则实现 - -### 测试文件 -- `__tests__/f-workbook-permission.spec.ts` (282 行) - 工作簿权限测试 -- `__tests__/f-worksheet-permission.spec.ts` (428 行) - 工作表权限测试 -- `__tests__/f-range-permission.spec.ts` (307 行) - 范围权限测试 -- `__tests__/f-range-protection-rule.spec.ts` (507 行) - 保护规则测试 -- `__tests__/permission-combination.spec.ts` (483 行) - 权限组合逻辑测试 -- `__tests__/TEST_SUMMARY.md` - 测试总结文档 - -### 支持文件 -- `index.ts` (22 行) - 导出文件 -- `README.md` (本文件) - 项目文档 - -### 集成文件(已修改) -- `f-workbook.ts` - 添加 `getWorkbookPermission()` 方法 -- `f-worksheet.ts` - 添加 `getWorksheetPermission()` 方法 -- `f-range.ts` - 添加 `getRangePermission()` 方法 -- `f-permission.ts` - 添加 `@deprecated` 标记 - -## 代码统计 - -- **总代码行数**: 约 4,200+ 行 -- **核心实现**: 1,929 行 -- **类型定义**: 571 行 -- **单元测试**: 2,007 行 - - `f-workbook-permission.spec.ts`: 282 行 - - `f-worksheet-permission.spec.ts`: 428 行 - - `f-range-permission.spec.ts`: 307 行 - - `f-range-protection-rule.spec.ts`: 507 行 - - `permission-combination.spec.ts`: 483 行 -- **文档**: 本 README (800+ 行) -- **编译错误**: 0 个 -- **测试覆盖率**: 全面覆盖所有核心功能 - -## 运行测试 - -### 运行所有权限测试 - -```bash -# 进入 univer 子模块 -cd submodules/univer - -# 运行权限相关的所有测试 -pnpm test packages/sheets/src/facade/permission/__tests__ -``` - -### 运行单个测试文件 - -```bash -# 测试 WorkbookPermission -pnpm test packages/sheets/src/facade/permission/__tests__/f-workbook-permission.spec.ts - -# 测试 WorksheetPermission -pnpm test packages/sheets/src/facade/permission/__tests__/f-worksheet-permission.spec.ts - -# 测试 RangePermission -pnpm test packages/sheets/src/facade/permission/__tests__/f-range-permission.spec.ts - -# 测试 RangeProtectionRule -pnpm test packages/sheets/src/facade/permission/__tests__/f-range-protection-rule.spec.ts - -# 测试权限组合逻辑 -pnpm test packages/sheets/src/facade/permission/__tests__/permission-combination.spec.ts -``` - -### 测试覆盖率报告 - -```bash -# 生成测试覆盖率报告 -pnpm test --coverage packages/sheets/src/facade/permission -``` - -### 监视模式(开发时使用) - -```bash -# 监视文件变化,自动运行测试 -pnpm test --watch packages/sheets/src/facade/permission/__tests__ -``` - -## 测试结构 - -### 测试文件组织 - -``` -__tests__/ -├── f-workbook-permission.spec.ts # WorkbookPermission 单元测试 -├── f-worksheet-permission.spec.ts # WorksheetPermission 单元测试 -├── f-range-permission.spec.ts # RangePermission 单元测试 -├── f-range-protection-rule.spec.ts # RangeProtectionRule 单元测试 -└── permission-combination.spec.ts # 权限组合逻辑集成测试 -``` - -### 测试覆盖范围 - -#### FWorkbookPermission 测试 (282 行) -- ✅ 基础操作:获取实例、设置/获取权限点、获取快照 -- ✅ 模式操作:viewer、editor、owner、commenter -- ✅ 快捷方法:setReadOnly()、setEditable() -- ✅ 响应式流:permission$ 订阅和变化监听 -- ✅ 权限点覆盖:测试所有主要权限点 - -#### FWorksheetPermission 测试 (428 行) -- ✅ 基础操作:获取实例、权限点操作、可编辑性检查 -- ✅ 模式操作:readOnly、editable、filterOnly、commentOnly -- ✅ 单元格级权限:canEditCell()、canViewCell() -- ✅ 范围保护:protectRanges()、unprotectRules()、批量操作 -- ✅ 调试工具:debugCellPermission() 详细调试信息 -- ✅ 响应式流:permission$、rangeProtectionChange$、rangeProtectionRules$ - -#### FRangePermission 测试 (307 行) -- ✅ 基础操作:获取实例、权限快照、权限点 -- ✅ 保护操作:protect()、unprotect()、用户白名单、元数据 -- ✅ 状态检查:isProtected()、canEdit() -- ✅ 规则管理:listRules()、重叠范围处理 -- ✅ 响应式流:permission$、protectionChange$ -- ✅ 错误处理:边界情况和异常处理 - -#### FRangeProtectionRule 测试 (507 行) -- ✅ 基础操作:创建规则、访问属性 -- ✅ 更新范围:updateRanges()、多范围、重叠检测 -- ✅ 更新选项:name、allowEdit、allowedUsers、metadata、部分更新 -- ✅ 删除规则:remove()、重复删除处理 -- ✅ 复杂场景:多次连续更新、独立更新验证 - -#### 权限组合逻辑测试 (483 行) -- ✅ 层级权限:workbook、worksheet、range 三级组合 -- ✅ 单元格权限:范围保护、重叠规则、调试工具 -- ✅ 批量操作:批量创建、批量删除、性能验证 -- ✅ 响应式流:combineLatest、变化监听、规则列表 -- ✅ 模式转换:不同模式间的转换验证 -- ✅ 边界情况:空列表、不存在的单元格、重复操作 - -### 测试最佳实践 - -所有测试遵循以下最佳实践: - -1. **使用 Vitest 框架**: 快速、现代的测试框架 -2. **测试隔离**: 每个测试用例独立运行,不依赖其他测试 -3. **完整设置**: 使用 `createFacadeTestBed()` 创建完整的测试环境 -4. **命令注册**: 在 `beforeEach` 中注册必要的命令 -5. **清晰断言**: 使用明确的 `expect()` 断言 -6. **错误处理**: 测试正常流程和异常情况 -7. **响应式测试**: 验证 Observable 流的行为 -8. **批量操作**: 测试性能关键的批量操作 - -## 代码统计 - -- **总代码行数**: 约 4,200+ 行 -- **核心实现**: 1,929 行 -- **类型定义**: 571 行 -- **单元测试**: 2,007 行 -- **文档**: 本 README -- **编译错误**: 0 个 -- **测试覆盖率**: 全面覆盖所有核心功能 - -## 常见问题(FAQ) - -### Q1: 新旧 API 可以混用吗? - -**A**: 可以。新旧 API 完全兼容,可以在同一代码库中共存。但建议逐步迁移到新 API 以获得更好的开发体验。 - -### Q2: 为什么使用枚举而不是类构造器? - -**A**: 枚举更直观、更易用,降低了学习成本。开发者不需要了解底层的权限类结构,只需要知道权限点的名称即可。 - -### Q3: 批量操作的性能优势有多大? - -**A**: 批量操作只执行一次 Command,而逐个操作需要多次 Command 执行。对于创建 10 个保护规则,批量操作可以节省 90% 的性能开销。 - -### Q4: Observable 流的内存开销如何? - -**A**: 使用了 `shareReplay(1)` 和 `distinctUntilChanged()`,确保内存高效。但请记得在组件销毁时取消订阅以避免内存泄漏。 - -### Q5: 如何调试权限问题? - -**A**: 使用 `debugCellPermission()` 方法可以查看单元格命中了哪些保护规则,这对于排查复杂的权限配置非常有用。 - -### Q6: 权限是如何层级组合的? - -**A**: 权限遵循层级递减原则:Workbook > Worksheet > Range。如果 Workbook 禁止编辑,即使 Worksheet 和 Range 允许,最终也无法编辑。 - -### Q7: 如何处理权限冲突? - -**A**: -- 同级范围保护不能重叠(会抛出错误) -- 使用 `updateRanges()` 方法时会自动检测重叠 -- 调用 `debugCellPermission()` 可以看到所有命中的规则 - -### Q8: 支持协作者的细粒度权限吗? - -**A**: 是的。可以为每个协作者设置不同的角色(owner/editor/reader),并在保护规则中指定 `allowedUsers` 白名单。 - -## 已知限制 - -1. **范围保护不能重叠**: 这是设计限制,确保权限逻辑清晰 -2. **权限变化需要 Command 执行**: 所有权限修改都通过 Command 系统,支持 undo/redo -3. **跨 Workbook 权限**: 当前 API 主要关注单个 Workbook 内的权限管理 - -## 未来计划 - -- [ ] 添加权限模板功能(预定义常用的权限组合) -- [ ] 支持权限继承和权限组 -- [ ] 提供权限变更历史记录 -- [ ] 添加权限导入/导出功能 -- [ ] 优化大量范围保护的性能 - -## 贡献指南 - -欢迎贡献!请遵循以下步骤: - -1. Fork 本仓库 -2. 创建特性分支:`git checkout -b feature/permission-templates` -3. 编写代码和测试 -4. 确保所有测试通过:`pnpm test` -5. 提交更改:`git commit -am 'Add permission templates'` -6. 推送到分支:`git push origin feature/permission-templates` -7. 提交 Pull Request - -### 代码风格 - -- 使用 TypeScript 严格模式 -- 遵循 ESLint 配置 -- 添加完整的 JSDoc 注释 -- 为新功能编写单元测试 - -## 许可证 - -Apache License 2.0 - ---- - -**感谢使用 Univer 权限系统 Facade API!** - -如有问题或建议,请在 GitHub 上提 Issue 或 Pull Request。 diff --git a/packages/sheets/src/facade/permission/f-range-permission.ts b/packages/sheets/src/facade/permission/f-range-permission.ts index 14c63f9654..f5eeb5011d 100644 --- a/packages/sheets/src/facade/permission/f-range-permission.ts +++ b/packages/sheets/src/facade/permission/f-range-permission.ts @@ -208,7 +208,7 @@ export class FRangePermission implements IRangePermission { * ```ts * const range = univerAPI.getActiveWorkbook()?.getActiveSheet()?.getRange('A1:B2'); * const permission = range?.getRangePermission(); - * const canEdit = permission?.getPoint(RangePermissionPoint.Edit); + * const canEdit = permission?.getPoint(univerAPI.Enum.RangePermissionPoint.Edit); * console.log(canEdit); * ``` */ @@ -359,8 +359,8 @@ export class FRangePermission implements IRangePermission { * const range = univerAPI.getActiveWorkbook()?.getActiveSheet()?.getRange('A1:B2'); * const permission = range?.getRangePermission(); * // Can set permission points without calling protect() first (local-only mode) - * await permission?.setPoint(RangePermissionPoint.Edit, false); // Disable edit - * await permission?.setPoint(RangePermissionPoint.View, true); // Enable view + * await permission?.setPoint(univerAPI.Enum.RangePermissionPoint.Edit, false); // Disable edit + * await permission?.setPoint(univerAPI.Enum.RangePermissionPoint.View, true); // Enable view * ``` */ async setPoint(point: RangePermissionPoint, value: boolean): Promise { diff --git a/packages/sheets/src/facade/permission/f-range-protection-rule.ts b/packages/sheets/src/facade/permission/f-range-protection-rule.ts index 8330056e82..376fd38f53 100644 --- a/packages/sheets/src/facade/permission/f-range-protection-rule.ts +++ b/packages/sheets/src/facade/permission/f-range-protection-rule.ts @@ -44,7 +44,7 @@ export class FRangeProtectionRule implements IRangeProtectionRule { * @example * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); - * const permission = worksheet?.permission(); + * const permission = worksheet?.getWorksheetPermission(); * const rules = await permission?.listRangeProtectionRules(); * const ruleId = rules?.[0]?.id; * console.log(ruleId); @@ -60,7 +60,7 @@ export class FRangeProtectionRule implements IRangeProtectionRule { * @example * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); - * const permission = worksheet?.permission(); + * const permission = worksheet?.getWorksheetPermission(); * const rules = await permission?.listRangeProtectionRules(); * const ranges = rules?.[0]?.ranges; * console.log(ranges); @@ -76,7 +76,7 @@ export class FRangeProtectionRule implements IRangeProtectionRule { * @example * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); - * const permission = worksheet?.permission(); + * const permission = worksheet?.getWorksheetPermission(); * const rules = await permission?.listRangeProtectionRules(); * const options = rules?.[0]?.options; * console.log(options); @@ -93,7 +93,7 @@ export class FRangeProtectionRule implements IRangeProtectionRule { * @example * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); - * const permission = worksheet?.permission(); + * const permission = worksheet?.getWorksheetPermission(); * const rules = await permission?.listRangeProtectionRules(); * const rule = rules?.[0]; * await rule?.updateRanges([worksheet.getRange('A1:C3')]); @@ -150,7 +150,7 @@ export class FRangeProtectionRule implements IRangeProtectionRule { * @example * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); - * const permission = worksheet?.permission(); + * const permission = worksheet?.getWorksheetPermission(); * const rules = await permission?.listRangeProtectionRules(); * const rule = rules?.[0]; * await rule?.updateOptions({ name: 'New Protection Name', allowEdit: true }); @@ -188,7 +188,7 @@ export class FRangeProtectionRule implements IRangeProtectionRule { * @example * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); - * const permission = worksheet?.permission(); + * const permission = worksheet?.getWorksheetPermission(); * const rules = await permission?.listRangeProtectionRules(); * const rule = rules?.[0]; * await rule?.remove(); diff --git a/packages/sheets/src/facade/permission/f-workbook-permission.ts b/packages/sheets/src/facade/permission/f-workbook-permission.ts index 61d697ad23..444d48b872 100644 --- a/packages/sheets/src/facade/permission/f-workbook-permission.ts +++ b/packages/sheets/src/facade/permission/f-workbook-permission.ts @@ -338,7 +338,7 @@ export class FWorkbookPermission implements IWorkbookPermission { * ```ts * const workbook = univerAPI.getActiveWorkbook(); * const permission = workbook?.getWorkbookPermission(); - * await permission?.setPoint(WorkbookPermissionPoint.Print, false); + * await permission?.setPoint(univerAPI.Enum.WorkbookPermissionPoint.Print, false); * ``` */ async setPoint(point: WorkbookPermissionPoint, value: boolean): Promise { @@ -374,7 +374,7 @@ export class FWorkbookPermission implements IWorkbookPermission { * ```ts * const workbook = univerAPI.getActiveWorkbook(); * const permission = workbook?.getWorkbookPermission(); - * const canPrint = permission?.getPoint(WorkbookPermissionPoint.Print); + * const canPrint = permission?.getPoint(univerAPI.Enum.WorkbookPermissionPoint.Print); * console.log(canPrint); * ``` */ @@ -416,11 +416,11 @@ export class FWorkbookPermission implements IWorkbookPermission { * await permission?.setCollaborators([ * { * user: { userID: 'user1', name: 'John Doe', avatar: 'https://...' }, - * role: UnitRole.Editor + * role: univerAPI.Enum.UnitRole.Editor * }, * { * user: { userID: 'user2', name: 'Jane Smith', avatar: '' }, - * role: UnitRole.Reader + * role: univerAPI.Enum.UnitRole.Reader * } * ]); * ``` @@ -463,7 +463,7 @@ export class FWorkbookPermission implements IWorkbookPermission { * const permission = workbook?.getWorkbookPermission(); * await permission?.addCollaborator( * { userID: 'user1', name: 'John Doe', avatar: 'https://...' }, - * UnitRole.Editor + * univerAPI.Enum.UnitRole.Editor * ); * ``` */ @@ -498,7 +498,7 @@ export class FWorkbookPermission implements IWorkbookPermission { * const permission = workbook?.getWorkbookPermission(); * await permission?.updateCollaborator( * { userID: 'user1', name: 'John Doe Updated', avatar: 'https://...' }, - * UnitRole.Reader + * univerAPI.Enum.UnitRole.Reader * ); * ``` */ diff --git a/packages/sheets/src/facade/permission/f-worksheet-permission.ts b/packages/sheets/src/facade/permission/f-worksheet-permission.ts index 5a0f98d2e9..08c1ca3853 100644 --- a/packages/sheets/src/facade/permission/f-worksheet-permission.ts +++ b/packages/sheets/src/facade/permission/f-worksheet-permission.ts @@ -517,7 +517,7 @@ export class FWorksheetPermission implements IWorksheetPermission { * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); * const permission = worksheet?.getWorksheetPermission(); - * await permission?.setPoint(WorksheetPermissionPoint.InsertRow, false); + * await permission?.setPoint(univerAPI.Enum.WorksheetPermissionPoint.InsertRow, false); * ``` */ async setPoint(point: WorksheetPermissionPoint, value: boolean): Promise { @@ -553,7 +553,7 @@ export class FWorksheetPermission implements IWorksheetPermission { * ```ts * const worksheet = univerAPI.getActiveWorkbook()?.getActiveSheet(); * const permission = worksheet?.getWorksheetPermission(); - * const canInsertRow = permission?.getPoint(WorksheetPermissionPoint.InsertRow); + * const canInsertRow = permission?.getPoint(univerAPI.Enum.WorksheetPermissionPoint.InsertRow); * console.log(canInsertRow); * ``` */ @@ -595,8 +595,8 @@ export class FWorksheetPermission implements IWorksheetPermission { * await permission?.applyConfig({ * mode: 'readOnly', * points: { - * [WorksheetPermissionPoint.View]: true, - * [WorksheetPermissionPoint.Edit]: false + * [univerAPI.Enum.WorksheetPermissionPoint.View]: true, + * [univerAPI.Enum.WorksheetPermissionPoint.Edit]: false * } * }); * ``` @@ -637,11 +637,11 @@ export class FWorksheetPermission implements IWorksheetPermission { * const rules = await permission?.protectRanges([ * { * ranges: [worksheet.getRange('A1:B2')], - * options: { name: 'Protected Area 1', allowEdit: false, allowView: true } + * options: { name: 'Protected Area 1', allowEdit: false, allowViewByOthers: true } * }, * { * ranges: [worksheet.getRange('C3:D4')], - * options: { name: 'Protected Area 2', allowEdit: true, allowView: false } + * options: { name: 'Protected Area 2', allowEdit: true, allowViewByOthers: false } * } * ]); * console.log(rules);