# ELN 实验模板标注 完整的目标架构、Vue 3 + TypeScript + Ant Design Vue 4.x 技术栈迁移、Dev Server、HMR、缓存、发布和后续迭代方案见 [`docs/onlyoffice/eln-template-annotator-development.md`](../../../docs/onlyoffice/eln-template-annotator-development.md)。 该插件在 ONLYOFFICE Word Editor 中把实验模板位置标注为标准 DOCX 内容控件。插件编码为 `eln-template-annotator`,workspace 包名为 `onlyoffice-eln-template-annotator`,安装包名为 `eln-template-annotator.plugin`。当前版本为 `0.1.0`,插件 GUID 为 `asc.{2D6D6CB6-F6FC-45B7-8EC7-0CC1940F1A6F}`,最低支持 ONLYOFFICE Docs `9.4.0`。 ## 字段模型 插件支持五类字段: | 类型 | 内容控件与校验 | | -------- | ---------------------------------------------------------------------------------------------------------- | | 单行文本 | 行内内容控件;默认值不能包含换行,可包裹当前选区。 | | 下拉选择 | 组合框内容控件;至少两个选项,业务值必须唯一,默认项必须存在于选项中。SDK 中相关属性可能沿用“单选组”命名。 | | 复选框 | 复选框内容控件;默认状态是布尔值,不使用占位文本。 | | 日期 | 日期内容控件;格式固定为 `yyyy-MM-dd`,并校验真实日历日期。 | | 多行文本 | 块级内容控件;允许换行,会形成独立块,不保证行内排版。 | 字段编码、重复组编码和行 ID 只允许稳定的英文字母、数字、下划线和连字符组合;其他字符会规范化为下划线。普通字段 Tag 格式为 `eln.field.`;重复表格字段 Tag 格式为 `eln.repeat...`。插入前会拒绝已有的重复 Tag。 ## 运行边界 插件运行在 ONLYOFFICE 的独立 iframe 中,不依赖 JNPF Vue 应用、Pinia、路由、`#/` 别名或前端请求封装。`Asc.plugin.callCommand` 的命令回调必须自包含,只能使用 ONLYOFFICE 提供的 `Api`,以及通过 `Asc.scope` 序列化传入的数据,不能捕获回调外部变量、模块或闭包。 执行插入命令前后都要枚举内容控件。只有目标 Tag 的计数从零增加到一,操作才算成功;不能仅凭 `callCommand` 回调执行结束判定成功。刷新字段列表时也必须拒绝非数组响应。 ## 开发命令 在仓库根目录执行: ```bash pnpm --filter onlyoffice-eln-template-annotator dev pnpm --filter onlyoffice-eln-template-annotator typecheck pnpm --filter onlyoffice-eln-template-annotator test pnpm --filter onlyoffice-eln-template-annotator build pnpm --filter onlyoffice-eln-template-annotator package ``` `dev` 启动独立 Vite Dev Server,默认监听 `4173`,并在终端输出可直接用于编辑器 `pluginsData` 的完整 manifest URL。正式版本始终保持 `0.1.0`,`0.1.0-dev` 仅是开发 release 路径。`ONLYOFFICE_PLUGIN_*` 和 `ONLYOFFICE_DOCS_URL` 只是 Dev Server 环境变量,不会进入 iframe 或正式产物。 Vue 组件和 CSS 修改使用 Vite HMR;`src/main.ts`、`src/onlyoffice/` 或 `src/composables/useTemplateAnnotator.ts` 修改会让插件 iframe 完整刷新,避免 ONLYOFFICE 生命周期代码重复注册。 本地打包需要 `jq`、`zip` 和 `unzip`。`package` 会先构建正式 Vue 产物,再生成并校验同目录临时包,最后原子替换旧安装包;失败时保留已有安装包。插件版本仍保持 `0.1.0`。在默认的 `cx-onlyoffice` 容器中安装或覆盖插件: ```bash packages/onlyoffice-plugins/eln-template-annotator/scripts/install-docker.sh ``` 安装已有包时可以显式传入已存在的绝对路径,例如在仓库根目录执行: ```bash ONLYOFFICE_PLUGIN_PACKAGE="$PWD/packages/onlyoffice-plugins/eln-template-annotator/dist/eln-template-annotator.plugin" \ packages/onlyoffice-plugins/eln-template-annotator/scripts/install-docker.sh ``` 也可以把容器名作为第一个参数传入。安装脚本会在调用 Docker 前校验压缩包完整性、GUID、版本和入口,保留首次安装与覆盖安装两条路径,并在覆盖后清理旧的 `.gz` sidecar 和容器临时包。该脚本仅用于本地真实编辑器冒烟,不是生产部署方案。 ## 发布与安全 使用仓库外的显式输出根目录生成不可变 release: ```bash ONLYOFFICE_RELEASE_ROOT=/absolute/path/to/releases \ pnpm --filter onlyoffice-eln-template-annotator release ``` 脚本生成 `onlyoffice-plugins/0.1.0/eln-template-annotator/`。相同内容可幂等重复执行;已有目录内容不同时会拒绝覆盖。Nginx 缓存配置样例位于 `deploy/nginx/eln-template-annotator.conf.example`。 正式发布使用不可变 release 路径和带版本或哈希的资源地址。版本资源可以使用长期 `immutable` 缓存;发布新版本时创建新 release,不能覆盖固定目录来期待浏览器刷新缓存。后端签发的编辑器配置应准确指向对应 release 的 `pluginsData` URL。 后端插件配置片段由独立命令生成,不会在普通 build/package/release 时修改后端 YAML: ```bash ONLYOFFICE_PLUGIN_PUBLIC_BASE_URL=https://example.com/onlyoffice-plugins \ ONLYOFFICE_PLUGIN_API_BASE_URL=https://api.example.com \ ONLYOFFICE_PLUGIN_CONFIG_OUTPUT=/absolute/path/to/onlyoffice-plugin.yaml \ ONLYOFFICE_RELEASE_ROOT=/absolute/path/to/releases \ pnpm --filter onlyoffice-eln-template-annotator generate:backend-config ``` 生成文件需由部署流程合并到 `config/shared/jnpf-biz-common.yaml` 现有的 `onlyoffice:` 节点下。生成的 `plugins.definitions` 支持登记多个插件,场景通过 `plugin-codes` 引用。输出路径必须位于公网 release 根目录之外;正式 release 默认是 `0.1.0`,本地开发可显式设置 `ONLYOFFICE_PLUGIN_RELEASE=0.1.0-dev`。 插件访问业务 API 时只能使用绑定用户、文档、场景和插件的短期、限用途凭证。登录鉴权、业务权限、文档 key、回调 JWT、会话关联和保存权限都由后端校验;插件 UI 隐藏、内容控件锁定和前端参数不是权限边界。密钥不得进入插件、URL、源码或文档示例。 仓库禁止提交 `.plugin` 文件、`dist`、`.DS_Store`、密钥、客户 DOC/DOCX、保存后的文档、截图、PDF、授权字体和其他实验数据或验证证据。 ## 真实编辑器回归 每次升级 ONLYOFFICE 或发布插件版本时,至少检查: - 五类字段均可插入、保存、关闭并重新打开,Tag、Alias、类型和默认值保持不变。 - 普通字段与重复表格字段的 Tag 格式正确,重复 Tag 被拒绝,插入后计数规则生效。 - 单行选区包裹、下拉业务值、复选状态、日期格式和多行块级排版符合约束。 - 字段列表可刷新并定位内容控件,锁定字段拒绝不允许的写入。 - Word/WPS 打开和另存后字段标识和值仍可回读,缺失字体时评估分页与字形影响。 - 新 release 在全新编辑器会话中加载正确版本,旧 release 不被原地覆盖。