Commit 17185812 authored by tao.huang's avatar tao.huang

feat: 初始化

parent c4af49dd
{
"name": "Vue 3 项目开发规范",
"version": "1.0.0",
"description": "Vue 3 Composition API 项目开发规范与最佳实践",
"rules": {
"项目结构": {
"description": "项目目录结构规范",
"rules": [
"/ 作为源代码根目录",
"components/ 存放可复用组件",
"composables/ 存放组合式函数",
"views/ 存放页面级组件",
"router/ 路由配置",
"store/ 状态管理",
"assets/ 静态资源",
"utils/ 工具函数",
"api/ 接口请求",
"pages/ 页面文件",
"static/ 静态资源",
"uni_modules/ 插件目录"
]
},
"组件开发": {
"description": "组件开发规范",
"rules": [
"使用 <script setup> 语法糖",
"组件名使用 PascalCase 命名",
"props 必须指定类型",
"使用 defineProps 和 defineEmits 声明属性和事件",
"组件文件使用 .vue 扩展名",
"组件名与文件名保持一致"
]
},
"组合式函数": {
"description": "组合式函数开发规范",
"rules": [
"使用 use 前缀命名",
"一个函数只做一件事",
"返回响应式数据和方法",
"处理异步操作时使用 try/catch",
"提供清晰的函数文档"
]
},
"状态管理": {
"description": "状态管理规范",
"rules": [
"使用 ref 管理简单状态",
"使用 reactive 管理复杂状态",
"使用 computed 处理派生状态",
"使用 watch 处理副作用",
"大型应用使用 Pinia 进行状态管理",
"避免过度使用全局状态"
]
},
"性能优化": {
"description": "性能优化规范",
"rules": [
"合理使用 v-memo",
"避免不必要的组件重渲染",
"使用 shallowRef 处理大型对象",
"及时清理事件监听器",
"使用 keep-alive 缓存组件",
"合理使用异步组件"
]
},
"代码风格": {
"description": "代码风格规范",
"rules": [
"使用 ESLint 和 Prettier",
"遵循 Vue 3 风格指南",
"使用有意义的变量命名",
"添加必要的代码注释",
"保持代码简洁清晰",
"遵循 DRY 原则"
]
},
"错误处理": {
"description": "错误处理规范",
"rules": [
"使用 try/catch 处理异步错误",
"实现全局错误处理",
"提供友好的错误提示",
"记录关键错误日志",
"处理边界情况",
"实现优雅的降级策略"
]
},
"测试规范": {
"description": "测试规范",
"rules": [
"编写单元测试",
"测试关键业务逻辑",
"模拟外部依赖",
"测试错误场景",
"保持测试代码简洁",
"定期运行测试用例"
]
},
"uni-app规范": {
"description": "uni-app 开发规范",
"rules": [
"使用条件编译处理平台差异",
"遵循 uni-app 生命周期规范",
"使用 uni-app 内置组件和API",
"合理使用 uni-app 路由管理",
"适配不同平台样式差异",
"使用 uni-app 插件市场组件"
]
},
"跨平台适配": {
"description": "跨平台开发规范",
"rules": [
"使用 rpx 作为尺寸单位",
"适配不同平台导航栏",
"处理平台特定API差异",
"使用条件编译处理平台特性",
"统一管理平台特定样式",
"处理不同平台兼容性问题"
]
},
"性能优化": {
"description": "uni-app 性能优化规范",
"rules": [
"合理使用分包加载",
"优化图片资源大小",
"使用组件按需加载",
"避免频繁的页面跳转",
"合理使用缓存机制",
"优化首屏加载速度"
]
}
},
"开发工具": {
"description": "推荐使用的开发工具",
"tools": [
"HBuilderX - 开发工具",
"Vite - 构建工具",
"ESLint - 代码检查",
"Prettier - 代码格式化",
"Vue DevTools - 调试工具",
"Vitest - 单元测试"
]
},
"最佳实践": {
"description": "Vue 3 开发最佳实践",
"practices": [
"使用 Composition API 组织代码",
"实现响应式数据管理",
"优化组件性能",
"保持代码可维护性",
"遵循 Vue 3 官方推荐",
"遵循 uni-app 开发规范",
"做好跨平台适配",
"注重性能优化"
]
}
}
\ No newline at end of file
# 顶部的EditorConfig文件
root = true
# unix风格的换行符,每个文件都以换行符结尾
[*]
end_of_line = lf
insert_final_newline = true
# 设置默认字符集
charset = utf-8
# 去除行尾空白字符
trim_trailing_whitespace = true
# 使用空格缩进,设置2个空格缩进
indent_style = space
indent_size = 2
# 忽略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]
},
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',
},
},
],
};
.DS_Store
node_modules/
___cache/
__cache/
coverage/
npm-debug.log
selenium-debug.log
.idea
.builds
.project
.vscode
yarn-error.log
.yarn
.package-lock
yarn.lock
.cache
packages/**/package-lock.json
released
output.js
output.js.map
.psd
.psb
#src/assets/
\ No newline at end of file
registry = http://npm.dui88.com
\ No newline at end of file
module.exports = {
semi: true, // 结尾加分号
singleQuote: false, // 使用单引号
jsxSingleQuote: false, // jsx中使用单引号
bracketSpacing: true, // 括号和参数之间有空格
jsxBracketSameLine: true, // 标签属性较多时,标签箭头>另起一行
quoteProps: 'as-needed', // 属性加引号需要加时再加
printWidth: 120, // 每行字符个数
};
registry "http://npm.dui88.com"
\ No newline at end of file
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>
import MD from 'spark-utils/out/md/index.js';
import { logClick, logExposure } from "@spark/utils/src-js/md";
let appId = CFG.appID;
const dcm = '202.' + CFG.projectId + '.0.0';
const domain = '//embedlog.duiba.com.cn';
let MDList = [
{
ele: `.test-md1`,
data: {
dpm: `${appId}.110.5.1`,
dcm,
domain,
appId
},
once: false
}
];
export default () =>
MD({
show: MDList, // 曝光
click: MDList // 点击
});
export function handleLogExposure(id, id2 = 1) {
logExposure({
dpm: `${appId}.110.${id}.${id2}`,
dcm,
domain,
appId,
});
}
export function handleLogClick(id, id2 = 1) {
logClick({
dpm: `${appId}.110.${id}.${id2}`,
dcm,
domain,
appId,
});
}
### 注意事项
xxxxxxx
### 迭代日志
## 20230202 [大雁链接](https://www.bilibili.com)
+ haha
+ haha1
+ haha2
\ No newline at end of file
// API 配置文件
export const BASE_URL = 'https://api.example.com' // 替换为实际的 API 地址
// 请求超时时间
export const TIMEOUT = 10000
// 请求状态码
export const HTTP_STATUS = {
SUCCESS: 200,
CREATED: 201,
ACCEPTED: 202,
CLIENT_ERROR: 400,
AUTHENTICATE: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
SERVER_ERROR: 500,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504
}
// 错误信息
export const ERROR_MESSAGE = {
NETWORK_ERROR: '网络异常,请检查您的网络连接',
TIMEOUT_ERROR: '请求超时,请稍后重试',
SERVER_ERROR: '服务器异常,请稍后重试',
UNKNOWN_ERROR: '未知错误,请稍后重试'
}
\ No newline at end of file
import { api } from './request'
// 用户相关接口
export const userApi = {
// 登录
demo: (data) => api.post('/demo', data),
}
// 导出 http 实例,方便直接使用
export { api }
\ No newline at end of file
// request.js
// 通常可以吧 baseUrl 单独放在一个 js 文件了
const baseUrl = "http://xxx.xxx.4.xxxx:8093/chemApp";
const request = (options = {}) => {
// 在这里可以对请求头进行一些设置
// 例如:
// options.header = {
// "Content-Type": "application/x-www-form-urlencoded"
// }
return new Promise((resolve, reject) => {
uni
.request({
url: baseUrl + options.url || "",
method: options.type || "GET",
data: options.data || {},
header: options.header || {},
})
.then((data) => {
let [err, res] = data;
resolve(res);
})
.catch((error) => {
reject(error);
});
});
};
const get = (url, data, options = {}) => {
options.type = "GET";
options.data = data;
options.url = url;
return request(options);
};
const post = (url, data, options = {}) => {
options.type = "POST";
options.data = data;
options.url = url;
return request(options);
};
export default {
request,
api: {
get,
post,
},
};
/*
将此文件放到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 = []; // 在/src/assets文件夹下,请设置需要预加载的资源文件目录,默认值预加载为loading文件夹, 其他均为异步加载
const otherFolder = ['loadingDemo']; // 在/src/assets文件夹下,不做任务处理的文件夹,不需要预加载, 也不需要异步加载
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) {
const preLoadImg = [], asyncLoadImg = [];
const searchOneDir = (absolutePath, relativePath) => {
fs.readdirSync(absolutePath).forEach(v => {
const absPath = absolutePath + '/' + v;
const relPath = relativePath ? relativePath + '/' + v : v;
if(fs.statSync(absPath).isFile()) {
if(regExp.test(v)){
if(preloadFolder.includes(relPath.split('/')[0])){
preLoadImg.push(relPath);
}else if(!otherFolder.includes(relPath.split('/')[0])){
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 HtmlWebpackPlugin = require("html-webpack-plugin");
const { exec } = require('child_process');
class GitToHtmlPlugin {
process(htmlPluginData) {
return new Promise(function (resolve) {
let gitStr = '';
exec('git remote -v & git branch --show-current & git config --global user.name', (_err, stdout, _stderr) => {
if (stdout) {
gitStr = `<script>CFG.___G___='${encodeURIComponent(stdout.replace(/\n/g, ';'))}'</script>`
}
htmlPluginData.html = htmlPluginData.html.replace('</body>', `${gitStr}</body>`);
resolve();
});
});
};
apply(compiler) {
compiler.hooks.compilation.tap('GitToHtmlPlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).afterTemplateExecution.tapAsync(
"GitToHtmlPlugin",
async (html, cb) => {
await this.process(html);
cb(null, html);
}
);
});
}
}
module.exports = GitToHtmlPlugin;
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;
// 端口是否被占用
const net = require("net");
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;
}
}
exports.isPortAvailable = function (port, host = "localhost") {
return new Promise((resolve) => {
if (isNaN(port) || port != parseInt(port) || port < 0 || port > 65536) {
resolve(false);
}
try {
const tester = net.createServer()
// catch errors, and resolve false
.once('error', err => {
resolve(false);
})
// return true if succed
.once('listening', (err) => {
tester
.once('close', () => {
resolve(true);
})
.close()
})
.listen(port, host);
} catch (e) {
resolve(false);
}
})
}
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;
}
const path = require('path');
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 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: /noRem/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: {
strictExportPresence: true,
rules: [
{
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: /\.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 opn = require("opn");
const apiMocker = require('mocker-api');
const path = require('path');
const { getProcessIdOnPort } = require("./scripts/utils");
const {isPortAvailable} = require("./scripts/utils");
const sparkConfig = require(path.resolve(SPARK_CONFIG));
const webpackDevConfig = function () {
return {
devServer: {
useLocalIp: true,
open: false,
hot: true,
host: "0.0.0.0",
// hotOnly: true
before(app) {
app.use(/^\/$/, async (req, res, next) => {
const send = res.send.bind(res);
res.send = (body) => {
let result = body.toString();
result = result.replace('${APPID}', 'test');
send(result);
}
next();
})
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 (config) {
const { port } = config;
return new Promise(async (resolve, reject) => {
const config = WebpackMerge(webpackBaseConfig(false), webpackDevConfig());
const compiler = Webpack(config);
const devServerOptions = Object.assign({}, config.devServer);
console.log('devServerOptions', devServerOptions);
const server = new WebpackDevServer(compiler, devServerOptions);
let i = 0;
for (; i < 100; i++) {
const canUse = await isPortAvailable(port + i, "0.0.0.0");
console.log(port + i, canUse)
if (canUse) {
break;
}
}
server.listen(
port + i || 8088,
"0.0.0.0",
() => {
console.log(`Starting server on http://localhost:${port}`);
opn(`http://localhost:${port + i || 8088}`);
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 {Uploader} = require("spark-assets");
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 GitToHtmlPlugin = require("./scripts/plugins/GitToHtmlPlugin");
const JavaScriptObfuscator = require("javascript-obfuscator");
const PATH_ROOT = 'spark/v2';
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:'\/src\/assets\/')|(RES_PATH: '\/src\/assets\/')|("RES_PATH":"\/src\/assets\/")|("RES_PATH": "\/src\/assets\/")/,
replaceTo: `"RES_PATH":"${resPathProd}/"`
}
]
}
}
]
},
]
},
plugins: [
new Webpack.IgnorePlugin(/[\\/]mock[\\/]/),
new ScriptExtHtmlWebpackPlugin({
custom: {
test: /\.js$/,
attribute: 'crossorigin',
value: 'anonymous'
}
}),
new GitToHtmlPlugin(),
new HtmlJsToES5Plugin(),
new DepReporter(),
new Webpack.ContextReplacementPlugin(
/moment[/\\]locale$/,
/zh-cn/,
),
],
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 用作
const newSparkCfg = Object.assign({}, sparkConfig);
newSparkCfg['JS_PATH_PROD'] = `https://yun.duiba.com.cn/spark/v2/${cdnFolderName}/js`;
const str = `module.exports =${JSON.stringify(newSparkCfg, null, 2)}`;
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`);
const files = fs.readdirSync(config.output.path + "/js");
let fileName = "";
for (let i = 0; i < files.length; i++) {
if (files[i].endsWith('.js') && files[i].indexOf("main") == 0) {
fileName = files[i];
}
}
const js = fs.readFileSync(path.join(config.output.path, "js/" + fileName), "utf-8");
const resJs = JavaScriptObfuscator.obfuscate(
js,
{
debugProtectionInterval: 4000,
debugProtection: true,
// 单行输出
compact: true,
selfDefending: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.3,
// 注入死代码
deadCodeInjection: true,
deadCodeInjectionThreshold: 0.2,
// 标识符名称生成器
// hexadecimal 16进制 包体增大较多
// mangled 短名称
// mangled-shuffled 与mangled相同,但带有洗牌字母表
// "identifier-names-generator": 'mangled-shuffled',
// 数字转表达式 如:
// const foo = 1234;
// const foo=-0xd93+-0x10b4+0x41*0x67+0x84e*0x3+-0xff8;
// numbersToExpressions: true,
log: true,
// 拆分字面字符串
splitStrings: true,
stringArray: true,
stringArrayRotate: true,
stringArrayCallsTransform: true,
stringArrayCallsTransformThreshold: 1,
stringArrayWrappersParametersMaxCount: 5,
stringArrayThreshold: 1,
// transformObjectKeys: true,
target: "browser-no-eval",
}
);
fs.writeFileSync(path.join(config.output.path, "js/" + fileName), resJs.getObfuscatedCode(), "utf-8");
const uploader = new Uploader();
await Promise.all([
await uploader.uploadDir(
config.output.path,
`${PATH_ROOT}/${cdnFolderName}`,
/.(html|map)$/
),
await uploader.uploadDir(
config.output.path + "/js",
`${PATH_ROOT}/${cdnFolderName}/js/map_123_map`,
/.(html|js|css|css\.map)$/
),
]);
resolve();
});
});
};
buildProd();
{
"version": "0.2",
"language": "en",
"words": [
"duiba",
"fyge",
"hanzi",
"projectx",
"webfonts",
"Weixin",
"webp"
]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>
{
"compilerOptions": {
"experimentalDecorators": true,
"baseUrl": "./",
"paths": {
"@src/*": ["src/*"]
}
},
"exclude": [
"node_modules"
]
}
\ No newline at end of file
{"numOfComponents":1259,"numOfProject":796}
\ No newline at end of file
import App from "./App";
// #ifndef VUE3
import Vue from "vue";
import "./uni.promisify.adaptor";
import { api } from "@/api/index.js";
// 全局挂载后使用
Vue.prototype.$api = api;
Vue.config.productionTip = false;
App.mpType = "app";
const app = new Vue({
...App,
});
app.$mount();
// #endif
// #ifdef VUE3
import { createSSRApp } from "vue";
export function createApp() {
const app = createSSRApp(App);
return {
app,
};
}
// #endif
{
"name" : "20250528_FHQ1",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios打包配置 */
"ios" : {},
/* SDK配置 */
"sdkConfigs" : {}
}
},
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}
const {AES, enc, mode, pad} = require("crypto-js");
const getOptions = (iv) => {
return {
iv: enc.Utf8.parse(iv),
mode: mode.CBC,
padding: pad.ZeroPadding,
};
}
/** 加密 */
exports.AESEncrypt = (str, key, iv) => {
const options = getOptions(iv);
return AES.encrypt(str, enc.Utf8.parse(key), options).toString();
};
/** 解密 */
exports.AESDecrypt = (cipherText, key, iv) => {
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, '');
};
module.exports = {
"success": true,
"message": "报错了~",
"code": null,
"data": {
"test_config_01": "前端配置项测试",
"test_config_03": null,
"test_config_02": "111"
}
}
\ No newline at end of file
module.exports = {
"data": 3,
"success": true
}
\ No newline at end of file
module.exports = {
"data": "<p>以下是游戏规则:手速要快,点击红包雨。。333。。。。。。。。。。。。。。。。。。。。11111111111111sadasdadadsad5555555557777777777799999999999911111111111111111111111222222222222222222222222222222222222222222222222222222222222222333333333333333333333333333333333333333333333333333333333333311111111111111111111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333333331111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222223333333333333333333333333333333333333333333333333333333333333</p>",
"success": true
}
\ No newline at end of file
const rule = require("./common/rule");
const drawNum = require("./common/drawNum");
const coopFrontVariable = require("./common/coopFrontVariable");
const {AESEncrypt} = require("./Crypto");
const proxy = {
...require("./project"),
"GET /projectRule.query": rule,
"GET /drawNum.query": drawNum,
"GET /coop_frontVariable.query": coopFrontVariable,
"GET /spring/start.do": {
"code": "code",
"success": true,
"message": "message",
"timeStamp": Date.now(),
"data": AESEncrypt(JSON.stringify({
"startId": "officia",
"countDown": 30
}), "1696BD3E5BB915A0", "cDOiBC1n2QrkAY2P"),
},
};
module.exports = proxy;
const fs = require('fs')
let proxy = {}
fs.readdirSync(__dirname)
.some(filename => {
if (filename !== 'index.js') {
proxy = Object.assign(proxy, require('./' + filename))
}
})
module.exports = proxy;
{
"name": "temp_base",
"version": "3.6.8",
"private": true,
"scripts": {
"dev": "cross-env NODE_ENV=development node ./config/scripts/assets/generateAssetList.js && node ./config/webpack.dev.config.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",
"spark:prebuild": "ncu @spark/pre-build -u && yarn && npx pre-build",
"lint": "eslint --ext .js,jsx,.html src public",
"prod": "rimraf ./dist &&cross-env NODE_ENV=production node ./config/webpack.prod.config.js",
"build": "rimraf ./dist &&cross-env NODE_ENV=production node ./config/scripts/assets/generateAssetList.js && node ./config/scripts/assets/index.js imgmin imgup && node ./config/webpack.prod.config.js"
},
"dependencies": {
"@spark/api-base": "^2.0.7",
"@spark/svgaplayer": "^2.0.5",
"@spark/ui": "^2.0.8",
"@spark/utils": "^2.0.17",
"classnames": "^2.5.1",
"crypto-js": "^4.2.0",
"duiba-utils": "^1.0.2",
"history": "^4.10.1",
"javascript-obfuscator": "^4.1.0",
"mobx": "^6.2.0",
"mobx-react": "^7.1.0",
"qs": "^6.9.4",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"spark-utils": "^0.0.12",
"terser-webpack-plugin": "4.2.3"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/eslint-parser": "^7.17.0",
"@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",
"@spark/eslint-plugin-best-practices": "^0.0.1-beta.2",
"@spark/eslint-plugin-security": "^0.0.1-beta.5",
"@spark/pre-build": "^0.0.1-beta.11",
"@types/crypto-js": "^4.2.2",
"autoprefixer": "^9.8.6",
"babel-loader": "^8.2.2",
"chalk": "^4.1.0",
"clean-webpack-plugin": "^3.0.0",
"commander": "^11.1.0",
"cross-env": "^7.0.3",
"css-loader": "^3.6.0",
"eslint": "^8.22.0",
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-react": "^7.29.4",
"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",
"npm-check-updates": "^12.5.4",
"optimize-css-assets-webpack-plugin": "^5.0.4",
"postcss-loader": "^3.0.0",
"postcss-px2rem-exclude": "0.0.6",
"prettier": "^2.0.5",
"progress-bar-webpack-plugin": "^2.1.0",
"rimraf": "^3.0.2",
"script-ext-html-webpack-plugin": "^2.1.5",
"spark-assets": "^2.6.2",
"spark-log-event": "^1.0.4",
"style-loader": "^1.2.1",
"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"
}
}
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path" : "pages/brand/brand",
"style" :
{
"navigationBarTitleText" : "品牌"
}
},
{
"path" : "pages/integral/integral",
"style" :
{
"navigationBarTitleText" : "积分"
}
},
{
"path" : "pages/my/my",
"style" :
{
"navigationBarTitleText" : "我的"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#FF0000",
"backgroundColor": "#FFFFFF",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/brand/brand",
"text": "品牌"
},
{
"pagePath": "pages/integral/integral",
"text": "积分"
},
{
"pagePath": "pages/my/my",
"text": "我的"
}
]
},
"uniIdRouter": {}
}
<template>
<view>
</view>
</template>
<script setup>
</script>
<style>
</style>
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{title}}</text>
<textarea name="" id="" cols="30" rows="10"></textarea>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
},
methods: {
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
<template>
<view>
</view>
</template>
<script setup>
</script>
<style>
</style>
<template>
<view>
</view>
</template>
<script setup>
</script>
<style>
</style>
{"proSetting":{"projectxIDs":{"testId":[],"prodId":[]},"skinVariables":[],"mockSetting":{"projectId":"","pageId":""}},"envSetting":{},"psdSetting":{"psdFSSetting":true,"psdCenterSetting":true}}
<!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">
<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/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>
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>
<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":"20250528_飞鹤一期","proDesc":"","proPath":"C:\\Users\\DuibaGroup\\Desktop\\work\\20250528_FHQ1\\20250528_飞鹤一期","createTime":1748397556294}
module.exports ={
"OUTPUT_DIR": "dist",
"SOURCE_DIR": "src",
"TEMP_DIR": "./.temp",
"ENTRY": "src/app.jsx",
"TEMPLATE": "./public/index.html",
"API_MOCK": true,
"PX2REM": true,
"IMAGE_Q1": 0.6,
"IMAGE_Q2": 0.8,
"RES_PATH": "/src/assets/",
"RES_PATH_PROD": "//yun.duiba.com.cn/spark/v2/temp_base/1716371639557",
"JS_PATH_PROD": "https://yun.duiba.com.cn/spark/v2/temp_base/1716371641511/js"
}
\ No newline at end of file
{"assetsPathArr":["//yun.duiba.com.cn/spark/v2/temp_base/1678169645525/loadingDemo/上面.png","//yun.duiba.com.cn/spark/v2/temp_base/1678169645525/loadingDemo/底部173.png","//yun.duiba.com.cn/spark/v2/temp_base/1678169645525/loadingDemo/背景.jpg"]}
\ No newline at end of file
import API from "@src/api";
import {
setCookieId
} from "@src/api/utils";
/**
* 普通跳转客服
* 注意:小程序需要额外增加业务域名 https://duiba.qiyukf.com/
*/
export function jumpService() {
location.href = location.origin + '/faq/index'
}
export function appJump(url, hideNavbar = false, data = {}) {
API.tempSaveCookie().then((res) => {
if (res?.data) {
localStorage.setItem("db_temp_cookie", res.data);
// setCookieId(res.data);
}
}).finally(() => {
// app对应跳转方法
window.MarsJSBridge.invoke("pushPage", {
uri: url,
data,
pageParams: {
hideNavbar,
}
});
});
}
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 'duiba-utils';
import { errorHandler } from "@src/utils/errorHandler";
import API from "@src/api/index";
import { getPxToken } from "@src/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;
}
/**
* 活动主入口
*/
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { observer } from "mobx-react";
import "./app.less";
import store from "./store/index";
import Modal from "./modal/modal";
import "./utils/checkwebp"; // webp检查
import MD from "../MD"; // 埋点
MD();
// 此处为spark-cli动态生成
import LoadingDemo from "@src/pages/LoadingDemo/LoadingDemo";
import HomeDemo from "@src/pages/HomeDemo/HomeDemo";
import { PAGE_MAP } from "./utils/constants";
/**
* 所有页面场景
*/
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 />
</>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
* {
margin: 0;
padding: 0;
-webkit-touch-callout: none; /*系统默认菜单被禁用*/
-khtml-user-select: none; /*早期浏览器*/
-moz-user-select: none; /*火狐*/
-ms-user-select: none; /*IE10*/
-webkit-tap-highlight-color: transparent;
user-select: text;
-webkit-user-select: text;
}
*:not(input,textarea) {
-webkit-touch-callout: none;
-webkit-user-select: none;
}
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;
}
#root {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.com_Container {
width: 100%;
height: 100%;
position: relative;
overflow-x: hidden;
overflow-y: auto;
}
{"preLoadImg":[],"asyncLoadImg":["LoadingPage/loadingBg.jpg","LoadingPage/loadingFill.png","LoadingPage/loadingIp.png"]}
\ No newline at end of file
export class CodeError {
name = 'CodeError';
code = '';
message = '';
stack;
payload;
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失败
};
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, { useState } from "react";
/**
* @param children
* @param className
* @param onClick
* @param style
* @return {React.DetailedReactHTMLElement<{onTouchStart: onTouchStart, onClick: (function(): any), onTouchCancel: onTouchEnd, className, style: {transform: string, transitionDuration: number}, onTouchEnd: onTouchEnd}, HTMLElement>}
* @constructor
*/
export const Button = ({ children, className, onClick = () => void 0, style = {} }) => {
const [scale, setScale] = useState("unset");
const onTouchStart = () => {
setScale("scale(0.9,0.9)");
};
const onTouchEnd = () => {
setScale("unset");
};
const onTouchCancel = onTouchEnd;
return React.createElement("div", {
className, onTouchStart, onTouchEnd, onTouchCancel, onClick, style: {
transitionDuration: 0.5, transform: scale, ...style
}
}, children);
};
import 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
.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 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";
.homeDemo {}
import React from "react";
import {observer} from "mobx-react";
import store from "../../store/index";
import "./LoadingDemo.less";
import {preloadAsset} from "@src/utils/preload1.3.js";
import assetList from "@src/assetList.json";
import {PAGE_MAP} from "@src/utils/constants";
@observer
class LoadingDemo extends React.Component {
state = {
curPercentage: 0
}
curPercentage = 0;
intervalId = 0;
isEvenLoad = true; // 是否匀速加载进度条
componentDidMount() {
this.preloadAssetInit();
}
/**
* 资源预加载
*/
preloadAssetInit = async () => {
const imageList = assetList.preLoadImg;
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) => {
this.intervalId && clearInterval(this.intervalId);
let curPercentage = this.curPercentage;
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;
@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-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;
}
}
@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 { PAGE_MAP } from '@src/utils/constants';
import { makeAutoObservable } from 'mobx';
import API from '../api/index';
import { GetCurrSkinId, getCustomShareId } from "@src/utils/utils";
const skinId = GetCurrSkinId() || getCustomShareId();
const store = makeAutoObservable({
/** 活动规则 */
ruleInfo: '',
/** 前端开发配置 */
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;
},
/** 获取活动规则 */
async initRule() {
// 模拟获取远程的数据
const { data } = await API.getRule();
this.ruleInfo = data;
},
/** 获取前端配置项 */
async getFrontVariable() {
// 获取前端开发配置
const { data } = await API.getFrontVariable();
this.frontVariable = data || {};
console.log('前端开发配置', data)
}
})
export default store;
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 {AES, enc, mode, pad} from "crypto-js";
const getOptions = (iv) => {
return {
iv: enc.Utf8.parse(iv),
mode: mode.CBC,
padding: pad.ZeroPadding,
};
}
/** 加密 */
export const AESEncrypt = (str, key, iv) => {
const options = getOptions(iv);
return AES.encrypt(str, enc.Utf8.parse(key), options).toString();
};
/** 解密 */
export const AESDecrypt = (cipherText, key, iv) => {
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='
}
})()
/*
* @Author: super
* @Date: 2021-01-20 14:08:27
* @LastEditTime: 2021-01-20 14:53:39
* @LastEditors: super
* @Description:
*/
/**
* 弹窗优先级 可以是负数, 不写默认是10, 数值越小,层级越高
*/
export const MODAL_INDEX = {
// rank: 1,
};
/** 网络异常默认展示 */
export const DEFAULT_NET_ERROR = '网络异常,请稍后再试';
export const PAGE_MAP = {
HOME_PAGE: "homePage",
LOADING_PAGE: "loadingPage",
};
/** @Author: all
* @Date: 2022-04-12 11:26:44
**/
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.
uni.addInterceptor({
returnValue (res) {
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
return res;
}
return new Promise((resolve, reject) => {
res.then((res) => {
if (!res) return resolve(res)
return res[0] ? reject(res[0]) : resolve(res[1])
});
});
},
});
\ No newline at end of file
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\n","import App from \"./App\";\r\n\r\n// #ifndef VUE3\r\nimport Vue from \"vue\";\r\nimport \"./uni.promisify.adaptor\";\r\n\r\nimport { api } from \"@/api/index.js\";\r\n\r\n// 全局挂载后使用\r\nVue.prototype.$api = api;\r\n\r\nVue.config.productionTip = false;\r\nApp.mpType = \"app\";\r\n\r\nconst app = new Vue({\r\n ...App,\r\n});\r\napp.$mount();\r\n// #endif\r\n\r\n// #ifdef VUE3\r\nimport { createSSRApp } from \"vue\";\r\nexport function createApp() {\r\n const app = createSSRApp(App);\r\n return {\r\n app,\r\n };\r\n}\r\n// #endif\r\n"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACWM,SAAS,YAAY;AAC1B,QAAM,MAAMC,2BAAaC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACJ;AACA;;;"}
\ No newline at end of file
{"version":3,"file":"assets.js","sources":["static/logo.png"],"sourcesContent":["export default \"__VITE_ASSET__46719607__\""],"names":[],"mappings":";AAAA,MAAe,aAAA;;"}
\ No newline at end of file
This diff is collapsed.
{"version":3,"file":"brand.js","sources":["pages/brand/brand.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/huangtao/Duiba/project/20250528_FHQ1/pages/brand/brand.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;AACA,GAAG,WAAW,eAAe;"}
\ No newline at end of file
{"version":3,"file":"index.js","sources":["pages/index/index.vue","pages/index/index.vue?type=page"],"sourcesContent":["<template>\r\n\t<view class=\"content\">\r\n\t\t<image class=\"logo\" src=\"/static/logo.png\"></image>\r\n\t\t<view class=\"text-area\">\r\n\t\t\t<text class=\"title\">{{title}}</text>\n\t\t\t\n\t\t\t<textarea name=\"\" id=\"\" cols=\"30\" rows=\"10\"></textarea>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\texport default {\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\ttitle: 'Hello'\r\n\t\t\t}\r\n\t\t},\r\n\t\tonLoad() {\r\n\r\n\t\t},\r\n\t\tmethods: {\r\n\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.content {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t}\r\n\r\n\t.logo {\r\n\t\theight: 200rpx;\r\n\t\twidth: 200rpx;\r\n\t\tmargin-top: 200rpx;\r\n\t\tmargin-left: auto;\r\n\t\tmargin-right: auto;\r\n\t\tmargin-bottom: 50rpx;\r\n\t}\r\n\r\n\t.text-area {\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: center;\r\n\t}\r\n\r\n\t.title {\r\n\t\tfont-size: 36rpx;\r\n\t\tcolor: #8f8f94;\r\n\t}\r\n</style>\n","import MiniProgramPage from '/Users/huangtao/Duiba/project/20250528_FHQ1/pages/index/index.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;AAYC,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO;AAAA,MACN,OAAO;AAAA,IACR;AAAA,EACA;AAAA,EACD,SAAS;AAAA,EAER;AAAA,EACD,SAAS,CAET;AACD;;;;;;;;ACvBD,GAAG,WAAW,eAAe;"}
\ No newline at end of file
{"version":3,"file":"integral.js","sources":["pages/integral/integral.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/huangtao/Duiba/project/20250528_FHQ1/pages/integral/integral.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;AACA,GAAG,WAAW,eAAe;"}
\ No newline at end of file
{"version":3,"file":"my.js","sources":["pages/my/my.vue?type=page"],"sourcesContent":["import MiniProgramPage from '/Users/huangtao/Duiba/project/20250528_FHQ1/pages/my/my.vue'\nwx.createPage(MiniProgramPage)"],"names":[],"mappings":";;;;;;;AACA,GAAG,WAAW,eAAe;"}
\ No newline at end of file
"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const common_vendor = require("./common/vendor.js");
if (!Math) {
"./pages/index/index.js";
"./pages/brand/brand.js";
"./pages/integral/integral.js";
"./pages/my/my.js";
}
const _sfc_main = {
onLaunch: function() {
common_vendor.index.__f__("log", "at App.vue:4", "App Launch");
},
onShow: function() {
common_vendor.index.__f__("log", "at App.vue:7", "App Show");
},
onHide: function() {
common_vendor.index.__f__("log", "at App.vue:10", "App Hide");
}
};
function createApp() {
const app = common_vendor.createSSRApp(_sfc_main);
return {
app
};
}
createApp().app.mount("#app");
exports.createApp = createApp;
//# sourceMappingURL=../.sourcemap/mp-weixin/app.js.map
{
"pages": [
"pages/index/index",
"pages/brand/brand",
"pages/integral/integral",
"pages/my/my"
],
"window": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#FF0000",
"backgroundColor": "#FFFFFF",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/brand/brand",
"text": "品牌"
},
{
"pagePath": "pages/integral/integral",
"text": "积分"
},
{
"pagePath": "pages/my/my",
"text": "我的"
}
]
},
"usingComponents": {}
}
\ No newline at end of file
/*每个页面公共css */
page{--status-bar-height:25px;--top-window-height:0px;--window-top:0px;--window-bottom:0px;--window-left:0px;--window-right:0px;--window-magin:0px}[data-c-h="true"]{display: none !important;}
\ No newline at end of file
"use strict";
const _imports_0 = "/static/logo.png";
exports._imports_0 = _imports_0;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/assets.js.map
This diff is collapsed.
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {};
function _sfc_render(_ctx, _cache) {
return {};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/brand/brand.js.map
{
"navigationBarTitleText": "品牌",
"usingComponents": {}
}
\ No newline at end of file
<view></view>
\ No newline at end of file
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_assets = require("../../common/assets.js");
const _sfc_main = {
data() {
return {
title: "Hello"
};
},
onLoad() {
},
methods: {}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_assets._imports_0,
b: common_vendor.t($data.title)
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/index/index.js.map
{
"navigationBarTitleText": "首页",
"usingComponents": {}
}
\ No newline at end of file
<view class="content"><image class="logo" src="{{a}}"></image><view class="text-area"><text class="title">{{b}}</text><textarea name="" id="" cols="30" rows="10"></textarea></view></view>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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