Commit 5458a8f6 authored by haiyoucuv's avatar haiyoucuv

内置工具包built-in

埋点
parent 2bcf5fd8
...@@ -16,3 +16,4 @@ playground-temp ...@@ -16,3 +16,4 @@ playground-temp
temp temp
TODOs.md TODOs.md
.eslintcache .eslintcache
*.tsbuildinfo
export * from "./utils";
export * from "./net";
export * from "./md"; export * from "./md";
export * from "./net";
\ No newline at end of file
import { doClickLog, doShowLog } from "./main"; import { doShowLog } from "./main";
import { getReference } from "../utils";
import { IAutoMdData } from "./interface.ts"; import { IAutoMdData } from "./interface.ts";
import { logClick } from "./manual.ts";
// 如果点击埋点和跳转埋点只能添加一个的话,两个mapper可以合并成为一个mapper // 如果点击埋点和跳转埋点只能添加一个的话,两个mapper可以合并成为一个mapper
const clickMapper = new Map(); const clickMapper = new Map();
const generatorClick = (item: IAutoMdData) => { const generatorClick = (item: IAutoMdData) => {
const ele = getReference(item.ele); const ele = document.querySelector(item.ele);
if (!!ele && !clickMapper.get(item.ele)) { if (!!ele && !clickMapper.get(item.ele)) {
const handler = doClickLog.bind(null, item.data); const handler = logClick.bind(null, item.data);
clickMapper.set(item.ele, { clickMapper.set(item.ele, {
ele, ele,
handler, handler,
......
export * from './interface.ts';
export * from './auto.ts'; export * from './auto.ts';
export * from './manual.ts'; export * from './manual.ts';
import "./intersection-observer.polyfill.js"; import "./intersection-observer.polyfill.js";
import "intersection-observer"; import "intersection-observer";
import { jsonp } from "../net";
// import axios from "axios";
import { obj2query } from "../utils";
import { IAutoMdData } from "./interface.ts"; import { IAutoMdData } from "./interface.ts";
import { logExposure } from "./manual.ts";
const showedMapper = {}; const showedMapper = {};
const intersectionObservers: IntersectionObserver[] = [] const intersectionObservers: IntersectionObserver[] = [];
function registryIntersectionObserver(items: any[]) { function registryIntersectionObserver(items: any[]) {
for (const observer of intersectionObservers) { for (const observer of intersectionObservers) {
observer.disconnect() observer.disconnect();
} }
intersectionObservers.splice(0) intersectionObservers.splice(0);
items.forEach((item) => { items.forEach((item) => {
if (typeof item.ele === "string") { if (typeof item.ele === "string") {
...@@ -44,7 +41,7 @@ const generatorShow = (item: IAutoMdData) => { ...@@ -44,7 +41,7 @@ const generatorShow = (item: IAutoMdData) => {
observer.unobserve(change.target); observer.unobserve(change.target);
jsonp(`${data.domain}/exposure/standard?${obj2query(data)}`); logExposure(data);
} else { } else {
// 消失在视口 // 消失在视口
...@@ -63,7 +60,7 @@ const generatorShow = (item: IAutoMdData) => { ...@@ -63,7 +60,7 @@ const generatorShow = (item: IAutoMdData) => {
} }
}; };
export function doShowLog(items) { export function doShowLog(items: IAutoMdData[]) {
// 先注册一次 // 先注册一次
registryIntersectionObserver(items); registryIntersectionObserver(items);
...@@ -73,8 +70,3 @@ export function doShowLog(items) { ...@@ -73,8 +70,3 @@ export function doShowLog(items) {
}); });
observer.observe(document, { childList: true, subtree: true }); observer.observe(document, { childList: true, subtree: true });
} }
export function doClickLog(data) {
jsonp(`${location.host}/log/click?${obj2query(data)}`);
// axios.get(`/log/click?${obj2query(data)}`);
}
import { doClickLog } from "./main.ts";
import { IMdData } from "./interface.ts"; import { IMdData } from "./interface.ts";
import { jsonp } from "../net";
import { obj2query } from "../utils";
export function handleLogExposure(data: IMdData) { export function logExposure(data: IMdData) {
jsonp(`${data.domain}/exposure/standard?${obj2query(data)}`);
} }
export function handleLogClick(data: IMdData) { export function logClick(data: IMdData) {
doClickLog(data); jsonp(`/log/click?${obj2query(data)}`);
} }
import { obj2query, urlJoin } from "../utils"; import { obj2query, urlJoin } from "../utils";
export function jsonp(url: string, params: any = {}) { export function jsonp(url: string, params: any = {}) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const src = urlJoin(url, obj2query(params)); const src = urlJoin(url, obj2query(params));
const scriptEl = document.createElement('script'); const scriptEl = document.createElement('script');
scriptEl.src = src;
scriptEl.onload = function () { scriptEl.src = src;
resolve(); scriptEl.onload = function () {
document.body.removeChild(scriptEl); resolve();
}; document.body.removeChild(scriptEl);
scriptEl.onerror = function () { };
reject(); scriptEl.onerror = function () {
document.body.removeChild(scriptEl); reject();
}; document.body.removeChild(scriptEl);
/*const callbackFuncName = '__zeroing_jsonp_callback__' + Math.random(); };
window[callbackFuncName] = function () { /*const callbackFuncName = '__zeroing_jsonp_callback__' + Math.random();
callback(result); window[callbackFuncName] = function () {
};*/ callback(result);
document.body.appendChild(scriptEl); };*/
}) document.body.appendChild(scriptEl);
} })
\ No newline at end of file }
/**
* 添加script
* @ctype PROCESS
* @description 添加script,可以多个
* @param {string} script - js脚本
* @param {HTMLElement} [parentNode=document.head] - 父节点
* @example 一般用法
* appendScript('function test(text){console.log("text:", text)}')
* test('hello'); //hello
*/
export function appendScript(script: string, parentNode: HTMLElement = document.head) {
if (!script) {
return {
type: 'failed'
}
}
if (script.indexOf('<script') === 0) {
let temp = document.createElement('div')
temp.innerHTML = script
for (let i = 0, li = temp.children.length; i < li; i++) {
const child: any = temp.children[i]
if (child.src) {
appendJsScript(child.src, parentNode)
i--
li--
} else {
evalJsScript(child.innerHTML, parentNode)
}
}
} else {
evalJsScript(script, parentNode)
}
}
/**
* 执行js脚本
* @ctype PROCESS
* @description 执行js脚本
* @param {string} script - 脚本文本
* @param {HTMLElement} [parentNode=document.head] - 父节点
*/
export function evalJsScript(script: string, parentNode: HTMLElement = document.head) {
let el = document.createElement('script')
el.innerHTML = script
document.head.appendChild(el)
setTimeout(() => {
document.head.removeChild(el)
}, 1)
}
/**
* 动态引入js
* @ctype PROCESS
* @description 执行js脚本
* @param {string} url - js脚本地址
* @param {HTMLElement} [parentNode=document.head] - 父节点
*/
export function appendJsScript(url: string, parentNode: HTMLElement = document.head) {
return new Promise((resolve, reject) => {
let scriptEl = document.createElement('script')
scriptEl.src = url
scriptEl.onload = function () {
document.head.removeChild(scriptEl)
resolve(true)
}
scriptEl.onerror = function () {
document.head.removeChild(scriptEl)
reject();
}
parentNode.appendChild(scriptEl)
})
}
export * from "./utils.ts"; export * from "./utils.ts";
\ No newline at end of file export * from "./browser.ts";
...@@ -23,12 +23,3 @@ export function urlJoin(url: string, query: any) { ...@@ -23,12 +23,3 @@ export function urlJoin(url: string, query: any) {
return url; return url;
} }
} }
export const getReference = (ele: string | HTMLElement) => {
if (ele instanceof HTMLElement) {
return ele;
} else if (typeof ele === "string") {
return document.querySelector(ele);
}
}
\ No newline at end of file
...@@ -21,6 +21,9 @@ ...@@ -21,6 +21,9 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"declaration": true, "declaration": true,
"sourceMap": true, "sourceMap": true,
"composite": false,
}, },
"include": ["src"] "include": ["src"]
} }
{"root":["./src/main.tsx","./src/vite-env.d.ts","./src/md/auto.ts","./src/md/index.ts","./src/md/interface.ts","./src/md/main.ts","./src/md/manual.ts","./src/net/index.ts","./src/net/jsonp.ts","./src/utils/index.ts","./src/utils/utils.ts"],"version":"5.6.3"}
\ No newline at end of file
{ {
"files": [], "files": [],
"compilerOptions": {
"composite": false
},
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" } { "path": "./tsconfig.node.json" }
......
...@@ -19,7 +19,9 @@ ...@@ -19,7 +19,9 @@
"strict": false, "strict": false,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true "noFallthroughCasesInSwitch": true,
"composite": false
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }
{"root":["./vite.config.ts"],"version":"5.6.3"}
\ No newline at end of file
# 顶部的EditorConfig文件
root = true
# unix风格的换行符,每个文件都以换行符结尾
[*]
end_of_line = lf
insert_final_newline = true
# 设置默认字符集
charset = utf-8
# 去除行尾空白字符
trim_trailing_whitespace = true
# 使用空格缩进,设置2个空格缩进
indent_style = space
indent_size = 2
...@@ -8,7 +8,6 @@ pnpm-debug.log* ...@@ -8,7 +8,6 @@ pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
node_modules node_modules
dist
dist-ssr dist-ssr
*.local *.local
...@@ -22,3 +21,6 @@ dist-ssr ...@@ -22,3 +21,6 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
dist/*/*
!dist/index.html
\ No newline at end of file
UPLOAD_DIR=db_games/spark/v3
\ No newline at end of file
# .env.production
CDN_DOMAIN=//yun.duiba.com.cn
OSS_REGION=oss-cn-hangzhou
OSS_BUCKET=duiba
OSS_ACCESS_KEY_ID=LTAI5tPUSSxgkEmKPAfVXUQQ
OSS_ACCESS_SECRET=6sk3EDd1BYrXlAUoh8maMuN7hOMkh1
\ No newline at end of file
import chalk from "chalk";
import AutoUpload from "../Uploader/Uploader.ts";
import * as path from "path";
interface IDuibaPublishOptions {
buildVersion: string | number,
uploadDir: string,
accessKeySecret: string,
accessKeyId: string,
bucket: string,
region: string,
}
export default function DuibaPublish(options: IDuibaPublishOptions) {
const {
buildVersion,
uploadDir,
accessKeySecret,
accessKeyId,
bucket,
region,
} = options;
return {
name: 'duiba-publish',
async closeBundle() {
const autoUpload = new AutoUpload({
dir: path.resolve("dist"),
originDir: `/${uploadDir}/${buildVersion}/`,
accessKeySecret, accessKeyId, bucket, region,
});
await autoUpload.start();
console.log(`${chalk.green(`上传成功: ${buildVersion}`)}`);
}
}
}
import * as path from "path";
import * as fs from "fs";
import ProgressBar from "progress";
import OSS from "ali-oss";
interface IAutoUploadOptions {
dir: string,
originDir: string,
bucket: string,
accessKeyId: string,
accessKeySecret: string,
region: string,
}
export default class AutoUpload {
options: IAutoUploadOptions = {
dir: "",
originDir: "",
region: "oss-cn-hangzhou",
accessKeyId: "",
accessKeySecret: "",
bucket: ""
};
client = null;
bar = null;
private _files: any[];
private existFiles: number = 0;
private uploadFiles: number = 0;
private errorFiles: number = 0;
constructor(props: IAutoUploadOptions) {
this.options = Object.assign({}, this.options, props);
const checkOptions = [
"dir", "originDir",
"bucket", "region",
"accessKeySecret", "accessKeyId",
];
for (const optionKey of checkOptions) {
if (!this.options[optionKey]) {
throw new Error(`AutoUpload: required option "${optionKey}"`);
}
}
this.init();
}
init() {
const {accessKeyId, accessKeySecret, bucket, region} = this.options;
this.client = new OSS({region, accessKeyId, accessKeySecret, bucket});
this.bar = new ProgressBar(`文件上传中 [:bar] :current/${this.files().length} :percent :elapseds`, {
complete: "●",
incomplete: "○",
width: 20,
total: this.files().length,
callback: () => {
console.log("%cAll complete.", "color: green");
console.log(`%c本次队列文件共${this.files().length}个,已存在文件${this.existFiles}个,上传文件${this.uploadFiles}个,上传失败文件${this.errorFiles}个`, "color: green");
}
})
return this;
}
files() {
if (this._files) return this._files;
this._files = [];
/**
* 文件遍历方法
* @param filePath 需要遍历的文件路径
*/
const fileDisplay = (filePath) => {
//根据文件路径读取文件,返回文件列表
const files = fs.readdirSync(filePath);
files.forEach((filename) => {
//获取当前文件的绝对路径
const fileDir = path.join(filePath, filename);
//根据文件路径获取文件信息,返回一个fs.Stats对象
const stats = fs.statSync(fileDir);
const isFile = stats.isFile();//是文件
const isDir = stats.isDirectory();//是文件夹
if (isFile) {
this._files.push(fileDir);
} else if (isDir) {
fileDisplay(fileDir);//递归,如果是文件夹,就继续遍历该文件夹下面的文件
}
});
}
//调用文件遍历方法
fileDisplay(this.options.dir);
return this._files;
}
async start() {
const ps = this.files().map((file) => {
const relativePath = path.relative(this.options.dir, file)
.replace(path.sep, "/");
this.existFiles = 0;
this.uploadFiles = 0;
this.errorFiles = 0;
const originPath = `${this.options.originDir}${relativePath}`;
return (async () => {
let originFile = null;
originFile = await this.client.head(originPath)
.catch((error: Error) => originFile = error);
try {
if (originFile.status === 404) {
await this.client.put(originPath, file);
this.uploadFiles += 1;
} else {
this.existFiles += 1;
}
} catch (error) {
this.errorFiles += 1;
}
this.bar.tick();
})();
});
await Promise.all(ps).catch((err) => {
console.error("上传错误", err);
});
}
}
...@@ -19,6 +19,11 @@ export default tseslint.config( ...@@ -19,6 +19,11 @@ export default tseslint.config(
}, },
rules: { rules: {
...reactHooks.configs.recommended.rules, ...reactHooks.configs.recommended.rules,
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-unused-expressions": "off",
"prefer-rest-params": "off",
'react-refresh/only-export-components': [ 'react-refresh/only-export-components': [
'warn', 'warn',
{ allowConstantExport: true }, { allowConstantExport: true },
......
...@@ -2,12 +2,39 @@ ...@@ -2,12 +2,39 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="theme-color" content="#000000">
<title>Vite + React + TS</title> <link rel="dns-prefetch" href="//yun.duiba.com.cn" />
<link rel="preconnect" href="//embedlog.duiba.com.cn">
<title>活动标题</title>
<script type="text/javascript">
if (localStorage && localStorage.isWebp) {
document
.getElementsByTagName('html')[0]
.setAttribute('duiba-webp', 'true');
}
</script>
<script src="//yun.duiba.com.cn/js-libs/rem/1.1.3/rem.min.js"></script>
<script>
var CFG = CFG || {};
CFG.projectId = location.pathname.split('/')[2] || '1';
function getUrlParam(name) {
var search = window.location.search;
var matched = search
.slice(1)
.match(new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'));
return search.length ? matched && matched[2] : null;
}
CFG.appID = '${APPID}';
if (!getUrlParam("appID")) {
// alert("【警告】检测到活动url中没有appID参数\n缺少该参数会导致埋点、分享、app信息获取错误。")
}
</script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/App.tsx"></script>
</body> </body>
</html> </html>
import CryptoJS from 'crypto-js';
const { mode, pad, enc, AES } = CryptoJS;
const getOptions = (iv: string) => {
return {
iv: enc.Utf8.parse(iv),
mode: mode.CBC,
padding: pad.ZeroPadding,
};
}
/** 加密 */
export function AESEncrypt(str: string, key: string, iv: string) {
const options = getOptions(iv);
return AES.encrypt(str, enc.Utf8.parse(key), options).toString();
}
/** 解密 */
export function AESDecrypt(cipherText: string, key: string, iv: string) {
const options = getOptions(iv);
return AES.decrypt(cipherText, enc.Utf8.parse(key), options)
.toString(enc.Utf8)
.trim()
.replace(//g, '')
.replace(//g, '')
.replace(/\v/g, '')
.replace(/\x00/g, '');
}
import { AESEncrypt } from "./Crypto";
export default [
{
url: '/projectRule.query',
method: 'get',
response: ({query}) => {
return {
"data": "<p>以下是游戏规则:手速要快,点击红包雨。。333。。。。。。。。。。。。。。。。。。。。11111111111111sadasdadadsad5555555557777777777799999999999911111111111111111111111222222222222222222222222222222222222222222222222222222222222222333333333333333333333333333333333333333333333333333333333333311111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333331111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333</p>",
"success": true
}
},
},
{
url: '/coop_frontVariable.query',
method: 'get',
response: ({query}) => {
return {
"success": true,
"message": "报错了~",
"code": null,
"data": {
"test_config_01": "前端配置项测试",
"test_config_03": null,
"test_config_02": "111"
}
}
},
},
{
url: '/spring/start.do',
method: 'get',
response: ({query}) => {
return {
"code": "code",
"success": true,
"message": "message",
"timeStamp": Date.now(),
"data": AESEncrypt(JSON.stringify({
"startId": "officia",
"countDown": 30
}), "1696BD3E5BB915A0", "cDOiBC1n2QrkAY2P"),
}
},
},
]
export default [
{
url: '/log/click',
method: 'get',
response: 1,
},
]
{ {
"name": "playground", "name": "@grace/playground",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
...@@ -10,22 +10,49 @@ ...@@ -10,22 +10,49 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@csstools/normalize.css": "^12.1.1",
"@grace/built-in": "workspace:^", "@grace/built-in": "workspace:^",
"@grace/svgaplayer": "workspace:^", "@grace/svgaplayer": "workspace:^",
"@spark/ui": "^2.1.28",
"@types/node": "^22.7.6",
"axios": "^1.7.7",
"chalk": "^5.3.0",
"crypto-js": "^4.2.0",
"duiba-utils": "^2.0.2",
"intersection-observer": "^0.12.2",
"less": "^4.2.0",
"mobx": "^6.13.1",
"mobx-react": "^9.1.1",
"progress": "^2.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1",
"spark-utils": "^1.1.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.13.0", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@types/react": "^18.3.11", "@babel/plugin-proposal-decorators": "^7.24.7",
"@types/react-dom": "^18.3.1", "@eslint/js": "^9.9.0",
"@vitejs/plugin-react": "^4.3.3", "@types/ali-oss": "^6.16.11",
"eslint": "^9.13.0", "@types/crypto-js": "^4.2.2",
"eslint-plugin-react-hooks": "^5.0.0", "@types/postcss-pxtorem": "^6.0.3",
"eslint-plugin-react-refresh": "^0.4.13", "@types/progress": "^2.0.7",
"globals": "^15.11.0", "@types/react": "^18.3.3",
"typescript": "~5.6.2", "@types/react-dom": "^18.3.0",
"typescript-eslint": "^8.10.0", "@vitejs/plugin-legacy": "^5.4.2",
"vite": "^5.4.9" "@vitejs/plugin-react": "^4.3.1",
"ali-oss": "^6.21.0",
"autoprefixer": "^10.4.20",
"dotenv": "^16.4.5",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"mockjs": "^1.1.0",
"postcss-pxtorem": "^6.1.0",
"terser": "^5.36.0",
"typescript": "5.5.4",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.6",
"vite-plugin-mock": "^3.0.2"
} }
} }
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
...@@ -30,3 +30,13 @@ body { ...@@ -30,3 +30,13 @@ body {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.console {
width: 100%;
height: 100%;
}
.testBtn{
width: 750px;
height: 100px;
}
\ No newline at end of file
.svgaTest {
background: rgba(0, 0, 0, 0.5);
width: 300px;
height: 100px;
position: absolute;
}
.svgaTest2 {
background: rgba(0, 0, 0, 0.5);
width: 300px;
height: 100px;
position: absolute;
left: 0;
top: 110px;
}
.svgaTest3 {
background: rgba(0, 0, 0, 0.5);
width: 300px;
height: 100px;
position: absolute;
left: 0;
top: 220px;
}
.button {
margin: 5px;
padding: 10px;
border: 1px solid black;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
}
.container {
position: absolute;
left: 0;
top: 800px;
width: 100%;
display: flex;
align-items: center;
justify-content: start;
flex-wrap: wrap;
}
.play {
}
\ No newline at end of file
import { SvgaPlayer, SvgaPlayerRef } from "@grace/svgaplayer"; import React, { Component } from 'react'
import { observer } from "mobx-react";
import { createRoot } from "react-dom/client";
import store from "./store/store";
import styles from "./App.module.less"; import "./utils/checkwebp";
import svga from "./assets/svga/1红色标题.svga"; import MD from "./MD"; // 埋点
import svga2 from "./assets/svga/2输出加载页氛围.svga"; import { PAGE_MAP } from "./utils/constants";
import icon from "./assets/react.svg"; import LoadingDemo from "./pages/LoadingDemo/LoadingDemo";
import HomeDemo from "./pages/HomeDemo/HomeDemo";
import Modal from "./modal/modal";
import { useEffect, useRef } from "react"; import './App.less'
import '@csstools/normalize.css';
import API from "./api";
function App() { MD();
const svgaRef = useRef<SvgaPlayerRef>(null); const pageMap = {
[PAGE_MAP.LOADING_PAGE]: <LoadingDemo />,
[PAGE_MAP.HOME_PAGE]: <HomeDemo />,
};
useEffect(() => { @observer
console.log(svgaRef.current.getPlayer()); class App extends Component {
});
const clickPlay = () => { async componentDidMount() {
svgaRef.current?.start(); await store.getFrontVariable();
} store.initRule();
const clickStop = () => { API.doJoin();
svgaRef.current?.stop();
}
const clickReplaceImage = () => {
svgaRef.current?.addImage("img_3929", "5555", icon, 200, 200);
} }
const clickRemoveImage = () => { render() {
svgaRef.current?.removeImage("5555"); const { curPage, pageData } = store;
return (
<>
{{ ...pageMap[curPage], props: { ...pageData } }}
<Modal />
</>
);
} }
return <>
<SvgaPlayer
ref={svgaRef}
className={styles.svgaTest}
src={svga}
/>
<SvgaPlayer
className={styles.svgaTest2}
src={svga2}
/>
<SvgaPlayer
className={styles.svgaTest3}
src={svga2}
/>
<div className={styles.container}>
<div className={styles.button} onClick={clickPlay}>play</div>
<div className={styles.button} onClick={clickStop}>stop</div>
<div className={styles.button} onClick={clickReplaceImage}>replaceImage</div>
<div className={styles.button} onClick={clickRemoveImage}>removeImage</div>
</div>
</>
} }
export default App createRoot(document.getElementById('root')!).render(
<App />
);
import { logClick, logExposure, MDAuto } from "@grace/built-in";
const appId = CFG.appID;
const dcm = "202." + CFG.projectId + ".0.0";
const domain = "https://embedlog.duiba.com.cn";
const MDList = new Array(2).fill("").map((v, i) => {
return {
ele: `.md${i + 1}`,
data: {
dpm: `${appId}.110.${i + 1}.0`,
dcm,
domain,
appId,
},
once: false,
};
});
export default () =>
MDAuto({
show: MDList, // 曝光
click: MDList, // 点击
});
export function handleLogExposure(id, id2 = 0) {
logExposure({
dpm: `${appId}.110.${id}.${id2}`,
dcm,
domain,
appId,
});
}
export function handleLogClick(id, id2 = 0) {
logClick({
dpm: `${appId}.110.${id}.${id2}`,
dcm,
domain,
appId,
});
}
import { generateAPI } from "./utils"
const API = generateAPI({
/** 获取活动规则 */
getRule: 'projectRule.query',
/** 获取前端配置项 */
getFrontVariable: 'coop_frontVariable.query',
/** 参与接口 post请求 */
doJoin: {
uri: 'join.do',
method: "post"
},
/** 签到 */
doSign: {
uri: 'checkin_1/doSign.do',
withToken: true, // 携带token
},
// cookie丢失-临时保存cookie
tempSaveCookie: {
uri: "/autoLogin/tempSaveCookie",
showMsg: false,
},
// cookie丢失-重新设置cookie
resetCookie: "/autoLogin/resetCookie",
userLogin: {
uri: "userLogin.check",
showMsg: false,
},
buriedPoint: {
uri: "home/buriedPoint.do",
showMsg: false,
},
})
// console.log('======', API)
export default API
// import { Toast } from '@spark/ui'
import { isFromShare, newUser } from '../built-in/duiba-utils';
import { errorHandler } from "@/utils/errorHandler.js";
import { getPxToken } from "@/built-in/getPxToken.js";
import { Axios } from 'axios';
interface IRes {
success: boolean;
data: any;
msg: string;
code: number | string;
}
const mergeData = {
user_type: newUser ? '0' : '1',
is_from_share: isFromShare ? '0' : '1',
}
// let tempCookieId = "";
//
// export function setCookieId(cookieId) {
// tempCookieId = cookieId;
// }
export function resetBackCookie(duibaTempCookieId) {
return new Promise((resolve) => {
apiAxios.request({
url: "/autoLogin/resetCookie",
data: {
duibaTempCookieId
}
}).then((resp) => {
return resolve('success');
}, (e) => {
return resolve(e);
});
});
}
/**
* 请求方法get、post处理
* @param {*} value
* @returns
*/
function getRequestParams(value) {
if (typeof value === 'string') {
return {
uri: value,
method: 'get'
}
} else if (typeof value === 'object') {
const {
uri,
method = 'get',
showMsg = true,
headers,
withToken,
} = value;
return {
uri,
method,
headers,
withToken,
showMsg
}
} else {
console.error('getRequestParams: 传参有误');
}
}
const apiAxios = new Axios({
timeout: 10000,
});
apiAxios.interceptors.response.use(
async (resp) => {
try {
const res = JSON.parse(resp.data);
if (res.success) {
return res;
} else {
return { success: false };
}
} catch (e) {
return { success: false };
}
},
async (error) => {
console.error(error);
return { success: false };
}
);
/**
* 请求API通用处理
* @returns
* @param apiList
*/
export function generateAPI(apiList): { [key in string]: (params?, headers?) => Promise<IRes> } {
const api = {};
for (const key in apiList) {
let value = apiList[key];
if (typeof value === 'string') {
value = {
uri: value,
method: 'get'
}
}
const {
method = 'get',
uri,
headers: mHeaders,
withToken,
showMsg = true
} = value;
api[key] = async (params: any = {}, headers: any = {}) => {
// cookie丢失的问题
// 如遇跳转Cookie丢失,打开如下代码
// const duibaTempCookieId = localStorage.getItem("db_temp_cookie");
// // const duibaTempCookieId = tempCookieId;
//
// if (duibaTempCookieId) {
// localStorage.removeItem("db_temp_cookie");
// // tempCookieId = "";
//
// const res = await API.userLogin()
// .catch(async () => {
// await resetBackCookie(duibaTempCookieId);
// });
//
// if (!res || !res.success) {
// await resetBackCookie(duibaTempCookieId);
// }
// }
if (withToken) { // 是否携带token
params.token = await getPxToken()
.catch(() => {
// Toast('网络异常,请稍后再试~');
return ({ success: false, data: '' });
});
}
const mergedHeaders = { ...mHeaders, ...headers }
params = { ...params, ...mergeData };
const res: IRes = await apiAxios.request({
method,
url: uri,
headers: mergedHeaders,
data: params,
});
if (res) {
if (!res.success && showMsg) {
errorHandler(res);
}
return res;
} else {
return { success: false, data: '' };
}
}
}
return api;
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
\ No newline at end of file
export class CodeError {
name = 'CodeError';
code = '';
message = '';
stack: string;
payload: any;
constructor(input, message?) {
if (typeof input === 'number' || typeof input === 'string') {
this.code = input + '';
} else if (typeof input === 'object') {
if (input['code']) {
this.code = input['code'];
} else {
if (input instanceof Error) {
this.code = input['message'];
} else {
console.warn('input without code field:', JSON.stringify(input));
}
}
if (input['message']) {
this.message = input['message'];
}
if (input['payload']) {
this.payload = input['payload'];
}
}
if (message) {
this.message = message;
}
this.stack = (new Error()).stack;
if (!Error['captureStackTrace'])
this.stack = (new Error()).stack;
else
Error['captureStackTrace'](this, this.constructor);
}
}
export const Errors = {
UNKNOWN: 200001, // 未知异常
NET_ERROR: 210001, // 网络异常
// REQUEST_TIMEOUT, // 请求超时
// POLLING_TIMEOUT, // 轮询超时
// INVALID_RESPONSE, // 无效的响应体
// CALL_LIMITING, // 接口限流
GET_PX_TOKEN_FAILED: 220001, // 获取星速台token失败
};
/**
* Created by rockyl on 2019-12-10.
*/
export const queryParams: any = {};
let search = window.location.search;
try {
search = top.location.search; //尝试获取顶层的链接
} catch (e) { /* empty */ }
for (const item of search.replace('?', '').split('&')) {
const arr = item.split('=');
queryParams[arr[0]] = arr.length === 1 ? true : decodeURIComponent(arr[1]);
}
export function windowVisibility(callback?: (visible: boolean) => void) {
//设置隐藏属性和改变可见属性的事件的名称
let visibilityChange;
if (typeof document.hidden !== 'undefined') {
visibilityChange = 'visibilitychange';
} else if (typeof document['msHidden'] !== 'undefined') {
visibilityChange = 'msvisibilitychange';
} else if (typeof document['webkitHidden'] !== 'undefined') {
visibilityChange = 'webkitvisibilitychange';
}
const handleVisibilityChange = (e) => {
callback && callback(document.visibilityState == "visible");
};
document.addEventListener(
visibilityChange,
handleVisibilityChange,
false
);
}
/**
* Created by rockyl on 2020/11/30.
*/
declare const CFG: any;
/**
* Created by rockyl on 2020/9/21.
*/
export * from './net'
export * from './browser'
export * from './utils'
export * from './init'
/**
* Created by rockyl on 2020/9/21.
*/
import {queryParams, windowVisibility,} from "./browser";
import {accessLog} from "./utils";
export let appID: string, channelType: string, projectID: string, isFromShare: boolean, newUser: boolean = true;
//appID提取
if (queryParams.appID) {
appID = queryParams.appID;
}else if (CFG.appID) {
appID = CFG.appID;
}
//渠道类型提取
if (queryParams.channelType) {
channelType = queryParams.channelType;
}
//projectID提取
if (queryParams.projectID) {
projectID = queryParams.projectID;
} else {
const result = window.location.pathname.match(/\/projectx\/(.*?)\/.*?/);
if (result) {
projectID = result[1];
}
}
//是否是分享回流
isFromShare = Object.prototype.hasOwnProperty.call(window, 'isFromShare') ? window['isFromShare'] : !!queryParams.is_from_share;
/**
* 手动设置来自分享
*/
export function setFromShare() {
isFromShare = true;
}
window['setFromShare'] = setFromShare
//新用户标记提取
function getNewUser() {
if (!localStorage) {
return
}
const key = 'nu_' + appID + '_' + projectID;
const v = localStorage.getItem(key);
if (v) {
newUser = false;
} else {
localStorage.setItem(key, '1');
}
}
getNewUser();
if (window['isSharePage']) {
setTimeout(function () {
accessLog(506);
}, 100)
}
function dealPageRemainTime() {
let startTimer = new Date().getTime();
let endTimer;
windowVisibility((visibility) => {
if (visibility) {
startTimer = new Date().getTime();
//console.log('starttimer', startTimer)
} else {
endTimer = new Date().getTime();
//console.log('endTimer', endTimer);
sendData();
}
})
const sendData = () => {
const t0 = endTimer - startTimer;
//console.log('duration', t0);
accessLog(156, {
remain: t0,
});
};
document.body['onbeforeunload'] = () => {
endTimer = new Date().getTime();
return sendData();
}
}
if (!window['stop_report_page_remain_time']) {
dealPageRemainTime();
}
/**
* Created by rockyl on 2019-11-22.
*/
import { injectProp, obj2query } from "./utils";
/**
* http请求
* @param url
* @param method
* @param params
* @param type
* @param headers
*/
export function httpRequest(url: string, method: string = 'get', params?: any, type: 'text' | 'json' | 'jsonp' = 'text', headers?) {
let openUrl = url.indexOf('blob') === 0 ? url : urlJoin(url, '__ts__=' + Date.now());
let mParams = {};
injectProp(mParams, params);
return new Promise((resolve, reject) => {
let mHeaders = {
'Content-Type': 'application/x-www-form-urlencoded',
};
injectProp(mHeaders, headers);
let request = { url, method, params: mParams, type, headers: mHeaders };
const mock = window['mock'];
let mockRule;
if (mock) {
mock(request, (rule) => {
mockRule = rule;
if (!mockRule) {
doRequest(request, resolve, reject);
}
}, resolve, reject);
} else {
request.url = openUrl;
doRequest(request, resolve, reject);
}
});
}
function doProxyRequest(payload, resolve, reject) {
let proxyWindow = window['proxy_window'];
window.addEventListener('message', onMessage, false);
proxyWindow.postMessage(JSON.stringify({
action: 'http-request-proxy',
payload: payload,
}), '*');
function onMessage(event) {
window.removeEventListener('message', onMessage);
try {
let data = JSON.parse(event.data);
console.log('onMessage', event.data);
switch (data.action) {
case 'http-request-proxy-resolve':
resolve(data.payload);
break;
case 'http-request-proxy-reject':
reject(data.payload);
break;
}
} catch (e) {
}
}
}
function doXhrRequest({ url, method, params, type, headers }, resolve, reject) {
let xhr;
if (window["XMLHttpRequest"]) {
xhr = new XMLHttpRequest();
} else if (window["ActiveXObject"]) {
xhr = new window["ActiveXObject"]();
} else {
console.log('no xhr');
}
if (xhr != null) {
const isGet = method.toUpperCase() === 'GET';
const queryStr = obj2query(params);
let openUrl = url;
if (openUrl.indexOf('projectx') == 0) {
openUrl = '/' + openUrl;
}
if (isGet) {
openUrl = urlJoin(openUrl, queryStr);
}
xhr.open(method, openUrl, true);
for (let key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
xhr.responseType = type;
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status == 200) {
resolve(xhr.response)
}
};
xhr.onerror = (reason): void => {
reject(reason)
};
xhr.onloadend = (): void => {
if (xhr.status == 404) {
reject(url + ' 404 (Not Found)')
}
};
if (isGet) {
xhr.send();
} else {
xhr.send(queryStr);
}
}
}
function doRequest(payload, resolve, reject) {
if (window['proxy_window'] && payload.url.indexOf('blob') !== 0) {
doProxyRequest(payload, function (p) {
resolve(p);
}, reject);
} else {
doXhrRequest(payload, resolve, reject);
}
}
export function urlJoin(url, query) {
if (query) {
url += url.indexOf('?') < 0 ? '?' : '';
url += url[url.length - 1] === '?' ? '' : '&';
return url + query;
} else {
return url;
}
}
/**
* Created by rockyl on 2020/9/21.
*/
import {httpRequest} from "./net";
import {appID, projectID} from "./init";
export function cleanNewUser() {
if(!localStorage) return;
let key = 'nu_' + appID + '_' + projectID;
localStorage.removeItem(key);
}
export function accessLog(pageBizId, params?) {
let p = {
pageBizId,
};
injectProp(p, params);
return httpRequest('buriedPoint', 'get', p);
}
/**
* 属性注入方法
* @param target 目标对象
* @param data 被注入对象
* @param callback 自定义注入方法
* @param ignoreMethod 是否忽略方法
* @param ignoreNull 是否忽略Null字段
*
* @return 是否有字段注入
*/
export function injectProp(target: any, data?: any, callback?: Function, ignoreMethod: boolean = true, ignoreNull: boolean = true): boolean {
if(!target || !data) {
return false;
}
let result = false;
for(let key in data) {
let value: any = data[key];
if((!ignoreMethod || typeof value != 'function') && (!ignoreNull || value != null)) {
if(callback) {
callback(target, key, value);
} else {
try {
target[key] = value;
} catch(e) {
}
}
result = true;
}
}
return result;
}
/**
* 数组查找
* @param arr
* @param predicate
*/
export function arrayFind(arr, predicate) {
if(!arr) {
return;
}
for(let i = 0, li = arr.length; i < li; i++) {
const item = arr[i];
if(predicate(item, i, arr)) {
return item;
}
}
}
/**
* 对象转query字符串
* @param obj
*/
export function obj2query(obj: any): string {
if(!obj) {
return '';
}
const arr: string[] = [];
for(const key in obj) {
arr.push(key + (key ? '=' : '') + obj[key]);
}
return arr.join('&');
}
import { CodeError } from "./common-helpers/CodeError";
import { Errors as ERRORS } from "./common-helpers/errors";
import { jsonp } from "@grace/built-in";
import { evalJsScript } from "@grace/built-in";
import axios from "axios";
const isProd = location.href.indexOf('.com.cn/projectx') >= 0;
let pxTokenKeyValid = false;
let getting = false;
const _queue = [];
/**
* 获取token
* @ctype PROCESS
* @description 获取星速台防刷token
* @returns
* token string token
* @example 一般用法
* const token = await getPxToken().catch(e=>{
* console.log('获取失败,失败原因:', e.message)
* })
* if(token){
* console.log('获取成功,token:', token)
* }
*/
export async function getPxToken() {
if (!isProd) {
console.log('[Mock] getPxToken');
return 'test_token';
}
return new Promise((resolve, reject) => {
_queue.push({resolve, reject});
setTimeout(_getPxToken, 10);
});
}
async function _getPxToken() {
if (getting) {
return;
}
if (_queue.length > 0) {
const p = _queue.shift();
try {
getting = true;
const token = await _tryGetPxToken();
p.resolve(token);
getting = false;
setTimeout(_getPxToken, 10);
} catch (e) {
getting = false;
p.reject(e);
}
}
}
async function _tryGetPxToken() {
let token, err;
for (let i = 0; i < 2; i++) {
try {
token = await _doGetPxToken();
break;
} catch (e) {
err = e;
invalidPxTokenKey();
}
}
if (token) {
return token;
} else {
throw err;
}
}
async function _doGetPxToken() {
if (!pxTokenKeyValid) {
await refreshPxTokenKey();
}
return getToken();
}
/**
* 让tokenKey失效
* @ctype PROCESS
* @description 比如活动页跳转到其他星速台页面,回来的时候就需要监听history然后让tokenKey失效
* @example 一般用法
* invalidPxTokenKey()
*/
export function invalidPxTokenKey() {
pxTokenKeyValid = false;
}
async function refreshPxTokenKey() {
if (!isProd) {
pxTokenKeyValid = true;
console.log('[Mock] refreshPxTokenKey');
return;
}
pxTokenKeyValid = false;
await jsonp('getTokenKey?_t=' + Date.now());
pxTokenKeyValid = true;
}
async function getToken() {
const resp = await axios.get('getToken');
if (resp.data.success) {
evalJsScript(resp.data);
const token = window['ohjaiohdf']();
if (token) {
return token;
}
throw new CodeError(ERRORS.GET_PX_TOKEN_FAILED, '获取token失败,请查明key是否被覆盖');
} else {
throw new CodeError(resp);
}
}
/**
* index.jsx
* Created by 还有醋v on 2022/10/10 下午3:03.
* Copyright © 2022 haiyoucuv. All rights reserved.
*/
import React, { CSSProperties, MouseEventHandler, ReactElement, useState } from "react";
interface IButtonProps {
className?: string;
onClick?: MouseEventHandler;
children?: ReactElement,
style?: CSSProperties,
}
/**
* @return ReactElement
* @constructor
* @param props
*/
export const Button = (props: IButtonProps): ReactElement => {
const {
children,
className,
onClick = () => void 0,
style = {}
} = props;
const [scale, setScale] = useState("unset");
const onTouchStart = () => {
setScale("scale(0.9,0.9)");
};
const onTouchEnd = () => {
setScale("unset");
};
const onTouchCancel = onTouchEnd;
return React.createElement("div", {
className, onTouchStart, onTouchEnd, onTouchCancel, onClick, style: {
transitionDuration: 0.5, transform: scale, ...style
}
}, children);
};
import { createRoot } from 'react-dom/client'
import './index.less'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<App />,
)
.modal-hoc-bg {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
}
\ No newline at end of file
import React, { Component } from "react";
import './modal.less';
import { observer } from 'mobx-react';
import modalStore from '../store/modal';
import { toJS } from 'mobx';
/**
* 弹窗配置
*/
export const cfg = {};
@observer
class Modal extends Component {
constructor(props) {
super(props);
}
componentDidMount() { }
render() {
const list = toJS(modalStore.popList);
if (!list.length) {
// TODO:此处根据需要自行修改
// document.body.style.overflow='auto';
return <section></section>;
}
let PopUpMulti, popUpMultiData;
if (list.length > 1 && list[list.length - 1].isMulti == true) {
const popObj2 = list[list.length - 1];
PopUpMulti = cfg[popObj2.key];
popUpMultiData = popObj2.data;
}
const popObj = list[0];
const PopUp = cfg[popObj.key];
const popData = popObj.data;
if (PopUp || PopUpMulti) {
document.body.style.overflow='hidden';
}
return <section className="modal-hoc-bg" style={{
zIndex: modalStore.popList.length ? 1000 : -1,
display: modalStore.popList.length ? 'block' : 'none'
}}>
{PopUp && <PopUp popData={popData} />}
{PopUpMulti && <section className="modal-hoc-bg" style={{
zIndex: modalStore.popList.length ? 1000 : -1,
display: modalStore.popList.length ? 'block' : 'none'
}}><PopUpMulti popData={popUpMultiData} />
</section>}
</section>;
}
}
export default Modal;
\ No newline at end of file
@import "../../res.less";
.homeDemo {
position: absolute;
.homeImg {
position: absolute;
left: 100px;
top: 400px;
width: 200px;
height: 200px;
.webpBg("LoadingPage/loadingIp.png");
}
.svga {
position: absolute;
left: 0;
top: 0;
width: 400px;
height: 400px;
}
}
import React from 'react';
import { observer } from 'mobx-react';
import './HomeDemo.less';
import { SvgaPlayer } from "@grace/svgaplayer";
import svga from "../../assets/LoadingPage/1红色标题.svga";
import png from "../../assets/LoadingPage/loadingIp.png";
@observer
class HomeDemo extends React.Component {
componentDidMount() {
}
render() {
return <div className="homeDemo md1">
<div className="homeImg md1" />
<img src={png} />
当前为活动首页
<SvgaPlayer className="svga" src={svga} />
</div>;
}
}
export default HomeDemo;
@import "../../res.less";
.loading {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
.webpBg("LoadingPage/loadingBg.jpg");
.loading-ip {
position: absolute;
left: 198px;
top: 572-153px;
width: 570px;
height: 508px;
.webpBg("LoadingPage/loadingIp.png");
}
.progressBarBg {
position: absolute;
left: 82px;
top: 898-153px;
width: 590px;
height: 30px;
border-radius: 118px;
background: #FCE4D4;
border: 2px solid #FFFFFF;
}
.progressBar {
position: absolute;
left: 84px;
top: 900-153px;
width: 590px;
height: 30px;
overflow: hidden;
border-radius: 118px;
.progressBarFill {
position: absolute;
left: 0;
top: 0;
width: 590px;
height: 30px;
.webpBg("LoadingPage/loadingFill.png")
}
}
.progressTxt {
position: absolute;
left: 246px;
top: 952-153px;
width: 256px;
height: 22px;
opacity: 1;
font-size: 24px;
font-weight: normal;
line-height: 22px;
color: #999999;
}
}
import React from "react";
import { observer } from "mobx-react";
import { PAGE_MAP } from "../../utils/constants";
import store from "../../store/store";
import { preload } from "../../utils/preload.ts";
import "./LoadingDemo.less";
@observer
class LoadingDemo extends React.Component {
state = {
curPercentage: 0
}
curPercentage = 0;
intervalId: number = 0;
componentDidMount() {
this.preloadAssetInit();
// this.jump();
}
/**
* 资源预加载
*/
preloadAssetInit = async () => {
const files = import.meta.glob("../../assets/**/*", {
import: "default",
eager: true,
});
const urls = Object.values(files) as string[];
await preload(urls, (progress, loaded, total) => {
const percentage = Math.floor(progress * 100);
this.setEvenProgress(percentage);
});
};
jump = () => {
setTimeout(() => {
store.changePage(PAGE_MAP.HOME_PAGE); // 跳转页面
}, 100);
};
/**
* 以1%匀速加载进度
* @param {*} percentage
*/
setEvenProgress = (percentage) => {
this.intervalId && clearInterval(this.intervalId);
let curPercentage = this.curPercentage;
this.intervalId = window.setInterval(() => {
if (curPercentage >= percentage) {
clearInterval(this.intervalId);
this.jump();
return;
}
curPercentage += 1;
this.curPercentage = curPercentage;
this.setState({
curPercentage,
});
}, 10);
};
render() {
const { curPercentage } = this.state;
return <div className="loading">
<div className="loading-ip" />
<div className="progressBarBg" />
<div className="progressBar">
<div className="progressBarFill" style={{
transform: `translateX(${curPercentage - 100}%)`
}} />
</div>
<span className="progressTxt">金豆正在路上...... {curPercentage}%</span>
</div>;
}
}
export default LoadingDemo;
@RES_PATH: '/src/assets/';
@webp: '?x-oss-process=image/format,webp';
.sparkBg(@value) {
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: left top;
--p: url("@{RES_PATH}@{value}");
background-image: var(--p);
[duiba-webp='true'] & {
--p: url("@{RES_PATH}@{value}@{webp}");
background-image: var(--p);
}
}
.webpBg(@value) {
.sparkBg("@{value}");
}
.mask(@value) {
mask-image: url("@{RES_PATH}@{value}");
-webkit-mask-image: url("@{RES_PATH}@{value}");
mask-repeat: no-repeat;
}
// 定位-水平垂直居中
.transform_center() {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: absolute;
}
// flex-水平垂直居中
.flex_center() {
display: flex;
justify-content: center;
align-items: center;
}
/* 文本过长隐藏文字并显示省略号 单行*/
.lineClamp1() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.lineClampN(@num) {
text-overflow: -o-ellipsis-lastline;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: @num;
overflow: hidden;
}
// 按钮呼吸动效
.breathAnimation() {
animation: scale 1s linear infinite alternate;
@keyframes scale {
0% {
transform: scale(1);
}
100% {
transform: scale(1.12);
}
}
}
// 弹窗居中缩放动效
// 注意 加该样式的元素居中方式不能用translate(-50%, -50%)
// 可以用margin-left、margin-top为负的宽度、高度的一半
.popupCenterShow() {
animation: centerShowAni 300ms ease-out;
@keyframes centerShowAni {
0% {
transform: scale(0);
}
66.7% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
}
// 渐入动画
.fade-in {
opacity: 0;
animation: fadeIn ease-in 1;
animation-fill-mode: forwards;
animation-duration: 1s;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
70% {
opacity: 0;
}
100% {
opacity: 1;
}
}
// 隐藏滚动条
.hideScrollbar() {
scrollbar-width: none;
/* firefox */
-ms-overflow-style: none;
/* IE 10+ */
&::-webkit-scrollbar {
display: none;
/* Chrome Safari */
}
}
.ruleStyle {
overflow-x: hidden;
overflow-y: scroll;
word-break: break-all;
}
// 上滑弹窗动画
.topPop_move {
animation: move_top_ani 500ms ease-in-out;
@keyframes move_top_ani {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0);
}
}
}
import { makeAutoObservable } from 'mobx';
// 此处配置页面的优先级,越大优先级越高
// PopIndex:11
/**
* 弹窗优先级 可以是负数, 不写默认是10, 数值越小,层级越高
*/
const modalIndex = {}
const modalStore = makeAutoObservable({
popList: [],
/**
*
* @param {*} key 弹窗名,一般是类名的字符串
* @param {*} data 需要传递的数据,弹窗中使用 const {popData} = props; 获取
* @param {*} isMulti 是否是二级弹窗,在不关闭已有弹窗的基础上,弹出当前弹窗。注意,如果是二级弹窗,关闭时必须传key
*/
pushPop(key, data = {}, isMulti = false) {
if (this.popList.length) {
let cacheList = this.popList.slice();
cacheList.push({ key, data, isMulti });
cacheList = cacheList.sort((a, b) => ((modalIndex[b.key] ? modalIndex[b.key] : 10) - (modalIndex[a.key] ? modalIndex[a.key] : 10)))
this.popList.clear();
this.popList.push(...cacheList);
} else {
this.popList.push({ key, data, isMulti });
}
// console.log("this.popList:::",toJS(this.popList));
},
/**
*
* @param {*} key 弹窗名,一般是类名的字符串,关闭指定弹窗,若未指定,则关闭当前弹窗
*/
closePop(key) {
if (key) {
let cacheList = this.popList.slice();
cacheList = cacheList.filter(obj => (obj.key != key));
this.popList.clear();
this.popList.push(...cacheList);
} else {
this.popList.shift();
}
},
/**
* 关闭所有弹窗
*/
closePopAll() {
this.popList.clear();
}
});
export default modalStore;
import { makeAutoObservable, } from 'mobx';
import API from '../api/index';
import { PAGE_MAP } from "../utils/constants";
import { GetCurrSkinId, getCustomShareId } from "../utils/utils";
const skinId = GetCurrSkinId() || getCustomShareId();
class Store {
/** 前端开发配置 */
frontVariable = {};
/** 当前页面 */
curPage = {
// TODO 举例子,自定义页面,因为mng更新原因原数字id会对应一个新的字符串id
"5055": "sharePage",
Did1NDA0NDc: "sharePage",
myPrize: "myPrize", // TODO 举例子 新宿台奖品页
index: PAGE_MAP.LOADING_PAGE,
}[skinId] || PAGE_MAP.LOADING_PAGE;
pageData = {};
/** 场景切换 */
changePage(page, data = {}) {
this.pageData = data;
this.curPage = page;
}
ruleInfo = '';
/** 获取活动规则 */
async initRule() {
// 模拟获取远程的数据
const {data} = await API.getRule();
this.ruleInfo = data;
}
/** 获取前端配置项 */
async getFrontVariable() {
// 获取前端开发配置
const {data} = await API.getFrontVariable();
this.frontVariable = data || {};
console.log('前端开发配置', data)
}
}
const store: Store = makeAutoObservable(new Store());
export default store;
import { mode, pad, enc, AES } from 'crypto-js';
const getOptions = (iv: string) => {
return {
iv: enc.Utf8.parse(iv),
mode: mode.CBC,
padding: pad.ZeroPadding,
};
}
/** 加密 */
export function AESEncrypt(str: string, key: string, iv: string) {
const options = getOptions(iv);
return AES.encrypt(str, enc.Utf8.parse(key), options).toString();
}
/** 解密 */
export function AESDecrypt(cipherText: string, key: string, iv: string) {
const options = getOptions(iv);
return AES.decrypt(cipherText, enc.Utf8.parse(key), options)
.toString(enc.Utf8)
.trim()
.replace(//g, '')
.replace(//g, '')
.replace(/\v/g, '')
.replace(/\x00/g, '');
}
/**
* 检查是否支持.webp 格式图片
* 支持 webp CDN ?x-oss-process=image/format,webp
* 不支持 CDN ?x-oss-process=image/quality,Q_80
*/
(function () {
let lowAdr = false;
const ua = navigator.userAgent.toLowerCase()
const isAndroid = ua.indexOf('android') > -1 || ua.indexOf('adr') > -1;
if (isAndroid) {
const ver = parseFloat(ua.substr(ua.indexOf('android') + 8, 3));
lowAdr = ver < 4.4;
}
if (lowAdr && localStorage) {
delete localStorage.isWebp;
}
if (localStorage && !localStorage.isWebp) {
const img = new Image()
img.onload = function () {
if (img.width === 1 && !lowAdr) {
localStorage.isWebp = true;
document.getElementsByTagName('html')[0].setAttribute('duiba-webp', 'true')
} else {
localStorage.isWebp = '';
}
}
img.onerror = function () {
localStorage.isWebp = ''
}
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='
}
})();
export const webpQuery = "?x-oss-process=image/format,webp";
export function supportWebp() {
return !!localStorage?.isWebp;
}
\ No newline at end of file
/**
* 弹窗优先级 可以是负数, 不写默认是10, 数值越小,层级越高
*/
export const MODAL_INDEX = {
// rank: 1,
};
/** 网络异常默认展示 */
export const DEFAULT_NET_ERROR = '网络异常,请稍后再试';
export const PAGE_MAP = {
HOME_PAGE: "homePage",
LOADING_PAGE: "loadingPage",
};
// import { Toast } from "@spark/ui";
import { DEFAULT_NET_ERROR } from "./constants";
// 需要过滤的错误码
export const filterCode = ["600002"];
export const errMessageMap = {
1020: "活动未开始",
1021: "活动已结束",
1007: "活动太火爆了,奖品已抢完咯~",
100001: "登录过期啦,请重新登录哦~",
};
/**
* 统一错误处理
* @param e
*/
export function errorHandler(e) {
if ((e.code == 0 && e.message == "请稍后再试") || filterCode.indexOf(`${e.code}`) >= 0) return;
switch (e.code) {
default: {
const msg = errMessageMap[e.code] || e.message || DEFAULT_NET_ERROR;
// Toast(msg, 2000, { hideOthers: true });
break;
}
}
}
import { supportWebp, webpQuery } from "./checkwebp.ts";
export const svgaRegex = (/\.(svga)$/);
export const jpgRegex = (/\.(jpe?g)$/);
export const pngRegex = (/\.(png)$/);
export const webpRegex = (/\.(webp)$/);
export const gifRegex = (/\.(gif)$/);
export const imgRegex = (/\.(png|jpe?g|webp|gif)$/);
const typeRegex = (/\.([^.]+)$/);
const loaders = {
".jpg": loadImg,
".jpeg": loadImg,
".png": loadImg,
".webp": loadImg,
".gif": loadImg,
'.svga': loadSvga,
}
export async function preload(
urls: string[],
onProcess?: (progress: number, loaded: number, total: number) => void,
onComplete?: () => void
) {
const ps = [];
let loaded = 0;
const total = urls.length;
// 获取文件类型
urls.forEach((url) => {
const type = url.match(typeRegex)?.[0];
const loadFunc = loaders[type] || loadUnknown;
const p = loadFunc(url).then(() => {
loaded++;
const progress = loaded / total;
onProcess && onProcess(progress, loaded, total);
if (loaded >= total) {
onComplete && onComplete();
}
});
ps.push(p);
});
}
/**
* 加载一张图片
* @param {string} url 地址
*/
export function loadImg(url: string) {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = err => {
console.warn('load', url, err);
resolve(null);
};
img.crossOrigin = 'anonymous';
img.src = url + (supportWebp() ? webpQuery : '');
});
}
export function loadSvga(url: string) {
return Promise.resolve();
}
export function loadUnknown(url: string) {
console.warn(`load unknown: ${url}`);
return Promise.resolve();
}
\ No newline at end of file
import { loadSvga } from '@spark/svgaplayer'
// import * as FYGE from 'fyge';
// import { Howl } from 'howler';
import { RES_PATH } from '../../sparkrc'
/**
* 预加载资源(/png|jpg|jpeg|svga|spi|json|mp3|wav/)
* @param {string[]} urlList 资源地址列表
* @param {number} batchNum 每批并行加载的资源个数(一般来说该数字越大整体加载速度越快,但加载前期会更卡顿)
* @param {Function} [onProgress] 加载进度回调,每加载完一个资源回调一次,入参为进度值(0,1]
* @returns {Promise} 返回一个只会resolve(loadedData)的promise,loadedData保存了所有预加载好的资源,可通过相对路径索引
* @example
* //例
* const loadedData = await PreloadAsset(urlList, 10, onProgress);
* const image = loadedData['image/fish.png'];
* const svgaData = loadedData['svga/fish.svga'];
* const spiData = loadedData['spine/fish.spi'];
* const lottieData = loadedData['lottie/fish.json'];
*/
export function preloadAsset(urlList, batchNum, onProgress) {
return new Promise((resolve) => {
/** 要加载资源总数 */
const totalNum = urlList.length;
/** 要加载的资源索引 */
let assetIndex = -1;
/** 已加载完毕的资源个数 */
let loadedNum = 0;
/** 存放加载好的数据,用地址索引 */
const loadedData = {};
/** 加载逻辑 */
const doLoad = async () => {
if (loadedNum >= totalNum) {
totalNum == 0 && onProgress && onProgress(1); // 无加载资源时,即为假loading
resolve(loadedData); // 加载完毕
} else {
assetIndex++;
if (assetIndex >= totalNum) return
const key = urlList[assetIndex];
const url = RES_PATH + urlList[assetIndex];
const result = await loadOneAsset(url);
if (!result) {
console.warn('加载异常', url);
}
loadedData[key] = result;
loadedNum++;
onProgress && onProgress(loadedNum / totalNum);
doLoad();
}
}
batchNum = batchNum || 1;
for (let index = 0; index < batchNum; index++) {
doLoad();
}
})
}
/**
* 加载一个资源
* @param {string} url 地址
*/
async function loadOneAsset(url) {
const fileType = url.split('.').pop();
switch (true) {
case (/png|jpg|jpeg/).test(fileType):
return await loadOneImg(url);
case (/svga/).test(fileType):
return await loadOneSvga(url);
// case (/spi/).test(fileType):
// return await loadOneSpi(url);
// case (/json/).test(fileType):
// return await loadOneJson(url);
// case (/mp3|wav/).test(fileType):
// return await loadOneAudio(url);
default:
console.warn('非法资源', url);
return false;
}
}
/**
* 加载一张图片
* @param {string} url 地址
*/
function loadOneImg(url) {
const isWebp = localStorage.isWebp;
return new Promise(resolve => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = err => {
console.warn('load', url, err);
resolve(false)
};
img.crossOrigin = 'Anonymous'
img.src = url + (isWebp ? '?x-oss-process=image/format,webp' : '');
})
}
/**
* 加载一个svga
* @param {string} url 地址
*/
function loadOneSvga(url) {
return new Promise(resolve => {
loadSvga(url).then((data) => resolve(data[0])).catch(err => {
console.warn('load', url, err);
resolve(false)
});
})
}
// /**
// * 加载一个spine
// * @param {string} url 地址
// */
// function loadOneSpi(url) {
// return new Promise(resolve => {
// FYGE.loadSpine(url, spineData => {
// resolve(spineData);
// }, err => {
// console.warn('load', url, err);
// resolve(false);
// })
// })
// }
// /**
// * 加载一个Json
// * @param {string} url 地址
// */
// function loadOneJson(url) {
// return new Promise(resolve => {
// FYGE.GlobalLoader.loadJson((result, res) => {
// if (result) {
// resolve(res);
// } else {
// console.warn('load fail', url);
// resolve(false);
// }
// }, url)
// })
// }
// /**
// * 加载一个音频
// * @param {string} url 地址
// */
// function loadOneAudio(url) {
// return new Promise(resolve => {
// const sound = new Howl({
// src: url,
// onload: () => resolve(sound),
// onloaderror: err => {
// console.warn('load fail', url, err);
// resolve(false);
// },
// });
// })
// }
/**
* @description: 小程序跳转
* @param {*}
* @return {*}
*/
export const miniGoUrl = (url) => {
wx.miniProgram.navigateTo({ url: url });
}
/**
* 判断是否为ios系统
*/
export function isIos() {
return navigator.userAgent.match(/iphone|ipod|ipad/gi)
}
/** 判断微信环境 */
export function isWeChat() {
const ua = window.navigator.userAgent.toLowerCase()
return ua.match(/MicroMessenger/i) == 'micromessenger'
}
import { useRef, useEffect, useCallback } from "react";
export function getQueryString(params) {
const paramArr = [];
for (const key in params) {
paramArr.push(key + "=" + params[key]);
}
return paramArr.join("&");
}
export {
_throttle, // 节流
useThrottle, // 节流,函数组件
_debounce, // 防抖
getCookie, // 获取cookie的值
getUrlParam, // 获取url参数
delUrlParam, // 删除url中的参数
subStringCE, // 截取字符串 中2英1
check2Object, // 判断两个对象相等
getThousandToK, // 转换k
dateFormatter, // 日期格式化
dealTime, // 时间格式化
second2Date, // 秒转时间对象
waitTime, // 等待一段时间再执行
randomNum, // 获取区间随机数 [min,max)
shuffleArr, // 随机打乱数组
flatten, // 数据扁平化
onCtrScroll, // 控制滚动--兼容ios
trimSpace, // 去除字符串空格
GetCurrSkinId, // 获取当前链接皮肤id
windowJumpUrl // window的跳转链接
}
/**
* @description: 函数节流,普通防连点
* @param {(Function, number?)}
* @return {Function}
*/
const _throttle = (fun, delay = 2000) => {
let last: number;
return function () {
const now = +new Date();
if (last && now < last + delay) {
// clearTimeout(deferTimer);
// deferTimer = setTimeout(() => {
// last = now;
// }, delay);
} else {
last = now;
fun.apply(this, arguments);
}
};
};
/**
* @description: 支持异步函数的节流,防止接口时间太长击穿防连点
* @return {Function}
* @param fun
* @param delay
*/
export const _asyncThrottle = (fun, delay = 2000) => {
let last: number;
let canClick = true;
return function () {
const now = Date.now();
if (!canClick) return;
if (last && now < last + delay) {
// clearTimeout(deferTimer);
// deferTimer = setTimeout(() => {
// last = now;
// }, delay);
} else {
last = now;
const ps = fun.apply(this, arguments);
if (ps instanceof Promise) {
canClick = false;
ps.finally(() => {
canClick = true;
});
}
}
};
};
function useThrottle(fn, delay = 2000, dep = []) {
const {current} = useRef({fn, timer: null});
useEffect(function () {
current.fn = fn;
}, [fn]);
return useCallback(function f(...args) {
if (!current.timer) {
current.timer = setTimeout(() => {
delete current.timer;
}, delay);
current.fn.call(this, ...args);
}
}, dep);
}
/**
* @description: 函数防抖
* @param {(Function, number?, boolean? )}
* @return {Function}
*/
const _debounce = (fn, wait = 2000, immediate = false) => {
let timer = null
return function () {
const later = function () {
fn.apply(this, arguments)
}
if (immediate && !timer) {
later()
}
if (timer) clearTimeout(timer)
timer = setTimeout(later, wait)
}
}
/**
* 获取cookie的值
* @param {*} cookieName
*/
function getCookie(cookieName) {
const strCookie = document.cookie;
const arrCookie = strCookie.split('; ');
for (let i = 0; i < arrCookie.length; i++) {
const arr = arrCookie[i].split('=');
if (cookieName == arr[0]) {
return arr[1];
}
}
return '';
}
/**
* 获取url参数
* @param {string} name
*/
function getUrlParam(name) {
const search = window.location.search;
const matched = search
.slice(1)
.match(new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'));
return search.length ? matched && matched[2] : null;
}
/**
* 删除url中的参数
* @param {*} url
* @param {*} arg
*/
function delUrlParam(url, paramKey) {
const _url = new URL(url)
const search = new URLSearchParams(_url.search)
search.delete(paramKey)
_url.search = search.toString();
return _url.href;
}
/**
* 日期格式化
* @param date 接收可以被new Date()方法转换的内容
* @param format 字符串,需要的格式例如:'yyyy/MM/dd hh:mm:ss'
* @returns {String}
*/
const dateFormatter = (date, format = "yyyy/MM/dd") => {
if (!date) return "-";
date = new Date(
// @ts-ignore
typeof date === "string" && isNaN(date)
? date.replace(/-/g, "/")
: Number(date)
);
const o = {
"M+": date.getMonth() + 1,
"d+": date.getDate(),
"h+": date.getHours(),
"m+": date.getMinutes(),
"s+": date.getSeconds(),
"q+": Math.floor((date.getMonth() + 3) / 3),
S: date.getMilliseconds(),
};
if (/(y+)/.test(format)) {
format = format.replace(
RegExp.$1,
(date.getFullYear() + "").substr(4 - RegExp.$1.length)
);
}
for (const k in o) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length)
);
}
}
return format;
};
/** 时间格式化 */
const dealTime = (msTime) => {
const time = msTime / 1000;
let hour: number | string = Math.floor(time / 60 / 60) % 24;
let minute: number | string = Math.floor(time / 60) % 60;
let second: number | string = Math.floor(time) % 60;
hour = hour > 9 ? hour : "0" + hour;
minute = minute > 9 ? minute : "0" + minute;
second = second > 9 ? second : "0" + second;
return `${hour}:${minute}:${second}`;
}
/**
* 转换k
* @param {*} num
*/
function getThousandToK(num) {
let s_x;
if (num >= 1000) {
let result = num / 1000;
result = Math.floor(result * 10) / 10;
s_x = result.toString();
let pos_decimal = s_x.indexOf(".");
if (pos_decimal < 0) {
pos_decimal = s_x.length;
s_x += ".";
}
while (s_x.length <= pos_decimal + 1) {
s_x += "0";
}
s_x += "k";
} else {
s_x = num;
}
return s_x;
}
/**
* 截取字符串 中2英1
* @param {*} str
* @param {*} sub_length
*/
function subStringCE(str, sub_length) {
const temp1 = str.replace(/[^\x20-\xff]/g, "**");
const temp2 = temp1.substring(0, sub_length);
const x_length = temp2.split("*").length - 1;
const hanzi_num = x_length / 2;
sub_length = sub_length - hanzi_num;
const res = str.substring(0, sub_length);
let endStr;
if (sub_length < str.length) {
endStr = res + "...";
} else {
endStr = res;
}
return endStr;
}
/**
* 随机打乱数组
* @param {*} arr
* @returns
*/
function shuffleArr(arr) {
for (let i = arr.length - 1; i >= 0; i--) {
const randomIndex = Math.floor(Math.random() * (i + 1))
const itemAtIndex = arr[randomIndex]
arr[randomIndex] = arr[i]
arr[i] = itemAtIndex
}
return arr
}
/**
* 获取区间随机数 [min,max)
* @export
* @param {*} min
* @param {*} max
* @return {*}
*/
function randomNum(min, max) {
return Math.floor(Math.random() * (max - min)) + min
}
/**
* 数据扁平化
* @export
* @param {*} arr
* @return {*}
*/
function flatten(arr) {
return arr.reduce((result, item) => {
return result.concat(Array.isArray(item) ? flatten(item) : item)
}, [])
}
/** 判断两个对象相等 */
const check2Object = (obj1, obj2) => {
const o1 = obj1 instanceof Object
const o2 = obj2 instanceof Object
if (!o1 || !o2) { /* 判断不是对象 */
return obj1 === obj2
}
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false
}
for (const attr in obj1) {
const t1 = obj1[attr] instanceof Object
const t2 = obj2[attr] instanceof Object
if (t1 && t2) {
return check2Object(obj1[attr], obj2[attr])
} else if (obj1[attr] !== obj2[attr]) {
return false
}
}
return true
}
/**
* 秒转时间对象
* @param {Number} totalSecond 总秒数
* @return {{
* day: String,
* hour: String,
* minute: String,
* second: String
* }}
*/
const second2Date = (totalSecond) => {
const millisecond = totalSecond % 1000
totalSecond = totalSecond / 1000
// 获得总分钟数
const totalMinute = totalSecond / 60
// 获得剩余秒数
const second = totalSecond % 60
// 获得小时数
const totalHour = totalMinute / 60
// 获得分钟数
const minute = totalMinute % 60
// 获得天数
const day = totalHour / 24
// 获得剩余小时数
const hour = totalHour % 24
// 格式化的键值
const includesKey = ['month', 'day', 'hour', 'minute', 'second', 'totalHour', 'totalMinute']
// 日期对象
const dateObj = {day, hour, minute, second, millisecond, totalHour, totalMinute}
return Object.keys(dateObj).reduce((preVal, key) => {
// 值取整
const value = parseInt(dateObj[key])
if (includesKey.includes(key) && value < 10) {
if (value < 0) {
preVal[key] = '00'
} else {
preVal[key] = '0' + value
}
} else {
if (value.toString() === 'NaN') {
preVal[key] = '0'
} else {
preVal[key] = value.toString()
}
}
return preVal
}, {})
}
/**
* 等待一段时间再执行
* @param {number} time 等待的时间ms
*/
function waitTime(time) {
return new Promise(resolve => setTimeout(resolve, time))
}
/** 控制滚动--兼容ios */
const bodyScroll = (event) => {
event.preventDefault();
}
const onCtrScroll = (flag = true) => {
if (flag) { // 禁止滚动
document.body.addEventListener('touchmove', bodyScroll, {passive: false});
} else { // 开启滚动
document.body.removeEventListener('touchmove', bodyScroll);
}
}
/**
* 删除中间所有的空格
* @param {string} str
* @returns
*/
const trimSpace = (str) => {
let result;
result = str.replace(/(^\s+)|(\s+$)/g, "");
result = result.replace(/\s/g, "");
return result;
}
/**
* 获取当前皮肤id
* @returns
*/
const GetCurrSkinId = () => {
// eslint-disable-next-line no-useless-escape
const matched = location.pathname.match(/\/([^\/]*).html/);
const currSkinId = matched ? matched && matched[1] : ''
console.log('当前皮肤id', currSkinId)
return currSkinId
}
export const getCustomShareId = () => {
const matched = location.href.match(/\/share\?id=(.*)/);
const shareId = matched ? matched && matched[1] : '';
console.log('自定义活动页id', shareId);
return shareId;
};
/** 跳转 */
const windowJumpUrl = (url: string) => {
if (url) {
location.href = url;
}
};
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module "*.svga" { declare module "*.svga" {
const src: string; const src: string;
export default src; export default src;
} }
\ No newline at end of file
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"useDefineForClassFields": true,
"skipLibCheck": true, "skipLibCheck": true,
"experimentalDecorators": true,
/* Bundler mode */ "moduleResolution": "bundler",
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"isolatedModules": true, "isolatedModules": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react",
/* Linting */
"strict": false, "strict": false,
"noUnusedLocals": true, "noImplicitAny": false,
"noUnusedParameters": true, "noUnusedLocals": false,
"noFallthroughCasesInSwitch": true "noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": [
"./src/*"
]
}
}, },
"include": ["src"] "include": [
"src"
]
} }
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext", "module": "ESNext",
"lib": ["ES2023"],
"useDefineForClassFields": true,
"skipLibCheck": true, "skipLibCheck": true,
"experimentalDecorators": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "Bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"isolatedModules": true, "isolatedModules": true,
"moduleDetection": "force", "moduleDetection": "force",
"noEmit": true, "noEmit": true,
/* Linting */ /* Linting */
"strict": true, "strict": false,
"noUnusedLocals": true, "noImplicitAny": false,
"noUnusedParameters": true, "noUnusedLocals": false,
"noFallthroughCasesInSwitch": true, "noUnusedParameters": false,
"noFallthroughCasesInSwitch": true
}, },
"include": ["vite.config.ts"] "include": [
"vite.config.ts"
]
} }
import { defineConfig } from 'vite' import { defineConfig, UserConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import legacy from '@vitejs/plugin-legacy'
import autoprefixer from "autoprefixer"
import postcsspxtorem from "postcss-pxtorem"
import { viteMockServe } from "vite-plugin-mock";
import DuibaPublish from "./config/DuibaPublish/DuibaPublish.ts";
import dotenv from 'dotenv';
import * as path from "node:path";
// https://vite.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig(({ mode }): UserConfig => {
assetsInclude: [/\.(svga)$/],
build: { dotenv.config({ path: [`./config/.env.global`, `./config/.env.${mode}`] });
cssTarget: 'chrome61',
}, const {
plugins: [ NODE_ENV,
react({ UPLOAD_DIR, CDN_DOMAIN,
babel: { OSS_REGION, OSS_BUCKET,
plugins: [ OSS_ACCESS_KEY_ID, OSS_ACCESS_SECRET,
["@babel/plugin-proposal-decorators", { legacy: true }], } = process.env;
["@babel/plugin-proposal-class-properties", { loose: true }],
], // console.log(UPLOAD_DIR, NODE_ENV, CDN_DOMAIN, OSS_REGION, OSS_BUCKET, OSS_ACCESS_KEY_ID, OSS_ACCESS_SECRET)
const isDev = NODE_ENV == "development";
const versionStamp = Date.now();
const prodBase = `${CDN_DOMAIN}/${UPLOAD_DIR}/${versionStamp}/`;
return {
base: !isDev ? prodBase : "",
server: {
// port: 3001
open: false,
host: "0.0.0.0",
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
}
}, },
}), assetsInclude: [/\.(svga)$/],
], build:{
css: { cssTarget: 'chrome61',
preprocessorOptions: {
less: {
javascriptEnabled: true,
}, },
}, plugins: [
modules: { react({
localsConvention: 'camelCase' babel: {
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
],
},
}),
legacy({
targets: ['defaults', 'not IE 11'],
}),
viteMockServe({
// default
mockPath: 'mock',
enable: true,
}),
!isDev && DuibaPublish({
buildVersion: versionStamp,
uploadDir: UPLOAD_DIR,
region: OSS_REGION,
bucket: OSS_BUCKET,
accessKeyId: OSS_ACCESS_KEY_ID,
accessKeySecret: OSS_ACCESS_SECRET,
}),
],
css: {
postcss: {
plugins: [
autoprefixer({
overrideBrowserslist: [
"Android 4.1",
"iOS 7.1",
"Chrome > 31",
"ff > 31",
"ie >= 8",
"last 10 versions", // 所有主流浏览器最近10版本用
],
grid: true
}),
postcsspxtorem({
rootValue: 100,
propList: ["*", "!border"], // 除 border 外所有px 转 rem
selectorBlackList: [".noRem-"], // 过滤掉.noRem-开头的class,不进行rem转换
}),
],
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
modules: {
localsConvention: 'camelCase'
}
}
} }
} }
}) )
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment