☰
截图救不了复刻:GLM-5.1 复刻原神地图实录——Vue2+Leaflet 配置与验证
2026/9/28 19:40:01 网站建设 项目流程

1. 截图救不了复刻:GLM-5.1 复刻原神地图实录——Vue2+Leaflet 配置与验证

先说结论:截图对比、outerHTML 扒结构、F12 抄 CSS 这套"前端复刻三件套",在静态落地页上确实好用,但遇到原神大世界交互地图这种项目会直接失效。原因不复杂——这个页面根本不是"一张图",而是 10 万+ 动态标记点、自定义像素坐标系、Vue2+Quasar+Leaflet 深度耦合、服务端按需查询的组合体。你截一张图,AI 能给你生成一个"看起来像"的页面,但标记点位置全偏、弹窗不跟随、聚合失效、进度条永远 0%。

我这次用 GLM-5.1 走了一遍完整流程:从截图识别到瓦片坐标对齐,从 deck.gl 误判到 Leaflet 重写,从 Python 数据管道丢字段到前端表现异常。踩过的坑里最有代表性的是:一个 30 个字符的import 'leaflet/dist/leaflet.css'让模型卡了 15 分钟,以及 Python 转换脚本里 5 个 CRITICAL 数据损毁——脚本能跑、JSON 能生成、前端能加载,但所有功能都是错的。

这篇文章写给想用 AI 做地图类复刻的工程师。我会给出可复制的config.toml骨架、TaoToken 统一 Key/API 通道接入步骤、坐标校验与渲染验证动作,目标是把一个可交互地图原型跑通。适合谁:有 Vue2/Vue3 基础、想用 Leaflet 做自定义坐标系地图、或者正在被"AI 生成的代码能跑但视觉不对"折磨的人。

2. 原问题与场景:为什么截图三件套会失效

2.1 这个页面不是"一张图"

原神交互地图的核心复杂度在于它不是静态视觉,而是运行时行为。具体拆开看:

  • 10 万+ 动态标记点,每个标记有自己的图标、状态、点击弹窗、所属物品分类
  • 自定义坐标系,不是 WebMercator 经纬度,而是游戏像素坐标 + LeafletCRS.Simple投影
  • Vue2 + Quasar + Leaflet 深度耦合,560 行单文件IndexPage.vue、14 个 API 文件、魔改过的 markercluster 插件
  • 服务端按需查询,每次操作向后端发 5-8 个 API 请求,过滤逻辑在 Java 后端

截图能捕获的只有"某一时刻某一缩放级别下的视觉快照",捕获不了标记点的数据绑定、坐标变换、图层切换、状态同步。这就是为什么abi/screenshot-to-code这类工具在静态页面上表现优秀,在动态地图上捕获率接近 0。

2.2 三方关系决定了架构边界

复刻之前必须理清数据来源和代码参考的关系,否则后面所有决策都会踩坑:

角色做了什么协议
空荧酒馆社区团队原创地图数据维护,开源 v3 前端MulanPSL-2.0
v3.yuanshen.site官方 Web 地图,复刻目标Vue2 + Quasar + Leaflet
第三方独立开发者项目完全独立重写,Preact + 自研 canvas 地图MIT,数据停在旧版本

关键结论:第三方项目和官方 v3 零代码共享。UI/交互参考官方开源前端,数据走官方公开 API。这个边界定清楚了,后面才不会在"要不要抄第三方实现"上浪费时间。

2.3 真实工程规模

很多人对"复刻一个网页"没概念,看几个数字就明白了:

数据数量说明
标记点105,231 个全地图每个宝箱、神瞳、特产的位置
物品分类3,926 个跨越 14 个类型
区域树37 个主区域 + 子区域
图标1,087 个 PNG含中文文件名、哈希名、数字名三种命名
瓦片12,750 张四级缩放,带负坐标,约 300MB
data.json39 MB全量点位单体文件

这不是"做个组件"的工作量,是"搭一个数据管道 + 渲染引擎 + UI 系统"的小型工程。AI 要在这其中做对架构决策、翻译 Vue2 到 Vue3、写 Python 数据转换脚本三层工作,任何一层失误都会连锁影响下游。

3. TaoToken 前置:统一 Key/API 通道接入

3.1 为什么需要统一通道

复刻过程中会频繁调用模型做代码翻译、Bug 定位、数据字段分析。如果每个环节用不同的 Key 和 endpoint,排查问题时很难区分"是模型能力问题"还是"是通道配置问题"。用 TaoToken 做统一入口的好处是:一个 Key 覆盖模型对话、编码计划、控制台管理,接入文档和 API Keys 都在同一处管理。

