> **本文回答**：Vite / Vue 3 / Vue 2 / React / 纯 HTML 五种集成方式的起点样板。

## 一、Vite + Vanilla JS

```bash
npm create vite@latest my-app -- --template vanilla
cd my-app
npm i vjmap vjmapext
```

`src/main.js`：

```js
import "vjmap/dist/vjmap.min.css";
import vjmap from "vjmap";

async function main() {
  const svc = new vjmap.Service(SERVICE_URL, TOKEN);
  const res = await svc.openMap({ mapid: "sys_zp", mapopenway: vjmap.MapOpenWay.GeomRender });
  if (res.error) throw new Error(res.error);

  const prj = new vjmap.GeoProjection(res.bounds);
  const map = new vjmap.Map({
    container: "map",
    style: svc.vectorStyle(),
    center: prj.toLngLat(prj.getMapExtent().center()),
    zoom: 2,
    renderWorldCopies: false
  });
  map.attach(svc, prj);
  await map.onLoad();
}
main();
```

> `GeoProjection` 构造函数在运行时兼容 `res.bounds` 字符串（内部自动 `GeoBounds.fromString`），但 d.ts 只声明了 `GeoBounds`——**TS 工程**须显式 `new vjmap.GeoProjection(vjmap.GeoBounds.fromString(res.bounds))`。

## 二、Vue 3 + `<script setup>`

```vue
<script setup lang="ts">
import "vjmap/dist/vjmap.min.css";
import vjmap, { type Map } from "vjmap";
import { MapCadLayer } from "vjmapext";
import { onMounted, onUnmounted } from "vue";

let map: Map | null = null;

onMounted(async () => {
  const svc = new vjmap.Service(SERVICE_URL, TOKEN);
  const res = await svc.openMap({ mapid: "sys_zp", mapopenway: vjmap.MapOpenWay.GeomRender });
  if (res.error) throw new Error(String(res.error));

  const prj = new vjmap.GeoProjection(res.bounds);
  map = new vjmap.Map({
    container: "map-container",
    style: svc.rasterStyle(),
    center: prj.toLngLat(prj.getMapExtent().center()),
    zoom: 2,
    renderWorldCopies: false
  });
  map.attach(svc, prj);
  await map.onLoad();

  const cad = new MapCadLayer();
  map.addControl(cad);
  cad.createUI({ theme: "dark" });
});

onUnmounted(() => {
  map?.remove();
  map = null;
});
</script>

<template>
  <div id="map-container" />
</template>

<style scoped>
#map-container { position: absolute; inset: 0; background: #022b4f; }
</style>
```

## 三、Vue 2

```vue
<template>
  <div id="map-container" />
</template>

<script>
import "vjmap/dist/vjmap.min.css";
import vjmap from "vjmap";
import { MapCadLayer } from "vjmapext";

export default {
  data() { return { map: null }; },
  async mounted() {
    const svc = new vjmap.Service(this.$env.serviceUrl, this.$env.token);
    const res = await svc.openMap({
      mapid: "sys_zp",
      mapopenway: vjmap.MapOpenWay.GeomRender
    });
    if (res.error) throw new Error(res.error);
    const prj = new vjmap.GeoProjection(res.bounds);
    this.map = new vjmap.Map({
      container: "map-container",
      style: svc.rasterStyle(),
      center: prj.toLngLat(prj.getMapExtent().center()),
      zoom: 2
    });
    this.map.attach(svc, prj);
    await this.map.onLoad();
    const cad = new MapCadLayer();
    this.map.addControl(cad);
  },
  beforeDestroy() {
    this.map?.remove();
    this.map = null;
  }
};
</script>
```

## 四、React（Hooks）

```jsx
import "vjmap/dist/vjmap.min.css";
import vjmap from "vjmap";
import { MapCadLayer } from "vjmapext";
import { useEffect, useRef } from "react";

export default function MapView() {
  const ref = useRef(null);
  useEffect(() => {
    let map;
    let disposed = false;
    (async () => {
      const svc = new vjmap.Service(process.env.REACT_APP_SERVICE, process.env.REACT_APP_TOKEN);
      const res = await svc.openMap({ mapid: "sys_zp", mapopenway: vjmap.MapOpenWay.GeomRender });
      if (res.error || disposed) return;
      const prj = new vjmap.GeoProjection(res.bounds);
      map = new vjmap.Map({
        container: ref.current,
        style: svc.rasterStyle(),
        center: prj.toLngLat(prj.getMapExtent().center()),
        zoom: 2
      });
      map.attach(svc, prj);
      await map.onLoad();
      if (disposed) { map.remove(); return; }
      const cad = new MapCadLayer();
      map.addControl(cad);
    })();
    return () => { disposed = true; map?.remove(); };
  }, []);
  return <div ref={ref} style={{ position: "absolute", inset: 0 }} />;
}
```

> 注意 React StrictMode 会双调用 useEffect，闭包中的 `disposed` 和 `map?.remove()` 必不可少。

## 五、纯 HTML `<script>`

```html
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://vjmap.com/demo/js/vjmap/vjmap.min.css" />
  <script src="https://vjmap.com/demo/js/vjmap/vjmap.min.js"></script>
  <script src="https://vjmap.com/demo/js/vjmap/vjmapext.min.js"></script><!-- 注意在 /js/vjmap/ 目录下 -->
</head>
<body style="margin:0;background:#022B4F;">
<div id="map" style="position:absolute;inset:0"></div>
<script>
(async () => {
  const svc = new vjmap.Service(SERVICE_URL, TOKEN);
  const res = await svc.openMap({ mapid: "sys_zp", mapopenway: vjmap.MapOpenWay.GeomRender });
  if (res.error) return console.error(res.error);
  const prj = new vjmap.GeoProjection(res.bounds);
  const map = new vjmap.Map({
    container: "map",
    style: svc.rasterStyle(),
    center: prj.toLngLat(prj.getMapExtent().center()),
    zoom: 2
  });
  map.attach(svc, prj);
  await map.onLoad();
  const cad = new vjmapext.MapCadLayer();
  map.addControl(cad);
  cad.createUI({ theme: "dark" });
})();
</script>
</body>
</html>
```

## 六、不同框架共性注意

- 所有框架都要 `await map.onLoad()` 之后再 add source / layer / Marker / Control。
- Vue `<keep-alive>` 场景用 `activated/deactivated` 控制 map 的 `remove/resize`。
- React 热更新（HMR）时，旧 map 会泄漏，需在 effect cleanup 中 `map.remove()`。
- SSR 框架（Next/Nuxt）中 vjmap 只在客户端可用：`if (typeof window !== "undefined") { ... }`，或用动态 `import`。
