# Rental Estimate — 前端完整文案与流程(1:1 逐字版)

> 来源文件:`resources/views/public/rental-estimate/index.blade.php`(文案直接写在模板里,全中文单语)
> **原文件 verbatim 副本可直接下载:`/rental-estimate-index.blade.php.txt`** — 要 1:1 port 请以该文件为准,本文档是可读版。
> 页面无独立文案数据文件(与 property-match 的 `PropertyMatchData.php` 不同)。

---

## 1. 页面级文案

| 位置 | 文案 |
| --- | --- |
| `<title>` | 租金估算 · 翻新提升 · PropertyLab |
| `<meta description>` | 免费估算你的单位市场月租 —— 定位、卧室、家私状况,即时读取我们的在管租金数据。想提高租金?一键联系我们的翻新团队。 |
| 页头品牌 | PropertyLab **· 租金估算**(后半蓝色) |
| 进度条 | 6 段,`step>=n` 亮蓝 |

**全局状态**(Alpine `rentWizard()`):`step`(1–6)、`lat/lng/place`、`beds`、`size`、`furnish`、`name`(楼盘)、`cName/cEmail/cPhone`(联系人)、`subId`(提交后的 DB id)、`est`(估值结果)、`loading`、`q/preds/searching`(搜索)。

**底部 sticky 导航**(毛玻璃):
- 「上一步」— step>1 显示(灰色按钮,`back()`)
- 主按钮文字随步骤变:step 1–3 →「**继续**」;step 4 →「**提交 · 查看租金**」;step 5 →「**下一步 →**」(loading 时整颗按钮隐藏)
- step 6 → 绿色链接按钮「**立即咨询翻新**」(= WhatsApp CTA,同 `waLink()`)
- 不满足 `canNext()` 时主按钮 disabled(半透明)

---

## 2. 第 1 步 · 定位

| 元素 | 文案 |
| --- | --- |
| Eyebrow | 第 1 步 · 定位 |
| 标题 | 你的单位在哪里? |
| 副文 | 输入楼盘名称或地址,从列表选择,我们会自动读取 GPS 定位。 |
| 搜索框 placeholder | 搜索楼盘名 / 地址… |
| 下拉分组标题 1 | 地点 · Locations |
| 下拉分组标题 2 | 楼盘 · Properties |
| 地图下方提示 | 📍 选择后可拖动定位针微调 |
| 无 Mapbox token 时 | 未配置地图 |
| 已定位回显 | 📍 已定位:**{place 或 "lat, lng"(5 位小数)}** |

**搜索行为**(`onSearch()` → `_doSearch()`):
- 350ms debounce,≥2 字符才查;Esc 关闭下拉;点击外部关闭
- **并发两路,结果合并(locations 在前)**:
  1. **Nominatim**(OpenStreetMap,免 key):`https://nominatim.openstreetmap.org/search?format=json&countrycodes=my&limit=4&q={q}, Malaysia` — 取 `display_name` 第一段做主名(按名称去重),第 2–3 段做副标;type 映射标签:suburb/neighbourhood/residential→`Area`、city/town→`City`、state→`State`(绿色图钉图标 + 绿标签)
  2. **自家 DB**:`GET /rental-estimate/search?q=` → `edgeprop_projects` 最多 6 条(蓝色楼房图标 + `RM {psf} psf` 蓝标签)
- 搜索中右侧转圈 spinner

**选中**(`pickPlace`):取 lat/lng(6 位小数)、`place = name · subtitle`、搜索框回填名称;若第 4 步「楼盘名称」还空,自动带入;地图落针。

**地图**(Mapbox GL v3.7.0,token 来自 `config('services.mapbox.token')`):
- 初始中心 KL `[101.6958, 3.1466]` zoom 10,style `mapbox://styles/mapbox/standard`,`language:'zh-Hans'`,lightPreset day,右上导航控件
- **点地图任意处**直接定位(清空 place 标签);**蓝色 marker 可拖**,dragend 更新坐标

**校验**:必须有 lat + lng 才能「继续」。

---

## 3. 第 2 步 · 卧室