官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=

API 基础地址:https://taotoken.net/api

3.2 获取 Key 与配置

第一步,打开控制台创建 API Key:

https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console

第二步,在 API Keys 页面复制你的 Key:

https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys

第三步,把 Key 写进项目根目录的.env,不要提交到 Git:

# .env TAOTOKEN_API_KEY=sk-你的key TAOTOKEN_BASE_URL=https://taotoken.net/api

3.3 config.toml 骨架

如果你用支持 TOML 配置的客户端或自建脚本,可以直接用下面这个骨架。注意base_url不要带 UTM 参数,API 调用只认纯地址:

# config.toml [provider] name = "taotoken" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" [model] default = "glm-5.1" max_tokens = 8192 temperature = 0.2 [project] # 复刻项目根目录 root = "/root/code/ssbh/capturegenshin" # 数据管道脚本 sync_script = "GenshenMap-python/map_sync.py" split_script = "GenshenMap-python/split_data.py" [map] # Leaflet 自定义坐标系配置 crs = "Simple" center_x = 3568 center_y = 6969 zoom_min = 10 zoom_max = 13 tile_size = 256

注意:center_y这个值很关键。我一开始用了源码里的 fallback 默认值 6286,结果标记点整体偏移 683 像素。真实运行时配置在webapp.json里,不是web-map.json。这个坑后面第 5 节会详细讲。

3.4 模型对话与 Coding Plan 分流

如果你只是验证模型对某段 Leaflet 配置的理解,用模型对话页面快速试:

https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat

如果你要长期做编码、Agent 任务、多轮代码翻译,建议走 Coding Plan,额度更稳定:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan

接入文档在这里,遇到参数问题先查文档:

https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc

4. 可复制配置:Leaflet 集成与坐标对齐

4.1 第一次选型的教训:deck.gl 看起来很美

我最初让 GLM-5.1 做技术选型,它选了 deck.gl,理由是"WebGL 渲染,10 万+ 标记点无压力,正交投影比 Leaflet hack CRS 更自然"。这套方案在 Phase 0 和 Phase 1 确实顺利,瓦片能加载、标记能点击、弹窗能显示。

但到 Phase 1.5 开始崩。12 个意外踩坑接连出现,其中 5 个是架构级的:

能力Leafletdeck.gl
标记点点击弹窗layer.bindPopup自动定位手动管理 DOM 弹窗 + 监听视图变化
标记点聚合L.markerClusterGroup一行配置无原生聚合,需自行实现
图层独立管理每物品独立 layer一个 ScatterplotLayer 管全部
标记完成状态layer.setIcon直接交换 DOM维护两份标记集合并切换
CSS 控制显隐修改 DOM class 即可走数据层过滤或图层参数

核心问题:GLM-5.1 选 deck.gl 时只看"渲染性能"一个维度,没看"生态完整度"。在重度依赖 Leaflet 生态的项目里,这个误判的代价是 8 个架构级问题,继续修复最多只能解决 12/20 个问题。

4.2 重写方案:保留 Leaflet 引擎

第三次尝试的策略很朴素:整体迁入官方开源前端的 Leaflet 代码,直连 API 先跑通,跑通后再把数据迁移到自建 COS。保留引擎,8 个架构问题全部消失。

关键配置如下:

// src/map/map-init.js import L from 'leaflet' import 'leaflet/dist/leaflet.css' // 这一行不能省,后面会讲为什么 export function createMap(containerId, center) { const map = L.map(containerId, { crs: L.CRS.Simple, minZoom: 10, maxZoom: 13, zoomControl: false, attributionControl: false, center: [center.y, center.x], zoom: 11 }) L.tileLayer('/tiles/twt642/{z}/{x}/{y}.png', { tileSize: 256, minZoom: 10, maxZoom: 13, noWrap: true, bounds: [[-4096, -4096], [4096, 4096]] }).addTo(map) return map }

4.3 坐标对齐:683 像素偏移的教训

用户反馈"黄点点和地图没有正确对应上"。我让 GLM-5.1 找原因,它去翻了源码,找到center: [3568, 6286]这个 fallback 值,套上去——用户反馈"比以前好一点还是差一些"。

后来用 Playwright 打开目标站点监听所有 JSON 响应,一分钟后就发现真实配置文件不叫web-map.json而是webapp.json,里面的center是[3568, 6969]——Y 值差了 683。

