用 AI + 高德地图 MCP,3 小时做出杭州美食地图

用 AI + 高德地图 MCP,3 小时做出杭州美食地图最近刷到一篇热帖 一位在阿里五年的同学总结了一份她这五年 吃的每一口记录 收集的杭州美食排行榜 为 美食荒漠 正名 也让我回忆起自己初到杭州进入团队时也曾想整理一份 美食地图 却因种种原因而作罢 恰逢最近团队准备选团建的餐厅 于是立马把这

欢迎大家来到IT世界,在知识的湖畔探索吧!

最近刷到一篇热帖,一位在阿里五年的同学总结了一份她这五年(吃的每一口记录)收集的杭州美食排行榜,为“美食荒漠”正名。也让我回忆起自己初到杭州进入团队时也曾想整理一份“美食地图”,却因种种原因而作罢。恰逢最近团队准备选团建的餐厅,于是立马把这篇帖子分享给投食官。投食官仔细调研后感慨道,“这篇文章真是太好了,如果能在地图上看到它们的位置就更好了。”

这让我萌生了为这个美食排行榜做一个杭州美食地图的想法,以便能快速地看到餐厅的位置并进行选择。实现方法上 AI coding 加高德 MCP 有没有搞头?

文末附源码和项目地址供大家参考和取用。

安装高德MCP server

过程参考 :Cursor快速接入高德MCP server

https://lbs.amap.com/api/mcp-server/gettingstarted

只需准备好你的高德 key,在 cursor 上设置里如下配置即可。

{   "mcpServers": {     "amap-amap-sse": {       "url": "https://mcp.amap.com/sse?key=您在高德官网上申请的key"     }   } }

欢迎大家来到IT世界,在知识的湖畔探索吧!

配置好后检查 mcpserver 的状态,如下图则是正常可用的。

用 AI + 高德地图 MCP,3 小时做出杭州美食地图



欢迎大家来到IT世界,在知识的湖畔探索吧!

给 AI 提需求-构建工程提示词

欢迎大家来到IT世界,在知识的湖畔探索吧!1. 在当前文件夹下进行编写项目,尽量少的创建文件,只创建必要文件。
2. 根据给定的杭州推荐的美食店,制作一份可以预览的美食地图。最终的产物使用便捷的方式呈现,如使用html进行展示。
3. 地图上标注下面每个店铺精确的位置,为每个类型的餐厅用不同的合适的图标表示。
4. 地图采用真实的杭州市地图。
5. 点击图标可显示餐厅更多信息,餐厅优势,预约电话,推荐指数等。
6. 过程中可以借助任何工具和能力实现这一目标,如 高德MCP工具。
7. 展示的店铺已给定的为准,最终给出的数据要真实可靠。
8.将下列数据,通过amp-amp-sse 的 mcpserver,制作成适配高德地图的 json 格式数据。若没有找到餐厅,不要编造数据,坐标字段设为空值即可。
餐厅数据如下:
如*:主厨团队对传统杭帮菜的解构能力堪称艺术,招牌鱼圆弹润如凝脂,黄酒鹅肝配牛小排的组合既有东方韵味又有西式巧思。景观位虽为日式下沉设计,但私密性极佳,适合重要纪念日。唯一提醒:订位时建议直接对接主理人确认细节,米其林加身后服务响应速度有提升空间。
金*厅:四季酒店的镇店之宝,时令菜单每月更新,清明前的香椿嫩芽配河虾仁、霜降后的桂花糖藕都是必尝限定。脆皮鸡的表皮酥脆度堪比艺术品,和牛炒饭的镬气十足,适合带海外友人感受中式烹饪的精细度。
八*杯:里园的孪生品牌,早期的黄鱼锅贴仍是教科书级别,但近年创新菜式如松茸酿饭口感层次稍显单薄。建议避开周末午市,工作日晚间用餐能更好体会主厨团队的匠心。
集*楼:庭院里的江南美学展,青梅鹅肝配茉莉花茶冻的创意值得点赞。近期推出的「盲盒套餐」性价比突出,适合想体验高端杭菜又怕踩雷的新客。
木*溪:宋宴主题套餐需提前三天预订,茶点与菜肴的搭配暗合二十四节气,但每道菜量控制在2-3口刚好尝鲜。特别推荐茶艺师现场点茶,宋代七汤点茶法视觉效果震撼。
桂*房:满觉陇店的露台夜景是最大卖点,桂花酿醉虾的甜度把控精准。若多人聚餐可尝试「山房九宫格」拼盘,冷热荤素搭配考究,但需注意部分创意菜式如黑松露猫耳朵口味较重。
【浙菜】
锦*湖:原乾胧汇班底升级之作,龙翔桥店的脆皮鸡改良自港式烧味,外皮的焦糖层厚度比EFC店更均匀。推荐搭配店家自酿的青梅酒,解腻效果出众。
算*挡:滨江园区的宝藏食堂,人均150可吃到现拆的蟹粉豆腐和炭烤鳗鱼。建议4人以上拼单,把招牌菜凑齐分着吃更划算。
【创意菜】
雅*:西溪湿地内的隐秘据点,荷叶包蒸东山羊是夏季限定,肉质软烂入味。划船至码头需提前预约接驳船,整体体验像私人会所。
【融合菜】
乔*:阿里园区周边的老牌宴请场所,最近推出的「数字化点餐系统」可实时查看后厨烹饪进度。推荐黑椒牛排配龙井茶汤,西式主菜与杭帮调味的结合恰到好处。
略...

输入“需求文档”后,cursor 会进入 Plan 模式进行规划整体设计方案。

已开始为你的美食地图收集真实的高德地图数据。以下是实现方案和进度说明:
实现方案
数据收集:为每个餐厅通过高德地图API检索真实的地理位置(经纬度)、地址、照片等信息。若未找到则坐标为空。


数据格式:整理为适配高德地图MCP的JSON格式,包含必要的字段(如名称、类型、坐标、推荐指数、电话、优势等)。


地图展示:最终生成一个HTML文件,使用高德地图JS API进行可视化。不同类型餐厅用不同图标,点击可弹窗显示详细信息。


