diff --git a/guide.md b/guide.md new file mode 100644 index 0000000..f189dd1 --- /dev/null +++ b/guide.md @@ -0,0 +1,319 @@ +已根据你的要求,将 Web 端技术栈从 Next.js 调整为 Vue。以下是更新后的完整规划文档,修改部分已用 **【修订标记】** 标注。 + +--- + +# OpenCloud 产品规划文档 + +**版本**:1.1 +**日期**:2026年5月20日 +**作者**:OpenCloud 产品团队 + +--- + +## 目录 +1. [项目概述](#1-项目概述) +2. [市场与用户分析](#2-市场与用户分析) +3. [竞品分析](#3-竞品分析) +4. [产品定位与差异化](#4-产品定位与差异化) +5. [核心功能模块设计](#5-核心功能模块设计) +6. [商业模式](#6-商业模式) +7. [技术架构与选型](#7-技术架构与选型) +8. [实施路线图](#8-实施路线图) +9. [风险与应对策略](#9-风险与应对策略) +10. [结语](#10-结语) + +--- + +## 1. 项目概述 + +### 1.1 愿景 +**成为全球天空爱好者共同绘制、探索与收藏的“活的天空地图”。** + +### 1.2 使命 +通过AI识别、实时地理映射与轻量社交,让每一次仰望天空都变成一次有意义的发现与连接。 + +### 1.3 产品简介 +OpenCloud 是一款面向云朵爱好者、气象爱好者及摄影爱好者的跨平台应用(Web + 移动端)。它整合了**实时云图地图**、**游戏化云朵图鉴**与**轻量社区**三大核心模块,让用户可以随时拍摄天空、自动识别云的类型,将作品实时呈现在世界地图上,同时像收集精灵一样解锁各类云朵成就,并与全球同好温和互动。 + +--- + +## 2. 市场与用户分析 + +### 2.1 市场背景 +- **兴趣社群成熟**:国际赏云协会(Cloud Appreciation Society)拥有近 6 万付费会员,证明云朵主题具有激发付费意愿的情感基础。 +- **视觉社交需求**:Instagram 上 #clouds 标签有上亿帖子,小红书、抖音上云朵内容持续火爆,但缺乏垂直整合拍摄、识别、社区的专业工具。 +- **AI 视觉技术成熟**:以 GPT-4o 为代表的视觉模型使低成本的实时云朵分类成为可能,用户可以零学习成本获得专业级气象信息。 + +### 2.2 目标用户画像 +- **核心用户**:云朵摄影爱好者、赏云协会成员、气象爱好者。 +- **潜力用户**:喜欢记录日常的普通人、追求新奇 App 的年轻群体、教育工作者(用于自然教学)。 +- **需求特征**:渴望被识别、收藏、展示自己的天空作品;喜欢地理发现和轻量收集游戏;对深度社交压力敏感。 + +### 2.3 用户痛点与需求 +| 痛点 | OpenCloud 解决方案 | +|------|---------------------| +| 拍了云不知道它叫什么 | AI 一键识别,附带气象知识 | +| 云图分享缺乏专属平台 | 主题化社区,无算法噪音 | +| 记录零散,缺乏成就感 | 游戏化图鉴,解锁稀有成就 | +| 看不到全球此刻的天空 | 实时云图地图,感受世界脉动 | +| 害怕隐私泄露 | 多层模糊化处理,隐身模式 | + +--- + +## 3. 竞品分析 + +### 3.1 核心竞品 +| 产品 | 特点 | 差距与机会 | +|------|------|-------------| +| **CloudSpotter** | 赏云协会官方 App,图鉴+社区 | 无实时地图,AI 识别为后期添加,互动偏学术 | +| **See My Clouds** | 云朵版 Instagram,个人画廊 | 无 AI 识别,无地图,纯粹社交 | +| **Cloud Point** | AI 识别 + 学习 + 社区 | 功能较全,但创新深度不足,无地图实时性 | +| **每日一云小程序** | 国内头部云主题小程序 | 依托微信,功能受限,无独立 App 体验 | + +### 3.2 间接与替代品 +- **AI 云识别工具**:Atmosphere、云识别扫描器 —— 单点功能,无社区。 +- **天气应用**:Your Weather、Poweather —— 天气数据为主,缺少用户生成内容。 +- **通用社交平台**:Instagram、小红书 —— 内容海量但无结构化识别与收集。 + +### 3.3 竞争优势总结 +OpenCloud 是首个将 **实时地图、游戏化图鉴、轻社交** 深度融合的产品,形成“拍摄→识别→地图浮现→收藏→社区分享→再次出发”的闭环体验。竞品多在某一模块有所建树,但缺乏完整闭环。 + +--- + +## 4. 产品定位与差异化 + +### 4.1 核心价值主张 +**“你拍的每一朵云,都将在这张世界地图上活起来。”** +- **实时性**:云不再是一张静态照片,而是此刻天空的脉动。 +- **专业性**:AI 精准识别 + 气象数据加持,让普通用户触及科学之美。 +- **游戏性**:图鉴系统提供长期激励,消除低频兴趣的留存难题。 +- **温暖感**:轻社交设计,远离评论压力,仅保留对天空的共同赞叹。 + +### 4.2 差异化特征 +- **天空实时仪表盘**:结合天气图层与用户上传的实时云标,形成预测-拍摄-验证的主动探索体验。 +- **分层云朵图鉴**:基础属、变种、稀有度、环境成就四维收集体系,深度远超简单的类型列表。 +- **隐私优先的地图设计**:默认模糊定位+隐身模式,在酷炫与安全间取得平衡。 +- **跨平台一致体验**:Web 端浏览与传播,移动端拍摄与即时互动,后台统一管理。 + +--- + +## 5. 核心功能模块设计 + +### 5.1 总体架构 +``` + ┌──────────────────────────────────┐ + │ OpenCloud 平台 │ + └──────────────────────────────────┘ + │ │ │ + ┌─────────┼──────────┼──────────┼─────────┐ + ▼ ▼ ▼ ▼ ▼ + 实时地图 云朵图鉴 轻量社区 用户系统 AI引擎 +``` + +### 5.2 模块一:实时云图地图 +**核心目标**:让用户每次打开都能感受到“此刻的天空在发生什么”。 + +#### 功能点: +- **实时浮现**:最近30分钟内上传的云以动画淡入,2小时后半透明,24小时后默认移出主视图(可手动开启历史层)。 +- **天气图层叠加**:显示云层覆盖、降水区域,结合用户上传形成“追云”预期。 +- **定位模糊与隐身**: + - 默认坐标精度约1km(小数点后2位) + - 用户可选择精确(100m)或仅城市级别 + - 隐身模式:上传但地图不显示位置,仅存在于个人图鉴 +- **卡片交互**:点击云图标弹出精致卡片(缩略图、云类型、拍摄者、时间、天气简述),并提供「看同类云」「看这里其他云」快捷操作。 +- **愿望清单联动**:在地图上看到稀有云,可一键加入“我想拍这个”清单,附近出现时推送通知。 + +#### 隐私策略: +- 绝对不显示可识别个人住址的标记。 +- 多张照片的位置元数据不会组合推断轨迹(服务器端即时模糊后丢弃原始精确坐标)。 +- 符合 GDPR 及《个人信息保护法》要求。 + +### 5.3 模块二:游戏化云朵图鉴 +**核心目标**:将一次性新奇转化为有深度、有进度的收集游戏,提升长期留存。 + +#### 收集体系: +- **基础云属**(10 种):积云、层云、卷云、积雨云等。 +- **变种与特征**:乳状云、波状云、雨幡洞、晕、虹彩云等。 +- **稀有度分级**: + - 常见(积云、层云) + - 少见(荚状云) + - 罕见(贝母云、夜光云) +- **环境成就**:黄金时刻云、暴风雨前云、高海拔云(>3000m)、跨赤道云等。 + +#### AI 识别与验证: +- 拍摄后自动调用 OpenAI Vision(或后续自研模型)识别,返回云型与置信度。 +- 用户可修正类型;稀有云型提交后进入“待验证”队列,由其他资深用户或管理员投票验证,通过后获得金框认证。 + +#### 激励与视觉呈现: +- 每解锁一项获得一枚精致云形徽章,可生成卡片分享至社交媒体。 +- 图鉴内附带手绘风格气象小知识,成为一本可阅读的“云朵百科全书”。 +- 愿望清单功能:用户标记未解锁云型,地图附近出现或天气条件有利时推送通知。 + +### 5.4 模块三:轻量社交社区 +**核心目标**:提供温暖、低负担的交流空间,不追求高互动频率,但求每次浏览都有美感体验。 + +#### 每日天空精选(The Daily Sky) +- 每日由编辑/算法从全球选出 9-12 张云图拼成“今日天空拼图”,作为社区首页。 +- 浏览体验类似翻日历,拒绝无限信息流带来的疲劳感。 + +#### 微频道(基于云型) +- 每个云型自动生成频道:积云频道、卷云频道、乳状云频道……内容自动归入。 +- 避免用户自建话题冷场,保证每个频道总有内容。 + +#### 情绪反应代替文字评论 +- 提供6种与天空共情的情绪:✨震撼、🌿宁静、🌧忧伤、🔥热烈、💭梦幻、🫂温暖。 +- 文字评论保留但非主要引导方式,降低互动门槛。 + +#### 个人天空日志 +- 每位用户拥有个人主页,按时间线展示其云图,形成天空日记。 +- 允许“订阅”其他拍摄者,获得新上传推送,但不公开关注数,避免社交压力。 + +#### 共同观察事件 +- 定期发起主题挑战(如“本周最奇特的云”)。 +- 特殊天象发生时推送区域用户,集体拍摄,形成同一时刻的天空共鸣。 + +### 5.5 模块四:用户系统与内容审核 + +#### 用户注册登录 +- 支持邮箱注册、Google/Apple 第三方登录。 +- 使用 Supabase Auth 实现,与数据库 RLS 深度集成。 + +#### 审核流程 +1. **自动审核**:上传后由 OpenAI Vision 同时完成云型识别与违规内容检测(色情、暴力、人脸、车牌等)。 +2. **人工兜底**:标记低置信度或疑似违规内容进入管理后台待审队列。 +3. **社区举报**:允许用户举报不当内容,补充审核闭环。 +4. **审核结果**: + - 通过:标记为 `approved`,公开可见。 + - 拒绝:标记为 `rejected`,仅自己可见或自动删除原图。 + +#### 后台管理(Web 端) +- 管理员通过 Web 端专用路由(如 `/admin`)访问,基于用户角色鉴权。 + **【修订】**:Web 端使用 **Vue 3 + Vue Router** 构建,管理后台作为受路由守卫保护的一组页面。 +- 功能包括:待审核队列操作、用户管理(禁用/恢复)、数据统计仪表盘、云型标准库维护。 + +--- + +## 6. 商业模式 + +**基础免费 + 增值订阅 + 周边衍生** + +### 收入来源: +1. **会员订阅(核心)** + - 免费版:基础识别、普通图鉴、带水印地图、广告支持。 + - Pro 版(月/年费):高精度识别、去水印高清原图、稀有云型验证优先权、天气预测提醒、不限容量的云朵存储空间。 +2. **虚拟商品** + - 特殊徽章外框、主题皮肤、图鉴装饰。 +3. **联名与周边** + - 与赏云协会、气象机构合作推出实体云图日历、图册。 + - 定制天气周边(如“今日云朵”明信片)电商导流。 +4. **企业级 API** + - 向教育、科研、天气 App 提供云朵识别 API(后期)。 + +### 定价参考 +- 参考赏云协会年费约 $5,提供更丰富数字体验,Pro 订阅可定价 $2.99/月 或 $19.99/年。 + +--- + +## 7. 技术架构与选型 + +### 7.1 整体架构 【修订】 +``` +客户端:React Native (移动端) + Vue 3 (Web/管理后台) + │ + ├── 实时通信:Supabase Realtime (WebSocket) + ├── 数据库查询:Supabase JS SDK + └── 文件上传:Supabase Storage (S3) + │ +后端服务:Supabase (托管 PostgreSQL, Auth, Storage, Edge Functions) + │ + ├── AI 审核与识别:OpenAI GPT-4o Vision (初期) + └── 天气数据:OpenWeatherMap API + │ +部署:Vercel / Netlify (Web) + Expo EAS (App) + Supabase Cloud +``` + +### 7.2 核心选型理由 【修订】 +| 组件 | 技术 | 理由 | +|------|------|------| +| **移动端** | React Native + Expo | 成熟的跨平台移动框架,与 Vue 共享 TypeScript 业务逻辑与 Supabase 客户端 | +| **Web 前端** | **Vue 3** + **Vite** + **Tailwind CSS** | 按团队技术偏好选择,轻量且高效。如后期需要 SEO,可平滑迁移至 **Nuxt 3**(基于 Vue 的服务端渲染框架)。 | +| **Web 路由与状态管理** | Vue Router + Pinia | 官方配套,管理页面路由与全局状态 | +| **管理后台** | 同 Web 端 Vue 项目,使用路由守卫区分权限 | 无额外学习成本,与用户端共享组件和 API 层 | +| **后端服务** | Supabase | 提供即时认证、REST API、实时订阅、存储,省去大量后端开发 | +| **数据库** | PostgreSQL (Supabase内置) | 成熟的空间查询支持 (PostGIS),RLS 精细权限控制 | +| **AI 识别** | OpenAI GPT-4o-mini (Vision) | 按量付费,每图约$0.002,可同时完成识别和合规检查 | +| **地图** | react-native-maps (App) / Leaflet 或 Mapbox (Web) | 移动端原生性能,Web端灵活 | +| **天气数据** | OpenWeatherMap | 免费套餐可满足初期需求 | +| **代码共享** | 共享 Supabase 客户端配置、TypeScript 类型定义、业务工具函数 | 通过 monorepo 或 npm 私有包在不同平台间复用逻辑层 | + +### 7.3 数据隐私与合规 +- 所有用户上传图片默认通过 Supabase Storage 存储,开启自动模糊化处理(需自行实现或调用第三方)。 +- 坐标在服务器端舍入后丢弃原始精确定位。 +- 使用 Row Level Security 确保用户只能访问自己被允许的数据。 + +--- + +## 8. 实施路线图 + +### 阶段 1:MVP(核心闭环,预计 6-8 周) +**目标**:验证核心价值——“拍云→识别→地图浮现→图鉴收集”。 + +- **Web 端(Vue 3)**: + - 用户注册登录(Supabase Auth) + - 云图上传(手动选类型+拍摄时间),调用 OpenAI 识别 + - 基础画廊:按类型筛选浏览 + - 极简管理后台:查看/审核图片 +- **移动端(App)**: + - 拍照/相册选取,自动获取位置 + - 展示识别结果,支持手动修正 + - 上传后在基础地图上显示(刷新可见,非实时) +- **后端**: + - Supabase 表结构搭建(users, clouds, cloud_types) + - 审核 Edge Function 实现 + +### 阶段 2:社交与地图增强(预计 8-12 周) +**目标**:打造“实时地图”与“轻社区”,强化留存。 + +- 地图实时浮现(Supabase Realtime 订阅) +- 天气图层叠加 +- 游戏化图鉴系统(基础属+变种,稀有度,徽章分享) +- 每日天空精选与云型微频道 +- 情绪反应互动系统 +- 个人主页与订阅 +- 推送通知(附近稀有云、天气提醒) + +### 阶段 3:AI 优化与商业化(长期迭代) +**目标**:建立壁垒,实现正向营收。 + +- 收集用户反馈数据,训练自有云朵分类模型(基于 CloudSEN12+ 等数据集微调) +- 上线 Pro 会员订阅 +- 多语言支持,全球化运营 +- 对外提供识别 API +- 与赏云协会等建立品牌合作 + +--- + +## 9. 风险与应对策略 + +| 风险 | 影响 | 应对策略 | +|------|------|----------| +| **冷启动内容匮乏** | 新用户流失 | 种子内容计划:引入 Unsplash 等免版权云图并标注;邀请早期核心用户内测生产内容 | +| **用户使用频次低** | DAU 低迷 | 图鉴愿望清单+天气预测推送,变被动等待为主动引导 | +| **AI 识别准确率不足** | 用户信任崩塌 | 允许用户修正并作为训练数据;稀有云型增加人工验证机制,维护专业形象 | +| **隐私与合规问题** | 法律风险、口碑危机 | 地图默认模糊定位,提供隐身模式;严格数据丢弃策略;尽早获取法律意见 | +| **审核遗漏违规内容** | 应用商店下架、法律纠纷 | 多层审核:AI 初筛→人工复核→社区举报;敏感内容立即隐藏 | +| **大平台竞争** | 用户流失 | 坚持垂直深度体验,不与通用平台比拼内容量;以“专属图鉴”和“天空日记”沉淀用户数字资产,提高迁移成本 | +| **商业变现困难** | 无法持续运营 | 初期靠付费订阅覆盖边际成本;后期拓展教育与天气衍生价值,控制团队规模,保持精益 | + +--- + +## 10. 结语 + +OpenCloud 不仅仅是一个云朵识别工具,它是一次将自然观察、科技美学与人文连接相结合的尝试。在注意力被无限撕扯的时代,我们希望为人们保留一小块仰望天空的空间——在那里,每一朵云都值得被看见、被理解、被世界记住。 + +本规划文档定义了产品的方向、骨架与演进路径。接下来,我们将基于此进行最小可行性版本的敏捷开发,用真实用户反馈打磨每一个呼吸的角落。 + +--- + +*天空无界,探索不止。* +**OpenCloud 团队** \ No newline at end of file diff --git a/plan.md b/plan.md new file mode 100644 index 0000000..723364c --- /dev/null +++ b/plan.md @@ -0,0 +1,959 @@ +# OpenCloud Web MVP — 详细实施方案 + +**版本**:1.0 +**日期**:2026年5月20日 +**技术栈**:Vue 3 + Vite + Tailwind CSS + Vue Router + Pinia + Supabase + 高德地图 JS API 2.0 + +--- + +## 目录 + +1. [关键决策](#1-关键决策) +2. [技术栈与依赖清单](#2-技术栈与依赖清单) +3. [目录结构](#3-目录结构) +4. [阶段 0:基础设施搭建](#4-阶段-0基础设施搭建) +5. [阶段 1:用户认证](#5-阶段-1用户认证) +6. [阶段 2:云图上传](#6-阶段-2云图上传) +7. [阶段 3:高德 3D 云图地图](#7-阶段-3高德-3d-云图地图) +8. [阶段 4:图鉴系统](#8-阶段-4图鉴系统) +9. [阶段 5:社区 + 管理后台 + 收尾](#9-阶段-5社区--管理后台--收尾) +10. [数据库设计总览](#10-数据库设计总览) +11. [环境变量清单](#11-环境变量清单) +12. [风险与注意事项](#12-风险与注意事项) + +--- + +## 1. 关键决策 + +| 决策项 | 选择 | 理由 | +|--------|------|------| +| 地图库 | **高德地图 JS API 2.0** | 国内数据准确,3D/卫星/视角切换,免费额度充足 | +| 实时性 | MVP **跳过 Supabase Realtime** | 刷新加载代替,降低复杂度,快速跑通闭环 | +| 认证方式 | MVP **仅邮箱密码** | OAuth 配置复杂,留到后续阶段 | +| AI 识别 | MVP **暂不接入** | 先完成手动选型上传闭环,降低开发复杂度;后续接入 OpenAI Vision | +| 目标用户 | 国内用户为主 | 高德地图优先;若后续全球化需接入 Mapbox 双引擎 | + +--- + +## 2. 技术栈与依赖清单 + +### 生产依赖 + +| 包名 | 用途 | +|------|------| +| `vue` | 核心框架(已安装 ^3.5.34) | +| `vue-router` | SPA 路由管理 | +| `pinia` | 全局状态管理 | +| `@supabase/supabase-js` | Supabase 客户端(已安装 ^2.106.1) | +| `@amap/amap-jsapi-loader` | 高德地图 JS API 官方加载器 | + +### 开发依赖 + +| 包名 | 用途 | +|------|------| +| `tailwindcss` | 原子化 CSS 框架 | +| `@tailwindcss/vite` | Tailwind Vite 插件 | +| `vite` | 构建工具(已安装 ^8.0.12) | +| `@vitejs/plugin-vue` | Vue Vite 插件(已安装 ^6.0.6) | +| `typescript` | 类型系统(已安装 ~6.0.2) | +| `vue-tsc` | Vue TypeScript 类型检查(已安装 ^3.2.8) | +| `@types/node` | Node.js 类型定义(已安装 ^24.12.3) | + +--- + +## 3. 目录结构 + +``` +src/ +├── assets/ # 静态资源(图标、图片) +├── components/ # 通用组件 +│ ├── layout/ # 布局组件 (AppHeader, AppFooter, AppSidebar) +│ ├── cloud/ # 云朵相关组件 (CloudCard, CloudMarker, CloudBadge) +│ ├── map/ # 地图相关组件 (MapContainer, MapPopup) +│ └── ui/ # 基础 UI 组件 (Button, Input, Modal, Badge) +├── composables/ # 组合式函数 +│ ├── useAuth.ts # 认证逻辑 +│ ├── useClouds.ts # 云图数据操作 +│ └── useMap.ts # 地图逻辑封装 +├── lib/ # 第三方库初始化 +│ ├── supabase.ts # Supabase 客户端 +│ └── amap.ts # 高德地图加载器封装 +├── router/ # 路由配置 +│ └── index.ts # 路由表 + 导航守卫 +├── stores/ # Pinia stores +│ ├── auth.ts # 用户认证状态 +│ ├── clouds.ts # 云图数据状态 +│ └── encyclopedia.ts # 图鉴数据状态 +├── types/ # TypeScript 类型定义 +│ ├── database.ts # Supabase 数据库类型 +│ ├── cloud.ts # 云朵相关类型 +│ └── user.ts # 用户相关类型 +├── views/ # 页面组件 +│ ├── auth/ +│ │ ├── LoginView.vue +│ │ └── RegisterView.vue +│ ├── map/ +│ │ └── MapView.vue +│ ├── upload/ +│ │ └── UploadView.vue +│ ├── encyclopedia/ +│ │ ├── EncyclopediaView.vue +│ │ └── CloudTypeView.vue +│ ├── gallery/ +│ │ └── GalleryView.vue +│ ├── profile/ +│ │ └── ProfileView.vue +│ └── admin/ +│ ├── AdminView.vue +│ └── ReviewQueue.vue +├── App.vue +├── main.ts +└── style.css +``` + +--- + +## 4. 阶段 0:基础设施搭建 + +**预计工期**:1-2 天 + +### 4.1 安装依赖 + +```bash +npm install vue-router pinia @amap/amap-jsapi-loader +npm install -D tailwindcss @tailwindcss/vite +``` + +### 4.2 清理脚手架 + +| 操作 | 说明 | +|------|------| +| 删除 `src/components/HelloWorld.vue` | 默认模板组件 | +| 清空并重写 `src/style.css` | 替换为 Tailwind 指令 | +| 删除 `src/assets/hero.png` | 模板资源 | +| 删除 `src/assets/vite.svg` | 模板资源 | +| 删除 `src/assets/vue.svg` | 模板资源 | +| 删除 `public/icons.svg` | 模板资源 | +| 替换 `public/favicon.svg` | 使用 OpenCloud 图标(临时可用占位图标) | + +### 4.3 Vite 配置 + +`vite.config.ts` 更新内容: + +```ts +import { defineConfig } from 'vite' +import { fileURLToPath, URL } from 'node:url' +import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [vue(), tailwindcss()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, +}) +``` + +### 4.4 TypeScript 配置 + +`tsconfig.app.json` 新增 `paths`: + +```json +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +新增 `src/types/amap.d.ts`:高德地图类型声明,避免 TS 报错。 + +### 4.5 Tailwind CSS 配置 + +`src/style.css`: + +```css +@import 'tailwindcss'; +``` + +### 4.6 Supabase 客户端 + +`src/lib/supabase.ts`: + +```ts +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL +const supabaseKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY + +export const supabase = createClient(supabaseUrl, supabaseKey) +``` + +### 4.7 高德地图加载器 + +`src/lib/amap.ts`: + +```ts +import AMapLoader from '@amap/amap-jsapi-loader' + +export function loadAMap() { + return AMapLoader.load({ + key: import.meta.env.VITE_AMAP_KEY, + version: '2.0', + plugins: [ + 'AMap.Scale', + 'AMap.ToolBar', + 'AMap.ControlBar', + 'AMap.Geolocation', + 'AMap.Marker', + 'AMap.InfoWindow', + ], + }) +} +``` + +### 4.8 Vue Router 基础配置 + +`src/router/index.ts`: + +```ts +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + name: 'map', + component: () => import('@/views/map/MapView.vue'), + }, + { + path: '/login', + name: 'login', + component: () => import('@/views/auth/LoginView.vue'), + }, + { + path: '/register', + name: 'register', + component: () => import('@/views/auth/RegisterView.vue'), + }, + { + path: '/upload', + name: 'upload', + component: () => import('@/views/upload/UploadView.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/encyclopedia', + name: 'encyclopedia', + component: () => import('@/views/encyclopedia/EncyclopediaView.vue'), + }, + { + path: '/encyclopedia/:id', + name: 'cloud-type', + component: () => import('@/views/encyclopedia/CloudTypeView.vue'), + }, + { + path: '/gallery', + name: 'gallery', + component: () => import('@/views/gallery/GalleryView.vue'), + }, + { + path: '/profile', + name: 'profile', + component: () => import('@/views/profile/ProfileView.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/admin', + name: 'admin', + component: () => import('@/views/admin/AdminView.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + }, + ], +}) + +router.beforeEach((to, _from, next) => { + const authStore = useAuthStore() + if (to.meta.requiresAuth && !authStore.isLoggedIn) { + next({ name: 'login', query: { redirect: to.fullPath } }) + } else { + next() + } +}) + +export default router +``` + +### 4.9 Pinia 注册 + +`src/main.ts`: + +```ts +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' +import './style.css' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.mount('#app') +``` + +### 4.10 应用外壳 + +`src/App.vue`: + +```vue + + + +``` + +### 4.11 环境变量更新 + +`.env` 新增: + +``` +VITE_AMAP_KEY=你的高德Key +VITE_AMAP_SECRET=你的高德安全密钥 +``` + +### 4.12 阶段 0 验收标准 + +- [ ] `npm run dev` 启动无报错 +- [ ] Tailwind 类名生效(可测试 `bg-blue-500 text-white`) +- [ ] `@/` 路径别名正常工作 +- [ ] Vue Router 路由切换正常 +- [ ] 浏览器控制台无 TS/amap 类型报错 +- [ ] 脚手架模板内容已全部清除 + +--- + +## 5. 阶段 1:用户认证 + +**预计工期**:2-3 天 + +### 5.1 数据库表设计 + +#### `profiles` 表 + +```sql +CREATE TABLE profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + username TEXT UNIQUE NOT NULL, + avatar_url TEXT, + role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')), + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- RLS +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Public profiles are viewable by everyone" + ON profiles FOR SELECT USING (true); + +CREATE POLICY "Users can update own profile" + ON profiles FOR UPDATE USING (auth.uid() = id); + +-- 注册时自动创建 profile +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.profiles (id, username, avatar_url) + VALUES ( + NEW.id, + COALESCE(NEW.raw_user_meta_data->>'username', 'user_' || LEFT(NEW.id::text, 6)), + NEW.raw_user_meta_data->>'avatar_url' + ); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); +``` + +### 5.2 Auth Store + +`src/stores/auth.ts`: + +```ts +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { supabase } from '@/lib/supabase' +import type { User } from '@supabase/supabase-js' + +export const useAuthStore = defineStore('auth', () => { + const user = ref(null) + const profile = ref<{ username: string; avatar_url: string | null; role: string } | null>(null) + const loading = ref(true) + + const isLoggedIn = computed(() => !!user.value) + const isAdmin = computed(() => profile.value?.role === 'admin') + + async function fetchProfile() { /* ... */ } + async function login(email: string, password: string) { /* ... */ } + async function register(email: string, password: string, username: string) { /* ... */ } + async function logout() { /* ... */ } + + // 监听认证状态变化 + supabase.auth.onAuthStateChange((_event, session) => { + user.value = session?.user ?? null + if (user.value) fetchProfile() + else profile.value = null + }) + + return { user, profile, loading, isLoggedIn, isAdmin, login, register, logout } +}) +``` + +### 5.3 Auth Composable + +`src/composables/useAuth.ts`:封装表单逻辑、验证、错误处理。 + +### 5.4 页面实现 + +#### `/login` — LoginView.vue + +- 邮箱 + 密码输入表单 +- "没有账号?去注册" 链接 +- 登录成功后跳转首页或 redirect 参数指定页面 +- 错误提示(邮箱未验证、密码错误等) + +#### `/register` — RegisterView.vue + +- 用户名 + 邮箱 + 密码 + 确认密码 +- 基础前端验证(邮箱格式、密码长度≥8、两次密码一致、用户名唯一性检查) +- 注册成功后自动登录并跳转首页 + +### 5.5 路由守卫完善 + +- `requiresAuth` 路由:未登录跳转 `/login?redirect=xxx` +- `requiresAdmin` 路由:非管理员跳转首页 +- 已登录用户访问 `/login` `/register` 自动跳转首页 + +### 5.6 阶段 1 验收标准 + +- [ ] 邮箱注册成功,profiles 表自动生成记录 +- [ ] 登录后获取到 profile 数据 +- [ ] 登出后清除状态,跳转首页 +- [ ] 未登录访问 `/upload` 跳转登录页,登录后回到 /upload +- [ ] 非管理员访问 `/admin` 被拦截 + +--- + +## 6. 阶段 2:云图上传 + +**预计工期**:2-3 天 + +### 6.1 数据库表设计 + +#### `cloud_types` 表(预置数据) + +```sql +CREATE TABLE cloud_types ( + id SERIAL PRIMARY KEY, + name TEXT UNIQUE NOT NULL, -- 积云、层云、卷云... + name_en TEXT NOT NULL, -- Cumulus, Stratus, Cirrus... + genus TEXT NOT NULL, -- 基础属名 + rarity TEXT NOT NULL DEFAULT 'common' CHECK (rarity IN ('common', 'uncommon', 'rare')), + description TEXT, -- 气象小知识 + icon_url TEXT, -- 图鉴图标 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- 预置 10 种基础云属 +INSERT INTO cloud_types (name, name_en, genus, rarity, description) VALUES + ('积云', 'Cumulus', 'Cumulus', 'common', '最常见的基础云型,晴天常客,像棉花糖一样蓬松。'), + ('层云', 'Stratus', 'Stratus', 'common', '灰蒙蒙的低云,常覆盖整个天空,像一层灰色的毯子。'), + ('卷云', 'Cirrus', 'Cirrus', 'common', '高空的丝缕状云,由冰晶组成,天气变化的先行者。'), + ('积雨云', 'Cumulonimbus', 'Cumulonimbus', 'uncommon', '雷暴的制造者, towering 巨人,顶部常呈铁砧状。'), + ('层积云', 'Stratocumulus', 'Stratocumulus', 'common', '成片的低云群,常有缝隙露出蓝天。'), + ('高积云', 'Altocumulus', 'Altocumulus', 'common', '中层的云群,常呈鱼鳞状排列,谚语"鱼鳞天"说的就是它。'), + ('高层云', 'Altostratus', 'Altostratus', 'common', '中层的灰白色云层,透过它太阳像隔了层磨砂玻璃。'), + ('雨层云', 'Nimbostratus', 'Nimbostratus', 'common', '灰暗厚重的低云,持续性降水的标志。'), + ('卷层云', 'Cirrostratus', 'Cirrostratus', 'uncommon', '薄纱般的高云,常产生日晕和月晕现象。'), + ('卷积云', 'Cirrocumulus', 'Cirrocumulus', 'uncommon', '高空的小云朵群,排列如涟漪,是天气变化的信号。'); +``` + +#### `clouds` 表 + +```sql +CREATE TABLE clouds ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + cloud_type_id INTEGER NOT NULL REFERENCES cloud_types(id), + image_url TEXT NOT NULL, + thumbnail_url TEXT, + latitude DOUBLE PRECISION, -- 模糊化后的纬度(~1km精度) + longitude DOUBLE PRECISION, -- 模糊化后的经度(~1km精度) + location_name TEXT, -- 城市/区域名 + weather TEXT, -- 拍摄时天气简述(用户手动输入) + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')), + is_hidden BOOLEAN NOT NULL DEFAULT false, -- 隐身模式 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- RLS +ALTER TABLE clouds ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Approved clouds are viewable by everyone" + ON clouds FOR SELECT USING (status = 'approved' AND NOT is_hidden); + +CREATE POLICY "Users can view own clouds" + ON clouds FOR SELECT USING (auth.uid() = user_id); + +CREATE POLICY "Authenticated users can insert clouds" + ON clouds FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can update own clouds" + ON clouds FOR UPDATE USING (auth.uid() = user_id); + +-- 索引 +CREATE INDEX idx_clouds_status ON clouds(status); +CREATE INDEX idx_clouds_location ON clouds(latitude, longitude); +CREATE INDEX idx_clouds_user_id ON clouds(user_id); +CREATE INDEX idx_clouds_created_at ON clouds(created_at DESC); +``` + +### 6.2 Supabase Storage 配置 + +```sql +-- 创建 clouds bucket +INSERT INTO storage.buckets (id, name, public) VALUES ('clouds', 'clouds', true); + +-- 上传策略:已登录用户可上传 +CREATE POLICY "Authenticated users can upload" + ON storage.objects FOR INSERT + WITH CHECK (bucket_id = 'clouds' AND auth.role() = 'authenticated'); + +-- 读取策略:所有人可读 +CREATE POLICY "Clouds images are publicly accessible" + ON storage.objects FOR SELECT + USING (bucket_id = 'clouds'); +``` + +### 6.3 上传页面实现 + +#### `/upload` — UploadView.vue + +流程: + +1. **选择图片** — 文件选择器或拖拽上传,前端预览 +2. **选择云型** — 从 10 种基础云属下拉列表中手动选择(MVP 无 AI 识别,后续接入) +3. **位置信息** — 调用高德 Geolocation 获取当前位置,或手动输入城市名 +4. **天气描述** — 用户手动输入(如"晴天"、"多云") +5. **隐私选项** — 是否隐身模式(地图不显示) +6. **提交上传** — 图片 → Supabase Storage,元数据 → clouds 表 + +#### 关键实现细节 + +- 图片上传前压缩至合理大小(maxWidth: 1920, quality: 0.8) +- 坐标模糊化:前端将经纬度保留小数点后 2 位(~1km 精度),不上传精确坐标原始值 +- 生成缩略图:前端用 Canvas 生成 300x300 缩略图,同时上传至 Storage +- 云型选择提供图文辅助:每种云型附带简短描述和示意图,帮助用户正确选择 + +### 6.4 审核流程 + +MVP 阶段采用管理员手动审核: + +- 用户上传后状态为 `pending` +- 管理员在后台审核通过后状态变为 `approved`,地图和画廊可见 +- 审核不通过状态变为 `rejected`,仅用户本人可见 + +### 6.5 阶段 2 验收标准 + +- [ ] 选择图片后可手动选择云型并提交 +- [ ] 图片成功上传至 Supabase Storage +- [ ] clouds 表记录正确写入(含模糊化坐标) +- [ ] 上传后状态为 pending,等待管理员审核 +- [ ] 不登录无法访问上传页面 + +> **后续迭代**:接入 OpenAI Vision Edge Function 实现自动识别 + 违规检测,用户可在 AI 结果基础上修正。clouds 表届时可新增 `confidence` 和 `user_corrected_type_id` 字段。 + +--- + +## 7. 阶段 3:高德 3D 云图地图 + +**预计工期**:3-4 天 + +### 7.1 地图初始化 + +`src/views/map/MapView.vue`: + +```ts +import { loadAMap } from '@/lib/amap' + +onMounted(async () => { + const AMap = await loadAMap() + const map = new AMap.Map('map-container', { + viewMode: '3D', + pitch: 45, + rotation: -15, + zoom: 5, + center: [104.07, 30.67], // 成都 + mapStyle: 'amap://styles/fresh', + features: ['bg', 'road', 'building', 'point'], + }) + + map.addControl(new AMap.Scale()) + map.addControl(new AMap.ToolBar({ position: 'RT' })) + map.addControl(new AMap.ControlBar({ position: { right: '10px', top: '80px' } })) +}) +``` + +### 7.2 图层切换 + +实现卫星/路网/3D 地形视图切换: + +```ts +const satellite = new AMap.TileLayer.Satellite() +const roadNet = new AMap.TileLayer.RoadNet() + +function switchToSatellite() { + map.add(satellite) + map.add(roadNet) +} + +function switchToNormal() { + map.remove(satellite) + map.remove(roadNet) +} +``` + +### 7.3 云标记渲染 + +从 Supabase 获取已审核云图数据,在地图上渲染标记: + +```ts +async function loadCloudMarkers() { + const { data } = await supabase + .from('clouds') + .select('id, latitude, longitude, cloud_type_id, image_url, thumbnail_url, created_at, profiles(username)') + .eq('status', 'approved') + .eq('is_hidden', false) + .order('created_at', { ascending: false }) + .limit(500) + + data?.forEach(cloud => { + const marker = new AMap.Marker({ + position: [cloud.longitude, cloud.latitude], + content: getMarkerContent(cloud.cloud_type_id), // 自定义标记样式 + offset: new AMap.Pixel(-15, -15), + }) + + marker.on('click', () => showCloudCard(cloud)) + map.add(marker) + }) +} +``` + +### 7.4 云卡片弹窗 + +点击标记弹出 InfoWindow,展示: + +- 云图片缩略图 +- 云类型名称 +- 拍摄者用户名 +- 拍摄时间 +- 天气简述 +- 快捷操作:看同类云、看这里其他云 + +### 7.5 标记样式 + +按云型稀有度区分标记样式: + +| 稀有度 | 标记样式 | +|--------|----------| +| common | 白色圆形标记 | +| uncommon | 蓝色圆形标记 + 闪光效果 | +| rare | 紫色圆形标记 + 呼吸动画 | + +### 7.6 时间衰减显示 + +```ts +function getMarkerOpacity(createdAt: string): number { + const hoursDiff = (Date.now() - new Date(createdAt).getTime()) / (1000 * 60 * 60) + if (hoursDiff <= 0.5) return 1 // 30min 内全透明度 + if (hoursDiff <= 2) return 0.6 // 2h 内半透明 + return 0.3 // 24h 内低透明度 +} +``` + +24h 以上的云标记默认不加载,用户可通过开关显示历史层。 + +### 7.7 阶段 3 验收标准 + +- [ ] 地图正常加载,3D 模式可旋转/俯仰 +- [ ] 卫星/路网图层切换正常 +- [ ] 云标记正确显示在对应位置 +- [ ] 点击标记弹出云卡片 +- [ ] 标记透明度随时间衰减 +- [ ] 地图性能流畅(500 标记无卡顿) + +--- + +## 8. 阶段 4:图鉴系统 + +**预计工期**:2-3 天 + +### 8.1 数据库补充 + +#### `user_collections` 表 + +```sql +CREATE TABLE user_collections ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + cloud_type_id INTEGER NOT NULL REFERENCES cloud_types(id), + first_cloud_id UUID REFERENCES clouds(id), -- 解锁该类型的第一张云图 + unlocked_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE(user_id, cloud_type_id) +); + +ALTER TABLE user_collections ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Users can view own collections" + ON user_collections FOR SELECT USING (auth.uid() = user_id); + +CREATE POLICY "Users can insert own collections" + ON user_collections FOR INSERT WITH CHECK (auth.uid() = user_id); +``` + +#### 解锁触发器 + +```sql +-- 云图审核通过时,自动解锁对应用户的图鉴 +CREATE OR REPLACE FUNCTION unlock_cloud_type() +RETURNS TRIGGER AS $$ +BEGIN + IF NEW.status = 'approved' AND (OLD.status IS NULL OR OLD.status != 'approved') THEN + INSERT INTO user_collections (user_id, cloud_type_id, first_cloud_id) + VALUES (NEW.user_id, NEW.cloud_type_id, NEW.id) + ON CONFLICT (user_id, cloud_type_id) DO NOTHING; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +CREATE TRIGGER on_cloud_approved + AFTER UPDATE ON clouds + FOR EACH ROW EXECUTE FUNCTION unlock_cloud_type(); +``` + +### 8.2 图鉴页面 + +#### `/encyclopedia` — EncyclopediaView.vue + +- 网格布局展示 10 种基础云属 +- 每张卡片包含:云型图标、名称、稀有度标签 +- 已解锁:正常显示 + 金色边框 + 首次拍摄缩略图 +- 未解锁:灰色剪影 + 锁图标 +- 点击已解锁卡片进入详情页 +- 点击未解锁卡片提示"拍摄该类型云朵即可解锁" + +#### `/encyclopedia/:id` — CloudTypeView.vue + +- 顶部大图/手绘风格插图区域 +- 云型名称 + 英文名 + 稀有度标签 +- 气象小知识(来自 cloud_types.description) +- 该类型云图画廊(来自 clouds 表 + Storage) +- 收集统计:已拍 X 次,首次解锁时间 + +### 8.3 图鉴 Store + +`src/stores/encyclopedia.ts`: + +- `cloudTypes`:所有云型列表(来自 cloud_types 表) +- `myCollection`:当前用户已解锁列表(来自 user_collections 表) +- `isUnlocked(cloudTypeId)`:判断某类型是否已解锁 +- `unlockProgress`:解锁进度(X/10) + +### 8.4 徽章分享 + +每解锁一枚云型徽章: +- 弹出祝贺弹窗,展示徽章动画 +- 生成分享卡片(Canvas 绘制),包含:徽章图标、云型名称、解锁日期、用户名 +- 提供"保存到相册"和"分享到社交平台"按钮(MVP 阶段可简化为仅保存图片) + +### 8.5 阶段 4 验收标准 + +- [ ] 图鉴页面正确展示 10 种云型(已解锁/未解锁状态) +- [ ] 云图审核通过后自动解锁对应图鉴 +- [ ] 详情页展示气象知识和该类型云图画廊 +- [ ] 个人图鉴进度正确显示 +- [ ] 徽章解锁时有祝贺提示 + +--- + +## 9. 阶段 5:社区 + 管理后台 + 收尾 + +**预计工期**:2-3 天 + +### 9.1 画廊浏览 + +#### `/gallery` — GalleryView.vue + +- 顶部云型筛选栏(全部 + 10 种云型 tab) +- 瀑布流或网格展示云图 +- 每张卡片:缩略图、云型标签、拍摄者、时间 +- 点击进入大图查看模式 +- 分页加载(每页 20 张,滚动加载更多) + +### 9.2 情绪反应 + +#### `reactions` 表 + +```sql +CREATE TABLE reactions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + cloud_id UUID NOT NULL REFERENCES clouds(id) ON DELETE CASCADE, + emoji TEXT NOT NULL CHECK (emoji IN ('✨', '🌿', '🌧', '🔥', '💭', '🫂')), + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE(user_id, cloud_id) +); + +ALTER TABLE reactions ENABLE ROW LEVEL SECURITY; + +CREATE POLICY "Reactions are viewable by everyone" + ON reactions FOR SELECT USING (true); + +CREATE POLICY "Authenticated users can add reactions" + ON reactions FOR INSERT WITH CHECK (auth.uid() = user_id); + +CREATE POLICY "Users can remove own reactions" + ON reactions FOR DELETE USING (auth.uid() = user_id); +``` + +实现方式:每张大图下方展示 6 个情绪按钮,已点击的高亮显示,旁边显示计数。 + +### 9.3 管理后台 + +#### `/admin` — AdminView.vue + +- 侧边栏导航:待审核队列、用户管理、数据统计 +- **待审核队列**:展示 status=pending 的云图列表,支持批量通过/拒绝 +- **用户管理**:展示用户列表,支持禁用/恢复(profiles 表加 is_disabled 字段) +- **数据统计**:总用户数、今日上传数、待审核数、各云型分布 +- 路由守卫:仅 `role = 'admin'` 可访问 + +### 9.4 个人主页 + +#### `/profile` — ProfileView.vue + +- 用户头像、用户名、注册日期 +- 收集进度条(X/10 云型已解锁) +- 个人云图时间线(按时间倒序展示) +- 天空日志概念:按月分组展示 +- 基础统计:总拍摄数、最常拍云型、拍摄天数 + +### 9.5 响应式适配 + +- 移动端:地图全屏、底部导航栏(地图/图鉴/上传/我的) +- 桌面端:顶部导航栏、侧边面板 +- 上传页面:移动端简化流程(更少的手动输入) + +### 9.6 阶段 5 验收标准 + +- [ ] 画廊筛选和分页加载正常 +- [ ] 情绪反应可添加/取消,计数正确 +- [ ] 管理后台可审核云图、查看统计 +- [ ] 个人主页展示图鉴进度和云图时间线 +- [ ] 移动端布局适配正常 +- [ ] 核心流程端到端跑通 + +--- + +## 10. 数据库设计总览 + +### ER 关系 + +``` +auth.users (Supabase 内置) + │ 1:1 + ▼ +profiles + │ 1:N + ├──────────► clouds ──► cloud_types + │ │ + │ ├── 1:N + │ ▼ + │ reactions + │ + └──────────► user_collections ──► cloud_types +``` + +### 完整表清单 + +| 表名 | 用途 | 主要字段 | +|------|------|----------| +| `profiles` | 用户资料 | id, username, avatar_url, role, is_disabled | +| `cloud_types` | 云型标准库 | id, name, name_en, genus, rarity, description, icon_url | +| `clouds` | 云图记录 | id, user_id, cloud_type_id, image_url, latitude, longitude, status, is_hidden | +| `user_collections` | 图鉴收集 | id, user_id, cloud_type_id, first_cloud_id, unlocked_at | +| `reactions` | 情绪反应 | id, user_id, cloud_id, emoji | +| `storage.objects` | 图片文件 | bucket_id: 'clouds' | + +### RLS 策略总览 + +| 表 | SELECT | INSERT | UPDATE | DELETE | +|----|--------|--------|--------|--------| +| `profiles` | 所有人 | 触发器创建 | 仅本人 | - | +| `clouds` | approved 或本人 | 仅本人 | 仅本人 | - | +| `user_collections` | 仅本人 | 仅本人 | - | - | +| `reactions` | 所有人 | 已登录用户 | - | 仅本人 | + +--- + +## 11. 环境变量清单 + +```env +# Supabase +VITE_SUPABASE_URL=https://xxx.supabase.co +VITE_SUPABASE_PUBLISHABLE_KEY=sb_publishable_xxx + +# 高德地图 +VITE_AMAP_KEY=你的高德Key +VITE_AMAP_SECRET=你的高德安全密钥 +``` + +> **后续迭代环境变量**(AI 识别接入时在 Supabase Dashboard 中配置): +> - `OPENAI_API_KEY` — OpenAI API 密钥 +> - `OPENWEATHERMAP_API_KEY` — 天气数据 API 密钥 + +--- + +## 12. 风险与注意事项 + +| 风险 | 应对 | +|------|------| +| 高德地图 Key 申请被拒 | 提前申请,准备企业资质;备选方案 Mapbox | +| Supabase Storage 流量超限 | 图片压缩 + 缩略图策略,使用 CDN | +| 高德地图国内定位不准 | 提供手动输入位置备选方案 | +| 500+ 地图标记卡顿 | 聚合标记(AMap.MarkerCluster);按视口区域加载 | +| 图片审核遗漏违规内容 | 管理员人工审核 + 社区举报;后续接入 AI 自动检测 | +| MVP 上线后冷启动 | 预置 Unsplash 免版权云图作为种子内容 | +| 用户选错云型 | 云型选择提供图文辅助;后续接入 AI 识别自动建议 | + +--- + +*天空无界,探索不止。* +**OpenCloud 团队**