作为一个历史爱好者,经常在看历史书籍、古装剧、穿越历史小说时,会对书中提到的古代地名产生好奇:这个地名现在在哪里?它现在叫什么名字?现在地图上什么位置?
我之前都是打开搜索引擎,输入古代地名找到答案,再根据其对应的现在位置,打开地图APP找到这个位置,这个过程既繁琐又不够直观。
于是有了开发一个小程序的想法:输入古代地名,AI自动解析对应的现代位置,并在地图上直观展示。(尝鲜版到原文:用 OpenCode 开发"古今地图"微信小程序实战记录)
本文将完整记录这个开发过程,展示 AI 如何改变传统软件开发模式。
一、项目生成阶段:用自然语言描述想法
1.1 输入需求
通过 OpenCode 发送了需求描述:
开发一个"古今地图"微信小程序,帮助用户查询古代地名对应的现代地理位置。
主要功能包括:
在首页输入古代地名(如"北宋大名府"、"三国陇右地区")
调用大模型API解析古代地名,找到对应的现代位置
集成腾讯地图API,生成地图缩略图展示
支持点击地图跳转到详情页,可以缩放查看
自动保存查询历史,支持收藏功能
提供快捷示例,方便用户快速体验
技术要求:
- 使用微信小程序原生框架
- 大模型使用腾讯混元(通过云开发AI能力调用)
- 地图服务使用腾讯地图API
- UI风格采用深色主题,体现历史厚重感
1.2 OpenCode生成过程
OpenCode理解需求后,自动进行了项目规划:
1. 架构设计:确定使用微信小程序原生框架,无需额外脚手架
2. 页面规划:首页(查询)、地图页(详情)、历史页(记录管理)
3. 服务设计:LLM服务(调用混元大模型)、地图服务(腾讯地图API)
4. 数据流设计:本地存储历史记录和收藏,云端调用AI服务
1.3 生成的项目结构
OpenCode自动生成了完整的小程序项目:
ancient-map-weapp/
├── pages/
│ ├── index/ # 首页 - 地名查询主页面
│ │ ├── index.wxml # 页面结构
│ │ ├── index.wxss # 页面样式
│ │ ├── index.js # 页面逻辑
│ │ └── index.json # 页面配置
│ ├── map/ # 地图详情页
│ │ ├── map.wxml
│ │ ├── map.wxss
│ │ ├── map.js
│ │ └── map.json
│ ├── history/ # 历史记录页
│ │ ├── history.wxml
│ │ ├── history.wxss
│ │ ├── history.js
│ │ └── history.json
│ └── webview/ # 地图APP跳转页
│ └── ...
├── services/
│ ├── llmService.js # 大模型服务(混元AI调用)
│ └── mapService.js # 地图服务(腾讯地图API)
├── cloudfunctions/
│ └── callHunyuan/ # 云函数(备用AI调用方案)
│ ├── index.js
│ ├── config.json
│ └── package.json
├── utils/
│ └── util.js # 工具函数
├── app.js # 应用入口(全局数据、生命周期)
├── app.json # 应用配置(页面路由、TabBar)
├── app.wxss # 全局样式└── AI_CONFIG_GUIDE.md # AI配置指南
核心代码示例,LLM服务调用腾讯混元大模型:
// services/llmService.js
asyncfunctioncallLLM(ancientName) {
const prompt = buildPrompt(ancientName)
// 调用微信小程序官方AI能力
const model = wx.cloud.extend.AI.createModel('hunyuan-exp')
const res = await model.generateText({
model: 'hunyuan-turbos-latest',
messages: [
{
role: 'system',
content: '你是一个中国古代历史地理专家...'
},
{
role: 'user',
content: prompt
}
]
})
returnparseLLMResponse(res.choices[0].message.content)
}
functionbuildPrompt(ancientName) {
return`请解析以下中国古代地名,并返回JSON格式:
{
"modernName": "现代地名",
"location": "所在省份",
"coordinates": { "lat": "纬度", "lng": "经度" },
"description": "历史背景说明"
}
古代地名:${ancientName}`
}
二、编译调试阶段:错误排查与修复
2.1 第一次编译: 云开发 未初始化
将代码导入微信开发者工具后,编译报错:
[错误] 云开发未初始化,请先开通微信云开发
原因分析:小程序使用了 wx.cloud.extend.AI 能力,需要先开通云开发环境。
解决方案:
- 1. 在微信开发者工具中点击「云开发」按钮,按指引开通
- 2. 在 app.js 中配置环境ID:
wx.cloud.init({
env: 'cloudbase-xxx', // 替换为实际环境ID
traceUser: true
})
2.2 第二次编译:AI能力版本不支持
再次编译,遇到新错误:
原因分析:代码生成缺失大括号导致编译失败
解决方案:
- Opencode再次检查代码并完善前后括号,最终编译通过
三、 UI 优化阶段:从"能用"到"好用"
3.1 第一轮优化:首页搜索区域
用户反馈:
首页搜索区域看起来太平淡了,缺少视觉层次感,搜索按钮和输入框的样式可以更精致一些。
OpenCode优化方案:
- 添加深色渐变背景(
#1a1a2e→#16213e),体现历史厚重感 - 输入框添加图标和清除按钮,提升交互体验
- 按钮添加渐变和阴影效果
- 卡片采用圆角设计,增加现代感
/* 搜索区域优化 */
.search-section {
padding: 40rpx 30rpx;
background: linear-gradient(135deg, [#1a1a2e]()0%, [#16213e]()100%);
border-radius: 20rpx;
}
.search-title {
font-size: 44rpx;
font-weight: 700;
color: [#fff]();
text-align: center;
}
.input-group {
display: flex;
align-items: center;
background: rgba(255,255,255,0.1);
border-radius: 50rpx;
padding: 20rpx 30rpx;
}
3.2 第二轮优化:搜索结果展示
用户反馈:
搜索结果的展示可以更美观,特别是古代地名和现代地名的对比展示,以及地图区域的呈现。
OpenCode优化方案:
- 古代地名使用大号字体,突出显示
- 现代信息采用卡片式布局,层次分明
- 地图缩略图添加渐变遮罩和提示文字
- 操作按钮采用主次分明的配色
/* 结果卡片优化 */
.result-header {
background: linear-gradient(135deg, [#1a1a2e]()0%, [#16213e]()100%);
padding: 30rpx;
}
.ancient-name {
font-size: 40rpx;
font-weight: 700;
color: [#fff]();
}
.modern-name {
font-size: 32rpx;
font-weight: 600;
color: [#1a1a2e]();
}
.map-preview {
position: relative;
height: 400rpx;
}
.map-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx;
background: linear-gradient(transparent, rgba(0,0,0,0.5));
}
3.3 第三轮优化:快捷示例和历史记录
用户反馈:
快捷示例的样式可以更精致,历史记录页面可以显示更多有用信息,比如查询时间。
OpenCode优化方案:
- 快捷示例采用标签云式布局
- 历史记录添加时间格式化显示("刚刚"、"5分钟前"等)
- 收藏和历史采用Tab切换,节省空间
// 时间格式化
formatTime(timestamp) {
const diff = newDate() - newDate(timestamp)
if (diff < 3600000) {
const minutes = Math.floor(diff / 60000)
return minutes < 1 ? '刚刚' : `${minutes}分钟前`
}
if (diff < 86400000) {
const hours = Math.floor(diff / 3600000)
return`${hours}小时前`
}
// ... 更多逻辑
}
3.4 最终产品效果
四、深度优化阶段:解决核心问题
4.1 问题一:地图预览不显示
用户反馈:
搜索完成后,预览卡片没有显示预览图,点击详情才能看到。
问题诊断:
OpenCode分析了代码,发现问题根源:
1. 使用了腾讯地图静态图 API - 需要配置 downloadFile 合法域名
2. 静态图可能加载失败 - 外部图片依赖网络和服务稳定性
3. WXML逻辑判断问题 - 代码优先判断 mapImageUrl,失败后回退到 <map> 组件
解决方案讨论:
用户:使用方案1(小程序内置地图组件)
OpenCode:收到,我将:
- 修改
index.wxml- 简化地图预览区域,直接使用<map>组件 - 修改
index.wxss- 调整地图组件样式 - 修改
index.js- 移除getMapStaticImage调用 - 可选更新
mapService.js- 注释掉静态图相关函数
实施过程:
// 原代码(有问题的逻辑)
<view class="map-preview"wx:if="{{searchResult.coordinates}}">
<imagewx:if="{{mapImageUrl}}"src="{{mapImageUrl}}"mode="aspectFill" />
<viewwx:elseclass="map-placeholder">
<mapid="miniMap"... />
</view>
</view>
// 新代码(简化后)
<viewclass="map-preview"wx:if="{{searchResult.coordinates}}">
<map
id="miniMap"
class="map-component"
latitude="{{searchResult.coordinates.lat}}"
longitude="{{searchResult.coordinates.lng}}"
scale="12"
show-location
markers="{{[{id: 1, latitude, longitude, title, width: 30, height: 30}]}}"
/>
</view>
结果:
- ✅ 不再依赖外部图片域名
- ✅ 搜索完成后直接显示地图预览
- ✅ 使用小程序原生组件,更稳定
遇到的新问题:
[渲染层网络层错误] Failed to load local image resource /images/location.png
解决:移除了自定义 iconPath,使用默认marker:
markers="{{[{
id: 1,
latitude: searchResult.coordinates.lat,
longitude: searchResult.coordinates.lng,
title: searchResult.modernName,
width: 30,
height: 30
// 移除了:iconPath: '/images/location.png'
}]}}"
4.2 问题二:区域显示应为范围而非单点
用户反馈:
现在搜索显示的只是一个地点,我需要显示范围,比如河南省,现在只标记郑州具体某一个地点。我需要你修改一下大模型调用和地图显示,标记整个河南省范围。
问题分析:
搜索"河南省"时,AI返回的是河南省的某个中心点坐标(如郑州),而不是整个河南省的地理范围。
解决方案:
OpenCode提出三个修改方向:
- 修改AI提示词 - 让AI返回区域边界坐标
- 修改地图显示 - 使用多边形显示区域
- 处理数据传递 - 将区域信息传到详情页
实施过程:
步骤1:修改AI提示词(services/llmService.js)
function buildPrompt(ancientName) {
return `请解析以下中国古代地名,并返回JSON格式的结果:
古代地名:${ancientName}
请返回以下信息(JSON格式):
{
"modernName": "现代地名(精确到市/县)",
"location": "所在省份和位置描述",
"coordinates": {
"lat": "中心点纬度",
"lng": "中心点经度"
},
"bounds": {
"northeast": {"lat": "东北角纬度", "lng": "东北角经度"},
"southwest": {"lat": "西南角纬度", "lng": "西南角经度"}
},
"isRegion": true,
"description": "简要历史背景"
}
注意事项:
1. 如果是区域名称(如"陇右"、"河南"),请返回该区域的范围bounds
2. isRegion标记是否为区域(true为区域,false为具体地点)`
}
步骤2:解析响应新增字段(services/llmService.js)
return {
modernName: result.modernName || '未知',
location: result.location || '未知',
coordinates: result.coordinates || null,
bounds: result.bounds || null, // 新增:区域边界
isRegion: result.isRegion || false, // 新增:是否区域
description: result.description || '暂无详细说明'
}
步骤3:修改地图显示(pages/index/index.wxml)
<map
latitude="{{searchResult.coordinates.lat}}"
longitude="{{searchResult.coordinates.lng}}"
scale="{{searchResult.isRegion ? 6 : 12}}"
markers="{{[...]}}"
polygons="{{searchResult.bounds ? [{
points: [
{latitude: northeast.lat, longitude: southwest.lng},
{latitude: northeast.lat, longitude: northeast.lng},
{latitude: southwest.lat, longitude: northeast.lng},
{latitude: southwest.lat, longitude: southwest.lng}
],
fillColor: '[#1a1a2e33]()',
strokeColor: '[#1a1a2e]()',
strokeWidth: 2
}] : []}}"
/>
优化颜色:
用户:黑色方框显示不太美观,优化一下
OpenCode:改为天蓝色系:
fillColor: '#3b82f620', // 半透明天蓝色填充
strokeColor: '#3b82f6', // 天蓝色边框
结果:
- ✅ 搜索"河南省"显示整个河南省的矩形边界
- ✅ 搜索"北宋大名府"显示具体地点标记
- ✅ 自动调整缩放级别(区域用scale 6,地点用scale 12)
4.3 问题三:预览卡片按钮美化
用户反馈:
预览卡片的两个按钮不太美观,优化一下。
解决方案:
从传统按钮改为图标+文字卡片式:
<!-- 原代码 -->
<viewclass="action-buttons">
<buttonclass="btn btn-secondary">在地图APP中打开</button>
<buttonclass="btn btn-primary">分享位置</button>
</view>
<!-- 新代码 -->
<viewclass="action-buttons">
<viewclass="action-btn"bindtap="openInMapApp">
<textclass="action-icon">🗺️</text>
<textclass="action-text">地图导航</text>
</view>
<viewclass="action-btn share-btn"bindtap="shareLocation">
<textclass="action-icon">📤</text>
<textclass="action-text">分享位置</text>
</view>
</view>
.action-btn {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20rpx 0;
background: [#f8f9fa]();
border-radius: 16rpx;
}
.share-btn {
background: linear-gradient(135deg, [#667eea]()0%, [#764ba2]()100%);
}
结果:
- 🗺️ 地图导航 - 简洁灰色按钮
- 📤 分享位置 - 紫色渐变按钮
4.4 问题四:古代名称详细信息展示
用户反馈:
预览也需要显示古代的名称,详细一点,修改下大模型调用和预览显示。
需求分析:
用户希望在搜索结果中显示更详细的古代地名信息,包括:
- 古代地名的标准写法
- 别称(历史称呼)
- 所属朝代
- 历史地位
实施过程:
步骤1:更新AI提示词
function buildPrompt(ancientName) {
return `请解析以下中国古代地名...
{
"ancientName": "古代地名的标准写法",
"ancientAliases": ["别称1", "别称2"],
"dynasty": "所属朝代或历史时期",
"modernName": "现代地名",
"historicalSignificance": "历史地位和重要性说明(30字以内)",
...
}`
}
步骤2:新增展示区域(pages/index/index.wxml)
<view class="result-header">
<viewclass="ancient-title">
<textclass="ancient-name">{{searchResult.ancientName}}</text>
<textclass="dynasty-tag"wx:if="{{searchResult.dynasty}}">
{{searchResult.dynasty}}
</text>
</view>
</view>
<viewclass="aliases-row"wx:if="{{searchResult.ancientAliases.length > 0}}">
<textclass="aliases-label">别称:</text>
<textclass="aliases-text">{{ancientAliases.join('、')}}</text>
</view>
<viewclass="significance"wx:if="{{searchResult.historicalSignificance}}">
<textclass="significance-text">{{searchResult.historicalSignificance}}</text>
</view>
步骤3:样式美化
.dynasty-tag {
font-size: 22rpx;
color: [#ffd700]();
background: linear-gradient(135deg, [#1a1a2e]()0%, [#16213e]()100%);
padding: 6rpx 14rpx;
border-radius: 8rpx;
}
.aliases-row {
padding: 16rpx;
background: [#f8f9fa]();
border-radius: 12rpx;
}
.significance {
padding: 16rpx;
background: linear-gradient(90deg, [#e9ecef]()0%, [#f8f9fa]()100%);
border-radius: 12rpx;
}
结果:
搜索"北宋大名府"后显示:
🏛️ 大名府 [北宋]
📜 别称:北京、大名府路
今名:河北省邯郸市大名县
位置:河北省南部,邯郸市下辖县
⭐ 北宋时期陪都,北方军事重镇
简介:北宋...
4.5 问题五:标签对齐优化
用户反馈:
预览卡片的今名、位置不对齐,美化一下。
用户后续反馈:
不要换行,太丑了。
我说的是label区不要换行,文字可以。
理解需求:
用户希望:
- "今名"、"位置"标签本身不换行(保持在一行)
- 内容文字可以正常换行
- 标签和内容顶部对齐
实施过程:
.modern-info, .location-info {
display: flex;
align-items: flex-start; /* 顶部对齐 */
padding: 20rpx 30rpx;
}
.modern-label, .location-label {
font-size: 28rpx;
color: [#999]();
flex-shrink: 0; /* 不被压缩 */
white-space: nowrap; /* 标签不换行 */
word-break: keep-all; /* 中文词语完整 */
margin-top: 4rpx; /* 微调对齐 */
}
.modern-name, .location-text {
flex: 1;
line-height: 1.5; /* 内容可以换行 */
}
结果:
- ✅ "今名"、"位置"标签完整显示,不会换行
- ✅ 内容文字自动换行,行高1.5倍
- ✅ 标签和内容顶部对齐
五、开发过程总结
5.1 我的角色(用户)
在整个开发过程中,我只需要通过 OpenCode 做三件事:
1. 描述需求 - 用自然语言告诉 AI 想要什么功能
- "开发一个古今地图小程序..."
- "搜索完成后预览卡片没有显示预览图..."
- "现在搜索显示的只是一个地点,我需要显示范围..."
2. 反馈问题 - 截图或复制错误信息给 AI
- "预览卡片的两个按钮不太美观..."
- "预览也需要显示古代的名称,详细一点..."
- "预览卡片的今名、位置不对齐..."
3. 确认方案 - 在多个方案中选择
- "使用方案1(小程序内置地图组件)"
- "可行"(确认修复计划)
5.2 OpenCode的角色
OpenCode承担了所有技术工作:
1. 需求分析 - 理解用户的自然语言描述
- 从"显示范围"理解到需要区域边界坐标
- 从"详细一点"理解到需要古代名称、别称、朝代
2. 架构设计 - 设计小程序的页面结构和数据模型
- 规划pages/目录结构
- 设计services/服务层
3. 代码生成 - 编写 WXML、WXSS、JS 代码
- 约2000行代码全部由AI生成
4. 错误修复 - 分析编译错误并自动修复
- 修复了index.js的语法错误(多余的})
- 修复了map.js的重复代码和语法问题
- 处理了marker图片加载失败的降级
5. UI 优化 - 根据反馈调整布局和配色
- 深色主题、渐变背景
- 紫色渐变按钮
- 天蓝色区域边框
6. 文档生成 - 自动生成开发文档
5.3 典型对话示例
场景1:地图预览不显示
用户:"搜索完成后,预览卡片没有显示预览图,点击详情才能看到"
OpenCode:分析了问题原因(静态图API需要配置域名),提出三种方案:
- 使用小程序内置地图组件
- 配置腾讯地图静态图域名
- 使用Canvas绘制地图
用户:"使用方案1"
OpenCode:执行修复,创建计划文档,修改代码...
场景2:区域范围显示
用户:"现在搜索显示的只是一个地点,我需要显示范围,比如河南省,现在只标记郑州具体某一个地点。我需要你标记整个河南省范围"
OpenCode:理解需求后制定计划:
- 修改提示词让AI返回区域范围坐标
- 修改地图显示处理区域
- 修改WXML支持多边形显示
然后依次执行,并处理用户反馈的颜色优化...
六、最后一公里:发布上线
虽然 OpenCode 完成了 90% 的开发工作,但最后 10% 的发布环节仍需要人工操作:
6.1 准备工作
1. 注册小程序账号 - 在微信公众平台注册,填写名称、图标、简介
2. 配置 云开发 环境 - 开通云开发,获取环境ID
6.2 配置AI能力
- 在云开发控制台「扩展应用」中开通「AI」扩展
- 选择「混元大模型」
- 确保小程序基础库版本 >= 3.7.1
6.3 上传代码、提交审核、发布
- 在微信开发者工具中点击「上传」,填写版本号
- 在公众平台提交审核,等待微信官方审核(1-3个工作日)
- 审核通过后发布上线
七、技术亮点
7.1 AI驱动的地名解析
利用大模型的知识能力,自动将古代地名映射到现代地理位置:
// 示例:输入"北宋大名府"
// AI返回:
{
"ancientName": "大名府",
"dynasty": "北宋",
"ancientAliases": ["北京", "大名府路"],
"modernName": "河北省邯郸市大名县",
"location": "河北省南部,邯郸市下辖县",
"coordinates": { "lat": 36.28, "lng": 115.15 },
"bounds": {
"northeast": { "lat": 36.5, "lng": 115.5 },
"southwest": { "lat": 36.0, "lng": 115.0 }
},
"isRegion": false,
"historicalSignificance": "北宋陪都,北方军事重镇",
"description": "北宋时期为北京大名府,是北宋的陪都之一..."
}
7.2 多层级地图展示
- 首页:微信小程序原生 <map> 组件(无需外部域名)
- 区域显示:使用 polygons 属性显示区域边界
- 详情页:可交互的地图组件,支持缩放
- 跳转:支持腾讯、高德、百度地图APP
// 区域多边形示例
polygons: [{
points: [
{lat: 36.5, lng: 114.0}, // 左上
{lat: 36.5, lng: 116.0}, // 右上
{lat: 34.0, lng: 116.0}, // 右下
{lat: 34.0, lng: 114.0} // 左下
],
fillColor: '[#3b82f620]()', // 半透明填充
strokeColor: '[#3b82f6]()', // 边框颜色
strokeWidth: 2
}]
7.3 AI驱动的区域识别
大模型不仅返回坐标,还能识别是区域还是具体地点:
// AI返回示例
{
"isRegion": false, // false=具体地点,true=区域
"bounds": { ... } // 区域边界坐标
}
// 系统自动处理
if (isRegion) {
scale = 6 // 缩小显示整个区域
showPolygons() // 显示多边形边界
} else {
scale = 12 // 放大显示具体地点
showMarker() // 显示标记点
}
7.4 迭代式 UI 优化
通过多轮对话不断优化:
| 轮次 | 用户反馈 | OpenCode优化 |
|---|---|---|
| 1 | 搜索区域平淡 | 深色渐变背景、卡片圆角 |
| 2 | 结果展示不够美观 | 古代地名突出、地图遮罩 |
| 3 | 快捷示例不精致 | 标签云布局、时间格式化 |
| 4 | 地图预览不显示 | 改用原生map组件 |
| 5 | 只显示单点 | 支持区域多边形 |
| 6 | 按钮不美观 | 图标+文字卡片式 |
| 7 | 古代信息不够 | 新增别称、朝代、历史地位 |
| 8 | 标签不对齐 | 顶部对齐、标签不换行 |
八、结语
通过这次实践,深刻感受到 AI 正在改变软件开发的方式。
开发体验对比
传统方式:
- 需要学习微信小程序框架
- 需要了解地图API的使用
- 需要手动编写所有代码
- 需要逐个排查错误
- 开发周期:1-2周
AI辅助方式:
- 用自然语言描述需求
- AI自动选择技术方案
- AI自动生成完整代码
- AI自动分析并修复错误
- 开发周期:1.5天
关键收获
1. 想法比技能更重要 - 只要有清晰的需求描述,AI就能帮你实现
2. 对话式开发 - 像和产品经理对话一样,一步步完善功能
3. 快速迭代 - 几分钟就能完成一轮UI调整,快速验证想法
4. 零代码门槛 - 不懂代码也能开发出功能完整的小程序
项目信息
- 开发工具:OpenCode
- 开发周期:1天(初始开发)+ 0.5天(优化迭代)
- 代码量:约 2000 行(全部由 AI 生成)
- 技术栈:微信小程序原生 + 腾讯混元大模型 + 腾讯地图API
- 主要优化:地图组件重构、区域显示、UI美化
本系列说明:在这个系列中教你打造24小时在线的AI员工OpenClaw,掌握Claude Code的开源替代OpenCode,从Coding Agent到更加通用的终端Local AI Agent进行架构剖析和对比,并描述相应实战案例过程。
—End—
本文作者:木昆子
本文原载:公众号“木昆子记录AI”