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 diff is collapsed.
{
"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 // 链接
}
});
}
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