// 坐标校验脚本 async function verifyCenter() { const response = await fetch('/api/config/webapp.json') const config = await response.json() console.log('真实 center:', config.center) // 对比你代码里的 center,差值超过 1 像素就要查 const expected = [3568, 6969] const diff = Math.abs(config.center[1] - expected[1]) if (diff > 1) { console.warn(`Y 轴偏移 ${diff} 像素,检查是否用了 fallback 默认值`) } }

这个坑的本质是:AI 读到 fallback 默认值就用了,没想到去抓真实运行时配置。你可以在项目里加一个启动时的配置校验,把这类问题挡在开发阶段。

4.4 瓦片坐标与负值处理

瓦片 ID 不是文档里的twt40而是twt642,瓦片源不是tiles.yuanshen.site(那是个文件浏览器)而是assets.yuanshen.site。更麻烦的是瓦片坐标有负值,旧脚本只覆盖正坐标。

# 瓦片下载脚本片段 def tile_range(z): n = 2 ** z # 自定义坐标系下范围是 [-n/2, n/2] half = n // 2 return range(-half, half) for z in range(10, 14): for x in tile_range(z): for y in tile_range(z): url = f"https://assets.yuanshen.site/tiles/twt642/{z}/{x}/{y}.png" # 下载逻辑

批量下载脚本容易被 shell 600s 超时杀掉,用nohup后台跑:

nohup python3 download_tiles.py > tiles.log 2>&1 &

5. 验证请求与成功结果

5.1 一行 CSS 让模型卡了 15 分钟

Leaflet 集成进 Vue3 后,打开页面看到的是一个诡异的大椭圆形暗角圆环:白色背景,边缘是 radial-gradient 过渡,没有任何瓦片、标记或 UI 元素可见。后端 API 全部返回 200,Leaflet 实例成功创建,DOM 里.leaflet-map-pane也存在,但就是没有瓦片渲染在视口里。

排查花了 15 分钟,试了 CRS 投影配置、mapPane transform、center 覆盖、merge 顺序,全部没用。最后用 Playwright 检查 DOM,发现所有.leaflet-pane元素的position都是static——而 Leaflet 默认应该是absolute。

根因:MapPage.vue缺少一行import 'leaflet/dist/leaflet.css'。

Leaflet 的 CSS 里定义了.leaflet-pane { position: absolute; }和.leaflet-map-pane { transform-origin: 0 0; }。没有这个 CSS,所有 pane 保持默认的position: static,translate3dtransform 无法正确偏移内容,瓦片全部渲染在视口外。

加上这一行,150/150 瓦片瞬间全部可见。

提示:TS 编译器对这种不导出值的 side-effect import 不报错也不警告,所以 AI 翻译时很容易把它当作"无用 import"忽略。你可以在 ESLint 里加一条规则,禁止删除 CSS side-effect import。

5.2 渲染验证动作

跑通后,用下面这套动作验证渲染是否正确:

// 渲染验证脚本,在浏览器控制台执行 function verifyRender() { const map = document.querySelector('.leaflet-container') const panes = document.querySelectorAll('.leaflet-pane') const tiles = document.querySelectorAll('.leaflet-tile-loaded') console.log('地图容器存在:', !!map) console.log('pane 数量:', panes.length) console.log('已加载瓦片:', tiles.length) // 检查 pane 定位 panes.forEach((pane, i) => { const pos = getComputedStyle(pane).position if (pos !== 'absolute') { console.error(`pane ${i} position 异常: ${pos}`) } }) // 检查瓦片是否在视口内 const viewport = map.getBoundingClientRect() let inViewport = 0 tiles.forEach(tile => { const rect = tile.getBoundingClientRect() if (rect.right > viewport.left && rect.left < viewport.right) { inViewport++ } }) console.log('视口内瓦片:', inViewport) } verifyRender()

预期结果:pane 全部absolute,视口内瓦片数量大于 0,地图可拖动缩放。

5.3 标记点渲染验证

// 标记点验证 function verifyMarkers() { const markers = document.querySelectorAll('.leaflet-marker-icon') console.log('标记点数量:', markers.length) // 检查第一个标记的坐标 if (markers.length > 0) { const first = markers[0] const transform = first.style.transform console.log('第一个标记 transform:', transform) // 解析 translate3d(x, y, 0) const match = transform.match(/translate3d\(([^p]+)px,\s*([^p]+)px/) if (match) { console.log('标记坐标:', { x: parseFloat(match[1]), y: parseFloat(match[2]) }) } } } verifyMarkers()

