Ui修改
@@ -42,7 +42,7 @@ DMP编码: JHA-214 # 无则留空
|
||||
|
||||
备注:
|
||||
- `package.json` 中 `name` 为 `landpage`,仓库/目录名为 `91dj-landpage-client`。
|
||||
- 业务 `appId` 为 **204**(见 `src/views/land/index.vue`),埋点 `appId` 为 **JHA-204**(见 `public/index.html` 与 `src/utils/eventTracker.js`)。
|
||||
- 下载接口 `appId` 为 **5**(见 `src/api/btn.js`),已无埋点。
|
||||
- 接口路径前缀为 **`/plm`**,开发环境由 `vue.config.js` 代理;选线候选域名见各环境 `.env.*` 中的 `VUE_APP_BASE_HOSTS`(JSON 数组),勿将含密钥、内网地址的未脱敏内容对外发布。
|
||||
---
|
||||
|
||||
@@ -60,14 +60,13 @@ DMP编码: JHA-214 # 无则留空
|
||||
| 特定 PC URL | `tips.vue` |
|
||||
| 接口失败选线 | `selectLinePopup.vue` |
|
||||
|
||||
子组件通过 props 接收 `os`、`configs`、`switchData` 等,通过 `$emit` 向上传递 `getApkInfo`、`recordClick` 等事件。
|
||||
子组件通过 props 接收 `os`、`switchData` 等,通过 `$emit` 向上传递 `getApkInfo` 事件。
|
||||
|
||||
**启动流程(`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 下载链接。
|
||||
2. 获取外网 IP 后调用 `bottonlist`(`https://blsp01.com/api/stat/version/bottonlist`)拉取按钮开关(`switchData`,不含下载地址)。
|
||||
3. 用户点击下载时 `handleGetApkInfo`:iOS 轻量版调 `h5/config` 下载描述文件;iOS 商店版(`iosStore`)与 Android 调 `bottonclick` 拿 `data.link` 跳转,后端同时记录点击。
|
||||
5. 接口失败时弹出 **选线弹窗**,用户选择后切换 `axios.defaults.baseURL` 并重试。
|
||||
|
||||
**技术栈要点:**
|
||||
@@ -76,7 +75,6 @@ DMP编码: JHA-214 # 无则留空
|
||||
- 样式:**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**。
|
||||
@@ -104,7 +102,6 @@ DMP编码: JHA-214 # 无则留空
|
||||
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
|
||||
@@ -115,8 +112,8 @@ DMP编码: JHA-214 # 无则留空
|
||||
│ ├── views/land/
|
||||
│ │ ├── index.vue # 落地页主入口:设备判断、选线、下载逻辑
|
||||
│ │ └── components/ # mobile1 / mobile2 / pc / pc2 / tips / selectLinePopup
|
||||
│ ├── api/ # getApkInfo、getTemplate、getIp、getCustomerServiceUrl
|
||||
│ ├── utils/ # request、eventTracker、userAgent 等
|
||||
│ ├── api/ # btn(下载接口)、getIp
|
||||
│ ├── utils/ # request、traceId、userAgent 等
|
||||
│ ├── plugins/ # flexible、element、scale_no
|
||||
│ └── assets/ # 样式、各模板图片(mobile1/mobile2/pc/pc2)
|
||||
├── vue.config.js
|
||||
@@ -128,8 +125,8 @@ DMP编码: JHA-214 # 无则留空
|
||||
|
||||
**接口与选线:**
|
||||
|
||||
- 业务接口统一走 **`/plm/okn/ijb/...`** 路径(见 `src/api/`),由网关或 devServer 反代。
|
||||
- **`VUE_APP_BASE_HOSTS`**:各环境 `.env.*` 中为 **JSON 数组字符串**,供选线弹窗展示候选 API 域名;用户选择后写入 `axios.defaults.baseURL` 并重试 `getTemplate`。
|
||||
- 下载接口直连 **`https://blsp01.com/api/stat/version`**(见 `src/api/btn.js`,接口已开 CORS)。
|
||||
- **`VUE_APP_BASE_HOSTS`**:各环境 `.env.*` 中为 **JSON 数组字符串**,供选线弹窗展示候选 API 域名;用户选择后写入 `axios.defaults.baseURL` 并重试 `bottonlist`。
|
||||
- 生产部署需由 **Nginx / 网关** 将 **`/plm`** 转发至实际业务后端;History 模式需配置 **单页回退**(`try_files` → `index.html`)。
|
||||
|
||||
---
|
||||
@@ -140,7 +137,6 @@ DMP编码: JHA-214 # 无则留空
|
||||
|
||||
- **业务 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` 中,当前主流程未启用)。
|
||||
|
||||
@@ -173,8 +169,7 @@ DMP编码: JHA-214 # 无则留空
|
||||
# 4. 风险说明
|
||||
|
||||
- **强依赖网关反代**:生产若 `/plm` 代理错误,**配置拉取与下载上报将不可用**;History 模式服务器配置错误会导致 **深链 404**。
|
||||
- **选线失败**:`getTemplate` 请求失败时弹出选线弹窗;若所有候选域名不可用,用户可能无法获取下载配置。
|
||||
- **埋点依赖 SDK**:`LandingSDK` 未加载完成前,`window.tracker` 使用队列缓冲;SDK 加载失败会导致埋点丢失。
|
||||
- **选线失败**:`bottonlist` 请求失败时弹出选线弹窗;若所有候选域名不可用,用户可能无法获取下载配置。
|
||||
- **渠道参数**:下载与剪贴板逻辑依赖 URL 参数(`dc`、`ch`、`pc`、`tid` 等),参数缺失或格式不符可能影响跳转与统计。
|
||||
- **配置敏感信息**:`.env`、选线域名、部署脚本中的主机信息 **勿提交到公开仓库**;供应链上锁定 `package-lock.json` 并定期审计依赖。
|
||||
|
||||
@@ -191,6 +186,5 @@ DMP编码: JHA-214 # 无则留空
|
||||
- 落地页主逻辑集中在 **`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 头部元数据若与真实组织不一致,请在内部系统中核对后修改。
|
||||
|
||||
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 99 KiB |
@@ -5,86 +5,27 @@
|
||||
<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="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>91短剧 APP下载 | 国产大神成人短剧平台 百万资源免费</title>
|
||||
<meta name="description" content="91大神 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<meta name="keywords" content="91大神 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<title>黄果漫剧 APP下载 | 百万大神短剧原创 海量资源免费</title>
|
||||
<meta name="description" content="黄果漫剧 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<meta name="keywords" content="黄果漫剧 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<!-- <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="91短剧 APP下载 | 国产大神成人短剧平台 百万资源免费" />
|
||||
<meta property="og:site_name" content="91短剧" />
|
||||
<meta property="og:title" content="黄果漫剧 APP下载 | 百万大神短剧原创 海量资源免费" />
|
||||
<meta property="og:site_name" content="黄果漫剧" />
|
||||
<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="91大神 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<meta property="og:description" content="黄果漫剧 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
|
||||
<meta name="twitter:title" content="91短剧 APP下载 | 国产大神成人短剧平台 百万资源免费" />
|
||||
<meta name="twitter:description" content="91大神 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<meta name="twitter:title" content="黄果漫剧 APP下载 | 百万大神短剧原创 海量资源免费" />
|
||||
<meta name="twitter:description" content="黄果漫剧 成人短剧 AI短剧 国产情侣 国产原创 真实自拍 反差婊 性奴母狗 极品学妹 童颜巨乳 宿舍自慰 偷情少妇 学生泄密 探花精选" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<!-- <meta name="twitter:image" content="https://91porn01.cc/favicon.ico" />-->
|
||||
<meta name="twitter:site" content="91短剧" />
|
||||
<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-214",
|
||||
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>
|
||||
<meta name="twitter:site" content="黄果漫剧" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
// 下载相关接口对齐 huangguo_landpage,域名写死(接口已开 CORS: *)
|
||||
const STAT_API = "https://blsp01.com/api/stat/version";
|
||||
|
||||
// 黄果短剧 App 在下载接口的 appId
|
||||
export const APP_ID = 5;
|
||||
|
||||
// 按钮开关:iosDown / iosStore / iosTF / iosH5 / androidButton,不含下载地址
|
||||
export const getButtonList = data =>
|
||||
request({
|
||||
method: "post",
|
||||
url: `${STAT_API}/bottonlist`,
|
||||
data: { ...data, appId: APP_ID }
|
||||
});
|
||||
|
||||
// 点击下载:返回 data.link(下载地址数组),同时后端记录点击
|
||||
export const bottonclick = data =>
|
||||
request({
|
||||
method: "post",
|
||||
url: `${STAT_API}/bottonclick`,
|
||||
data: { ...data, appId: APP_ID }
|
||||
});
|
||||
|
||||
// iOS H5 描述文件:返回 .mobileconfig 原文(非 JSON)
|
||||
export const downloadH5 = async data => {
|
||||
const res = await fetch(`${STAT_API}/h5/config`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ...data, appId: APP_ID })
|
||||
});
|
||||
if (!res.ok) throw new Error("获取描述文件失败 " + res.status);
|
||||
return res.text();
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* @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
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export default data =>
|
||||
request({
|
||||
method: "post",
|
||||
url: "/plm/okn/ijb/fsdfrererdfdf",
|
||||
data,
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* @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: 26 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 5.0 MiB After Width: | Height: | Size: 4.9 MiB |
@@ -1,190 +0,0 @@
|
||||
const APPID = "JHA-214";
|
||||
const APP_VERSION = "1.0.0";
|
||||
|
||||
export function getTraceId() {
|
||||
let str = sessionStorage.getItem(`${APPID}_traceId`);
|
||||
if (str) return str;
|
||||
str =
|
||||
Math.random()
|
||||
.toString()
|
||||
.substr(2, 36) + Date.now().toString(36);
|
||||
sessionStorage.setItem(`${APPID}_traceId`, str);
|
||||
return str;
|
||||
}
|
||||
|
||||
function getAndroidBrand(model = "") {
|
||||
const m = model.toLowerCase();
|
||||
if (!m) return "";
|
||||
if (/^(sm-|gt-|sch-|sgh-|shv-)/.test(m)) return "Samsung";
|
||||
if (/^(mix|mi |mi-|mi\d|redmi|m\d{1,2}[a-z]|poco|2\d{3}[a-z0-9]{4,})/.test(m)) return "Xiaomi";
|
||||
if (/^(pixel)/.test(m)) return "Google";
|
||||
if (/^(huawei|huawe|ana-|bla-|ele-|lya-|noh-|tas-|vog-|aln-|bra-|clt-|dub-|eml-|frl-|hma-|ine-|jef-|jkm-|jny-|lio-|mar-|mrd-|nam-|oce-|pct-|pot-|sea-|snk-|stk-|tet-|wlz-|yal-)/.test(m)) return "HUAWEI";
|
||||
if (/^(honor|ali-|bkl-|yal-|nth-|peg-|any-|are-|bkk-|cdy-|cph-|dnn-|fne-|hry-|jsn-|lld-|mov-|rne-|rea-|tfy-|tnr-|crr-|ksa-)/.test(m)) return "HONOR";
|
||||
if (/^(vivo|v\d|pd\d)/.test(m)) return "vivo";
|
||||
if (/^(oppo|cph|peh|pht|pgc|pgd|pgf|a\d{3})/.test(m)) return "OPPO";
|
||||
if (/^(oneplus|hd19|kb20|le21|gm19|cph2\d|de2\d|ne2\d|in2\d|kb2\d|le2\d)/.test(m)) return "OnePlus";
|
||||
if (/^(realme|rmx)/.test(m)) return "realme";
|
||||
if (/^(moto|motorola|xt\d)/.test(m)) return "Motorola";
|
||||
if (/^(iqoo|i2\d|v\d{4}[a-z])/.test(m)) return "iQOO";
|
||||
if (/^(nokia|ta-\d+)/.test(m)) return "Nokia";
|
||||
if (/^(sony|xq-|g8\d|xperia)/.test(m)) return "Sony";
|
||||
if (/^(asus|zs\d|ai\d|rog)/.test(m)) return "ASUS";
|
||||
if (/^(meizu|m\d+ note)/.test(m)) return "Meizu";
|
||||
if (/^(nubia|nx\d)/.test(m)) return "nubia";
|
||||
if (/^(zte|zte-)/.test(m)) return "ZTE";
|
||||
if (/^(lenovo|tb-|l3\d)/.test(m)) return "Lenovo";
|
||||
return "";
|
||||
}
|
||||
|
||||
function extractMobileModel(ua = "") {
|
||||
const sectionMatch = ua.match(
|
||||
/\(([^)]*(Android|HarmonyOS|OpenHarmony)[^)]*)\)/i
|
||||
);
|
||||
if (!sectionMatch || !sectionMatch[1]) return "";
|
||||
const candidates = sectionMatch[1]
|
||||
.split(";")
|
||||
.map(item => item.trim())
|
||||
.map(item => item.replace(/\s+Build\/.*$/i, "").trim())
|
||||
.filter(
|
||||
item =>
|
||||
item &&
|
||||
!/^linux$/i.test(item) &&
|
||||
!/^u$/i.test(item) &&
|
||||
!/^wv$/i.test(item) &&
|
||||
!/^android[\d.\s]*$/i.test(item) &&
|
||||
!/^[a-z]{2}([-_][a-z]{2})?$/i.test(item)
|
||||
);
|
||||
return candidates.length ? candidates[candidates.length - 1] : "";
|
||||
}
|
||||
|
||||
function getBrandFromUA(model = "", ua = "") {
|
||||
const brand = getAndroidBrand(model);
|
||||
if (brand) return brand;
|
||||
if (/honor/i.test(ua)) return "HONOR";
|
||||
if (/(huawei|huaweibrowser)/i.test(ua)) return "HUAWEI";
|
||||
if (/\bxiaomi\b|miuibrowser/i.test(ua)) return "Xiaomi";
|
||||
if (/\boppo\b|oppobrowser|heytap/i.test(ua)) return "OPPO";
|
||||
if (/\bvivo\b|vivobrowser/i.test(ua)) return "vivo";
|
||||
if (/\bsamsungbrowser\b/i.test(ua)) return "Samsung";
|
||||
if (/\boneplus\b/i.test(ua)) return "OnePlus";
|
||||
if (/\brealme\b/i.test(ua)) return "realme";
|
||||
if (/\bmeizu\b/i.test(ua)) return "Meizu";
|
||||
if (/\blenovo\b/i.test(ua)) return "Lenovo";
|
||||
return "";
|
||||
}
|
||||
|
||||
function parseDeviceInfo(ua = "") {
|
||||
if (!ua) return { device_brand: "", device_model: "" };
|
||||
if (/iPhone/i.test(ua)) return { device_brand: "Apple", device_model: "iPhone" };
|
||||
if (/iPad/i.test(ua)) return { device_brand: "Apple", device_model: "iPad" };
|
||||
if (/iPod/i.test(ua)) return { device_brand: "Apple", device_model: "iPod" };
|
||||
if (/Android/i.test(ua) || /(harmonyos|openharmony|hongmeng|hmos)/i.test(ua)) {
|
||||
const model = extractMobileModel(ua);
|
||||
return { device_brand: getBrandFromUA(model, ua), device_model: model };
|
||||
}
|
||||
if (/Macintosh|Mac OS X/i.test(ua)) return { device_brand: "Apple", device_model: "Mac" };
|
||||
if (/Windows/i.test(ua)) return { device_brand: "Microsoft", device_model: "Windows PC" };
|
||||
if (/Linux/i.test(ua)) return { device_brand: "Linux", device_model: "Linux PC" };
|
||||
return { device_brand: "", device_model: "" };
|
||||
}
|
||||
|
||||
export function getSystemType(ua = navigator.userAgent || "") {
|
||||
if (
|
||||
/Android/i.test(ua) ||
|
||||
/(harmonyos|openharmony|hongmeng|hmos)/i.test(ua)
|
||||
) {
|
||||
return "android";
|
||||
}
|
||||
if (/iPhone|iPad|iPod/i.test(ua)) {
|
||||
return "ios";
|
||||
}
|
||||
return "pc";
|
||||
}
|
||||
|
||||
export function getSystemVersion(ua = navigator.userAgent || "") {
|
||||
const iosMatch = ua.match(/OS (\d+(?:[_\d]+)?)/i);
|
||||
if (iosMatch && iosMatch[1]) {
|
||||
return iosMatch[1].replace(/_/g, ".");
|
||||
}
|
||||
const androidMatch = ua.match(/Android\s+([\d.]+)/i);
|
||||
if (androidMatch && androidMatch[1]) {
|
||||
return androidMatch[1];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function getDeviceType(ua = navigator.userAgent || "") {
|
||||
if (/iPhone/i.test(ua)) return "iPhone";
|
||||
if (/iPad/i.test(ua)) return "iPad";
|
||||
if (/iPod/i.test(ua)) return "iPod";
|
||||
if (
|
||||
/Android/i.test(ua) ||
|
||||
/(harmonyos|openharmony|hongmeng|hmos)/i.test(ua)
|
||||
) {
|
||||
return extractMobileModel(ua);
|
||||
}
|
||||
const { device_model } = parseDeviceInfo(ua);
|
||||
return device_model;
|
||||
}
|
||||
|
||||
let _cachedDeviceInfo = null;
|
||||
function getDeviceInfo() {
|
||||
if (!_cachedDeviceInfo) {
|
||||
_cachedDeviceInfo = parseDeviceInfo(navigator.userAgent || "");
|
||||
}
|
||||
return _cachedDeviceInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报落地页展示
|
||||
* vm: 组件 this,必须传(读取 vm.version)
|
||||
* opts: { landing_page_id }
|
||||
*/
|
||||
export function landingPageView(vm, opts = {}) {
|
||||
const { device_brand, device_model } = getDeviceInfo();
|
||||
const data = {
|
||||
event: "landing_page_view",
|
||||
landing_page_id: opts.landing_page_id || `${APPID}_${vm.version}`,
|
||||
device_brand,
|
||||
device_model,
|
||||
app_version: APP_VERSION
|
||||
};
|
||||
return window.tracker(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报落地页点击
|
||||
* vm: 组件 this
|
||||
* opts: { landing_page_id, tab_key, tab_name, click_coordinates_x, click_coordinates_y,
|
||||
* click_x_percent, click_y_percent, screen_width, screen_height, trace_id }
|
||||
*/
|
||||
export function landingPageClick(vm, opts = {}) {
|
||||
const { device_brand, device_model } = getDeviceInfo();
|
||||
const screenW = window.screen ? window.screen.width : opts.screen_width || 0;
|
||||
const screenH = window.screen
|
||||
? window.screen.height
|
||||
: opts.screen_height || 0;
|
||||
const data = {
|
||||
event: "landing_page_click",
|
||||
landing_page_id: opts.landing_page_id || `${APPID}_${vm.version}`,
|
||||
tab_key: opts.tab_key || "",
|
||||
tab_name: opts.tab_name || "",
|
||||
click_coordinates_x: opts.click_coordinates_x || 0,
|
||||
click_coordinates_y: opts.click_coordinates_y || 0,
|
||||
click_x_percent:
|
||||
typeof opts.click_x_percent === "number"
|
||||
? opts.click_x_percent
|
||||
: Math.round(((opts.click_coordinates_x || 0) / (screenW || 1)) * 100),
|
||||
click_y_percent:
|
||||
typeof opts.click_y_percent === "number"
|
||||
? opts.click_y_percent
|
||||
: Math.round(((opts.click_coordinates_y || 0) / (screenH || 1)) * 100),
|
||||
screen_width: screenW,
|
||||
screen_height: screenH,
|
||||
trace_id: opts.trace_id || getTraceId(),
|
||||
device_brand,
|
||||
device_model,
|
||||
app_version: APP_VERSION
|
||||
};
|
||||
return window.tracker(data);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 会话 trace id:剪贴板里的 tid 供 App 首启做安装归因
|
||||
const KEY = "hgmj_traceId";
|
||||
|
||||
export function getTraceId() {
|
||||
let str = sessionStorage.getItem(KEY);
|
||||
if (str) return str;
|
||||
str =
|
||||
Math.random()
|
||||
.toString()
|
||||
.substr(2, 36) + Date.now().toString(36);
|
||||
sessionStorage.setItem(KEY, str);
|
||||
return str;
|
||||
}
|
||||
@@ -1,35 +1,13 @@
|
||||
<template>
|
||||
<div class="mobilePage" @click="pageClick">
|
||||
<div class="mobilePage">
|
||||
<div class="headerBox" :class="{ isIos: isIos }">
|
||||
<div class="navBrand">
|
||||
<img class="logoBox" :src="logoHeader" alt="91短剧" />
|
||||
<img class="logoBox" :src="logoHeader" alt="黄果漫剧" />
|
||||
<div class="brandCopy">
|
||||
<p class="brandTitle">91短剧 原创基地</p>
|
||||
<p class="brandTitle">黄果漫剧 原创基地</p>
|
||||
<p class="brandDesc">沉浸体验 极速观影</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightBox" v-if="configs && configs.length">
|
||||
<div class="navItem" v-if="cTg" @click.stop="jumpUrl(cTg, $event)">
|
||||
<div class="telegram"></div>
|
||||
<span>群聊</span>
|
||||
</div>
|
||||
<div
|
||||
class="navItem"
|
||||
v-if="cBusiness"
|
||||
@click.stop="jumpUrl(cBusiness, $event)"
|
||||
>
|
||||
<div class="business"></div>
|
||||
<span>商务</span>
|
||||
</div>
|
||||
<div
|
||||
class="navItem"
|
||||
v-if="cChannel"
|
||||
@click.stop="jumpUrl(cChannel, $event)"
|
||||
>
|
||||
<div class="channel"></div>
|
||||
<span>客服</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="iosBanner"
|
||||
@click.stop="getApkInfo(hasShopIos ? 'shopIos' : true, $event)"
|
||||
@@ -122,13 +100,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { landingPageClick } from "@/utils/eventTracker";
|
||||
import tabContents from "./mobile1Contents";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
os: {},
|
||||
configs: {},
|
||||
switchData: {},
|
||||
version: {
|
||||
type: String,
|
||||
@@ -151,11 +127,8 @@ export default {
|
||||
{ label: "刚满18", value: "adult" }
|
||||
],
|
||||
tabContents,
|
||||
cTg: "",
|
||||
cBusiness: "",
|
||||
cChannel: "",
|
||||
showIosTip: true,
|
||||
logoHeader: require("@/assets/images/mobile1/logoHeader.webp")
|
||||
logoHeader: require("@/assets/images/common/hgmj_logo.png")
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -163,30 +136,7 @@ export default {
|
||||
return !!(this.os && this.os.isPhone);
|
||||
},
|
||||
hasShopIos() {
|
||||
return !!(
|
||||
this.isIos &&
|
||||
this.switchData &&
|
||||
this.switchData.shopIosDownUrl
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
configs: {
|
||||
immediate: true,
|
||||
handler(configs) {
|
||||
if (!configs || !configs.length) {
|
||||
return;
|
||||
}
|
||||
configs.forEach(item => {
|
||||
if (item.configType === "tg_group") {
|
||||
this.cTg = item.link;
|
||||
} else if (item.configType === "sw_cooperate") {
|
||||
this.cBusiness = item.link;
|
||||
} else if (item.configType === "qd_cooperate") {
|
||||
this.cChannel = item.link;
|
||||
}
|
||||
});
|
||||
}
|
||||
return !!(this.isIos && this.switchData && this.switchData.iosStore);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -204,18 +154,6 @@ export default {
|
||||
this.clearTabAutoPlay();
|
||||
},
|
||||
methods: {
|
||||
pageClick(event) {
|
||||
let click_coordinates_x = event.clientX;
|
||||
let click_coordinates_y = event.clientY;
|
||||
landingPageClick(this, {
|
||||
tab_key: this.tabActive,
|
||||
tab_name: this.getTabName(),
|
||||
click_coordinates_x,
|
||||
click_coordinates_y
|
||||
}).then(res => {
|
||||
console.log(res);
|
||||
});
|
||||
},
|
||||
getTabName() {
|
||||
const tab = this.tabs.find(item => item.value === this.tabActive);
|
||||
return tab ? tab.label : "推荐";
|
||||
@@ -256,7 +194,7 @@ export default {
|
||||
this.androidPromptShow = true;
|
||||
}
|
||||
},
|
||||
async getApkInfo(flag, event) {
|
||||
async getApkInfo(flag) {
|
||||
let type = this.os.isPhone ? "ios" : "android";
|
||||
if (flag === "shopIos") {
|
||||
type = "shopIos";
|
||||
@@ -267,25 +205,9 @@ export default {
|
||||
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 ? event.clientX : 0,
|
||||
click_coordinates_y: event ? event.clientY : 0
|
||||
});
|
||||
},
|
||||
jumpUrl(url, event) {
|
||||
landingPageClick(this, {
|
||||
tab_key: this.tabActive,
|
||||
tab_name: this.getTabName(),
|
||||
click_coordinates_x: event ? event.clientX : 0,
|
||||
click_coordinates_y: event ? event.clientY : 0
|
||||
});
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -389,53 +311,6 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rightBox {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 13px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
|
||||
.navItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: 28px;
|
||||
|
||||
.telegram,
|
||||
.business,
|
||||
.channel {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.telegram {
|
||||
background-image: url("./../../../assets/images/mobile1/telegram.svg");
|
||||
}
|
||||
|
||||
.business {
|
||||
background-image: url("./../../../assets/images/mobile1/business.svg");
|
||||
}
|
||||
|
||||
.channel {
|
||||
background-image: url("./../../../assets/images/mobile1/channel.svg");
|
||||
}
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pageBody {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<template>
|
||||
<div class="mobilePage" @click="pageClick">
|
||||
<div class="mobilePage">
|
||||
<div
|
||||
class="headerBox"
|
||||
@click.stop="
|
||||
getApkInfo(
|
||||
os.isPhone && switchData.shopIosDownUrl ? 'shopIos' : true,
|
||||
$event
|
||||
)
|
||||
getApkInfo(os.isPhone && switchData.iosStore ? 'shopIos' : true, $event)
|
||||
"
|
||||
>
|
||||
<div class="header-bg">
|
||||
@@ -21,30 +18,12 @@
|
||||
></div>
|
||||
<div class="navBox">
|
||||
<div class="navBrand">
|
||||
<img class="logoBox" :src="logoHeader" alt="海角社区" />
|
||||
<img class="logoBox" :src="logoHeader" alt="黄果漫剧" />
|
||||
<div class="brandCopy">
|
||||
<div class="brandName">海角社区</div>
|
||||
<div class="brandName">黄果漫剧</div>
|
||||
<div class="brandDesc">全球最大乱伦平台</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rightBox" v-if="configs && configs.length">
|
||||
<div class="navItem" v-if="cTg" @click.stop="jumpUrl(cTg)">
|
||||
<div class="telegram"></div>
|
||||
<span>群聊</span>
|
||||
</div>
|
||||
<div
|
||||
class="navItem"
|
||||
v-if="cBusiness"
|
||||
@click.stop="jumpUrl(cBusiness)"
|
||||
>
|
||||
<div class="business"></div>
|
||||
<span>商务</span>
|
||||
</div>
|
||||
<div class="navItem" v-if="cChannel" @click.stop="jumpUrl(cChannel)">
|
||||
<div class="channel"></div>
|
||||
<span>流量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-tabs
|
||||
@@ -68,7 +47,7 @@
|
||||
class="main"
|
||||
@click.stop="
|
||||
getApkInfo(
|
||||
os.isPhone && switchData.shopIosDownUrl ? 'shopIos' : true,
|
||||
os.isPhone && switchData.iosStore ? 'shopIos' : true,
|
||||
$event
|
||||
)
|
||||
"
|
||||
@@ -105,10 +84,7 @@
|
||||
class="bottomBox ios"
|
||||
@click.stop="getApkInfo('shopIos', $event)"
|
||||
>
|
||||
<div
|
||||
class="shopBtn"
|
||||
@click.stop="getApkInfo('shopIos', $event)"
|
||||
></div>
|
||||
<div class="shopBtn" @click.stop="getApkInfo('shopIos', $event)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<van-overlay :show="androidPromptShow" z-index="999" class="androidOverlay">
|
||||
@@ -135,10 +111,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { landingPageClick } from "@/utils/eventTracker";
|
||||
|
||||
export default {
|
||||
props: ["os", "configs", "switchData"],
|
||||
props: ["os", "switchData"],
|
||||
data() {
|
||||
return {
|
||||
androidPromptShow: false,
|
||||
@@ -221,30 +195,13 @@ export default {
|
||||
"【妹妹不睡我不睡,我玩游戏操亲妹!】爸妈外出后,我带妹妹玩真心话大冒险,没想到妹妹居然还穿着透明蕾丝内裤!看的我鸡巴一柱擎天!脱下裤子狂操妹妹,要不是怕妹妹怀孕,我高低要试试内射亲妹的滋味!!"
|
||||
}
|
||||
],
|
||||
cTg: "",
|
||||
cBusiness: "",
|
||||
cChannel: "",
|
||||
version: "B",
|
||||
logoHeader: require("@/assets/images/mobile2/figma-logo.png")
|
||||
logoHeader: require("@/assets/images/common/hgmj_logo.png")
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasShopIos() {
|
||||
return this.os && this.os.isPhone && this.switchData.shopIosDownUrl;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
configs(configs) {
|
||||
configs.forEach(item => {
|
||||
if (item.configType === "tg_group") {
|
||||
this.cTg = item.link;
|
||||
} else if (item.configType === "sw_cooperate") {
|
||||
this.cBusiness = item.link;
|
||||
} else if (item.configType === "qd_cooperate") {
|
||||
this.cChannel = item.link;
|
||||
}
|
||||
});
|
||||
return configs;
|
||||
return this.os && this.os.isPhone && this.switchData.iosStore;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -258,14 +215,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageClick(event) {
|
||||
landingPageClick(this, {
|
||||
tab_key: this.tabActive,
|
||||
tab_name: this.getTabName(),
|
||||
click_coordinates_x: event.clientX,
|
||||
click_coordinates_y: event.clientY
|
||||
});
|
||||
},
|
||||
getTabName() {
|
||||
const tab = this.tabs.find(item => item.value === this.tabActive);
|
||||
return tab ? tab.label : "精选";
|
||||
@@ -280,7 +229,7 @@ export default {
|
||||
},
|
||||
onTabBeforeChange() {
|
||||
this.getApkInfo(
|
||||
this.os.isPhone && this.switchData.shopIosDownUrl ? "shopIos" : true
|
||||
this.os.isPhone && this.switchData.iosStore ? "shopIos" : true
|
||||
);
|
||||
return false;
|
||||
},
|
||||
@@ -291,7 +240,7 @@ export default {
|
||||
this.androidPromptShow = true;
|
||||
}
|
||||
},
|
||||
async getApkInfo(flag, event) {
|
||||
async getApkInfo(flag) {
|
||||
let type = this.os.isPhone ? "ios" : "android";
|
||||
if (flag === "shopIos") {
|
||||
type = "shopIos";
|
||||
@@ -302,19 +251,9 @@ export default {
|
||||
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 ? event.clientX : 0,
|
||||
click_coordinates_y: event ? event.clientY : 0
|
||||
});
|
||||
},
|
||||
jumpUrl(url) {
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -390,52 +329,6 @@ export default {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.rightBox {
|
||||
position: absolute;
|
||||
right: 13px;
|
||||
top: 12px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
|
||||
.navItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 24px;
|
||||
|
||||
span {
|
||||
margin-top: 2px;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
|
||||
.telegram,
|
||||
.business,
|
||||
.channel {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.telegram {
|
||||
background-image: url("./../../../assets/images/mobile2/figma-group.svg");
|
||||
}
|
||||
|
||||
.business {
|
||||
background-image: url("./../../../assets/images/mobile2/figma-business.svg");
|
||||
}
|
||||
|
||||
.channel {
|
||||
background-image: url("./../../../assets/images/mobile2/figma-channel.svg");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
|
||||
@@ -3,43 +3,13 @@
|
||||
<div class="page-bg"></div>
|
||||
|
||||
<div class="pcm-brand">
|
||||
<img class="pcm-logo" :src="logoImg" alt="91短剧" />
|
||||
<img class="pcm-logo" :src="logoImg" alt="黄果漫剧" />
|
||||
<div class="pcm-brand-copy">
|
||||
<div class="pcm-brand-title">91短剧 原创基地</div>
|
||||
<div class="pcm-brand-title">黄果漫剧 原创基地</div>
|
||||
<div class="pcm-brand-desc">沉浸体验 极速观影</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pcm-actions" v-if="configs && configs.length">
|
||||
<button
|
||||
class="pcm-action"
|
||||
v-if="cTg"
|
||||
type="button"
|
||||
aria-label="群聊"
|
||||
@click.stop="jumpUrl(cTg)"
|
||||
>
|
||||
<img :src="tgIcon" alt="" />
|
||||
</button>
|
||||
<button
|
||||
class="pcm-action"
|
||||
v-if="cBusiness"
|
||||
type="button"
|
||||
aria-label="商务合作"
|
||||
@click.stop="jumpUrl(cBusiness)"
|
||||
>
|
||||
<img :src="businessIcon" alt="" />
|
||||
</button>
|
||||
<button
|
||||
class="pcm-action"
|
||||
v-if="cChannel"
|
||||
type="button"
|
||||
aria-label="客服"
|
||||
@click.stop="jumpUrl(cChannel)"
|
||||
>
|
||||
<img :src="channelIcon" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pcm-download">
|
||||
<div class="pcm-qrcode">
|
||||
<div class="pcm-qrcode-inner">
|
||||
@@ -74,38 +44,13 @@ import autofit from "autofit.js";
|
||||
autofit.init();
|
||||
|
||||
export default {
|
||||
props: ["qrImg", "os", "configs"],
|
||||
props: ["qrImg", "os"],
|
||||
data() {
|
||||
return {
|
||||
cTg: "",
|
||||
cBusiness: "",
|
||||
cChannel: "",
|
||||
logoImg: require("@/assets/images/pc/logo.webp"),
|
||||
tgIcon: require("@/assets/images/pc/telegram.svg"),
|
||||
businessIcon: require("@/assets/images/pc/business.svg"),
|
||||
channelIcon: require("@/assets/images/pc/channel.svg"),
|
||||
logoImg: require("@/assets/images/common/hgmj_logo.png"),
|
||||
scanIcon: require("@/assets/images/pc/scanIcon.svg")
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
configs: {
|
||||
immediate: true,
|
||||
handler(configs) {
|
||||
if (!configs || !configs.length) {
|
||||
return;
|
||||
}
|
||||
configs.forEach(item => {
|
||||
if (item.configType === "tg_group") {
|
||||
this.cTg = item.link;
|
||||
} else if (item.configType === "sw_cooperate") {
|
||||
this.cBusiness = item.link;
|
||||
} else if (item.configType === "qd_cooperate") {
|
||||
this.cChannel = item.link;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
autofit.init(
|
||||
{
|
||||
@@ -123,12 +68,6 @@ export default {
|
||||
methods: {
|
||||
getApkInfo() {
|
||||
this.$emit("getApkInfo", "android");
|
||||
},
|
||||
jumpUrl(url) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -204,32 +143,6 @@ export default {
|
||||
text-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.pcm-actions {
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
right: 96px;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 61px;
|
||||
}
|
||||
|
||||
.pcm-action {
|
||||
width: 69px;
|
||||
height: 69px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 69px;
|
||||
height: 69px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.pcm-download {
|
||||
position: absolute;
|
||||
left: 170px;
|
||||
|
||||
@@ -4,43 +4,13 @@
|
||||
<img class="pcm-top-mask" :src="pcTopMask" alt="" />
|
||||
|
||||
<div class="pcm-brand">
|
||||
<img class="pcm-logo" :src="logoImg" alt="海角社区" />
|
||||
<img class="pcm-logo" :src="logoImg" alt="黄果漫剧" />
|
||||
<div class="pcm-brand-copy">
|
||||
<div class="pcm-brand-title">海角社区</div>
|
||||
<div class="pcm-brand-title">黄果漫剧</div>
|
||||
<div class="pcm-brand-desc">全球最大乱伦平台</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pcm-actions" v-if="configs && configs.length">
|
||||
<button
|
||||
class="pcm-action"
|
||||
v-if="cTg"
|
||||
type="button"
|
||||
aria-label="群组"
|
||||
@click.stop="jumpUrl(cTg)"
|
||||
>
|
||||
<img :src="tgIcon" alt="" />
|
||||
</button>
|
||||
<button
|
||||
class="pcm-action"
|
||||
v-if="cBusiness"
|
||||
type="button"
|
||||
aria-label="商务合作"
|
||||
@click.stop="jumpUrl(cBusiness)"
|
||||
>
|
||||
<img :src="businessIcon" alt="" />
|
||||
</button>
|
||||
<button
|
||||
class="pcm-action"
|
||||
v-if="cChannel"
|
||||
type="button"
|
||||
aria-label="渠道合作"
|
||||
@click.stop="jumpUrl(cChannel)"
|
||||
>
|
||||
<img :src="channelIcon" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pcm-download">
|
||||
<div class="pcm-qrcode">
|
||||
<img class="pcm-qrcode-img" :src="qrImg" alt="扫码下载" />
|
||||
@@ -71,40 +41,15 @@
|
||||
import autofit from "autofit.js";
|
||||
|
||||
export default {
|
||||
props: ["qrImg", "os", "configs"],
|
||||
props: ["qrImg", "os"],
|
||||
data() {
|
||||
return {
|
||||
cTg: "",
|
||||
cBusiness: "",
|
||||
cChannel: "",
|
||||
pcBg: require("@/assets/images/pc2/figma-pc-bg.webp"),
|
||||
pcTopMask: require("@/assets/images/pc2/figma-pc-top-mask.png"),
|
||||
logoImg: require("@/assets/images/pc2/figma-pc-logo.png"),
|
||||
tgIcon: require("@/assets/images/pc2/figma-pc-tg.svg"),
|
||||
businessIcon: require("@/assets/images/pc2/figma-pc-business.svg"),
|
||||
channelIcon: require("@/assets/images/pc2/figma-pc-channel.svg"),
|
||||
logoImg: require("@/assets/images/common/hgmj_logo.png"),
|
||||
scanIcon: require("@/assets/images/pc2/figma-pc-scan.svg")
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
configs: {
|
||||
immediate: true,
|
||||
handler(configs) {
|
||||
if (!configs || !configs.length) {
|
||||
return;
|
||||
}
|
||||
configs.forEach(item => {
|
||||
if (item.configType === "tg_group") {
|
||||
this.cTg = item.link;
|
||||
} else if (item.configType === "sw_cooperate") {
|
||||
this.cBusiness = item.link;
|
||||
} else if (item.configType === "qd_cooperate") {
|
||||
this.cChannel = item.link;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
autofit.init(
|
||||
{
|
||||
@@ -122,9 +67,6 @@ export default {
|
||||
methods: {
|
||||
getApkInfo() {
|
||||
this.$emit("getApkInfo", "android");
|
||||
},
|
||||
jumpUrl(url) {
|
||||
window.open(url);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -216,32 +158,6 @@ export default {
|
||||
text-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.pcm-actions {
|
||||
position: absolute;
|
||||
top: 86px;
|
||||
right: 32px;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 29px;
|
||||
}
|
||||
|
||||
.pcm-action {
|
||||
width: 69px;
|
||||
height: 69px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
width: 69px;
|
||||
height: 69px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.pcm-download {
|
||||
position: absolute;
|
||||
left: 112px;
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
class="mobile"
|
||||
:os="os"
|
||||
@getApkInfo="handleGetApkInfo"
|
||||
@recordClick="recordClick"
|
||||
:ip="ip"
|
||||
:configs="configs"
|
||||
:switchData="switchData"
|
||||
:version="version"
|
||||
style="height:100%"
|
||||
@@ -22,7 +20,6 @@
|
||||
class="pc"
|
||||
:qr-img="QRImg"
|
||||
:os="os"
|
||||
:configs="configs"
|
||||
@getApkInfo="handleGetApkInfo"
|
||||
></pc>
|
||||
</template>
|
||||
@@ -32,9 +29,7 @@
|
||||
class="mobile"
|
||||
:os="os"
|
||||
@getApkInfo="handleGetApkInfo"
|
||||
@recordClick="recordClick"
|
||||
:ip="ip"
|
||||
:configs="configs"
|
||||
:switchData="switchData"
|
||||
style="height:100%"
|
||||
ref="mobileRef"
|
||||
@@ -47,7 +42,6 @@
|
||||
class="pc"
|
||||
:qr-img="QRImg"
|
||||
:os="os"
|
||||
:configs="configs"
|
||||
@getApkInfo="handleGetApkInfo"
|
||||
></pc2>
|
||||
</template>
|
||||
@@ -74,8 +68,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import getApkInfo from "@/api/getApkInfo";
|
||||
import getButtonlist from "@/api/getTemplate";
|
||||
import { getButtonList, bottonclick, downloadH5 } from "@/api/btn";
|
||||
import userAgent from "@/utils/userAgent";
|
||||
import QRCode from "qrcode";
|
||||
import axios from "@/utils/request.js";
|
||||
@@ -85,8 +78,7 @@ import pc from "./components/pc";
|
||||
import pc2 from "./components/pc2";
|
||||
import tips from "./components/tips";
|
||||
import selectLinePopup from "./components/selectLinePopup";
|
||||
import { getTraceId, landingPageView } from "@/utils/eventTracker";
|
||||
const APPID = 214;
|
||||
import { getTraceId } from "@/utils/traceId";
|
||||
export default {
|
||||
components: {
|
||||
mobile1,
|
||||
@@ -98,29 +90,15 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: "",
|
||||
QRImg: "",
|
||||
os: "",
|
||||
param: "",
|
||||
cutInfo: {}, //替换url参数对象内<key> dc内容;
|
||||
ip: "",
|
||||
switchData: {},
|
||||
page_pc_url: "",
|
||||
iosDownUrl: "",
|
||||
androidDownUrl: "",
|
||||
shopIosDownUrl: "", // 商店包链接
|
||||
storeAndroidDownUrl: "",
|
||||
channelApkAndDownUrl: "",
|
||||
// iosJumpSetUpUrl: "", // 执行完下载链接后需要执行的跳转
|
||||
loading: true,
|
||||
currUrl: window.location.href,
|
||||
apiUrls: JSON.parse(process.env.VUE_APP_BASE_HOSTS), // api数组
|
||||
showOverlay: false, // 显示选线弹窗
|
||||
configs: [],
|
||||
// tgUrl: "",
|
||||
// potatoUrl: "",
|
||||
// swUrl: "",
|
||||
// qdUrl: "",
|
||||
version: "A",
|
||||
trace_id: getTraceId()
|
||||
};
|
||||
@@ -132,67 +110,47 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// const queryVersion = (this.$route.query.version || "").toUpperCase();
|
||||
// this.version =
|
||||
// queryVersion === "A" || queryVersion === "B"
|
||||
// ? queryVersion
|
||||
// : Math.random() < 0.5
|
||||
// ? "A"
|
||||
// : "B";
|
||||
this.version = "A";
|
||||
this.getPcUrl();
|
||||
this.os = userAgent();
|
||||
this.functionParam();
|
||||
this.getButtonlist();
|
||||
landingPageView(this);
|
||||
this.init();
|
||||
const referrer = document.referrer;
|
||||
// console.log(referrer, "-----------referrer");
|
||||
if (referrer) {
|
||||
sessionStorage.setItem("referrer", referrer);
|
||||
}
|
||||
if (this.$route.query.dc) {
|
||||
sessionStorage.setItem("dc", this.$route.query.dc);
|
||||
}
|
||||
// this.getIp();
|
||||
},
|
||||
mounted() {
|
||||
this.getQRImg();
|
||||
},
|
||||
methods: {
|
||||
isDcMatch(value) {
|
||||
const reg = /^(?:ky(?:(?:0[1-9]|[1-9][0-9]|100))|TLGG(?:(?:0[1-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-6]))|erra(?:(?:0[1-9]|10))|cctl(?:(?:0[1-9]|10))|mijieer[1-5][a-d])$/;
|
||||
return reg.test(value);
|
||||
},
|
||||
getPcUrl() {
|
||||
this.page_pc_url = window.location.href;
|
||||
},
|
||||
//1.判断进来的url地址 替换到需要手动设置的参数 如cd
|
||||
functionParam() {
|
||||
this.param =
|
||||
JSON.stringify(this.$route.query) === "{}"
|
||||
? "{}"
|
||||
: JSON.stringify(this.$route.query);
|
||||
async init() {
|
||||
await this.getIp();
|
||||
await this.getButtonlist();
|
||||
},
|
||||
//2.获取按钮状态信息
|
||||
// 去掉空值后序列化渠道参数
|
||||
filterChannel() {
|
||||
const result = {};
|
||||
Object.entries(this.$route.query).forEach(([key, value]) => {
|
||||
if (value) result[key] = value;
|
||||
});
|
||||
return JSON.stringify(result);
|
||||
},
|
||||
//1.获取按钮开关(iosDown / iosStore / iosTF / iosH5 / androidButton)
|
||||
async getButtonlist() {
|
||||
try {
|
||||
let param_btn_list = {
|
||||
appId: APPID,
|
||||
channel: this.param,
|
||||
domain: window.location.protocol + "//" + window.location.host,
|
||||
version: this.version
|
||||
};
|
||||
const res = await getButtonlist(param_btn_list);
|
||||
const res = await getButtonList({
|
||||
ip: this.ip,
|
||||
channel: JSON.stringify(this.$route.query)
|
||||
});
|
||||
this.loading = false;
|
||||
if (res.data.code === 200) {
|
||||
this.switchData = res.data.data;
|
||||
this.configs = res.data.data.configs || [];
|
||||
this.androidDownUrl = res.data.data.androidDownUrl;
|
||||
this.storeAndroidDownUrl = res.data.data.storeAndroidDownUrl;
|
||||
this.channelApkAndDownUrl = res.data.data.channelApkAndDownUrl;
|
||||
this.iosDownUrl = res.data.data.iosDownUrl;
|
||||
this.shopIosDownUrl = res.data.data.shopIosDownUrl;
|
||||
// this.iosJumpSetUpUrl = res.data.data.iosJumpSetUpUrl;
|
||||
this.switchData = res.data.data || {};
|
||||
}
|
||||
} catch (err) {
|
||||
this.loading = false;
|
||||
@@ -200,105 +158,73 @@ export default {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
//3. 获取外网IP地址
|
||||
getIp() {
|
||||
axios.get("https://api.ipify.org/?format=json").then(res => {
|
||||
this.ip = res.data.ip;
|
||||
});
|
||||
//2. 获取外网IP地址
|
||||
async getIp() {
|
||||
try {
|
||||
const res = await fetch("https://api.ipify.org?format=json");
|
||||
const data = await res.json();
|
||||
this.ip = data.ip || "";
|
||||
} catch (err) {
|
||||
console.log("获取外部IP失败" + err);
|
||||
}
|
||||
},
|
||||
//4. 点击下载按钮获取apk信息并下载
|
||||
jumpToLink(link) {
|
||||
// 微信内加时间戳,避免命中缓存
|
||||
window.location.href = this.os.isWx ? link + "?time=" + Date.now() : link;
|
||||
},
|
||||
// bottonclick 返回的下载地址数组
|
||||
async fetchLinks(terminal, extra = {}) {
|
||||
const res = await bottonclick({
|
||||
ip: this.ip,
|
||||
channel: this.filterChannel(),
|
||||
domain: window.location.host || "",
|
||||
type: "iosH5",
|
||||
terminal,
|
||||
...extra
|
||||
});
|
||||
const link = res && res.data && res.data.data && res.data.data.link;
|
||||
return Array.isArray(link) ? link.filter(Boolean) : [];
|
||||
},
|
||||
// iOS 轻量版:直接调 h5/config 下载描述文件
|
||||
async downloadMobileConfig() {
|
||||
const paramData = {};
|
||||
if (this.$route.query.dc) paramData.dc = String(this.$route.query.dc);
|
||||
if (this.$route.query.pc) paramData.pc = String(this.$route.query.pc);
|
||||
const content = await downloadH5(paramData);
|
||||
const file = new File([content], "descriptionFile.mobileconfig", {
|
||||
type: "application/x-apple-aspen-config"
|
||||
});
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
const link = document.createElement("a");
|
||||
link.href = objectUrl;
|
||||
link.download = "descriptionFile.mobileconfig";
|
||||
link.click();
|
||||
setTimeout(() => URL.revokeObjectURL(objectUrl), 10000);
|
||||
},
|
||||
//3. 点击下载按钮获取下载地址并下载
|
||||
async handleGetApkInfo(type) {
|
||||
await this.doCopy();
|
||||
let terminal;
|
||||
let packageType;
|
||||
if (type === "ios") {
|
||||
terminal = "ios";
|
||||
packageType = 1;
|
||||
} else if (type == "shopIos") {
|
||||
terminal = "ios";
|
||||
packageType = 2;
|
||||
} else if (type == "androidSpare") {
|
||||
terminal = "androidSpare";
|
||||
} else {
|
||||
terminal = "android";
|
||||
try {
|
||||
if (type === "ios") {
|
||||
await this.downloadMobileConfig();
|
||||
} else if (type === "shopIos") {
|
||||
if (!this.switchData.iosStore) return;
|
||||
const links = await this.fetchLinks("ios", {
|
||||
type: "iosStore",
|
||||
packageType: 2
|
||||
});
|
||||
if (links.length) this.jumpToLink(links[0]);
|
||||
} else {
|
||||
const links = await this.fetchLinks("android");
|
||||
const target = (type === "androidSpare" && links[1]) || links[0];
|
||||
if (target) this.jumpToLink(target);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("下载失败" + err);
|
||||
}
|
||||
if (terminal === "android" || terminal === "androidSpare") {
|
||||
let dc = this.$route.query.dc;
|
||||
if (this.storeAndroidDownUrl && this.isDcMatch(dc)) {
|
||||
let url = this.storeAndroidDownUrl || this.channelApkAndDownUrl;
|
||||
window.location.href = url;
|
||||
} else if (terminal === "androidSpare") {
|
||||
window.location.href = this.androidDownUrl;
|
||||
} else {
|
||||
let jumpUrl = this.channelApkAndDownUrl || this.androidDownUrl;
|
||||
window.location.href = jumpUrl;
|
||||
}
|
||||
} else {
|
||||
let dc = this.$route.query.dc;
|
||||
let ch = this.$route.query.ch;
|
||||
let pc = this.$route.query.pc;
|
||||
let linkUrl = "";
|
||||
if (dc) {
|
||||
linkUrl = `${this.iosDownUrl}?dc=${dc}&tid=${this.trace_id}`;
|
||||
} else if (ch) {
|
||||
linkUrl = `${this.iosDownUrl}?ch=${ch}&tid=${this.trace_id}`;
|
||||
} else if (pc) {
|
||||
linkUrl = `${this.iosDownUrl}?pc=${pc}&tid=${this.trace_id}`;
|
||||
} else {
|
||||
linkUrl = `${this.iosDownUrl}?tid=${this.trace_id}`;
|
||||
}
|
||||
if (type == "shopIos" && this.shopIosDownUrl) {
|
||||
window.open(this.shopIosDownUrl);
|
||||
} else {
|
||||
window.location.href = linkUrl;
|
||||
}
|
||||
// if (this.iosJumpSetUpUrl) {
|
||||
// setTimeout(() => {
|
||||
// window.location.href = this.iosJumpSetUpUrl;
|
||||
// }, 1500);
|
||||
// }
|
||||
}
|
||||
// getApkInfo(param);
|
||||
},
|
||||
recordClick(type) {
|
||||
let terminal;
|
||||
let packageType;
|
||||
if (type === "ios") {
|
||||
terminal = "ios";
|
||||
packageType = 1;
|
||||
} else if (type == "shopIos") {
|
||||
terminal = "ios";
|
||||
packageType = 2;
|
||||
} else {
|
||||
terminal = "android";
|
||||
}
|
||||
const param = {
|
||||
appId: APPID,
|
||||
ip: this.ip ? this.ip : "", //获取ip地址
|
||||
channel: JSON.stringify({
|
||||
...JSON.parse(this.param),
|
||||
...this.cutInfo
|
||||
}),
|
||||
domain: window.location.protocol + "//" + window.location.host,
|
||||
terminal,
|
||||
packageType,
|
||||
version: this.version
|
||||
};
|
||||
getApkInfo(param);
|
||||
},
|
||||
//5.生成二维码
|
||||
//4.生成二维码
|
||||
getQRImg() {
|
||||
// const referrer = sessionStorage.getItem("referrer");
|
||||
// const dc = sessionStorage.getItem("dc");
|
||||
// if (referrer && dc) {
|
||||
// this.currUrl = referrer + "user.html?dc=" + dc;
|
||||
// } else {
|
||||
// if (referrer) {
|
||||
// this.currUrl = referrer + "user.html";
|
||||
// } else {
|
||||
// this.currUrl = window.location.href;
|
||||
// }
|
||||
// }
|
||||
QRCode.toDataURL(this.currUrl, {
|
||||
width: 170,
|
||||
margin: 1,
|
||||
@@ -317,13 +243,13 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
// 6.剪贴板
|
||||
// 5.剪贴板
|
||||
doCopy() {
|
||||
let req = JSON.stringify({
|
||||
...this.$route.query,
|
||||
tid: this.trace_id
|
||||
});
|
||||
this.$copyText(req).then(
|
||||
return this.$copyText(req).then(
|
||||
() => {
|
||||
console.log("复制成功");
|
||||
},
|
||||
|
||||