| 元素 | 文案 |
| --- | --- |
| Eyebrow | 第 2 步 · 卧室 |
| 标题 | 几间卧室? |
| 副文 | 选择你单位的卧室数量。 |
| Pills | `1房` `2房` `3房` `4房` `5房` + `Studio`(小字 `开放式`,值 = 0) |
| 面积 label | 建筑面积 (sqft) **· 可留空,按房数估算**(后半灰色) |
| 面积 placeholder | 如 850 |

**校验**:`beds !== null`(面积可空;服务器默认值:Studio 450 / 1房 650 / 2房 850 / 3房 1100 / 4房 1500 / 5房 2000)。

---

## 4. 第 3 步 · 家私

| 元素 | 文案 |
| --- | --- |
| Eyebrow | 第 3 步 · 家私 |
| 标题 | 家私状况? |
| 副文 | 家私程度直接影响你的月租水平。 |

三张选卡(图标 + 主标题 + 副文,选中蓝边 + 右侧 ✓):

| value | 主标题 | 副文 |
| --- | --- | --- |
| `bare` | 空屋(无家私) | Bare unit · 只有基本装修 |
| `partial` | 部分家私 | Partially furnished · 厨具 + 冷气 + 热水器 |
| `fully` | 全套家私 | Fully furnished · 拎包入住 |

**校验**:必须选一个。(对应估算系数:bare ×0.85 / partial ×1.0 / fully ×1.15)

---

## 5. 第 4 步 · 确认 · 提交

| 元素 | 文案 |
| --- | --- |
| Eyebrow | 第 4 步 · 确认 · 提交 |
| 标题 | 确认楼盘,留下联系方式 |
| 副文 | 确认楼盘名称并留下联系方式,提交后即可查看你的市场租金。 |
| 楼盘名 label | 楼盘名称 |
| 楼盘名 placeholder | 如 Sunway Velocity、Mont Kiara… |
| 回显卡第 1 行 | 📍 位置:**{place 或 4 位小数坐标 或 "未定位"}** |
| 回显卡第 2 行 | 🛏️ 卧室:**{Studio / N 房 / —}** · 🛋️ **{家私中文}**( · **{size} sqft** 若有) |
| 姓名 label | 姓名 * |
| 姓名 placeholder | 你的称呼 |
| 手机 label | 手机号码 * |
| 手机 placeholder | +60 12-345 6789 |
| 邮箱 label | 邮箱(选填) |
| 邮箱 placeholder | name@example.com |

- 楼盘名输入带 **`<datalist>` autocomplete**(服务器预载 ≤800 个 MY 楼盘名:`rental_properties` ∪ `ih_my_projects` 各 400,去重/去纯数字/≥3 字符)
- 服务器 422 错误信息(红字显示在表单下):`请先在第一步定位你的单位。` / `请填写姓名和手机号码。`

**校验**:楼盘名 ≥2 字符 + 姓名非空 + 手机数字 ≥7 位。按「提交 · 查看租金」→ `submitLead()`:立即跳 step 5 进 loading,POST `/rental-estimate/submit`,**30 秒 AbortController 超时**。

---

## 6. 第 5 步 · 市场租金(结果页)

| 元素 | 文案 |
| --- | --- |
| Eyebrow | 第 5 步 · 市场租金 |
| 标题 | {楼盘名,空则「你的单位」} |
| 副文 | {Studio / N房} · {家私中文} |

**Loading 卡**(spinner):
- 主文:`正在分析周边同户型实际租金…`
- 副文:`读取周边成交数据,约需 5–15 秒 · 请稍候`

**估值命中卡**(`est.found === true`,深蓝渐变):
- label:`预估市场月租`
- 大数字:`RM {est.mid}`(千分位)
- 区间行:`合理区间 RM {low} – {high} / 月`
- note(服务器生成):`基于附近 {N} 套同户型({LAYOUT})实际租金 · 中位数已按「{家私中文}」调整`

**样本不足卡**(`est.found === false`,灰渐变):
- label:`附近同户型样本不足`
- 大字:`顾问为你精准估算`
- note(服务器按情况三选一):
  - 有少量样本:`附近同户型样本较少,仅供参考 —— 顾问会为你精准估算。`
  - 无样本:`附近暂无同户型租金样本 —— 顾问会在 24 小时内为你精准估算。`
  - 引擎异常:`暂时无法读取周边租金数据 —— 顾问会在 24 小时内为你精准估算。`

