初始化
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
description: 用户要求 git commit、提交代码、写 commit message 或创建提交时,必须先加载 commit-convention skill 并严格遵守
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git 提交规范自动激活
|
||||||
|
|
||||||
|
当用户要求**提交代码**、**git commit**、**写提交说明**或执行提交流程时:
|
||||||
|
|
||||||
|
1. 必须先加载 `.cursor/skills/commit-convention/SKILL.md`。
|
||||||
|
2. Agent 代提交时 subject **必须**以 `[Ai]` 开头,并写清业务变更或 Bug 修复,禁止敷衍 subject。
|
||||||
|
3. 未获用户明确授权不得 commit;不得提交 `.env` 等含密钥文件;默认不 push。
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
description: 项目通用规范,涵盖技术栈、目录结构、编码约定
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# 项目约定
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **框架**: Vue 2 + Vue CLI 4
|
||||||
|
- **路由**: Vue Router 3 (history 模式)
|
||||||
|
- **状态管理**: Vuex 3
|
||||||
|
- **语言**: JavaScript (无 TypeScript)
|
||||||
|
- **UI 库**: Element UI + Vant 2
|
||||||
|
- **样式**: SCSS + scoped + PostCSS px2rem (rootValue: 37.5)
|
||||||
|
- **移动端适配**: lib-flexible + postcss-plugin-px2rem
|
||||||
|
- **HTTP**: Axios
|
||||||
|
|
||||||
|
## 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── api/ # API 请求模块
|
||||||
|
├── assets/ # 静态资源 (图片、样式)
|
||||||
|
├── plugins/ # 插件 (Element UI、flexible 等)
|
||||||
|
├── router/ # 路由配置
|
||||||
|
├── store/ # Vuex store
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── views/ # 页面视图
|
||||||
|
│ └── land/ # 落地页
|
||||||
|
│ ├── index.vue # 主入口,根据设备/模板切换组件
|
||||||
|
│ └── components/ # 落地页子组件 (mobile1, pc2 等)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 编码规范
|
||||||
|
|
||||||
|
- 使用 ES6+ 语法,Options API 风格
|
||||||
|
- 组件通过 props 接收 `os`、`configs`、`switchData` 等父级数据
|
||||||
|
- 事件通过 `$emit` 向上传递 (`recordClick`、`getApkInfo`)
|
||||||
|
- 图片资源使用相对路径引用,放在 `assets/images/` 对应子目录下
|
||||||
|
- px 值会被 PostCSS 自动转为 rem,带 `pcm` 选择器前缀的除外
|
||||||
|
- 始终用中文回复
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
description: SCSS 样式编写规范,包含移动端适配和 px2rem 规则
|
||||||
|
globs: "**/*.vue,**/*.scss"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# SCSS 样式规范
|
||||||
|
|
||||||
|
## px2rem 适配
|
||||||
|
|
||||||
|
- 项目使用 `postcss-plugin-px2rem`,rootValue 为 **37.5**
|
||||||
|
- 设计稿以 **375px** 宽度为基准,直接写 px 值即可自动转换
|
||||||
|
- 带 `pcm` 选择器前缀的样式**不会**被转换 (PC 端样式)
|
||||||
|
|
||||||
|
## 样式写法
|
||||||
|
|
||||||
|
- 使用 `<style lang="scss" scoped>` 保证组件样式隔离
|
||||||
|
- 深层覆盖 Vant 组件样式使用 `::v-deep`:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
::v-deep .van-tabs__wrap {
|
||||||
|
.van-tab--active {
|
||||||
|
background: #f68804;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用模式
|
||||||
|
|
||||||
|
### 全屏背景图
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.contentBlock {
|
||||||
|
height: 940px;
|
||||||
|
width: 343px;
|
||||||
|
background: url("./../../../assets/images/{template}/content.webp") no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 固定定位浮层
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.floatingBox {
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 187px;
|
||||||
|
z-index: 998;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 下载按钮
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.downBtn {
|
||||||
|
border-radius: 24px;
|
||||||
|
background: #ff6f2e;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 颜色常量 (项目常用)
|
||||||
|
|
||||||
|
| 用途 | 色值 |
|
||||||
|
|------|------|
|
||||||
|
| 页面背景 | `#141414` |
|
||||||
|
| 主按钮 | `#ff6f2e` / `#f68804` |
|
||||||
|
| Tab 背景 | `#2d2c2f` |
|
||||||
|
| 弹窗按钮 | `#5b92ee` |
|
||||||
|
| 文字白色 | `#ffffff` |
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
description: Vue 组件开发规范,适用于落地页组件
|
||||||
|
globs: "**/*.vue"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue 组件规范
|
||||||
|
|
||||||
|
## 组件结构
|
||||||
|
|
||||||
|
遵循 `<template>` → `<script>` → `<style>` 顺序:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="pageName">
|
||||||
|
<!-- 内容 -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ["os", "configs", "switchData"],
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
watch: {},
|
||||||
|
mounted() {},
|
||||||
|
methods: {},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.pageName {
|
||||||
|
/* 样式 */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 命名约定
|
||||||
|
|
||||||
|
- 文件名: 小写或 camelCase (`mobile1.vue`, `selectLinePopup.vue`)
|
||||||
|
- 新落地页模板: `mobile{N}.vue` (移动端) / `pc{N}.vue` (PC 端)
|
||||||
|
- CSS 类名: camelCase (`headerBox`, `floatingBox`, `bottomBox`)
|
||||||
|
- 事件名: camelCase (`getApkInfo`, `recordClick`)
|
||||||
|
|
||||||
|
## Props 通信
|
||||||
|
|
||||||
|
落地页组件接收三个核心 props:
|
||||||
|
|
||||||
|
| Prop | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `os` | 设备信息 (isPhone, userAgent 等) |
|
||||||
|
| `configs` | 配置列表 (tg_group, potato_group 等) |
|
||||||
|
| `switchData` | 开关数据 (下载链接、功能开关) |
|
||||||
|
|
||||||
|
## 事件追踪
|
||||||
|
|
||||||
|
使用 `landingPageClick` 上报用户点击行为,传入 tab 和坐标:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { landingPageClick } from "@/utils/eventTracker";
|
||||||
|
landingPageClick(this, { tab_key, tab_name, click_coordinates_x, click_coordinates_y });
|
||||||
|
```
|
||||||
|
|
||||||
|
## 图片资源引用
|
||||||
|
|
||||||
|
图片放在 `src/assets/images/{templateName}/` 下,在 SCSS 中通过相对路径引用:
|
||||||
|
|
||||||
|
```scss
|
||||||
|
background: url("./../../../assets/images/mobile1/logo.png") no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
```
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: commit-convention
|
||||||
|
description: 为本仓库生成符合团队规范的 Git 提交信息,并在用户要求提交代码时执行完整提交流程。用于 git commit、提交代码、写 commit message、创建提交、push 前整理提交等场景;Agent 代提交时必须使用 [Ai] 标记并写清业务变更。
|
||||||
|
---
|
||||||
|
|
||||||
|
# Git 提交规范(Agent 代提交)
|
||||||
|
|
||||||
|
## 触发条件
|
||||||
|
|
||||||
|
满足任一条件,**必须先加载本 skill**,再执行 `git add` / `git commit`:
|
||||||
|
|
||||||
|
- 用户明确要求:提交、commit、git commit、保存提交、push 前先 commit 等
|
||||||
|
- 用户规则中的「committing-changes-with-git」流程已启动
|
||||||
|
|
||||||
|
**未获用户明确授权时,不得执行 commit。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent 代提交:标记规则(强制)
|
||||||
|
|
||||||
|
由 Cursor Agent 发起的提交,**必须**在 subject 前加 `[Ai]`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[Ai] <type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body 可选:业务背景、影响范围、关联 Bug>
|
||||||
|
```
|
||||||
|
|
||||||
|
- `type`:`feat` | `fix` | `refactor` | `style` | `perf` | `chore` | `docs` | `test` | `build` | `ci`
|
||||||
|
- `scope`:模块/页面,如 `benefits`、`mine`、`community`、`player`
|
||||||
|
- 可在正文末尾追加 trailer(可选):
|
||||||
|
|
||||||
|
```text
|
||||||
|
Co-Authored-By: Cursor <noreply@cursor.com>
|
||||||
|
```
|
||||||
|
|
||||||
|
用户本人手动提交、且明确说明「人工提交」时,建议使用 `[Human]`;Agent **不要**替用户假标 `[Human]`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Subject 质量(强制)
|
||||||
|
|
||||||
|
提交说明必须让人一眼看懂**改了什么业务**或**修了什么问题**。
|
||||||
|
|
||||||
|
### 必须做到
|
||||||
|
|
||||||
|
1. 先并行执行:`git status`、`git diff`(含 staged)、`git log -5 --oneline` 了解风格与变更范围
|
||||||
|
2. subject 用**完整语义**描述,优先中文;可中英混用 scope
|
||||||
|
3. 多文件、多模块时:一条 commit 只包同一业务目标;若混杂无关改动,先询问是否拆分
|
||||||
|
4. body 写:原因、用户可见变化、风险点(如有)
|
||||||
|
|
||||||
|
### 严禁(subject 不得仅为或等同于)
|
||||||
|
|
||||||
|
`test`、`修改`、`update`、`fix`、`xxx`、`111`、`wip`、`temp`、`提交`、`save`、纯标点、纯数字、单字
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```text
|
||||||
|
# ✅
|
||||||
|
[Ai] feat(benefits): 新增七日签到弹窗与连续签到奖励展示
|
||||||
|
[Ai] fix(player): 修复横屏切换后 HLS 首帧黑屏
|
||||||
|
[Ai] refactor(mine): 签到任务列表抽离 useSignTask composable
|
||||||
|
|
||||||
|
# ❌
|
||||||
|
[Ai] fix: 修改
|
||||||
|
[Ai] feat: test
|
||||||
|
修改播放器
|
||||||
|
111
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 提交流程(与 user rule 对齐)
|
||||||
|
|
||||||
|
1. `git status` + `git diff` + `git log`(可并行)
|
||||||
|
2. 根据 diff **撰写**符合本规范的 message(HEREDOC 传 `-m`)
|
||||||
|
3. 只 `git add` 与本次任务相关的文件
|
||||||
|
4. **不得**提交 `.env`、密钥、凭据类文件;若用户在 staged 中含此类文件,警告并排除
|
||||||
|
5. `git commit` 后 `git status` 确认成功
|
||||||
|
6. hook 失败时:**不要** `commit --amend`,修 message 后**新建 commit**
|
||||||
|
7. **除非用户明确要求,否则不 `git push`**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 输出给用户
|
||||||
|
|
||||||
|
提交完成后简要说明:
|
||||||
|
|
||||||
|
- commit hash(若有)
|
||||||
|
- 本次纳入的文件范围
|
||||||
|
- message 摘要(一行)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与本地 Hook / CI 的关系
|
||||||
|
|
||||||
|
本 skill 约束 **Agent 行为**。若仓库后续接入 `husky` + `commitlint` 或 CI,以仓库脚本校验为准;Agent 仍须预先满足 `[Ai]` 与 subject 质量,避免 hook 拒绝。
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
name: figma-to-landpage
|
||||||
|
description: 将 Figma 设计稿转换为 Vue 2 落地页组件。当用户提供 Figma 链接并要求生成落地页、实现设计稿、创建新模板时触发。适用于移动端和 PC 端落地页开发。
|
||||||
|
---
|
||||||
|
|
||||||
|
# Figma 转落地页
|
||||||
|
|
||||||
|
将 Figma 设计稿翻译为本项目的 Vue 2 落地页组件,保持视觉还原度。
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
|
||||||
|
### 第 1 步: 获取设计稿信息
|
||||||
|
|
||||||
|
从 Figma URL 中提取 `fileKey` 和 `nodeId`:
|
||||||
|
- URL 格式: `https://figma.com/design/:fileKey/:fileName?node-id=1-2`
|
||||||
|
- 将 `node-id` 中的 `-` 转为 `:`
|
||||||
|
|
||||||
|
依次调用:
|
||||||
|
1. `get_design_context(fileKey, nodeId)` — 获取布局、颜色、字体等结构化数据
|
||||||
|
2. `get_screenshot(fileKey, nodeId)` — 获取视觉截图作为还原参考
|
||||||
|
|
||||||
|
如果设计太复杂导致返回截断:
|
||||||
|
1. 先调 `get_metadata(fileKey, nodeId)` 获取节点树
|
||||||
|
2. 对各子节点分别调 `get_design_context`
|
||||||
|
|
||||||
|
### 第 2 步: 确定模板编号
|
||||||
|
|
||||||
|
查看 `src/views/land/components/` 下已有的模板文件:
|
||||||
|
- 移动端: `mobile1.vue`, `mobile2.vue`, ...
|
||||||
|
- PC 端: `pc1.vue`, `pc2.vue`, ...
|
||||||
|
|
||||||
|
新模板使用下一个序号。
|
||||||
|
|
||||||
|
### 第 3 步: 下载资源
|
||||||
|
|
||||||
|
将设计稿中的图片、图标导出并放入:
|
||||||
|
```
|
||||||
|
src/assets/images/{templateName}/
|
||||||
|
```
|
||||||
|
|
||||||
|
例如 `mobile3` 模板的资源放在 `src/assets/images/mobile3/`。
|
||||||
|
|
||||||
|
Figma MCP 返回的 localhost 资源链接可直接下载使用。
|
||||||
|
|
||||||
|
### 第 4 步: 创建组件文件
|
||||||
|
|
||||||
|
在 `src/views/land/components/` 下创建新的 `.vue` 文件。
|
||||||
|
|
||||||
|
**必须遵循的组件结构:**
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="mobilePage" @click="pageClick">
|
||||||
|
<!-- 顶部固定区域 -->
|
||||||
|
<div class="headerBox" @click.stop="getApkInfo(os.isPhone && switchData.shopIosDownUrl ? 'shopIos' : true, $event)">
|
||||||
|
<div class="header-bg"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 主体内容区 -->
|
||||||
|
<div class="main">
|
||||||
|
<!-- Figma 设计内容转换到这里 -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 社交浮窗 -->
|
||||||
|
<div class="floatingBox" v-if="configs && configs.length">
|
||||||
|
<div class="telegram" v-if="cTg" @click="jumpUrl(cTg)"></div>
|
||||||
|
<div class="potato" v-if="cPotato" @click="jumpUrl(cPotato)"></div>
|
||||||
|
<div class="business" v-if="cBusiness" @click="jumpUrl(cBusiness)"></div>
|
||||||
|
<div class="channel" v-if="cChannel" @click="jumpUrl(cChannel)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 下载按钮区 -->
|
||||||
|
<div class="bottomBox" @click.stop="getApkInfo(true, $event)"></div>
|
||||||
|
|
||||||
|
<!-- 安卓/iOS 提示弹窗 -->
|
||||||
|
<van-overlay :show="androidPromptShow" z-index="999" class="androidOverlay">
|
||||||
|
<!-- ... -->
|
||||||
|
</van-overlay>
|
||||||
|
<van-overlay :show="iosPromptShow" z-index="999" class="iosOverlay">
|
||||||
|
<!-- ... -->
|
||||||
|
</van-overlay>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { landingPageClick } from "@/utils/eventTracker";
|
||||||
|
export default {
|
||||||
|
props: ["os", "configs", "switchData"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
androidPromptShow: false,
|
||||||
|
iosPromptShow: false,
|
||||||
|
showTip: false,
|
||||||
|
currUrl: window.location.href,
|
||||||
|
cTg: "",
|
||||||
|
cPotato: "",
|
||||||
|
cBusiness: "",
|
||||||
|
cChannel: "",
|
||||||
|
showIosTip: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
configs(configs) {
|
||||||
|
configs.forEach((item) => {
|
||||||
|
if (item.configType === "tg_group") this.cTg = item.link;
|
||||||
|
else if (item.configType === "potato_group") this.cPotato = item.link;
|
||||||
|
else if (item.configType === "sw_cooperate") this.cBusiness = item.link;
|
||||||
|
else if (item.configType === "qd_cooperate") this.cChannel = item.link;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
pageClick(event) {
|
||||||
|
landingPageClick(this, {
|
||||||
|
tab_key: "main",
|
||||||
|
tab_name: "主页",
|
||||||
|
click_coordinates_x: event.clientX,
|
||||||
|
click_coordinates_y: event.clientY,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
doCopy() {
|
||||||
|
this.$copyText(this.currUrl).then(() => { this.showTip = false; });
|
||||||
|
},
|
||||||
|
async showInstallTutorial(type) {
|
||||||
|
if (type === "ios") this.iosPromptShow = true;
|
||||||
|
else this.androidPromptShow = true;
|
||||||
|
},
|
||||||
|
async getApkInfo(flag, event) {
|
||||||
|
let type = this.os.isPhone ? "ios" : "android";
|
||||||
|
if (flag === "shopIos") type = "shopIos";
|
||||||
|
if (flag === "androidSpare") type = "androidSpare";
|
||||||
|
if (flag && flag !== "shopIos") await this.showInstallTutorial(type);
|
||||||
|
this.$emit("recordClick", type);
|
||||||
|
setTimeout(() => { this.$emit("getApkInfo", type); }, 500);
|
||||||
|
landingPageClick(this, {
|
||||||
|
tab_name: "下载按钮",
|
||||||
|
tab_key: "download",
|
||||||
|
click_coordinates_x: event.clientX,
|
||||||
|
click_coordinates_y: event.clientY,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
jumpUrl(url) { window.open(url); },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 样式使用 SCSS + scoped,px 值基于 375 设计稿 */
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 5 步: 样式转换要点
|
||||||
|
|
||||||
|
将 Figma 设计数据转为 SCSS 时注意:
|
||||||
|
|
||||||
|
1. **尺寸**: 基于 375px 设计稿直接写 px,px2rem 自动转换
|
||||||
|
2. **颜色**: 使用 Figma 给出的精确色值
|
||||||
|
3. **图片**: 大面积背景用 webp 格式,图标用 png
|
||||||
|
4. **字体**: Figma 字体映射到系统字体栈
|
||||||
|
5. **布局**: 优先 Flex 布局,配合 `position: fixed` 实现吸顶/吸底
|
||||||
|
6. **Vant 组件覆写**: 使用 `::v-deep` 覆盖 Vant 默认样式
|
||||||
|
7. **PC 端样式**: 选择器加 `pcm` 前缀以避免 px2rem 转换
|
||||||
|
|
||||||
|
### 第 6 步: 注册组件
|
||||||
|
|
||||||
|
在 `src/views/land/index.vue` 中导入并注册新模板:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import mobileN from "./components/mobileN";
|
||||||
|
// 在 components 中注册
|
||||||
|
```
|
||||||
|
|
||||||
|
并在模板切换逻辑中添加对应条件。
|
||||||
|
|
||||||
|
### 第 7 步: 验证
|
||||||
|
|
||||||
|
- [ ] 视觉与 Figma 截图对比一致
|
||||||
|
- [ ] 移动端/PC 端分别测试
|
||||||
|
- [ ] 下载按钮、社交链接、弹窗功能正常
|
||||||
|
- [ ] 事件追踪正常上报
|
||||||
|
- [ ] 图片资源加载无误
|
||||||
|
|
||||||
|
## Figma 到 Vue 映射参考
|
||||||
|
|
||||||
|
| Figma 属性 | Vue/SCSS 对应 |
|
||||||
|
|------------|--------------|
|
||||||
|
| Auto Layout (横向) | `display: flex` |
|
||||||
|
| Auto Layout (纵向) | `display: flex; flex-direction: column` |
|
||||||
|
| Fill 颜色 | `background-color: #xxx` |
|
||||||
|
| 图片 Fill | `background: url("...") no-repeat; background-size: 100% 100%` |
|
||||||
|
| 固定位置 | `position: fixed` |
|
||||||
|
| 圆角 | `border-radius: Npx` |
|
||||||
|
| 文字样式 | `font-size`, `font-weight`, `line-height`, `color` |
|
||||||
|
| 阴影 | `box-shadow` |
|
||||||
|
| 间距 (Gap) | `gap: Npx` 或 `margin`/`padding` |
|
||||||
|
| 透明度 | `opacity` 或 `rgba()` |
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'development'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/'
|
||||||
|
VUE_APP_APP_API='/api'
|
||||||
|
VUE_APP_WEB_API=''
|
||||||
|
VUE_APP_BASE_HOSTS = ["https://d1f02e9hwxyr26.cloudfront.net","https://xa98jupp.ddcu8aw.com"]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# just a flag
|
||||||
|
ENV = 'production'
|
||||||
|
|
||||||
|
# base api
|
||||||
|
VUE_APP_BASE_API = '/api'
|
||||||
|
VUE_APP_BASE_HOSTS = ["https://d1f02e9hwxyr26.cloudfront.net","https://xa98jupp.ddcu8aw.com"]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
|
||||||
|
parserOptions: {
|
||||||
|
parser: "babel-eslint",
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
|
||||||
|
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
|
||||||
|
},
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: [
|
||||||
|
"**/__tests__/*.{j,t}s?(x)",
|
||||||
|
"**/tests/unit/**/*.spec.{j,t}s?(x)",
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
/logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# Nuxt generate
|
||||||
|
dist
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless
|
||||||
|
|
||||||
|
# IDE / Editor
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Service worker
|
||||||
|
sw.*
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Vim swap files
|
||||||
|
*.swp
|
||||||
|
mitaonewapp.tar.gz
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# sh 执行打包到正式服上
|
||||||
|
npm run build
|
||||||
|
zip -q -r dist.zip dist
|
||||||
|
|
||||||
|
scp ./dist.zip server@116.212.120.2:/home/server/91porn/ldy/pro
|
||||||
|
ssh server@116.212.120.2 "/home/server/91porn/ldy/pro/dist.sh"
|
||||||
|
if [ $? -eq 0 ];then
|
||||||
|
echo “更新成功!“
|
||||||
|
else
|
||||||
|
echo "更新失败!"
|
||||||
|
fi
|
||||||
|
rm dist.zip
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: "@vue/cli-plugin-unit-jest"
|
||||||
|
};
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "landpage",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"test-build": "vue-cli-service build --mode test",
|
||||||
|
"lint": "eslint --fix --ext .js,.vue src",
|
||||||
|
"test:unit": "vue-cli-service test:unit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"autofit.js": "^2.0.5",
|
||||||
|
"axios": "^0.19.2",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"element-ui": "^2.13.2",
|
||||||
|
"lib-flexible": "^0.3.2",
|
||||||
|
"moment": "^2.26.0",
|
||||||
|
"postcss-plugin-px2rem": "^0.8.1",
|
||||||
|
"postcss-px2rem": "^0.3.0",
|
||||||
|
"qrcode": "^1.4.4",
|
||||||
|
"register-service-worker": "^1.7.1",
|
||||||
|
"swiper": "^5.4.5",
|
||||||
|
"vant": "^2.8.4",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-analytics": "^5.22.1",
|
||||||
|
"vue-awesome-swiper": "^4.1.1",
|
||||||
|
"vue-clipboard2": "^0.3.1",
|
||||||
|
"vue-lottery": "^1.0.3",
|
||||||
|
"vue-router": "^3.3.1",
|
||||||
|
"vue-touch-hotfix": "^2.0.0-beta.4",
|
||||||
|
"vuex": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "^4.4.1",
|
||||||
|
"@vue/cli-plugin-eslint": "^4.4.1",
|
||||||
|
"@vue/cli-plugin-unit-jest": "^4.4.1",
|
||||||
|
"@vue/cli-service": "^4.4.1",
|
||||||
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
|
"@vue/test-utils": "1.0.0-beta.31",
|
||||||
|
"babel-eslint": "^10.0.3",
|
||||||
|
"babel-plugin-component": "^1.1.1",
|
||||||
|
"compression-webpack-plugin": "^1.1.12",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
|
"eslint-plugin-vue": "^6.1.2",
|
||||||
|
"node-sass": "^4.14.1",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"sass": "~1.26.5",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"vue-cli-plugin-element": "^1.0.1",
|
||||||
|
"vue-template-compiler": "^2.6.11",
|
||||||
|
"webpack-bundle-analyzer": "^4.4.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
---
|
||||||
|
项目名称: 91pron H5 客户端 # 与 DMP 或内部命名一致时请自行替换
|
||||||
|
一级部门: 北斗 # 请填写一级部门名称
|
||||||
|
二级部门: 瑞升 # 请填写二级部门名称,无则留空
|
||||||
|
|
||||||
|
DMP编码: JHA-204 # 无则留空
|
||||||
|
|
||||||
|
项目类型: H5
|
||||||
|
# 可选值:
|
||||||
|
# H5 / APP / PC / 服务 / 组件 / 工具 / AI / 中间件
|
||||||
|
|
||||||
|
开发语言: Node
|
||||||
|
# 可选值:
|
||||||
|
# GO / Java / PHP / Node
|
||||||
|
|
||||||
|
项目负责人: DN4929,DN4868 # 员工编号,多个用逗号隔开,这里的负责是这套代码的负责人
|
||||||
|
|
||||||
|
运维负责人: DN9460,DN9651,DN4792 # 负责部署、监控、K8S、发布、证书等
|
||||||
|
|
||||||
|
产品负责人: YY1145,YY1208 # 项目产品经理
|
||||||
|
|
||||||
|
项目成员: DN4929,DN4868
|
||||||
|
|
||||||
|
项目状态: 开发中
|
||||||
|
# 可选值:
|
||||||
|
# 开发中 / 维护中 / 已停止 / 已归档
|
||||||
|
|
||||||
|
项目级别: 中
|
||||||
|
# 可选值:
|
||||||
|
# 高 / 中 / 低
|
||||||
|
|
||||||
|
核心分支: main
|
||||||
|
|
||||||
|
代码仓库:
|
||||||
|
- https://bd-git.jsyyds.com/ruisheng/jha-204-91pron/frontend/91porn-landpage-client.git
|
||||||
|
|
||||||
|
最后审计人:
|
||||||
|
|
||||||
|
最后审计时间:
|
||||||
|
|
||||||
|
告警群:
|
||||||
|
|
||||||
|
备注:
|
||||||
|
- `package.json` 中 `name` 为 `landpage`,仓库/目录名为 `91pron-landpage-client`。
|
||||||
|
- 业务 `appId` 为 **204**(见 `src/views/land/index.vue`),埋点 `appId` 为 **JHA-204**(见 `public/index.html` 与 `src/utils/eventTracker.js`)。
|
||||||
|
- 接口路径前缀为 **`/plm`**,开发环境由 `vue.config.js` 代理;选线候选域名见各环境 `.env.*` 中的 `VUE_APP_BASE_HOSTS`(JSON 数组),勿将含密钥、内网地址的未脱敏内容对外发布。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. 项目说明
|
||||||
|
|
||||||
|
本仓库为面向 **移动端 H5 浏览器**(含 PC 落地页展示)的 **Vue 2 + Vue CLI 4** 单页应用,语言为 **JavaScript**(无 TypeScript)。UI 同时使用 **Vant 2** 与 **Element UI 2**,状态管理为 **Vuex 3**(当前 store 为空壳,业务数据主要在页面组件内维护),路由为 **Vue Router 3**,模式为 **`history`(HTML5 History)**(见 `src/router/index.js`)。入口为 `src/main.js`,挂载 Router、Vuex、Vant、Element、剪贴板、Swiper、Google Analytics(`vue-analytics`)等。
|
||||||
|
|
||||||
|
**业务定位:** 91pron **App 下载落地页**(Landing Page),单路由 `/` 对应 `src/views/land/index.vue`。根据设备类型(`userAgent`)与模板版本(`version`: `A` / `B`)切换子组件:
|
||||||
|
|
||||||
|
| 场景 | 组件 |
|
||||||
|
|------|------|
|
||||||
|
| 移动端 + 版本 A | `mobile1.vue` |
|
||||||
|
| 移动端 + 版本 B | `mobile2.vue` |
|
||||||
|
| PC 端 | `pc.vue`(A/B 共用) |
|
||||||
|
| 特定 PC URL | `tips.vue` |
|
||||||
|
| 接口失败选线 | `selectLinePopup.vue` |
|
||||||
|
|
||||||
|
子组件通过 props 接收 `os`、`configs`、`switchData` 等,通过 `$emit` 向上传递 `getApkInfo`、`recordClick` 等事件。
|
||||||
|
|
||||||
|
**启动流程(`land/index.vue`):**
|
||||||
|
|
||||||
|
1. 解析 URL 查询参数(`dc`、`ch`、`pc` 等)并写入 sessionStorage。
|
||||||
|
2. 调用 `getTemplate`(`/plm/okn/ijb/nuS0EDXSczKgf3Xh`)拉取按钮/下载配置(`switchData`、`configs`、各平台下载链接)。
|
||||||
|
3. 上报落地页展示埋点 `landingPageView`(`eventTracker.js` → `window.tracker` / LandingSDK)。
|
||||||
|
4. 用户点击下载时:`recordClick` 上报点击并调用 `getApkInfo`;`handleGetApkInfo` 根据终端类型跳转 iOS/Android 下载链接。
|
||||||
|
5. 接口失败时弹出 **选线弹窗**,用户选择后切换 `axios.defaults.baseURL` 并重试。
|
||||||
|
|
||||||
|
**技术栈要点:**
|
||||||
|
|
||||||
|
- 构建:**Vue CLI 4**(`vue.config.js`),生产使用 **compression-webpack-plugin** 生成 **gzip** 产物。
|
||||||
|
- 样式:**Sass/SCSS**(`node-sass` + `sass-loader`),`element-variables.scss` 定制 Element 主题。
|
||||||
|
- 适配:**lib-flexible** + **postcss-plugin-px2rem**(`rootValue: 37.5`,设计稿基准 375px);带 **`pcm`** 选择器前缀的样式不转 rem(PC 端)。
|
||||||
|
- 网络:**axios** 封装于 `src/utils/request.js`,默认 `baseURL: '/'`,业务码 `200` 视为成功。
|
||||||
|
- 埋点:`public/index.html` 异步加载 **`/sdk/landing-sdk-v1.1.1.js`**(LandingSDK),`src/utils/eventTracker.js` 封装 `landing_page_view` / `landing_page_click` 事件及设备信息解析。
|
||||||
|
- 其他:**qrcode** 生成 PC 扫码图、**vue-clipboard2** 复制渠道参数、**moment**、**swiper**、**vue-analytics**(UA-165940626-2)、**register-service-worker**(PWA 配置于 `vue.config.js`)。
|
||||||
|
|
||||||
|
**Node 版本:** 仓库 `README.md` 建议使用 **12.18.1**。
|
||||||
|
|
||||||
|
**常用脚本(`package.json`):**
|
||||||
|
|
||||||
|
| 命令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `npm run dev` | 本地开发(`vue-cli-service serve`) |
|
||||||
|
| `npm run build` | 生产构建 |
|
||||||
|
| `npm run test-build` | 测试环境构建(`--mode test`) |
|
||||||
|
| `npm run lint` | ESLint(`src` 下 `.js` / `.vue`,带 `--fix`) |
|
||||||
|
| `npm run test:unit` | Jest 单元测试 |
|
||||||
|
|
||||||
|
**开发环境代理(`vue.config.js`):**
|
||||||
|
|
||||||
|
| 路径 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `/plm` | 业务接口代理(`target` 以文件内配置为准) |
|
||||||
|
| `/dcserv` | 代理至 `https://api.shuifeng.cc`(调试用途) |
|
||||||
|
|
||||||
|
**项目目录结构(核心):**
|
||||||
|
|
||||||
|
```
|
||||||
|
91pron-landpage-client/
|
||||||
|
├── public/
|
||||||
|
│ ├── index.html # 入口 HTML,含 LandingSDK 与 SEO meta
|
||||||
|
│ └── sdk/landing-sdk-v1.1.1.js
|
||||||
|
├── src/
|
||||||
|
│ ├── main.js # 入口:Router、Vuex、Vant、Element、Analytics 等
|
||||||
|
│ ├── App.vue
|
||||||
|
│ ├── router/
|
||||||
|
│ │ └── index.js # 单路由 History 模式
|
||||||
|
│ ├── store/
|
||||||
|
│ │ └── index.js # Vuex(当前无业务 state)
|
||||||
|
│ ├── views/land/
|
||||||
|
│ │ ├── index.vue # 落地页主入口:设备判断、选线、下载逻辑
|
||||||
|
│ │ └── components/ # mobile1 / mobile2 / pc / pc2 / tips / selectLinePopup
|
||||||
|
│ ├── api/ # getApkInfo、getTemplate、getIp、getCustomerServiceUrl
|
||||||
|
│ ├── utils/ # request、eventTracker、userAgent 等
|
||||||
|
│ ├── plugins/ # flexible、element、scale_no
|
||||||
|
│ └── assets/ # 样式、各模板图片(mobile1/mobile2/pc/pc2)
|
||||||
|
├── vue.config.js
|
||||||
|
├── .env.development / .env.production
|
||||||
|
├── build.sh # 正式环境打包发布脚本(示例)
|
||||||
|
├── test_build.sh # 测试环境打包发布脚本(示例)
|
||||||
|
└── project.md # 本文件
|
||||||
|
```
|
||||||
|
|
||||||
|
**接口与选线:**
|
||||||
|
|
||||||
|
- 业务接口统一走 **`/plm/okn/ijb/...`** 路径(见 `src/api/`),由网关或 devServer 反代。
|
||||||
|
- **`VUE_APP_BASE_HOSTS`**:各环境 `.env.*` 中为 **JSON 数组字符串**,供选线弹窗展示候选 API 域名;用户选择后写入 `axios.defaults.baseURL` 并重试 `getTemplate`。
|
||||||
|
- 生产部署需由 **Nginx / 网关** 将 **`/plm`** 转发至实际业务后端;History 模式需配置 **单页回退**(`try_files` → `index.html`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. 基础设施与中间件
|
||||||
|
|
||||||
|
本仓库为 **纯前端工程**,不内置后端;运行时依赖:
|
||||||
|
|
||||||
|
- **业务 API**:`/plm` 路径下的配置与下载上报接口;静态部署时需保证 **`/plm` 反代** 与 HTTPS 证书策略一致。
|
||||||
|
- **选线域名**:`VUE_APP_BASE_HOSTS` 中的候选域名需可达,否则用户只能通过选线弹窗手动切换。
|
||||||
|
- **LandingSDK**:`public/sdk/landing-sdk-v1.1.1.js`,用于埋点上报(`window.tracker` / `LandingSDK.init`)。
|
||||||
|
- **静态资源托管**:`npm run build` 产物在 **`dist/`**,由 CDN 或 Web 服务器提供。
|
||||||
|
- **第三方**:Google Analytics(`UA-165940626-2`)、可选外网 IP 查询(`api.ipify.org`,`getIp` 中,当前主流程未启用)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3. 运维部署
|
||||||
|
|
||||||
|
**构建产物:** 执行 `npm run build` 或 `npm run test-build` 后,在 **`dist/`** 生成静态资源;可能同时生成 **`.gz`**(依赖服务器是否启用 `gzip_static` 或等价能力)。
|
||||||
|
|
||||||
|
**推荐发布流程(示例):**
|
||||||
|
|
||||||
|
1. 使用约定 **Node** 版本(建议 12.18.1)安装依赖:`npm ci` 或 `npm install`。
|
||||||
|
2. 确认各环境 **`.env.*`** 中 `VUE_APP_BASE_HOSTS`、`VUE_APP_BASE_API` 等与当前发布目标一致。
|
||||||
|
3. 执行 `npm run build` 或 `npm run test-build`。
|
||||||
|
4. 将 **`dist/`** 同步至静态服务器;确认 **History 路由回退** 与 **`/plm` 反代**。
|
||||||
|
5. 仓库内 **`build.sh`**(正式)、**`test_build.sh`**(测试)为示例流程(打包 zip → scp → 远程 `dist.sh`),**主机与路径以实际运维规范为准**,避免泄露账号与内网信息。
|
||||||
|
|
||||||
|
**环境变量说明(摘录,完整以 `.env.*` 为准):**
|
||||||
|
|
||||||
|
| 变量 | 作用 |
|
||||||
|
|------|------|
|
||||||
|
| `ENV` | 环境标识(`development` / `production`) |
|
||||||
|
| `VUE_APP_BASE_API` | API 根路径前缀(生产多为 `/api`) |
|
||||||
|
| `VUE_APP_APP_API` | 应用 API 前缀(开发环境 `/api`) |
|
||||||
|
| `VUE_APP_WEB_API` | Web API 前缀(开发环境可为空) |
|
||||||
|
| `VUE_APP_BASE_HOSTS` | 选线候选 API 域名列表(JSON 数组字符串) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4. 风险说明
|
||||||
|
|
||||||
|
- **强依赖网关反代**:生产若 `/plm` 代理错误,**配置拉取与下载上报将不可用**;History 模式服务器配置错误会导致 **深链 404**。
|
||||||
|
- **选线失败**:`getTemplate` 请求失败时弹出选线弹窗;若所有候选域名不可用,用户可能无法获取下载配置。
|
||||||
|
- **埋点依赖 SDK**:`LandingSDK` 未加载完成前,`window.tracker` 使用队列缓冲;SDK 加载失败会导致埋点丢失。
|
||||||
|
- **渠道参数**:下载与剪贴板逻辑依赖 URL 参数(`dc`、`ch`、`pc`、`tid` 等),参数缺失或格式不符可能影响跳转与统计。
|
||||||
|
- **配置敏感信息**:`.env`、选线域名、部署脚本中的主机信息 **勿提交到公开仓库**;供应链上锁定 `package-lock.json` 并定期审计依赖。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5. 历史事故(可选)
|
||||||
|
|
||||||
|
- 暂无(请运维/研发后续补充)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6. 交接说明(可选)
|
||||||
|
|
||||||
|
- 落地页主逻辑集中在 **`src/views/land/index.vue`**;新增模板时参考 `mobile1` / `mobile2` / `pc` 组件模式,并在 `index.vue` 中注册与切换。
|
||||||
|
- 子组件规范见 **`.cursor/rules/vue-component.mdc`**;样式与 px2rem 见 **`.cursor/rules/scss-styling.mdc`**。
|
||||||
|
- 接口封装在 **`src/api/`**;HTTP 请求统一走 **`src/utils/request.js`**。
|
||||||
|
- 埋点上报使用 **`src/utils/eventTracker.js`** 的 `landingPageView` / `landingPageClick`,勿绕过 SDK 直接上报。
|
||||||
|
- Figma 转落地页工作流见 **`.cursor/skills/figma-to-landpage/SKILL.md`**。
|
||||||
|
- **请勿将含密钥、生产内网地址的 `.env` 或未脱敏部署脚本提交至对外可见仓库**;YAML 头部元数据若与真实组织不一致,请在内部系统中核对后修改。
|
||||||
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,99 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="renderer" content="webkit" />
|
||||||
|
<meta name="author" content="ijh-615" />
|
||||||
|
<meta name="theme-color" content="rgb(0,0,0)" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0,user-scalable=no, viewport-fit=cover" />
|
||||||
|
<meta name="screen-orientation" content="portrait" />
|
||||||
|
<meta name="x5-orientation" content="portrait" />
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||||
|
<title>91PornApp下载|十年经典老站,全网精品国产原创视频,免费观看</title>
|
||||||
|
<meta name="description" content="91PornApp下载|十年经典老站,全网精品国产原创视频,免费观看。涵盖91论坛、91大神、国产视频、国产专区、91手势认证,以及国产原创、国产探花、好利来服务员、反差婊、调教大神、童颜巨乳、约炮达人、约炮大神、国产UP主、偷情约炮、黑料曝光、校园黑料、国产自拍、情侣自拍、学生视频、国产调教、一杆大烟枪、老虎菜、桥本香菜等热门内容。" />
|
||||||
|
<meta name="keywords" content="91论坛,91大神,国产视频,国产专区,91手势认证,国产原创,国产探花,好利来服务员,反差婊,调教大神,童颜巨乳,约炮达人,约炮大神,国产UP主,偷情约炮,黑料曝光,校园黑料,国产自拍,情侣自拍,学生视频,国产调教,一杆大烟枪,老虎菜,桥本香菜" />
|
||||||
|
<meta name="rating" content="RTA-5042-1996-1400-1577-RTA" />
|
||||||
|
<meta name="rating" content="adult" />
|
||||||
|
<meta name="robots" content="index, follow" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="91PornApp下载|十年经典老站,全网精品国产原创视频,免费观看" />
|
||||||
|
<meta property="og:site_name" content="91Porn" />
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:url" content="https://91porn01.cc" />
|
||||||
|
<meta property="og:image" content="https://91porn01.cc/favicon.ico" />
|
||||||
|
<meta property="og:description" content="91PornApp下载|十年经典老站,全网精品国产原创视频,免费观看。涵盖91论坛、91大神、国产视频、国产专区、91手势认证,以及国产原创、国产探花、好利来服务员、反差婊、调教大神、童颜巨乳、约炮达人、约炮大神、国产UP主、偷情约炮、黑料曝光、校园黑料、国产自拍、情侣自拍、学生视频、国产调教、一杆大烟枪、老虎菜、桥本香菜等热门内容。" />
|
||||||
|
|
||||||
|
<meta name="twitter:title" content="91PornApp下载|十年经典老站,全网精品国产原创视频,免费观看" />
|
||||||
|
<meta name="twitter:description" content="91PornApp下载|十年经典老站,全网精品国产原创视频,免费观看。涵盖91论坛、91大神、国产视频、国产专区、91手势认证,以及国产原创、国产探花、好利来服务员、反差婊、调教大神、童颜巨乳、约炮达人、约炮大神、国产UP主、偷情约炮、黑料曝光、校园黑料、国产自拍、情侣自拍、学生视频、国产调教、一杆大烟枪、老虎菜、桥本香菜等热门内容。" />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="twitter:image" content="https://91porn01.cc/favicon.ico" />
|
||||||
|
<meta name="twitter:site" content="91Porn" />
|
||||||
|
<script>
|
||||||
|
(function(i, s, o, g, r) {
|
||||||
|
i[r] =
|
||||||
|
i[r] ||
|
||||||
|
function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
(i[r].q = i[r].q || []).push({
|
||||||
|
args: args,
|
||||||
|
resolve: resolve,
|
||||||
|
reject: reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
i[r].l = 1 * new Date();
|
||||||
|
|
||||||
|
var a = s.createElement(o);
|
||||||
|
var m = s.getElementsByTagName(o)[0];
|
||||||
|
a.async = 1;
|
||||||
|
a.src = "/sdk/landing-sdk-v1.1.1.js";
|
||||||
|
m.parentNode.insertBefore(a, m);
|
||||||
|
})(window, document, "script", "/sdk/landing-sdk-v1.1.1.js", "tracker");
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
appId: "JHA-204",
|
||||||
|
domain: "",
|
||||||
|
channel: (function() {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
return params.get("dc") || params.get("ch") || params.get("pc") || "";
|
||||||
|
})(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var timer = setInterval(function() {
|
||||||
|
if (window.LandingSDK) {
|
||||||
|
clearInterval(timer);
|
||||||
|
window.LandingSDK.init(config);
|
||||||
|
|
||||||
|
var queued = (window.tracker && window.tracker.q) || [];
|
||||||
|
var real = window.LandingSDK.track;
|
||||||
|
|
||||||
|
real.q = [];
|
||||||
|
window.tracker = real;
|
||||||
|
|
||||||
|
queued.forEach(function(item) {
|
||||||
|
Promise.resolve()
|
||||||
|
.then(function() {
|
||||||
|
return real.apply(null, item.args);
|
||||||
|
})
|
||||||
|
.then(item.resolve, item.reject);
|
||||||
|
});
|
||||||
|
queued.length = 0;
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
|
||||||
|
properly without JavaScript enabled. Please enable it to
|
||||||
|
continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
robots.txt
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "app",
|
||||||
|
components: {},
|
||||||
|
created() {}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@media screen and (min-width: 620px) {
|
||||||
|
/* pc */
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: hidden !important;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 620px) {
|
||||||
|
/* 手机 */
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: auto !important;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: 王枫叶
|
||||||
|
* @Date: 2020-07-20 20:48:00
|
||||||
|
* @LastEditors: 王枫叶
|
||||||
|
* @LastEditTime: 2020-10-05 17:23:15
|
||||||
|
*/
|
||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
export default data =>
|
||||||
|
request({
|
||||||
|
method: "post",
|
||||||
|
url: "/plm/okn/ijb/vl0gpuBNWklJwcv1",
|
||||||
|
data
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
export default data =>
|
||||||
|
request({
|
||||||
|
method: "post",
|
||||||
|
url: "/plm/okn/ijb/fsdfrererdfdf",
|
||||||
|
data,
|
||||||
|
});
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
export default params =>
|
||||||
|
request({
|
||||||
|
method: "get",
|
||||||
|
url: "https://api.ipify.org/?format=json",
|
||||||
|
params
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* @Author: 王枫叶
|
||||||
|
* @Date: 2020-08-10 22:55:30
|
||||||
|
* @LastEditors: 王枫叶
|
||||||
|
* @LastEditTime: 2020-10-28 22:55:14
|
||||||
|
*/
|
||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
export default data =>
|
||||||
|
request({
|
||||||
|
method: "post",
|
||||||
|
url: "/plm/okn/ijb/nuS0EDXSczKgf3Xh",
|
||||||
|
data
|
||||||
|
});
|
||||||
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 933 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 384 B |
|
After Width: | Height: | Size: 561 B |
|
After Width: | Height: | Size: 402 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 1009 B |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 713 B |
|
After Width: | Height: | Size: 477 B |
|
After Width: | Height: | Size: 577 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 591 B |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 506 B |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 455 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 454 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 472 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 487 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 180 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 225 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 269 B |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 1.9 KiB |