diff --git a/project.md b/project.md index 3c4803e..cead6b7 100644 --- a/project.md +++ b/project.md @@ -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 头部元数据若与真实组织不一致,请在内部系统中核对后修改。 diff --git a/public/favicon.ico b/public/favicon.ico index 28b642b..424f800 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html index 888a869..2261800 100644 --- a/public/index.html +++ b/public/index.html @@ -5,86 +5,27 @@ - - + - 91短剧 APP下载 | 国产大神成人短剧平台 百万资源免费 - - + 黄果漫剧 APP下载 | 百万大神短剧原创 海量资源免费 + + - - + + - - - + - - + + - - - + diff --git a/public/sdk/.gitkeep b/public/sdk/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/public/sdk/landing-sdk-v1.1.1.js b/public/sdk/landing-sdk-v1.1.1.js deleted file mode 100644 index 8d9b07b..0000000 --- a/public/sdk/landing-sdk-v1.1.1.js +++ /dev/null @@ -1 +0,0 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).LandingSDK=t()}(this,function(){"use strict";if("function"!=typeof Object.keys||"function"!=typeof Date.now||"function"!=typeof Array.isArray){var e=function(){},t=function(){};t.landingPageView=e,t.landingPageClick=e;var n=function(){};if(n.init=e,n.track=t,"undefined"!=typeof globalThis)try{globalThis.LandingSDK=n}catch(e){}else"undefined"!=typeof window&&(window.LandingSDK=n);return n}function r(e){return r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r(e)}var i,o="_s_token_",a="_d_token_",u="_d_sig_",c="_ping_flag_",d="data-app-id",l="app-id",f="event_id",v=null,p=null,s=null,g=null,y=function(e){var t=e.toString(16);return 1===t.length?"0"+t:t},_=function(){for(var e="",t=0;t<16;t++)e+=y(Math.floor(256*Math.random()));return e},h=function(){if(null!==g)return g;try{return window.localStorage.setItem(c,"1"),window.localStorage.removeItem(c),g=!0,!0}catch(e){return g=!1,!1}},w=function(){if("undefined"==typeof navigator)return"PC";var e=navigator.userAgent||"";return/Android/i.test(e)?"Android":/iPhone|iPad|iPod/i.test(e)?"iOS":"PC"},m=function(e){if(!e)return"";for(var t=e+"device_id_secret_key_v1",n=0,r=0;r0&&void 0!==arguments[0]?arguments[0]:"")||""),n=0;n>>0).toString(16).padStart(8,"0")}(y));return function(t){if(t&&h())try{window.localStorage.setItem(e,t)}catch(e){}}(_),_}catch(e){return""}},S=function(){var e,t,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=function(){if("undefined"==typeof window)return"";if(h())try{var e=window.localStorage.getItem(a),t=window.localStorage.getItem(u);if(e&&!t){var n=_(),r=m(n);return window.localStorage.setItem(a,n),window.localStorage.setItem(u,r),n}if(e&&t){if(m(e)===t)return e;var i=_(),o=m(i);return window.localStorage.setItem(a,i),window.localStorage.setItem(u,o),i}var c=_(),d=m(c);return window.localStorage.setItem(a,c),window.localStorage.setItem(u,d),c}catch(e){}return s||(s=_()),s}(),i=function(){var e,t,n,r=(t=(e=new Date).getMonth()+1,n=e.getDate(),e.getFullYear()+"-"+(t<10?"0"+t:t)+"-"+(n<10?"0"+n:n));if("undefined"!=typeof window&&h())try{var i=window.localStorage.getItem(o);if(i)try{var a=JSON.parse(i);if(a.date===r&&a.sid)return a.sid}catch(e){}var u=_();return window.localStorage.setItem(o,JSON.stringify({sid:u,date:r})),u}catch(e){}return v&&p===r||(v=_(),p=r),v}(),c="",d="";return{app_id:n.appId||b(),uid:0,channel:n.channel||"",sid:i,client_ts:Math.floor(Date.now()/1e3),device:w(),device_id:n.deviceId||r,user_agent:"undefined"==typeof navigator?"":navigator.userAgent||"",device_brand:c,device_model:d,fp_version:"1.0.0",device_fingerprint:k(),sdk_version:"1.1.1",app_version:(e=n.appVersion,t=String(null==e?"":e).trim(),t?/^\d+\.\d+\.\d+$/.test(t)?t:("undefined"!=typeof console&&console.warn,"1.0.0"):"1.0.0")}},A=function(){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1],t=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"")||(window.location.origin?window.location.origin:window.location.protocol+"//"+window.location.hostname+(window.location.port?":"+window.location.port:""));if(!n)return"";var r="/"===n.charAt(n.length-1)?n.slice(0,-1):n,i=e?"/dcserv":"",o=t?"/api/eventTracking/batchReport.json":"/api/eventTracking/report.json";return"".concat(r).concat(i).concat(o)},j=function(){for(var e={},t=0;t>6,128|63&i);else if(i>=55296&&i<=56319&&r+1=56320&&o<=57343){var a=o-56320+(i-55296<<10)+65536;n.push(240|a>>18,128|a>>12&63,128|a>>6&63,128|63&a),r+=1}else n.push(224|i>>12,128|i>>6&63,128|63&i)}else n.push(224|i>>12,128|i>>6&63,128|63&i)}var u=8*n.length;for(n.push(128);n.length%64!=56;)n.push(0);for(var c=0;c<8;c+=1)n.push(u>>>8*c&255);for(var d=function(e,t){return e<>>32-t},l=function(e,t){return((e>>>0)+(t>>>0)&4294967295)>>>0},f=function(e,t,n,r){return l(l(e,t),l(n,r))},v=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],p=new Array(64),s=0;s<64;s+=1)p[s]=Math.floor(4294967296*Math.abs(Math.sin(s+1)))>>>0;for(var g=1732584193,y=4023233417,_=2562383102,h=271733878,w=0;w>>0}for(var S=g,A=y,j=_,I=h,O=0;O<64;O+=1){var P=void 0,T=void 0;O<16?(P=A&j|~A&I,T=O):O<32?(P=I&A|~I&j,T=(5*O+1)%16):O<48?(P=A^j^I,T=(3*O+5)%16):(P=j^(A|~I),T=7*O%16);var q=I;I=j,j=A,A=l(A,d(f(S,P,p[O],m[T]),v[O])),S=q}g=l(g,S),y=l(y,A),_=l(_,j),h=l(h,I)}var x=function(e){for(var t="",n=0;n<4;n+=1)t+=(256+(e>>>8*n&255)).toString(16).slice(1);return t};return""+x(g)+x(y)+x(_)+x(h)},O=function(e){return null===e?"__t:null__":"string"==typeof e?"__t:str__:"+e:"number"==typeof e&&isFinite(e)?"__t:num__:"+String(e):"boolean"==typeof e?"__t:bool__:"+(e?"1":"0"):Array.isArray(e)?"["+e.map(O).join(",")+"]":"object"===r(e)?"{"+Object.keys(e).sort().map(function(t){return t+":"+O(e[t])}).join(",")+"}":"__t:any__:"+String(e)},P=function(e){var t=(e=e||{}).endpoint?e.endpoint:A(e.domain,!0,e.batch)||"",n=function(e){return e=e||{},{sendPayload:function(t,n){if(n){var i,o;if(e.batch?(i=JSON.stringify(t),o="application/json"):(i=function(e){for(var t=Object.keys(e||{}),n=[],i=0;i100?E(e,t,r,"must be int in [0,100]"):{keep:!0,value:o}}if(function(e){return"page_no"===e||"click_position"===e||/_position$/.test(e)}(t)){var a=M(n);return!D(a)||a<1?E(e,t,r,"must be int >= 1"):{keep:!0,value:a}}if("amount"===t||"coin_quantity"===t||"search_result_count"===t||"click_page_x"===t||"click_page_y"===t||"screen_width"===t||"screen_height"===t||"video_duration"===t||"play_duration"===t||"page_load_time"===t){var u=M(n);return!D(u)||u<=0?E(e,t,r,"must be int > 0"):{keep:!0,value:u}}if(x[t]){var c=C(n);if(!c)return E(e,t,r,"must not be empty");var d=c.split(",").map(function(e){return e.trim()}).filter(Boolean).join(",");return d?{keep:!0,value:d}:E(e,t,r,"must not be empty")}if(function(e){return/_key$/.test(e)}(t)){var l=C(n);return!l||l.length>64||!/^[A-Za-z0-9_]+$/.test(l)?E(e,t,r,"must match key rule"):{keep:!0,value:l}}if(function(e){return/_name$/.test(e)}(t)){var f=C(n);return f?{keep:!0,value:f.slice(0,128)}:E(e,t,r,"must not be empty")}if(function(e){return/(^id$|_id$)/.test(e)}(t)){var v=C(n);return v?{keep:!0,value:v.slice(0,128)}:E(e,t,r,"must not be empty")}if(function(e){return"comment_content"===e||"keyword"===e||/_title$/.test(e)||"novel_title"===e||"video_title"===e}(t)){var p=C(n),s="comment_content"===t||"keyword"===t?500:256;return p?{keep:!0,value:p.slice(0,s)}:E(e,t,r,"must not be empty")}if(Object.prototype.hasOwnProperty.call(q,t)){var g=C(n);return q[t][g]?{keep:!0,value:g}:E(e,t,r,"must be in enum whitelist")}return"string"==typeof n?{keep:!0,value:n.trim()}:{keep:!0,value:n}},N=function(e){return null===e?"null":Array.isArray(e)?"array":r(e)},R=function(e,t,n){t=t||{};var i=(n=n||{}).allowExtra,o=void 0!==i&&i,a=t&&"object"===r(t)&&!Array.isArray(t)?t:null;if(!a)throw new TypeError("[sdk][event] "+e+" payload must be an object");for(var u=T[e]||{},c=Object.keys(a),d={},l=0;l + 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(); +}; diff --git a/src/api/getApkInfo.js b/src/api/getApkInfo.js deleted file mode 100644 index b1941ad..0000000 --- a/src/api/getApkInfo.js +++ /dev/null @@ -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 - }); diff --git a/src/api/getCustomerServiceUrl.js b/src/api/getCustomerServiceUrl.js deleted file mode 100644 index b130a40..0000000 --- a/src/api/getCustomerServiceUrl.js +++ /dev/null @@ -1,8 +0,0 @@ -import request from "@/utils/request"; - -export default data => - request({ - method: "post", - url: "/plm/okn/ijb/fsdfrererdfdf", - data, - }); diff --git a/src/api/getTemplate.js b/src/api/getTemplate.js deleted file mode 100644 index 974e87a..0000000 --- a/src/api/getTemplate.js +++ /dev/null @@ -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 - }); diff --git a/src/assets/images/common/hgmj_logo.png b/src/assets/images/common/hgmj_logo.png new file mode 100644 index 0000000..090c04b Binary files /dev/null and b/src/assets/images/common/hgmj_logo.png differ diff --git a/src/assets/images/mobile1/bottomBg.webp b/src/assets/images/mobile1/bottomBg.webp index 927467e..aba6eb0 100644 Binary files a/src/assets/images/mobile1/bottomBg.webp and b/src/assets/images/mobile1/bottomBg.webp differ diff --git a/src/assets/images/mobile1/iosTip.webp b/src/assets/images/mobile1/iosTip.webp index f8e789d..ae13dcc 100644 Binary files a/src/assets/images/mobile1/iosTip.webp and b/src/assets/images/mobile1/iosTip.webp differ diff --git a/src/assets/images/pc/bg.png b/src/assets/images/pc/bg.png index c85875b..5825a39 100644 Binary files a/src/assets/images/pc/bg.png and b/src/assets/images/pc/bg.png differ diff --git a/src/utils/eventTracker.js b/src/utils/eventTracker.js deleted file mode 100644 index b79f12b..0000000 --- a/src/utils/eventTracker.js +++ /dev/null @@ -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); -} diff --git a/src/utils/traceId.js b/src/utils/traceId.js new file mode 100644 index 0000000..758371f --- /dev/null +++ b/src/utils/traceId.js @@ -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; +} diff --git a/src/views/land/components/mobile1.vue b/src/views/land/components/mobile1.vue index a93c177..c8dbb0d 100644 --- a/src/views/land/components/mobile1.vue +++ b/src/views/land/components/mobile1.vue @@ -1,35 +1,13 @@