This commit is contained in:
谢宇宁
2026-09-15 16:25:45 +07:00
parent da39ab9a2a
commit 2d63d37930
449 changed files with 25632 additions and 24085 deletions
@@ -0,0 +1,519 @@
# 黄果短剧落地页(电脑版 5 变体)实施计划
> **For agentic workers:** 用 superpowers:executing-plans 或 subagent-driven-development 按 Task 执行。步骤用 `- [ ]` 勾选。
>
> **用户规则覆盖(最高优先级):**
> - 不强制 TDD:本计划不写"先写测试"步骤。
> - 收尾只做静态检查(`pnpm exec tsc --noEmit` / `pnpm build`),不跑单测/E2E。
> - 不自动 commit / push / 建 PR:除非用户明确说"提交"。
**Goal:** 把 repo 改造为黄果短剧落地页:1 个共用电脑模板 `PcLanding.vue` + 5 变体素材(背景大图 + 底部封面),进站 5 路随机分流 A~E,并更新 index.html 为简体黄果 SEO。
**Architecture:** Vue 3 `<script setup lang="ts">` + Vite + SCSS`zoom` 缩放(基于 1920×1080),绝对定位 + 图片叠加,`<picture>` webp+png。版面只写一次,`variants.ts` 映射每变体的 `bg`/`covers`。共用元素(logo/主文案/二维码文字烤成图,导航程式画)全变体复用。二维码复用 `DesktopQrcode`
**Tech Stack:** Vue 3 + Vite + SCSS + `qrcode.vue`Figma MCP `get_screenshot`(导出节点 PNG+ `curl` 下载;`pnpm webp`sharp q80)生成 webp。
**规格来源:** `docs/superpowers/specs/2026-05-29-huangguo-landing-design.md`
---
## 关键设计参数(Figma `yD7lloED2Okdp1FJTYvhe3`,变体 1 `73:53`5 变体共用版面)
画布 1920×1080,全部绝对坐标。
| 元素 | 位置 (x,y) | 尺寸 | 说明 |
|---|---|---|---|
| 背景大图 | 0,0 | 1920×1080 | 每变体不同,含底部深色渐变 |
| 顶部 pill 条 | 360,0 | 1200×70 | 半透明圆角,水平居中(CSS 画) |
| logo 锁定组 | 420,15 | 154×40 | 烤图(icon+黄果短剧+标语) |
| 联系方式 容器 | 1094,23 | 406×24 | 3 项 + 圆点分隔(程式画) |
| 主文案 | 204,308 | 630×381 | 烤图 |
| 二维码本体 | 204,750 | 180×180 | 白底圆角,内 150×150 动态二维码 |
| 二维码文字块 | 408,757 | 502×166 | 烤图 |
| 底部封面行 | 1184,778 | 606×152 | 每变体不同,烤成长条图 |
联系方式 3 项(相对 406×24 容器,容器位于顶部内容 (734,8)):
- 官方群组:item @x=0icon 24×24 + 文字 @x=30
- 圆点分隔 @x=124(竖排 4 点,2×2,间距 4px)
- 商务合作:item @x=156
- 圆点分隔 @x=280
- 渠道合作:item @x=312
导出节点 ID
| 变体(版本) | 背景 frame | 轮动 cover row |
|---|---|---|
| 1 (A) | `73:54` | `81:766` |
| 2 (B) | `73:112` | `81:735` |
| 3 (C) | `73:169` | `81:704` |
| 4 (D) | `73:225` | `81:673` |
| 5 (E) | `73:281` | `73:356` |
共用元素(从变体 1 导出):logo `73:76`、主文案 `73:62`、二维码文字 `73:69`、官方群组 icon `73:83`、商务合作 icon `73:93`、渠道合作 icon `73:104`
---
## File Structure
```
src/
├── pages/index.vue # 改:设备检测 + 5 路随机 A~E
├── components/landing/PcLanding.vue # 新:电脑共用模板
├── config/variants.ts # 新:变体素材映射 + 导航链接常量
├── components/desktop/qrcode.vue # 复用(必要时微调 .qr-inner 尺寸)
└── assets/image/hg/
├── common/ logo / theme / qr-text / icon-tg / icon-biz / icon-channel (.png+.webp)
└── pc/ bg1..bg5 / covers1..covers5 (.png+.webp)
index.html # 改:简体黄果 SEO
```
---
# Tasks
## Task 1:从 Figma 导出全部素材 PNG
**Files:**
- Create: `src/assets/image/hg/common/{logo,theme,qr-text,icon-tg,icon-biz,icon-channel}.png`
- Create: `src/assets/image/hg/pc/{bg1..bg5,covers1..covers5}.png`
导出方式:对每个节点调用 `mcp__figma__get_screenshot`fileKey `yD7lloED2Okdp1FJTYvhe3`),拿到 `image_url` 后用 `curl -s -o <目标路径> "<url>"` 下载。背景用默认整合渲染;其余叠加元素用 `contentsOnly: true`(透明底)。
- [ ] **Step 1.1:建目录**
```bash
mkdir -p src/assets/image/hg/common src/assets/image/hg/pc
```
- [ ] **Step 1.2:导出 5 张背景**`maxDimension: 1920`,默认模式)
- 73:54 → `src/assets/image/hg/pc/bg1.png`
- 73:112 → `bg2.png`
- 73:169 → `bg3.png`
- 73:225 → `bg4.png`
- 73:281 → `bg5.png`
- [ ] **Step 1.3:导出 5 张封面长条**`contentsOnly: true``maxDimension: 1212` ≈2×)
- 81:766 → `covers1.png`81:735 → `covers2.png`81:704 → `covers3.png`81:673 → `covers4.png`73:356 → `covers5.png`
- [ ] **Step 1.4:导出共用元素**`contentsOnly: true`
- 73:76 → `common/logo.png``maxDimension: 308`
- 73:62 → `common/theme.png``maxDimension: 1260`
- 73:69 → `common/qr-text.png``maxDimension: 1004`
- 73:83 → `common/icon-tg.png``maxDimension: 72`
- 73:93 → `common/icon-biz.png``maxDimension: 72`
- 73:104 → `common/icon-channel.png``maxDimension: 72`
- [ ] **Step 1.5:核对**
```bash
ls -la src/assets/image/hg/common src/assets/image/hg/pc
```
Expected14 个 png,皆非 0 字节。用 Read 抽看 `theme.png`/`qr-text.png`/`logo.png` 确认是透明底、无背景大图穿透;若穿透,改该节点为 `contentsOnly: true` 重导,或退化为 CSS 文字渲染(见 Task 3 备注)。
- [ ] **Step 1.6:生成 webp**
```bash
pnpm webp
```
Expected:上述 png 同目录生成同名 `.webp`
---
## Task 2:建立 `src/config/variants.ts`
**Files:**
- Create: `src/config/variants.ts`
- [ ] **Step 2.1:写入完整内容**
```ts
import bg1 from '~/assets/image/hg/pc/bg1.png'
import bg1W from '~/assets/image/hg/pc/bg1.webp'
import bg2 from '~/assets/image/hg/pc/bg2.png'
import bg2W from '~/assets/image/hg/pc/bg2.webp'
import bg3 from '~/assets/image/hg/pc/bg3.png'
import bg3W from '~/assets/image/hg/pc/bg3.webp'
import bg4 from '~/assets/image/hg/pc/bg4.png'
import bg4W from '~/assets/image/hg/pc/bg4.webp'
import bg5 from '~/assets/image/hg/pc/bg5.png'
import bg5W from '~/assets/image/hg/pc/bg5.webp'
import c1 from '~/assets/image/hg/pc/covers1.png'
import c1W from '~/assets/image/hg/pc/covers1.webp'
import c2 from '~/assets/image/hg/pc/covers2.png'
import c2W from '~/assets/image/hg/pc/covers2.webp'
import c3 from '~/assets/image/hg/pc/covers3.png'
import c3W from '~/assets/image/hg/pc/covers3.webp'
import c4 from '~/assets/image/hg/pc/covers4.png'
import c4W from '~/assets/image/hg/pc/covers4.webp'
import c5 from '~/assets/image/hg/pc/covers5.png'
import c5W from '~/assets/image/hg/pc/covers5.webp'
export interface PcVariant {
bg: string
bgW: string
covers: string
coversW: string
}
export const PC_VARIANTS: PcVariant[] = [
{ bg: bg1, bgW: bg1W, covers: c1, coversW: c1W },
{ bg: bg2, bgW: bg2W, covers: c2, coversW: c2W },
{ bg: bg3, bgW: bg3W, covers: c3, coversW: c3W },
{ bg: bg4, bgW: bg4W, covers: c4, coversW: c4W },
{ bg: bg5, bgW: bg5W, covers: c5, coversW: c5W },
]
export const VERSION_LETTERS = ['A', 'B', 'C', 'D', 'E'] as const
// 导航链接:官方群组已定,其余为占位,给到网址改这里即可
export const NAV_LINKS = {
group: 'https://t.me/huangguoshortdrama',
biz: '#TODO-business',
channel: '#TODO-channel',
}
```
---
## Task 3:建立 `src/components/landing/PcLanding.vue`
**Files:**
- Create: `src/components/landing/PcLanding.vue`
- [ ] **Step 3.1:写入完整 SFC**
```vue
<template>
<div class="hg-pc">
<!-- 背景大图变体 -->
<div class="bg">
<picture>
<source type="image/webp" :srcset="v.bgW" />
<img :src="v.bg" alt="" />
</picture>
</div>
<!-- 顶部 pill 导航条 -->
<div class="top">
<div class="logo">
<picture>
<source type="image/webp" :srcset="logoW" />
<img :src="logo" alt="黄果短剧" />
</picture>
</div>
<div class="nav">
<a class="item" :href="NAV_LINKS.group">
<picture>
<source type="image/webp" :srcset="iconTgW" />
<img :src="iconTg" alt="官方群组" />
</picture>
<span>官方群組</span>
</a>
<i class="dot" />
<a class="item" :href="NAV_LINKS.biz">
<picture>
<source type="image/webp" :srcset="iconBizW" />
<img :src="iconBiz" alt="商务合作" />
</picture>
<span>商務合作</span>
</a>
<i class="dot" />
<a class="item" :href="NAV_LINKS.channel">
<picture>
<source type="image/webp" :srcset="iconChW" />
<img :src="iconCh" alt="渠道合作" />
</picture>
<span>渠道合作</span>
</a>
</div>
</div>
<!-- 主文案 -->
<div class="theme">
<picture>
<source type="image/webp" :srcset="themeW" />
<img :src="theme" alt="每一帧都能解你的色" />
</picture>
</div>
<!-- 二维码区 -->
<div class="qr">
<div class="qr-box">
<DesktopQrcode />
</div>
<div class="qr-text">
<picture>
<source type="image/webp" :srcset="qrTextW" />
<img :src="qrText" alt="" />
</picture>
</div>
</div>
<!-- 底部封面行变体 -->
<div class="covers">
<picture>
<source type="image/webp" :srcset="v.coversW" />
<img :src="v.covers" alt="" />
</picture>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, onBeforeUnmount } from 'vue'
import DesktopQrcode from '~/components/desktop/qrcode.vue'
import { PC_VARIANTS, NAV_LINKS } from '~/config/variants'
import logo from '~/assets/image/hg/common/logo.png'
import logoW from '~/assets/image/hg/common/logo.webp'
import theme from '~/assets/image/hg/common/theme.png'
import themeW from '~/assets/image/hg/common/theme.webp'
import qrText from '~/assets/image/hg/common/qr-text.png'
import qrTextW from '~/assets/image/hg/common/qr-text.webp'
import iconTg from '~/assets/image/hg/common/icon-tg.png'
import iconTgW from '~/assets/image/hg/common/icon-tg.webp'
import iconBiz from '~/assets/image/hg/common/icon-biz.png'
import iconBizW from '~/assets/image/hg/common/icon-biz.webp'
import iconCh from '~/assets/image/hg/common/icon-channel.png'
import iconChW from '~/assets/image/hg/common/icon-channel.webp'
const props = defineProps<{ variant: number }>()
const v = computed(() => PC_VARIANTS[props.variant] ?? PC_VARIANTS[0])
function resizeBox() {
const w = window.innerWidth
const h = window.innerHeight
const ratio = w / h
const pct = ratio >= 1920 / 1080 ? (h / 1080) * 100 : (w / 1920) * 100
document.body.style.cssText = 'zoom:' + pct + '%'
}
onMounted(() => {
resizeBox()
window.addEventListener('resize', resizeBox)
})
onBeforeUnmount(() => window.removeEventListener('resize', resizeBox))
</script>
<style lang="scss" scoped>
img { -webkit-user-drag: none; }
.hg-pc {
user-select: none;
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
.bg {
position: absolute;
inset: 0;
z-index: 0;
img { width: 100%; height: 100%; object-fit: cover; }
}
.top {
position: absolute;
left: 360px;
top: 0;
width: 1200px;
height: 70px;
z-index: 3;
display: flex;
align-items: center;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(10px);
border-radius: 0 0 20px 20px;
.logo {
position: absolute;
left: 60px;
top: 15px;
width: 154px;
height: 40px;
img { width: 100%; height: 100%; object-fit: contain; }
}
.nav {
position: absolute;
left: 734px;
top: 23px;
height: 24px;
display: flex;
align-items: center;
gap: 32px;
.item {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
text-decoration: none;
img { width: 24px; height: 24px; display: block; }
span { color: #fff; font-size: 16px; line-height: 1; white-space: nowrap; }
}
.dot {
width: 2px;
height: 14px;
background-image: radial-gradient(rgba(255,255,255,0.6) 1px, transparent 1px);
background-size: 2px 4px;
}
}
}
.theme {
position: absolute;
left: 204px;
top: 308px;
width: 630px;
height: 381px;
z-index: 2;
pointer-events: none;
img { width: 100%; height: 100%; object-fit: contain; }
}
.qr {
position: absolute;
left: 204px;
top: 750px;
width: 706px;
height: 180px;
z-index: 2;
.qr-box {
position: absolute;
left: 0;
top: 0;
width: 180px;
height: 180px;
background: #fff;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.qr-text {
position: absolute;
left: 204px;
top: 7px;
width: 502px;
height: 166px;
img { width: 100%; height: 100%; object-fit: contain; }
}
}
.covers {
position: absolute;
left: 1184px;
top: 778px;
width: 606px;
height: 152px;
z-index: 2;
pointer-events: none;
img { width: 100%; height: 100%; object-fit: contain; }
}
}
</style>
```
> **备注(若 Task 1.5 发现 theme/qr-text/logo 透明导出失败):** 把对应 `<picture>` 换成 CSS 文字。logo 文字「黃果短劇」18px bold + 标语 12px;主文案标题约 60px bold + 副标 18px;二维码文字三行(24px / 18px semibold / 13px regular)。字体用系统 CJK 栈 `"PingFang SC","Microsoft YaHei",sans-serif`。
---
## Task 4:改造 `src/pages/index.vue` 为 5 路随机分流
**Files:**
- Modify: `src/pages/index.vue`
- [ ] **Step 4.1:替换为以下完整内容**
```vue
<script setup>
import PcLanding from '~/components/landing/PcLanding.vue'
import { computed, onBeforeMount, onBeforeUnmount, ref } from 'vue'
import { useEventTracker, getSessionTraceId } from '~/utils/useEventTracker'
import { VERSION_LETTERS } from '~/config/variants'
const isMobile = computed(() =>
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
)
const variantIndex = ref(0)
const version = computed(() => VERSION_LETTERS[variantIndex.value])
const { trackLandingPageView, trackLandingPageClick, generateSessionId } = useEventTracker()
const handleClick = (e) => {
const rawText = e?.target?.alt || e.target.innerText || ''
const tabKey = rawText.replace(/[^A-Za-z0-9_一-鿿]/g, '_').replace(/_{2,}/g, '_').replace(/^_+|_+$/g, '') || ''
const isDownload = tabKey.toLowerCase() === 'download'
trackLandingPageClick({
landing_page_id: `JHA-126${version.value}`,
tab_key: tabKey,
tab_name: rawText,
click_coordinates_x: e.clientX,
click_coordinates_y: e.clientY,
click_x_percent: Math.round((e.clientX / window.innerWidth) * 100),
click_y_percent: Math.round((e.clientY / window.innerHeight) * 100),
screen_width: window.innerWidth,
screen_height: window.innerHeight,
trace_id: isDownload ? getSessionTraceId() : generateSessionId(),
})
}
onBeforeMount(() => {
variantIndex.value = Math.floor(Math.random() * 5)
trackLandingPageView({ landing_page_id: `JHA-126${version.value}` })
window.addEventListener('click', handleClick)
})
onBeforeUnmount(() => {
window.removeEventListener('click', handleClick)
})
</script>
<template>
<!-- 手机版黄果变体待做暂时桌面/手机都先用 PcLanding 占位 -->
<PcLanding :variant="variantIndex" />
</template>
<style scoped></style>
```
> **手机过渡说明:** 黄果手机版未做,此处先全部走 `PcLanding`。手机 Figma 到位后新增 `MobileLanding.vue`,模板改为 `isMobile ? MobileLanding : PcLanding`。`isMobile` 已保留备用。
---
## Task 5:更新 `index.html` 为简体黄果 SEO
**Files:**
- Modify: `index.html`
- [ ] **Step 5.1:替换 `<title>` 与各 meta**
- `<title>``黄果短剧官网 - 竖屏短剧APP下载 | 黄果`
- `description``黄果短剧官网 —— 竖屏短剧新世代。强情节、快节奏、随时随地,把最上瘾的好故事装进你的每一个碎片时间。支持安卓/iOS下载,海量都市情感、甜宠恋爱、古装传奇、悬疑反转短剧,免费在线看。`
- `keywords`:用户提供整串清单(逗号分隔)
- `og:title``黄果短剧 | 竖屏短剧APP下载官方入口``og:site_name``黄果短剧``og:url``https://huangguo.com``og:description` 同 description
- `twitter:title/description/site` 同步为黄果
- `canonical``https://huangguo.com`
- 删除 bili 专用 `rating`adult / RTA)两行
- `favicon` / `og:image`:暂留现有(开放项)
---
## Task 6:静态检查 + 浏览器目检
> 不跑测试。先静态检查,再起 dev server 目检。
- [ ] **Step 6.1:类型检查** `pnpm exec tsc --noEmit` → 无错误
- [ ] **Step 6.2:构建** `pnpm build` → 成功输出 dist/
- [ ] **Step 6.3dev 目检** `pnpm dev`,开 `http://localhost:3000/`,多刷几次确认随机命中不同变体;核对:背景大图、顶部 logo+3 导航、主文案、二维码(扫码=当前页)、底部封面行位置正确;点官方群组跳 `https://t.me/huangguoshortdrama`;缩放窗口无横向滚动条。
---
## Self-Review
- ✅ Spec §2 架构 → Task 2/3/4。✅ §3 坐标 → Task 3 样式。✅ §4 烤图/程式画 → Task 1+3。✅ §5 分流埋点 → Task 4。✅ §6 导航链接常量 → Task 2。✅ §7 二维码现状 → 复用 DesktopQrcode。✅ §8 SEO → Task 5。✅ §9 压缩 → Task 1.6 + `<picture>` + vsharp。
- Placeholderbiz/channel 链接为有意占位(规格 §10 已记),非计划缺漏。
- 类型一致:`PC_VARIANTS`/`NAV_LINKS`/`VERSION_LETTERS` 在 Task 2 定义,Task 3/4 引用一致;`PcVariant` 字段 `bg/bgW/covers/coversW` 与模板用法一致。
- 风险:Figma 文字节点透明导出可能穿背景 → Task 1.5 已加核对 + Task 3 CSS 退化备注。
@@ -0,0 +1,173 @@
# 黄果短剧落地页设计规格(电脑版 5 变体)
> **用户规则覆盖(最高优先级):**
> - 不强制 TDD:不写"先写测试"步骤。
> - 收尾只做静态检查(`tsc --noEmit` / lint / `pnpm build`),不自动跑单测/E2E。
> - 不自动 commit / push / 建 PR:除非用户明确说"提交"。
**日期:** 2026-05-29
**范围:** 本规格只覆盖**电脑版 5 个变体**。手机版 Figma 用户稍后提供,届时另起规格沿用同一套架构。
---
## 1. 目标
把当前 repo(残留的"逼哩逼哩 bili"模板)改造为**黄果短剧**落地页。电脑版有 5 个变体,版面结构完全相同,仅**背景大图**与**底部 5 张封面**不同。进站随机分流到其中之一(5 路 A~E,各 20%),并复用现有埋点。
设计来源 Figma`yD7lloED2Okdp1FJTYvhe3`
- 变体 1 `73:53`、变体 2 `73:111`、变体 3 `73:168`、变体 4 `73:224`、变体 5 `73:280`
---
## 2. 架构总览
沿用现有技术栈:Vue 3 `<script setup lang="ts">` + Vite + SCSS,电脑端 `zoom` 缩放方案(基于 1920×1080)。**不引入新依赖**。
```
src/
├── pages/index.vue # 入口:设备检测 + 5 路随机分流 A~E
├── components/landing/PcLanding.vue # 电脑版共用模板(本次重点)
├── components/landing/MobileLanding.vue # 手机版共用模板(占位,后续做)
├── components/desktop/qrcode.vue # 复用现有二维码组件
├── config/variants.ts # 变体 → 素材映射表 + 导航链接常量
└── assets/image/hg/
├── common/ # 5 变体共用素材
│ ├── logo.png / .webp # 左上 logo 锁定组(图标+黄果短剧+标语)
│ ├── theme.png / .webp # 主文案大图(每一帧都能… + 副标)
│ ├── qr-text.png / .webp # 二维码旁文字块(安卓手机 / UC扫码 / 安装提示)
│ ├── icon-tg.png / .webp # 官方群组图标
│ ├── icon-biz.png / .webp # 商务合作图标
│ └── icon-channel.png / .webp # 渠道合作图标
└── pc/ # 每变体一份
├── bg1.png … bg5.png / .webp # 背景大图 1920×1080
└── covers1.png … covers5.png / .webp # 底部封面长条 606×152(静态,5 张烤成一张)
```
**核心思路:版面只写一次。** `PcLanding.vue` 接收 `variant`0~4prop,从 `variants.ts` 取该变体的 `bg``covers` 图,其余元素(导航、主文案、二维码区)5 变体完全共用。
---
## 3. 版面坐标(取自 Figma 变体 1 `73:53`5 变体共用)
画布 1920×1080,全部为绝对坐标。
| 元素 | 位置 (x,y) | 尺寸 | 说明 |
|---|---|---|---|
| 背景大图 | 0,0 | 1920×1080 | **每变体不同**,含底部深色渐变压暗(保证文字可读) |
| 顶部 pill 导航条 | 360,0 | 1200×70 | 半透明圆角条,水平居中 |
| ├ logo 锁定组 | 420,15 | 154×40 | 图标 40×40 + "黄果短剧" + 标语,烤成一张图 |
| └ 联系方式(3 项) | 1094,23 | 406×24 | 官方群组 / 商务合作 / 渠道合作,项间有圆点分隔 |
| 主文案 | 204,308 | 630×381 | 标题"每一幀都能 解你的色" + 副标,烤成一张图 |
| 二维码区 | 204,750 | 706×180 | 左二维码 + 右文字块 |
| ├ 二维码本体 | 204,750 | 180×180 | 白底圆角,内 150×150 动态二维码 |
| └ 二维码文字块 | 408,757 | 502×166 | 安卓手机 / 建议使用UC浏览器扫码 / 安装提示,烤成一张图 |
| 底部封面行 | 1184,778 | 606×152 | **每变体不同**,5 张 110×152 封面,间距 14px,烤成一张长条图 |
> 实作计画阶段会逐变体拉取背景与封面的 imageRef / 导出节点。3 个联系方式项的精确间距(item1@0 / 分隔@124 / item2@156 / 分隔@280 / item3@312,相对 406 宽容器)届时核对。
---
## 4. 烤图 vs 程式画(沿用 pc2 约定)
| 元素 | 做法 | 共用/变体 |
|---|---|---|
| 背景大图 | `<picture>` webp+png | **变体** bg1bg5 |
| 底部 5 封面 | 烤成 606×152 长条图 | **变体** covers1covers5 |
| logo 锁定组 | 烤图 | 共用 |
| 主文案 | 烤图 | 共用 |
| 二维码旁文字 | 烤图 | 共用 |
| 3 个导航项(图标+文字) | **程式画**:可点击、可埋点 | 共用 |
| 二维码本体 | 动态 `DesktopQrcode`(编码当前网址) | 共用 |
---
## 5. 路由 / 分流 / 埋点
改造 `src/pages/index.vue`
- 设备检测沿用现有 UA 正则。
- 把当前 `Math.random() < 0.5 ? "A" : "B"` 扩成 **5 路随机**`variantIndex = Math.floor(Math.random() * 5)`0~4),`version = ["A","B","C","D","E"][variantIndex]`
- 桌面 → `<PcLanding :variant="variantIndex" />`
- 埋点照旧:`trackLandingPageView({ landing_page_id: 'JHA-126' + version })`,点击埋点 `handleClick` 逻辑保留不动。
- **手机分支(过渡):** 黄果手机版尚未做。本期先让手机分支渲染一个简单的黄果占位(或暂时保留现有 mobile 组件),等手机 Figma 到位后换成 `<MobileLanding :variant="variantIndex" />`。**实作时确认**取哪种过渡(默认:简单占位)。
---
## 6. 导航链接(`config/variants.ts` 顶部常量,易替换)
```ts
export const NAV_LINKS = {
group: 'https://t.me/huangguoshortdrama', // 官方群组(已定)
biz: '#TODO-business', // 商务合作(待用户提供)
channel: '#TODO-channel', // 渠道合作(待用户提供)
}
```
- 商务合作 / 渠道合作链接**用户尚未提供**,先放占位常量,给到网址后改一行即可。
- 点击导航项跳转 `window.location.href = NAV_LINKS.xxx`,并经由现有 `handleClick`(监听 window click,按 alt/innerText 取 tab_key)上报埋点。
---
## 7. 二维码行为
维持现状:`DesktopQrcode` 编码 `window.location.href`(手机扫码 → 打开当前落地页的手机版)。
> 用户尚未决定是否改成固定 APK/下载链接。若后续要改,只需把 `qrcode.vue` 的 `URL.value` 来源换成固定值,影响面单一。
---
## 8. index.html SEO 改造(简体,黄果短剧)
| 字段 | 新值 |
|---|---|
| `lang` | 保持 `zh-CN` |
| `<title>` | 黄果短剧官网 - 竖屏短剧APP下载 \| 黄果 |
| `description` | 黄果短剧官网 —— 竖屏短剧新世代。强情节、快节奏、随时随地,把最上瘾的好故事装进你的每一个碎片时间。支持安卓/iOS下载,海量都市情感、甜宠恋爱、古装传奇、悬疑反转短剧,免费在线看。 |
| `keywords` | 用户提供的整串清单(黄果 / 黄果短剧 / 短剧 / 竖屏短剧 / 下载 / 安卓 / iOS / 各题材 / 竞品词 等,逗号分隔) |
| `og:title` | 黄果短剧 \| 竖屏短剧APP下载官方入口 |
| `og:site_name` | 黄果短剧 |
| `og:url` / `canonical` | https://huangguo.com |
| `og:description` / twitter 同 description |
| `theme-color` 等 | 保留 |
- `favicon` / `og:image`:暂保留现有,等黄果素材到位再换(**实作开放项**)。
- 移除 `rating: adult` / RTA 等 bili 专用、与黄果无关的 meta(实作时确认)。
---
## 9. 图片压缩流程(用户明确要求)
1. 从 Figma 导出 PNG:背景用 **1×(1920×1080**,不用 2×,控制体积;图标/封面按需 1×~2×。
2.`pnpm webp``convertToWebp.js`sharp `quality: 80`)批量生成 `.webp` 同名副本。
3. 模板用 `<picture><source type="image/webp" :srcset="xxxW" /><img :src="xxx" /></picture>` 优先吃 webp、png 后备。
4. build 时 `vite-plugin-vsharp` 再压一次。
5. 背景为最大体积项,重点核对压后单张体积(目标尽量 < 300KB webp,必要时降质量或尺寸)。
---
## 10. 开放项(实作前/中确认)
1. 商务合作 / 渠道合作 链接 — 用户提供后替换占位。
2. 二维码是否改固定下载链接 — 默认维持现状。
3. 手机分支过渡方案 — 默认简单占位。
4. favicon / og:image 黄果素材 — 默认暂留现有。
5. 是否删除 / 保留旧 bili 页面(pc/pc2/mobile/mobile2)与 `project.md` — 默认**先保留**,不在本期清理。
---
## 11. 不在本期范围
- 手机版 5 变体(等 Figma)。
- 删除旧 bili 页面 / 重写 project.md。
- 新增后端接口 / 埋点字段。
- 自动 commit / 部署。
---
## 12. 自检
- ✅ 5 变体共用版面 → `PcLanding.vue` 单组件 + `variants.ts` 映射。
- ✅ 仅背景+封面随变体变 → 素材分 `common/``pc/`
- ✅ 5 路随机分流 + 埋点 A~E → `index.vue` 改造。
- ✅ 图片压缩 → `pnpm webp` + `<picture>` + vsharp。
- ✅ 官方群组链接已定,其余占位易替换。
- ⚠️ 占位项(biz/channel 链接、二维码、手机过渡、favicon)已在第 10 节列明,不阻塞电脑版实作。