文件结构:只创建必要的文件(如index.html和数据JSON),保证项目简洁。


进度说明
已完成“如*”“金*厅”真实数据检索,获取了精确坐标和相关信息。
正在依次为所有餐厅检索真实数据,确保数据可靠。
检索不到的餐厅将坐标设为空,不会编造信息。


请确认:
是否需要为每家餐厅都补充“推荐指数、预约电话、优势”等字段?如无公开数据,是否允许留空或用描述性文字?


是否需要将所有数据单独导出为JSON文件,还是直接嵌入HTML中?


如无异议,将继续批量检索并整理数据,随后生成地图预览HTML。你可随时查看进度或指定展示细节。

十几分钟写完“需求文档”,晚饭时间到,提给 AI 让它先写着,我们去吃饭~

AI coding-开始寻找杭州美食之旅

调用高德 MCP Server-获取餐厅准确位置和信息

第一轮-模糊查询找到候选餐厅

AI 会先调用高德 MCP Server 的 maps_text_search 方法通过模糊查询,查到候选餐厅信息。

用 AI + 高德地图 MCP,3 小时做出杭州美食地图

输入

欢迎大家来到IT世界,在知识的湖畔探索吧!{   "keywords": "如院餐厅 杭州",   "city": "杭州",   "citylimit": true }

输出

可以看到,查出了一系列餐厅,其中有如*餐厅的基本信息,取出 id,作为下一轮的输入。

{   "pois": [     ...     {       "id": "B0JA5UF6R*",       "name": "如*餐厅",       "address": "玉泉路1*号",       "typecode": "0501",       "photo": "http://store.is.autonavi.com/showpic/b08795ab6adeba25a0115d"     },      {       "id": "B023B019A*",       "name": "群*饭店(信*路店)",       "address": "滨安路119*号(近信*路)",       "typecode": "0501",       "photo": "http://store.is.autonavi.com/showpic/2ac9e95efffe6e27a8eda43ef82711"     },     ...   ],   "suggestion": {     "keywords": "",     "ciytes": {       "suggestion": []     }   } }

第二轮-通过 id 获取餐厅详情

可以看到紧接着 AI 会调用高德 MCP Server 的maps_search_detail能力,通过 id 来获取餐厅详情。

用 AI + 高德地图 MCP,3 小时做出杭州美食地图

输入

欢迎大家来到IT世界,在知识的湖畔探索吧!{   "id": "B0JA5UF6R*" }

输出

可以看到这里获取到了餐厅的详情信息,包含名称、经纬度、地址、照片、营业时间、评分等。

{"id":"B0JA5UF6R*","name":"如*餐厅","location":"120.,30.","address":"玉泉路1*号","business_area":"西湖旅游区中心片区","city":"杭州市","type":"餐饮服务;中餐厅;中餐厅","alias":"","photo":"http://store.is.autonavi.com/showpic/b08795ab6adeba25a0115da*","cost":"","opentime2":"周一至周日 11:30-14:00,17:30-21:00","rating":"4.7","open_time":"11:30-14:00 17:30-21:00","meal_ordering":"0"}

AI 会将高德 MCP Server 中获取的数据格式化,整理到 json 文件中,作为数据源。

编写页面

数据收集完后,就开始根据需求,编写代码了,通过引入高德 API 展示杭州地图,然后将上一步整理的数据引入进来,标出餐厅的位置。并且支持点击餐厅的名称,弹框展示餐厅的详情,餐厅的推荐理由。

用 AI + 高德地图 MCP,3 小时做出杭州美食地图

虽然没有关注代码具体怎么实现的,但是它就这样水灵灵的工作起来了~

但我认为我们还是需要理解 AI 写的每一行代码,对代码质量有一个评估,一方面自己能有所进步,一方面不能让 AI 自由发挥的太过,到后面一行也看不懂,改也改不动的时候,项目就崩了。自己不理解的代码,可以让 AI 为代码都加上注释,方便理解。

调试页面

AI 写完了主体页面和功能点后,预览一下,还是有挺多细节需要修改的,如名称大小、背景框样式、弹框点击关闭逻辑等,这些和 AI 沟通,说明需要修改的位置和要求即可,如果说不清的时候,可以直接截图给 AI,告诉它要改什么,理解意图上还是十分强大的。

找素材

但是 AI 有一点不足的是不太方便从外部直接获取资源,比如试图让 AI 自己去网上找合适的素材图标,但是失败了,它会让我去给它下载,不然它会自己画一个丑陋抽象的 SVG 图标。

实在忍不了,只能去网上找素材,可以说整个过程中,花的最多的时间就是这里了。找合适的素材,尝试下载,要收费?换一个地方找,终于是免费的了,下载下来,传到图床上,变成公网可访问的链接。

整个找素材的过程十分痛苦,如果 AI 可以自己找就好了,也许是使用的姿势不太对,还需要继续研究。

好在最终找到了一个非常棒的平台,那就是阿里巴巴图标矢量库,还是自家兄弟好,素材非常的全且好用,大力推荐。

地址:https://www.iconfont.cn/?spm=a313x.search_index.i3.d4d0a486a.2dff3a81XH4Q3q

餐厅分类与筛选

对于之前的收集的餐厅的数据,没有一个很好的分类进行表达,于是让AI 重新分析一下整个餐厅数据,结合餐厅的信息,进行分类,然后将餐厅类型与图标素材再进行一个匹配。得到一个分类好的餐厅列表,与图标的映射关系。

用 AI + 高德地图 MCP,3 小时做出杭州美食地图

然后就可以愉快的让 AI 结合这个分类数据,改写一下餐厅的图标啦,并且基于分类,再加上了图例列表,和筛选项的功能,方便展示,不然 100个餐厅全部展示在地图上,也不太方便小伙伴们看。

然后页面的效果如下:

用 AI + 高德地图 MCP,3 小时做出杭州美食地图

AI 推荐餐厅

看了地图上标注的餐厅后,才发现,离自己园区的距离都比较远,于是含泪再写一份自己团队的聚餐选点指南,让 AI 帮助推荐餐厅。

