Commit 6c7ea2cb authored by 余成's avatar 余成

初始化项目

parent b2d68441
/node_modules
/.history
\ No newline at end of file
思维导图地址 # 这个webpack的基础项目 当模板来使用
https://alidocs.dingtalk.com/i/team/3YxXANaQeaQ3LmNy/docs/3YxXAR7oJbBWLmNy?iframeQuery=mode%3D2 \ No newline at end of file
\ No newline at end of file
/*
将此文件放到project/config/scripts/assets/目录下
在package.json文件的"scripts"字段下,分别修改dev和build命令:
"dev": "node ./config/scripts/assets/generateAssetList.js && node ./config/webpack.dev.config.js"
"build": "node ./config/scripts/assets/generateAssetList.js && node ./config/scripts/assets/index.js imgmin imgup && node ./config/webpack.prod.config.js"
*/
const fs = require('fs')
const path = require('path')
/*请先配置:预加载的资源文件夹名称,或者设置预加载、异步加载资源路径*/
const preloadFolder = ['loading']; //在/src/assets文件夹下,请设置需要预加载的资源文件目录,默认值预加载为loading文件夹, 其他均为异步加载
const initAssetList = { //初始化预设资源处理
preLoadImg:[], //设置预加载图片,例如:["loading/bg174.png","loading/上面.png","loading/底部173.png"]
asyncLoadImg:[] //设置异步加载图片
}
/**
* 搜索文件夹里的文件
* @param {*} folderList 预加载文件夹名称数组
* @param {*} folderPath 文件夹地址,绝对路径
* @param {*} regExp 正则表达式,用于匹配目标文件
* @returns {string[]} 返回文件相对路径地址
*/
function searchFileFromFolder(folderPath='/src/assets', regExp=/\.(png|jpg|jpeg|svga|spi|json|mp3|wav)$/i) {
let preLoadImg = [], asyncLoadImg = [];
const searchOneDir = (absolutePath, relativePath) => {
fs.readdirSync(absolutePath).forEach(v => {
let absPath = absolutePath + '/' + v;
let relPath = relativePath ? relativePath + '/' + v : v;
if(fs.statSync(absPath).isFile()) {
if(regExp.test(v)){
if(preloadFolder.includes(relPath.split('/')[0])){
preLoadImg.push(relPath);
}else{
asyncLoadImg.push(relPath)
}
}
}else {
searchOneDir(absPath, relPath);
}
});
}
searchOneDir(path.resolve('.') + folderPath, '');
console.log('资源预处理成功~')
return {
preLoadImg: [
...initAssetList.preLoadImg,
...preLoadImg
],
asyncLoadImg: [
...initAssetList.asyncLoadImg,
...asyncLoadImg
]
};
}
// 读资源目录
const assetList = searchFileFromFolder();
// 写资源列表json
fs.writeFileSync(path.resolve('.') + '/src/assetList.json', JSON.stringify(assetList))
\ No newline at end of file
const { assets } = require("spark-assets");
const args = process.argv.splice(2);
let argsObj = {
imgmin: false,
imgup: false
}
if (args.length == 1) {
argsObj.imgmin = 'imgmin' == args[0];
argsObj.imgup = 'imgup' == args[0];
} else if (args.length == 2) {
argsObj.imgmin = 'imgmin' == args[0];
argsObj.imgup = 'imgup' == args[1];
}
assets(argsObj)
\ No newline at end of file
exports.SPARK_CONFIG_DIR_KEY = ['OUTPUT_DIR', 'SOURCE_DIR', 'TEMP_DIR', 'ENTRY', 'TEMPLATE']
exports.SPARK_CONFIG = 'sparkrc.js'
//对应项目在线素材存储的cdn配置,用于迭代开发从线上拉取素材到本地
exports.SPARK_CDN_RES_CFG='sparkrescfg.json'
\ No newline at end of file
const loaderUtils = require('loader-utils');
module.exports = function (source) {
const options = loaderUtils.getOptions(this);
let result = source;
if (options.arr) {
options.arr.map(op => {
result = result.replace(op.replaceFrom, op.replaceTo);
})
} else {
result = source.replace(options.replaceFrom, options.replaceTo);
}
return result
};
const babel = require('@babel/core');
const HtmlWebpackPlugin = require("html-webpack-plugin");
class HtmlJsToES5Plugin {
process(htmlPluginData) {
return new Promise(function (resolve) {
const scriptRegExp = /<script>[\s\S]*?<\/script>/gis;
htmlPluginData.html = htmlPluginData.html.replace(scriptRegExp, function (match) {
const code = match.replace("<script>", "").replace("</script>", "");
const es5Code = babel.transform(code, { 'presets': ['@babel/preset-env'] }).code;
return `<script>${es5Code}</script>`;
});
resolve();
});
};
apply(compiler){
compiler.hooks.compilation.tap('HtmlJsToES5Plugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync(
"HtmlJsToES5Plugin",
async (html, cb) => {
await this.process(html);
cb(null, html);
}
);
});
}
}
// exports.default = HtmlJsToES5Plugin;
module.exports = HtmlJsToES5Plugin;
// 端口是否被占用
exports.getProcessIdOnPort=function(port) {
try {
const execOptions = {
encoding: 'utf8',
stdio: [
'pipe',
'pipe',
'ignore',
],
};
return execSync('lsof -i:' + port + ' -P -t -sTCP:LISTEN', execOptions)
.split('\n')[0]
.trim();
} catch (e) {
return null;
}
}
const childProcessSync=async function(cmd, params, cwd, printLog = true) {
return new Promise((resolve, reject) => {
let proc = childProcess(cmd, params, cwd, printLog);
proc.on('close', (code) => {
if (code === 0) {
resolve(proc['logContent']);
} else {
reject(code);
}
});
});
}
const getGitBranch=async function(cwd) {
try {
const result = await childProcessSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], cwd, false);
if (!result.startsWith('fatal:')) {
return result.trim();
}
} catch (e) {
return undefined;
}
}
const getProjectNameByPackage=function() {
return require(`${process.cwd()}/package.json`).name
}
/**
* 理论上每个项目独一无二的文件夹名字-默认取分支名
* 如果当前未创建分支,取包名+日期
* (实际很多情况是直接clone老项目,包名相同,以防资源被替换,所以用日期加一下)
*/
exports.getCdnFolderName=async function() {
const branch = await getGitBranch(process.cwd());
const date = Date.now();
if (branch) {
return branch + "/" + date;
}
let foldername = getProjectNameByPackage() + "/" + date;
return foldername;
}
\ No newline at end of file
const path = require('path');
const fs = require("fs");
const { SPARK_CONFIG_DIR_KEY, SPARK_CONFIG } = require('./scripts/constant');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
module.exports = function (isProd) {
const appPath = process.cwd();
const sparkConfig = require(path.join(appPath, SPARK_CONFIG));
const isEslint = fs.existsSync(`${appPath}/.eslintrc.js`);
const cssReg = /\.(css|less)$/;
// 处理相对路径
SPARK_CONFIG_DIR_KEY.map((key) => {
sparkConfig[key] = path.resolve(appPath, sparkConfig[key]);
});
const stylePlugins = [
require("autoprefixer")({
overrideBrowserslist: ["> 1%", "last 2 versions", "not ie <= 8"],
})
];
if (sparkConfig.PX2REM) {
stylePlugins.push(
require("postcss-px2rem-exclude")({
remUnit: 100, // 注意算法,这是750设计稿,html的font-size按照750比例
exclude: /node_modules/i,
})
);
}
const styleLoader = (cssOptions = {}) => {
return [
{
loader: "style-loader",
},
isProd && {
loader: MiniCssExtractPlugin.loader,
options: {
esModule: false,
},
},
{
loader: "css-loader",
options: {
...cssOptions,
importLoaders: 2, // 如果遇到css里面的 @import 执行后面两个loader。 不然如果import了less,css-loader是解析不了
},
},
{
loader: "postcss-loader",
options: {
sourceMap: !isProd,
plugins: stylePlugins,
},
},
{
loader: require.resolve("less-loader"),
options: {
sourceMap: !isProd,
lessOptions: {
modifyVars: {
"@RES_PATH": `"${isProd ? sparkConfig.RES_PATH_PROD + '/' : sparkConfig.RES_PATH}"`,
},
}
},
},
].filter(Boolean);
};
return {
entry: sparkConfig.ENTRY,
mode: isProd ? 'production' : 'development',
devtool: isProd ? "source-map" : "cheap-module-source-map",
output: {
path: path.resolve(__dirname, sparkConfig.OUTPUT_DIR),
filename: "js/[name].js",
},
resolve: {
extensions: ['.js', '.jsx', '.json'],
alias: {
"@src": path.resolve(__dirname, sparkConfig.SOURCE_DIR),
},
},
module: {
rules: [
// 提前进行eslint, 默认从下往上,通过enforce pre提前
isEslint && {
test: /\.js|jsx/,
enforce: "pre",
loader: "eslint-loader",
options: {
cache: true,
formatter: require("eslint-friendly-formatter"),
fix: true,
failOnError: true,
configFile: `${appPath}/.eslintrc.js`,
},
include: sparkConfig.SOURCE_DIR,
},
{
test: cssReg,
use: styleLoader(),
include: sparkConfig.SOURCE_DIR,
},
{
test: /\.(js|jsx)$/,
loader: require.resolve("babel-loader"),
exclude: [path.resolve("node_modules")],
options: {
presets: [
require("@babel/preset-env").default,
require("@babel/preset-react").default,
],
plugins: [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": false }],
require("@babel/plugin-transform-runtime").default,
],
sourceType: 'unambiguous'
},
},
{
test: [/\.(jpg|jpeg|png|svg|bmp)$/, /\.(eot|woff2?|ttf|svg)$/],
loader: require.resolve("url-loader"),
options: {
name: "[path][name].[ext]", // name默认是加上hash值。这里做了更改,不让加
outputPath: "images",
limit: 10240, // url-loader处理图片默认是转成base64, 这里配置如果小于10kb转base64,否则使用file-loader打包到images文件夹下
},
},
].filter(Boolean),
},
plugins: [
isProd &&
new MiniCssExtractPlugin({
filename: "styles/[name].[hash].css",
}),
new HtmlWebpackPlugin({
template: sparkConfig.TEMPLATE,
minify: !sparkConfig.UNMINIFY_INDEX && isProd,
}),
new CleanWebpackPlugin({
// cleanOnceBeforeBuildPatterns:['**/*', 'dist'] // 这里不用写 是默认的。 路径会根据output 输出的路径去清除
}),
new ProgressBarPlugin(),
].filter(Boolean),
optimization: {
minimize: isProd,
minimizer: [
// 替换的js压缩 因为uglifyjs不支持es6语法,
new TerserPlugin({
cache: true,
sourceMap: !isProd,
extractComments: false, // 提取注释
parallel: true, // 多线程
terserOptions: {
compress: {
pure_funcs: [
//"console.log"
],
},
},
}),
// 压缩css
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require("cssnano"),
cssProcessorPluginOptions: {
preset: ["default", { discardComments: { removeAll: true } }],
},
canPrint: true,
}),
],
// 修改文件的ids的形成方式,避免单文件修改,会导致其他文件的hash值变化,影响缓存
moduleIds: "hashed",
splitChunks: {
chunks: "all",
minSize: 30000, //小于这个限制的会打包进Main.js
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10, // 优先级权重,层级 相当于z-index。 谁值越大权会按照谁的规则打包
name: "vendors",
},
},
},
// chunks 映射关系的 list单独从 app.js里提取出来
runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`,
},
},
};
}
const {SPARK_CONFIG} = require("./scripts/constant");
const Webpack = require("webpack");
const webpackBaseConfig = require("./webpack.common.config");
const WebpackMerge = require("webpack-merge");
const WebpackDevServer = require("webpack-dev-server");
const apiMocker = require('mocker-api');
const path = require('path');
const {getProcessIdOnPort} = require("./scripts/utils");
const sparkConfig = require(path.resolve(SPARK_CONFIG));
const webpackDevConfig = function (options) {
let {port} = options;
return {
devServer: {
useLocalIp: true,
open: true,
hot: true,
host: "0.0.0.0",
disableHostCheck: true,
port: port,
headers: {
'Access-Control-Allow-Origin': '*'
},
// hotOnly: true
before(app) {
if (sparkConfig.API_MOCK) {
apiMocker(app, path.resolve('./mock/index.js'), {
changeHost: true,
})
}
}
},
plugins: [
// new Webpack.WatchIgnorePlugin([/[\\/]mock[\\/]/]),
new Webpack.HotModuleReplacementPlugin()
]
};
};
const buildDev = async function (options) {
let {port} = options;
return new Promise((resolve, reject) => {
const config = WebpackMerge(webpackBaseConfig(false), webpackDevConfig(options));
const compiler = Webpack(config);
const devServerOptions = Object.assign({}, config.devServer);
console.log('devServerOptions', devServerOptions);
const server = new WebpackDevServer(compiler, devServerOptions);
if (getProcessIdOnPort(port)) {
reject(`端口 ${port} 已被使用`);
return;
} else {
server.listen(
port || 8088,
"0.0.0.0",
() => {
console.log(`Starting server on http://localhost:${port}`);
resolve();
},
(err) => {
if (err) console.error("server linsten err--", err);
reject();
}
);
}
});
};
const args = process.argv.splice(2);
const port = args[0] || 8088
buildDev({
port: Number(port)
})
const path = require("path");
const chalk = require("chalk");
const fs = require('fs-extra');
const Webpack = require("webpack");
const WebpackMerge = require("webpack-merge");
const webpackBaseConfig = require("./webpack.common.config");
const {uploadFiles} = require("spark-assets");
const {BundleAnalyzerPlugin} = require("webpack-bundle-analyzer");
const isProd = true;
const {getCdnFolderName} = require("./scripts/utils");
const {SPARK_CONFIG} = require("./scripts/constant");
const HtmlJsToES5Plugin = require("./scripts/plugins/HtmlJsToES5Plugin");
const {DepReporter} = require('spark-log-event');
const sparkConfig = require('../sparkrc');
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");
const webpackProdConfig = function (cdnFolderName, resPathProd) {
return {
output: {
publicPath: `//yun.duiba.com.cn/spark/v2/${cdnFolderName}/`,
filename: isProd ? "js/[name].[contenthash:8].js" : "js/[name].[contenthash:4].js",
},
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, './scripts/loaders')]
},
module: {
rules: [
{
test: /sparkrc\.js$/,
exclude: [path.resolve("node_modules")],
use: [
{
loader: 'replaceLoader',
options: {
arr: [
{
replaceFrom: /(MOCK_STATUS: true)|(MOCK_STATUS:true)|("MOCK_STATUS": true)|("MOCK_STATUS":true)/,
replaceTo: '"MOCK_STATUS": false'
},
{
replaceFrom: /(RES_PATH:.*,)|("RES_PATH":.*,)|('RES_PATH':.*,)/,
replaceTo: `"RES_PATH":"${resPathProd}/",`
}
]
}
}
]
}
]
},
plugins: [
new Webpack.IgnorePlugin(/[\\/]mock[\\/]/),
new ScriptExtHtmlWebpackPlugin({
custom: {
test: /\.js$/,
attribute: 'crossorigin',
value: 'anonymous'
}
}),
new HtmlJsToES5Plugin(),
new DepReporter(),
new BundleAnalyzerPlugin({
analyzerMode: 'disabled'
}),
],
node: {
crypto: 'empty'
}
};
};
const buildProd = async function () {
const cdnFolderName = await getCdnFolderName();
const appPath = process.cwd();
const sparkConfig = require(path.join(appPath, SPARK_CONFIG));
const _webpackProdConfig = await webpackProdConfig(cdnFolderName, sparkConfig.RES_PATH_PROD || '');
//新增 JS_PATH_PROD 用作
let newSparkCfg = Object.assign({}, sparkConfig);
newSparkCfg['JS_PATH_PROD'] = `https://yun.duiba.com.cn/spark/v2/${cdnFolderName}/js`;
let str = `
const page = process.env.PAGE || 'index'
console.log('Current page:', page)
module.exports = ${JSON.stringify(newSparkCfg, null, 2)}
`;
str = str.replace(/"dist\/.*"/, '`dist/${page}`')
.replace(/"src\/.*.jsx"/, '`src/${page}.jsx`')
.replace(/".\/public\/.*.html"/, '`./public/${page}.html`')
fs.writeFileSync(path.join(appPath, SPARK_CONFIG), str);
return new Promise((resolve, reject) => {
const config = WebpackMerge(webpackBaseConfig(isProd), _webpackProdConfig);
const compiler = Webpack(config);
compiler.run(async (error, stats) => {
if (error) {
return reject(error);
}
console.log(
stats.toString({
chunks: false, // 使构建过程更静默无输出
colors: true, // 在控制台展示颜色
})
);
console.log(`${chalk.yellow("打包成功, 等待上传")}\n`);
// await uploadFiles(config.output.path, '', cdnFolderName);
await uploadFiles(config.output.path, '', cdnFolderName, /.map$/);
// 上传map到不同路径,避免泄漏源码
await uploadFiles(
config.output.path + "/js",
'js/map_123_map',
cdnFolderName,
/.(js|css|css\.map)$/
);
resolve();
});
});
};
buildProd();
module.exports = {
'GET /coop_frontVariable.query': {
"success": true,
"message": "这是返回",
"code": null,
"data": {
callAppUrl: 'https://mywap2.icbc.com.cn/ICBCWAPBank/servlet/WAPBAppInject?injectMenuId=conformity',
defaultAvatarImg: 'https://yun.duiba.com.cn/zengkaiwen/cmsSpring/default_avatar.png', // 默认头像图片
shareImg: 'https://yun.duiba.com.cn/zengkaiwen/cmsSpring/cmsShare.png', // 分享缩略图
iosDownloadUrl: 'https://apps.apple.com/cn/app/cams-plus/id1492244351', // iOS安装包下载链接
androidDownloadUrl: 'https://webcdn.m.qq.com/webapp/homepage/index.html#/appDetail?apkName=com.ezia.mobile.ezia_mobile_cams&info=FEBEDACB3DD683723E67C7AAC08C86BA', // 安卓安装包下载链接
lookMoreUrl: "https://www.baidu.com", //首页底部查看更多url
taskViewTime: 15,
}
}
}
\ No newline at end of file
const proxy = {},currentUseDir = 'common';
const fs = require('fs');
fs.readdirSync(__dirname+'/'+currentUseDir).forEach((name)=>{
Object.assign(proxy,require('./'+currentUseDir+'/'+name));
console.log('name:',proxy)
})
module.exports = proxy;
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "webpack base",
"private": true,
"scripts": {
"dev": "npm run assets && cross-env PAGE=index node ./config/webpack.dev.config.js 8089",
"dev:share": "npm run assets && cross-env PAGE=share node ./config/webpack.dev.config.js",
"prod": "npm version patch --no-git-tag-version && cross-env PAGE=index node ./config/webpack.prod.config.js",
"prod:share": "npm version patch --no-git-tag-version && cross-env PAGE=share node ./config/webpack.prod.config.js",
"build": "npm run assets && npm run imgminup && cross-env npm run prod",
"build:share": "npm run assets && cross-env PAGE=share npm run imgminup && cross-env PAGE=share npm run prod:share",
"assets": "node ./config/scripts/assets/generateAssetList.js",
"imgmin": "node ./config/scripts/assets/index.js imgmin",
"imgup": "node ./config/scripts/assets/index.js imgup",
"imgminup": "node ./config/scripts/assets/index.js imgmin imgup"
},
"dependencies": {
"@spark/share": "^2.0.276",
"@spark/svgaplayer": "^2.0.5",
"@spark/ui": "^2.1.8",
"@spark/utils": "^2.0.82",
"css-loader": "^3.6.0",
"dayjs": "^1.10.8",
"duiba-utils": "^1.0.12",
"history": "^4.10.1",
"mobx": "^6.2.0",
"mobx-react": "^7.1.0",
"postcss-loader": "^3.0.0",
"prettier": "^2.0.5",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-router-dom": "^5.2.0",
"spark-wrapper-fyge": "^1.1.21",
"style-loader": "^1.2.1"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-decorators": "^7.13.15",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.12.10",
"@types/react": "^17.0.43",
"autoprefixer": "^9.8.6",
"babel-loader": "^8.2.2",
"chalk": "^4.1.0",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"eslint-loader": "^4.0.2",
"fs-extra": "^9.0.1",
"html-webpack-plugin": "^4.5.1",
"less": "^4.1.0",
"less-loader": "^7.2.1",
"mini-css-extract-plugin": "^1.3.4",
"mocker-api": "^2.7.5",
"mockjs": "^1.1.0",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"postcss-px2rem-exclude": "0.0.6",
"progress-bar-webpack-plugin": "^2.1.0",
"script-ext-html-webpack-plugin": "^2.1.5",
"spark-assets": "^1.1.6",
"spark-log-event": "^1.0.4",
"url-loader": "^4.1.1",
"webpack": "^4.43.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.3.1",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2"
}
}
{"proSetting":{"projectxIDs":{"devId":"p6e78cb4b","testId":"pedd5a881","prodId":"p65d1852e"},"skinVariables":[]},"envSetting":{}}
<!DOCTYPE html>
<html lang="zh">
<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">
<title>首页</title>
<style>
body {
background-color: #ffdbba;
}
/* 这里会导致手机上input 无法输入 所有用not */
*:not(input,textarea){
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
</style>
<!-- <script src="https://yun.duiba.com.cn/db_games/debug/vconsole.min.js"></script> -->
<script>
// new VConsole();
</script>
<script src="//yun.duiba.com.cn/spark/v2/spark.base.fz.wxpollyfill.js"></script>
<script src="//yun.duiba.com.cn/js-libs/rem/1.1.3/rem.min.js"></script>
<script src="//yun.duiba.com.cn/h5/lib/zepto.min.js"></script>
<script src="//res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
<script crossorigin="anonymous" src="//yun.dui88.com/ /jimu/source/icbc_core1.js"></script>
<script>
function getApp() {
return {
cloud: {},
cloudName: "clientTemplate2C",
requestType: "mock"
}
}
var CFG = CFG || {};
CFG.projectID = location.pathname.split('/')[2] || '1';
var sp = new URLSearchParams(location.search)
CFG.appID = sp.get("appID") || '${APPID}';
console.log('appID:', CFG.appID)
console.log('projectID:', CFG.projectID)
//window['svga_parser_disable_worker'] = true
</script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="zh">
<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">
<title>聚宝盆-分享页</title>
<style>
body {
background-color: #6C300B;
}
</style>
<script src="//yun.duiba.com.cn/spark/v2/spark.base.fz.wxpollyfill.js"></script>
<script src="//yun.duiba.com.cn/js-libs/rem/1.1.3/rem.min.js"></script>
<script src="//yun.duiba.com.cn/h5/lib/zepto.min.js"></script>
<script src="//res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
<script>
function getApp() {
return {
cloud: {},
cloudName: "clientTemplate2C",
requestType: "mock"
}
}
var CFG = CFG || {};
CFG.projectID = location.pathname.split('/')[2] || '1';
var sp = new URLSearchParams(location.search)
CFG.appID = sp.get("appID") || '${APPID}';
CFG.indexUrl = window.location.origin + '/projectx/' + CFG.projectId + '/index.html?appID=' + CFG.appID
console.log('appID:', CFG.appID)
console.log('projectID:', CFG.projectID)
</script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
\ No newline at end of file
{"proName":"聚宝盆-模板项目","proDesc":"","proPath":"/Users/wuzengxi/Desktop/qinghaigonghang-steppecat","createTime":1639794863408}
const page = process.env.PAGE || 'index'
console.log('Current page:', page)
module.exports = {
"OUTPUT_DIR": `dist/${page}`,
"SOURCE_DIR": "src",
"TEMP_DIR": "./.temp",
"ENTRY": `src/${page}.jsx`,
"TEMPLATE": `./public/${page}.html`,
"API_MOCK": true,
"PX2REM": true,
"IMAGE_Q1": 0.6,
"IMAGE_Q2": 0.8,
"UNMINIFY_INDEX": true,
"RES_PATH": "/src/assets/",
"RES_PATH_PROD": "//yun.duiba.com.cn/spark/v2/tpl_mvp_jubaopen/1656323829685",
"JS_PATH_PROD": "https://yun.duiba.com.cn/spark/v2/tpl_mvp_jubaopen/1656324036616/js"
}
const apiCfg = {
getRule: `projectRule.query`,
doJoin: {
uri: `join.do`,
method: "post"
},
// 前端配置项
getFrontVariable:`coop_frontVariable.query`
}
export default apiCfg;
/**
* Created by rockyl on 2021/12/1.
*/
import {Toast, appendToast} from '@spark/ui'
import {CodeError} from "@spark/common-helpers"
const errMessageMap = {
100013: '助力失败!分享助力码失效',
100014: '助力失败,自己不能给自己助力哦~',
200303: '助力失败!您的助力次数已达上限~',
200305: '助力失败!您已为该好友助力啦~',
//600002: "非法参数",
//600003: "系统繁忙请稍后再试",
600004: "助力失败!您不是新用户喔~",
//600005: "非法操作",
//600006: "反射获取bean失败",
//600007: "运营配置项异常",
//600008: "策略类实现失败",
600009: '今日收集金币已达上限',
600010: '当前等级未开放该功能',
600011: '金币宝箱领取次数超限',
600012: '请完成新手引导',
600013: '请先点击进入活动',
//600014: "道具配置错误",
600016: "金币数量不足",
600017: "当前金币产量不足以收集,请耐心等待一会儿",
600021: '助力失败,您还在为好友加速中~',
600022: "助力者已达到助力上限",
600023: "给同一个用户助力次数到达上限",
600024: "助力者已达到每日助力上限",
600025: "邀请码不存在",
600047: "无法给自己助力",
600045: '好友被助力已达上限,晚点再为TA<br/>助力吧~',
600046: "好友加速功能尚未开放",
600026: "用户已签到或功能尚未开放",
600027: "任务功能未开放",
600028: "任务无法完成",
600029: "任务类型未定义",
600030: "未达到兑换等级",
600031: "已达到投教体验上限",
//600032: "投教引导没有走完",
//600033: "已走过新手引导,不要重复请求",
//600034: "没有走重新分配就直接调用保存数据接口",
600035: "投教功能暂未开放或者没有到达开放等级",
600036: "lv8任务已触发,无法重复触发",
600038: "lv8任务已完成,无法重复触发",
600039: "lv9任务已触发,无法重复触发",
600040: "lv9任务已完成,无法重复触发",
//600041: "等级任务组件配置异常,奖品配置错误",
//600042: "lv8任务未达到配置等级",
//600043: "lv9任务未达到配置等级",
210001: '网络错误,请检查网络是否通畅',
}
const uriExcludes = [
'mvpBowlMain/latestSpeedUpList.do', //轮询加速列表接口不用错误提示
]
/**
* 统一错误处理
* @param e
* @param uri
*/
export function errorHandler(e, uri) {
if (e.code == '0' && e.message === "请稍后再试") {
return
}
if (uriExcludes.indexOf(uri) >= 0) {
return
}
const code = parseInt(e.code)
switch (code) {
case 600021:
case 600022:
case 600023:
case 600024:
case 600025:
//助力失败错误需要用toast队列
const assistMsg = errMessageMap[code] || e.message || '网络异常'
appendToast(assistMsg)
break
default:
const msg = errMessageMap[code] || e.message || '网络异常'
Toast(msg)
console.warn(msg, uri)
break
}
}
/**
* 错误码集合
*/
export const errors = {}
/**
* 错误码消息
*/
const errorMessages = {}
/**
* 创建错误对象
*/
export function makeError(code, message) {
return new CodeError(code, message || errorMessages[code])
}
import apiCfg from './apicfg'
import {getPxToken} from "@spark/projectx"
import {callApi} from '@spark/api-base'
import {Toast} from '@spark/ui'
import {isFromShare, newUser} from 'duiba-utils'
import {errorHandler} from "./error-handler"
let mergeData = {
user_type: newUser ? '0' : '1',
is_from_share: isFromShare ? '0' : '1',
}
const apiList = {
...apiCfg
}
const API = generateAPI(apiList)
export default API
function getRequestParams(value) {
if (typeof value === 'string') {
return {
uri: value,
method: 'get'
}
} else if (typeof value === 'object') {
const {uri, method = 'get', headers, withToken, secret, secretKey, contentType = 'form', ignoreError} = value
return {
uri,
method,
headers,
withToken,
secret,
secretKey,
contentType,
ignoreError,
}
} else {
console.error('getRequestParams: 传参有误')
}
}
function generateAPI(apiList) {
const api = {}
for (let key in apiList) {
let value = apiList[key]
const {
method,
uri,
headers: mHeaders,
withToken,
secret,
secretKey,
contentType,
ignoreError
} = getRequestParams(value)
api[key] = async (params = {}, headers) => {
let token
if (withToken) {
try {
token = await getPxToken()
} catch (e) {
if (e.code == 210001) {
console.warn('网络原因导致token获取失败,下游接口:', uri)
errorHandler(e)
} else {
console.warn('token获取失败,下游接口:', uri)
Toast('token获取失败,请刷新页面重试')
}
throw e
}
}
let mergedHeaders = {...mHeaders, ...headers}
if (withToken && token) {
params.token = token
}
params = {...params, ...mergeData}
return callApi(uri, params, method, mergedHeaders, true, secret, secretKey, contentType)
.catch(e => {
//捕获网络异常
//Toast((e.message || '网络异常') + ' ***请补全该处理逻辑***')
if (ignoreError) {
console.warn('call api failed:', uri, e)
} else {
errorHandler(e, uri)
throw e
}
})
/*if (result) {
//判断接口错误
if (!result.success) {
Toast((result.message || '接口错误') + ' ***请补全该处理逻辑***')
}
//返回整个结果
return result
}*/
}
}
return api
}
{"preLoadImg":[],"asyncLoadImg":[]}
\ No newline at end of file
import React, {Component} from "react"
import ReactDOM from "react-dom"
import {observer} from 'mobx-react'
import "./index.less"
import Modal from './modal/modal'
import './md-index'
//此处为spark-cli动态生成
import HomePage from "@src/pages/homePage/homePage"
@observer
class Index extends Component {
render() {
return (
<div>
<HomePage/>
{/* <Modal/> */}
</div>
)
}
}
ReactDOM.render(<Index/>, document.getElementById("root"))
* {
margin: 0;
padding: 0;
/* Standard syntax */
-webkit-tap-highlight-color: rgba(0, 0, 0, 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;
--swiper-theme-color: transparent;
}
input {
outline: none;
}
input:focus {
outline: none;
}
input[type="range"] {
opacity: 0;
-webkit-appearance: none;
background-color: transparent;
-webkit-appearance: none;
height: 50px;
padding: 0;
border: none;
-webkit-appearance: none; /*清除系统默认样式*/
}
/*拖动块的样式*/
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; /*清除系统默认样式*/
background: transparent;
border: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
/*去除默认样式*/
cursor: default;
top: 0;
height: 100px;
width: 100px;
transform: translateY(0px);
}
.guide-hand {
animation: guide-hand-ani 1s infinite ease-in-out;
pointer-events: none !important;
}
@keyframes light_rotation {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
}
.unset_background {
background: none !important;
}
.width_100 {
left: 0 !important;
width: 100% !important;
}
.width_80 {
left: 10% !important;
width: 80% !important;
text-align: center !important;
}
.height_100 {
height: 100% !important;
width: auto !important;
}
.height_unset {
height: unset !important;
}
.width_100_unset_height {
left: 0 !important;
width: 100% !important;
height: unset;
}
.unset_width_unset_height {
width: unset !important;
height: unset !important;
}
.textover {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text_center {
text-align: center !important;
}
.unset_line_height {
line-height: unset !important;
}
.unset_left {
left: unset !important;
}
.unset_top_left {
left: unset !important;
top: unset !important;
}
.unset_top_left_position {
left: unset !important;
top: unset !important;
position: relative !important;
}
.unset_all_from_import {
left: unset !important;
top: unset !important;
width: unset !important;
height: unset !important;
position: unset !important;
}
.unset_position {
position: unset !important;
}
.unset_bound_from_import {
left: unset !important;
top: unset !important;
width: unset !important;
height: unset !important;
}
.text_ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.light_rotate {
animation: anim_rotate 4s linear infinite;
}
.hidden {
display: none !important;
}
//去除滚动条的样式
::-webkit-scrollbar {
height: 0;
opacity: 0;
display: none;
}
@keyframes pulse {
0% {
transform: scale(1, 1);
}
50% {
transform: scale(1.05, 1.05);
}
100% {
transform: scale(1, 1);
}
}
@keyframes anim_rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
\ No newline at end of file
import {md} from '@spark/utils';
const {appID, projectID} = window['CFG']
const dcm = '202.' + projectID + '.0.0';
const configs = []
for (let i = 1; i <= 63; i++) {
configs.push({
id: i,
ele: '.md_' + i,
data: {
dpm: `${appID}.110.${i}.1`,
dcm,
}
})
}
// console.log('configs',configs)
// md.prepare({appId: appID})
// md.registerBuriedPoints(configs)
import React from "react";
import {Modal} from '@spark/ui';
export const cfg = {
};
export default function ModalWrapper() {
return <Modal modals={cfg}/>;
}
\ No newline at end of file
import React, { Component } from 'react';
import './homePage.less'
export default class homePage extends Component {
render() {
return (
<div>
homePage
</div>
)
}
}
@import "../../res.less";
.homePage {
}
\ No newline at end of file
@RES_PATH: '/src/assets/';
.sparkBg(@value) {
background: url("@{RES_PATH}@{value}") no-repeat top left / 100% 100%;
}
@font-face {
font-family: mFont;
src: url("@{RES_PATH}ZiZhiQuXiMaiTi-2.ttf");
}
// 多行显示省略号
.textClampOverFlow(@clamp:2) {
-webkit-box-orient: vertical; // 避免压缩后删除此行
-webkit-line-clamp: @clamp; // 显示省略号行数
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
}
\ 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);
// Itoast('网络异常,请检查网络状态!');
}
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) {
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;
})
}
/**
* 加载一个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 {callShare, start, updateShare, 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() {
var 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) => {
var shareData = {
title: opts.shareTitle,
content: opts.shareContent,
url: opts.shareUrl,
images: [{image: opts.shareThumbnail, type: "url"}],
};
console.log('分享数据', opts);
var 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 // 链接
}
});
}
/*
* @Author: all
* @Date: 2021-11-01 09:30:00
* @LastEditTime: 2021-11-02 18:30:00
* @LastEditors: all
* @Description:
*/
import {useCallback, useEffect, useRef} from "react";
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, //数据扁平化
getNumFormString, // 提取字符串中的数字
toFixedFun, // 解决toFeixd 自动进位问题
}
/**
* @description: 函数节流,普通防连点
* @param {(Function, number?)}
* @return {Function}
*/
const _throttle = (fun, delay = 2000) => {
let last, deferTimer;
return function () {
let now = +new Date();
if (last && now < last + delay) {
clearTimeout(deferTimer);
deferTimer = setTimeout(() => {
last = now;
}, delay);
} else {
last = now;
fun.apply(this, arguments);
}
};
};
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 () {
var 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++) {
let arr = arrCookie[i].split('=');
if (cookieName == arr[0]) {
return arr[1];
}
}
return '';
}
/**
* 获取url参数
* @param {string} name
*/
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;
}
/**
* 删除url中的参数
* @param {*} url
* @param {*} arg
*/
function delUrlParam(url, ref) {
// 如果不包括此参数
if (url.indexOf(ref) == -1) return url;
let arr_url = url.split('?');
let base = arr_url[0];
let arr_param = arr_url[1].split('&');
let index = -1;
for (let i = 0; i < arr_param.length; i++) {
let paired = arr_param[i].split('=');
if (paired[0] == ref) {
index = i;
break;
}
}
if (index == -1) {
return url;
} else {
arr_param.splice(index, 1);
return base + '?' + arr_param.join('&');
}
}
/**
* 日期格式化
* @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(
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) => {
let time = msTime / 1000;
let hour = Math.floor(time / 60 / 60) % 24;
let minute = Math.floor(time / 60) % 60;
let second = 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) {
if (num >= 1000) {
let result = num / 1000;
result = Math.floor(result * 10) / 10;
var s_x = result.toString();
var 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) {
let temp1 = str.replace(/[^\x00-\xff]/g, "**");
let temp2 = temp1.substring(0, sub_length);
let x_length = temp2.split("\*").length - 1;
let hanzi_num = x_length / 2;
sub_length = sub_length - hanzi_num;
let 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 (var i = arr.length - 1; i >= 0; i--) {
var randomIndex = Math.floor(Math.random() * (i + 1))
var 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) => {
var o1 = obj1 instanceof Object
var o2 = obj2 instanceof Object
if (!o1 || !o2) { /* 判断不是对象 */
return obj1 === obj2
}
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return false
}
for (var attr in obj1) {
var t1 = obj1[attr] instanceof Object
var 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.entries(dateObj).reduce((preVal, [key, value]) => {
// 值取整
value = parseInt(value)
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))
}
/** 控制滚动--兼容ioss */
function bodyScroll(event) {
event.preventDefault();
}
export function setScreenScroll(lock = true) {
if (lock) { //禁止滚动
document.body.addEventListener('touchmove', bodyScroll, {passive: false});
} else { //开启滚动
document.body.removeEventListener('touchmove', bodyScroll, {passive: false});
}
}
function toFixed(num, fractionDigits) {
const mul = Math.pow(10, fractionDigits)
return Math.floor(num * mul) / mul
}
/**
* 缩写数字
* @param num
* @param isDenominator 是不是分母
*/
export function shortNumber(num, isDenominator) {
let str
if (num >= 10000) {
str = toFixed(num / 10000, 1) + (isDenominator ? 'w' : 'W')
} else if (num >= 1000) {
str = toFixed(num / 1000, 1) + (isDenominator ? 'k' : 'K')
} else {
str = num.toString()
}
if (isDenominator) {
for (let i = 0; i < 10; i++) {
str = str.replace(new RegExp(i + '', 'g'), String.fromCharCode(i + 97))
}
}
return str
}
/**
* 美化数字为货币样式
* @param num
* @return {string}
*/
export const toThousands = (num = 0) => {
const str = num + ''
return str.split('').reduce((pv, cv, i) => {
return str[str.length - i - 1] + (i % 3 === 0 && i !== 0 ? ',' : '') + pv
}, '')
};
export const filiterGoldText = (num) => {
const _num = Number(num);
if (_num >= 100000) {
return Math.floor(num / 10000) + 'w'
} else if (_num >= 10000) {
return Math.floor(num / 10000 * 10) / 10 + 'w'
} else if (_num >= 1000) {
return Math.floor(num / 1000 * 10) / 10 + 'k'
} else {
return _num
}
}
/**
* 根据等级获取ip等级
* @param level
* @param levelMapping
* @return {number}
*/
export function getIpLevel(level, levelMapping) {
let index
for (let i = 0; i < levelMapping.length; i++) {
const lv = levelMapping[i]
if (level >= lv) {
index = i
}
}
return index + 1
}
// 两数相加
export const accAdd = (num1, num2) => {
var r1, r2, m;
try {
r1 = num1.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = num2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
// return (num1*m+num2*m)/m;
return Math.round(num1 * m + num2 * m) / m;
}
// 两个浮点数相减
export const accSub = (num1, num2) => {
var r1, r2, m, n;
try {
r1 = num1.toString().split('.')[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = num2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
n = (r1 >= r2) ? r1 : r2;
return (Math.round(num1 * m - num2 * m) / m).toFixed(n);
}
// 两数相除
export const accDiv = (num1, num2) => {
var t1, t2, r1, r2;
try {
t1 = num1.toString().split('.')[1].length;
} catch (e) {
t1 = 0;
}
try {
t2 = num2.toString().split(".")[1].length;
} catch (e) {
t2 = 0;
}
r1 = Number(num1.toString().replace(".", ""));
r2 = Number(num2.toString().replace(".", ""));
return (r1 / r2) * Math.pow(10, t2 - t1);
}
// 两数相x
export const accMul = (arg1, arg2) => {
var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
try {
m += s1.split(".")[1].length;
} catch (e) {
}
try {
m += s2.split(".")[1].length;
} catch (e) {
}
return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
}
// 提取字符串中的数字
const getNumFormString =(str) =>{
if(!str) return null
if(typeof str !== "string") return null
return str.replace(/[^0-9]/ig,"");
}
// 重写toFeixd
const toFixedFun = (data, len)=>{
// debugger
const number = Number(data);
if (isNaN(number) || number >= Math.pow(10, 21)) {
return number.toString();
}
if (typeof (len) === 'undefined' || len === 0) {
return (Math.round(number)).toString();
}
let result = number.toString();
const numberArr = result.split('.');
if (numberArr.length < 2) {
// 整数的情况
return padNum(result);
}
const intNum = numberArr[0]; // 整数部分
const deciNum = numberArr[1];// 小数部分
const lastNum = deciNum.substr(len, 1);// 最后一个数字
if (deciNum.length === len) {
// 需要截取的长度等于当前长度
return result;
}
if (deciNum.length < len) {
// 需要截取的长度大于当前长度 1.3.toFixed(2)
return padNum(result);
}
// 需要截取的长度小于当前长度,需要判断最后一位数字
result = `${intNum}.${deciNum.substr(0, len)}`;
if (parseInt(lastNum, 10) >= 5) {
// 最后一位数字大于5,要进位
const times = Math.pow(10, len); // 需要放大的倍数
let changedInt = Number(result.replace('.', ''));// 截取后转为整数
changedInt++; // 整数进位
changedInt /= times;// 整数转为小数,注:有可能还是整数
result = padNum(`${changedInt }`);
}
return result;
// 对数字末尾加0
function padNum(num) {
const dotPos = num.indexOf('.');
if (dotPos === -1) {
// 整数的情况
num += '.';
for (let i = 0; i < len; i++) {
num += '0';
}
return num;
} else {
// 小数的情况
const need = len - (num.length - dotPos - 1);
for (let j = 0; j < need; j++) {
num += '0';
}
return num;
}
}
}
\ No newline at end of file
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