唯杰地图 logo唯杰地图
VJMAP
EN

初始化坑 — 黑屏 / Engine 未就绪 / token 失败

vjcad/references/trouble/init-pitfalls.md 9.1 KB217 行
原文

文档:vjcad/doc/guide/quick-start.md、vjcad/doc/tutorial/index.md 示例:vjcad/example/basic/01init.js、02onload.js

1. 现象速查

现象 可能根因 跳到
容器黑屏,没报错 DOM 容器未渲染 / 容器宽高为 0 §2
实体没进图且不报错 / Engine.editor 为 undefined 在 cadView.onLoad() 之前用了 Engine §3
控制台一直 "等待加载..." wasmServiceUrl 配置错 / 跨域 §4
HTTP 403(code: 7)/ 返回 {status: 403, errorCode} accessToken / auth_callback 配置错 §5
Cannot read properties of null (reading 'getContext') 容器 ID 不对 / 同一 ID 多次挂载 §2
一刷新就重置 状态保存到了 localStorage 之外 §6

2. 容器问题

// ❌ 错:DOM 还没渲染
const cadView = new MainView({ ... });
initCadContainer('map', cadView);  // 找不到 #map

// ✅ 对:等 DOM 准备好
window.addEventListener('DOMContentLoaded', () => {
    const cadView = new MainView({ ... });
    initCadContainer('map', cadView);
});

容器 必须 满足:

  1. getElementById('map') 不为 null
  2. CSS 上 width / height 不为 0(vjcad 不会自适应空容器)
  3. 不能同时挂多个 vjcad 到同一个 ID
#map { width: 100%; height: 100vh; }

如果是 SPA / 路由切换:

// React 例:用 useRef + useEffect 管理生命周期
const ref = useRef(null);
useEffect(() => {
    const cadView = new MainView({ /* ... */ });
    initCadContainer(ref.current.id, cadView);
    return () => { cadView.destroy(); };  // 切走时一定 destroy(Promise)
}, []);

3. Engine 未就绪

// ❌ 错:onLoad 之前 pcanvas 为 null,addEntities 静默丢弃(不报错)
const cadView = new MainView({ /* ... */ });
initCadContainer('map', cadView);
Engine.addEntities([line]);          // 实体凭空消失

// ✅ 对:等待 onLoad
await cadView.onLoad();
Engine.addEntities([line]);

Engine 是静态类,永远不为 null/undefined。MainView 实例化后到 onLoad 完成 之间:Engine.pcanvas 为 null、Engine.editor 为 undefined、Engine.currentDoc 是懒建的孤儿文档,Engine.addEntities 静默丢弃不报错。详见 setup/onload-lifecycle。

4. WASM 加载失败

vjcad 的部分能力(SVG 解析、文字 / 字体光栅化、瓦片生成)走 WASM。不传 wasmServiceUrl 时默认 <服务根>/_webcad/wasm(服务根 = serviceUrl 去掉 api/…;无 serviceUrl 时为 ./wasm)。加载的是两个文件 webcad_core.js + webcad_core.wasm,按 ${wasmServiceUrl}/webcad_core.wasm 拼接,所以 wasmServiceUrl 末尾不要带 /;自定义部署时:

new MainView({
    serviceUrl: 'https://api.your-domain.com/api/v1',
    wasmServiceUrl: 'https://cdn.your-domain.com/vjcad/wasm',   // 末尾不带 /
});

排查:

// 在控制台
fetch('https://cdn.your-domain.com/vjcad/wasm/webcad_core.wasm', { method: 'HEAD' })
    .then(r => console.log('wasm status:', r.status));
fetch('https://cdn.your-domain.com/vjcad/wasm/webcad_core.js', { method: 'HEAD' })
    .then(r => console.log('wasm js status:', r.status));

需要 200 + 正确的 MIME (application/wasm);webcad_core.js 也是用 fetch 拉取的,同样要允许跨域。Nginx 通常要加:

types { application/wasm wasm; }

跨域时还要:

add_header Access-Control-Allow-Origin *;

5. accessToken / auth_callback

表现 含义
HTTP 403 + {code: 7, msg: 'Token is invalid' / 'Token expired' / ..., data: {reload: true}} token 缺失 / 失效 / 过期(服务端没有 401)
响应 JSON {status: 403, error, errorCode} auth_callback 业务回调拒绝(需按 status / errorCode 字段判断)
HTTP 500 + {code: 8} 后端错误

服务端只认请求头 token(或 URL 参数 ?token=),不认 Authorization: Bearer,也没有 /auth/check 路由;vjcad 前端发请求时自动带 token 头。排查:

// 测试 token 是否可用:随便调一个需鉴权的接口,带 token 请求头
fetch(`${env.serviceUrl}/service/getWorkspace`, {
    headers: { token: env.accessToken }
}).then(r => r.json().then(j => console.log('HTTP', r.status, 'code', j.code, j.msg)));   // 失效:HTTP 403 + code 7

