第03章 项目结构解析

3.1 整体目录结构

map-360-demo 采用标准的 Vite + Vue 3 + TypeScript 项目结构:

map-360-demo/
├── public/                          # 静态资源(构建时原样复制到 dist/)
│   └── icons/
│       └── link-pin.svg            # 场景跳转标记图标(本地资源,不依赖 CDN)
├── src/                             # 源代码
│   ├── App.vue                     # 根组件:编排状态、跳转逻辑、导入导出
│   ├── main.ts                     # 入口文件
│   ├── env.d.ts                    # 类型声明(vite/client + *.vue 模块)
│   ├── components/                 # 组件
│   │   ├── PsvContainer.vue        # 核心:封装 PSV viewer 生命周期
│   │   ├── SceneSelector.vue       # 场景下拉选择
│   │   ├── MarkerModal.vue         # 添加/编辑标记弹窗
│   │   ├── MarkerList.vue          # 底部标记列表
│   │   └── ToastHost.vue           # 全局 Toast 反馈
│   ├── composables/                # 组合式函数
│   │   ├── useAppState.ts          # 全局响应式状态 + localStorage 持久化
│   │   ├── useAddMarkerFlow.ts     # 添加/编辑标记状态机
│   │   └── useToast.ts             # Toast 单例状态
│   ├── data/
│   │   └── scenes.ts               # 预设场景、默认标记、PSV 标记配置转换
│   ├── styles/
│   │   └── psv.css                 # PSV 全局样式覆盖
│   ├── types/
│   │   └── index.ts                # 类型定义
│   └── utils/
│       └── geo.ts                  # 方位角换算(点击位置 ↔ 经纬度)
├── index.html                      # HTML 模板
├── vite.config.ts                  # Vite 配置
├── tsconfig.json                   # TypeScript 根配置
├── tsconfig.app.json               # 应用代码配置
├── tsconfig.node.json              # Node 环境配置(vite.config.ts)
└── package.json                    # 项目依赖与脚本

3.2 各目录职责详解

3.2.1 public/

存放静态资源,构建时原样复制到 dist/ 目录。icons/link-pin.svg 是一个 32×32 的绿色箭头 pin 图标,用于场景跳转标记,同时被用作 index.html 的 favicon。

3.2.2 src/components/

存放 Vue 组件,每个组件职责单一:

组件 职责
PsvContainer.vue 唯一与 PSV 直接交互的组件,封装 Viewer + 两个插件的完整生命周期
SceneSelector.vue 场景下拉选择,纯展示组件
MarkerModal.vue 添加/编辑标记弹窗,含类型选择、焦点管理、过渡动效
MarkerList.vue 底部标记列表,含搜索、折叠、编辑/删除、导入导出
ToastHost.vue 全局 Toast 反馈

3.2.3 src/composables/

存放组合式函数,是项目状态管理的核心:

文件 职责
useAppState.ts 全局响应式状态 + localStorage 持久化 + 导入导出
useAddMarkerFlow.ts 添加/编辑标记状态机(选点、预览 pin、弹窗协调)
useToast.ts Toast 单例状态(定时器自动清理)

3.2.4 src/data/

存放静态数据与配置转换逻辑:

  • scenes.ts:预设场景、默认标记、PSV 标记配置转换函数

3.2.5 src/styles/

存放全局样式:

  • psv.css:PSV 全局样式覆盖(tooltip、预览脉冲、跳转标记)

3.2.6 src/types/

存放 TypeScript 类型定义:

  • index.ts:Scene / MarkerData / MarkerPayload / PsvMarkerConfig 类型

3.2.7 src/utils/

存放工具函数:

  • geo.ts:方位角换算(点击位置 ↔ 经纬度)

3.3 数据流架构

项目的核心数据流如下:

App.vue (编排层)
  ├── useAppState()      → scenes / currentSceneId / currentMarkers / CRUD
  ├── useAddMarkerFlow() → 添加/编辑状态机
  ├── useToast()         → Toast 反馈
  └── PsvContainer       → PSV viewer 生命周期封装
        ├── Viewer       → 全景查看器
        ├── MarkersPlugin → 全景标记
        └── PlanPlugin   → Leaflet 地图

关键设计

  1. App.vue 不持有业务数据:全部状态来自 composables,只做编排
  2. PsvContainer 是唯一与 PSV 交互的组件:通过 defineExpose 暴露语义化接口
  3. 模块级单例:composables 中的状态是模块顶层 ref,多处调用共享同一份数据

3.4 类型定义

src/types/index.ts 定义了核心类型:

// 场景
interface Scene {
  id: string
  name: string
  description: string
  panorama: string
  coordinates: [number, number]  // [lng, lat]
  bearing: string                // 方位角,如 '120deg'
  defaultZoom: number
}

// 标记类型
type MarkerType = 'info' | 'link'

// 标记数据
interface MarkerData {
  id: string
  sceneId: string
  name: string
  description: string
  position: { yaw: number, pitch: number }  // 全景球形坐标
  coordinates: [number, number]             // GPS 经纬度
  createdAt: number
  type?: MarkerType                          // 缺省视为 info
  targetSceneId?: string                     // link 类型的目标场景
}

// 弹窗确认后的表单数据
interface MarkerPayload {
  name: string
  description: string
  coordinates: [number, number]
  type: MarkerType
  targetSceneId?: string
}

// PSV markers-plugin 配置格式
interface PsvMarkerConfig {
  id: string
  tooltip: string
  content: string
  position: { yaw: number, pitch: number }
  image: string
  size: { width: number, height: number }
  anchor: string
  className?: string
  data: {
    plan: {  // PlanPlugin 联动字段
      coordinates: [number, number]
      size: number
      image: string
    }
  }
}

3.5 下一步

了解了项目结构后,进入 第04章 核心组件 PsvContainer 详解 深入理解核心组件的实现。