現(xiàn)指定區(qū)域可編輯的完整指南)
“想用在線表格做一個(gè)報(bào)名表或者工單登記表業(yè)務(wù)同學(xué)打開網(wǎng)頁(yè)就能填但只能改我指定的那幾格標(biāo)題、說(shuō)明還有那些公式列一概不能碰”——這是我最近被問(wèn)到次數(shù)最多的一個(gè)需求。字面聽上去不復(fù)雜真正落到代碼里才發(fā)現(xiàn)彎彎繞繞不少。如果你正在用Univer搞這類“用戶定義表格 指定單元格填寫、其他單元格鎖定”的在線表格場(chǎng)景或者剛接觸 Univer 在評(píng)估它能不能扛起這個(gè)業(yè)務(wù)這篇就是給你準(zhǔn)備的實(shí)操筆記。我會(huì)從項(xiàng)目選型講到權(quán)限模型再給可直接抄的配置和代碼最后整理幾個(gè)我實(shí)際踩過(guò)的坑。1. Univer 是什么項(xiàng)目定位與核心技術(shù)拆解1.1 開箱即用的在線表格內(nèi)核Univer 是一套基于 TypeScript 構(gòu)建的開源辦公套件方案主打在線創(chuàng)建、編輯和預(yù)覽電子表格、文檔與幻燈片。如果你聽過(guò) Luckysheet那可以把它理解為 Luckysheet 團(tuán)隊(duì)在架構(gòu)上推翻重做后的下一代產(chǎn)物渲染層換成了 Canvas 繪制核心與 UI 徹底解耦插件化程度更高還內(nèi)置了一套公式引擎和命令系統(tǒng)。從業(yè)務(wù)接入角度Univer 最讓人舒服的一點(diǎn)是它不是給你一個(gè)“靜態(tài)表格組件”而是給你一個(gè)完整的、可編程的表格運(yùn)行時(shí)。你可以在里面定義工作簿、工作表、單元格數(shù)據(jù)、公式、樣式、數(shù)據(jù)校驗(yàn)也可以通過(guò)命令服務(wù)去執(zhí)行“設(shè)置單元格值”“合并單元格”“開啟工作表保護(hù)”這些操作。換句話說(shuō)你不需要像過(guò)去那樣在一堆 div 和 table 標(biāo)簽上模擬表格行為而是直接操作一個(gè)真實(shí)存在的電子表格實(shí)例。Univer 的模塊劃分大概是這樣univerjs/core核心數(shù)據(jù)模型工作簿、工作表、單元格、區(qū)域、樣式、命令框架都在這層。univerjs/sheets表格業(yè)務(wù)邏輯包括單元格編輯、選區(qū)、公式計(jì)算、篩選、排序等基礎(chǔ)能力。univerjs/sheets-ui表格 UI 層負(fù)責(zé)渲染工具欄、編輯欄、右鍵菜單、彈窗。univerjs/ui通用 UI 基礎(chǔ)設(shè)施讓 Univer 可以嵌入 React、Vue 或原生 JS 項(xiàng)目。univerjs/engine-formula公式引擎用來(lái)處理跨表、跨工作簿的公式計(jì)算。univerjs/engine-renderCanvas 渲染引擎負(fù)責(zé)把表格畫到頁(yè)面上。這個(gè)分層直接決定了它的擴(kuò)展性。比如你只想用表格編輯能力不想顯示官方那一整條工具欄那完全可以不注冊(cè) UI 插件自己寫一套編輯入口反過(guò)來(lái)如果你需要標(biāo)準(zhǔn) Excel 體驗(yàn)把 sheets-ui 注冊(cè)上就基本齊了。正因?yàn)檫@種靈活度Univer 特別適合做“半定制化”的業(yè)務(wù)表格而不是只能全盤照搬的標(biāo)準(zhǔn) Excel。1.2 為什么選 Univer 做“用戶填表”場(chǎng)景做“用戶填寫指定單元格”這件事傳統(tǒng)方案一般有三條路直接發(fā) Excel 模板讓人填了再回收、用傳統(tǒng)前端表格組件仿一個(gè)填表頁(yè)、或者直接在自己系統(tǒng)里做表單引擎。這三條路各有各的別扭Excel 模板回收版本混亂、格式被改、收集匯總?cè)咳巳怏w驗(yàn)很差。前端表格組件仿填表頁(yè)表格行為很難做到位。用戶想要拖動(dòng)填充、下拉選擇、公式聯(lián)動(dòng)時(shí)基本都要自己造輪子。表單引擎雖然能限制輸入項(xiàng)但表達(dá)不了表格布局。比如一行一個(gè)項(xiàng)目的工時(shí)填報(bào)或者橫豎軸交叉的排班表用表單控件排出來(lái)非常痛苦。Univer 剛好卡在中間它有原生表格的交互能力又允許你用編程方式控制哪些單元格可編輯、哪些被鎖定。管理員先在頁(yè)面上把模板搭好鎖定不需要用戶碰的區(qū)域再把鏈接發(fā)給用戶用戶在網(wǎng)頁(yè)里只能按預(yù)定位置填寫。整個(gè)流程在線上閉環(huán)數(shù)據(jù)直接回傳后端既避免了 Excel 文件滿天飛又保留了表格天然的布局表達(dá)能力。更關(guān)鍵的是Univer 的保護(hù)機(jī)制不是“只能設(shè)置整表只讀”這種一刀切它支持把工作表的“保護(hù)”和“單元格的鎖定屬性”拆開組合配合非常細(xì)的權(quán)限范圍能夠準(zhǔn)確實(shí)現(xiàn)“某些區(qū)域可以編輯、其他區(qū)域不能改”的需求。這就是這篇實(shí)操里最核心的切入點(diǎn)。2. 需求拆解讓用戶填寫指定單元格其余鎖定2.1 “用戶定義表格”的業(yè)務(wù)本質(zhì)先把“用戶定義表格”這個(gè)說(shuō)法拆開。用戶這個(gè)詞在不同場(chǎng)景里指代不一樣。在多數(shù)業(yè)務(wù)系統(tǒng)里設(shè)計(jì)表格模板的是管理員或財(cái)務(wù)最終填寫數(shù)據(jù)的是普通員工或外部客戶。所以“用戶定義表格”實(shí)際上包含兩層含義模板定義權(quán)誰(shuí)來(lái)創(chuàng)建表格結(jié)構(gòu)、設(shè)置標(biāo)題、公式、校驗(yàn)規(guī)則、鎖定規(guī)則。數(shù)據(jù)填寫權(quán)誰(shuí)能在特定區(qū)域內(nèi)填入內(nèi)容。這篇文章要解決的核心是第二層但實(shí)現(xiàn)第二層之前必須先想清楚第一層。因?yàn)槟0宓亩x過(guò)程往往也需要在 Univer 里面完成如果管理員自己都分不清哪些單元格是鎖定用的、哪些是放開用的后面所有規(guī)則都是空中樓閣。一個(gè)典型的業(yè)務(wù)例子是培訓(xùn)報(bào)名表A1:D1 是合并標(biāo)題“2025年第三期安全培訓(xùn)報(bào)名表”。第二行是列名姓名、部門、郵箱、是否住宿。管理員不希望用戶改標(biāo)題和列名甚至不希望用戶能選中這些單元格用戶只需要從第三行往下填寫自己的信息如果郵箱格式錯(cuò)了表格應(yīng)給出提示。這個(gè)例子里的“可編輯區(qū)域”就是一個(gè)從第三行到表格末尾的數(shù)據(jù)區(qū)域。用戶在這個(gè)區(qū)域里輸入內(nèi)容其他區(qū)域要么鎖定、要么只讀。管理員創(chuàng)建模板時(shí)也應(yīng)該把這個(gè)規(guī)則體現(xiàn)在配置里而不是等表格上線后再去臨時(shí)設(shè)置。2.2 權(quán)限模型與可編輯范圍控制的關(guān)鍵點(diǎn)Univer 控制可編輯性的機(jī)制本質(zhì)上沿用了 Excel 那套經(jīng)典的“工作表保護(hù) 單元格鎖定”模型。先記住一個(gè)關(guān)鍵結(jié)論單元格默認(rèn)的鎖定狀態(tài)并不等于用戶不可編輯。只有當(dāng)工作表開啟了保護(hù)protection之后鎖定屬性才會(huì)生效。這個(gè)關(guān)系可以類比成小區(qū)門禁每個(gè)房間有門鎖單元格 locked 狀態(tài)但只有保安啟動(dòng)門禁系統(tǒng)工作表保護(hù)這些門鎖才真正起作用。如果你只給每個(gè)房間換了鎖卻讓保安放假那誰(shuí)都能推門進(jìn)去。在 Univer 的配置模型里工作表保護(hù)對(duì)象至少包含這幾個(gè)關(guān)鍵字段sheet布爾值表示這張工作表是否啟用保護(hù)。lockCells布爾值表示當(dāng)前工作表是否鎖定所有單元格。ranges數(shù)組用來(lái)聲明保護(hù)范圍內(nèi)的例外區(qū)域每個(gè)區(qū)域可以單獨(dú)設(shè)置是否允許鎖定。當(dāng)lockCells: true且protection.sheet: true時(shí)整張表默認(rèn)不可編輯只有ranges里明確列為 unlock 的區(qū)域可以編輯。反過(guò)來(lái)如果lockCells: false整張表默認(rèn)可編輯ranges里的區(qū)域可以被單獨(dú)鎖死。大多數(shù)“用戶填表”場(chǎng)景用的是前者先鎖全表再把填寫區(qū)域放出來(lái)。還有兩個(gè)容易被忽略的配置項(xiàng)allowSelectingLockedCells和allowSelectingUnlockedCells。前者控制用戶能不能點(diǎn)選鎖定區(qū)域后者控制用戶能不能點(diǎn)選可編輯區(qū)域。如果業(yè)務(wù)上要求“用戶連標(biāo)題都選不中”就把a(bǔ)llowSelectingLockedCells設(shè)為 false如果允許用戶點(diǎn)選已填寫的內(nèi)容只是不能修改那就保持為 true。我這邊的經(jīng)驗(yàn)是填表業(yè)務(wù)里通常把兩個(gè)都放開因?yàn)橛脩暨x中有助于看清他填過(guò)什么只要不能編輯就可以了。另外要注意Univer 的保護(hù)模型是工作表級(jí)別的不是工作簿級(jí)別。如果你想整個(gè)工作簿都進(jìn)入“填表模式”需要遍歷里面每一張工作表分別設(shè)置保護(hù)。如果有多個(gè) Sheet且用戶應(yīng)該只能看到其中一張?zhí)顖?bào)表那更實(shí)用的做法是直接隱藏其他工作表只保留目標(biāo)表。3. 實(shí)操在 Univer 中實(shí)現(xiàn)“可指定區(qū)域填寫”3.1 環(huán)境準(zhǔn)備與最小示例先搭一個(gè)最小可運(yùn)行的 Univer 項(xiàng)目。我用的是 Vite TypeScript 的 React 工程其實(shí)框架不限Univer 官方封裝好了 React 組件和非 React 接入兩種方式核心邏輯一樣。安裝依賴npm install univerjs/core univerjs/sheets univerjs/sheets-ui univerjs/ui univerjs/engine-formula univerjs/engine-render初始化代碼大概長(zhǎng)這樣import { Univer } from univerjs/core; import { UniverSheetsPlugin } from univerjs/sheets; import { UniverSheetsUIPlugin } from univerjs/sheets-ui; import { UniverUIPlugin } from univerjs/ui; import { UniverFormulaEnginePlugin } from univerjs/engine-formula; const univer new Univer({ locale: zhCN, }); univer.registerPlugin(UniverSheetsPlugin); univer.registerPlugin(UniverFormulaEnginePlugin); univer.registerPlugin(UniverUIPlugin, { container: app, }); univer.registerPlugin(UniverSheetsUIPlugin);這一步跑起來(lái)以后頁(yè)面右上角會(huì)出現(xiàn) Univer 自己的工具欄。我建議開發(fā)階段不要急著隱藏工具欄因?yàn)椤氨Wo(hù)工作表”這個(gè)功能在工具欄里可以直接點(diǎn)方便你驗(yàn)證效果。生產(chǎn)環(huán)境再根據(jù)業(yè)務(wù)隱藏不必要按鈕。然后在項(xiàng)目里創(chuàng)建一張工作表數(shù)據(jù)。Univer 創(chuàng)建表格時(shí)可以傳一個(gè)類似“工作表配置快照”的對(duì)象里面包含單元格數(shù)據(jù)、樣式、合并信息、行高列寬還有我們關(guān)心的保護(hù)配置。下面的示例是完整模板結(jié)構(gòu)const formSheet { id: training-signup, name: 報(bào)名填寫, rowCount: 20, columnCount: 6, cellData: { 0: { 0: { v: 2025年第三期安全培訓(xùn)報(bào)名表, s: { bl: 1, bg: #f2f2f2, locked: true, merge: 3 } }, }, 1: { 0: { v: 姓名, s: { locked: true, bg: #e8e8e8 } }, 1: { v: 部門, s: { locked: true, bg: #e8e8e8 } }, 2: { v: 郵箱, s: { locked: true, bg: #e8e8e8 } }, 3: { v: 是否住宿, s: { locked: true, bg: #e8e8e8 } }, }, }, protection: { sheet: true, lockCells: true, allowSelectingLockedCells: true, allowSelectingUnlockedCells: true, ranges: [ { range: { startRow: 2, endRow: 19, startColumn: 0, endColumn: 3 }, lock: false, }, ], }, }; univer.createSheet(formSheet);這段配置的作用很直白把整張表鎖定然后在第 3 行到第 20 行的前四列放出一個(gè)可編輯區(qū)域。用戶在網(wǎng)頁(yè)上打開后表頭區(qū)域內(nèi)容灰色、選中但改不了第 3 行及以下的白色區(qū)域內(nèi)用戶可以像操作 Excel 一樣輸入姓名、部門、郵箱和住宿信息。3.2 定義可填寫區(qū)域模板配置方式上一步的 protection 配置里ranges就是指“放給用戶編輯的區(qū)域”。這個(gè)數(shù)組可以包含多個(gè)不連續(xù)的區(qū)域比如一張表上既有“基本信息區(qū)”又有“家庭成員區(qū)”那就寫兩個(gè) range 對(duì)象。注意startRow和endRow、startColumn、endColumn都是從 0 開始計(jì)數(shù)的表格第 1 行對(duì)應(yīng) startRow 0第 1 列對(duì)應(yīng) startColumn 0。這個(gè)計(jì)數(shù)方式特別容易踩坑我第一次寫就把第三行寫成了 startRow 2 還是 3 糾結(jié)了半天。單元格樣式里的locked是給單元格本身打標(biāo)用的。如果你在創(chuàng)建模板時(shí)想明確某個(gè)單元格“永遠(yuǎn)不能被編輯”除了在 protection 的 ranges 里不放這個(gè)區(qū)域還可以同時(shí)在s.locked上做標(biāo)記。但需要說(shuō)明locked 只是標(biāo)簽保護(hù)范圍才是最終執(zhí)行依據(jù)。開啟保護(hù)后Univer 執(zhí)行編輯命令時(shí)會(huì)去檢查“當(dāng)前選中區(qū)域是否在 protection 的允許編輯范圍內(nèi)”。所以模板正確性最終看 protection.ranges單元格的 locked 屬性用來(lái)配合 UI 展示比如給鎖定區(qū)域加淺灰背景讓用戶一眼看出哪里不能填。3.3 運(yùn)行時(shí)切換編輯權(quán)限命令方式模板配置是“靜態(tài)初始化”的做法適合表格結(jié)構(gòu)在代碼里寫死。但真實(shí)業(yè)務(wù)里管理員很可能要在界面上臨時(shí)修改可編輯范圍這就需要用命令動(dòng)態(tài)調(diào)整保護(hù)配置。Univer 的命令服務(wù)CommandService是運(yùn)行時(shí)改變表格狀態(tài)的唯一正規(guī)入口。示例代碼如下import { ICommandService } from univerjs/core; import { SetWorksheetProtectionCommand } from univerjs/sheets; const commandService univer.getCommandService(); await commandService.executeCommand(SetWorksheetProtectionCommand.id, { unitId: training-signup, subUnitId: training-signup, protection: { sheet: true, lockCells: true, ranges: [ { range: { startRow: 2, endRow: 19, startColumn: 0, endColumn: 3 }, lock: false }, { range: { startRow: 2, endRow: 19, startColumn: 4, endColumn: 5 }, lock: true }, ], }, });這里有兩個(gè) idunitId是工作簿的 idsubUnitId是工作表的 id。在我這個(gè)例子里兩者都用了同一個(gè)字符串。如果你的業(yè)務(wù)里有兩張 sheet那 subUnitId 就分別指向?qū)?yīng) sheet。很多初學(xué)時(shí)困惑的“為什么執(zhí)行命令沒(méi)反應(yīng)”八成是這兩個(gè) id 沒(méi)對(duì)上。調(diào)試時(shí)可以先打印工作簿和工作表的 idconst workbook univer.getActiveWorkbook(); const worksheet workbook.getActiveSheet(); console.log(workbook.getId(), worksheet.getId());執(zhí)行完這個(gè)命令后表格的編輯權(quán)限會(huì)立即變化不需要刷新頁(yè)面。這種動(dòng)態(tài)控制很適合做“審批流”管理員編輯模板時(shí)保護(hù)未開啟審核通過(guò)后調(diào)用命令開啟保護(hù)業(yè)務(wù)用戶拿到鏈接就進(jìn)入了填寫模式。3.4 前端純攔截兜底方案保護(hù)機(jī)制原理上是攔截了 Univer 內(nèi)部的編輯命令但并不是所有交互都能被 protection 覆蓋。比如某些版本里你仍然可以通過(guò)填充柄向下拖拽一個(gè)鎖定區(qū)域的值或者復(fù)制鎖定區(qū)域粘貼到可編輯區(qū)域。遇到這種情況光靠 protection 不夠還得在前端事件層做兜底。Univer 提供命令執(zhí)行監(jiān)聽我們可以攔下不必要的操作univer.getCommandService().onCommandExecuted((command) { if (command.id sheet.command.set-range-values) { // 檢查 command.params 里的 range 是否落在可編輯區(qū)域內(nèi) // 如果不在保護(hù)范圍內(nèi)就攔截或者回滾 } });更簡(jiǎn)單的做法是在編輯器外層加一層業(yè)務(wù)校驗(yàn)?zāi)玫接脩籼峤粩?shù)據(jù)后在后端再次校驗(yàn)“提交的字段是否都位于允許范圍內(nèi)”。前端保護(hù)是為了體驗(yàn)后端校驗(yàn)才是底線。這個(gè)原則放在任何表格權(quán)限場(chǎng)景都適用。4. 進(jìn)階協(xié)同填寫與后端保存的完整方案4.1 多用戶同時(shí)填寫時(shí)保護(hù)區(qū)域怎么保證如果你只是把一張帶保護(hù)的工作表發(fā)給用戶每個(gè)用戶獨(dú)立打開、獨(dú)立填寫那權(quán)限控制很清晰。但現(xiàn)實(shí)里經(jīng)常出現(xiàn)幾十個(gè)人同時(shí)打開同一張表各自往自己那一行填數(shù)據(jù)。Univer 本身支持協(xié)同編輯底層可以用 WebSocket、Yjs 等同步但協(xié)同模式下保護(hù)邏輯的復(fù)雜度會(huì)上升。首要原則是不要把后端權(quán)限校驗(yàn)寄托在前端保護(hù)上。前端保護(hù)只是 UI 層面的限制協(xié)同服務(wù)收到操作指令后必須自己校驗(yàn)這個(gè) range 是否允許寫入。否則一個(gè)懂點(diǎn)前端的人可以繞過(guò)界面直接調(diào)協(xié)同同步接口把鎖定區(qū)域的數(shù)據(jù)改掉。具體操作層面我建議把“保護(hù)配置”提升為后端的一張配置表這張表里存了工作簿的 unitId、sheet 的 subUnitId、允許編輯的 ranges 列表。用戶發(fā)起編輯時(shí)協(xié)同服務(wù)先查配置表校驗(yàn)操作范圍再?zèng)Q定是否放行。如果業(yè)務(wù)里用到了 Univer 官方協(xié)同方案可以基于其命令廣播機(jī)制在命令進(jìn)入同步管道之前加一個(gè)鑒權(quán)中間層。4.2 把填寫結(jié)果持久化到后端填表業(yè)務(wù)最終要落庫(kù)。Univer 里讀取用戶填寫內(nèi)容有幾種做法用戶填完后前端統(tǒng)一從表格實(shí)例中取出整個(gè)數(shù)據(jù)區(qū)。監(jiān)聽單元格變更事件實(shí)時(shí)增量提交。第一種做法適合“填完點(diǎn)提交”的流程。代碼大概這樣const worksheet univer.getActiveWorkbook().getActiveSheet(); // 讀固定區(qū)域的數(shù)據(jù) const rangeData worksheet.getRange({ startRow: 2, endRow: 19, startColumn: 0, endColumn: 3, }); const rows rangeData.map(row ({ name: row.cells?.[0]?.v, department: row.cells?.[1]?.v, email: row.cells?.[2]?.v, accommodation: row.cells?.[3]?.v, }));然后把這組對(duì)象 POST 到后端接口。這里的重點(diǎn)是讀取數(shù)據(jù)時(shí)不要讀全表只讀你允許填寫的區(qū)域既減少不必要的傳輸也天然規(guī)避了越權(quán)數(shù)據(jù)被帶上來(lái)的風(fēng)險(xiǎn)。第二種做法適合表格長(zhǎng)期打開、自動(dòng)保存的場(chǎng)景。Univer 的命令服務(wù)有對(duì)應(yīng)事件univer.getCommandService().onCommandExecuted((command) { if (command.id sheet.command.set-cell-value) { // 把 command.params 里的 values 增量提交 } });增量提交要做防抖不然用戶連續(xù)輸入十幾個(gè)字符會(huì)打出十幾條請(qǐng)求。我習(xí)慣把變更先緩存到一個(gè) Map 里用 500ms 的定時(shí)器統(tǒng)一上報(bào)。4.3 配合表單校驗(yàn)與數(shù)據(jù)聯(lián)動(dòng)“能編輯”和“能填對(duì)”是兩回事。用戶雖然只能寫指定區(qū)域但寫出來(lái)的內(nèi)容可能格式完全不對(duì)。Univer 在填表場(chǎng)景下最好開啟數(shù)據(jù)校驗(yàn)?zāi)芰?。比如郵箱列可以在模板配置里給單元格加上校驗(yàn)規(guī)則或者使用公式做判斷。Univer 支持在初始化時(shí)給單元格指定 validator在填表業(yè)務(wù)中更實(shí)用的做法是監(jiān)聽值變更在 UI 上實(shí)時(shí)提醒。這里有一個(gè)經(jīng)驗(yàn)校驗(yàn)規(guī)則不要寫在保護(hù)配置里也不要散落在模板各處最好集中在一個(gè)數(shù)據(jù)字典結(jié)構(gòu)里帶進(jìn)模板這樣后端校驗(yàn)和前端提示共用同一份規(guī)則避免兩邊不一致。舉個(gè)例子郵箱列的校驗(yàn)規(guī)則可以定義為{ type: regex, pattern: ^[\\w.-][\\w-](\\.[\\w-])$, message: 郵箱格式不正確, }用戶填完不合法提交按鈕置灰只有全部合法才能提交。這提升了表格的可用性也讓后臺(tái)少收很多臟數(shù)據(jù)。5. 常見(jiàn)問(wèn)題與避坑指南5.1 保護(hù)開啟后鎖定區(qū)域仍能編輯這是我在社區(qū)里看到最多的問(wèn)題自己第一次也遇到。排查順序如下確認(rèn) protection 的sheet是否真的為 true。很多人只設(shè)置了lockCells沒(méi)有把sheet打開保護(hù)等于沒(méi)啟用。確認(rèn)lockCells是否為 true。如果 lockCells 為 falseranges 之外的區(qū)域默認(rèn)可編輯保護(hù)范圍的含義反過(guò)來(lái)了。確認(rèn) ranges 的lock字段??删庉媴^(qū)域要用lock: false顯式標(biāo)記。有些版本字段名是locked拿到的示例代碼里寫的是lock就照抄結(jié)果毫無(wú)反應(yīng)。確認(rèn)執(zhí)行的是重新設(shè)置整套 protection而不是增量 patch。Univer 命令執(zhí)行時(shí)通常會(huì)整體替換 protection 對(duì)象所以每次更新都要把完整的 ranges 放進(jìn)去。5.2 公式計(jì)算、填充柄繞過(guò)保護(hù)保護(hù)只能攔編輯命令攔不住用戶把可編輯區(qū)域的公式向下填充到鎖定區(qū)域。如果想徹底避免這種問(wèn)題有兩個(gè)思路在鎖定區(qū)域不上公式改由后端統(tǒng)一計(jì)算。在事件層攔截填充操作只允許在非鎖定區(qū)域范圍內(nèi)執(zhí)行。第二種思路實(shí)現(xiàn)起來(lái)要監(jiān)聽比較底層的命令比較麻煩。我的建議是填表場(chǎng)景能不用公式就不用公式。表格的計(jì)算能力讓管理員在后臺(tái)設(shè)計(jì)模板時(shí)用最終提交給用戶的填表視圖盡量只展示普通文本和數(shù)字把公式計(jì)算挪到保存后的結(jié)果頁(yè)。5.3 Excel 導(dǎo)入與保護(hù)兼容性問(wèn)題Univer 支持導(dǎo)入 xlsx但導(dǎo)入文件的保護(hù)配置不一定能完整還原。Excel 里“允許用戶編輯區(qū)域”是通過(guò)范圍安全性設(shè)置的Univer 從 xlsx 里解析時(shí)可能會(huì)丟失或者轉(zhuǎn)換偏差。如果業(yè)務(wù)要求管理員先上傳 Excel 模板再由系統(tǒng)啟用填表模式我的建議是上傳后不要依賴原文件的保護(hù)屬性而是按業(yè)務(wù)規(guī)則重新生成 protection ranges。怎么做呢上傳后先解析 Excel 的單元格結(jié)構(gòu)然后通過(guò)規(guī)則匹配出可編輯區(qū)域。比如約定“所有帶黃色背景的單元格為可編輯區(qū)域”解析時(shí)讀單元格背景色把這些坐標(biāo)轉(zhuǎn)成 ranges。這樣做的好處是模板設(shè)計(jì)者不需要懂 Univer API只要在 Excel 里涂色就行。5.4 大表格初始化性能填表模板一般不會(huì)太大但如果管理員從 Excel 導(dǎo)入了幾千行數(shù)據(jù)Univer 初始化時(shí)全量渲染會(huì)卡頓。優(yōu)化手段有幾個(gè)減少初始 cellData 里所有單元格都賦空對(duì)象的情況Univer 對(duì)稀疏數(shù)據(jù)渲染更友好。隱藏不必要的行列不要讓用戶看到空白區(qū)域。條件格式和校驗(yàn)規(guī)則不要鋪滿整張表只設(shè)置在真正的數(shù)據(jù)區(qū)域。如果可編輯區(qū)域很單調(diào)優(yōu)先用 range 規(guī)則代替逐格樣式盡量減少單元格級(jí)對(duì)象數(shù)量。6. 一套可直接使用的“可填寫表格”配置參考6.1 模板配置速查最后給一份完整的、可以改改就用的配置。我用“工時(shí)統(tǒng)計(jì)”舉例管理員每月發(fā)一張表給組員填寫組員只能填“項(xiàng)目名稱、工時(shí)、說(shuō)明”三列其他列鎖定。工作表規(guī)劃第 1 行合并標(biāo)題。第 2 行列名項(xiàng)目編號(hào)、項(xiàng)目名稱、工時(shí)、說(shuō)明。第 3 行到第 20 行填寫區(qū)。項(xiàng)目編號(hào)列鎖定內(nèi)容由系統(tǒng)寫入項(xiàng)目名稱、工時(shí)、說(shuō)明列可編輯。配置如下const timesheet { id: timesheet-2025-06, name: 6月工時(shí), rowCount: 20, columnCount: 4, cellData: { 0: { 0: { v: 2025年6月工時(shí)登記表, s: { bl: 1, bg: #f2f2f2, locked: true } }, 1: { v: , s: { locked: true } }, }, 1: { 0: { v: 項(xiàng)目編號(hào), s: { locked: true, bg: #e8e8e8 } }, 1: { v: 項(xiàng)目名稱, s: { locked: true, bg: #e8e8e8 } }, 2: { v: 工時(shí), s: { locked: true, bg: #e8e8e8 } }, 3: { v: 說(shuō)明, s: { locked: true, bg: #e8e8e8 } }, }, }, protection: { sheet: true, lockCells: true, ranges: [ { range: { startRow: 2, endRow: 19, startColumn: 1, endColumn: 3 }, lock: false, }, ], }, };用戶打開后項(xiàng)目編號(hào)列由系統(tǒng)預(yù)先填好用戶只能填項(xiàng)目名稱、工時(shí)、說(shuō)明三列。這樣收集上來(lái)的數(shù)據(jù)非常規(guī)整后端解析也方便。6.2 事件聯(lián)動(dòng)填寫完成后的處理一個(gè)比較好用的小技巧是監(jiān)聽單元格變更之后把變更單元格標(biāo)成其他背景色這樣用戶一眼就能看出自己填了哪些格子管理員也能快速判斷哪些數(shù)據(jù)是新增的。核心代碼就幾行univer.getCommandService().onCommandExecuted((command) { if (command.id sheet.command.set-cell-value) { const { unitId, subUnitId, values } command.params; // 遍歷 values把對(duì)應(yīng)單元格背景色置為淺綠或淺黃 } });要提醒一句這個(gè)監(jiān)聽事件非常頻繁做樣式更新時(shí)最好合并批處理避免每輸入一個(gè)字符就重繪一次。我一般把待更新格子攢到一個(gè)數(shù)組里等事件循環(huán)空閑時(shí)統(tǒng)一應(yīng)用。這個(gè)“可填寫區(qū)域控制”的功能真正的關(guān)鍵不在 API 調(diào)用而是把權(quán)限模型想清楚。前端保護(hù)做得再好也只是給用戶一個(gè)順暢的操作邊界后端必須持有同一份規(guī)則做最終校驗(yàn)。我在實(shí)際項(xiàng)目里被坑最慘的一次就是前端保護(hù)全做好了協(xié)同接口漏了校驗(yàn)結(jié)果用戶直接繞過(guò)界面改掉了鎖定的公式列。后來(lái)我把 ranges 配置抽成公共模塊前后端共用問(wèn)題才徹底消失。你動(dòng)手做的時(shí)候建議第一步就先設(shè)計(jì)好這份公共配置再碰 Univer 的代碼后面會(huì)省掉非常多的返工。