Commit 5fb81cc6 authored by haiyoucuv's avatar haiyoucuv

init

parents
# 顶部的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
# 忽略eslint校验路径,例如:
# src/libs/@spark
\ No newline at end of file
module.exports = {
parser: '@babel/eslint-parser',
env: {
browser: true,
es6: true,
node: true,
},
globals: {
CFG: true,
wx: true,
FYGE: true,
SPARK_ESLINT_PLUGIN: true,
remScale: true,
},
plugins: ['html', 'react', '@spark/best-practices', '@spark/security'],
extends: ['eslint:recommended', 'plugin:react/recommended'],
settings: {
react: {
version: 'detect',
},
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 7,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
},
babelOptions: {
configFile: './node_modules/@spark/code-inspector/static/babel.config.js',
},
},
rules: {
'no-undef': 'error',
'no-unused-vars': ['error', { vars: 'all', args: 'after-used', argsIgnorePattern: '^_', varsIgnorePattern: '^_', ignoreRestSiblings: true }],
'no-dupe-keys': 'error',
'no-fallthrough': 'error',
'no-global-assign': 'error',
'no-implied-eval': 'error',
'no-self-assign': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true, allowTaggedTemplates: true }],
'no-useless-escape': 'error',
'no-empty-pattern': 'error',
'no-empty-function': ['error', { allow: ['arrowFunctions', 'functions', 'methods'] }],
'no-var': 'error',
'no-dupe-class-members': 'error',
'no-unsafe-optional-chaining': 'error',
'no-const-assign': 'error',
'no-empty': ['error', { allowEmptyCatch: true }],
'prefer-const': 'warn',
'no-extra-boolean-cast': 'warn',
'no-mixed-spaces-and-tabs': 'warn',
'no-alert': 'warn',
'no-new-wrappers': 'warn',
'no-useless-concat': 'warn',
'no-useless-return': 'warn',
'prefer-promise-reject-errors': ['warn', { allowEmptyReject: true }],
'spaced-comment': 'warn',
'react/prop-types': 'off',
'react/display-name': 'off',
'react/jsx-pascal-case': 'error',
'jsx-quotes': 'warn',
// 'react/jsx-tag-spacing': 'error',
'react/require-resnder-return': 'error',
'semi': [1],
"prefer-rest-params": "off",
},
overrides: [
{
files: ['public/**/*.html'],
rules: {
'no-var': 'off',
'@spark/security/third-party-whitelist': 'error',
'@spark/best-practices/no-url-in-js': 'error',
'@spark/best-practices/no-arrow-function': 'error',
'@spark/best-practices/no-es6-variable-declaration': 'error',
},
},
{
files: ['src/**/*.{js,jsx}'],
rules: {
'@spark/best-practices/no-url-in-js': 'error',
},
},
],
};
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"prefer-rest-params": "off",
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<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>
<body>
<div id="root"></div>
<script type="module" src="/src/App.tsx"></script>
</body>
</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";
import { MockMethod, MockConfig } from 'vite-plugin-mock';
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"),
}
},
},
]
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@csstools/normalize.css": "^12.1.1",
"@spark/api-base": "^2.0.36",
"@spark/share": "^2.0.340",
"@spark/svgaplayer": "^2.0.5",
"@spark/ui": "^2.1.28",
"@spark/utils": "^2.0.88",
"@types/crypto-js": "^4.2.2",
"@types/node": "^22.5.1",
"crypto-js": "^4.2.0",
"duiba-utils": "^2.0.2",
"gitinfo-ts": "^0.0.2",
"less": "^4.2.0",
"mobx": "^6.13.1",
"mobx-react": "^9.1.1",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@eslint/js": "^9.9.0",
"@types/postcss-pxtorem": "^6.0.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-legacy": "^5.4.2",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"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.31.6",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1",
"vite-plugin-mock": "^3.0.2"
}
}
* {
margin: 0;
padding: 0;
}
html,
body {
font-size: 24px;
width: 100%;
height: 100%;
-webkit-text-size-adjust: 100% !important;
text-size-adjust: 100% !important;
-moz-text-size-adjust: 100% !important;
overflow: hidden;
}
.modal_center {
left: 0 !important;
top: 0 !important;
bottom: 0 !important;
right: 0 !important;
margin: auto;
}
#app {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.console {
width: 100%;
height: 100%;
}
.testBtn{
width: 750px;
height: 100px;
}
\ No newline at end of file
import React, { Component, StrictMode } from 'react'
import { observer } from "mobx-react";
import { createRoot } from "react-dom/client";
import store from "./store/store";
import '@csstools/normalize.css';
import './App.less'
import { PAGE_MAP } from "./utils/constants";
import LoadingDemo from "./pages/LoadingDemo/LoadingDemo";
import HomeDemo from "./pages/HomeDemo/HomeDemo";
import Modal from "./modal/modal";
const pageMap = {
[PAGE_MAP.LOADING_PAGE]: <LoadingDemo />,
[PAGE_MAP.HOME_PAGE]: <HomeDemo />,
};
@observer
class App extends Component {
async componentDidMount() {
await store.getFrontVariable();
}
render() {
const {curPage, pageData} = store;
return (
<>
{{...pageMap[curPage], props: {...pageData}}}
<Modal/>
</>
);
}
}
createRoot(document.getElementById('root')!).render(
<App />
);
import { generateAPI } from "./utils.js"
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
declare type Methods = 'get' | 'post';
declare interface APIArguments {
uri: string,
method?: Methods | Uppercase<Methods>,
headers?: any
withToken?: boolean
secret?: string,
secretKey?: string,
contentType?: string,
showMsg?:boolean,
showLoading?: boolean;
}
declare type APIConfig = APIArguments | string
declare interface GenerateAPIParams {
[key: string]: APIConfig
}
declare type RequestParamType = Record<string, any>
declare interface ResponseType<T = any> {
code: number,
success: boolean,
message: string,
data: T
}
export declare function generateAPI<
T extends GenerateAPIParams = GenerateAPIParams,
P = RequestParamType,
Q = ResponseType>
(apiList: T): {
[key in keyof T]: (args?: P) => Promise<Q>
}
import { callApi } from '@spark/api-base'
import { Loading, Toast } from '@spark/ui'
import { isFromShare, newUser } from '../built-in/duiba-utils';
import { errorHandler } from "../utils/errorHandler";
import { getPxToken } from "../built-in/getPxToken";
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) => {
callApi("/autoLogin/resetCookie", {
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, showLoading = false, headers, withToken, secret, secretKey, contentType = 'form' } = value;
return {
uri,
method,
headers,
withToken,
secret,
secretKey,
contentType,
showLoading,
showMsg
}
} else {
console.error('getRequestParams: 传参有误');
}
}
/**
* 请求API通用处理
* @param {*} value
* @returns
*/
export function generateAPI(apiList) {
const api = {};
for (const key in apiList) {
const value = apiList[key];
const { method, uri, headers: mHeaders, withToken, secret, secretKey, contentType, showLoading, showMsg = true } = getRequestParams(value);
api[key] = async (params = {}, headers) => {
// 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);
// }
// }
// 根据接口配置showLoading展示loading
// 600ms内结束的请求不显示loading,避免闪烁
let query = false;
showLoading && setTimeout(() => {
if (!query) {
Loading.show();
}
}, 600);
let token;
if (withToken) { // 是否携带token
try {
token = await getPxToken(); // 获取token
} catch (e) {
Toast('网络异常,请稍后再试~');
query = true;
showLoading && Loading.hide();// 根据接口配置showLoading关闭loading
return ({ success: false, data: '' });
}
}
const mergedHeaders = { ...mHeaders, ...headers }
if (withToken && token) {
params.token = token;
}
params = { ...params, ...mergeData };
const result = await callApi(uri, params, method, mergedHeaders, false, secret, secretKey, contentType)
.catch(e => {
query = true;
// 捕获网络异常
showMsg && Toast(e.message || '网络异常,请稍后再试~');
showLoading && Loading.hide();
});
query = true;
showLoading && Loading.hide();// 根据接口配置showLoading关闭loading
return new Promise((resolve) => {
if (result) {
// 判断接口错误
if (!result.success && showMsg) {
errorHandler(result);
}
// 返回整个结果
resolve(result);
} else {
resolve({ 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 let queryParams: any = {};
let search = window.location.search;
try {
search = top.location.search; //尝试获取顶层的链接
} catch (e) {
}
for (let item of search.replace('?', '').split('&')) {
let arr = item.split('=');
queryParams[arr[0]] = arr.length === 1 ? true : decodeURIComponent(arr[1]);
}
/**
* 添加一个script
* @param script
* @param parent
*/
export function appendScript(script, parent = document.body) {
if (!script) {
return;
}
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) {
parent.appendChild(child);
i--;
li--;
} else {
_appendScript(child.innerHTML, parent);
}
}
} else {
_appendScript(script, parent);
}
}
function _appendScript(scriptContent, parent) {
let scriptEl = document.createElement('script');
scriptEl.innerHTML = scriptContent;
parent.appendChild(scriptEl);
}
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);
if (type === "jsonp") {
return jsonp(openUrl, mParams);
} else {
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);
}
}
/**
* jsonp请求
* @param url
* @param params
*/
export function jsonp(url, params) {
return new Promise<void>((resolve, reject) => {
const src = urlJoin(url, obj2query(params));
const scriptEl = document.createElement('script');
scriptEl.src = src;
scriptEl.onload = function () {
resolve();
document.body.removeChild(scriptEl);
};
scriptEl.onerror = function () {
reject();
document.body.removeChild(scriptEl);
};
/*const callbackFuncName = '__zeroing_jsonp_callback__' + Math.random();
window[callbackFuncName] = function () {
callback(result);
};*/
document.body.appendChild(scriptEl);
})
}
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 '';
}
let arr: string[] = [];
for(let key in obj) {
arr.push(key + (key ? '=' : '') + obj[key]);
}
return arr.join('&');
}
import { callApi, jsonp } from '@spark/api-base';
import { evalJsScript } from '@spark/utils';
import { CodeError } from "./common-helpers/CodeError";
import { Errors as ERRORS } from "./common-helpers/errors";
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 callApi('getToken', undefined, undefined, undefined, false);
if (resp.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);
};
.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 {}
import React from 'react';
import {observer} from 'mobx-react';
import './HomeDemo.less';
@observer
class HomeDemo extends React.Component {
render() {
return <div className="homeDemo">
当前为活动首页
</div>;
}
}
export default HomeDemo;
@import "../../res.less";
.loading {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
//background: linear-gradient(152deg, #AEE7FF 6%, #F1DEFC 43%, #F3E7FB 73%);
.webpBg("LoadingPage/loadingBg.jpg");
//background-image: url("../../assets/LoadingPage/loadingBg.jpg");
background-size: 750px 1624px;
.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 "./LoadingDemo.less";
// import {preloadAsset} from "../../utils/preload1.3";
import {PAGE_MAP} from "../../utils/constants";
import store from "../../store/store";
@observer
class LoadingDemo extends React.Component {
state = {
curPercentage: 0
}
curPercentage = 0;
intervalId: number = 0;
isEvenLoad = true; // 是否匀速加载进度条
componentDidMount() {
this.preloadAssetInit();
// this.jump();
}
/**
* 资源预加载
*/
preloadAssetInit = async () => {
// const imageList = [];
// preloadAsset(
// imageList,
// 3,
// this.onLoadingProgress,
// ).then(() => {
// // 预加载资源完成
// // 异步加载默认关闭
// // setTimeout(() => {
// // // 异步加载资源开始
// // const asyncImageList = assetList.asyncLoadImg;
// // preloadAsset(asyncImageList, 1)
// // }, 5000);
// });
};
jump = () => {
setTimeout(() => {
store.changePage(PAGE_MAP.HOME_PAGE); // 跳转页面
}, 100);
};
/**
* 资源加载进度回调
* @param {*} progress
*/
onLoadingProgress = (progress) => {
const percentage = Math.floor(progress * 100);
console.log("progress", percentage);
if (this.isEvenLoad) {
this.setEvenProgress(percentage);
} else {
if (percentage == 100) {
this.jump();
}
}
};
/**
* 以1%匀速加载进度
* @param {*} percentage
*/
setEvenProgress = (percentage) => {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
this.intervalId && clearInterval(this.intervalId);
let curPercentage = this.curPercentage;
// @ts-ignore
this.intervalId = 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;
background-image: url("@{RES_PATH}@{value}");
[duiba-webp='true'] & {
background-image: url("@{RES_PATH}@{value}@{webp}");
}
}
.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 urlArr = [];
let flag = false,
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 () {
flag = true
localStorage.isWebp = ''
}
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='
}
})()
/**
* 弹窗优先级 可以是负数, 不写默认是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 { 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);
// },
// });
// })
// }
import {
start,
updateShare,
callShare,
Weixin,
} from "@spark/share"
/**
* @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'
}
/**
* 初始化分享
*/
export async function onInitShare(cb) {
await start([Weixin], function (success) {
console.log("share result:----", success)
cb && cb()
})
}
/**
* 更新分享
* @param {*} shareParams
* @param {*} justUpdate
*/
export function onUpdateShare(shareParams) {
console.info("更新分享", shareParams)
updateShare(shareParams)
}
/**
* 被动分享 - 北京银行
* @param {*} shareParams
*/
export function onCallShare(shareParams) {
console.info("分享链接", shareParams)
callShare(shareParams);
}
/**
* @description: 分享处理中心
* @param {Object} 分享信息
*/
export const requireShare = (opts) => {
const shareData = {
title: opts.shareTitle,
content: opts.shareContent,
url: opts.shareUrl,
images: [{ image: opts.shareThumbnail, type: "url" }],
};
console.log('分享数据', opts);
const shareStr = JSON.stringify(shareData);
return shareStr;
};
/**
* @description: 小程序分享
* @param {*}
* @return {*}
*/
export const miniDoShare = (opts) => {
console.log(opts);
wx.miniProgram.postMessage({
data: {
title: opts.title, // 标题
desc: opts.desc, // 描述
imgUrl: opts.imgUrl, // 图片
link: opts.link // 链接
}
});
}
This diff is collapsed.
/// <reference types="vite/client" />
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"experimentalDecorators": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "react",
/* Linting */
"strict": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": false
},
"include": ["src"]
}
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
{
"compilerOptions": {
"target": "ES2022",
"lib": [
"ES2023"
],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"vite.config.ts"
]
}
import { defineConfig } from 'vite'
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";
const isProd = process.env.NODE_ENV == "production";
// https://vitejs.dev/config/
export default defineConfig({
base: isProd ? '//yun.duiba.com.cn/db_games/qx/testSparkVite/' : "",
plugins: [
react({
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,
}),
],
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: [".el-"], // 过滤掉.el-开头的class,不进行rem转换
}),
],
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
modules: {
localsConvention: 'camelCase'
}
}
})
This diff is collapsed.
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