diff --git a/assets/architecture.svg b/assets/architecture.svg index 4b4afee..ac7ad0c 100644 --- a/assets/architecture.svg +++ b/assets/architecture.svg @@ -83,7 +83,7 @@ 供策略 / 回测 / 监控取用 页面扩展 - 11 官方页 + 页面包 + 11 官方页 + 页面插件 custom 后端 AI 助手参考实现 diff --git a/backend/app/api/ext_pages.py b/backend/app/api/ext_pages.py index c01c218..47f2b0c 100644 --- a/backend/app/api/ext_pages.py +++ b/backend/app/api/ext_pages.py @@ -1,4 +1,4 @@ -"""运行时页面包 API — 上传/清单回填/启停/删除/bundle 分发。 +"""运行时页面插件 API — 上传/清单回填/启停/删除/bundle 分发。 分发端点位于 /api 前缀下, 自动受集中鉴权中间件保护: 浏览器同源动态 import 会携带登录 cookie, 与页面会话同一访问边界。 @@ -33,7 +33,7 @@ async def _write_bundle_capped(file: UploadFile, dest: Path, max_bytes: int) -> break total += len(chunk) if total > max_bytes: - raise HTTPException(413, f"页面包过大(上限 {max_bytes // (1024 * 1024)}MB)") + raise HTTPException(413, f"页面插件过大(上限 {max_bytes // (1024 * 1024)}MB)") f.write(chunk) @@ -57,17 +57,17 @@ async def upload_page_pack( id: Annotated[str, Form()], file: Annotated[UploadFile, File()], ) -> PagePackMeta: - """上传页面包产物(.js/.mjs), 覆盖式: 同 ID 重新上传即更新。""" + """上传页面插件产物(.js/.mjs), 覆盖式: 同 ID 重新上传即更新。""" suffix = Path(file.filename or "").suffix.lower() if suffix not in (".js", ".mjs"): - raise HTTPException(400, "仅支持 .js / .mjs 页面包产物") + raise HTTPException(400, "仅支持 .js / .mjs 页面插件产物") store = _store(request) tmp_dir = Path(tempfile.mkdtemp()) try: tmp_path = tmp_dir / "bundle.js" await _write_bundle_capped(file, tmp_path, _MAX_BUNDLE_BYTES) if tmp_path.stat().st_size == 0: - raise HTTPException(400, "页面包为空文件") + raise HTTPException(400, "页面插件为空文件") try: return store.save_upload(id, tmp_path) except ValueError as e: @@ -91,7 +91,7 @@ def get_page_pack_bundle(pack_id: str, request: Request) -> FileResponse: raise HTTPException(400, str(e)) from e bundle = store.bundle_path(pack_id) if meta else None if meta is None or bundle is None: - raise HTTPException(404, f"页面包 '{pack_id}' 不存在") + raise HTTPException(404, f"页面插件 '{pack_id}' 不存在") return FileResponse( bundle, media_type="text/javascript", @@ -109,7 +109,7 @@ def put_page_pack_manifest(pack_id: str, body: ManifestBody, request: Request) - try: return store.update_manifest(pack_id, label=body.label, path=body.path, error=body.error) except KeyError: - raise HTTPException(404, f"页面包 '{pack_id}' 不存在") from None + raise HTTPException(404, f"页面插件 '{pack_id}' 不存在") from None except ValueError as e: raise HTTPException(400, str(e)) from e @@ -120,7 +120,7 @@ def put_page_pack_enabled(pack_id: str, body: EnabledBody, request: Request) -> try: return store.set_enabled(pack_id, body.enabled) except KeyError: - raise HTTPException(404, f"页面包 '{pack_id}' 不存在") from None + raise HTTPException(404, f"页面插件 '{pack_id}' 不存在") from None except ValueError as e: raise HTTPException(400, str(e)) from e @@ -130,7 +130,7 @@ def delete_page_pack(pack_id: str, request: Request) -> dict: store = _store(request) try: if not store.delete(pack_id): - raise HTTPException(404, f"页面包 '{pack_id}' 不存在") + raise HTTPException(404, f"页面插件 '{pack_id}' 不存在") except ValueError as e: raise HTTPException(400, str(e)) from e return {"ok": True} diff --git a/backend/app/services/ext_pages.py b/backend/app/services/ext_pages.py index 8c1ecb9..7dd67a1 100644 --- a/backend/app/services/ext_pages.py +++ b/backend/app/services/ext_pages.py @@ -1,6 +1,6 @@ -"""运行时页面包存储 — data/ext_pages/{id}/ 目录式持久化。 +"""运行时页面插件存储 — data/ext_pages/{id}/ 目录式持久化。 -页面包是前端扩展的打包产物(单文件 ES 模块): 上传后由前端在启动引导窗口 +页面插件是前端扩展的打包产物(单文件 ES 模块): 上传后由前端在启动引导窗口 动态 import 挂载(见 frontend/src/extensions/runtime.ts)。后端只负责存储、 元数据与分发, 不解析 JS 内容 —— 清单契约(能否 import、apiVersion、路由 冲突)由前端注册表校验, 校验结果经 manifest 接口回填到 page.json。 @@ -41,7 +41,7 @@ def _sha256_file(path: Path) -> str: class PagePackStore: - """每个页面包一个目录: page.json(元数据) + bundle.js(产物)。 + """每个页面插件一个目录: page.json(元数据) + bundle.js(产物)。 元数据写入走 临时文件+rename, 与 ext_data 的原子写同语义, 避免半写 状态被启动扫描读到。 @@ -53,7 +53,7 @@ class PagePackStore: def _dir(self, pack_id: str) -> Path: if not _VALID_ID.match(pack_id or ""): - raise ValueError("页面包 ID 仅允许字母/数字/下划线, 长度 1-64") + raise ValueError("页面插件 ID 仅允许字母/数字/下划线, 长度 1-64") return self.data_dir / pack_id def _write_meta(self, meta: PagePackMeta) -> None: @@ -86,7 +86,7 @@ class PagePackStore: try: return PagePackMeta(**json.loads(p.read_text(encoding="utf-8"))) except (json.JSONDecodeError, TypeError): - # 半写/损坏的元数据按损坏页处理, 不阻断其他页面包 + # 半写/损坏的元数据按损坏页处理, 不阻断其他页面插件 return PagePackMeta(id=pack_id, error="page.json 损坏, 请重新上传") def bundle_path(self, pack_id: str) -> Path | None: diff --git a/backend/tests/test_ext_pages.py b/backend/tests/test_ext_pages.py index 6e0350b..be4f34a 100644 --- a/backend/tests/test_ext_pages.py +++ b/backend/tests/test_ext_pages.py @@ -1,4 +1,4 @@ -"""运行时页面包 API 测试 — 上传校验/生命周期/bundle 分发。 +"""运行时页面插件 API 测试 — 上传校验/生命周期/bundle 分发。 TestClient 最小 app 风格(同 test_ext_market_level): 不起完整后端, app.state.repo 只提供 store.data_dir。 diff --git a/docs/page-sdk.md b/docs/page-sdk.md index 66e494d..3ece4f3 100644 --- a/docs/page-sdk.md +++ b/docs/page-sdk.md @@ -1,6 +1,6 @@ -# 页面包开发指南 (Page Pack SDK v1) +# 页面插件开发指南 (Page Pack SDK v1) -页面包是**运行时可挂载的页面产物**:把页面打包成单文件 ES 模块(`extension.js`),在「设置 → 页面包」上传,刷新后即成为完整页面——自动注册路由、侧栏入口,可在菜单设置里排序/隐藏。本地 dev、生产构建、桌面端三种运行形态一致生效,宿主无需重新构建。 +页面插件是**运行时可挂载的页面产物**:把页面打包成单文件 ES 模块(`extension.js`),在「设置 → 页面插件」上传,刷新后即成为完整页面——自动注册路由、侧栏入口,可在菜单设置里排序/隐藏。本地 dev、生产构建、桌面端三种运行形态一致生效,宿主无需重新构建。 ## 快速开始 @@ -9,7 +9,7 @@ cp -r frontend/page-pack-template my-pack && cd my-pack pnpm install && pnpm build # → dist/extension.js ``` -打开宿主「设置 → 页面包」→ 填 ID(字母/数字/下划线)→ 选择 `dist/extension.js` → 上传并校验 → 刷新页面。 +打开宿主「设置 → 页面插件」→ 填 ID(字母/数字/下划线)→ 选择 `dist/extension.js` → 上传并校验 → 刷新页面。 ## 产物契约 (apiVersion 1) @@ -29,7 +29,7 @@ const extension = { }, }], navigation: [{ - id: 'hello', routeId: 'hello', label: '页面包演示', icon: 'Sparkles', + id: 'hello', routeId: 'hello', label: '页面插件演示', icon: 'Sparkles', }], } export default extension @@ -39,7 +39,7 @@ export default extension ## 能力桥 ctx -页面包**自包含**(React 等依赖由模板打进产物,与宿主不共享模块实例),宿主能力经 `ctx` 暴露: +页面插件**自包含**(React 等依赖由模板打进产物,与宿主不共享模块实例),宿主能力经 `ctx` 暴露: | 能力 | 说明 | | --- | --- | @@ -60,13 +60,13 @@ export default extension - **路由集成**:页面挂在宿主路由容器内(`/你的路径` 可直达、可刷新),但包内不共享宿主 Router 上下文——需要跳转宿主页面时用 `window.location` 或自行包 `MemoryRouter`。 - **生效方式**:上传/启停后**刷新页面生效**(挂载发生在启动引导窗口),不做热插拔。 - **大小**:上限 5MB/包;自包含 React 后典型产物 60–150KB(minify + 可自行加 brotli/gzip 由后端协商)。 -- **安全**:页面包是任意代码,与登录会话同权运行——只上传自己或可信来源的产物;这是单用户产品的既定边界,第三方插件沙箱不在 v1 范围。 +- **安全**:页面插件是任意代码,与登录会话同权运行——只上传自己或可信来源的产物;这是单用户产品的既定边界,第三方插件沙箱不在 v1 范围。 - **版本**:宿主升级破坏契约时提升 `apiVersion` 并保留旧版兼容期;产物内 `apiVersion` 不匹配会被明确拒绝。 ## 与其他扩展方式的取舍 | 方式 | 适用 | 见 | | --- | --- | --- | -| 页面包(运行时产物) | 免重建挂载、独立分发 | 本文档 | +| 页面插件(运行时产物) | 免重建挂载、独立分发 | 本文档 | | 源码级扩展 `frontend/src/custom//extension.tsx` | 随宿主一起构建维护、可用全部核心模块 | `docs/secondary-development.md` §3 | | 声明式分析页面(零代码) | 扩展数据 + 模板化列表/分组页 | 设置 → 扩展页面 | diff --git a/frontend/page-pack-template/README.md b/frontend/page-pack-template/README.md index d5cfa72..db5b41b 100644 --- a/frontend/page-pack-template/README.md +++ b/frontend/page-pack-template/README.md @@ -1,6 +1,6 @@ -# 页面包模板 (Page Pack Template) +# 页面插件模板 (Page Pack Template) -把一个页面打包成**单文件 ES 模块产物**(`dist/extension.js`),上传到运行中的面板(设置 → 页面包),刷新后即成为完整页面——自动注册路由与侧栏入口,可在菜单设置里排序/隐藏。本地 dev、Docker、桌面端一致生效,**宿主无需重新构建**。 +把一个页面打包成**单文件 ES 模块产物**(`dist/extension.js`),上传到运行中的面板(设置 → 页面插件),刷新后即成为完整页面——自动注册路由与侧栏入口,可在菜单设置里排序/隐藏。本地 dev、Docker、桌面端一致生效,**宿主无需重新构建**。 ## 打包 @@ -13,7 +13,7 @@ pnpm build # → dist/extension.js ## 上传 -面板 **设置 → 页面包**:填 ID(字母/数字/下划线)→ 选择 `dist/extension.js` → 上传并校验(即时试载,清单不合法或路由冲突会被拒绝并显示原因)→ 启用 → 刷新页面。 +面板 **设置 → 页面插件**:填 ID(字母/数字/下划线)→ 选择 `dist/extension.js` → 上传并校验(即时试载,清单不合法或路由冲突会被拒绝并显示原因)→ 启用 → 刷新页面。 ## 开发要点 diff --git a/frontend/page-pack-template/src/extension.tsx b/frontend/page-pack-template/src/extension.tsx index 2cb67f0..c0b1065 100644 --- a/frontend/page-pack-template/src/extension.tsx +++ b/frontend/page-pack-template/src/extension.tsx @@ -1,5 +1,5 @@ /** - * 页面包示例 — 复制本模板改造你的页面。 + * 页面插件示例 — 复制本模板改造你的页面。 * * 产物契约(v1): * - default 导出清单对象; apiVersion 固定 1 @@ -51,9 +51,9 @@ function HelloPage() { return ( - 页面包演示页 + 页面插件演示页 - 这是一个自包含的运行时页面: 打包成单文件 ES 模块后上传到「设置 → 页面包」, + 这是一个自包含的运行时页面: 打包成单文件 ES 模块后上传到「设置 → 页面插件」, 刷新即出现在侧栏。{serverTime} setCount(n => n + 1)}> @@ -79,7 +79,7 @@ const extension = { }, ], navigation: [ - { id: 'hello', routeId: 'hello', label: '页面包演示', icon: 'Sparkles' }, + { id: 'hello', routeId: 'hello', label: '页面插件演示', icon: 'Sparkles' }, ], } diff --git a/frontend/page-pack-template/src/tsp.ts b/frontend/page-pack-template/src/tsp.ts index d9ba890..b001e62 100644 --- a/frontend/page-pack-template/src/tsp.ts +++ b/frontend/page-pack-template/src/tsp.ts @@ -1,11 +1,11 @@ /** * tsp.ts — 宿主能力桥读取。 * - * window.__TSP_EXT__ 由宿主 main.tsx 在引导时安装; 页面包经 mount(el, ctx) + * window.__TSP_EXT__ 由宿主 main.tsx 在引导时安装; 页面插件经 mount(el, ctx) * 收到的 ctx 即本对象。api 为宿主完整 API 封装(同源 fetch, 自带登录态), * QK 为集中查询键, cn 为样式合并工具, format 为格式化工具集。 * - * 自包含说明: 页面包产物自带 React/ReactDOM (由本模板的 vite 构建打入), + * 自包含说明: 页面插件产物自带 React/ReactDOM (由本模板的 vite 构建打入), * 与宿主不共享模块实例 —— 请勿 import 宿主内部模块, 只经由 ctx 取能力。 */ type Bridge = { diff --git a/frontend/page-pack-template/vite.config.ts b/frontend/page-pack-template/vite.config.ts index 949c907..11f89ad 100644 --- a/frontend/page-pack-template/vite.config.ts +++ b/frontend/page-pack-template/vite.config.ts @@ -1,8 +1,8 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -// 页面包构建: 单文件自包含 ES 模块 (react 等全部打入), 产物 dist/extension.js -// 直接上传到宿主「设置 → 页面包」。不做代码分割 — 宿主按整个文件加载挂载。 +// 页面插件构建: 单文件自包含 ES 模块 (react 等全部打入), 产物 dist/extension.js +// 直接上传到宿主「设置 → 页面插件」。不做代码分割 — 宿主按整个文件加载挂载。 export default defineConfig({ plugins: [react()], // lib 模式不会自动替换依赖里的 process.env.NODE_ENV, 浏览器无 process 全局 diff --git a/frontend/src/custom/README.md b/frontend/src/custom/README.md index ccb6e4f..25065b5 100644 --- a/frontend/src/custom/README.md +++ b/frontend/src/custom/README.md @@ -34,6 +34,6 @@ frontend/src/custom//extension.tsx 以 [`_template/extension.tsx.example`](_template/extension.tsx.example) 为起点(模板文件不参与构建),并遵循 [`docs/secondary-development.md`](../../../docs/secondary-development.md)。 -## 不想重建宿主?用页面包 +## 不想重建宿主?用页面插件 -若目标是把页面打包成产物、在运行中的面板上直接上传使用(本地 / Docker / 桌面端一致),走**页面包**:用 [`frontend/page-pack-template`](../../page-pack-template) 打包单文件 ES 模块,在 设置 → 页面包 上传,刷新生效。契约与边界见 [`docs/page-sdk.md`](../../../docs/page-sdk.md)。 +若目标是把页面打包成产物、在运行中的面板上直接上传使用(本地 / Docker / 桌面端一致),走**页面插件**:用 [`frontend/page-pack-template`](../../page-pack-template) 打包单文件 ES 模块,在 设置 → 页面插件 上传,刷新生效。契约与边界见 [`docs/page-sdk.md`](../../../docs/page-sdk.md)。 diff --git a/frontend/src/extensions/runtime.test.ts b/frontend/src/extensions/runtime.test.ts index 857b2c5..5ca706f 100644 --- a/frontend/src/extensions/runtime.test.ts +++ b/frontend/src/extensions/runtime.test.ts @@ -42,7 +42,7 @@ describe('packToExtension: 契约校验与转换', () => { }) describe('loadRuntimePageExtensions', () => { - it('只加载已启用页面包, loader 键带 runtime: 前缀且完成契约转换', async () => { + it('只加载已启用页面插件, loader 键带 runtime: 前缀且完成契约转换', async () => { const rec = recorder() await loadRuntimePageExtensions({ fetchList: async () => [ diff --git a/frontend/src/extensions/runtime.tsx b/frontend/src/extensions/runtime.tsx index ee67e73..bd81a32 100644 --- a/frontend/src/extensions/runtime.tsx +++ b/frontend/src/extensions/runtime.tsx @@ -1,20 +1,20 @@ /** - * 运行时页面包加载 — 从后端拉取已启用清单, 在启动引导窗口动态 import。 + * 运行时页面插件加载 — 从后端拉取已启用清单, 在启动引导窗口动态 import。 * * 时机关键: 必须在 initializeFrontendExtensions() 之后、import('./router') * 之前调用(main.tsx 的 bootstrap)。router 模块求值时会 finalize 注册表 - * 并以冻结快照建路由表, 错过窗口的页面包只能等下次刷新生效。 + * 并以冻结快照建路由表, 错过窗口的页面插件只能等下次刷新生效。 * - * fail-open: 后端不可达 / 未登录(401) / 超时一律静默跳过 —— 页面包是 + * fail-open: 后端不可达 / 未登录(401) / 超时一律静默跳过 —— 页面插件是 * 增强能力, 不得拖慢或阻断宿主启动; 单个包 import 失败由注册表记入 * loadErrors, 不影响其他包与静态扩展。 * - * 契约(v1 自包含包): 页面包自带 React/路由(由 SDK 模板封装打包), 宿主不 + * 契约(v1 自包含包): 页面插件自带 React/路由(由 SDK 模板封装打包), 宿主不 * 与其共享模块实例 —— 页面通过 mount(el, ctx) 挂到宿主提供的容器里, * ctx 暴露宿主 api/QK/cn/format 能力桥。这与源码级扩展的 component 契约 * 不同: 此处由 packToExtension 把 mount 包装成宿主 React 组件再交给注册 * 表, 因此路由注册、冲突检测、错误隔离全部复用源码扩展的既有机制。 - * 后续若升级为 import map 共享实例, 只需改 SDK 与本文件, 已发布页面包 + * 后续若升级为 import map 共享实例, 只需改 SDK 与本文件, 已发布页面插件 * 源码不受影响。 */ import { useEffect, useRef, type ComponentType } from 'react' @@ -46,7 +46,7 @@ export interface PagePackNavigation { icon?: string } -/** 页面包产物 default 导出契约 — apiVersion 与源码扩展同源治理 */ +/** 页面插件产物 default 导出契约 — apiVersion 与源码扩展同源治理 */ export interface PagePackExtension { id: string apiVersion: 1 @@ -94,12 +94,12 @@ function createMountComponent(mount: PagePackRoute['mount']): ComponentType { } /** - * 页面包清单 → 注册表扩展。形态问题(缺字段/版本不符/路径非法)在此抛出, + * 页面插件清单 → 注册表扩展。形态问题(缺字段/版本不符/路径非法)在此抛出, * 经 loader 传给 loadFrontendExtensions 记入 loadErrors —— 与源码扩展 * 的失败隔离同一出口。 */ export function packToExtension(pack: unknown): FrontendExtension { - if (!pack || typeof pack !== 'object') throw new Error('页面包必须 default 导出清单对象') + if (!pack || typeof pack !== 'object') throw new Error('页面插件必须 default 导出清单对象') const p = pack as Partial if (p.apiVersion !== 1) throw new Error(`apiVersion 必须为 1, 收到 ${String(p.apiVersion)}`) if (typeof p.id !== 'string' || !p.id.includes('.')) throw new Error('id 必须是带命名空间的字符串 (如 demo.hello)') @@ -179,7 +179,7 @@ export async function loadRuntimePageExtensions(opts: LoadRuntimeOptions = {}): } /** - * 宿主应用层能力桥: 页面包经 ctx 读取宿主 api/QK/cn/format。 + * 宿主应用层能力桥: 页面插件经 ctx 读取宿主 api/QK/cn/format。 * 安装在 main.tsx 引导早期, 生命周期与页面一致。 */ export function installTspExtBridge(bridge: PagePackCtx): void { diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7a39458..cb51fcf 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -63,9 +63,9 @@ const queryClient = new QueryClient({ async function bootstrap() { await initializeFrontendExtensions() - // 页面包能力桥: 运行时页面包经 shim 读取宿主 api/QK/cn/format + // 页面插件能力桥: 运行时页面插件经 shim 读取宿主 api/QK/cn/format installTspExtBridge({ api, QK, cn, format }) - // 运行时页面包: 必须在 router 模块求值(finalize + 建路由表)之前挂载 + // 运行时页面插件: 必须在 router 模块求值(finalize + 建路由表)之前挂载 await loadRuntimePageExtensions() const { router } = await import('./router') ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 088b1fc..2e57ba7 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -36,7 +36,7 @@ const TABS: readonly TabDef[] = [ { key: 'ai', label: 'AI 设置', icon: Sparkles, panel: SettingsAIPanel }, { key: 'monitoring', label: '实时监控', icon: Radio, panel: SettingsMonitoringPanel }, { key: 'ext-pages', label: '扩展页面', icon: BarChart3, panel: SettingsExtPagesPanel }, - { key: 'page-packs', label: '页面包', icon: Package, panel: SettingsPagePacksPanel }, + { key: 'page-packs', label: '页面插件', icon: Package, panel: SettingsPagePacksPanel }, { key: 'timeout', label: '网络设置', icon: Clock3, panel: SettingsTimeoutPanel }, { key: 'menus', label: '菜单设置', icon: SlidersHorizontal, panel: SettingsMenuSettingsPanel }, { key: 'api-tokens', label: '开放接口', icon: KeyRound, panel: SettingsApiTokensPanel }, diff --git a/frontend/src/pages/settings/PagePacks.tsx b/frontend/src/pages/settings/PagePacks.tsx index 159d5df..f918f21 100644 --- a/frontend/src/pages/settings/PagePacks.tsx +++ b/frontend/src/pages/settings/PagePacks.tsx @@ -1,5 +1,5 @@ /** - * 设置 → 页面包: 运行时扩展页面的上传/试载/启停/删除。 + * 设置 → 页面插件: 运行时扩展页面的上传/试载/启停/删除。 * * 生命周期: 上传(后端存 pending) → 前端立即动态 import 试载并做契约校验 * (packToExtension) → 校验结果回填 manifest(成功即启用) → 刷新页面后在 @@ -47,7 +47,7 @@ export function SettingsPagePacksPanel() { async function handleUpload() { const file = fileRef.current?.files?.[0] const id = idInput.trim() - if (!file) { toast('请选择页面包产物 (.js / .mjs)', 'error'); return } + if (!file) { toast('请选择页面插件产物 (.js / .mjs)', 'error'); return } if (!/^[a-zA-Z0-9_]{1,64}$/.test(id)) { toast('ID 仅允许字母/数字/下划线 (1-64 位)', 'error'); return } setBusy(true) try { @@ -64,8 +64,8 @@ export function SettingsPagePacksPanel() { } await api.extPagesPutManifest(id, { label, path, error }) await invalidate() - if (error) toast(`页面包校验失败: ${error}`, 'error') - else { toast('页面包已启用, 刷新页面后生效', 'success'); setIdInput(''); if (fileRef.current) fileRef.current.value = '' } + if (error) toast(`页面插件校验失败: ${error}`, 'error') + else { toast('页面插件已启用, 刷新页面后生效', 'success'); setIdInput(''); if (fileRef.current) fileRef.current.value = '' } } finally { setBusy(false) } @@ -77,7 +77,7 @@ export function SettingsPagePacksPanel() { return ( - 页面包 + 页面插件 上传打包页面产物, 免重建挂载 用 frontend/page-pack-template 打包页面 @@ -89,7 +89,7 @@ export function SettingsPagePacksPanel() { setIdInput(e.target.value)} - placeholder="页面包 ID (字母/数字/下划线)" + placeholder="页面插件 ID (字母/数字/下划线)" className="h-9 w-56 rounded-btn border border-border bg-elevated px-3 text-sm text-foreground placeholder:text-muted/60 focus:border-accent/50 focus:outline-none" /> 0 && ( - 本次启动有页面包挂载失败 + 本次启动有页面插件挂载失败 {bootErrors.map((e, i) => ( {e.source}: {e.error} @@ -135,7 +135,7 @@ export function SettingsPagePacksPanel() { 页面 / ID路径大小 / 时间启用操作 {packs.length === 0 && ( - 暂无页面包,用上方表单上传第一个。 + 暂无页面插件,用上方表单上传第一个。 )} {packs.map(p => ( { if (confirm(`删除页面包 "${p.label || p.id}"?`)) deleteMut.mutate(p.id) }} + onClick={() => { if (confirm(`删除页面插件 "${p.label || p.id}"?`)) deleteMut.mutate(p.id) }} className="rounded p-1.5 text-muted hover:bg-danger/10 hover:text-danger transition-colors" title="删除" >
- 这是一个自包含的运行时页面: 打包成单文件 ES 模块后上传到「设置 → 页面包」, + 这是一个自包含的运行时页面: 打包成单文件 ES 模块后上传到「设置 → 页面插件」, 刷新即出现在侧栏。{serverTime}
用 frontend/page-pack-template 打包页面 @@ -89,7 +89,7 @@ export function SettingsPagePacksPanel() { setIdInput(e.target.value)} - placeholder="页面包 ID (字母/数字/下划线)" + placeholder="页面插件 ID (字母/数字/下划线)" className="h-9 w-56 rounded-btn border border-border bg-elevated px-3 text-sm text-foreground placeholder:text-muted/60 focus:border-accent/50 focus:outline-none" /> 0 && ( - 本次启动有页面包挂载失败 + 本次启动有页面插件挂载失败 {bootErrors.map((e, i) => ( {e.source}: {e.error} @@ -135,7 +135,7 @@ export function SettingsPagePacksPanel() { 页面 / ID路径大小 / 时间启用操作