如果标记点数量为 0,检查selectedItemIds.size === 0时是否返回了全部 markers(这是个常见 bug,10 万个点位一次性渲染会卡顿)。

6. 本篇常见错排查

6.1 Python 数据管道的 5 个 CRITICAL 数据损毁

这是整个项目最深的教训。Python 脚本能跑、JSON 能生成、上传 COS 能访问、前端能加载、地图能渲染——但所有功能都是错的。不是 404,不是类型错误,是数据语义错误。

Bug 位置AI 写的代码后果
map_sync.py第 261 行item_ids = [it["itemId"] for it in item_list if "itemId" in it]只取 itemId,丢掉 count 字段,进度条永远 0%
split_data.py第 86 行if name.lstrip("-").isdigit(): icon_ids.add(int(name))只接受纯数字文件名,中文/哈希图标全过滤
map_sync.py第 266 行"title": m.get("markerTitle", "")存为 title,前端读 markerTitle,弹窗标题永远显示"点位信息"
map_sync.pytransform_items只存 id/name/icon/typeIds/areaId/markerIds没存 specialFlag 和 sortIndex,传送点开关失效
map_sync.py第 220 行"children": [c["id"] for c in a.get("children", [])]子地区存为 int 列表,后续当 dict 遍历,TypeError 崩溃

根因都是同一个:GLM-5.1 写转换函数时,没有先读前端代码确认依赖哪些字段,而是基于 API 返回结构"自行精简"。

修复方式:从原始 API 数据重跑split_data.py,覆盖生成新数据。但更重要的是加一道校验:

# 数据完整性校验 REQUIRED_MARKER_FIELDS = ['id', 'itemId', 'count', 'markerTitle', 'x', 'y'] REQUIRED_ITEM_FIELDS = ['id', 'name', 'icon', 'typeIds', 'areaId', 'specialFlag', 'sortIndex'] def validate_markers(markers): for m in markers: missing = [f for f in REQUIRED_MARKER_FIELDS if f not in m] if missing: raise ValueError(f"标记 {m.get('id')} 缺少字段: {missing}")

6.2 前端常见错

ItemSelector 宽 6416px:rem 缩放不生效,需动态设htmlfont-size。

选地区后列表为空:只检查selectedChildAreaId,顶级地区时为 null。要同时检查selectedAreaId。

COS 中文图标 404:double-encode 问题,中文文件名需要两次 encodeURIComponent。

图标存在也 403:翻译时照抄了源码的referrerpolicy="no-referrer",去掉即可。

地区面板死循环不显示:areaFirstChildcomputed 依赖未初始化的areaSelectedTop,加空值判断。

6.3 模型对话验证配置

如果你不确定某段 Leaflet 配置是否正确,可以把代码贴到模型对话页面快速验证:

https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat

比如问:"Leaflet CRS.Simple 下,center 设为 [3568, 6969],zoom 11,瓦片范围 [-4096, 4096],这个配置能正确渲染吗?"模型会帮你检查坐标系和边界。

6.4 接入文档与 API Keys

遇到 API 参数问题、Key 权限问题,先查接入文档:

https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc

Key 管理在 API Keys 页面:

https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys

7. 语义一致 CTA:按场景分流

7.1 排障与接入场景

如果你正在被"代码能跑但视觉不对"、"数据加载了但功能异常"这类问题折磨,建议先把 Key 和接入通道理顺,避免在排查时分心:

  • API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api-keys
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc

7.2 验证模型能力场景

想快速验证 GLM-5.1 对某段 Leaflet 配置、某个坐标变换、某个数据字段的理解,用模型对话页面:

https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model-chat

7.3 长期编码与 Agent 场景

如果你要长期做地图复刻、数据管道、多轮代码翻译,建议走 Coding Plan,额度更稳定,适合 Agent 类长程任务:

https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding-plan

7.4 Claude Code 接入

如果你用 Claude Code 做开发,Anthropic 兼容通道在这里:

https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=claude-code-anthropic

最后说一个我踩过的坑:Python 数据管道写完一定要加字段校验,不要相信"脚本能跑就是对的"。前端表现异常时,先查数据层,再查渲染层。地图类项目里,坐标偏移 1 像素和字段丢 1 个,表现可能都是"看起来不太对",但排查方向完全不同。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询