**前端兜底 note**(fetch 异常/超时,不来自服务器):
- 返回体不含估值:`我们已收到你的信息 —— 顾问会在 24 小时内为你精准估算。`
- fetch 超时/网络错误:`估算用时较长 —— 我们已收到你的信息,顾问会尽快为你估算并回复。`

**周边对比列表**(`est.comps` 非空时,最多 80 条):
- 标题行:`周边同户型租金` + 右侧 `{N} 套 · 按距离排序`
- 每行:楼盘名(粗)+ 小字 `{type} · {completion_year}` | 距离(<1km 显示 `{米}m`,否则 `{x.x}km`)| `RM {rent}`
- 底注:`数据源:PropSense 周边实际整租(3 公里内)· 与门户分析同源`

---

## 7. 第 6 步 · 翻新 Upsell

| 元素 | 文案 |
| --- | --- |
| Eyebrow | 最后一步 |
| 标题 | 想让租金更上一层楼? |
| 副文 | 好的翻新能显著提高月租与出租速度。让我们的团队帮你评估。 |
| 绿色徽章 | ↑ 翻新后,月租平均可提升 15–35% |
| 卡标题 | PropertyLab 翻新团队 |
| 卡正文 | 从设计、报价到施工一站式。加 WhatsApp,我们按你的单位情况给出翻新建议与投资回报测算。 |
| WhatsApp 按钮(绿) | 想翻新,联系我们 |
| 底部导航按钮 | 立即咨询翻新 |

**WhatsApp 预填消息模板**(`waLink()`,号码 `601133167831`):

```
你好!我想咨询翻新服务 🏠
楼盘:{name 或 —}
卧室:{Studio 或 N房 或 —}
面积:{size} sqft          ← 仅当填了面积才有这一行
家私:{空屋(无家私) / 部分家私 / 全套家私}
位置:https://maps.google.com/?q={lat},{lng}   ← 未定位则显示「未定位」
预估月租:RM {low}–{high}/月   ← 无估值则显示「待估算」
想了解如何通过翻新提高我的租金。
```

**点击追踪**(`waClick()`):有 `subId` 才发,POST `/rental-estimate/wa-click` `{id}`,`keepalive:true`(跳去 WhatsApp 页面卸载也能送达);服务器 `wa_clicked_at = now()`、`wa_clicks +1`。两个按钮(卡内 + 底部导航)都绑定。

---

## 8. Port 清单(1:1 搬迁时需要的东西)

| 项 | 来源 | 说明 |
| --- | --- | --- |
| 前端整页 | `/rental-estimate-index.blade.php.txt` | Blade;`{{ route(...) }}` ×3、`{{ $waNumber }}`、`@json($mapToken)`、`$names` datalist 循环需按新项目改 |
| 后端 controller | `app/Http/Controllers/PublicSite/RentalEstimateController.php` | page/search/submit/waClick + `computeEstimate()`(常量:`WA_NUMBER`、`DEFAULT_SIZE`) |
| 租金引擎 | `app/Services/InvestHink/PropSenseService` | **重依赖** — 需要周边租金数据源(`whole_unit_rental` comparable);没有它就没有估值 |
| 楼盘搜索数据 | `edgeprop_projects` 表 | search endpoint 用(project_name/area/psf_median/经纬度) |
| Datalist 名单 | `rental_properties` + `ih_my_projects` 表 | 没有可以留空数组,功能不受影响 |
| Model + migrations | `RentalEstimateSubmission` + 2 个 migration | 字段表见 `/rental-estimate-public.md` §6 |
| Routes | `routes/web.php:2965-2971` | 4 public + 1 admin |
| 外部 | Mapbox token(`services.mapbox.token`)、Nominatim(免 key)、WhatsApp 号 | Nominatim 有使用政策,量大需自架或换 geocoder |
| Admin 页 | `resources/views/investhink/rental-estimate-admin/index.blade.php` + controller | 纯只读,见 `/rental-estimate-admin.md` |