欢迎大家来到IT世界,在知识的湖畔探索吧!1. 在当前项目中进行改进,删去图例、筛选项功能。 2. 使用给出的餐厅数据,在地图上进行标注。 3. 我们的出发位置是杭州市阿里巴巴滨江园区,标注出发点位置,用星星图标表示。 4. 图标素材在 resource/icon.txt中. 5. 计算出发点到各个餐厅的位置,在地图上用线连起来进行展示。 6. 分析一下各个餐厅的优劣,离出发点的距离,交通方式,和所需时间,周边配套,列在餐厅详情里。 7. 最后进行一个智能总结,推荐一个餐厅 8. 9. 过程中可以借助任何工具和能力实现这一目标,如 高德MCP工具。 10. 展示的店铺已给定的为准,最终给出的数据要真实可靠。 将下列数据,通过amp-amp-sse 的 mcpserver,制作成适配高德地图的 json 格式数据。若没有找到餐厅,不要编造数据,坐标字段设为空值即可。 1. 海*捞中南乐游城店 2. 黄*海鲜大排档(长河地铁站旁) 3. 群*饭店 4. 龙*·滨江烤全羊(杭州总店)dong'da 5. 东*方保利时光里店

有了前面的基础,这次 20 分钟就搞定了,主要在写需求文档和微调页面样式上,可以分析一下各个餐厅的优劣,离出发点的距离,交通方式,和所需时间,周边配套等。 最后进行一个智能总结,推荐一个餐厅,效果如下:

用 AI + 高德地图 MCP,3 小时做出杭州美食地图

小伙伴们就可以愉快的进行决策,到底选哪个餐厅了~

新的功能正在和我们投食官持续优化中,比如先选择团建地方,再选择聚餐地方,多个地点让 AI 分析推荐等~

发布与部署

公网部署方法挺多的,比如 GitHub、Gitee上提供了静态页面部署的能力,或者可以租一个阿里云服务器申请域名,搭建自己的网站。

在 GitHub 部署静态页面的简要指南

以下为 GitHub Pages 静态部署的核心步骤,基于官方文档和工具链实现:

1.创建仓库

  • 新建公共仓库,命名格式为 username.github.iousername 为你的 GitHub 用户名)。
  • 克隆仓库到本地,添加静态文件(如 index.html),并推送至 main 分支。

2. 配置发布源

  • 进入仓库 Settings > Pages,选择发布分支(如 maingh-pages)或 docs 文件夹作为源。
  • 若使用自定义构建工具(如 Hugo、Vue 等),需禁用默认 Jekyll 构建:在源目录根路径创建空文件 .nojekyll

3. 使用 GitHub Actions 自动部署

  • 在工作流配置文件(如 .github/workflows/deploy.yml)中添加以下步骤:
- name: Deploy to GitHub Pages     uses: peaceiris/actions-gh-pages@v3     with:       github_token: ${{ secrets.GITHUB_TOKEN }}       publish_dir: ./public  # 替换为你的构建输出目录

此 Action 会将指定目录内容部署到 gh-pages 分支。

4.访问页面

  • 页面地址为 https://username.github.io,更新后通常需等待 1-10 分钟生效。

5. 注意事项

  • 权限问题:若使用 GitHub Actions 推送,需确保管理员已验证邮箱并推送过代码。
  • 符号链接:仓库含符号链接时,需通过 GitHub Actions 部署。

最后

这次的实践我认为还是非常的有趣的,整个开发过程大概 3-4 小时,而且主要花在了找素材和调整样式上了。放在以前,有了一个好的点子,但是可能会因为麻烦,跨技术栈,生产周期长、拖延症等各种因素导致“创业未半而中道崩殂”。

不得不感叹,AI 真的能帮助人们提高效率,并且使之前不可能或者很难完成的事情变得可行。在 AI 时代,可能需要从之前传统的“关注技术栈和专业,关注如何实现”的思维转向聚焦到“扩展思维的宽度和认知,寻找事物的可达路径”上。

另外,高德的小伙伴是否考虑一下,增加一个餐厅、团建的地点投票功能~

可以在高德上发起投票,录入几个候选位置,邀请小伙伴们一起投票。可以很方便的查看当前位置(或者出发地点)距离候选地点的距离、交通方式和用时、候选地点周边配套等。 也可以让 AI 结合需求背景,总结一下各候选地点的优势。然后小伙伴们就可以愉快地投票了!

附录

源码如下,需要的小伙伴可自取,在这个基础上改改就能用了。

杭州美食地图