如果 token 失效(HTTP 403 + code: 7):

  • 后端是 auth_callback 模式 → token 必须由你的业务系统签发
  • 是临时 token → 一段时间后失效
  • 网址写错(多了 /api/v1 或者少了)

⚠️ MainView({ onAuthError }) 在 vjcad@1.0.16 中从不被调用(构造时只保存回调,库内没有任何调用点)。需要自行检查 API 返回的 errorCode / status,必要时手动转发给已配置的回调:

const cadView = new MainView({
    onAuthError: error => {
        // 错误字段是 errorCode(字符串,如 'session_expired' / 'no_permission'),不是 status
        console.log('认证错误:', error.errorCode, error.message);
        if (error.errorCode === 'session_expired') location.href = '/login';
    }
});

// 自己判断返回值后手动触发(库不会替你调)
function checkAuth(res, operation) {
    if (res && res.status === 403 && res.errorCode) {
        cadView.getOnAuthError()?.({ errorCode: res.errorCode, message: res.error ?? '', operation });
        return false;
    }
    return true;
}

6. 状态丢失 / 一刷新就重置

vjcad 大部分配置是 运行时,刷新后丢失。下面这些会被自动持久化:

  • Engine.THEME_MODE(主题)
  • 浮动工具栏位置
  • "设置"对话框里改过的系统变量(SettingsCacheService 管理的那批,如 PICKBOX、APERTURE、GRIPSIZE 等)

不会自动持久化的:

  • Engine.OSMODE Engine.ORTHOMODE Engine.GRIDMODE(setter 只同步状态栏按钮,不写 localStorage)

  • 当前打开的图(mapid / version)

  • 当前选中的实体

  • 自定义注册的命令 / 插件

要做"刷新后恢复":

// 启动时(Engine.service.openMap 只是后端 openmap 接口,不会把图加载进编辑器)
const lastMap = localStorage.getItem('cad-last-map');
if (lastMap) {
    const { mapid, version, branch } = JSON.parse(lastMap);
    const r = await new DrawingManagerService().openDrawing({ type: 'imports', mapid, version, branch });
    if (r.success) {
        const file = new File([r.webcadJson], `${mapid}_${version}`, { type: 'application/json' });
        await Engine.view.openDbDoc(file, r.webcadData);
    }
}
// 或直接用 URL 参数 ?vcad_mapid=…&vcad_version=…,onLoad 时自动打开

// 关闭 / 切走时
const origDestroy = cadView.destroy.bind(cadView);
cadView.destroy = async () => {
    localStorage.setItem('cad-last-map', JSON.stringify({ mapid, version, branch }));
    return origDestroy();
};

7. 多实例 / 多 MainView

vjcad 按单视图设计:Engine 是静态类,文档集合(Engine.docs / activeDocument)、撤销、命令表、事件管理器都是全局共享的静态状态。

  • Engine.bindViewCache(view) 只替换缓存的 view / pcanvas,不切换文档;MainView 初始化时(firstUpdated)自动绑定,所以多个实例时 Engine 指向最后完成初始化的那个,与焦点无关(cadView.Engine 也不存在)。
  • 某个实例 destroy() 会释放 Engine.docs 里的全部文档并清空 Engine 缓存,会连带搞坏其他实例。
  • 同页对比 / 预览请用 PreviewView 或 <iframe> 隔离;弹框反复打开 CAD 时一次只保留一个视图:
// 关闭时
await cadView.destroy();          // 卸载插件、释放全部文档与画布、清理 Engine 缓存、移出 DOM
cadView = null;
// 再次打开时重新 new MainView + initCadContainer + await onLoad()

MCP 参考:vjcad/example/basic/07destroyviewer.js。

  • CommandRegistry 是 全局 的;注册一次所有视图共用
  • CadEventManager.getInstance() 也是全局的;事件不区分实例

红旗

  • 永远 await cadView.onLoad() 后再用 Engine
  • 容器宽高为 0 时不会报错,只是黑屏,先开浏览器审查元素
  • WASM 资源 404 / MIME 错误 → 黑屏 + 控制台报 wasm streaming compile failed
  • 同一 DOM 节点不要 initCadContainer 两次;要换实例先 await cadView.destroy()
  • 不要在同一页面同时跑多个 MainView:文档/撤销/命令/事件全局共享,bindViewCache 不切文档,A 写、B 改会"串了"
  • token 失效(HTTP 403 + code: 7)时不要立即重定向到登录页 —— 先判断当前路由 / token 状态;onAuthError 不会被库自动调用

参考(官方文档 MCP)

  • 文档:vjcad/doc/guide/quick-start.md、vjcad/doc/tutorial/index.md
  • 示例:vjcad/example/basic/01init.js、02onload.js