2025年04月16日18:15:16
This commit is contained in:
commit
d2ffa879f8
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
1
.env.development
Normal file
1
.env.development
Normal file
@ -0,0 +1 @@
|
||||
TARO_APP_API=""
|
1
.env.production
Normal file
1
.env.production
Normal file
@ -0,0 +1 @@
|
||||
TARO_APP_API=""
|
7
.eslintrc
Normal file
7
.eslintrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": ["taro/react"],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "off",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
}
|
||||
}
|
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
dist/
|
||||
deploy_versions/
|
||||
.temp/
|
||||
.rn_temp/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
.swc
|
34
babel.config.js
Normal file
34
babel.config.js
Normal file
@ -0,0 +1,34 @@
|
||||
// babel-preset-taro 更多选项和默认值:
|
||||
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
"taro",
|
||||
{
|
||||
framework: "react",
|
||||
ts: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"import",
|
||||
{
|
||||
libraryName: "@taroify/core",
|
||||
libraryDirectory: "",
|
||||
style: true,
|
||||
},
|
||||
"@taroify/core",
|
||||
],
|
||||
[
|
||||
"import",
|
||||
{
|
||||
libraryName: "@taroify/icons",
|
||||
libraryDirectory: "",
|
||||
camel2DashComponentName: false,
|
||||
style: () => "@taroify/icons/style",
|
||||
},
|
||||
"@taroify/icons",
|
||||
],
|
||||
],
|
||||
};
|
13
config/dev.js
Normal file
13
config/dev.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default {
|
||||
env: {
|
||||
NODE_ENV: '"development"'
|
||||
},
|
||||
logger: {
|
||||
quiet: false,
|
||||
stats: true
|
||||
},
|
||||
mini: {
|
||||
TARO_APP_API: 'prod.js'
|
||||
},
|
||||
h5: {}
|
||||
}
|
116
config/index.js
Normal file
116
config/index.js
Normal file
@ -0,0 +1,116 @@
|
||||
import { defineConfig } from '@tarojs/cli'
|
||||
|
||||
import devConfig from './dev'
|
||||
import prodConfig from './prod'
|
||||
|
||||
// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
|
||||
const path = require('path')
|
||||
export default defineConfig(async (merge, { command, mode }) => {
|
||||
const baseConfig = {
|
||||
alias: {
|
||||
'@': require('path').resolve(__dirname, '../src'),
|
||||
'@utils': require('path').resolve(__dirname, '../src/utils'),
|
||||
'@components': require('path').resolve(__dirname, '../src/components'),
|
||||
'@images': require('path').resolve(__dirname, '../src/images'),
|
||||
'@src': require('path').resolve(__dirname, '../src/src'),
|
||||
'@api': require('path').resolve(__dirname, '../src/api'),
|
||||
'@config': require('path').resolve(__dirname, '../src/config'),
|
||||
},
|
||||
projectName: 'myApp',
|
||||
date: '2023-8-18',
|
||||
designWidth: 750,
|
||||
deviceRatio: {
|
||||
640: 2.34 / 2,
|
||||
750: 1,
|
||||
375: 2,
|
||||
828: 1.81 / 2
|
||||
},
|
||||
sourceRoot: 'src',
|
||||
outputRoot: 'dist',
|
||||
plugins: [],
|
||||
defineConstants: {
|
||||
'process.env': {
|
||||
NODE_ENV: process.env.NODE_ENV // 将属性转化为全局变量,让代码中可以正常访问
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
patterns: [],
|
||||
options: {}
|
||||
},
|
||||
framework: 'react',
|
||||
compiler: 'webpack5',
|
||||
cache: {
|
||||
enable: false // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
|
||||
},
|
||||
mini: {
|
||||
postcss: {
|
||||
pxtransform: {
|
||||
enable: true,
|
||||
config: {}
|
||||
},
|
||||
url: {
|
||||
enable: true,
|
||||
config: {
|
||||
limit: 1024 // 设定转换尺寸上限
|
||||
}
|
||||
},
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
config: {
|
||||
namingPattern: 'module', // 转换模式,取值为 global/module
|
||||
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
miniCssExtractPluginOption: {
|
||||
ignoreOrder: true
|
||||
},
|
||||
webpackChain(chain) {
|
||||
chain.merge({
|
||||
module: {}
|
||||
})
|
||||
}
|
||||
},
|
||||
h5: {
|
||||
publicPath: '/',
|
||||
staticDirectory: 'static',
|
||||
output: {
|
||||
filename: 'js/[name].[hash:8].js',
|
||||
chunkFilename: 'js/[name].[chunkhash:8].js'
|
||||
},
|
||||
miniCssExtractPluginOption: {
|
||||
ignoreOrder: true,
|
||||
filename: 'css/[name].[hash].css',
|
||||
chunkFilename: 'css/[name].[chunkhash].css'
|
||||
},
|
||||
postcss: {
|
||||
autoprefixer: {
|
||||
enable: true,
|
||||
config: {}
|
||||
},
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
config: {
|
||||
namingPattern: 'module', // 转换模式,取值为 global/module
|
||||
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
rn: {
|
||||
appName: 'taroDemo',
|
||||
postcss: {
|
||||
cssModules: {
|
||||
enable: false // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// 本地开发构建配置(不混淆压缩)
|
||||
return merge({}, baseConfig, devConfig)
|
||||
}
|
||||
|
||||
// 生产构建配置(默认开启压缩混淆等)
|
||||
return merge({}, baseConfig, prodConfig)
|
||||
})
|
36
config/prod.js
Normal file
36
config/prod.js
Normal file
@ -0,0 +1,36 @@
|
||||
export default {
|
||||
mini: {
|
||||
TARO_APP_API: 'prod.js'
|
||||
},
|
||||
env: {
|
||||
NODE_ENV: '"production"'
|
||||
},
|
||||
h5: {
|
||||
/**
|
||||
* WebpackChain 插件配置
|
||||
* @docs https://github.com/neutrinojs/webpack-chain
|
||||
*/
|
||||
// webpackChain (chain) {
|
||||
// /**
|
||||
// * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。
|
||||
// * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer
|
||||
// */
|
||||
// chain.plugin('analyzer')
|
||||
// .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
|
||||
// /**
|
||||
// * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。
|
||||
// * @docs https://github.com/chrisvfritz/prerender-spa-plugin
|
||||
// */
|
||||
// const path = require('path')
|
||||
// const Prerender = require('prerender-spa-plugin')
|
||||
// const staticDir = path.join(__dirname, '..', 'dist')
|
||||
// chain
|
||||
// .plugin('prerender')
|
||||
// .use(new Prerender({
|
||||
// staticDir,
|
||||
// routes: [ '/pages/index/index' ],
|
||||
// postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') })
|
||||
// }))
|
||||
// }
|
||||
}
|
||||
}
|
10
iconfont.json
Normal file
10
iconfont.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"symbol_url": "http://at.alicdn.com/t/c/font_4676378_sj2smiyxso7.js",
|
||||
"save_dir": "./src/components/iconfont",
|
||||
"use_typescript": false,
|
||||
"platforms": ["weapp"],
|
||||
"use_rpx": true,
|
||||
"trim_icon_prefix": "",
|
||||
"default_icon_size": 18,
|
||||
"design_width": 750
|
||||
}
|
6
jest.config.js
Normal file
6
jest.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
const defineJestConfig = require('@tarojs/test-utils-react/dist/jest.js').default
|
||||
|
||||
module.exports = defineJestConfig({
|
||||
testEnvironment: 'jsdom',
|
||||
testMatch: ['<rootDir>/__tests__/?(*.)+(spec|test).[jt]s?(x)']
|
||||
})
|
15
jsconfig.json
Normal file
15
jsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/components/*": ["./src/components/*"],
|
||||
"@/utils/*": ["./src/utils/*"],
|
||||
"@/assets/*": ["./src/assets/*"],
|
||||
"@/images/*": ["./src/images/*"],
|
||||
"@/api/*": ["./src/api/*"],
|
||||
"@/store/*": ["./src/store/*"],
|
||||
"@/baseRouter/*": ["./src/baseRouter/*"],
|
||||
"@/src/*": ["./src/*"],
|
||||
}
|
||||
}
|
||||
}
|
1
loaders/injectComponent/index.d.ts
vendored
Normal file
1
loaders/injectComponent/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default function (source: string): string;
|
8
loaders/injectComponent/index.js
Normal file
8
loaders/injectComponent/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./taro-inject-component-loader.cjs.production.min.js')
|
||||
} else {
|
||||
module.exports = require('./taro-inject-component-loader.cjs.development.js')
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
||||
|
||||
var generate = _interopDefault(require('@babel/generator'));
|
||||
var traverse = _interopDefault(require('@babel/traverse'));
|
||||
var utils = _interopDefault(require('@babel/types'));
|
||||
var parser = require('@babel/parser');
|
||||
var loaderUtils = require('loader-utils');
|
||||
var schemaUtils = require('schema-utils');
|
||||
|
||||
var schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
importPath: {
|
||||
type: 'string'
|
||||
},
|
||||
isPage: {
|
||||
"instanceof": 'Function'
|
||||
},
|
||||
componentName: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
};
|
||||
function index (source) {
|
||||
// @ts-ignore
|
||||
var webpackEnv = this;
|
||||
var options = loaderUtils.getOptions(webpackEnv);
|
||||
schemaUtils.validate(schema, options, {
|
||||
name: 'taro-inject-component-loader'
|
||||
});
|
||||
|
||||
var _ref = options || {},
|
||||
_ref$importPath = _ref.importPath,
|
||||
importPath = _ref$importPath === void 0 ? '' : _ref$importPath,
|
||||
_ref$componentName = _ref.componentName,
|
||||
componentName = _ref$componentName === void 0 ? 'WebpackInjected' : _ref$componentName,
|
||||
_ref$isPage = _ref.isPage,
|
||||
isPage = _ref$isPage === void 0 ? defaultJudgePage : _ref$isPage; // 获取原始文件地址
|
||||
|
||||
|
||||
var filePath = webpackEnv.resourcePath;
|
||||
|
||||
if (typeof isPage === 'function' && isPage(filePath)) {
|
||||
// 生成 AST
|
||||
var ast = parser.parse(source, {
|
||||
sourceType: 'module',
|
||||
plugins: ['jsx', 'typescript', 'classProperties']
|
||||
}); // 如果有导入申明,则默认表示已手动导入了组件
|
||||
|
||||
var insert = false; // 保存所有顶层的声明
|
||||
|
||||
var declarations = new Map();
|
||||
traverse(ast, {
|
||||
// 查找是否有导入
|
||||
ImportDeclaration: function ImportDeclaration(path) {
|
||||
if (path.node.source.value === importPath) {
|
||||
insert = true;
|
||||
}
|
||||
},
|
||||
// 收集页面文件里的所有申明
|
||||
// 类组件
|
||||
ClassDeclaration: function ClassDeclaration(path) {
|
||||
// 如果不是顶层的申明,则直接返回
|
||||
if (path.parent.type !== 'Program') return;
|
||||
var type = path.node.type;
|
||||
var name = path.node.id.name;
|
||||
declarations.set(name, type);
|
||||
},
|
||||
// 函数申明
|
||||
FunctionDeclaration: function FunctionDeclaration(path) {
|
||||
var _path$node$id;
|
||||
|
||||
// 如果不是顶层的申明,则直接返回
|
||||
if (path.parent.type !== 'Program') return;
|
||||
var type = path.node.type;
|
||||
var name = (_path$node$id = path.node.id) == null ? void 0 : _path$node$id.name;
|
||||
if (!name) return;
|
||||
declarations.set(name, type);
|
||||
},
|
||||
// 表达式申明
|
||||
VariableDeclaration: function VariableDeclaration(path) {
|
||||
// 如果不是顶层的申明,则直接返回
|
||||
if (path.parent.type !== 'Program') return;
|
||||
path.node.declarations.forEach(function (declaration) {
|
||||
var _declaration$init, _declaration$init3, _declaration$init4;
|
||||
|
||||
// const a = () => {}
|
||||
if (((_declaration$init = declaration.init) == null ? void 0 : _declaration$init.type) === 'ArrowFunctionExpression') {
|
||||
var _declaration$init2, _declaration$id;
|
||||
|
||||
var type = (_declaration$init2 = declaration.init) == null ? void 0 : _declaration$init2.type;
|
||||
var name = (_declaration$id = declaration.id) == null ? void 0 : _declaration$id.name;
|
||||
declarations.set(name, type);
|
||||
} // const a = function(){}
|
||||
|
||||
|
||||
if (((_declaration$init3 = declaration.init) == null ? void 0 : _declaration$init3.type) === 'FunctionExpression') {
|
||||
var _type = declaration.init.type;
|
||||
var _name = declaration.id.name;
|
||||
declarations.set(_name, _type);
|
||||
} // const a = class {}
|
||||
|
||||
|
||||
if (((_declaration$init4 = declaration.init) == null ? void 0 : _declaration$init4.type) === 'ClassExpression') {
|
||||
var _type2 = declaration.init.type;
|
||||
var _name2 = declaration.id.name;
|
||||
declarations.set(_name2, _type2);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!insert) {
|
||||
// 记录组件插入状态
|
||||
var state = {
|
||||
importedDeclaration: false,
|
||||
importedComponent: false
|
||||
};
|
||||
traverse(ast, {
|
||||
// 添加申明
|
||||
ImportDeclaration: function ImportDeclaration(path) {
|
||||
if (!state.importedDeclaration) {
|
||||
state.importedDeclaration = true;
|
||||
path.insertBefore(utils.importDeclaration([utils.importDefaultSpecifier(utils.identifier('' + componentName))], utils.stringLiteral('' + importPath)));
|
||||
}
|
||||
},
|
||||
// 默认导出的为页面组件
|
||||
ExportDefaultDeclaration: function ExportDefaultDeclaration(path) {
|
||||
// 如果默认导出的是函数
|
||||
if (path.node.declaration.type === 'FunctionDeclaration') {
|
||||
var mainFnBody = path.node.declaration.body.body;
|
||||
var length = mainFnBody.length;
|
||||
var last = mainFnBody[length - 1];
|
||||
insertComponent(last, '' + componentName, state);
|
||||
} // 默认导出箭头函数
|
||||
|
||||
|
||||
if (path.node.declaration.type === 'ArrowFunctionExpression') {
|
||||
// export default () => { return <View></View> }
|
||||
if (path.node.declaration.body.type === 'BlockStatement') {
|
||||
var _mainFnBody = path.node.declaration.body.body;
|
||||
var _length = _mainFnBody.length;
|
||||
var _last = _mainFnBody[_length - 1];
|
||||
insertComponent(_last, '' + componentName, state);
|
||||
} else {
|
||||
// export default () => <View></View>
|
||||
insertComponent(path.node.declaration.body, '' + componentName, state);
|
||||
}
|
||||
} // 默认导出类
|
||||
|
||||
|
||||
if (path.node.declaration.type === 'ClassDeclaration') {
|
||||
traverse(path.node, {
|
||||
ClassMethod: function ClassMethod(path) {
|
||||
if (path.node.key.name === 'render') {
|
||||
var body = path.node.body.body || [];
|
||||
var _last2 = body[body.length - 1];
|
||||
insertComponent(_last2, '' + componentName, state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, path.scope, path);
|
||||
} // 如果默认导出的是一个申明
|
||||
|
||||
|
||||
if (path.node.declaration.type === "Identifier") {
|
||||
var name = path.node.declaration.name;
|
||||
var componentType = declarations.get(name);
|
||||
traverse(path.parent, {
|
||||
FunctionDeclaration: function FunctionDeclaration(path) {
|
||||
var _path$node$id2, _path$node, _path$node$body;
|
||||
|
||||
if (((_path$node$id2 = path.node.id) == null ? void 0 : _path$node$id2.name) !== name) return;
|
||||
var mainFnBody = (_path$node = path.node) == null ? void 0 : (_path$node$body = _path$node.body) == null ? void 0 : _path$node$body.body;
|
||||
var length = mainFnBody.length;
|
||||
var last = mainFnBody[length - 1];
|
||||
insertComponent(last, '' + componentName, state);
|
||||
},
|
||||
ClassDeclaration: function ClassDeclaration(path) {
|
||||
if (path.node.id.name !== name) return;
|
||||
traverse(path.node, {
|
||||
ClassMethod: function ClassMethod(path) {
|
||||
var _path$node$key;
|
||||
|
||||
if (((_path$node$key = path.node.key) == null ? void 0 : _path$node$key.name) !== 'render') return;
|
||||
var body = path.node.body.body || [];
|
||||
var last = body[body.length - 1];
|
||||
insertComponent(last, '' + componentName, state);
|
||||
}
|
||||
}, path.scope, path);
|
||||
},
|
||||
VariableDeclarator: function VariableDeclarator(path) {
|
||||
if (path.node.id.type !== 'Identifier') return;
|
||||
if (path.node.id.name !== name) return;
|
||||
if (!path.node.init) return;
|
||||
if (path.node.init.type !== componentType) return;
|
||||
|
||||
if (path.node.init.type === 'FunctionExpression') {
|
||||
var _mainFnBody2 = path.node.init.body.body;
|
||||
var _length2 = _mainFnBody2.length;
|
||||
var _last3 = _mainFnBody2[_length2 - 1];
|
||||
insertComponent(_last3, '' + componentName, state);
|
||||
}
|
||||
|
||||
if (path.node.init.type === 'ClassExpression') {
|
||||
traverse(path.node, {
|
||||
ClassMethod: function ClassMethod(path) {
|
||||
if (path.node.key.name !== 'render') return;
|
||||
var body = path.node.body.body || [];
|
||||
var last = body[body.length - 1];
|
||||
insertComponent(last, '' + componentName, state);
|
||||
}
|
||||
}, path.scope, path);
|
||||
}
|
||||
|
||||
if (path.node.init.type === 'ArrowFunctionExpression') {
|
||||
// const A = () => {}
|
||||
// export default A
|
||||
if (path.node.init.body.type == 'BlockStatement') {
|
||||
var _mainFnBody3 = path.node.init.body.body;
|
||||
var _length3 = _mainFnBody3.length;
|
||||
var _last4 = _mainFnBody3[_length3 - 1];
|
||||
insertComponent(_last4, '' + componentName, state);
|
||||
} else {
|
||||
// const A = () => <div></div>
|
||||
// export default A
|
||||
insertComponent(path.node.init.body, '' + componentName, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!state.importedComponent) {
|
||||
webpackEnv.emitWarning("\u9875\u9762: " + filePath + " \u6CE8\u5165\u7EC4\u4EF6\u5931\u8D25\uFF0C\u5EFA\u8BAE\u624B\u52A8\u5F15\u5165\u7EC4\u4EF6\u3002\u7EC4\u4EF6\u6CE8\u5165\u9650\u5236\u8BF7\u67E5\u9605: https://github.com/xdoer/taro-inject-component-loader");
|
||||
}
|
||||
|
||||
if (!state.importedDeclaration) {
|
||||
webpackEnv.emitWarning("\u9875\u9762: " + filePath + " \u6CE8\u5165\u5BFC\u5165\u7533\u660E\u5931\u8D25\uFF0C\u5EFA\u8BAE\u624B\u52A8\u5F15\u5165\u7EC4\u4EF6\u3002\u7EC4\u4EF6\u6CE8\u5165\u9650\u5236\u8BF7\u67E5\u9605: https://github.com/xdoer/taro-inject-component-loader");
|
||||
}
|
||||
|
||||
source = generate(ast).code;
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
function createElement(name) {
|
||||
var reactIdentifier = utils.identifier('React');
|
||||
var createElementIdentifier = utils.identifier('createElement');
|
||||
var callee = utils.memberExpression(reactIdentifier, createElementIdentifier);
|
||||
return utils.callExpression(callee, [utils.identifier(name)]);
|
||||
}
|
||||
|
||||
function createJSX(name) {
|
||||
return utils.jSXElement(utils.jSXOpeningElement(utils.jsxIdentifier('' + name), [], true), null, [], true);
|
||||
}
|
||||
|
||||
function insertComponent(node, componentName, state) {
|
||||
if ((node == null ? void 0 : node.type) === 'ReturnStatement') {
|
||||
var _node$argument, _node$argument$callee, _node$argument$callee2, _node$argument2;
|
||||
|
||||
// createElement
|
||||
if (((_node$argument = node.argument) == null ? void 0 : (_node$argument$callee = _node$argument.callee) == null ? void 0 : (_node$argument$callee2 = _node$argument$callee.property) == null ? void 0 : _node$argument$callee2.name) === 'createElement' && !state.importedComponent) {
|
||||
state.importedComponent = true;
|
||||
var reactCreateArguments = node.argument.arguments;
|
||||
reactCreateArguments.push(createElement(componentName));
|
||||
} // JSX
|
||||
|
||||
|
||||
if (((_node$argument2 = node.argument) == null ? void 0 : _node$argument2.type) === 'JSXElement' && !state.importedComponent) {
|
||||
state.importedComponent = true;
|
||||
node.argument.children.push(createJSX(componentName));
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type === 'JSXElement' && !state.importedComponent) {
|
||||
state.importedComponent = true;
|
||||
node.children.push(createJSX(componentName));
|
||||
}
|
||||
}
|
||||
|
||||
function defaultJudgePage(filePath) {
|
||||
// 兼容 windows 路径
|
||||
var formatFilePath = filePath.replace(/\\/g, '/');
|
||||
return /(package-.+\/)?pages\/[A-Za-z0-9-]+\/index\.[tj]sx$/.test(formatFilePath);
|
||||
}
|
||||
|
||||
exports.default = index;
|
||||
//# sourceMappingURL=taro-inject-component-loader.cjs.development.js.map
|
File diff suppressed because one or more lines are too long
2
loaders/injectComponent/taro-inject-component-loader.cjs.production.min.js
vendored
Normal file
2
loaders/injectComponent/taro-inject-component-loader.cjs.production.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0});var n=e(require("@babel/generator")),t=e(require("@babel/traverse")),o=e(require("@babel/types")),i=require("@babel/parser"),r=require("loader-utils"),a=require("schema-utils"),d={type:"object",properties:{importPath:{type:"string"},isPage:{instanceof:"Function"},componentName:{type:"string"}},additionalProperties:!1};function l(e){return o.jSXElement(o.jSXOpeningElement(o.jsxIdentifier(""+e),[],!0),null,[],!0)}function p(e,n,t){var i,r,a,d,p,c,s,u;"ReturnStatement"===(null==e?void 0:e.type)&&("createElement"!==(null==(i=e.argument)||null==(r=i.callee)||null==(a=r.property)?void 0:a.name)||t.importedComponent||(t.importedComponent=!0,e.argument.arguments.push((p=n,c=o.identifier("React"),s=o.identifier("createElement"),u=o.memberExpression(c,s),o.callExpression(u,[o.identifier(p)])))),"JSXElement"!==(null==(d=e.argument)?void 0:d.type)||t.importedComponent||(t.importedComponent=!0,e.argument.children.push(l(n)))),"JSXElement"!==e.type||t.importedComponent||(t.importedComponent=!0,e.children.push(l(n)))}function c(e){var n=e.replace(/\\/g,"/");return/(package-.+\/)?pages\/[A-Za-z0-9-]+\/index\.[tj]sx$/.test(n)}exports.default=function(e){var l=r.getOptions(this);a.validate(d,l,{name:"taro-inject-component-loader"});var s=l||{},u=s.importPath,m=void 0===u?"":u,y=s.componentName,f=void 0===y?"WebpackInjected":y,v=s.isPage,b=void 0===v?c:v,g=this.resourcePath;if("function"==typeof b&&b(g)){var h=i.parse(e,{sourceType:"module",plugins:["jsx","typescript","classProperties"]}),x=!1,D=new Map;if(t(h,{ImportDeclaration:function(e){e.node.source.value===m&&(x=!0)},ClassDeclaration:function(e){"Program"===e.parent.type&&D.set(e.node.id.name,e.node.type)},FunctionDeclaration:function(e){var n;if("Program"===e.parent.type){var t=null==(n=e.node.id)?void 0:n.name;t&&D.set(t,e.node.type)}},VariableDeclaration:function(e){"Program"===e.parent.type&&e.node.declarations.forEach((function(e){var n,t,o;if("ArrowFunctionExpression"===(null==(n=e.init)?void 0:n.type)){var i,r,a=null==(i=e.init)?void 0:i.type,d=null==(r=e.id)?void 0:r.name;D.set(d,a)}"FunctionExpression"===(null==(t=e.init)?void 0:t.type)&&D.set(e.id.name,e.init.type),"ClassExpression"===(null==(o=e.init)?void 0:o.type)&&D.set(e.id.name,e.init.type)}))}}),!x){var E={importedDeclaration:!1,importedComponent:!1};t(h,{ImportDeclaration:function(e){E.importedDeclaration||(E.importedDeclaration=!0,e.insertBefore(o.importDeclaration([o.importDefaultSpecifier(o.identifier(""+f))],o.stringLiteral(""+m))))},ExportDefaultDeclaration:function(e){if("FunctionDeclaration"===e.node.declaration.type){var n=e.node.declaration.body.body;p(n[n.length-1],""+f,E)}if("ArrowFunctionExpression"===e.node.declaration.type)if("BlockStatement"===e.node.declaration.body.type){var o=e.node.declaration.body.body;p(o[o.length-1],""+f,E)}else p(e.node.declaration.body,""+f,E);if("ClassDeclaration"===e.node.declaration.type&&t(e.node,{ClassMethod:function(e){if("render"!==e.node.key.name);else{var n=e.node.body.body||[];p(n[n.length-1],""+f,E)}}},e.scope,e),"Identifier"===e.node.declaration.type){var i=e.node.declaration.name,r=D.get(i);t(e.parent,{FunctionDeclaration:function(e){var n,t,o;if((null==(n=e.node.id)?void 0:n.name)===i){var r=null==(t=e.node)||null==(o=t.body)?void 0:o.body;p(r[r.length-1],""+f,E)}},ClassDeclaration:function(e){e.node.id.name===i&&t(e.node,{ClassMethod:function(e){var n;if("render"===(null==(n=e.node.key)?void 0:n.name)){var t=e.node.body.body||[];p(t[t.length-1],""+f,E)}}},e.scope,e)},VariableDeclarator:function(e){if("Identifier"===e.node.id.type&&e.node.id.name===i&&e.node.init&&e.node.init.type===r){if("FunctionExpression"===e.node.init.type){var n=e.node.init.body.body;p(n[n.length-1],""+f,E)}if("ClassExpression"===e.node.init.type&&t(e.node,{ClassMethod:function(e){if("render"===e.node.key.name){var n=e.node.body.body||[];p(n[n.length-1],""+f,E)}}},e.scope,e),"ArrowFunctionExpression"===e.node.init.type)if("BlockStatement"==e.node.init.body.type){var o=e.node.init.body.body;p(o[o.length-1],""+f,E)}else p(e.node.init.body,""+f,E)}}})}}}),E.importedComponent||this.emitWarning("页面: "+g+" 注入组件失败,建议手动引入组件。组件注入限制请查阅: https://github.com/xdoer/taro-inject-component-loader"),E.importedDeclaration||this.emitWarning("页面: "+g+" 注入导入申明失败,建议手动引入组件。组件注入限制请查阅: https://github.com/xdoer/taro-inject-component-loader"),e=n(h).code}}return e};
|
||||
//# sourceMappingURL=taro-inject-component-loader.cjs.production.min.js.map
|
File diff suppressed because one or more lines are too long
68
loaders/taro-inject-component-loader/package.json
Normal file
68
loaders/taro-inject-component-loader/package.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"version": "2.1.0",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"homepage": "https://github.com/xdoer/taro-inject-component-loader",
|
||||
"bugs": {
|
||||
"url": "https://github.com/xdoer/taro-inject-component-loader/issues",
|
||||
"email": "gotoanything@foxmail.com"
|
||||
},
|
||||
"keywords": [
|
||||
"taro",
|
||||
"react",
|
||||
"loader",
|
||||
"webpack",
|
||||
"inject"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "tsdx watch --format cjs",
|
||||
"build": "tsdx build --format cjs",
|
||||
"test": "tsdx test",
|
||||
"lint": "tsdx lint",
|
||||
"prepare": "tsdx build --format cjs",
|
||||
"size": "size-limit",
|
||||
"analyze": "size-limit --why"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "tsdx lint"
|
||||
}
|
||||
},
|
||||
"name": "taro-inject-component-loader",
|
||||
"author": "xdoer",
|
||||
"module": "dist/taro-inject-component-loader.esm.js",
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "dist/taro-inject-component-loader.cjs.production.min.js",
|
||||
"limit": "10 KB"
|
||||
},
|
||||
{
|
||||
"path": "dist/taro-inject-component-loader.esm.js",
|
||||
"limit": "10 KB"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "^4.9.1",
|
||||
"@types/loader-utils": "^2.0.1",
|
||||
"@types/schema-utils": "^2.4.0",
|
||||
"husky": "^4.3.6",
|
||||
"size-limit": "^4.9.1",
|
||||
"tsdx": "^0.14.1",
|
||||
"tslib": "^2.0.3",
|
||||
"typescript": "^4.1.3"
|
||||
}
|
||||
}
|
295
loaders/taro-inject-component-loader/src/index.ts
Normal file
295
loaders/taro-inject-component-loader/src/index.ts
Normal file
@ -0,0 +1,295 @@
|
||||
import generate from '@babel/generator'
|
||||
import traverse from '@babel/traverse'
|
||||
import utils from '@babel/types'
|
||||
import { parse } from '@babel/parser'
|
||||
import { getOptions } from 'loader-utils'
|
||||
import { validate } from 'schema-utils'
|
||||
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
importPath: {
|
||||
type: 'string',
|
||||
},
|
||||
isPage: {
|
||||
instanceof: 'Function',
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
}
|
||||
|
||||
export default function (source: string) {
|
||||
// @ts-ignore
|
||||
const webpackEnv = this
|
||||
|
||||
const options = getOptions(webpackEnv)
|
||||
|
||||
validate(schema as any, options, { name: 'taro-inject-component-loader' })
|
||||
|
||||
const { importPath = '', componentName = 'WebpackInjected', isPage = defaultJudgePage } = options || {}
|
||||
|
||||
// 获取原始文件地址
|
||||
const filePath = webpackEnv.resourcePath
|
||||
|
||||
if (typeof isPage === 'function' && isPage(filePath)) {
|
||||
// 生成 AST
|
||||
const ast: any = parse(source, {
|
||||
sourceType: 'module',
|
||||
plugins: ['jsx', 'typescript', 'classProperties'],
|
||||
})
|
||||
|
||||
// 如果有导入申明,则默认表示已手动导入了组件
|
||||
let insert = false
|
||||
|
||||
// 保存所有顶层的声明
|
||||
const declarations = new Map()
|
||||
|
||||
traverse(ast, {
|
||||
// 查找是否有导入
|
||||
ImportDeclaration(path) {
|
||||
if (path.node.source.value === importPath) {
|
||||
insert = true
|
||||
}
|
||||
},
|
||||
|
||||
// 收集页面文件里的所有申明
|
||||
// 类组件
|
||||
ClassDeclaration(path) {
|
||||
// 如果不是顶层的申明,则直接返回
|
||||
if (path.parent.type !== 'Program') return
|
||||
|
||||
const type = path.node.type
|
||||
const name = path.node.id.name
|
||||
declarations.set(name, type)
|
||||
},
|
||||
|
||||
// 函数申明
|
||||
FunctionDeclaration(path) {
|
||||
// 如果不是顶层的申明,则直接返回
|
||||
if (path.parent.type !== 'Program') return
|
||||
|
||||
const type = path.node.type
|
||||
const name = path.node.id?.name
|
||||
if (!name) return
|
||||
|
||||
declarations.set(name, type)
|
||||
},
|
||||
|
||||
// 表达式申明
|
||||
VariableDeclaration(path) {
|
||||
// 如果不是顶层的申明,则直接返回
|
||||
if (path.parent.type !== 'Program') return
|
||||
|
||||
path.node.declarations.forEach((declaration: any) => {
|
||||
|
||||
// const a = () => {}
|
||||
if (declaration.init?.type === 'ArrowFunctionExpression') {
|
||||
const type = declaration.init?.type
|
||||
const name = declaration.id?.name
|
||||
declarations.set(name, type)
|
||||
}
|
||||
|
||||
// const a = function(){}
|
||||
if (declaration.init?.type === 'FunctionExpression') {
|
||||
const type = declaration.init.type
|
||||
const name = declaration.id.name
|
||||
declarations.set(name, type)
|
||||
}
|
||||
|
||||
// const a = class {}
|
||||
if (declaration.init?.type === 'ClassExpression') {
|
||||
const type = declaration.init.type
|
||||
const name = declaration.id.name
|
||||
declarations.set(name, type)
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
if (!insert) {
|
||||
// 记录组件插入状态
|
||||
const state = {
|
||||
importedDeclaration: false,
|
||||
importedComponent: false,
|
||||
}
|
||||
|
||||
traverse(ast, {
|
||||
// 添加申明
|
||||
ImportDeclaration(path) {
|
||||
if (!state.importedDeclaration) {
|
||||
state.importedDeclaration = true
|
||||
path.insertBefore(
|
||||
utils.importDeclaration(
|
||||
[
|
||||
utils.importDefaultSpecifier(utils.identifier('' + componentName)),
|
||||
],
|
||||
utils.stringLiteral('' + importPath),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
// 默认导出的为页面组件
|
||||
ExportDefaultDeclaration(path) {
|
||||
|
||||
// 如果默认导出的是函数
|
||||
if (path.node.declaration.type === 'FunctionDeclaration') {
|
||||
const mainFnBody = path.node.declaration.body.body
|
||||
const length = mainFnBody.length
|
||||
const last = mainFnBody[length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
}
|
||||
|
||||
// 默认导出箭头函数
|
||||
if (path.node.declaration.type === 'ArrowFunctionExpression') {
|
||||
// export default () => { return <View></View> }
|
||||
if (path.node.declaration.body.type === 'BlockStatement') {
|
||||
const mainFnBody = path.node.declaration.body.body
|
||||
const length = mainFnBody.length
|
||||
const last = mainFnBody[length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
} else {
|
||||
// export default () => <View></View>
|
||||
insertComponent(path.node.declaration.body, '' + componentName, state)
|
||||
}
|
||||
}
|
||||
|
||||
// 默认导出类
|
||||
if (path.node.declaration.type === 'ClassDeclaration') {
|
||||
traverse(path.node, {
|
||||
ClassMethod(path) {
|
||||
if ((path.node.key as any).name === 'render') {
|
||||
const body = path.node.body.body || []
|
||||
const last = body[body.length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
return
|
||||
}
|
||||
},
|
||||
}, path.scope, path)
|
||||
}
|
||||
|
||||
// 如果默认导出的是一个申明
|
||||
if (path.node.declaration.type === "Identifier") {
|
||||
const name = path.node.declaration.name
|
||||
const componentType = declarations.get(name)
|
||||
|
||||
traverse(path.parent, {
|
||||
FunctionDeclaration(path) {
|
||||
if (path.node.id?.name !== name) return
|
||||
const mainFnBody = path.node?.body?.body
|
||||
const length = mainFnBody.length
|
||||
const last = mainFnBody[length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
},
|
||||
ClassDeclaration(path) {
|
||||
if (path.node.id.name !== name) return
|
||||
traverse(path.node, {
|
||||
ClassMethod(path) {
|
||||
if ((path.node.key as any)?.name !== 'render') return
|
||||
const body = path.node.body.body || []
|
||||
const last = body[body.length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
},
|
||||
}, path.scope, path)
|
||||
},
|
||||
VariableDeclarator(path) {
|
||||
if (path.node.id.type !== 'Identifier') return
|
||||
if (path.node.id.name !== name) return
|
||||
if (!path.node.init) return
|
||||
|
||||
if (path.node.init.type !== componentType) return
|
||||
|
||||
if (path.node.init.type === 'FunctionExpression') {
|
||||
const mainFnBody = path.node.init.body.body
|
||||
const length = mainFnBody.length
|
||||
const last = mainFnBody[length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
}
|
||||
|
||||
if (path.node.init.type === 'ClassExpression') {
|
||||
traverse(path.node, {
|
||||
ClassMethod(path) {
|
||||
if ((path.node.key as any).name !== 'render') return
|
||||
const body = path.node.body.body || []
|
||||
const last = body[body.length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
},
|
||||
}, path.scope, path)
|
||||
}
|
||||
|
||||
if (path.node.init.type === 'ArrowFunctionExpression') {
|
||||
// const A = () => {}
|
||||
// export default A
|
||||
if (path.node.init.body.type == 'BlockStatement') {
|
||||
const mainFnBody = path.node.init.body.body
|
||||
const length = mainFnBody.length
|
||||
const last = mainFnBody[length - 1]
|
||||
insertComponent(last, '' + componentName, state)
|
||||
} else {
|
||||
// const A = () => <div></div>
|
||||
// export default A
|
||||
insertComponent(path.node.init.body, '' + componentName, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
if (!state.importedComponent) {
|
||||
webpackEnv.emitWarning(`页面: ${filePath} 注入组件失败,建议手动引入组件。组件注入限制请查阅: https://github.com/xdoer/taro-inject-component-loader`)
|
||||
|
||||
}
|
||||
if (!state.importedDeclaration) {
|
||||
webpackEnv.emitWarning(`页面: ${filePath} 注入导入申明失败,建议手动引入组件。组件注入限制请查阅: https://github.com/xdoer/taro-inject-component-loader`)
|
||||
}
|
||||
|
||||
source = generate(ast).code
|
||||
}
|
||||
}
|
||||
|
||||
return source
|
||||
}
|
||||
|
||||
|
||||
function createElement(name: string) {
|
||||
const reactIdentifier = utils.identifier('React')
|
||||
const createElementIdentifier = utils.identifier('createElement')
|
||||
const callee = utils.memberExpression(reactIdentifier, createElementIdentifier)
|
||||
return utils.callExpression(callee, [utils.identifier(name)])
|
||||
}
|
||||
|
||||
function createJSX(name: string) {
|
||||
return utils.jSXElement(
|
||||
utils.jSXOpeningElement(utils.jsxIdentifier('' + name), [], true),
|
||||
null,
|
||||
[],
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
function insertComponent(node: any, componentName: string, state: any) {
|
||||
if (node?.type === 'ReturnStatement') {
|
||||
// createElement
|
||||
if (node.argument?.callee?.property?.name === 'createElement' && !state.importedComponent) {
|
||||
state.importedComponent = true
|
||||
const reactCreateArguments = node.argument.arguments
|
||||
reactCreateArguments.push(createElement(componentName))
|
||||
}
|
||||
// JSX
|
||||
if (node.argument?.type === 'JSXElement' && !state.importedComponent) {
|
||||
state.importedComponent = true
|
||||
node.argument.children.push(createJSX(componentName))
|
||||
}
|
||||
}
|
||||
if (node.type === 'JSXElement' && !state.importedComponent) {
|
||||
node.children.push(createJSX(componentName))
|
||||
}
|
||||
}
|
||||
|
||||
function defaultJudgePage(filePath: string) {
|
||||
// 兼容 windows 路径
|
||||
const formatFilePath = filePath.replace(/\\/g, '/')
|
||||
return /(package-.+\/)?pages\/[A-Za-z0-9-]+\/index\.[tj]sx$/.test(formatFilePath)
|
||||
}
|
35
loaders/taro-inject-component-loader/tsconfig.json
Normal file
35
loaders/taro-inject-component-loader/tsconfig.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
// see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
|
||||
"include": ["src", "types"],
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"lib": ["dom", "esnext"],
|
||||
"importHelpers": true,
|
||||
// output .d.ts declaration files for consumers
|
||||
"declaration": true,
|
||||
// output .js.map sourcemap files for consumers
|
||||
"sourceMap": true,
|
||||
// match output dir to input dir. e.g. dist/index instead of dist/src/index
|
||||
"rootDir": "./src",
|
||||
// stricter type-checking for stronger correctness. Recommended by TS
|
||||
"strict": true,
|
||||
// linter checks for common issues
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
// noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
// use Node's module resolution algorithm, instead of the legacy TS one
|
||||
"moduleResolution": "node",
|
||||
// transpile JSX to React.createElement
|
||||
"jsx": "react",
|
||||
// interop between ESM and CJS modules. Recommended by TS
|
||||
"esModuleInterop": true,
|
||||
// significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
|
||||
"skipLibCheck": true,
|
||||
// error out if import and file system have a casing mismatch. Recommended by TS
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
// `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
|
||||
"noEmit": true,
|
||||
}
|
||||
}
|
95
package.json
Normal file
95
package.json
Normal file
@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "fyk_loudspeaker_box",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"templateInfo": {
|
||||
"name": "default",
|
||||
"typescript": false,
|
||||
"css": "less"
|
||||
},
|
||||
"scripts": {
|
||||
"build:weapp": "taro build --type weapp",
|
||||
"build:swan": "taro build --type swan",
|
||||
"build:alipay": "taro build --type alipay",
|
||||
"build:tt": "taro build --type tt",
|
||||
"build:h5": "taro build --type h5",
|
||||
"build:rn": "taro build --type rn",
|
||||
"build:qq": "taro build --type qq",
|
||||
"build:jd": "taro build --type jd",
|
||||
"build:quickapp": "taro build --type quickapp",
|
||||
"dev:weapp": "npm run build:weapp -- --watch",
|
||||
"dev:swan": "npm run build:swan -- --watch",
|
||||
"dev:alipay": "npm run build:alipay -- --watch",
|
||||
"dev:tt": "npm run build:tt -- --watch",
|
||||
"dev:h5": "npm run build:h5 -- --watch",
|
||||
"dev:rn": "npm run build:rn -- --watch",
|
||||
"dev:qq": "npm run build:qq -- --watch",
|
||||
"dev:jd": "npm run build:jd -- --watch",
|
||||
"dev:quickapp": "npm run build:quickapp -- --watch",
|
||||
"test": "jest",
|
||||
"update-iconfont": "npx iconfont-taro"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 versions",
|
||||
"Android >= 4.1",
|
||||
"ios >= 8"
|
||||
],
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.5",
|
||||
"@taroify/core": "^0.6.4-alpha.0",
|
||||
"@tarojs/components": "3.6.13",
|
||||
"@tarojs/helper": "3.6.13",
|
||||
"@tarojs/plugin-framework-react": "3.6.13",
|
||||
"@tarojs/plugin-platform-alipay": "3.6.13",
|
||||
"@tarojs/plugin-platform-h5": "3.6.13",
|
||||
"@tarojs/plugin-platform-jd": "3.6.13",
|
||||
"@tarojs/plugin-platform-qq": "3.6.13",
|
||||
"@tarojs/plugin-platform-swan": "3.6.13",
|
||||
"@tarojs/plugin-platform-tt": "3.6.13",
|
||||
"@tarojs/plugin-platform-weapp": "3.6.13",
|
||||
"@tarojs/react": "3.6.13",
|
||||
"@tarojs/runtime": "3.6.13",
|
||||
"@tarojs/shared": "3.6.13",
|
||||
"@tarojs/taro": "3.6.13",
|
||||
"babel-plugin-import": "^1.13.8",
|
||||
"bignumber": "^1.1.0",
|
||||
"bignumber.js": "^9.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"crypto-js": "^3.3.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"immer": "^10.1.1",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"taro-react-echarts": "^1.2.2",
|
||||
"weapp-qrcode": "^1.0.0",
|
||||
"zustand": "^4.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.8.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
|
||||
"@tarojs/cli": "^3.6.19",
|
||||
"@tarojs/taro-loader": "3.6.13",
|
||||
"@tarojs/test-utils-react": "^0.1.1",
|
||||
"@tarojs/webpack5-runner": "3.6.13",
|
||||
"@types/jest": "^29.3.1",
|
||||
"@types/node": "^18.15.11",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/webpack-env": "^1.13.6",
|
||||
"babel-preset-taro": "3.6.13",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-taro": "3.6.13",
|
||||
"eslint-plugin-import": "^2.12.0",
|
||||
"eslint-plugin-react": "^7.8.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"jest": "^29.3.1",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"postcss": "^8.4.18",
|
||||
"react-refresh": "^0.11.0",
|
||||
"stylelint": "^14.4.0",
|
||||
"taro-iconfont-cli": "^3.3.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"webpack": "5.78.0"
|
||||
}
|
||||
}
|
16
project.config.json
Normal file
16
project.config.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"miniprogramRoot": "./dist",
|
||||
"projectname": "fyk_loudspeaker_box",
|
||||
"description": "",
|
||||
"appid": "wxbaa937103d585bc5",
|
||||
"setting": {
|
||||
"es6": true,
|
||||
"minified": true,
|
||||
"minifyWXSS": true,
|
||||
"postcss": true,
|
||||
"minifyWXML": true,
|
||||
"ignoreUploadUnusedFiles": true,
|
||||
"bigPackageSizeSupport": true
|
||||
},
|
||||
"compileType": "miniprogram"
|
||||
}
|
14
project.tt.json
Normal file
14
project.tt.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"miniprogramRoot": "./",
|
||||
"projectname": "fyk_loudspeaker_box",
|
||||
"appid": "wxbaa937103d585bc5",
|
||||
"setting": {
|
||||
"es6": true,
|
||||
"minified": true,
|
||||
"minifyWXSS": true,
|
||||
"postcss": true,
|
||||
"minifyWXML": true,
|
||||
"ignoreUploadUnusedFiles": true,
|
||||
"bigPackageSizeSupport": true
|
||||
}
|
||||
}
|
46
src/api/ajax.js
Normal file
46
src/api/ajax.js
Normal file
@ -0,0 +1,46 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
export const sendRequest = (url, method = 'GET', data = {}, contentType) => {
|
||||
const reqUrl = url.indexOf('http') == -1 ? `${process.env.TARO_APP_API}${url}` : url
|
||||
const token = Taro.getStorageSync('token') || ''
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
Taro.request({
|
||||
url: reqUrl,
|
||||
data: data,
|
||||
method: method,
|
||||
timeout: 15000,
|
||||
header: {
|
||||
...contentType,
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
success: function (res) {
|
||||
if (url.indexOf('http') == -1) {
|
||||
const data = res.data
|
||||
if (data.code == 200) {
|
||||
resolve(data.data)
|
||||
} else if (data.code == 901) {
|
||||
// Taro.reLaunch({ url: '/packageA/pages/login/index' })
|
||||
} else {
|
||||
Taro.showToast({ title: data.msg, icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
if (res.statusCode == 200) {
|
||||
resolve(res.data)
|
||||
} else {
|
||||
reject(res.data)
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: function (res) {
|
||||
console.log(JSON.stringify(res), `${process.env.TARO_APP_API}${url}`, 'Taro.request')
|
||||
reject(res)
|
||||
if (res.errno == '600003') return
|
||||
if (res.errMsg == 'request:fail timeout') {
|
||||
Taro.showToast({ title: '请求超时,请重新尝试', icon: 'none' })
|
||||
} else {
|
||||
Taro.showToast({ title: '服务器错误', icon: 'none' })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
6
src/api/index.js
Normal file
6
src/api/index.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { sendRequest } from "./ajax";
|
||||
|
||||
//通过微信授权码登录
|
||||
export function loginByJscode(params) {
|
||||
return sendRequest("/watch-api/user/loginByJscode", "POST", params);
|
||||
}
|
13
src/app.config.js
Normal file
13
src/app.config.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { useGlobalIconFont } from "./components/iconfont/helper";
|
||||
export default defineAppConfig({
|
||||
pages: [
|
||||
"pages/index/index",
|
||||
"pages/funList/index",
|
||||
],
|
||||
|
||||
window: {
|
||||
navigationBarBackgroundColor: "#fff",
|
||||
navigationBarTextStyle: "black",
|
||||
},
|
||||
usingComponents: Object.assign(useGlobalIconFont()),
|
||||
});
|
15
src/app.js
Normal file
15
src/app.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { useLaunch } from '@tarojs/taro'
|
||||
import './app.less'
|
||||
import Taro, { useDidHide, useDidShow } from '@tarojs/taro'
|
||||
import BLESDK from './utils/ble'
|
||||
|
||||
function App({ children }) {
|
||||
useLaunch(() => {
|
||||
BLESDK.openBluetoothAdapter()
|
||||
})
|
||||
|
||||
// children 是将要会渲染的页面
|
||||
return children
|
||||
}
|
||||
|
||||
export default App
|
10
src/app.less
Normal file
10
src/app.less
Normal file
@ -0,0 +1,10 @@
|
||||
.index {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
521
src/components/color-diy/index.jsx
Normal file
521
src/components/color-diy/index.jsx
Normal file
@ -0,0 +1,521 @@
|
||||
import { View } from '@tarojs/components'
|
||||
|
||||
import './index.less'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
export default function Index() {
|
||||
const [data, setData] = useState({
|
||||
boundaryData: [], // 元素边界信息
|
||||
gradient: '',
|
||||
hex: '#000000',
|
||||
rgba: {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
hsb: {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
},
|
||||
bgcolor: {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
point: [{
|
||||
top: 0, // 颜色盒子里位置信息
|
||||
left: 0
|
||||
}, {
|
||||
left: 0 // 颜色条
|
||||
}, {
|
||||
left: 0 // 透明度
|
||||
}],
|
||||
colorList: [{
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
}, {
|
||||
r: 233,
|
||||
g: 30,
|
||||
b: 99,
|
||||
a: 1
|
||||
}, {
|
||||
r: 156,
|
||||
g: 39,
|
||||
b: 176,
|
||||
a: 1
|
||||
}, {
|
||||
r: 103,
|
||||
g: 58,
|
||||
b: 183,
|
||||
a: 1
|
||||
}, {
|
||||
r: 12,
|
||||
g: 27,
|
||||
b: 230,
|
||||
a: 1
|
||||
}, {
|
||||
r: 63,
|
||||
g: 81,
|
||||
b: 181,
|
||||
a: 1
|
||||
}, {
|
||||
r: 33,
|
||||
g: 150,
|
||||
b: 243,
|
||||
a: 1
|
||||
}, {
|
||||
r: 3,
|
||||
g: 169,
|
||||
b: 244,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 188,
|
||||
b: 212,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 150,
|
||||
b: 136,
|
||||
a: 1
|
||||
}, {
|
||||
r: 76,
|
||||
g: 175,
|
||||
b: 80,
|
||||
a: 1
|
||||
}, {
|
||||
r: 50,
|
||||
g: 250,
|
||||
b: 3,
|
||||
a: 1
|
||||
}, {
|
||||
r: 139,
|
||||
g: 195,
|
||||
b: 74,
|
||||
a: 1
|
||||
}, {
|
||||
r: 205,
|
||||
g: 220,
|
||||
b: 57,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 235,
|
||||
b: 59,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 193,
|
||||
b: 7,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 152,
|
||||
b: 0,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 87,
|
||||
b: 34,
|
||||
a: 1
|
||||
}, {
|
||||
r: 121,
|
||||
g: 85,
|
||||
b: 72,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
}, {
|
||||
r: 158,
|
||||
g: 158,
|
||||
b: 158,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 255,
|
||||
b: 255,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0.5
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}]
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
show()
|
||||
}, [])
|
||||
|
||||
const show = () => {
|
||||
setData((state) => {
|
||||
state.hsb = rgbToHsb(data.rgba)
|
||||
state.hex = '#' + rgbToHex(data.rgba)
|
||||
return { ...state }
|
||||
})
|
||||
|
||||
wx.nextTick(() => {
|
||||
getBoundaryData();
|
||||
})
|
||||
}
|
||||
|
||||
// 获取元素边界值
|
||||
const getBoundaryData = () => {
|
||||
Taro.createSelectorQuery().selectAll('.range-box').boundingClientRect((data) => {
|
||||
if (!data || data.length === 0) {
|
||||
setTimeout(() => getBoundaryData(), 20)
|
||||
return
|
||||
}
|
||||
setData((state) => {
|
||||
state.boundaryData = data
|
||||
return { ...state }
|
||||
})
|
||||
}).exec()
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
const { rgba, hex } = data
|
||||
console.log({
|
||||
rgba: [rgba.r, rgba.g, rgba.b, rgba.a],
|
||||
hex
|
||||
});
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
|
||||
}
|
||||
|
||||
const touchstart = (e) => {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
setPointAndColor(pageX, pageY, index);
|
||||
}
|
||||
|
||||
const touchmove = (e) => {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
setPointAndColor(pageX, pageY, index);
|
||||
}
|
||||
|
||||
// 设置位置和颜色
|
||||
const setPointAndColor = (x, y, index) => {
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = data.boundaryData[index];
|
||||
// 颜色盒
|
||||
if (index == 0) {
|
||||
changeColorBox(x, y, index, top, left, width, height);
|
||||
}
|
||||
// 颜色条
|
||||
else if (index == 1) {
|
||||
changeColorHSB(x, y, index, top, left, width, height);
|
||||
}
|
||||
// 透明度
|
||||
else if (index == 2) {
|
||||
changeTransparency(x, y, index, top, left, width, height);
|
||||
}
|
||||
// rgb条
|
||||
else {
|
||||
changeColorRGB(x, y, index, top, left, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
// 点击操作颜色盒
|
||||
const changeColorBox = (x, y, index, top, left, width, height) => {
|
||||
let pointTop = Math.max(0, Math.min(parseInt(y - top), height));
|
||||
let pointLeft = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
// 设置颜色
|
||||
let hsb = {
|
||||
h: data.hsb.h,
|
||||
s: parseInt((100 * pointLeft) / width),
|
||||
b: parseInt(100 - (100 * pointTop) / height)
|
||||
}
|
||||
const rgb = hsbToRgb(hsb);
|
||||
let rgba = {
|
||||
r: rgb.r,
|
||||
g: rgb.g,
|
||||
b: rgb.b,
|
||||
a: data.rgba.a
|
||||
}
|
||||
|
||||
// 设置data
|
||||
setData((state) => {
|
||||
state.point[index] = {
|
||||
top: pointTop,
|
||||
left: pointLeft
|
||||
}
|
||||
state.hsb = hsb
|
||||
state.rgba = rgba
|
||||
state.hex = '#' + rgbToHex(rgb)
|
||||
return { ...state }
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 颜色条,饱和度
|
||||
const changeColorHSB = (x, y, index, top, left, width, height) => {
|
||||
let pointLeft = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
let hsb = {
|
||||
h: parseInt((360 * pointLeft) / width),
|
||||
s: 100,
|
||||
b: 100,
|
||||
}
|
||||
let rgb = hsbToRgb(hsb);
|
||||
let rgba = {
|
||||
r: rgb.r,
|
||||
g: rgb.g,
|
||||
b: rgb.b,
|
||||
a: data.rgba.a
|
||||
}
|
||||
setData((state) => {
|
||||
state.point[index].left = pointLeft
|
||||
state.hsb = hsb
|
||||
state.rgba = rgba
|
||||
state.hex = '#' + rgbToHex(rgb)
|
||||
state.bgcolor = hsbToRgb(hsb)
|
||||
return { ...state }
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 透明度
|
||||
const changeTransparency = (x, y, index, top, left, width, height) => {
|
||||
let pointLeft = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
setData((state) => {
|
||||
state.point[index].left = pointLeft
|
||||
state.rgba.a = (pointLeft / width).toFixed(1)
|
||||
return { ...state }
|
||||
})
|
||||
}
|
||||
|
||||
// rgb条
|
||||
const changeColorRGB = (x, y, index, top, left, width, height) => {
|
||||
let pointLeft = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
let pointValue = parseInt(pointLeft / width * 255);
|
||||
let rgba = {
|
||||
r: index == 3 ? pointValue : data.rgba.r,
|
||||
g: index == 4 ? pointValue : data.rgba.g,
|
||||
b: index == 5 ? pointValue : data.rgba.b,
|
||||
a: data.rgba.a
|
||||
}
|
||||
let hsb = rgbToHsb(rgba);
|
||||
// 计算颜色条的位置
|
||||
let hsb_left = parseInt((hsb.h * data.boundaryData[1].width) / 360);
|
||||
// 设置data
|
||||
setData((state) => {
|
||||
state.point[index].left = pointLeft
|
||||
state.point[1].left = hsb_left
|
||||
state.hsb = hsb
|
||||
state.rgba = rgba
|
||||
state.hex = '#' + rgbToHex(rgba)
|
||||
state.bgcolor = hsbToRgb(hsb)
|
||||
return { ...state }
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 常用颜色选择
|
||||
const selectColor = (event) => {
|
||||
setColorByOption(event.currentTarget.dataset.color)
|
||||
}
|
||||
|
||||
const setColorByOption = (optionColor) => {
|
||||
const {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
} = optionColor;
|
||||
let rgba = {
|
||||
r: r ? parseInt(r) : 0,
|
||||
g: g ? parseInt(g) : 0,
|
||||
b: b ? parseInt(b) : 0,
|
||||
a: a ? a : 0,
|
||||
};
|
||||
let hsb = rgbToHsb(rgba);
|
||||
|
||||
setData((state) => {
|
||||
state.rgba = rgba
|
||||
state.hsb = hsb
|
||||
state.hex = '#' + rgbToHex(rgba)
|
||||
state.bgcolor = hsbToRgb(hsb)
|
||||
return { ...state }
|
||||
})
|
||||
|
||||
changePoint();
|
||||
}
|
||||
|
||||
const changePoint = () => {
|
||||
const [a, b, c,] = data.boundaryData;
|
||||
let point = [{
|
||||
top: parseInt((100 - data.hsb.b) * a.height / 100), // 颜色盒子
|
||||
left: parseInt(data.hsb.s * a.width / 100)
|
||||
}, {
|
||||
left: data.hsb.h / 360 * b.width // 颜色条
|
||||
}, {
|
||||
left: data.rgba.a * c.width // 透明度
|
||||
}
|
||||
]
|
||||
setData((state) => {
|
||||
state.point = point
|
||||
return { ...state }
|
||||
})
|
||||
}
|
||||
|
||||
// rgb 转 hex
|
||||
const rgbToHex = (rgb) => {
|
||||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
||||
hex.map(function (str, i) {
|
||||
if (str.length == 1) {
|
||||
hex[i] = '0' + str;
|
||||
}
|
||||
});
|
||||
return hex.join('');
|
||||
}
|
||||
|
||||
// rgb 转 hsb
|
||||
const rgbToHsb = (rgb) => {
|
||||
let hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
let delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
} else hsb.h = -1;
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) hsb.h = 0;
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
}
|
||||
|
||||
// hsb 转 rgb 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
|
||||
const hsbToRgb = (hsb) => {
|
||||
let rgb = {};
|
||||
let h = Math.round(hsb.h);
|
||||
let s = Math.round((hsb.s * 255) / 100);
|
||||
let v = Math.round((hsb.b * 255) / 100);
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
let t1 = v;
|
||||
let t2 = ((255 - s) * v) / 255;
|
||||
let t3 = ((t1 - t2) * (h % 60)) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(rgb.r),
|
||||
g: Math.round(rgb.g),
|
||||
b: Math.round(rgb.b)
|
||||
};
|
||||
}
|
||||
const { bgcolor, point, rgba, colorList } = data
|
||||
return (
|
||||
<View className="cp-wrapper">
|
||||
<View className='cp-mask' onClick={close}></View>
|
||||
<View className="cp-box">
|
||||
<View className="cp-header">
|
||||
<View className="cp-header-button" onClick={close}>取消</View>
|
||||
<View className="cp-header-button cp-button-confirm" onClick={confirm}>确认</View>
|
||||
</View>
|
||||
<View className="cp-color-box" style={'background:' + ('rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')') + ';'}>
|
||||
<View className="cp-background range-box" data-index="0" onTouchStart={touchstart} onTouchMove={touchmove} >
|
||||
<View className="cp-color-mask"></View>
|
||||
<View className="cp-pointer" style={'top:' + (point[0].top - 8 + 'px') + ';' + ('left:' + (point[0].left - 8 + 'px') + ';')}></View>
|
||||
</View>
|
||||
</View>
|
||||
<View className="cp-control-box">
|
||||
<View className="cp-control-color">
|
||||
<View className="cp-control-color-content" style={'background:' + ('rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')') + ';'}></View>
|
||||
</View>
|
||||
<View className="cp-control-box-item">
|
||||
<View className="cp-controller range-box" data-index="1" onTouchStart={touchstart} onTouchMove={touchmove}>
|
||||
<View className="cp-hue">
|
||||
<View className="cp-circle" style={'left:' + (point[1].left - 12 + 'px') + ';'}></View>
|
||||
</View>
|
||||
</View>
|
||||
<View className="cp-controller range-box" data-index="2" onTouchStart={touchstart} onTouchMove={touchmove}>
|
||||
<View className="cp-transparency" style={'background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + '));'}>
|
||||
<View className="cp-circle" style={'left:' + (point[2].left - 12 + 'px') + ';'}></View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="cp-option">
|
||||
{
|
||||
colorList.map((item, index) => {
|
||||
return <View key={index}>
|
||||
<View className="cp-option-item">
|
||||
<View className="cp-option-item-content" data-color={item} style={'background:' + ('rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')') + ';'} onClick={selectColor}></View>
|
||||
</View>
|
||||
</View>
|
||||
})
|
||||
}
|
||||
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
339
src/components/color-diy/index.less
Normal file
339
src/components/color-diy/index.less
Normal file
@ -0,0 +1,339 @@
|
||||
.cp-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.cp-box {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 30rpx 0;
|
||||
padding-top: 0;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.cp-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 100rpx;
|
||||
border-bottom: 1px #eee solid;
|
||||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.cp-header-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 150rpx;
|
||||
height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
border: 2rpx solid #3963bc;
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
|
||||
.cp-button-confirm {
|
||||
background-color: #3963bc;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cp-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: -1;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.cp-color-box {
|
||||
position: relative;
|
||||
height: 400rpx;
|
||||
background: rgb(255, 0, 0);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin: 0 20rpx;
|
||||
margin-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cp-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.cp-color-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.cp-pointer {
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: -8px;
|
||||
z-index: 2;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px #fff solid;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cp-show-color {
|
||||
width: 100rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
.cp-control-box {
|
||||
margin-top: 50rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-left: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cp-control-color {
|
||||
flex-shrink: 0;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36rpx 36rpx;
|
||||
background-position: 0 0, 18rpx 18rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cp-control-color-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cp-control-box-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.cp-controller {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 32rpx 32rpx;
|
||||
background-position: 0 0, 16rpx 16rpx;
|
||||
}
|
||||
|
||||
.cp-hue {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
}
|
||||
|
||||
.cp-transparency {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
|
||||
}
|
||||
|
||||
.cp-circle {
|
||||
position: absolute;
|
||||
/* right: -10px; */
|
||||
top: -2px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.cp-rgb-control-box {
|
||||
margin-top: 20rpx;
|
||||
padding: 10rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cp-rgb-group {
|
||||
position: relative;
|
||||
margin-left: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
vertical-align: center;
|
||||
}
|
||||
|
||||
.cp-rgb {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.cp-rgb-text {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.cp-rgb-value {
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cp-rgb-box {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.cp-rgb-line {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.cp-rgb-r {
|
||||
background: red;
|
||||
}
|
||||
|
||||
.cp-rgb-g {
|
||||
background: green;
|
||||
}
|
||||
|
||||
.cp-rgb-b {
|
||||
background: blue;
|
||||
}
|
||||
|
||||
.cp-hex-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cp-hex-box-input {
|
||||
padding: 10rpx 0;
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
color: #999;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cp-hex-box-text {
|
||||
margin-top: 10rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.cp-change {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10rpx;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: 100rpx;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.cp-change-button {
|
||||
padding: 10rpx 0;
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
color: #999;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cp-change .cp-hex-box-input {
|
||||
border-radius: 10rpx;
|
||||
border: none;
|
||||
color: #999;
|
||||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.cp-change .cp-hex-box-input:active {
|
||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.cp-change-text {
|
||||
writing-mode: vertical-lr;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cp-option {
|
||||
margin: 60px 20px 40px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cp-option-item {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
margin: 0 auto;
|
||||
border-radius: 10rpx;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36rpx 36rpx;
|
||||
background-position: 0 0, 18rpx 18rpx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cp-option-item-content {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.cp-option-item:active {
|
||||
transition: all 0.3s;
|
||||
-webkit-transform: scale(1.1);
|
||||
transform: scale(1.1);
|
||||
}
|
17
src/components/icon/index.jsx
Normal file
17
src/components/icon/index.jsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { View } from '@tarojs/components'
|
||||
import IconFont from '@/components/iconfont'
|
||||
import './index.less'
|
||||
|
||||
export default function Index({ name, size = 25, style, color, onClick, className }) {
|
||||
return (
|
||||
<View
|
||||
className={`iconfont ${className}`}
|
||||
style={{
|
||||
...style
|
||||
}}
|
||||
onClick={() => onClick && onClick()}
|
||||
>
|
||||
<IconFont name={name} size={size} color={color}></IconFont>
|
||||
</View>
|
||||
)
|
||||
}
|
7
src/components/icon/index.less
Normal file
7
src/components/icon/index.less
Normal file
@ -0,0 +1,7 @@
|
||||
.iconfont {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 4rpx;
|
||||
display: inline-block;
|
||||
}
|
2
src/components/iconfont/helper.d.ts
vendored
Normal file
2
src/components/iconfont/helper.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/* eslint-disable */
|
||||
export declare var useGlobalIconFont: () => { iconfont: string };
|
9
src/components/iconfont/helper.js
Normal file
9
src/components/iconfont/helper.js
Normal file
@ -0,0 +1,9 @@
|
||||
/* eslint-disable */
|
||||
const useGlobalIconFont = () => {
|
||||
return {
|
||||
iconfont: `components/iconfont/${process.env.TARO_ENV}/${process.env.TARO_ENV}`,
|
||||
};
|
||||
};
|
||||
|
||||
// es modules is unavaiable.
|
||||
module.exports.useGlobalIconFont = useGlobalIconFont;
|
13
src/components/iconfont/index.d.ts
vendored
Normal file
13
src/components/iconfont/index.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/* eslint-disable */
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
interface Props {
|
||||
name: 'icon-xiaoshui3' | 'icon-xiaoshui2' | 'icon-xiaoshui1' | 'icon-piandi' | 'icon-piangao' | 'icon-qianshui2' | 'icon-qianshui1' | 'icon-qingxing2' | 'icon-shimian1' | 'icon-qingxing1' | 'icon-shenshui2' | 'icon-shimian2' | 'icon-rushuixiaoshuai' | 'icon-yandong2' | 'icon-yandong1' | 'icon-shenshui1' | 'icon-zongshuimian' | 'icon-shuimianxiaoshuai' | 'icon-qingxingcishu' | 'icon-xitongshezhi1' | 'icon-nvxingzhushou' | 'icon-shiwen' | 'icon-xieyang' | 'icon-tiwen' | 'icon-xieya' | 'icon-xinshuai' | 'icon-shuimian' | 'icon-huodongjilu' | 'icon-ECG' | 'icon-HRV' | 'icon-jibu' | 'icon-beijianhuren' | 'icon-xitongshezhi' | 'icon-yuyanqiehuan' | 'icon-shebeidingwei' | 'icon-wodequanyi' | 'icon-jinjilianxiren' | 'icon-jianhuren' | 'icon-a-huaban24' | 'icon-a-huaban14' | 'icon-a-huaban11' | 'icon-a-huaban25' | 'icon-a-huaban7' | 'icon-a-huaban23' | 'icon-a-huaban15' | 'icon-a-huaban141' | 'icon-a-huaban2' | 'icon-a-huaban12' | 'icon-a-huaban22' | 'icon-a-huaban21' | 'icon-a-huaban26' | 'icon-a-huaban1' | 'icon-a-huaban13' | 'icon-shen' | 'icon-gan' | 'icon-pi' | 'icon-xinzang' | 'icon-duigou' | 'icon-a-ziyuan77' | 'icon-xiangxia' | 'icon-xiangxia1' | 'icon-shangyiye' | 'icon-xiayiye' | 'icon-a-ziyuan15' | 'icon-a-ziyuan21' | 'icon-a-ziyuan42' | 'icon-a-ziyuan29' | 'icon-a-ziyuan31';
|
||||
size?: number;
|
||||
color?: string | string[];
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
declare const IconFont: FunctionComponent<Props>;
|
||||
|
||||
export default IconFont;
|
7
src/components/iconfont/index.js
Normal file
7
src/components/iconfont/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const IconFont = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export default IconFont;
|
16
src/components/iconfont/index.weapp.js
Normal file
16
src/components/iconfont/index.weapp.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import React from 'react';
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
const IconFont = (props) => {
|
||||
const { name, size, color, style } = props;
|
||||
|
||||
return <iconfont name={name} size={parseFloat(Taro.pxTransform(size))} color={color} style={style} />;
|
||||
};
|
||||
|
||||
IconFont.defaultProps = {
|
||||
size: 18,
|
||||
};
|
||||
|
||||
export default IconFont;
|
63
src/components/iconfont/weapp/weapp.js
Normal file
63
src/components/iconfont/weapp/weapp.js
Normal file
@ -0,0 +1,63 @@
|
||||
Component({
|
||||
properties: {
|
||||
// icon-xiaoshui3 | icon-xiaoshui2 | icon-xiaoshui1 | icon-piandi | icon-piangao | icon-qianshui2 | icon-qianshui1 | icon-qingxing2 | icon-shimian1 | icon-qingxing1 | icon-shenshui2 | icon-shimian2 | icon-rushuixiaoshuai | icon-yandong2 | icon-yandong1 | icon-shenshui1 | icon-zongshuimian | icon-shuimianxiaoshuai | icon-qingxingcishu | icon-xitongshezhi1 | icon-nvxingzhushou | icon-shiwen | icon-xieyang | icon-tiwen | icon-xieya | icon-xinshuai | icon-shuimian | icon-huodongjilu | icon-ECG | icon-HRV | icon-jibu | icon-beijianhuren | icon-xitongshezhi | icon-yuyanqiehuan | icon-shebeidingwei | icon-wodequanyi | icon-jinjilianxiren | icon-jianhuren | icon-a-huaban24 | icon-a-huaban14 | icon-a-huaban11 | icon-a-huaban25 | icon-a-huaban7 | icon-a-huaban23 | icon-a-huaban15 | icon-a-huaban141 | icon-a-huaban2 | icon-a-huaban12 | icon-a-huaban22 | icon-a-huaban21 | icon-a-huaban26 | icon-a-huaban1 | icon-a-huaban13 | icon-shen | icon-gan | icon-pi | icon-xinzang | icon-duigou | icon-a-ziyuan77 | icon-xiangxia | icon-xiangxia1 | icon-shangyiye | icon-xiayiye | icon-a-ziyuan15 | icon-a-ziyuan21 | icon-a-ziyuan42 | icon-a-ziyuan29 | icon-a-ziyuan31
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
// string | string[]
|
||||
color: {
|
||||
type: null,
|
||||
observer: function(color) {
|
||||
this.setData({
|
||||
colors: this.fixColor(),
|
||||
isStr: typeof color === 'string',
|
||||
});
|
||||
}
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
value: 18,
|
||||
observer: function(size) {
|
||||
this.setData({
|
||||
svgSize: size / 750 * wx.getSystemInfoSync().windowWidth,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
colors: '',
|
||||
svgSize: 18 / 750 * wx.getSystemInfoSync().windowWidth,
|
||||
quot: '"',
|
||||
isStr: true,
|
||||
},
|
||||
methods: {
|
||||
fixColor: function() {
|
||||
var color = this.data.color;
|
||||
var hex2rgb = this.hex2rgb;
|
||||
|
||||
if (typeof color === 'string') {
|
||||
return color.indexOf('#') === 0 ? hex2rgb(color) : color;
|
||||
}
|
||||
|
||||
return color.map(function (item) {
|
||||
return item.indexOf('#') === 0 ? hex2rgb(item) : item;
|
||||
});
|
||||
},
|
||||
hex2rgb: function(hex) {
|
||||
var rgb = [];
|
||||
|
||||
hex = hex.substr(1);
|
||||
|
||||
if (hex.length === 3) {
|
||||
hex = hex.replace(/(.)/g, '$1$1');
|
||||
}
|
||||
|
||||
hex.replace(/../g, function(color) {
|
||||
rgb.push(parseInt(color, 0x10));
|
||||
return color;
|
||||
});
|
||||
|
||||
return 'rgb(' + rgb.join(',') + ')';
|
||||
}
|
||||
}
|
||||
});
|
4
src/components/iconfont/weapp/weapp.json
Normal file
4
src/components/iconfont/weapp/weapp.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {}
|
||||
}
|
203
src/components/iconfont/weapp/weapp.wxml
Normal file
203
src/components/iconfont/weapp/weapp.wxml
Normal file
File diff suppressed because one or more lines are too long
3
src/components/iconfont/weapp/weapp.wxss
Normal file
3
src/components/iconfont/weapp/weapp.wxss
Normal file
@ -0,0 +1,3 @@
|
||||
.icon {
|
||||
background-repeat: no-repeat;
|
||||
}
|
17
src/index.html
Normal file
17
src/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
<meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-touch-fullscreen" content="yes">
|
||||
<meta name="format-detection" content="telephone=no,address=no">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="white">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
|
||||
<title>frontend_ebi_diagnosis</title>
|
||||
<script><%= htmlWebpackPlugin.options.script %></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
3
src/pages/funList/index.config.js
Normal file
3
src/pages/funList/index.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: "首页",
|
||||
});
|
162
src/pages/funList/index.jsx
Normal file
162
src/pages/funList/index.jsx
Normal file
@ -0,0 +1,162 @@
|
||||
import { View } from '@tarojs/components'
|
||||
import './index.less'
|
||||
import { Form, Toast, Cell, Slider, Radio, Switch, Stepper } from "@taroify/core"
|
||||
import { useCallback, useState } from 'react'
|
||||
import { hex, strInsert } from '@/utils/sendOrder'
|
||||
import { debounce } from '@/utils/index'
|
||||
import BLESDK from '@/utils/ble'
|
||||
|
||||
export default function Index() {
|
||||
const [ruleForm, setRuleForm] = useState({
|
||||
volume: 0, //话筒音量
|
||||
music: 0, //音乐音量
|
||||
source: 0, //音源设置
|
||||
bleSwitch: 0, //蓝牙开关
|
||||
soundCadSwitch: 0, //声卡开关
|
||||
playMode: 0, //播放模式
|
||||
musicControl: 0, //音乐控制
|
||||
girth: 0, //超低音强度
|
||||
reverberationMode: 0, //混响模式
|
||||
microphoneReverberation: 0, //话筒混响
|
||||
AI: 0, //AI
|
||||
})
|
||||
|
||||
const sendCode = useCallback(debounce((e) => {
|
||||
BLESDK.writeBleValue(new Uint8Array(strInsert(e)).buffer)
|
||||
}), [])
|
||||
const handleChange = (e, key) => {
|
||||
setRuleForm({
|
||||
...ruleForm,
|
||||
[key]: e
|
||||
})
|
||||
let str = ''
|
||||
switch (key) {
|
||||
case 'volume':
|
||||
str = `7B05EA05B2${hex(e)}`
|
||||
break;
|
||||
case 'music':
|
||||
str = `7B05EA06B2${hex(e)}`
|
||||
break;
|
||||
case 'source':
|
||||
str = `7B05EA07B2${hex(e)}`
|
||||
break;
|
||||
case 'bleSwitch':
|
||||
str = `7B05EA08B2${hex(e)}`
|
||||
break;
|
||||
case 'soundCadSwitch':
|
||||
str = `7B05EA09B2${hex(e)}`
|
||||
break;
|
||||
case 'playMode':
|
||||
str = `7B05EA0AB2${hex(e)}`
|
||||
break;
|
||||
case 'musicControl':
|
||||
str = `7B05EA0BB2${hex(e)}`
|
||||
break;
|
||||
case 'girth':
|
||||
str = `7B05EA0CB2${hex(e)}`
|
||||
break;
|
||||
case 'reverberationMode':
|
||||
str = `7B05EA0DB2${hex(e)}`
|
||||
break;
|
||||
case 'microphoneReverberation':
|
||||
str = `7B05EA0EB2${hex(e)}`
|
||||
break;
|
||||
case 'AI':
|
||||
str = `7B05EA11B2${hex(e)}`
|
||||
break;
|
||||
}
|
||||
sendCode(str)
|
||||
|
||||
}
|
||||
return (
|
||||
<View className="index">
|
||||
<Form className='form'>
|
||||
<Toast id="toast" />
|
||||
<Cell.Group inset>
|
||||
<Form.Item >
|
||||
<Form.Label>话筒音量</Form.Label>
|
||||
<Form.Control>
|
||||
<Slider onChange={(e) => handleChange(e, 'volume')} value={ruleForm.volume} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>音乐音量</Form.Label>
|
||||
<Form.Control>
|
||||
<Slider onChange={(e) => handleChange(e, 'music')} value={ruleForm.music} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>音源设置</Form.Label>
|
||||
<Form.Control>
|
||||
<Radio.Group onChange={(e) => handleChange(e, 'source')} defaultValue="1" direction="horizontal" value={ruleForm.source} >
|
||||
<Radio name={0}>Line</Radio>
|
||||
<Radio name={1}>OPT</Radio>
|
||||
<Radio name={2}>ARC</Radio>
|
||||
<Radio name={3}>USB</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>蓝牙开关</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch size="24" onChange={(e) => handleChange(e, 'bleSwitch')} checked={ruleForm.bleSwitch} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>声卡开关</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch size="24" onChange={(e) => handleChange(e, 'soundCadSwitch')} checked={ruleForm.soundCadSwitch} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>播放模式</Form.Label>
|
||||
<Form.Control>
|
||||
<Radio.Group onChange={(e) => handleChange(e, 'playMode')} defaultValue="1" direction="horizontal" value={ruleForm.playMode}>
|
||||
<Radio name={0}>列表循环</Radio>
|
||||
<Radio name={1}>随机播放</Radio>
|
||||
<Radio name={2}>单曲循环</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>音乐控制</Form.Label>
|
||||
<Form.Control>
|
||||
<Radio.Group onChange={(e) => handleChange(e, 'musicControl')} defaultValue="1" direction="horizontal" value={ruleForm.musicControl}>
|
||||
<Radio name={0}>停止</Radio>
|
||||
<Radio name={1}>播放</Radio>
|
||||
<Radio name={2}>暂停</Radio>
|
||||
<Radio name={3}>上一曲</Radio>
|
||||
<Radio name={4}>下一曲</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>超低音强度</Form.Label>
|
||||
<Form.Control>
|
||||
<Stepper onChange={(e) => handleChange(e, 'girth')} min={0} max={3} value={ruleForm.girth} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>混响模式</Form.Label>
|
||||
<Form.Control>
|
||||
<Stepper onChange={(e) => handleChange(e, 'reverberationMode')} min={1} max={6} value={ruleForm.reverberationMode} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>话筒混响</Form.Label>
|
||||
<Form.Control>
|
||||
<Stepper onChange={(e) => handleChange(e, 'microphoneReverberation')} min={0} max={32} value={ruleForm.microphoneReverberation} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
<Form.Item >
|
||||
<Form.Label>AI</Form.Label>
|
||||
<Form.Control>
|
||||
<Switch size="24" onChange={(e) => handleChange(e, 'AI')} checked={ruleForm.AI} />
|
||||
</Form.Control>
|
||||
</Form.Item>
|
||||
|
||||
</Cell.Group>
|
||||
</Form>
|
||||
</View >
|
||||
)
|
||||
}
|
8
src/pages/funList/index.less
Normal file
8
src/pages/funList/index.less
Normal file
@ -0,0 +1,8 @@
|
||||
.index {
|
||||
background: #F7F8FA;
|
||||
|
||||
.form {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
3
src/pages/index/index.config.js
Normal file
3
src/pages/index/index.config.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: "首页",
|
||||
});
|
44
src/pages/index/index.jsx
Normal file
44
src/pages/index/index.jsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { View } from '@tarojs/components'
|
||||
import './index.less'
|
||||
import { Button } from "@taroify/core"
|
||||
import { Scan } from "@taroify/icons"
|
||||
import BLESDK from '../../utils/ble'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function Index() {
|
||||
const [deviceInfo, setDeviceInfo] = useState(BLESDK.deviceInfo)
|
||||
const deviceCallBack = () => {
|
||||
setDeviceInfo({ ...BLESDK.deviceInfo })
|
||||
if (BLESDK.deviceInfo.state) {
|
||||
}
|
||||
}
|
||||
const lookSleep = () => {
|
||||
// if (!BLESDK.deviceInfo.state) return Taro.showToast({ title: '请先连接设备', icon: 'none' })
|
||||
Taro.navigateTo({ url: '/pages/funList/index' })
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
BLESDK.deviceCallBack(deviceCallBack)
|
||||
setDeviceInfo({ ...BLESDK.deviceInfo })
|
||||
})
|
||||
return (
|
||||
<View className="index">
|
||||
<View className='device'>
|
||||
<View className='item'>
|
||||
<View className='item-label'>NAME:</View>
|
||||
<View className='item-text'>{deviceInfo.name || '--'}</View>
|
||||
</View>
|
||||
<View className='item'>
|
||||
<View className='item-label'>MAC:</View>
|
||||
<View className='item-text'>{deviceInfo.mac || '--'}</View>
|
||||
</View>
|
||||
<View className='item'>
|
||||
<View className='item-label'>连接状态:</View>
|
||||
<View className='item-text' style={{ color: deviceInfo.state ? 'green' : 'red' }}>{deviceInfo.name ? deviceInfo.state ? '已连接' : '未连接' : '--'}</View>
|
||||
</View>
|
||||
</View>
|
||||
<Button color="warning" className='btn' onClick={lookSleep}>功能设置</Button>
|
||||
</View >
|
||||
)
|
||||
}
|
19
src/pages/index/index.less
Normal file
19
src/pages/index/index.less
Normal file
@ -0,0 +1,19 @@
|
||||
.btn {
|
||||
width: 80%;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.device {
|
||||
.item {
|
||||
width: 500rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.item-label {
|
||||
width: 160rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
10
src/store/index.js
Normal file
10
src/store/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { create } from "zustand";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
|
||||
const initialState = {
|
||||
userInfo: {},
|
||||
};
|
||||
const useStore = create(immer(() => initialState));
|
||||
|
||||
export default useStore;
|
349
src/utils/ble.js
Normal file
349
src/utils/ble.js
Normal file
@ -0,0 +1,349 @@
|
||||
import Taro from "@tarojs/taro";
|
||||
import { handshake, asyncDate } from "./sendOrder";
|
||||
import useStore from "@/store/index";
|
||||
import { aes128Decrypt } from "@/utils/index";
|
||||
import TASK from "./taskQueue";
|
||||
|
||||
const errMsg = {
|
||||
0: "正常",
|
||||
10000: "未初始化蓝牙适配器",
|
||||
10001: "请检查手机蓝牙是否打开或微信是否授权蓝牙",
|
||||
10002: "没有找到指定设备",
|
||||
10003: "连接失败",
|
||||
10004: "没有找到指定服务",
|
||||
10005: "没有找到指定特征",
|
||||
10006: "当前连接已断开",
|
||||
10007: "当前特征不支持此操作",
|
||||
10008: "其余所有系统上报的异常",
|
||||
10009: "Android系统版本低于4.3不支持蓝牙功能",
|
||||
10012: "连接超时",
|
||||
10013: "连接超时",
|
||||
1509008: "请检查手机定位是否打开或微信是否授权定位",
|
||||
};
|
||||
|
||||
class Bluetooth {
|
||||
config = {
|
||||
serviceId: "4E31BF4A-8507-3A2B-7344-C7EDACD38104", //设备服务id
|
||||
NotifyUUID: "4E31BF4B-8507-3A2B-7344-C7EDACD38104",
|
||||
WriteUUID: "4E31BF4C-8507-3A2B-7344-C7EDACD38104",
|
||||
};
|
||||
|
||||
// 连接状态
|
||||
linkFlag = false;
|
||||
// 蓝牙是否可用
|
||||
available = true;
|
||||
// 蓝牙初始化
|
||||
isInit = false;
|
||||
|
||||
timeout = null;
|
||||
timeout1 = null;
|
||||
|
||||
// 判断是否搜索到设备
|
||||
isFindBt = null;
|
||||
|
||||
platform = Taro.getSystemInfoSync().platform;
|
||||
appAuthorize = Taro.getAppAuthorizeSetting();
|
||||
deviceInfo = {};
|
||||
callBack = () => { };
|
||||
searchBack = () => { };
|
||||
|
||||
constructor() {
|
||||
// if (this.platform == "android") {
|
||||
// this.deviceInfo = { deviceId: "50:C0:F0:F2:99:5B", mac: "50:C0:F0:F2:99:5B", name: "W53A", state: false };
|
||||
// } else {
|
||||
// this.deviceInfo = { deviceId: "", mac: "50:C0:F0:C8:CC:88", name: "W53A", state: false };
|
||||
// // this.deviceInfo = { deviceId: "A890AB3C-0BC3-1418-1132-CA27514B147B", mac: "50:C0:F0:F2:99:5B", name: "W53A", state: false };
|
||||
// }
|
||||
this.onBluetoothAdapterStateChange();
|
||||
this.onBLEConnectionStateChange();
|
||||
this.onBluetoothDeviceFound();
|
||||
}
|
||||
// 错误
|
||||
fail(res) {
|
||||
this.linkFlag = false;
|
||||
if (!res.errCode) return;
|
||||
let starIde = res.errMsg.indexOf("fail") + 5;
|
||||
Taro.showToast({ title: errMsg[res.errCode] || errMsg[res.errno] || res.errMsg.substring(starIde), icon: "none", duration: 3000 });
|
||||
}
|
||||
|
||||
// 监测手机蓝牙状态
|
||||
onBluetoothAdapterStateChange() {
|
||||
Taro.onBluetoothAdapterStateChange(({ available, discovering }) => {
|
||||
this.available = available;
|
||||
// 手机蓝牙开启
|
||||
if (this.available && !this.isInit) {
|
||||
Taro.closeBluetoothAdapter({
|
||||
success: (res) => {
|
||||
this.openBluetoothAdapter();
|
||||
},
|
||||
});
|
||||
console.log("手机蓝牙开启");
|
||||
}
|
||||
// 手机蓝牙关闭
|
||||
if (!available) {
|
||||
this.isInit = false;
|
||||
this.deviceInfo.state = false;
|
||||
this.linkFlag = false;
|
||||
this.callBack(this.deviceInfo);
|
||||
console.log("手机蓝牙关闭");
|
||||
}
|
||||
});
|
||||
}
|
||||
// 初始化蓝牙模块
|
||||
openBluetoothAdapter() {
|
||||
return Taro.openBluetoothAdapter({
|
||||
success: () => {
|
||||
console.log("初始化蓝牙模块");
|
||||
this.startBluetoothDevicesDiscovery()
|
||||
this.isInit = true;
|
||||
},
|
||||
fail: (err) => {
|
||||
this.isInit = false;
|
||||
this.fail(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 开始搜寻附近的蓝牙外围设备
|
||||
startBluetoothDevicesDiscovery() {
|
||||
console.log("搜索蓝牙");
|
||||
if (this.appAuthorize.locationAuthorized != "authorized" && this.platform == "android")
|
||||
return Taro.showToast({ title: "请打开微信定位权限", icon: "none", duration: 3000 });
|
||||
Taro.startBluetoothDevicesDiscovery({
|
||||
allowDuplicatesKey: true,
|
||||
powerLevel: "high",
|
||||
success: (res) => {
|
||||
this.isFindBt = setTimeout(() => {
|
||||
if (this.platform == "android") {
|
||||
this.stopBluetoothDevicesDiscovery();
|
||||
Taro.showModal({
|
||||
title: "未搜索到设备",
|
||||
content: "1、请检查设备是否在附近,或者设备是否被其他应用连接。2、请检查手机微信应用权限中的‘附近的设备权限’是否打开。",
|
||||
showCancel: false,
|
||||
success: () => {
|
||||
const pages = Taro.getCurrentPages();
|
||||
if (pages?.length > 1) {
|
||||
Taro.navigateBack();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
this.isFindBt && this.clearTimeoutFn("isFindBt");
|
||||
}, 6000);
|
||||
},
|
||||
fail: (err) => {
|
||||
this.fail(err);
|
||||
this.stopBluetoothDevicesDiscovery();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 监听搜索到新设备的事件
|
||||
// 定义一个方法用于处理蓝牙设备发现事件
|
||||
onBluetoothDeviceFound() {
|
||||
Taro.onBluetoothDeviceFound((res) => {
|
||||
const devices = res.devices[0];
|
||||
const advertisData = this.ab2hex(devices.advertisData);
|
||||
const mac = this.strInsert(String(advertisData).substring(4, 16)).toLocaleUpperCase();
|
||||
// 判断是否有搜索到设备
|
||||
this.isFindBt && this.clearTimeoutFn("isFindBt");
|
||||
|
||||
if (devices.name && devices.name == "LE-AB2020") {
|
||||
const item = {
|
||||
deviceId: devices.deviceId,
|
||||
mac: mac,
|
||||
name: devices.name,
|
||||
};
|
||||
|
||||
console.log(JSON.stringify(item), "搜索到设备");
|
||||
this.deviceInfo = { ...this.deviceInfo, ...item }
|
||||
this.stopBluetoothDevicesDiscovery();
|
||||
this.createBleConnection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 蓝牙连接
|
||||
createBleConnection() {
|
||||
if (!this.available || this.linkFlag) return;
|
||||
this.linkFlag = true;
|
||||
Taro.setStorageSync("deviceInfo", this.deviceInfo);
|
||||
console.log("正在连接设备");
|
||||
Taro.createBLEConnection({
|
||||
timeout: 8000,
|
||||
deviceId: this.deviceInfo.deviceId,
|
||||
success: () => {
|
||||
this.deviceInfo.state = true;
|
||||
this.getBLEDeviceServices();
|
||||
// this.getBLEDeviceCharacteristics();
|
||||
},
|
||||
fail: (res) => {
|
||||
this.linkFlag = false;
|
||||
console.log(res, this.deviceInfo, "连接错误");
|
||||
this.callBack({ ...this.deviceInfo, errCode: 10003 });
|
||||
if (!this.deviceInfo.deviceId) return this.timeout && this.clearTimeoutFn("timeout");
|
||||
if (this.timeout) return;
|
||||
if (res.errno == "1509007") {
|
||||
this.closeBluetoothAdapter();
|
||||
} else {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout && this.clearTimeoutFn("timeout");
|
||||
this.createBleConnection();
|
||||
}, 10000);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 获取蓝牙多个service
|
||||
getBLEDeviceServices() {
|
||||
Taro.getBLEDeviceServices({
|
||||
deviceId: this.deviceInfo.deviceId,
|
||||
success: (res) => {
|
||||
console.log(JSON.stringify(res), 'JSON.stringify(res)');
|
||||
|
||||
this.getBLEDeviceCharacteristics();
|
||||
},
|
||||
fail: (err) => this.fail(err),
|
||||
});
|
||||
}
|
||||
// 获取蓝牙低功耗设备某个服务中所有特征
|
||||
getBLEDeviceCharacteristics() {
|
||||
Taro.getBLEDeviceCharacteristics({
|
||||
deviceId: this.deviceInfo.deviceId,
|
||||
serviceId: this.config.serviceId,
|
||||
success: (res) => {
|
||||
this.notifyBLECharacteristicValueChange();
|
||||
},
|
||||
fail: (err) => this.fail(err),
|
||||
});
|
||||
}
|
||||
|
||||
// 启用蓝牙低功耗设备特征值变化时的 notify 功能
|
||||
notifyBLECharacteristicValueChange() {
|
||||
Taro.notifyBLECharacteristicValueChange({
|
||||
deviceId: this.deviceInfo.deviceId,
|
||||
serviceId: this.config.serviceId,
|
||||
characteristicId: this.config.NotifyUUID,
|
||||
state: true,
|
||||
success: (res) => {
|
||||
console.log("连接成功");
|
||||
|
||||
this.callBack(this.deviceInfo);
|
||||
},
|
||||
fail: (err) => this.fail(err),
|
||||
});
|
||||
}
|
||||
//监听设备返回的数据
|
||||
onBLECharacteristicValueChange(fn) {
|
||||
Taro.onBLECharacteristicValueChange((data) => {
|
||||
let bytes = new Uint8Array(data.value);
|
||||
let aesBytes = null;
|
||||
let value = String(this.ab2hex(bytes));
|
||||
// 长度在16内的需要解密
|
||||
if (bytes.byteLength == 17 && bytes[0] == 0xfc) {
|
||||
let aesText = this.strInsert(aes128Decrypt(value.substring(2))).split(":");
|
||||
let bf = new ArrayBuffer(aesText.length);
|
||||
let dv = new DataView(bf);
|
||||
aesText.forEach((item, index) => {
|
||||
dv.setUint8(index, `0x${item}`);
|
||||
});
|
||||
aesBytes = new Uint8Array(bf);
|
||||
}
|
||||
// 获取电量
|
||||
if (aesBytes && aesBytes[1] == 0xa1 && aesBytes[2] == 0x07 && aesBytes[3] == 0xb3) {
|
||||
useStore.setState((state) => {
|
||||
state.battery = aesBytes[4];
|
||||
});
|
||||
}
|
||||
console.log(`设备回复====>${bytes.byteLength == 17 ? String(this.ab2hex(aesBytes)) : value}`);
|
||||
TASK.executeTask();
|
||||
fn && fn(bytes, aesBytes, value);
|
||||
});
|
||||
}
|
||||
// 向蓝牙写入数据
|
||||
writeBleValue(value) {
|
||||
console.log("发送指令====>" + String(this.ab2hex(value)).toLocaleUpperCase());
|
||||
return Taro.writeBLECharacteristicValue({
|
||||
deviceId: this.deviceInfo.deviceId,
|
||||
serviceId: this.config.serviceId,
|
||||
characteristicId: this.config.WriteUUID,
|
||||
writeType: "writeNoResponse",
|
||||
value: value,
|
||||
success(res) {
|
||||
console.log('writeBLECharacteristicValue success', JSON.stringify(res));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//蓝牙断开
|
||||
onBLEConnectionStateChange() {
|
||||
Taro.onBLEConnectionStateChange((res) => {
|
||||
if (!res.connected) {
|
||||
console.log(res, "蓝牙断开");
|
||||
this.deviceInfo.state = res.connected;
|
||||
this.callBack(this.deviceInfo);
|
||||
this.linkFlag = false;
|
||||
if (!this.available || this.timeout || !this.isInit || !this.deviceInfo.deviceId) return;
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout && this.clearTimeoutFn("timeout");
|
||||
this.createBleConnection();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
//停止搜寻附近的蓝牙外围设备
|
||||
stopBluetoothDevicesDiscovery() {
|
||||
this.isFindBt && this.clearTimeoutFn("isFindBt");
|
||||
Taro.stopBluetoothDevicesDiscovery();
|
||||
console.log("停止搜寻");
|
||||
}
|
||||
// 断开与蓝牙低功耗设备的连接
|
||||
closeBluetoothAdapter() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.linkFlag = false;
|
||||
this.deviceInfo = { state: false };
|
||||
wx.removeStorageSync("deviceInfo");
|
||||
this.callBack(this.deviceInfo);
|
||||
Taro.closeBluetoothAdapter({
|
||||
success: (res) => {
|
||||
this.openBluetoothAdapter().then(resolve).catch(reject);
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
// 设备响应回调
|
||||
deviceCallBack(fn) {
|
||||
this.callBack = fn;
|
||||
}
|
||||
// 转换二进制数据
|
||||
ab2hex(buffer) {
|
||||
if (!buffer) return;
|
||||
const hexArr = Array.prototype.map.call(new Uint8Array(buffer), function (bit) {
|
||||
return ("00" + bit.toString(16)).slice(-2);
|
||||
});
|
||||
return hexArr.join("").toLocaleLowerCase();
|
||||
}
|
||||
/**
|
||||
* 字符串插入字符
|
||||
* @param {*} str 字符串
|
||||
* @param {*} length 间隔多少个插入
|
||||
* @param {*} fmt 插入符号
|
||||
* @returns
|
||||
*/
|
||||
strInsert(str, length = 2, fmt = ":") {
|
||||
if (!str || !str.length) return "";
|
||||
var reg = new RegExp("\\w{1," + length + "}", "g");
|
||||
let ma = str.match(reg);
|
||||
return ma.join(fmt).toLocaleLowerCase();
|
||||
}
|
||||
|
||||
// 清除计时器
|
||||
clearTimeoutFn(name) {
|
||||
clearTimeout(this[name]);
|
||||
this[name] = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Bluetooth();
|
72
src/utils/index.js
Normal file
72
src/utils/index.js
Normal file
@ -0,0 +1,72 @@
|
||||
import dayjs from "dayjs";
|
||||
import Taro from "@tarojs/taro";
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import CryptoJS from "crypto-js";
|
||||
import useStore from "@/store/index";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} hexData
|
||||
* @param {*} key
|
||||
* @param {*} iv
|
||||
* @returns
|
||||
*/
|
||||
export const aes128Encrypt = (hexData, key = "23572fb6364ba347f8b441586b632d3f", iv = "") => {
|
||||
// 将十六进制字符串转换为字节数组
|
||||
var data = CryptoJS.enc.Hex.parse(hexData);
|
||||
// 将密钥和 IV(初始向量)转换为字节数组
|
||||
var keyBytes = CryptoJS.enc.Hex.parse(key);
|
||||
var ivBytes = CryptoJS.enc.Hex.parse(iv);
|
||||
// 执行 AES-128 加密,使用 CBC 模式和 PKCS7 填充
|
||||
var encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
|
||||
iv: ivBytes,
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.ZeroPadding,
|
||||
});
|
||||
// 将加密后的字节数组转换为十六进制字符串
|
||||
var encryptedHex = encrypted.ciphertext.toString();
|
||||
return encryptedHex;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
* @param data
|
||||
* @returns {string}
|
||||
*/
|
||||
export function aes128Decrypt(data, key = "23572fb6364ba347f8b441586b632d3f", iv = "") {
|
||||
// 十六位十六进制数作为密钥
|
||||
const keyBytes = CryptoJS.enc.Hex.parse(key);
|
||||
// 十六位十六进制数作为密钥偏移量
|
||||
const ivBytes = CryptoJS.enc.Hex.parse(iv);
|
||||
const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
|
||||
const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
|
||||
const decrypt = CryptoJS.AES.decrypt(str, keyBytes, {
|
||||
iv: ivBytes,
|
||||
mode: CryptoJS.mode.ECB,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
});
|
||||
const decryptedStr = decrypt.toString(CryptoJS.enc.Hex);
|
||||
return decryptedStr.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fn 需要防抖的函数
|
||||
* @param time 防抖时间
|
||||
* @returns
|
||||
*/
|
||||
|
||||
export const debounce = (fn, time = 300) => {
|
||||
let timer;
|
||||
return function (...argu) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
fn(...argu);
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}, time);
|
||||
};
|
||||
}
|
71
src/utils/sendOrder.js
Normal file
71
src/utils/sendOrder.js
Normal file
@ -0,0 +1,71 @@
|
||||
import BLESDK from './ble'
|
||||
import TASK from './taskQueue'
|
||||
import { aes128Encrypt } from '@/utils/index'
|
||||
|
||||
export function strInsert(str, length = 2) {
|
||||
if (!str || !str.length) return ''
|
||||
var reg = new RegExp('\\w{1,' + length + '}', 'g')
|
||||
let ma = str.match(reg)
|
||||
return ma.map((item) => `0x${item}`)
|
||||
}
|
||||
// 10进制转16进制
|
||||
export const hex = (hex) => {
|
||||
hex = Number(hex)
|
||||
return hex < 16 ? '0' + hex.toString(16) : String(hex.toString(16))
|
||||
}
|
||||
|
||||
// 握手
|
||||
export function handshake() {
|
||||
let list = strInsert(aes128Encrypt(`10a1010259414B2D56302E302E310000`))
|
||||
TASK.addTask(list)
|
||||
}
|
||||
|
||||
// 同步时间
|
||||
export function asyncDate() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString();
|
||||
const day = now.getDate().toString();
|
||||
const hours = now.getHours().toString();
|
||||
const minutes = now.getMinutes().toString();
|
||||
const seconds = now.getSeconds().toString();
|
||||
const zone = (new Date().getTimezoneOffset() / 60) * -1;
|
||||
let str = [
|
||||
"10",
|
||||
"a1",
|
||||
"04",
|
||||
"b2",
|
||||
hex(zone & 0xff),
|
||||
hex(year >> 8),
|
||||
hex(year & 0xff),
|
||||
hex(month),
|
||||
hex(day),
|
||||
hex(hours),
|
||||
hex(minutes),
|
||||
hex(seconds),
|
||||
"00",
|
||||
"00",
|
||||
"00",
|
||||
"00",
|
||||
]
|
||||
.toString()
|
||||
.replaceAll(",", "");
|
||||
TASK.addTask(strInsert(aes128Encrypt(str)));
|
||||
}
|
||||
|
||||
// 获取版本号
|
||||
export function getVersions() {
|
||||
TASK.addTask(strInsert(aes128Encrypt(`10a102b1000000000000000000000000`)));
|
||||
}
|
||||
// 重启
|
||||
export function restart() {
|
||||
TASK.addTask(strInsert(aes128Encrypt(`0906800000`)));
|
||||
}
|
||||
// 获取新版睡眠
|
||||
export function getNewSleep() {
|
||||
TASK.addTask(strInsert(aes128Encrypt(`10A31a00ff00000000000000000000`)));
|
||||
}
|
||||
// 新版睡眠-接收回复
|
||||
export function sleepResReply(type, date, num) {
|
||||
TASK.addTask(strInsert(aes128Encrypt(`10A3${hex(type)}${hex(date)}${hex(num)}0000000000000000000000`)));
|
||||
}
|
32
src/utils/taskQueue.js
Normal file
32
src/utils/taskQueue.js
Normal file
@ -0,0 +1,32 @@
|
||||
import BLESDK from './ble'
|
||||
|
||||
class taskQueue {
|
||||
list = []
|
||||
isExecute = false
|
||||
overtime = null
|
||||
|
||||
addTask(buffer) {
|
||||
this.list.push(buffer)
|
||||
if (!this.isExecute) {
|
||||
this.isExecute = true
|
||||
this.executeTask()
|
||||
}
|
||||
}
|
||||
|
||||
executeTask() {
|
||||
this.isExecute = !!this.list.length
|
||||
if (!this.list.length) return
|
||||
BLESDK.writeBleValue(new Uint8Array(['0x7b', ...this.list[0]]).buffer)
|
||||
this.list.shift()
|
||||
|
||||
this.overtime = setTimeout(() => {
|
||||
this.executeTask()
|
||||
}, 1000)
|
||||
}
|
||||
stopOverTime() {
|
||||
clearTimeout(this.overtime)
|
||||
this.overtime = null
|
||||
}
|
||||
}
|
||||
|
||||
export default new taskQueue()
|
Loading…
x
Reference in New Issue
Block a user