欢迎大家来到IT世界,在知识的湖畔探索吧!<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>杭州美食地图</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    html, body, #map { height: 100%; margin: 0; padding: 0; }
    .amap-info-content { font-size: 14px; }
    .legend {
      position: absolute;
      top: 10px;
      left: 10px;
      background: rgba(255,255,255,0.9);
      border-radius: 6px;
      padding: 10px;
      box-shadow: 02px 8px rgba(0,0,0,0.15);
      z-index: 1000;
    }
    .legend-item { display: flex; align-items: center; margin-bottom: 6px; }
    .legend-icon { width: 20px; height: 20px; margin-right: 6px; }
    .marker-label {
      font-size: 16px;
      color: #fff;
      background: rgba(34,34,34,0.75);
      border-radius: 6px;
      padding: 2px 10px;
      box-shadow: 02px 8px rgba(0,0,0,0.15);
      white-space: nowrap;
      transition: background 0.2s;
      border: none;
    }
    .marker-label-hover {
      background: rgba(34,34,34,0.95);
      color: #ffe58f;
      border: none;
    }
    /* 强制覆盖高德地图label的蓝色边框和背景 */
    .amap-marker-label {
      border: none !important;
      box-shadow: none !important;
      background: none !important;
      padding: 0 !important;
    }
    .filter-panel {
      position: absolute;
      top: 40px;
      right: 20px;
      width: 220px;
      background: rgba(255,255,255,0.97);
      border-radius: 10px;
      box-shadow: 02px 16px rgba(0,0,0,0.13);
      padding: 18px 18px 12px 18px;
      z-index: 1200;
      font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
      font-size: 15px;
      color: #222;
      transition: box-shadow 0.2s;
      max-height: 600px;
      overflow-y: auto;
    }
    .filter-title {
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 12px;
      letter-spacing: 2px;
    }
    .filter-section { margin-bottom: 14px; }
    .filter-label { font-size: 14px; margin-bottom: 6px; color: #666; }
    .filter-recommend label, .filter-category label {
      display: inline-block;
      margin-right: 10px;
      margin-bottom: 6px;
      cursor: pointer;
      font-size: 14px;
    }
    .filter-category {
      max-height: 280px;
      overflow-y: auto;
      border-radius: 6px;
      background: #f7f7f7;
      padding: 6px 4px 4px 8px;
      border: 1px solid #eee;
    }
    .filter-category label { width: 120px; }
    .filter-panel input[type="checkbox"] {
      accent-color: #1890ff;
      margin-right: 3px;
      vertical-align: middle;
    }
  </style>
  <!-- 高德地图 JS API -->
  <script src="https://webapi.amap.com/maps?v=2.0&key=「你的高德 apikey」"></script>
</head>
<body>
  <div id="map"></div>
  <div class="legend" id="legend"></div>
  <div id="filter-panel" class="filter-panel">
    <div class="filter-title">杭州美食指南</div>
    <div class="filter-section">
      <div class="filter-label">推荐指数</div>
      <div class="filter-recommend">
        <label><input type="checkbox" name="recommend" value="5"> 5星</label>
        <label><input type="checkbox" name="recommend" value="4"> 4星</label>
        <label><input type="checkbox" name="recommend" value="3"> 3星</label>
        <label><input type="checkbox" name="recommend" value="2"> 2星</label>
        <label><input type="checkbox" name="recommend" value="1"> 1星</label>
      </div>
    </div>
    <div class="filter-section">
      <div class="filter-label">餐厅类别</div>
      <div class="filter-category" id="filter-category-list"></div>
    </div>
  </div>
  <script>
    // 餐厅数据直接嵌入或读取同级目录下文件都可
    const data =  [
  {
    "name": "如*餐厅",
    "type": "中餐厅",
    "location": "120.122175,30.253356",
    "address": "玉泉路1*号",
    "photo": "http://store.is.autonavi.com/showpic/b08795ab6adeba25a0115d74130862aa",
    "cost": "",
    "open_time": "11:30-14:00 17:30-21:00",
    "rating": "4.7",
    "phone": "",
    "advantage": "推荐理由 xxxx",
    "recommend_index": "5",
    "category": "中餐"
  },
  {
    "name": "杭州湖四季酒店金沙厅",
    "type": "浙江菜",
    "location": "120.129637,30.251887",
    "address": "灵隐路5号杭州湖四季酒店1层(近曲院风荷)",
    "photo": "http://store.is.autonavi.com/showpic/03f435393b04359460529d3cc484e826",
    "cost": "533.00",
    "open_time": "11:30-14:00 17:30-21:00",
    "rating": "4.8",
    "phone": "",
    "advantage": "推荐理由 xxxx",
    "recommend_index": "5",
    "category": "杭帮菜/浙江菜"
  },
  {
    "name": "门没锁咖啡馆",
    "type": "咖啡馆",
    "location": "120.099355,30.297518",
    "address": "政紫弄23幢号沿街(地铁站F口步行210米)",
    "photo": "https://aos-comment.amap.com/B0KUPD8084/comment/A241830B_F0B4_4496_A253_95A1F5385C51_L0_001_1500_2000_1735672012627_76019873.jpg",
    "cost": "",
    "open_time": "10:00-17:00",
    "rating": "4.3",
    "phone": "",
    "advantage": "推荐理由 xxxx",
    "recommend_index": "4",
    "category": "咖啡馆"
  }
  {
    "name": "helloworld",
    "type": "咖啡馆",
    "location": "120.005480,30.280547",
    "address": "欧美金融城西溪丽晶居*幢底商1-13号",
    "photo": "http://store.is.autonavi.com/showpic/72efe174477c732d4264f7373dfaf768",
    "cost": "30.00",
    "open_time": "08:00-18:00",
    "rating": "4.3",
    "phone": "",
    "advantage": "推荐理由 xxxx",
    "recommend_index": "4",
    "category": "咖啡馆"
  }
];
    // 图标映射表,按最新icon.txt
    const typeIcons = {
      '辣椒': '「需要自己准备」',
      '甜点': '「需要自己准备」',
      '咖啡': '「需要自己准备」',
      '中餐': '「需要自己准备」',
      '日料': '「需要自己准备」',
      '快餐': '「需要自己准备」',
      '烤肉': '「需要自己准备」',
      '烧烤': '「需要自己准备」',
      '星星': '「需要自己准备」',
      '火锅': '「需要自己准备」',
      '牛排': '「需要自己准备」',
      '面食': '「需要自己准备」',
      '海鲜': '「需要自己准备」',
      '韩餐': '「需要自己准备」',
      '小吃': '「需要自己准备」',
      '粤菜': '「需要自己准备」',
      'default': '「需要自己准备」'
    };
    // 图例分类,按icon.txt顺序
    const legendTypes = [
      {type: '中餐', label: '中餐/杭帮菜/其他'},
      {type: '粤菜', label: '粤菜'},
      {type: '辣椒', label: '川菜'},
      {type: '火锅', label: '火锅'},
      {type: '烧烤', label: '烧烤'},
      {type: '烤肉', label: '烤肉'},
      {type: '日料', label: '日料'},
      {type: '韩餐', label: '韩餐'},
      {type: '牛排', label: '西餐'},
      {type: '面食', label: '面馆/面食'},
      {type: '小吃', label: '小吃'},
      {type: '甜点', label: '甜品/饮品'},
      {type: '咖啡', label: '咖啡馆'},
      {type: '快餐', label: '快餐'},
      {type: '海鲜', label: '海鲜'}
    ];
    // category到icon的映射规则
    function getCategoryIcon(category){
      if (!category) return typeIcons['default'];
      if (category === '中餐' || category === '杭帮菜/浙江菜' || category === '湘菜' || category === '东北菜' || category === '素食' || category === '私房菜') return typeIcons['中餐'];
      if (category === '粤菜') return typeIcons['粤菜'];
      if (category === '川菜') return typeIcons['辣椒'];
      if (category === '火锅') return typeIcons['火锅'];
      if (category === '烧烤/烤肉') return typeIcons['烤肉'];
      if (category === '烧烤') return typeIcons['烧烤'];
      if (category === '日料') return typeIcons['日料'];
      if (category === '韩餐') return typeIcons['韩餐'];
      if (category === '西餐') return typeIcons['牛排'];
      if (category === '面馆') return typeIcons['面食'];
      if (category === '小吃') return typeIcons['小吃'];
      if (category === '甜品/饮品') return typeIcons['甜点'];
      if (category === '咖啡馆') return typeIcons['咖啡'];
      if (category === '快餐') return typeIcons['快餐'];
      if (category === '海鲜') return typeIcons['海鲜'];
      return typeIcons['default'];
    }
    
    // 初始化地图
    constmap = new AMap.Map('map', {
      center: [120.15507, 30.274085], // 杭州中心
      zoom: 12
    });
    // 移除所有 data.forEach(...) 相关的marker渲染,只保留 renderMarkers() 控制
    // ... existing code ...
    // 图例渲染
    const legend = document.getElementById('legend');
    legend.innerHTML = legendTypes.map(t =>
      `<div class="legend-item"><img class='legend-icon' src='${typeIcons[t.type]}' alt='${t.label}' />${t.label}</div>`
    ).join('');
    // InfoWindow全局变量
    let infoWindow = null;
    // label样式
    const style = document.createElement('style');
    style.innerHTML = `
      .marker-label {
        font-size: 16px;
        color: #fff;
        background: rgba(34,34,34,0.75);
        border-radius: 6px;
        padding: 2px 10px;
        box-shadow: 02px 8px rgba(0,0,0,0.15);
        white-space: nowrap;
        transition: background 0.2s;
        border: none;
      }
      .marker-label-hover {
        background: rgba(34,34,34,0.95);
        color: #ffe58f;
        border: none;
      }
      /* 强制覆盖高德地图label的蓝色边框和背景 */
      .amap-marker-label {
        border: none !important;
        box-shadow: none !important;
        background: none !important;
        padding: 0 !important;
      }
    `;
    document.head.appendChild(style);
    // 动态生成类别筛选项
    const allCategories = Array.from(new Set(data.map(d => d.category).filter(Boolean)));
    const filterCategoryList = document.getElementById('filter-category-list');
    filterCategoryList.innerHTML = allCategories.map(cat =>
      `<label><input type='checkbox' name='category' value='${cat}'> ${cat}</label>`
    ).join('');
    // 默认勾选5星
    document.querySelector('input[name="recommend"][value="5"]').checked = true;


    // 筛选逻辑
    function getSelectedRecommends() {
      return Array.from(document.querySelectorAll('input[name="recommend"]:checked')).map(i => i.value);
    }
    function getSelectedCategories(){
      return Array.from(document.querySelectorAll('input[name="category"]:checked')).map(i => i.value);
    }
    let allMarkers = [];
    function renderMarkers(){
      // 清除旧marker
      allMarkers.forEach(m => m.setMap(null));
      allMarkers = [];
      const selectedRecommends = getSelectedRecommends();
      const selectedCategories = getSelectedCategories();
      let filtered = data;
      // 取交集策略:都选了才展示
      if (selectedRecommends.length) {
        filtered = filtered.filter(d => selectedRecommends.includes(d.recommend_index));
      }
      if (selectedCategories.length) {
        filtered = filtered.filter(d => selectedCategories.includes(d.category));
      }
      // 如果都没选,展示全部
      if (!selectedRecommends.length && !selectedCategories.length) {
        filtered = data;
      }
      filtered.forEach(item => {
        if (!item.location) return;
        const [lng, lat] = item.location.split(',').map(Number);
        const iconUrl = getCategoryIcon(item.category);
        const marker = new AMap.Marker({
          position: [lng, lat],
          icon: new AMap.Icon({
            image: iconUrl,
            size: new AMap.Size(32, 32),
            imageSize: new AMap.Size(32, 32)
          }),
          title: item.name,
          offset: new AMap.Pixel(-16, -36),
          label: {
            content: `<span class='marker-label'>${item.name}</span>`,
            direction: 'top',
            offset: new AMap.Pixel(0, -16),
            clickable: true
          }
        });
        marker.on('mouseover', () => marker.setLabel({ ...marker.getLabel(), content: `<span class='marker-label marker-label-hover'>${item.name}</span>` }));
        marker.on('mouseout', () => marker.setLabel({ ...marker.getLabel(), content: `<span class='marker-label'>${item.name}</span>` }));
        marker.on('click', () => showInfo(item));
        marker.on('labelClick', () => showInfo(item));
        marker.setMap(map);
        allMarkers.push(marker);
      });
    }
    // 监听筛选项变化
    document.querySelectorAll('.filter-panel input[type="checkbox"]').forEach(input => {
      input.addEventListener('change', renderMarkers);
    });
    // 初始化时渲染一次
    renderMarkers();


    function showInfo(item){
      // 关闭已有 infoWindow
      if (infoWindow) {
        infoWindow.close();
      }
      // 构建详情内容
      const content = `
        <div style="min-width:220px;max-width:320px;">
          <div style="font-size:18px;font-weight:bold;margin-bottom:6px;">${item.name}</div>
          <div style="margin-bottom:4px;">${item.address ? ' ' + item.address : ''}</div>
          ${item.photo ? `<img src="${item.photo}" style="width:100%;max-height:120px;object-fit:cover;border-radius:6px;margin-bottom:6px;">` : ''}
          <div>类型:${item.category || item.type || ''}</div>
          <div>推荐指数:${item.recommend_index || ''}</div>
          <div>评分:${item.rating || ''}</div>
          <div>人均:${item.cost ? '¥' + item.cost : ''}</div>
          <div>营业时间:${item.open_time || ''}</div>
          <div>电话:${item.phone || ''}</div>
          <div style="margin-top:4px;">${item.advantage || ''}</div>
        </div>
      `;
      // 取坐标
      const [lng, lat] = item.location.split(',').map(Number);
      infoWindow = new AMap.InfoWindow({
        content,
        offset: new AMap.Pixel(0, -36)
      });
      infoWindow.open(map, [lng, lat]);
    }


    // 点击地图空白处关闭详情弹窗
    map.on('click', function() {
      if (infoWindow) infoWindow.close();
    });
  </script>
</body>
</html>

“去哪吃”源码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>去哪吃-AI推荐餐厅</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    html, body, #map { height: 100%; margin: 0; padding: 0; }
    .amap-info-content { font-size: 14px; }
    .legend {
      position: absolute;
      top: 10px;
      left: 10px;
      background: rgba(255,255,255,0.9);
      border-radius: 6px;
      padding: 10px;
      box-shadow: 02px 8px rgba(0,0,0,0.15);
      z-index: 1000;
    }
    .legend-item { display: flex; align-items: center; margin-bottom: 6px; }
    .legend-icon { width: 20px; height: 20px; margin-right: 6px; }
    .marker-label {
      font-size: 16px;
      color: #fff;
      background: rgba(34,34,34,0.75);
      border-radius: 6px;
      padding: 2px 10px;
      box-shadow: 02px 8px rgba(0,0,0,0.15);
      white-space: nowrap;
      transition: background 0.2s;
      border: none;
    }
    .marker-label-hover {
      background: rgba(34,34,34,0.95);
      color: #ffe58f;
      border: none;
    }
    /* 强制覆盖高德地图label的蓝色边框和背景 */
    .amap-marker-label {
      border: none !important;
      box-shadow: none !important;
      background: none !important;
      padding: 0 !important;
    }
    .filter-panel {
      position: absolute;
      top: 40px;
      right: 20px;
      width: 220px;
      background: rgba(255,255,255,0.97);
      border-radius: 10px;
      box-shadow: 02px 16px rgba(0,0,0,0.13);
      padding: 18px 18px 12px 18px;
      z-index: 1200;
      font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
      font-size: 15px;
      color: #222;
      transition: box-shadow 0.2s;
      max-height: 600px;
      overflow-y: auto;
    }
    .filter-title {
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 12px;
      letter-spacing: 2px;
    }
    .filter-section { margin-bottom: 14px; }
    .filter-label { font-size: 14px; margin-bottom: 6px; color: #666; }
    .filter-recommend label, .filter-category label {
      display: inline-block;
      margin-right: 10px;
      margin-bottom: 6px;
      cursor: pointer;
      font-size: 14px;
    }
    .filter-category {
      max-height: 280px;
      overflow-y: auto;
      border-radius: 6px;
      background: #f7f7f7;
      padding: 6px 4px 4px 8px;
      border: 1px solid #eee;
    }
    .filter-category label { width: 120px; }
    .filter-panel input[type="checkbox"] {
      accent-color: #1890ff;
      margin-right: 3px;
      vertical-align: middle;
    }
    .detail-panel {
      position: absolute;
      top: 40px;
      right: 20px;
      width: 400px;
      background: rgba(255,255,255,0.98);
      border-radius: 14px;
      box-shadow: 04px 32px rgba(0,0,0,0.13);
      padding: 28px 28px 18px 28px;
      z-index: 1200;
      font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
      font-size: 16px;
      color: #222;
      max-height: 80vh;
      overflow-y: auto;
      transition: box-shadow 0.2s;
      display: none;
    }
    .detail-title {
      font-size: 22px;
      font-weight: bold;
      margin-bottom: 10px;
      color: #1890ff;
      letter-spacing: 1px;
    }
    .detail-recommend {
      background: #fffbe6;
      color: #faad14;
      border-radius: 8px;
      padding: 8px 14px;
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 16px;
      box-shadow: 02px 8px rgba(250,173,20,0.08);
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .detail-photo {
      width: 100%;
      max-height: 180px;
      object-fit: cover;
      border-radius: 8px;
      margin-bottom: 10px;
      box-shadow: 02px 8px rgba(0,0,0,0.08);
    }
    .detail-field { margin-bottom: 8px; }
    .detail-label { color: #888; margin-right: 6px; }
    .detail-analysis {
      background: #f6ffed;
      color: #389e0d;
      border-radius: 8px;
      padding: 10px 14px;
      margin-top: 12px;
      font-size: 15px;
      box-shadow: 02px 8px rgba(56,158,13,0.06);
    }
    .detail-panel-title {
      font-size: 20px;
      font-weight: bold;
      text-align: center;
      margin-bottom: 18px;
      color: #222;
      letter-spacing: 1px;
    }
  </style>
  <!-- 高德地图 JS API -->
  <script src="https://webapi.amap.com/maps?v=2.0&key=「你的高德 apikey」"></script>
</head>
<body>
  <div id="map"></div>
  <div id="detail-panel" class="detail-panel"></div>
  <script>
    // 直接嵌入餐厅数据
    const data = [
      {
        "name": "杭州市阿里巴巴滨江园区(出发点)",
        "type": "出发点",
        "location": "120.190371,30.189600",
        "address": "杭州市滨江区网商路6号",
        "photo": "https://gw.alicdn.com/imgextra/i2/O1CN014DVQlb1oVqsnsrbiT_!!6000000005231-2-tps-64-64.png",
        "cost": "-",
        "open_time": "-",
        "rating": "-",
        "phone": "-",
        "advantage": "公司园区,交通便利,地铁直达,周边配套齐全。",
        "recommend_index": "-",
        "category": "出发点",
        "is_origin": true
      },
      {
        "name": "海*捞火锅(中南乐游城店)",
        "type": "火锅",
        "location": "120.190309,30.193994",
        "address": "江南大道10号",
        "photo": "https://aos-comment.amap.com/B0GU9MT4N7/comment/6845185a8eedb6301996b74240ba088b_2048_2048_80.jpg",
        "cost": "80.00",
        "open_time": "09:00-07:00",
        "rating": "4.7",
        "phone": "-",
        "advantage": "服务优质,环境舒适,适合聚餐。",
        "recommend_index": "5",
        "category": "火锅",
        "distance_m": 2030,
        "duration_s": 459,
        "traffic": "驾车约8分钟,公交便利,地铁近。",
        "surrounding": "中南乐游城、知嘛家等商场,生活便利。",
        "analysis": "距离适中,交通便利,配套丰富,适合团队聚餐。"
      },
      {
        "name": "黄*海鲜大排档(滨江店)",
        "type": "海鲜",
        "location": "120.195922,30.197039",
        "address": "浙商发展大厦*座1-2楼(地铁站G口)",
        "photo": "https://aos-comment.amap.com/B0J1B5R0EY/comment/0e8ed310fa3bb3135ddbefe27244562f_2048_2048_80.jpg",
        "cost": "139.00",
        "open_time": "11:00-15:30 17:30-02:30",
        "rating": "4.7",
        "phone": "-",
        "advantage": "菜品新鲜,适合多人聚餐,夜宵丰富。",
        "recommend_index": "5",
        "category": "海鲜",
        "distance_m": 2395,
        "duration_s": 478,
        "traffic": "驾车约8分钟,地铁直达,公交便利。",
        "surrounding": "知嘛家等商场,夜生活丰富。",
        "analysis": "交通便利,夜宵丰富,适合下班聚会。"
      },
      {
        "name": "群*饭店(信诚路店)",
        "type": "中餐",
        "location": "120.174707,30.183901",
        "address": "滨安路11号(近信诚路)",
        "photo": "http://store.is.autonavi.com/showpic/2ac9e95efffe6e27a8eda43ef8271134",
        "cost": "66.00",
        "open_time": "11:00-13:30 16:00-20:30",
        "rating": "4.7",
        "phone": "-",
        "advantage": "家常菜,性价比高,适合日常用餐。",
        "recommend_index": "4",
        "category": "中餐",
        "distance_m": 2797,
        "duration_s": 757,
        "traffic": "驾车约13分钟,公交可达。",
        "surrounding": "周边无大型商场,环境安静。",
        "analysis": "性价比高,适合小团队日常聚餐。"
      },
      {
        "name": "龙*·滨江烤全羊(杭州总店)",
        "type": "烤肉",
        "location": "120.195890,30.184231",
        "address": "长河路47*号和瑞科技广场S*幢楼下",
        "photo": "http://store.is.autonavi.com/showpic/45d72ae00c69cf56d4fe514b1346c55c",
        "cost": "132.00",
        "open_time": "11:00-21:00",
        "rating": "4.8",
        "phone": "-",
        "advantage": "烤全羊特色,适合聚会,环境宽敞。",
        "recommend_index": "5",
        "category": "烤肉",
        "distance_m": 1226,
        "duration_s": 254,
        "traffic": "驾车约4分钟,公交可达。",
        "surrounding": "保利·时光里等商场,配套齐全。",
        "analysis": "距离最近,评分最高,配套好,强烈推荐。"
      },
      {
        "name": "东*方(杭州保利时光里店)",
        "type": "中餐",
        "location": "120.197088,30.182094",
        "address": "长河街道长河路与滨康路交叉口保利时光里*楼13号楼2017商铺",
        "photo": "http://store.is.autonavi.com/query_pic?id=st1ef630c8-39ef-4d63-93ce-ba3a9732851e&user=search&operate=original",
        "cost": "-",
        "open_time": "-",
        "rating": "4.8",
        "phone": "-",
        "advantage": "环境优雅,适合家庭聚餐。",
        "recommend_index": "5",
        "category": "中餐",
        "distance_m": 2088,
        "duration_s": 491,
        "traffic": "驾车约8分钟,公交便利。",
        "surrounding": "保利·时光里等商场,生活便利。",
        "analysis": "评分高,环境好,适合家庭和小型聚会。"
      }
    ];
    // 图标映射表
    const typeIcons = {
      '辣椒': '「需要自己准备」',
      '甜点': '「需要自己准备」',
      '咖啡': '「需要自己准备」',
      '中餐': '「需要自己准备」',
      '日料': '「需要自己准备」',
      '快餐': '「需要自己准备」',
      '烤肉': '「需要自己准备」',
      '烧烤': '「需要自己准备」',
      '星星': '「需要自己准备」',
      '火锅': '「需要自己准备」',
      '牛排': '「需要自己准备」',
      '面食': '「需要自己准备」',
      '海鲜': '「需要自己准备」',
      '韩餐': '「需要自己准备」',
      '小吃': '「需要自己准备」',
      '粤菜': '「需要自己准备」',
      'default': '「需要自己准备」'
    };
    function getCategoryIcon(category){
      if (!category) return typeIcons['default'];
      if (category === '出发点') return typeIcons['星星'];
      if (category === '中餐' || category === '杭帮菜/浙江菜' || category === '湘菜' || category === '东北菜' || category === '素食' || category === '私房菜') return typeIcons['中餐'];
      if (category === '粤菜') return typeIcons['粤菜'];
      if (category === '川菜') return typeIcons['辣椒'];
      if (category === '火锅') return typeIcons['火锅'];
      if (category === '烧烤/烤肉' || category === '烤肉') return typeIcons['烤肉'];
      if (category === '烧烤') return typeIcons['烧烤'];
      if (category === '日料') return typeIcons['日料'];
      if (category === '韩餐') return typeIcons['韩餐'];
      if (category === '西餐') return typeIcons['牛排'];
      if (category === '面馆') return typeIcons['面食'];
      if (category === '小吃') return typeIcons['小吃'];
      if (category === '甜品/饮品') return typeIcons['甜点'];
      if (category === '咖啡馆') return typeIcons['咖啡'];
      if (category === '快餐') return typeIcons['快餐'];
      if (category === '海鲜') return typeIcons['海鲜'];
      return typeIcons['default'];
    }
    // 初始化地图
    constmap = new AMap.Map('map', {
      center: [120.190371, 30.189600],
      zoom: 14
    });
    let allMarkers = [];
    let allLines = [];
    function renderMarkers(){
      // 清除旧marker和线
      allMarkers.forEach(m => m.setMap(null));
      allMarkers = [];
      allLines.forEach(l => l.setMap(null));
      allLines = [];
      if (!data.length) return;
      // 出发点
      const origin = data.find(d => d.is_origin);
      // 餐厅
      const restaurants = data.filter(d => !d.is_origin);
      // 出发点marker
      if (origin) {
        const [lng, lat] = origin.location.split(',').map(Number);
        const marker = new AMap.Marker({
          position: [lng, lat],
          icon: new AMap.Icon({
            image: getCategoryIcon('出发点'),
            size: new AMap.Size(40, 40),
            imageSize: new AMap.Size(40, 40)
          }),
          title: origin.name,
          offset: new AMap.Pixel(-20, -40),
          label: {
            content: `<span class='marker-label'>${origin.name}</span>`,
            direction: 'top',
            offset: new AMap.Pixel(0, -20),
            clickable: true
          }
        });
        marker.setMap(map);
        allMarkers.push(marker);
      }
      // 餐厅marker和连线
      restaurants.forEach(item => {
        if (!item.location) return;
        const [lng, lat] = item.location.split(',').map(Number);
        const iconUrl = getCategoryIcon(item.category);
        const marker = new AMap.Marker({
          position: [lng, lat],
          icon: new AMap.Icon({
            image: iconUrl,
            size: new AMap.Size(32, 32),
            imageSize: new AMap.Size(32, 32)
          }),
          title: item.name,
          offset: new AMap.Pixel(-16, -36),
          label: {
            content: `<span class='marker-label'>${item.name}</span>`,
            direction: 'top',
            offset: new AMap.Pixel(0, -16),
            clickable: true
          }
        });
        marker.on('mouseover', () => marker.setLabel({ ...marker.getLabel(), content: `<span class='marker-label marker-label-hover'>${item.name}</span>` }));
        marker.on('mouseout', () => marker.setLabel({ ...marker.getLabel(), content: `<span class='marker-label'>${item.name}</span>` }));
        marker.on('click', () => showInfo(item));
        marker.on('labelClick', () => showInfo(item));
        marker.setMap(map);
        allMarkers.push(marker);
        // 连线
        if (origin && origin.location) {
          const [olng, olat] = origin.location.split(',').map(Number);
          const line = new AMap.Polyline({
            path: [ [olng, olat], [lng, lat] ],
            strokeColor: '#1890ff',
            strokeWeight: 4,
            strokeOpacity: 0.7,
            isOutline: true,
            outlineColor: '#fff'
          });
          line.setMap(map);
          allLines.push(line);
        }
      });
    }
    // 智能推荐(评分最高且距离最近)
    function showRecommend(){
      if (!data.length) return;
      const restaurants = data.filter(d => !d.is_origin);
      // 评分优先,距离次之
      let best = restaurants[0];
      restaurants.forEach(r => {
        if (
          Number(r.rating) > Number(best.rating) ||
          (r.rating === best.rating && r.distance_m < best.distance_m)
        ) {
          best = r;
        }
      });
      showInfo(best, true);
    }
    // 右侧详情面板
    function showInfo(item, isRecommend = false){
      const panel = document.getElementById('detail-panel');
      if (!item) { panel.style.display = 'none'; return; }
      let html = '';
      html += `<div class='detail-panel-title'>AI推荐餐厅</div>`;
      if (isRecommend) {
        html += `<div class='detail-recommend'> 智能推荐:<span>${item.name}</span></div>`;
      }
      html += `<div class='detail-title'>${item.name}</div>`;
      if (item.photo) html += `<img class='detail-photo' src='${item.photo}' alt='${item.name}'>`;
      html += `<div class='detail-field'><span class='detail-label'>类型:</span>${item.type || ''}</div>`;
      html += `<div class='detail-field'><span class='detail-label'>地址:</span>${item.address || ''}</div>`;
      if (item.distance_m) html += `<div class='detail-field'><span class='detail-label'>距离:</span>${(item.distance_m/1000).toFixed(2)} km</div>`;
      if (item.duration_s) html += `<div class='detail-field'><span class='detail-label'>车程:</span>${Math.round(item.duration_s/60)} 分钟</div>`;
      if (item.cost && item.cost !== '-') html += `<div class='detail-field'><span class='detail-label'>人均:</span>¥${item.cost}</div>`;
      if (item.open_time && item.open_time !== '-') html += `<div class='detail-field'><span class='detail-label'>营业时间:</span>${item.open_time}</div>`;
      if (item.rating && item.rating !== '-') html += `<div class='detail-field'><span class='detail-label'>评分:</span>${item.rating}</div>`;
      if (item.phone && item.phone !== '-') html += `<div class='detail-field'><span class='detail-label'>电话:</span>${item.phone}</div>`;
      if (item.traffic) html += `<div class='detail-field'><span class='detail-label'>交通:</span>${item.traffic}</div>`;
      if (item.surrounding) html += `<div class='detail-field'><span class='detail-label'>周边配套:</span>${item.surrounding}</div>`;
      if (item.advantage) html += `<div class='detail-field'><span class='detail-label'>优点:</span>${item.advantage}</div>`;
      if (item.analysis) html += `<div class='detail-analysis'>${item.analysis}</div>`;
      panel.innerHTML = html;
      panel.style.display = 'block';
    }
    // 地图点击空白关闭详情
    map.on('click', function() {
      document.getElementById('detail-panel').style.display = 'none';
    });
    renderMarkers();
    showRecommend();
  </script>
</body>
</html>

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/138140.html

(0)
上一篇 17分钟前
下一篇 2分钟前

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们YX

mu99908888

在线咨询: 微信交谈

邮件:itzsgw@126.com

工作时间:时刻准备着!

关注微信