# 房源详情集成指南

本文档面向**接入众趣 zqsdk 的开发者**，说明如何在自己的项目中实现「房源详情」功能。房源详情用于展示房源的基本信息、地图位置、小区照片、经纪人信息等。



本项目的【房源详情】模块是按照众趣房产平台展示端的UI展示和交互效果开发的， 您可以根据您的业务自定义开发UI模块和交互逻辑。

---

## 一、功能概述

本项目房源详情模块包含以下功能：

| 功能 | 说明 |
|------|------|
| 顶部标题栏 | 显示房源名称，点击可展开/收起详情面板 |
| 房源信息面板 | 展示售价、户型、面积等名值对列表（两列网格布局） |
| 地图位置 | 房源所在位置的百度地图展示 |
| 小区照片 | 小区实勘图展示，点击可打开图片轮播 |
| 经纪人信息 | 底部工具栏展示经纪人头像、姓名、电话 |



### 1.1 数据来源

房源详情数据来自众趣开放平台的房源详情接口，数据结构参考：[众趣开放平台 - 房源详情对接接口](https://opendev.3dnest.cn/doc/open/callback/vr-house-info/)

### 1.2 效果预览
![房源详情展示](../../screenshot/house-info/房源详情展示.png)

---

## 二、接口数据结构

### 2.1 请求参数

```typescript
interface GetHouseInfoParams {
  model_id: string  // 模型 ID
  house_id: string  // 房源 ID
}
```

### 2.2 响应数据结构

```typescript
interface HouseInfoResponse {
  code: number           // 状态码，1 表示成功
  msg: string            // 响应消息
  detail_config: number  // 详情配置
  house_info: {
    // 基本信息
    title: string                    // 房源标题
    menu_info: {                     // 房源详情列表
      name: string                   // 字段名
      value: string                  // 字段值
      widthStyle: string             // 展示样式：1-3 单行，4-5 多行
    }[]
    
    // 地图信息
    address_x: string | null         // 经度（百度地图坐标）
    address_y: string | null         // 纬度（百度地图坐标）
    
    // 小区照片
    images: string[]                 // 实勘图链接地址
    
    // 经纪人信息
    middleman: string                // 经纪人姓名
    middleman_img: string            // 经纪人头像
    middleman_tel: string            // 经纪人电话
    
    // 航拍信息
    hangpaiUrl: string               // 航拍 URL
    hangpai_photo: string            // 航拍封面图
    
    // 分享信息
    jump_url: string                 // 更多房源地址
    share_title: string              // 微信分享标题
    share_desc: string               // 微信分享描述
    share_img: string                // 微信分享头像
    share_link: string               // 微信分享链接
  }
}
```

---

## 三、集成步骤

### 3.1 配置环境变量

在 `.env` 文件中配置：

```bash
# 房源详情接口地址
VITE_APP_GET_HOUSE_INFO_API_HOST=https://your-api-host.com

# 百度地图 API 密钥（用于地图展示）
VITE_APP_BAIDU_MAP_AK=your_baidu_map_ak
```

### 3.2 实现 API 接口

创建 `src/api/getHouseInfo.ts`：

```typescript
import axios from 'axios'

const houseInfoApiHost = import.meta.env.VITE_APP_GET_HOUSE_INFO_API_HOST

interface GetHouseInfoParams {
  model_id: string
  house_id: string
}

export const getHouseInfo = async (params: GetHouseInfoParams) => {
  const response = await axios.get(`${houseInfoApiHost}/api/v1/house_info`, {
    headers: { 'Content-Type': 'application/json' },
    params,
  })
  return response.data
}
```

### 3.3 创建状态管理

创建 `src/stores/house-info.ts`：

```typescript
import { ref } from 'vue'
import { defineStore } from 'pinia'
import { getHouseInfo } from '@/api/getHouseInfo'
import { useUrlParams } from '@/stores/url-params'

interface CommonHouseInfoData {
  address_x: string | null
  address_y: string | null
  jump_url: string
  hangpaiUrl: string
  hangpai_photo: string
  menu_info: { widthStyle: string; name: string; value: string }[]
  middleman: string
  middleman_img: string
  middleman_tel: string
  share_desc: string
  share_img: string
  share_link: string
  share_title: string
  title: string
  images: string[]
}

export const useHouseInfoStore = defineStore('houseInfo', () => {
  const houseDetailPanelVisible = ref(false)
  const houseInfo = ref<CommonHouseInfoData | null>(null)
  const houseTopBarVisible = ref(false)

  async function initHouseInfo(): Promise<void> {
    const urlParams = useUrlParams()
    const modelId = urlParams.modelId
    const houseId = urlParams.houseId

    if (!modelId || !houseId) {
      console.warn('参数缺失, 无法获取房源信息')
      return
    }

    try {
      const res = await getHouseInfo({ model_id: modelId, house_id: houseId })
      if (res.code === 1) {
        setHouseInfo(res.house_info)
        showHouseTopBar()
      }
    } catch (error) {
      console.error('初始化房源信息失败', error)
    }
  }

  function setHouseInfo(data: CommonHouseInfoData): void {
    houseInfo.value = data
  }

  function toggleHouseDetailPanelVisible(): void {
    houseDetailPanelVisible.value = !houseDetailPanelVisible.value
  }

  function closeHouseDetailPanel(): void {
    houseDetailPanelVisible.value = false
  }

  function showHouseTopBar(): void {
    houseTopBarVisible.value = true
  }

  return {
    houseDetailPanelVisible,
    houseInfo,
    houseTopBarVisible,
    initHouseInfo,
    setHouseInfo,
    toggleHouseDetailPanelVisible,
    closeHouseDetailPanel,
    showHouseTopBar,
  }
})
```

### 3.4 初始化时机

由于房源详情信息与zqsdk的生命周期无关，所以可在任意时刻获取房源详情信息，他不依赖zqsdk的回调消息。本demo项目在vr初始化的时候获取的房源详情信息

**src\modules\pc\vr-player\NestVrPlayer.vue** 23-29

```typescript
onMounted(async () => {
  // 初始化url参数，通过urlParam store模块统一管理
  urlParams.initUrlParams()

  await houseInfoStore.initHouseInfo() // 初始化房源信息，获取房源详情
  await vrStore.initSdk() // 初始化 SDK, VR渲染
})
```

---

## 四、UI 组件实现

### 4.1 组件架构

| 组件 | 路径 | 职责 |
|------|------|------|
| `NestHouseTopBar` | `src/modules/pc/house-info/` | 顶部标题栏，点击展开/收起详情 |
| `NestHouseDetailPanel` | `src/modules/pc/house-info/` | 详情面板容器，组合子组件 |
| `NestHouseInfo` | `src/components/common/pc/` | 房源信息列表（公共组件） |
| `NestHouseMap` | `src/components/common/pc/` | 百度地图（公共组件） |
| `NestCommunityPhotos` | `src/components/common/pc/` | 小区照片（公共组件） |
| `NestModalImgSwiper` | `src/components/common/pc/` | 图片轮播弹层（公共组件） |
| `NestBottomBar` | `src/modules/pc/toolbar/` | 底部工具栏（含经纪人信息） |

### 4.2 顶部标题栏

`NestHouseTopBar.vue` - 显示房源名称，点击切换详情面板：

```vue
<script setup lang="ts">
import { computed, watch } from 'vue'
import { useHouseInfoStore } from '@/stores/house-info'
import { useVrStore } from '@/stores/vr'
import { useIntroduceStore } from '@/stores/introduce'

const houseInfoStore = useHouseInfoStore()
const vrStore = useVrStore()
const introduceStore = useIntroduceStore()

// VR 就绪且非讲房模式且有标题时显示
const visible = computed(() => 
  vrStore.status === 'ready' && 
  !introduceStore.panelVisible && 
  !!houseInfoStore.houseInfo?.title
)

function handleClick(): void {
  houseInfoStore.toggleHouseDetailPanelVisible()
}

// 讲房模式时自动关闭房源详情面板
watch(() => introduceStore.panelVisible, (visible) => {
  if (visible) houseInfoStore.closeHouseDetailPanel()
})
</script>

<template>
  <div v-if="visible" class="house-top-bar" @click="handleClick">
    <div class="house-top-bar__title">
      {{ houseInfoStore.houseInfo?.title }}
    </div>
    <div class="house-top-bar__chevron">▼</div>
  </div>
</template>
```

### 4.3 详情面板

`NestHouseDetailPanel.vue` - 组合房源信息、地图、小区照片：

```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useHouseInfoStore } from '@/stores/house-info'
import NestHouseInfo from '@/components/common/pc/NestHouseInfo.vue'
import NestHouseMap from '@/components/common/pc/NestHouseMap.vue'
import NestCommunityPhotos from '@/components/common/pc/NestCommunityPhotos.vue'
import NestModalImgSwiper from '@/components/common/pc/NestModalImgSwiper.vue'

const houseInfoStore = useHouseInfoStore()
const showCommunitySwiper = ref(false)

// 转换房源信息列表
const houseInfoItems = computed(() => {
  return houseInfoStore.houseInfo?.menu_info?.map((item) => ({
    name: item.name,
    value: item.value,
    widthStyle: Number(item.widthStyle) || 1,
  })) ?? []
})

// 地图坐标
const lng = computed(() => Number(houseInfoStore.houseInfo?.address_x) || undefined)
const lat = computed(() => Number(houseInfoStore.houseInfo?.address_y) || undefined)

// 小区照片
const communityImages = computed(() => houseInfoStore.houseInfo?.images ?? [])
const communityCoverUrl = computed(() => communityImages.value[0] ?? '')

// 点击小区照片，展示轮播并折叠详情面板
function handlePhotoClick(): void {
  houseInfoStore.closeHouseDetailPanel()
  showCommunitySwiper.value = true
}
</script>

<template>
  <!-- 房源详情面板 -->
  <Transition name="panel-fade">
    <div v-if="houseInfoStore.houseDetailPanelVisible" class="house-detail-panel">
      <NestHouseInfo v-if="houseInfoItems.length" :items="houseInfoItems" />
      <NestHouseMap v-if="lng && lat" :lng="lng" :lat="lat" :zoom="15" />
      <NestCommunityPhotos 
        v-if="communityImages.length" 
        :cover-url="communityCoverUrl" 
        :count="communityImages.length"
        @photo-click="handlePhotoClick" 
      />
    </div>
  </Transition>

  <!-- 小区照片轮播弹层 -->
  <NestModalImgSwiper
    v-if="showCommunitySwiper && communityImages.length"
    :images="communityImages"
    :show-close="true"
    @close="showCommunitySwiper = false"
  />
</template>
```

### 4.4 经纪人信息（底部工具栏）

在 `NestBottomBar.vue` 中展示经纪人信息：

```vue
<script setup lang="ts">
import { computed } from 'vue'
import { useHouseInfoStore } from '@/stores/house-info'
import defaultHeadIcon from '@/assets/icons/default_head_icon.png'

const houseInfoStore = useHouseInfoStore()

// 是否有经纪人信息
const hasBrokerInfo = computed(() => {
  const info = houseInfoStore.houseInfo
  return !!(info?.middleman || info?.middleman_tel)
})

// 经纪人头像（无则使用默认头像）
const brokerAvatar = computed(() => 
  houseInfoStore.houseInfo?.middleman_img || defaultHeadIcon
)

// 经纪人姓名
const brokerName = computed(() => houseInfoStore.houseInfo?.middleman || '')

// 经纪人电话
const brokerTel = computed(() => houseInfoStore.houseInfo?.middleman_tel || '')
</script>

<template>
  <div class="bottom-bar__broker">
    <img :src="brokerAvatar" class="bottom-bar__avatar" />
    <template v-if="hasBrokerInfo">
      <div class="bottom-bar__broker-info">
        <span class="bottom-bar__broker-name">{{ brokerName }}</span>
        <span class="bottom-bar__broker-label">经纪人</span>
      </div>
      <span v-if="brokerTel" class="bottom-bar__broker-tel">{{ brokerTel }}</span>
    </template>
    <template v-else>
      <span class="bottom-bar__text">经纪人</span>
    </template>
  </div>
</template>
```

---

## 五、公共组件说明

### 5.1 NestHouseInfo - 房源信息组件

展示名值对列表，采用两列网格布局。

**Props：**

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `title` | `string` | `'房源信息'` | 区块标题 |
| `items` | `HouseInfoItem[]` | `[]` | 名值对列表 |
| `showMore` | `boolean` | `false` | 是否显示「查看更多」 |

**HouseInfoItem 类型：**

```typescript
interface HouseInfoItem {
  name: string       // 字段名
  value: string      // 字段值
  widthStyle?: number // 展示样式：1-3 单行，4-5 占满整行
}
```

### 5.2 NestHouseMap - 百度地图组件

展示房源位置的百度地图。

**Props：**

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `ak` | `string` | - | 百度地图 API 密钥 |
| `lng` | `number` | - | 经度 |
| `lat` | `number` | - | 纬度 |
| `zoom` | `number` | `15` | 缩放级别 |
| `showMarker` | `boolean` | `true` | 是否显示标记点 |

### 5.3 NestCommunityPhotos - 小区照片组件

展示小区首图和照片数量。

**Props：**

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `coverUrl` | `string` | `''` | 首图 URL |
| `count` | `number` | `0` | 照片张数 |
| `layout` | `'single' \| 'double'` | `'single'` | 布局模式 |

**Events：**

| 事件 | 说明 |
|------|------|
| `photo-click` | 点击小区图时触发 |

### 5.4 NestModalImgSwiper - 图片轮播组件

全屏图片轮播弹层，支持主图与缩略图联动。

**Props：**

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `images` | `string[]` | - | 图片 URL 列表 |
| `autoPlay` | `boolean` | `true` | 是否自动播放 |
| `showClose` | `boolean` | `false` | 是否显示关闭按钮 |
| `loop` | `boolean` | `true` | 是否循环播放 |

**Events：**

| 事件 | 说明 |
|------|------|
| `close` | 点击关闭按钮时触发 |

---

## 六、与讲房模式的关系

房源详情与讲房模式相互独立，需要处理以下交互：

1. **讲房时隐藏顶部栏**：通过 `introduceStore.panelVisible` 判断
2. **讲房时自动关闭详情面板**：监听 `introduceStore.panelVisible` 变化
3. **讲房房源信息独立**：讲房模块有独立的 `NestLectureHouseInfo` 组件

```typescript
// 在 NestHouseTopBar 中
watch(() => introduceStore.panelVisible, (visible) => {
  if (visible) houseInfoStore.closeHouseDetailPanel()
})
```

---

## 七、本仓库实现参考

| 文件 | 说明 |
|------|------|
| `src/api/getHouseInfo.ts` | 房源详情 API 接口（含 Mock 数据） |
| `src/stores/house-info.ts` | 房源详情状态管理 |
| `src/types/house-detail.ts` | 房源详情类型定义 |
| `src/modules/pc/house-info/NestHouseTopBar.vue` | 顶部标题栏组件 |
| `src/modules/pc/house-info/NestHouseDetailPanel.vue` | 详情面板容器组件 |
| `src/modules/pc/toolbar/NestBottomBar.vue` | 底部工具栏（含经纪人信息） |
| `src/components/common/pc/NestHouseInfo.vue` | 房源信息公共组件 |
| `src/components/common/pc/NestHouseMap.vue` | 百度地图公共组件 |
| `src/components/common/pc/NestCommunityPhotos.vue` | 小区照片公共组件 |
| `src/components/common/pc/NestModalImgSwiper.vue` | 图片轮播公共组件 |

---

## 八、常见问题

### 8.1 房源详情接口返回为空

检查以下配置：
- URL 参数是否包含 `m`（模型 ID）和 `house_id`（房源 ID）
- 环境变量 `VITE_APP_GET_HOUSE_INFO_API_HOST` 是否正确配置
- 接口响应的 `code` 是否为 1

### 8.2 地图不显示

检查以下配置：
- 环境变量 `VITE_APP_BAIDU_MAP_AK` 是否正确配置
- 房源数据中 `address_x` 和 `address_y` 是否有值
- 百度地图 API 密钥是否有效
- 是否配置了正确的域名白名单

### 8.3 经纪人信息不显示

检查房源数据中是否包含以下字段：
- `middleman`：经纪人姓名
- `middleman_img`：经纪人头像
- `middleman_tel`：经纪人电话

至少需要 `middleman` 或 `middleman_tel` 有值才会显示详细信息。

---

以上完成即可在你自己的项目中实现房源详情功能。
