├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .jestrc.json ├── .npmignore ├── CONTRIBUTOR.md ├── LICENSE ├── README.md ├── build ├── miniprogram.config.js ├── webpack.base.config.js ├── webpack.dev.config.js ├── webpack.library.js ├── webpack.mp.config.js └── webpack.prod.config.js ├── examples ├── App.vue ├── example.less ├── images │ ├── icon_footer.png │ ├── icon_footer_link.png │ ├── icon_intro.png │ ├── icon_nav_feedback.png │ ├── icon_nav_form.png │ ├── icon_nav_layout.png │ ├── icon_nav_nav.png │ ├── icon_nav_search.png │ ├── icon_nav_special.png │ ├── icon_nav_z-index.png │ ├── icon_tabbar.png │ ├── layers │ │ ├── content.png │ │ ├── navigation.png │ │ ├── popout.png │ │ └── transparent.gif │ ├── logo.png │ ├── pic_160.png │ ├── pic_article.png │ └── vcode.jpg ├── main.js ├── mp │ └── main.mp.js ├── route │ ├── config.js │ └── index.js ├── store │ ├── actions.js │ ├── index.js │ └── mutations.js ├── template │ └── index.html └── view │ ├── components │ ├── interaction.vue │ ├── login.vue │ ├── pulldown.vue │ ├── request.vue │ └── titleBar.vue │ ├── home │ └── index.vue │ └── utils │ └── config.js ├── package.json ├── src ├── api │ ├── index.d.ts │ └── index.ts ├── enums │ └── index.ts ├── fake │ ├── asyncApis.ts │ ├── index.ts │ ├── noopApis.ts │ └── syncApis.ts ├── index.ts ├── interaction │ ├── actionsheet.ts │ ├── index.ts │ ├── modal.ts │ └── toast.ts ├── network │ └── request.ts ├── utils │ └── index.ts └── wxapi │ ├── api.ts │ ├── index.ts │ └── mapContext.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }] 9 | ], 10 | "plugins": [ 11 | "@babel/transform-runtime" 12 | ], 13 | "env": { 14 | "test": { 15 | "presets": ["@babel/env"], 16 | // "plugins": ["@babel/plugin-transform-runtime", "transform-es2015-modules-commonjs", "dynamic-import-node"] 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /dist/ 3 | /*.js 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'extends': [ 3 | 'airbnb-base', 4 | 'plugin:promise/recommended', 5 | 'plugin:vue/recommended' 6 | ], 7 | 'parser': 'vue-eslint-parser', 8 | 'parserOptions': { 9 | 'parser': '@typescript-eslint/parser', 10 | 'ecmaVersion': 9, 11 | 'ecmaFeatures': { 12 | 'jsx': false 13 | }, 14 | 'sourceType': 'module', 15 | 'allowImportExportEverywhere': true 16 | }, 17 | 'env': { 18 | 'es6': true, 19 | 'node': true, 20 | 'jest': true 21 | }, 22 | 'plugins': [ 23 | 'import', 24 | 'node', 25 | 'promise' 26 | ], 27 | 28 | 'settings': { 29 | 'import/resolver': { 30 | 'node': { 31 | 'extensions': ['.js', '.jsx', '.ts', '.tsx'] 32 | } 33 | }, 34 | }, 35 | 'rules': { 36 | 'arrow-parens': 'off', 37 | 'comma-dangle': [ 38 | 'error', 39 | 'only-multiline' 40 | ], 41 | 'complexity': ['error', 10], 42 | 'func-names': 'off', 43 | 'global-require': 'off', 44 | 'handle-callback-err': [ 45 | 'error', 46 | '^(err|error)$' 47 | ], 48 | 'import/extensions': [ 49 | 'off' 50 | ], 51 | 'import/no-unresolved': [ 52 | 'error', 53 | { 54 | 'caseSensitive': true, 55 | 'commonjs': true, 56 | 'amd': true, 57 | 'ignore': ['^[^.]'] 58 | } 59 | ], 60 | 'import/prefer-default-export': 'off', 61 | 'linebreak-style': 'off', 62 | 'no-catch-shadow': 'error', 63 | 'no-continue': 'off', 64 | 'no-div-regex': 'warn', 65 | 'no-else-return': 'off', 66 | 'no-param-reassign': 'off', 67 | 'no-plusplus': 'off', 68 | 'no-shadow': 'off', 69 | 'no-multi-assign': 'off', 70 | 'no-underscore-dangle': 'off', 71 | 'node/no-deprecated-api': 'error', 72 | 'node/process-exit-as-throw': 'error', 73 | 'object-curly-spacing': [ 74 | 'error', 75 | 'never' 76 | ], 77 | 'operator-linebreak': [ 78 | 'error', 79 | 'after', 80 | { 81 | 'overrides': { 82 | ':': 'before', 83 | '?': 'before' 84 | } 85 | } 86 | ], 87 | 'prefer-arrow-callback': 'off', 88 | 'prefer-destructuring': 'off', 89 | 'prefer-template': 'off', 90 | 'quotes':['error','double'], 91 | 'quote-props': [ 92 | 1, 93 | 'as-needed', 94 | { 95 | 'unnecessary': true 96 | } 97 | ], 98 | 'semi': [ 99 | 'error', 100 | 'never' 101 | ], 102 | 'indent': ['error', 4], 103 | 'space-before-function-paren': ['error', 'never'], 104 | 'no-return-assign': 'off', 105 | 'complexity': 'off', 106 | 'no-use-before-define': 'off', 107 | 'max-len': 'off', 108 | 'no-restricted-syntax': 'off', 109 | 'no-console': 'off', 110 | 'class-methods-use-this': 'off', 111 | 'no-nested-ternary': 'off', 112 | 'no-mixed-operators': 'off', 113 | 'consistent-return': 'off', 114 | 'no-restricted-globals': 'off', 115 | 'promise/always-return': 'off', 116 | 'camelcase': 'off', 117 | 'no-control-regex': 'off', 118 | 'no-await-in-loop': 'off', 119 | 'vue/require-default-prop': 'off', 120 | "vue/html-self-closing": ["error", { 121 | "html": { 122 | "void": "always", 123 | "normal": "always", 124 | "component": "always" 125 | }, 126 | "svg": "always", 127 | "math": "always" 128 | }], 129 | }, 130 | 'globals': { 131 | 'window': true, 132 | 'document': true, 133 | 'App': true, 134 | 'Page': true, 135 | 'Component': true, 136 | 'Behavior': true, 137 | 'wx': true, 138 | 'getCurrentPages': true, 139 | 'WeUI': true, 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | yarn-debug.log 5 | yarn-error.log 6 | lerna-debug.log 7 | npm-debug.log.* 8 | yarn-debug.log.* 9 | yarn-error.log.* 10 | lerna-debug.log.* 11 | lib 12 | .idea 13 | .vscode 14 | .npmrc 15 | coverage 16 | dist 17 | package-lock.json -------------------------------------------------------------------------------- /.jestrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "\\.js$": "babel-jest", 4 | "\\.(vue)$": "vue-jest", 5 | "\\.(css|less)$": "/test/utils/css-loader-jest.js" 6 | }, 7 | "moduleNameMapper": { 8 | "^vue$": "vue/dist/vue.common.js", 9 | "^kbone-ui$": "/src/index.js", 10 | "^@utils/(.*)$": "/src/utils/$1" 11 | } 12 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | 5 | /build 6 | /dist 7 | /test 8 | /example 9 | 10 | .babelrc 11 | .eslintignore 12 | .eslintrc 13 | .gitignore 14 | .gitmodules 15 | .jestrc.json 16 | -------------------------------------------------------------------------------- /CONTRIBUTOR.md: -------------------------------------------------------------------------------- 1 | # 贡献规范 2 | 3 | 如果有兴趣一起参与开发和维护 kbone-api,可遵循此规范进行代码贡献。 4 | 5 | ## 开发流程 6 | 7 | 首先需要将代码仓库 clone 下来:https://github.com/wechat-miniprogram/kbone-api.git, 然后切到 develop 分支。 8 | 9 | 初次将代码 clone 下来后,需要先运行 `npm run install` 来完成 lerna 的初始化,之后才能进入开发的流程: 10 | 11 | 1. 完成代码开发 12 | 2. 补充单元测试 13 | 3. 在包内执行 `npm run test` 确保单元测试没有问题 14 | 4. 在 kbone-ui 根目录执行 `npm run test` 确保代码检查和所有包单元测试正常 15 | 5. 在包内的 CHANGELOG.md 中补充版本更新日志,具体格式可参考 packages/miniprogram-element/CHANGELOG.md 16 | 6. 如有必要,需要修改 docs 目录下的文档 17 | 7. 提交到 git 18 | 8. 在 kbone-ui 根目录执行 `npm run publish` 打标签并发布到 npm 19 | 20 | 具体规范下面进行说明: 21 | 22 | ## 目录规范 23 | 24 | 25 | ```sh 26 | . 27 | ├── .babelrc 28 | ├── .eslintignore 29 | ├── .eslintrc.js 30 | ├── .git 31 | ├── .gitignore 32 | ├── .npmignore 33 | ├── CONTRIBUTOR.md 34 | ├── LICENSE 35 | ├── README.md 36 | ├── build # 编译文件 37 | ├── examples # 对外示例 38 | ├── index.html # 模板文件 39 | ├── package.json 40 | ├── packages # 组件模板 41 | ├── src # 源代码 42 | ├── test 43 | └── typings 44 | ``` 45 | 46 | ## 其他规范 47 | 48 | ### 代码检查 49 | 50 | 统一走 eslint 来约束,在 kbone-ui 根目录下执行:`npm run lint` 会对各个包内的 src、test、tool 目录下的 js 文件进行检查,确保无任何规则失败提示。 51 | 52 | 53 | ### 单元测试 54 | 55 | 各个包内部实现单元测试和覆盖率检查,统一使用 jest 工具链;如果涉及到自定义组件则使用 miniprogram-simulate;如果有 CI 需求,则使用 codecov 来管理覆盖率检查。 56 | 57 | 包内命令统一为: 58 | 59 | ``` 60 | # 执行单元测试 61 | npm run test 62 | 63 | 64 | # 执行覆盖率检查 65 | npm run coverage 66 | 67 | ``` 68 | 69 | 在包内实现完单元测试后,需要在 kbone-ui 根目录下的 package.json 中补充相应的执行命令,确保在 kbone-ui 根目录下执行 `npm run test` 可以执行所有包内的单元测试。 70 | 71 | 测试用例规范:[vue-utils](https://vue-test-utils.vuejs.org/) 72 | 73 | ### 版本规范 74 | 75 | 参考:https://semver.org/lang/zh-CN/ 76 | 77 | ### commit 信息 78 | 79 | 格式为 `[变化]: 具体操作`,一条完整的示例:`feature: support camera inner component`。 80 | 81 | 变化支持如下枚举值: 82 | 83 | * feature:新增特性 84 | * fixed:修复 bug 85 | * docs:文档更新 86 | * update:demo、更新日志、构建代码等源码之外的一些更新调整 87 | * refactor:重构 88 | * lint:调整代码以通过代码检查 89 | 90 | ### 分支 91 | 92 | 默认开发分支为 develop 分支,如果需要提 pr,则以此分支为基准。当 develop 分支稳定后会合入 master 分支。 93 | 94 | 创建其他分支的命名规范: 95 | 96 | * feature-xxx:新特性分支 97 | * fixed-xxx:bugfix 分支 98 | * refactor-xxx:重构分支 99 | 100 | 合入流程: 101 | 102 | 其他分支 ---> develop 分支 ---> master 分支 103 | 104 | ### CI 105 | 106 | 目前未接入,待补充。 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 wechat-miniprogram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## kbone-api 2 | 3 | **kbone-api**是一个能同时支持 小程序和 web 开发的多端 api 库。 4 | 5 | ## 特性 6 | 7 | * 针对基于 kbone 的多端开发,满足在 Web 上直接使用小程序相关 api 8 | * 不依赖 kbone 和 kbone-ui,一个无依赖的小程序 api 的跨端库 9 | * 完整对齐 [wx[apis]](https://developers.weixin.qq.com/miniprogram/dev/api/) 10 | * 同时支持 promise 化和 callback 调用 11 | 12 | 13 | 14 | ## 快速上手 15 | 16 | 下载 kbone-api 17 | 18 | ``` 19 | npm install kbone-api 20 | ``` 21 | 22 | 通过模块的方式直接导出模块并使用: 23 | ```js 24 | import kboneAPI from 'kbone-api' 25 | 26 | kboneAPI.request() 27 | kboneAPI.showToast() 28 | kboneAPI.showModal() 29 | ``` 30 | 31 | 为了方便 Vue 开发,可以直接使用 Vue.use(kboneAPI) 来设置全局对象. 32 | 33 | ```js 34 | # main.js 35 | import Vue from 'vue' 36 | 37 | Vue.use(kboneAPI) 38 | 39 | # logic code 40 | 63 | ``` 64 | 65 | ## 文档 66 | 67 | 所有 api 使用对齐小程序 api,具体内容可以参考 [小程序 api](https://developers.weixin.qq.com/minigame/dev/api/)。 68 | 69 | 线上体验地址为:[api/ui/](https://wechat-miniprogram.github.io/api/ui/#/) 70 | 71 | ## LICENSE 72 | MIT -------------------------------------------------------------------------------- /build/miniprogram.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置参考:https://github.com/wechat-miniprogram/kbone/blob/develop/docs/miniprogram.config.js 3 | */ 4 | 5 | module.exports = { 6 | // 页面 origin,默认是 https://miniprogram.default 7 | origin: 'https://ui.kboneapi.com', 8 | // 入口页面路由,默认是 / 9 | entry: '/', 10 | // 页面路由,用于页面间跳转 11 | router: { 12 | // 路由可以是多个值,支持动态路由 13 | home: [ 14 | '/(home|index)?', 15 | '/:type', 16 | ], 17 | other: [ 18 | '/test/list/:id', 19 | '/test/detail/:id', 20 | ], 21 | }, 22 | // 特殊路由跳转 23 | redirect: { 24 | // 跳转遇到同一个 origin 但是不在 router 里的页面时处理方式,支持的值:webview - 使用 web-view 组件打开;error - 抛出异常;none - 默认值;什么都不做,router 配置项中的 key 25 | notFound: 'home', 26 | // 跳转到 origin 之外的页面时处理方式,值同 notFound 27 | accessDenied: 'home', 28 | }, 29 | // app 配置,同 https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#window 30 | app: { 31 | backgroundTextStyle: 'dark', 32 | navigationBarTextStyle: 'white', 33 | navigationBarTitleText: 'kbone-api', 34 | }, 35 | // 全局配置 36 | global: { 37 | share: true, // 是否支持分享,若支持,会展示分享按钮并调用 app 的 onShareAppMessage 按钮 38 | windowScroll: false, // 是否需要 window scroll 事件,会影响性能 39 | backgroundColor: '#F7F7F7', // page 的背景色 40 | pullDownRefresh: true, 41 | }, 42 | // 页面配置,可以为单个页面做个性化处理,覆盖全局配置 43 | pages: {}, 44 | // 项目配置,会被合并到 project.config.json 45 | projectConfig: { 46 | projectname: 'kbone-api', 47 | appid: '', 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const eslintFriendlyFormatter = require('eslint-friendly-formatter') 3 | 4 | const isProd = process.env.NODE_ENV === 'production' 5 | 6 | module.exports = { 7 | context: path.resolve(__dirname, '../'), 8 | entry: { 9 | app: path.resolve(__dirname, '../examples/main.js'), 10 | }, 11 | output: { 12 | path: path.resolve(__dirname, '../dist/web'), 13 | filename: '[name].js', 14 | publicPath: '/', 15 | }, 16 | module: { 17 | rules: [ 18 | // eslint 19 | { 20 | test: /\.(js|vue)$/, 21 | loader: 'eslint-loader', 22 | enforce: 'pre', 23 | include: [path.resolve(__dirname, '../examples')], 24 | options: { 25 | formatter: eslintFriendlyFormatter, 26 | emitWarning: true, 27 | }, 28 | }, 29 | // vue 30 | { 31 | test: /\.vue$/, 32 | use: [{ 33 | loader: 'thread-loader', 34 | }, { 35 | loader: 'vue-loader', 36 | options: { 37 | compilerOptions: { 38 | preserveWhitespace: false, 39 | }, 40 | }, 41 | }], 42 | }, 43 | // js 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [path.resolve(__dirname, '../examples')], 48 | }, 49 | { 50 | test: /\.tsx?$/, 51 | loaders: ['babel-loader',{ 52 | loader: 'ts-loader', 53 | options: { 54 | configFile: path.resolve(__dirname, '../tsconfig.json'), 55 | happyPackMode: true 56 | } 57 | }], 58 | }, 59 | // img res 60 | { 61 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 10000, 65 | name: path.posix.join('static', 'img/[name].[hash:7].[ext]'), 66 | }, 67 | }, 68 | // media res 69 | { 70 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 71 | loader: 'url-loader', 72 | options: { 73 | limit: 10000, 74 | name: path.posix.join('static', 'media/[name].[hash:7].[ext]'), 75 | }, 76 | }, 77 | // font res 78 | { 79 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 80 | loader: 'url-loader', 81 | options: { 82 | limit: 10000, 83 | name: path.posix.join('static', 'fonts/[name].[hash:7].[ext]'), 84 | }, 85 | } 86 | ], 87 | }, 88 | resolve: { 89 | extensions: ['.ts','.js', '.vue', '.json'], 90 | alias: { 91 | 'vue$': 'vue/dist/vue.esm.js', 92 | '@': path.resolve(__dirname, '../examples'), 93 | 'kbone-api': path.resolve(__dirname, '../src/index.ts') 94 | }, 95 | }, 96 | node: { 97 | // 避免 webpack 注入不必要的 setImmediate polyfill 因为 Vue 已经将其包含在内 98 | setImmediate: false, 99 | }, 100 | } 101 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const merge = require('webpack-merge') 3 | const baseWebpackConfig = require('./webpack.base.config') 4 | const HtmlWebpackPlugin = require('html-webpack-plugin') 5 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 6 | const portfinder = require('portfinder') 7 | const autoprefixer = require('autoprefixer') 8 | const {VueLoaderPlugin} = require('vue-loader') 9 | 10 | const devWebpackConfig = merge(baseWebpackConfig, { 11 | mode: 'development', 12 | devServer: { 13 | clientLogLevel: 'warning', 14 | historyApiFallback: { 15 | rewrites: [{from: /.*/, to: '/index.html'}], 16 | }, 17 | hot: true, 18 | contentBase: false, 19 | compress: true, 20 | host: process.env.HOST || 'localhost', 21 | port: +process.env.PORT || 8080, 22 | open: true, // 自动打开浏览器 23 | overlay: {warnings: false, errors: true}, // 展示全屏报错 24 | publicPath: '/', 25 | proxy: {}, 26 | quiet: true, // for FriendlyErrorsPlugin 27 | watchOptions: { 28 | poll: false, 29 | } 30 | }, 31 | module: { 32 | rules: [{ 33 | test: /\.(less|css)$/, 34 | use: [{ 35 | loader: 'vue-style-loader', 36 | }, { 37 | loader: 'css-loader', 38 | }, { 39 | loader: 'postcss-loader', 40 | options: { 41 | plugins: [ 42 | autoprefixer, 43 | ], 44 | } 45 | }, { 46 | loader: 'less-loader', 47 | }], 48 | }], 49 | }, 50 | devtool: 'cheap-module-eval-source-map', 51 | plugins: [ 52 | new webpack.DefinePlugin({ 53 | 'process.env': { 54 | NODE_ENV: '"development"', 55 | isMiniprogram:"false", 56 | }, 57 | }), 58 | new VueLoaderPlugin(), 59 | new webpack.HotModuleReplacementPlugin(), 60 | new webpack.NamedModulesPlugin(), // 开启 HMR 的时候使用该插件会显示模块的相对路径 61 | new webpack.NoEmitOnErrorsPlugin(), 62 | new HtmlWebpackPlugin({ 63 | filename: 'index.html', 64 | template: 'examples/template/index.html', 65 | inject: true, 66 | }), 67 | ], 68 | }) 69 | 70 | module.exports = new Promise((resolve, reject) => { 71 | portfinder.basePort = +process.env.PORT || 8080 72 | portfinder.getPort((err, port) => { 73 | if (err) { 74 | reject(err) 75 | } else { 76 | devWebpackConfig.devServer.port = port 77 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 78 | compilationSuccessInfo: { 79 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 80 | }, 81 | onErrors: undefined, 82 | })) 83 | 84 | resolve(devWebpackConfig) 85 | } 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /build/webpack.library.js: -------------------------------------------------------------------------------- 1 | const path = require("path") 2 | const ProgressBarPlugin = require("progress-bar-webpack-plugin") 3 | const eslintFriendlyFormatter = require('eslint-friendly-formatter') 4 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 5 | const TerserPlugin = require('terser-webpack-plugin') 6 | 7 | const isProd = process.env.NODE_ENV === 'production' 8 | 9 | module.exports = { 10 | devtool: isProd ? 'none' : 'cheap-module-eval-source-map', 11 | mode: isProd ? "production" : "development", 12 | entry: { 13 | index: path.join(process.cwd(), "src/index.ts"), 14 | }, 15 | output: { 16 | path: path.resolve(process.cwd(), "./lib"), 17 | filename: "[name].js", 18 | library: "kbone-api", 19 | libraryTarget: "umd", 20 | umdNamedDefine: true, 21 | libraryExport:"default", 22 | globalObject: 'this' 23 | }, 24 | module:{ 25 | rules: [ 26 | { 27 | test: /\.ts$/, 28 | loader: 'eslint-loader', 29 | enforce: 'pre', 30 | options: { 31 | formatter: eslintFriendlyFormatter, 32 | emitWarning: true, 33 | }, 34 | }, 35 | { 36 | test: /\.(tsx?|babel|es6)$/, 37 | include: process.cwd(), 38 | exclude: /node_modules|utils\/popper\.js|utils\/date.\js/, 39 | loaders: ['babel-loader',{ 40 | loader: 'ts-loader', 41 | options: isProd ? { 42 | configFile: path.resolve(__dirname, '../tsconfig.json'), 43 | happyPackMode: true 44 | } : { 45 | happyPackMode: true 46 | } 47 | }] 48 | }, 49 | { 50 | test: /\.css$/, 51 | loaders: ['style-loader', 'css-loader'] 52 | }, 53 | { 54 | test: /\.(svg|otf|ttf|woff2?|eot|gif|png|jpe?g)(\?\S*)?$/, 55 | loader: 'url-loader', 56 | query: { 57 | limit: 10000, 58 | name: path.posix.join('static', '[name].[hash:7].[ext]') 59 | } 60 | } 61 | ] 62 | }, 63 | resolve:{ 64 | extensions:['.js', '.ts','.json'], 65 | modules: ['node_modules'], 66 | }, 67 | optimization: { 68 | minimizer: [ 69 | new TerserPlugin({ 70 | terserOptions: { 71 | output: { 72 | comments: false 73 | } 74 | } 75 | }) 76 | ] 77 | }, 78 | plugins: [ 79 | new ProgressBarPlugin(), 80 | new FriendlyErrorsPlugin() 81 | ] 82 | }; 83 | -------------------------------------------------------------------------------- /build/webpack.mp.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 4 | const {VueLoaderPlugin} = require('vue-loader') 5 | const TerserPlugin = require('terser-webpack-plugin') 6 | const MpPlugin = require('mp-webpack-plugin') // 用于构建小程序代码的 webpack 插件 7 | const stylehacks = require('stylehacks') 8 | const autoprefixer = require('autoprefixer') 9 | const mpPluginConfig = require('./miniprogram.config.js') // 插件配置 10 | 11 | const isDevelop = process.env.NODE_ENV === 'development' 12 | const isOptimize = true // 是否压缩业务代码,开发者工具可能无法完美支持业务代码使用到的 es 特性,建议自己做代码压缩 13 | 14 | module.exports = { 15 | mode: 'production', 16 | entry: { 17 | // js 入口 18 | home: path.resolve(__dirname, '../examples/mp/main.mp.js'), 19 | }, 20 | output: { 21 | path: path.resolve(__dirname, '../dist/mp/common'), // 放到小程序代码目录中的 common 目录下 22 | filename: '[name].js', // 必需字段,不能修改 23 | library: 'createApp', // 必需字段,不能修改 24 | libraryExport: 'default', // 必需字段,不能修改 25 | libraryTarget: 'window', // 必需字段,不能修改 26 | }, 27 | watch: isDevelop, 28 | target: 'web', // 必需字段,不能修改 29 | optimization: { 30 | runtimeChunk: false, // 必需字段,不能修改 31 | splitChunks: { // 代码分割配置,不建议修改 32 | chunks: 'all', 33 | minSize: 1000, 34 | maxSize: 0, 35 | minChunks: 1, 36 | maxAsyncRequests: 100, 37 | maxInitialRequests: 100, 38 | automaticNameDelimiter: '~', 39 | name: true, 40 | cacheGroups: { 41 | vendors: { 42 | test: /[\\/]node_modules[\\/]/, 43 | priority: -10 44 | }, 45 | default: { 46 | minChunks: 2, 47 | priority: -20, 48 | reuseExistingChunk: true 49 | } 50 | } 51 | }, 52 | 53 | minimizer: isOptimize ? [ 54 | 55 | // 压缩 js 56 | new TerserPlugin({ 57 | test: /\.js(\?.*)?$/i, 58 | parallel: true, 59 | }) 60 | ] : [], 61 | }, 62 | module: { 63 | rules: [ 64 | // html 65 | { 66 | test: /\.html$/, 67 | loader: 'raw-loader' 68 | }, 69 | // css 70 | { 71 | test: /\.(less|css)$/, 72 | use: [ 73 | { 74 | loader: MiniCssExtractPlugin.loader, 75 | options: { 76 | modules: true, 77 | }, 78 | }, 79 | { 80 | loader: 'css-loader' 81 | }, 82 | { 83 | loader: 'postcss-loader', 84 | options: { 85 | ident: 'postcss', 86 | plugins: () => { 87 | return [ 88 | autoprefixer, 89 | stylehacks(), // 剔除 ie hack 代码 90 | ] 91 | } 92 | } 93 | }, 94 | { 95 | loader: 'less-loader' 96 | } 97 | ], 98 | }, 99 | // vue 100 | { 101 | test: /\.vue$/, 102 | use: [ 103 | 'thread-loader', 104 | { 105 | loader: 'vue-loader', 106 | options: { 107 | compilerOptions: { 108 | preserveWhitespace: false 109 | } 110 | } 111 | }, 112 | 'vue-improve-loader', 113 | ] 114 | }, 115 | // js 116 | { 117 | test: /\.js$/, 118 | use: [ 119 | 'thread-loader', 120 | { 121 | loader: 'babel-loader', 122 | options: { 123 | cacheDirectory: true, 124 | } 125 | } 126 | ], 127 | exclude: /node_modules/ 128 | }, 129 | { 130 | test: /\.tsx?$/, 131 | loaders: ['babel-loader',{ 132 | loader: 'ts-loader', 133 | options: { 134 | configFile: path.resolve(__dirname, '../tsconfig.json'), 135 | happyPackMode: true 136 | } 137 | }], 138 | }, 139 | // res 140 | { 141 | test: /\.(png|jpg|gif|svg|eot|woff|woff2|ttf)$/, 142 | use: [{ 143 | loader: 'url-loader', 144 | options: { 145 | limit: 1024, 146 | name: '[name]_[hash:hex:6].[ext]', 147 | publicPath: mpPluginConfig.origin + '/res', // 对于资源文件直接使用线上的 cdn 地址 148 | emitFile: false, 149 | } 150 | }], 151 | }, 152 | ] 153 | }, 154 | resolve: { 155 | extensions: ['.ts', '.js', '.vue', '.json'], 156 | alias: { 157 | 'vue$': 'vue/dist/vue.esm.js', 158 | '@': path.resolve(__dirname, '../examples'), 159 | 'kbone-api': path.resolve(__dirname, '../src/index.ts') 160 | }, 161 | }, 162 | plugins: [ 163 | new webpack.DefinePlugin({ 164 | 'process.env.isMiniprogram': process.env.isMiniprogram, // 注入环境变量,用于业务代码判断 165 | }), 166 | new MiniCssExtractPlugin({ 167 | filename: '[name].wxss', 168 | }), 169 | new VueLoaderPlugin(), 170 | new MpPlugin(mpPluginConfig), 171 | ], 172 | } 173 | -------------------------------------------------------------------------------- /build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webpack = require('webpack') 3 | const merge = require('webpack-merge') 4 | const baseWebpackConfig = require('./webpack.base.config') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 8 | const TerserPlugin = require('terser-webpack-plugin') 9 | const autoprefixer = require('autoprefixer') 10 | const {VueLoaderPlugin} = require('vue-loader') 11 | 12 | const webpackConfig = merge(baseWebpackConfig, { 13 | mode: 'production', 14 | output: { 15 | path: path.resolve(__dirname, '../dist/web'), 16 | filename: path.posix.join('static', 'js/[name].[chunkhash].js'), 17 | chunkFilename: path.posix.join('static', 'js/[id].[chunkhash].js') 18 | }, 19 | optimization: { 20 | splitChunks: { // 代码分割配置 21 | chunks: 'async', 22 | minSize: 30000, 23 | maxSize: 0, 24 | minChunks: 1, 25 | maxAsyncRequests: 5, 26 | maxInitialRequests: 3, 27 | automaticNameDelimiter: '~', 28 | name: true, 29 | cacheGroups: { 30 | vendors: { 31 | test: /[\\/]node_modules[\\/]/, 32 | priority: -10 33 | }, 34 | default: { 35 | minChunks: 2, 36 | priority: -20, 37 | reuseExistingChunk: true 38 | } 39 | } 40 | }, 41 | minimizer: [ 42 | // 压缩CSS 43 | new OptimizeCSSAssetsPlugin({ 44 | assetNameRegExp: /\.css$/g, 45 | cssProcessor: require('cssnano'), 46 | cssProcessorPluginOptions: { 47 | preset: ['default', { 48 | discardComments: { 49 | removeAll: true, 50 | }, 51 | }], 52 | }, 53 | canPrint: false 54 | }), 55 | // 压缩 js 56 | new TerserPlugin({ 57 | test: /\.js(\?.*)?$/i, 58 | parallel: true, 59 | }), 60 | ], 61 | }, 62 | devtool: false, 63 | module: { 64 | rules: [{ 65 | test: /\.(less|css)$/, 66 | use: [{ 67 | loader: MiniCssExtractPlugin.loader, 68 | }, { 69 | loader: 'css-loader', 70 | }, { 71 | loader: 'postcss-loader', 72 | options: { 73 | plugins: [ 74 | autoprefixer, 75 | ], 76 | } 77 | }, { 78 | loader: 'less-loader', 79 | }], 80 | }], 81 | }, 82 | plugins: [ 83 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 84 | new webpack.DefinePlugin({ 85 | 'process.env': { 86 | NODE_ENV: '"production"', 87 | }, 88 | }), 89 | new VueLoaderPlugin(), 90 | // 分离 css 文件 91 | new MiniCssExtractPlugin({ 92 | filename: path.posix.join('static', 'css/[name].[hash].css'), 93 | }), 94 | new HtmlWebpackPlugin({ 95 | filename: path.resolve(__dirname, '../dist/web/index.html'), 96 | template: 'index.html', 97 | inject: true, 98 | minify: { 99 | removeComments: true, 100 | collapseWhitespace: true, 101 | removeAttributeQuotes: true 102 | // 更多配置:https://github.com/kangax/html-minifier#options-quick-reference 103 | }, 104 | chunksSortMode: 'dependency' 105 | }), 106 | // 当 vendor 模块没有改变时,保证模块 id 不变 107 | new webpack.HashedModuleIdsPlugin(), 108 | ], 109 | }) 110 | 111 | module.exports = webpackConfig 112 | -------------------------------------------------------------------------------- /examples/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 20 | -------------------------------------------------------------------------------- /examples/example.less: -------------------------------------------------------------------------------- 1 | /* reset */ 2 | 3 | html, body { 4 | height: 100%; 5 | -webkit-tap-highlight-color: transparent; 6 | 7 | } 8 | 9 | 10 | body{ 11 | font-family: -apple-system-font, "Helvetica Neue", Helvetica, sans-serif; 12 | } 13 | 14 | ul{ 15 | list-style: none; 16 | } 17 | 18 | body, .page { 19 | background-color: #EDEDED; 20 | } 21 | .page{ 22 | box-sizing:border-box; 23 | } 24 | 25 | 26 | /* lib */ 27 | 28 | .link{ 29 | color: #07C160; 30 | } 31 | 32 | 33 | /* layout */ 34 | 35 | .container { 36 | top: 0; 37 | right: 0; 38 | bottom: 0; 39 | left: 0; 40 | overflow-x: hidden; 41 | overflow-y: auto; 42 | } 43 | 44 | .page { 45 | top: 0; 46 | right: 0; 47 | bottom: 0; 48 | left: 0; 49 | overflow-y: auto; 50 | -webkit-overflow-scrolling: touch; 51 | opacity: 0; 52 | z-index: 1; // fix 滑动几次后可滚动区域会卡住的问题 53 | &.js_show{ 54 | opacity: 1; 55 | } 56 | } 57 | 58 | .page__hd, .miniprogram-root .page__hd{ 59 | padding: 20px; 60 | } 61 | 62 | .page__bd {} 63 | 64 | .h5-body .page__bd_spacing,.page__bd_spacing { 65 | padding: 0 16px; 66 | } 67 | 68 | .page__ft{ 69 | padding-top: 40px; 70 | padding-bottom: 10px; 71 | padding-bottom: calc(10px ~"+ constant(safe-area-inset-bottom)"); 72 | padding-bottom: calc(10px ~"+ env(safe-area-inset-bottom)"); 73 | text-align: center; 74 | img{ 75 | height: 19px; 76 | } 77 | &.j_bottom{ 78 | position: absolute; 79 | bottom: 0; 80 | left: 0; 81 | right: 0; 82 | } 83 | } 84 | 85 | .page__title { 86 | text-align: left; 87 | font-size: 20px; 88 | font-weight: 400; 89 | } 90 | 91 | .page__desc,.h5-p { 92 | margin-top: 4px; 93 | 94 | color: rgba(0,0,0,.5); 95 | text-align: left; 96 | font-size: 14px; 97 | } 98 | 99 | 100 | /* widget */ 101 | 102 | .weui-cell_example{ 103 | &:before{ 104 | left: 52px; 105 | } 106 | } 107 | 108 | .page.progress{ 109 | background-color: #FFFFFF; 110 | } 111 | .page.home{ 112 | @pageHomePadding: 20px; 113 | .page__intro-icon{ 114 | margin-top: -.2em; 115 | margin-left: 5px; 116 | width: 16px; 117 | height: 16px; 118 | vertical-align: middle; 119 | } 120 | .page__title{ 121 | margin-bottom: 15px; 122 | } 123 | .page__bd{ 124 | img{ 125 | width: 30px; 126 | height: 30px; 127 | } 128 | li{ 129 | margin: 8px 0; 130 | background-color: #FFFFFF; 131 | overflow: hidden; 132 | border-radius: 2px; 133 | cursor: pointer; 134 | &.js_show{ 135 | .weui-flex{ 136 | opacity: .5; 137 | } 138 | .page__category{ 139 | height: auto; 140 | } 141 | .page__category-content{ 142 | opacity: 1; 143 | transform: translateY(0); 144 | } 145 | } 146 | &:first-child{ 147 | margin-top: 0; 148 | } 149 | } 150 | } 151 | .page__category{ 152 | height: 0; 153 | overflow: hidden; 154 | } 155 | .page__category-content{ 156 | opacity: 0; 157 | transform: translateY(-50%); 158 | transition: .3s; 159 | } 160 | .weui-flex{ 161 | padding: @pageHomePadding; 162 | align-items: center; 163 | transition: .3s; 164 | //&:active{ 165 | // background-color: #ECECEC; 166 | //} 167 | } 168 | .weui-cells{ 169 | margin-top: 0; 170 | &:before, &:after{ 171 | display: none; 172 | } 173 | } 174 | .weui-cell{ 175 | padding-left: @pageHomePadding; 176 | padding-right: @pageHomePadding; 177 | &:before{ 178 | left: @pageHomePadding; 179 | right: @pageHomePadding; 180 | } 181 | } 182 | } 183 | 184 | .page.form{ 185 | .weui-label{ 186 | width:3.1em; 187 | } 188 | } 189 | .page.form_page{ 190 | .weui-label{width:4.1em;} 191 | } 192 | .page.form_select{ 193 | .weui-cells__group_form{ 194 | .weui-cell_select-before { 195 | .weui-select{ 196 | width:3.1em; 197 | } 198 | } 199 | } 200 | } 201 | 202 | [class^="form_"], [class*=" form_"]{ 203 | &.page{ 204 | padding:0; 205 | } 206 | } 207 | 208 | .page.form_vcode, 209 | .page.form_input_status, 210 | .page.form_select, 211 | .page.form_select_primary{ 212 | .weui-label{width:3.1em;} 213 | } 214 | .page.button { 215 | background-color:#EDEDED; 216 | .weui-btn_mini{ 217 | vertical-align:middle; 218 | } 219 | .page__bd { 220 | padding: 0; 221 | } 222 | 223 | .button-sp-area { 224 | margin: 15px auto; 225 | padding: 15px; 226 | text-align:center; 227 | &.cell{padding:15px 0;} 228 | } 229 | } 230 | 231 | .page.cell { 232 | .page__bd { 233 | padding-bottom: 30px; 234 | } 235 | } 236 | 237 | .page.form { 238 | background-color: #FFFFFF; 239 | .page__bd { 240 | padding-bottom: 30px; 241 | } 242 | } 243 | 244 | .page.actionsheet{ 245 | background-color: #FFFFFF; 246 | } 247 | 248 | .page.dialog { 249 | background-color: #FFFFFF; 250 | .page__bd { 251 | padding: 0 15px; 252 | } 253 | } 254 | 255 | .page.msg, 256 | .page.msg_text, 257 | .page.msg_text_primary, 258 | .page.msg_success, 259 | .page.msg_warn { 260 | background-color: #FFFFFF; 261 | } 262 | 263 | .page.toast{ 264 | background-color: #FFFFFF; 265 | } 266 | 267 | .page.panel { 268 | .page__bd{ 269 | padding-bottom:20px; 270 | } 271 | } 272 | 273 | .page.article { 274 | background-color: #FFFFFF; 275 | } 276 | 277 | .page.icons-svg, 278 | .page.icons { 279 | text-align: center; 280 | .page__bd { 281 | padding: 0 40px; 282 | text-align: left; 283 | } 284 | .icon-box{ 285 | margin-bottom: 25px; 286 | display: flex; 287 | align-items: center; 288 | i{ 289 | margin-right: 18px; 290 | } 291 | } 292 | .icon-box__ctn{ 293 | flex-shrink: 100; 294 | } 295 | .icon-box__title{ 296 | font-weight: normal; 297 | } 298 | .icon-box__desc{ 299 | margin-top: 6px; 300 | font-size: 12px; 301 | color: #888888; 302 | } 303 | .icon_sp_area { 304 | margin-top: 10px; 305 | text-align: left; 306 | i:before{ 307 | margin-bottom: 5px; 308 | } 309 | } 310 | } 311 | 312 | .page.flex { 313 | .placeholder { 314 | margin: 5px; 315 | padding: 0 10px; 316 | background-color: #F7F7F7; 317 | height: 2.3em; 318 | line-height: 2.3em; 319 | text-align: center; 320 | color: rgba(0,0,0,.3); 321 | } 322 | } 323 | 324 | .page.loadmore{ 325 | background-color: #FFFFFF; 326 | } 327 | 328 | .page.layers{ 329 | @layerBaseTransform: translateX(15px) rotateX(45deg) rotateZ(10deg) skew(-15deg); 330 | @layerStartPos: 120px; 331 | @layerSpacing: 80px; 332 | @layerSmallStartPos: 140px; 333 | @layerSmallSpacing: 60px; 334 | 335 | overflow-x: hidden; 336 | perspective: 1000px; 337 | .page__hd{ 338 | @media only screen and (max-width: 320px) { 339 | padding-left: 20px; 340 | padding-right: 20px; 341 | } 342 | } 343 | .page__bd{ 344 | position: relative; 345 | } 346 | .page__desc{ 347 | min-height: 1.6 * 3em; 348 | } 349 | .layers__layer{ 350 | position: absolute; 351 | left: 50%; 352 | width: 150px; 353 | height: 266px; 354 | margin-left: -75px; 355 | box-sizing: border-box; 356 | transition: .5s; 357 | background: url(images/layers/transparent.gif) no-repeat center center; 358 | background-size: contain; 359 | font-size: 14px; 360 | color: #FFFFFF; 361 | span{ 362 | position: absolute; 363 | bottom: 5px; 364 | left: 0; 365 | right: 0; 366 | text-align: center; 367 | transition: .5s; 368 | } 369 | &:last-child{ 370 | span{ 371 | color: #AAAAAA; 372 | } 373 | } 374 | &.j_hide{ 375 | opacity: 0; 376 | } 377 | &.j_pic{ 378 | span{ 379 | color: transparent; 380 | } 381 | } 382 | @media only screen and (min-width: 375px) and (min-height: 603px) { 383 | width: 180px; 384 | height: 320px; 385 | margin-left: -90px; 386 | } 387 | @media only screen and (min-width: 414px) and (min-height: 640px) { 388 | width: 200px; 389 | height: 355px; 390 | margin-left: -100px; 391 | } 392 | } 393 | .layers__layer_popout{ 394 | border: 1px solid rgba(203, 203, 203, .5); 395 | z-index: 4; 396 | &.j_transform{ 397 | transform: @layerBaseTransform translateZ(@layerStartPos); 398 | @media only screen and (max-width: 320px) { 399 | transform: @layerBaseTransform translateZ(@layerSmallStartPos); 400 | } 401 | } 402 | &.j_pic{ 403 | border-color: transparent; 404 | background-image: url(images/layers/popout.png); 405 | } 406 | } 407 | .layers__layer_mask { 408 | background-color: rgba(0, 0, 0, 0.5); 409 | z-index: 3; 410 | &.j_transform{ 411 | transform: @layerBaseTransform translateZ(@layerStartPos - @layerSpacing); 412 | @media only screen and (max-width: 320px) { 413 | transform: @layerBaseTransform translateZ(@layerSmallStartPos - @layerSmallSpacing); 414 | } 415 | } 416 | } 417 | .layers__layer_navigation { 418 | background-color: rgba(40, 187, 102, 0.5); 419 | z-index: 2; 420 | &.j_transform{ 421 | transform: @layerBaseTransform translateZ(@layerStartPos - 2 * @layerSpacing); 422 | @media only screen and (max-width: 320px) { 423 | transform: @layerBaseTransform translateZ(@layerSmallStartPos - 2 * @layerSmallSpacing); 424 | } 425 | } 426 | &.j_pic{ 427 | background-color: transparent; 428 | background-image: url(images/layers/navigation.png); 429 | } 430 | } 431 | .layers__layer_content{ 432 | background-color: #FFFFFF; 433 | z-index: 1; 434 | &.j_transform{ 435 | transform: @layerBaseTransform translateZ(@layerStartPos - 3 * @layerSpacing); 436 | @media only screen and (max-width: 320px) { 437 | transform: @layerBaseTransform translateZ(@layerSmallStartPos - 3 * @layerSmallSpacing); 438 | } 439 | } 440 | &.j_pic{ 441 | background-image: url(images/layers/content.png); 442 | } 443 | } 444 | } 445 | 446 | .page.searchbar{ 447 | .searchbar-result { 448 | display: none; 449 | margin-top: 0; 450 | font-size: 14px; 451 | .weui-cell__bd{ 452 | padding:2px 0 2px 20px; 453 | color:#666; 454 | } 455 | } 456 | } 457 | 458 | .page.actionsheet{ 459 | overflow: hidden; 460 | } 461 | 462 | .page.picker{ 463 | background-color: #FFFFFF; 464 | overflow: hidden; 465 | } 466 | 467 | .page.gallery{ 468 | overflow: hidden; 469 | } 470 | 471 | .weui-half-screen-dialog{ 472 | transition:transform .3s; 473 | transform:translateY(100%); 474 | } 475 | .weui-half-screen-dialog_show{ 476 | transform:translateY(0); 477 | } 478 | 479 | 480 | /* animation */ 481 | 482 | @keyframes slideIn { 483 | from { 484 | transform: translate3d(100%, 0, 0); 485 | opacity: 0; 486 | } 487 | to { 488 | transform: translate3d(0, 0, 0); 489 | opacity: 1; 490 | } 491 | } 492 | 493 | @keyframes slideOut { 494 | from { 495 | transform: translate3d(0, 0, 0); 496 | opacity: 1; 497 | } 498 | to { 499 | transform: translate3d(100%, 0, 0); 500 | opacity: 0; 501 | } 502 | } 503 | 504 | .page.slideIn { 505 | animation: slideIn .2s forwards; 506 | } 507 | 508 | .page.slideOut { 509 | animation: slideOut .2s forwards; 510 | } 511 | 512 | // iphone x 513 | @supports (top: constant(safe-area-inset-top)){ 514 | .page{ 515 | padding:constant(safe-area-inset-top) constant(safe-area-inset-right) constant(safe-area-inset-bottom) constant(safe-area-inset-left); 516 | &.tabbar,&.navbar{ 517 | padding-left:0; 518 | padding-right:0; 519 | } 520 | } 521 | .weui-tab__panel{ 522 | padding-left:constant(safe-area-inset-left); 523 | padding-right:constant(safe-area-inset-right); 524 | } 525 | } 526 | @supports (top: env(safe-area-inset-top)){ 527 | .page{ 528 | padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); 529 | &.tabbar,&.navbar,&.msg_success,&.msg_warn,&.msg_text,&.msg_text_primary,&.article{ 530 | padding:0; 531 | } 532 | } 533 | } -------------------------------------------------------------------------------- /examples/images/icon_footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_footer.png -------------------------------------------------------------------------------- /examples/images/icon_footer_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_footer_link.png -------------------------------------------------------------------------------- /examples/images/icon_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_intro.png -------------------------------------------------------------------------------- /examples/images/icon_nav_feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_feedback.png -------------------------------------------------------------------------------- /examples/images/icon_nav_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_form.png -------------------------------------------------------------------------------- /examples/images/icon_nav_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_layout.png -------------------------------------------------------------------------------- /examples/images/icon_nav_nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_nav.png -------------------------------------------------------------------------------- /examples/images/icon_nav_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_search.png -------------------------------------------------------------------------------- /examples/images/icon_nav_special.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_special.png -------------------------------------------------------------------------------- /examples/images/icon_nav_z-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_nav_z-index.png -------------------------------------------------------------------------------- /examples/images/icon_tabbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/icon_tabbar.png -------------------------------------------------------------------------------- /examples/images/layers/content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/layers/content.png -------------------------------------------------------------------------------- /examples/images/layers/navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/layers/navigation.png -------------------------------------------------------------------------------- /examples/images/layers/popout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/layers/popout.png -------------------------------------------------------------------------------- /examples/images/layers/transparent.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/layers/transparent.gif -------------------------------------------------------------------------------- /examples/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/logo.png -------------------------------------------------------------------------------- /examples/images/pic_160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/pic_160.png -------------------------------------------------------------------------------- /examples/images/pic_article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/pic_article.png -------------------------------------------------------------------------------- /examples/images/vcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wechat-miniprogram/kbone-api/9dd4df880b2c1a33408dbabb3dc19a8c2453fc2b/examples/images/vcode.jpg -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import Vue from "vue" 3 | // eslint-disable-next-line 4 | import KboneUI from "kbone-ui" 5 | // eslint-disable-next-line 6 | import "kbone-ui/lib/weui/weui.css" 7 | // eslint-disable-next-line 8 | import {sync} from "vuex-router-sync" 9 | import router from "./route/index" 10 | import store from "./store/index" 11 | import App from "./App.vue" 12 | import KboneAPI from "../src/index" 13 | 14 | sync(store, router) 15 | Vue.use(KboneUI) 16 | Vue.use(KboneAPI) 17 | 18 | // eslint-disable-next-line no-new 19 | new Vue({ 20 | el: "#app", 21 | router, 22 | store, 23 | render: h => h(App), 24 | }) 25 | -------------------------------------------------------------------------------- /examples/mp/main.mp.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import {sync} from 'vuex-router-sync' 3 | import store from '../store' 4 | import router from '../route/index' 5 | import App from "../App.vue" 6 | import KboneUI from "kbone-ui" 7 | import KboneAPI from "../../src/index" 8 | import "kbone-ui/lib/weui/weui.css" 9 | 10 | Vue.use(KboneUI) 11 | Vue.use(KboneAPI) 12 | 13 | export default function createApp() { 14 | const container = document.createElement('div') 15 | container.id = 'app' 16 | document.body.appendChild(container) 17 | 18 | sync(store, router) 19 | 20 | return new Vue({ 21 | el: '#app', 22 | router, 23 | store, 24 | render: h => h(App) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /examples/route/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | index: "/(home|index)?", 3 | pages: { 4 | interaction: "/api/interaction", 5 | login: "/api/login", 6 | titleBar: "/api/titlebar", 7 | pullDown: "/api/pullDown", 8 | request: "/api/request", 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/route/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import Vue from "vue" 3 | // eslint-disable-next-line 4 | import Router from "vue-router" 5 | import routes from "./config" 6 | import Home from "../view/home/index.vue" 7 | import Interaction from "../view/components/interaction.vue" 8 | import Login from "../view/components/login.vue" 9 | import TitleBar from "../view/components/titleBar.vue" 10 | import PullDown from "../view/components/pulldown.vue" 11 | import Request from "../view/components/request.vue" 12 | 13 | Vue.use(Router) 14 | 15 | export default new Router({ 16 | mode: "history", 17 | routes: [ 18 | { 19 | path: routes.index, 20 | name: "Index", 21 | component: Home 22 | }, 23 | { 24 | path: routes.pages.interaction, 25 | name: "Interaction", 26 | component: Interaction 27 | }, 28 | { 29 | path: routes.pages.login, 30 | name: "login", 31 | component: Login 32 | }, 33 | { 34 | path: routes.pages.titleBar, 35 | name: "TitleBar", 36 | component: TitleBar 37 | }, 38 | { 39 | path: routes.pages.pullDown, 40 | name: "PullDown", 41 | component: PullDown 42 | }, 43 | { 44 | path: routes.pages.request, 45 | name: "Request", 46 | component: Request 47 | } 48 | ] 49 | }) 50 | -------------------------------------------------------------------------------- /examples/store/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | FAKE_ACTION({commit}, input) { 3 | setTimeout(() => { 4 | commit("FAKE_MUTATION", input) 5 | }, 500) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/store/index.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import Vue from "vue" 3 | // eslint-disable-next-line 4 | import Vuex from "vuex" 5 | import actions from "./actions" 6 | import mutations from "./mutations" 7 | 8 | Vue.use(Vuex) 9 | 10 | export default new Vuex.Store({ 11 | state: { 12 | input: "", 13 | }, 14 | actions, 15 | mutations, 16 | }) 17 | -------------------------------------------------------------------------------- /examples/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | FAKE_MUTATION(state, input) { 3 | state.input = input 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | kbone-api 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/view/components/interaction.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 118 | -------------------------------------------------------------------------------- /examples/view/components/login.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /examples/view/components/pulldown.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /examples/view/components/request.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 61 | -------------------------------------------------------------------------------- /examples/view/components/titleBar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 53 | -------------------------------------------------------------------------------- /examples/view/home/index.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 118 | -------------------------------------------------------------------------------- /examples/view/utils/config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /** 3 | * 小程序配置文件 4 | */ 5 | 6 | // 此处主机域名是腾讯云解决方案分配的域名 7 | // 小程序后台服务解决方案:https://www.qcloud.com/solution/la 8 | 9 | const host = '14592619.qcloud.la' 10 | 11 | const config = { 12 | 13 | // 下面的地址配合云端 Server 工作 14 | host, 15 | 16 | // 登录地址,用于建立会话 17 | loginUrl: `https://${host}/login`, 18 | 19 | // 测试的请求地址,用于测试会话 20 | requestUrl: `https://${host}/testRequest`, 21 | 22 | // 用code换取openId 23 | openIdUrl: `https://${host}/openid`, 24 | 25 | // 测试的信道服务接口 26 | tunnelUrl: `https://${host}/tunnel`, 27 | 28 | // 生成支付订单的接口 29 | paymentUrl: `https://${host}/payment`, 30 | 31 | // 发送模板消息接口 32 | templateMessageUrl: `https://${host}/templateMessage`, 33 | 34 | // 发送订阅消息接口 35 | subscribeMessageUrl: `https://${host}/subscribeMessage`, 36 | 37 | // 上传文件接口 38 | uploadFileUrl: `https://${host}/upload`, 39 | 40 | // 下载示例图片接口 41 | downloadExampleUrl: `https://${host}/static/weapp.jpg`, 42 | 43 | // 云开发环境 ID 44 | envId: 'release-b86096', 45 | 46 | // 云开发-存储 示例文件的文件 ID 47 | demoImageFileId: 'cloud://release-b86096.7265-release-b86096/demo.jpg', 48 | demoVideoFileId: 'cloud://release-b86096.7265-release-b86096/demo.mp4', 49 | } 50 | 51 | module.exports = config 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kbone-api", 3 | "version": "0.2.1", 4 | "description": "common api for developing cross-platform app", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "dev:web": "cross-env NODE_ENV=development webpack-dev-server --inline --progress --config build/webpack.dev.config.js", 8 | "dev:mp": "cross-env NODE_ENV=development webpack --config build/webpack.mp.config.js --progress --hide-modules", 9 | "build": "cross-env NODE_ENV=production webpack --config build/webpack.library.js --progress --hide-modules", 10 | "dev": "cross-env NODE_ENV=development webpack --config build/webpack.library.js --progress --hide-modules --watch", 11 | "test": "jest --colors --config .jestrc.json", 12 | "coverage": "jest --colors --coverage --config .jestrc.json", 13 | "cov": "cat ./coverage/lcov.info | coveralls" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/wechat-miniprogram/kbone-ui.git" 18 | }, 19 | "keywords": [ 20 | "kbone-api", 21 | "kbone", 22 | "kbone-ui" 23 | ], 24 | "author": "wechat-miniprogram", 25 | "license": "MIT", 26 | "browserslist": [ 27 | "ie > 9", 28 | "last 2 versions" 29 | ], 30 | "typings": "typings/index.d.ts", 31 | "devDependencies": { 32 | "@babel/core": "^7.0.0", 33 | "@babel/plugin-transform-runtime": "^7.0.0", 34 | "@babel/preset-env": "^7.0.0", 35 | "@babel/runtime": "^7.7.7", 36 | "@typescript-eslint/eslint-plugin": "^2.15.0", 37 | "@typescript-eslint/parser": "^2.15.0", 38 | "@vue/test-utils": "^1.0.0-beta.30", 39 | "autoprefixer": "^7.1.2", 40 | "babel-eslint": "^9.0.0", 41 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 42 | "babel-jest": "^24.9.0", 43 | "babel-loader": "^8.0.0", 44 | "coveralls": "^3.0.9", 45 | "cross-env": "^6.0.3", 46 | "css-loader": "^0.28.0", 47 | "eslint": "^6.4.0", 48 | "eslint-config-airbnb": "^18.0.1", 49 | "eslint-config-airbnb-base": "^11.3.0", 50 | "eslint-friendly-formatter": "^3.0.0", 51 | "eslint-import-resolver-webpack": "^0.8.3", 52 | "eslint-loader": "^3.0.2", 53 | "eslint-plugin-html": "^6.0.0", 54 | "eslint-plugin-import": "^2.7.0", 55 | "eslint-plugin-json": "^1.4.0", 56 | "eslint-plugin-node": "^10.0.0", 57 | "eslint-plugin-promise": "^4.2.1", 58 | "eslint-plugin-vue": "^6.1.2", 59 | "file-loader": "^1.1.4", 60 | "friendly-errors-webpack-plugin": "^1.6.1", 61 | "globby": "^10.0.1", 62 | "html-webpack-plugin": "^3.2.0", 63 | "jest": "^24.9.0", 64 | "kbone-ui": "^0.5.10", 65 | "less": "^3.10.3", 66 | "less-loader": "^5.0.0", 67 | "mini-css-extract-plugin": "^0.5.0", 68 | "mp-webpack-plugin": "^0.5.10", 69 | "node-notifier": "^5.1.2", 70 | "optimize-css-assets-webpack-plugin": "^5.0.3", 71 | "portfinder": "^1.0.13", 72 | "postcss-loader": "^3.0.0", 73 | "progress-bar-webpack-plugin": "^1.12.1", 74 | "reduce-loader": "^0.1.1", 75 | "rimraf": "^2.7.1", 76 | "sinon": "^8.0.2", 77 | "terser-webpack-plugin": "^2.2.1", 78 | "thread-loader": "^2.1.3", 79 | "ts-loader": "^6.2.1", 80 | "typescript": "^3.7.4", 81 | "url-loader": "^0.5.8", 82 | "vue": "^2.6.10", 83 | "vue-improve-loader": "^0.3.1", 84 | "vue-loader": "^15.8.3", 85 | "vue-router": "^3.0.1", 86 | "vue-template-compiler": "^2.6.10", 87 | "vuex": "^3.1.1", 88 | "vuex-router-sync": "^5.0.0", 89 | "webpack": "^4.29.6", 90 | "webpack-cli": "^3.2.3", 91 | "webpack-dev-server": "^3.8.1", 92 | "webpack-merge": "^4.1.0" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/api/index.d.ts: -------------------------------------------------------------------------------- 1 | export type AnyObj = {[key in string]: any} 2 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from "../interaction/index" 2 | export * from "../network/request" 3 | -------------------------------------------------------------------------------- /src/enums/index.ts: -------------------------------------------------------------------------------- 1 | export enum envVar { 2 | // eslint-disable-next-line 3 | web = "web", 4 | // eslint-disable-next-line 5 | wx = "wx" 6 | } 7 | -------------------------------------------------------------------------------- /src/fake/asyncApis.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import {AnyObj} from "../api/index.d" 3 | import intercetorApi, {wrapFn} from "../wxapi/index" 4 | 5 | function getSystemInfo(params: AnyObj) { 6 | if (params.success) { 7 | params.success({ 8 | SDKVersion: "2.0.4", 9 | batteryLevel: 100, 10 | benchmarkLevel: 1, 11 | brand: "devtools", 12 | fontSizeSetting: 16, 13 | language: "en", 14 | model: "iPhone 5", 15 | pixelRatio: 2, 16 | platform: "devtools", 17 | screenHeight: 568, 18 | screenWidth: 320, 19 | statusBarHeight: 20, 20 | system: "iOS 10.0.1", 21 | version: "7.0.4", 22 | windowHeight: 504, 23 | windowWidth: 320, 24 | }) 25 | } 26 | } 27 | 28 | function loadFontFace(params: AnyObj) { 29 | if (params.success) { 30 | params.success({ 31 | status: "loaded" 32 | }) 33 | } 34 | } 35 | 36 | function getSelectedTextRange(params: AnyObj) { 37 | if (params.success) { 38 | params.success({ 39 | start: 0, 40 | end: 0 41 | }) 42 | } 43 | } 44 | 45 | function downloadFile(params: AnyObj) { 46 | if (params.success) { 47 | params.success({ 48 | statusCode: 400 49 | }) 50 | } 51 | } 52 | 53 | function uploadFile(params: AnyObj) { 54 | if (params.success) { 55 | params.success({ 56 | statusCode: 400 57 | }) 58 | } 59 | } 60 | 61 | function connectSocket(params: AnyObj) { 62 | if (params.fail) { 63 | params.fail() 64 | } 65 | } 66 | 67 | function closeSocket(params: AnyObj) { 68 | if (params.fail) { 69 | params.fail() 70 | } 71 | } 72 | 73 | 74 | const asyncApis = [ 75 | "setEnableDebug", 76 | "switchTab", 77 | "reLaunch", 78 | "redirectTo", 79 | "navigateTo", 80 | "navigateBack", 81 | // 导航栏设置 82 | "showNavigationBarLoading", 83 | "setNavigationBarTitle", 84 | "setNavigationBarColor", 85 | "hideNavigationBarLoading", 86 | "hideHomeButton", 87 | "setBackgroundTextStyle", 88 | "setBackgroundColor", 89 | "showTabBarRedDot", 90 | "showTabBar", 91 | "setTabBarStyle", 92 | "setTabBarItem", 93 | "setTabBarBadge", 94 | "removeTabBarBadge", 95 | "hideTabBarRedDot", 96 | "hideTabBar", 97 | // 下拉刷新 98 | "stopPullDownRefresh", 99 | "startPullDownRefresh", 100 | // 滚动 101 | "pageScrollTo", 102 | "setTopBarText", 103 | "nextTick", 104 | // 键盘 105 | "hideKeyboard", 106 | // wesocket 107 | "sendSocketMessage", 108 | 109 | // mDNS 110 | "stopLocalServiceDiscovery", 111 | "startLocalServiceDiscovery", 112 | 113 | // 数据缓存 114 | "setStorage", 115 | "removeStorage", 116 | "getStorageInfo", 117 | "getStorage", 118 | "clearStorage", 119 | 120 | // 周期性更新 121 | "setBackgroundFetchToken", 122 | "onBackgroundFetchData", 123 | "getBackgroundFetchToken", 124 | "getBackgroundFetchData", 125 | 126 | // 图片选择 127 | "saveImageToPhotosAlbum", 128 | "previewImage", 129 | "getImageInfo", 130 | "compressImage", 131 | "chooseMessageFile", 132 | "chooseImage", 133 | 134 | // 视频 135 | "saveVideoToPhotosAlbum", 136 | "chooseVideo", 137 | "chooseMedia", 138 | 139 | // 音频 140 | "stopVoice", 141 | "setInnerAudioOption", 142 | "playVoice", 143 | "pauseVoice", 144 | "getAvailableAudioSources", 145 | 146 | // 背景音频 147 | "stopBackgroundAudio", 148 | "seekBackgroundAudio", 149 | "playBackgroundAudio", 150 | "pauseBackgroundAudio", 151 | "getBackgroundAudioPlayerState", 152 | 153 | // 录音 154 | "startRecord", 155 | "stopRecord", 156 | 157 | // 位置 158 | "stopLocationUpdate", 159 | "startLocationUpdateBackground", 160 | "startLocationUpdate", 161 | "openLocation", 162 | "getLocation", 163 | "chooseLocation", 164 | 165 | // 转发 166 | "updateShareMenu", 167 | "showShareMenu", 168 | "hideShareMenu", 169 | "getShareInfo", 170 | 171 | // 画布 172 | "canvasToTempFilePath", 173 | "canvasPutImageData", 174 | "canvasGetImageData", 175 | 176 | // 文件 177 | "saveFile", 178 | "removeSavedFile", 179 | "openDocument", 180 | "getSavedFileList", 181 | "getSavedFileInfo", 182 | "getFileInfo", 183 | 184 | // 开放接口 185 | "login", 186 | "checkSession", 187 | 188 | "navigateToMiniProgram", 189 | "navigateBackMiniProgram", 190 | 191 | "getUserInfo", 192 | "reportMonitor", 193 | "reportAnalytics", 194 | "requestPayment", 195 | "authorize", 196 | "openSetting", 197 | "getSetting", 198 | "chooseAddress", 199 | "openCard", 200 | "addCard", 201 | "chooseInvoiceTitle", 202 | "chooseInvoice", 203 | "startSoterAuthentication", 204 | "checkIsSupportSoterAuthentication", 205 | "checkIsSoterEnrolledInDevice", 206 | "getWeRunData", 207 | "requestSubscribeMessage", 208 | "showRedPackage", 209 | 210 | // 设备 211 | "stopBluetoothDevicesDiscovery", 212 | "startBluetoothDevicesDiscovery", 213 | "openBluetoothAdapter", 214 | "getConnectedBluetoothDevices", 215 | "getBluetoothDevices", 216 | "getBluetoothAdapterState", 217 | "closeBluetoothAdapter", 218 | 219 | // iBeacon 220 | "stopBeaconDiscovery", 221 | "startBeaconDiscovery", 222 | "getBeacons", 223 | 224 | // wifi 225 | "stopWifi", 226 | "startWifi", 227 | "setWifiList", 228 | "getWifiList", 229 | "getConnectedWifi", 230 | "connectWifi", 231 | 232 | // 联系人 233 | "addPhoneContact", 234 | 235 | // 蓝牙 236 | "writeBLECharacteristicValue", 237 | "readBLECharacteristicValue", 238 | "notifyBLECharacteristicValueChange", 239 | "getBLEDeviceServices", 240 | "getBLEDeviceCharacteristics", 241 | "createBLEConnection", 242 | "closeBLEConnection", 243 | 244 | // battery 245 | "getBatteryInfo", 246 | 247 | // clipboard 248 | "setClipboardData", 249 | "getClipboardData", 250 | 251 | // NFC 252 | "stopHCE", 253 | "startHCE", 254 | "sendHCEMessage", 255 | "getHCEState", 256 | "getNetworkType", 257 | 258 | // 屏幕 259 | "setScreenBrightness", 260 | "setKeepScreenOn", 261 | "getScreenBrightness", 262 | 263 | // phone 264 | "makePhoneCall", 265 | 266 | // accelerometer 267 | "stopAccelerometer", 268 | "startAccelerometer", 269 | 270 | // compass 271 | "stopCompass", 272 | "startCompass", 273 | 274 | // deviceMotion 275 | "stopDeviceMotionListening", 276 | "startDeviceMotionListening", 277 | 278 | // gyroscope 279 | "stopGyroscope", 280 | "startGyroscope", 281 | 282 | // scanCode 283 | "scanCode", 284 | 285 | // vibrate 286 | "vibrateShort", 287 | "vibrateLong", 288 | ] 289 | 290 | const apis: {[key in string]: (params: AnyObj) => void} = {} 291 | 292 | asyncApis.forEach(key => { 293 | apis[key] = function(params: AnyObj) { 294 | if (params.success) { 295 | params.success({}) 296 | } 297 | } 298 | }) 299 | 300 | // 异步处理 301 | const promiseApis: {[key in string]: (params: AnyObj) => void} = {} 302 | 303 | intercetorApi(promiseApis, apis) 304 | 305 | promiseApis.getSystemInfo = wrapFn(getSystemInfo) 306 | promiseApis.loadFontFace = wrapFn(loadFontFace) 307 | promiseApis.getSelectedTextRange = wrapFn(getSelectedTextRange) 308 | promiseApis.downloadFile = wrapFn(downloadFile) 309 | promiseApis.uploadFile = wrapFn(uploadFile) 310 | promiseApis.connectSocket = wrapFn(connectSocket) 311 | promiseApis.closeSocket = wrapFn(closeSocket) 312 | 313 | export default promiseApis 314 | -------------------------------------------------------------------------------- /src/fake/index.ts: -------------------------------------------------------------------------------- 1 | import asyncApis from "./asyncApis" 2 | import noopApis from "./noopApis" 3 | import * as syncApis from "./syncApis" 4 | 5 | 6 | const apis = Object.assign({}, asyncApis, noopApis, syncApis) 7 | 8 | export default apis 9 | -------------------------------------------------------------------------------- /src/fake/noopApis.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import {AnyObj} from "../api/index.d" 3 | 4 | // noop functionis 5 | const plainApisEnums = [ 6 | /** 7 | * 应用级别事件 8 | */ 9 | "onUnhandledRejection", 10 | "onPageNotFound", 11 | "onError", 12 | "onAudioInterruptionEnd", 13 | "onAudioInterruptionBegin", 14 | "onAppShow", 15 | "onAppHide", 16 | "offUnhandledRejection", 17 | "offPageNotFound", 18 | "offError", 19 | "offAudioInterruptionEnd", 20 | "offAudioInterruptionBegin", 21 | "offAppShow", 22 | "offAppHide", 23 | 24 | // 窗口监听 25 | "onWindowResize", 26 | "offWindowResize", 27 | "onKeyboardHeightChange", 28 | 29 | // websocket 30 | "onSocketOpen", 31 | "onSocketMessage", 32 | "onSocketError", 33 | "onSocketClose", 34 | "onLocalServiceResolveFail", 35 | "onLocalServiceLost", 36 | "onLocalServiceFound", 37 | "onLocalServiceDiscoveryStop", 38 | "offLocalServiceResolveFail", 39 | "offLocalServiceLost", 40 | "offLocalServiceFound", 41 | "offLocalServiceDiscoveryStop", 42 | 43 | // 数据缓存 44 | "setStorageSync", 45 | "removeStorageSync", 46 | "getStorageSync", 47 | "getStorageInfoSync", 48 | "clearStorageSync", 49 | 50 | // 背景音频 51 | "onBackgroundAudioStop", 52 | "onBackgroundAudioPlay", 53 | "onBackgroundAudioPause", 54 | 55 | // 位置 56 | "onLocationChange", 57 | "offLocationChange", 58 | 59 | // 性能上报 60 | "reportPerformance", 61 | 62 | "onBluetoothDeviceFound", 63 | "onBluetoothAdapterStateChange", 64 | "offBluetoothDeviceFound", 65 | "offBluetoothAdapterStateChange", 66 | 67 | // iBeacon 68 | "onBeaconUpdate", 69 | "onBeaconServiceChange", 70 | "offBeaconUpdate", 71 | "offBeaconServiceChange", 72 | 73 | // wifi 74 | "onWifiConnected", 75 | "onGetWifiList", 76 | "offWifiConnected", 77 | "offGetWifiList", 78 | 79 | // bluetooth 80 | "onBLEConnectionStateChange", 81 | "onBLECharacteristicValueChange", 82 | "offBLEConnectionStateChange", 83 | "offBLECharacteristicValueChange", 84 | 85 | // battery 86 | "getBatteryInfoSync", 87 | 88 | // on/off 89 | "onHCEMessage", 90 | "offHCEMessage", 91 | 92 | // networkStatus 93 | "onNetworkStatusChange", 94 | "offNetworkStatusChange", 95 | 96 | // 屏幕 97 | "onUserCaptureScreen", 98 | "offUserCaptureScreen", 99 | 100 | "onAccelerometerChange", 101 | "offAccelerometerChange", 102 | 103 | // compass 104 | "onCompassChange", 105 | "offCompassChange", 106 | 107 | // deviceMotion Event 108 | "onDeviceMotionChange", 109 | "offDeviceMotionChange", 110 | 111 | // gyroscope 112 | "onGyroscopeChange", 113 | "offGyroscopeChange", 114 | 115 | // memory 116 | "onMemoryWarning", 117 | "offMemoryWarning", 118 | ] 119 | 120 | const plainApis: {[key in string]: (params: AnyObj)=>void} = {} 121 | 122 | plainApisEnums.forEach(api => { 123 | plainApis[api] = function() {} 124 | }) 125 | 126 | 127 | export default plainApis 128 | -------------------------------------------------------------------------------- /src/fake/syncApis.ts: -------------------------------------------------------------------------------- 1 | 2 | export function canIUse() { 3 | return false 4 | } 5 | 6 | export function base64ToArrayBuffer(base64: string) { 7 | const binaryString = atob(base64) 8 | const len = binaryString.length 9 | const bytes = new Uint8Array(len) 10 | for (let i = 0; i < len; i++) { 11 | bytes[i] = binaryString.charCodeAt(i) 12 | } 13 | return bytes.buffer 14 | } 15 | 16 | export function arrayBufferToBase64(buffer: ArrayBuffer) { 17 | let binaryString = "" 18 | const bytes = new Uint8Array(buffer) 19 | const len = bytes.byteLength 20 | for (let i = 0; i < len; i++) { 21 | binaryString += String.fromCharCode(bytes[i]) 22 | } 23 | return btoa(binaryString) 24 | } 25 | 26 | 27 | export function getSystemInfoSync() { 28 | return { 29 | SDKVersion: "2.0.4", 30 | batteryLevel: 100, 31 | benchmarkLevel: 1, 32 | brand: "devtools", 33 | fontSizeSetting: 16, 34 | language: "en", 35 | model: "iPhone 5", 36 | pixelRatio: 2, 37 | platform: "devtools", 38 | screenHeight: 568, 39 | screenWidth: 320, 40 | statusBarHeight: 20, 41 | system: "iOS 10.0.1", 42 | version: "7.0.4", 43 | windowHeight: 504, 44 | windowWidth: 320, 45 | } 46 | } 47 | 48 | class UpdateManager { 49 | applyUpdate() { } 50 | onCheckForUpdate() { } 51 | onUpdateFailed() { } 52 | onUpdateReady() { } 53 | } 54 | 55 | export function getUpdateManager() { 56 | return new UpdateManager() 57 | } 58 | 59 | /** 60 | * 声明周期 61 | */ 62 | export function getLaunchOptionsSync() { 63 | return { 64 | path: "pages/index/index", 65 | query: {}, 66 | referrerInfo: {}, 67 | scene: 1001, 68 | shareTicket: "" 69 | } 70 | } 71 | 72 | export function getEnterOptionsSync() { 73 | return { 74 | path: "pages/index/index", 75 | query: {}, 76 | referrerInfo: {}, 77 | scene: 1001, 78 | shareTicket: "" 79 | } 80 | } 81 | 82 | 83 | /** 84 | * 调试 Sync api 85 | */ 86 | 87 | class RealtimeLogManager { 88 | addFilterMsg() { } 89 | error() { } 90 | in() { } 91 | info() { } 92 | setFilterMsg() { } 93 | warn() { } 94 | } 95 | 96 | export function getRealtimeLogManager() { 97 | return new RealtimeLogManager() 98 | } 99 | 100 | class LogManager { 101 | debug() { } 102 | info() { } 103 | log() { } 104 | warn() { } 105 | } 106 | 107 | export function getLogManager() { 108 | return new LogManager() 109 | } 110 | 111 | // 动画 112 | class Animation { 113 | backgroundColor() { return this } 114 | bottom() { return this } 115 | export() { return this } 116 | height() { return this } 117 | left() { return this } 118 | matrix() { return this } 119 | matrix3d() { return this } 120 | opacity() { return this } 121 | right() { return this } 122 | rotate() { return this } 123 | rotate3d() { return this } 124 | rotateX() { return this } 125 | rotateY() { return this } 126 | rotateZ() { return this } 127 | scale() { return this } 128 | scale3d() { return this } 129 | scaleX() { return this } 130 | scaleY() { return this } 131 | scaleZ() { return this } 132 | skew() { return this } 133 | skewX() { return this } 134 | skewY() { return this } 135 | step() { return this } 136 | top() { return this } 137 | translate() { return this } 138 | translate3d() { return this } 139 | translateX() { return this } 140 | translateY() { return this } 141 | translateZ() { return this } 142 | width() { return this } 143 | } 144 | 145 | export function createAnimation() { 146 | return new Animation() 147 | } 148 | 149 | export function getMenuButtonBoundingClientRect() { 150 | return { 151 | bottom: 58, 152 | height: 32, 153 | left: 223, 154 | right: 310, 155 | top: 26, 156 | width: 87, 157 | } 158 | } 159 | 160 | class UDPSocket { 161 | bind() {} 162 | close() {} 163 | offClose() {} 164 | offError() {} 165 | offListening() {} 166 | offMessage() {} 167 | onClose() {} 168 | onError() {} 169 | onListening() {} 170 | onMessage() {} 171 | send() {} 172 | } 173 | 174 | export function createUDPSocket() { 175 | return new UDPSocket() 176 | } 177 | 178 | // 地图 179 | class MapContext { 180 | getCenterLocation() {} 181 | getRegion() {} 182 | getRotate() {} 183 | getScale() {} 184 | getSkew() {} 185 | includePoints() {} 186 | moveToLocation() {} 187 | setCenterOffset() {} 188 | translateMarker() {} 189 | } 190 | 191 | export function createMapContext() { 192 | return new MapContext() 193 | } 194 | 195 | class VideoContext { 196 | exitFullScreen() {} 197 | hideStatusBar() {} 198 | pause() {} 199 | play() {} 200 | playbackRate() {} 201 | requestFullScreen() {} 202 | seek() {} 203 | sendDanmu() {} 204 | showStatusBar() {} 205 | stop() {} 206 | } 207 | 208 | export function createVideoContext() { 209 | return new VideoContext() 210 | } 211 | 212 | 213 | class InnerAudioContext { 214 | destroy() {} 215 | offCanplay() {} 216 | offEnded() {} 217 | offError() {} 218 | offPause() {} 219 | offPlay() {} 220 | offSeeked() {} 221 | offSeeking() {} 222 | offStop() {} 223 | offTimeUpdate() {} 224 | offWaiting() {} 225 | onCanplay() {} 226 | onEnded() {} 227 | onError() {} 228 | onPause() {} 229 | onPlay() {} 230 | onSeeked() {} 231 | onSeeking() {} 232 | onStop() {} 233 | onTimeUpdate() {} 234 | onWaiting() {} 235 | pause() {} 236 | play() {} 237 | seek() {} 238 | stop() {} 239 | } 240 | 241 | export function createInnerAudioContext() { 242 | return new InnerAudioContext() 243 | } 244 | 245 | class AudioContext { 246 | pause() {} 247 | play() {} 248 | seek() {} 249 | setSrc() {} 250 | } 251 | 252 | export function createAudioContext() { 253 | return new AudioContext() 254 | } 255 | 256 | class BackgroundAudioManager { 257 | onCanplay() {} 258 | onEnded() {} 259 | onError() {} 260 | onNext() {} 261 | onPause() {} 262 | onPlay() {} 263 | onPrev() {} 264 | onSeeked() {} 265 | onSeeking() {} 266 | onStop() {} 267 | onTimeUpdate() {} 268 | onWaiting() {} 269 | pause() {} 270 | play() {} 271 | seek() {} 272 | stop() {} 273 | } 274 | 275 | export function getBackgroundAudioManager() { 276 | return new BackgroundAudioManager() 277 | } 278 | 279 | class LivePUsherContext { 280 | pause() {} 281 | pauseBGM() {} 282 | playBGM() {} 283 | resume() {} 284 | resumeBGM() {} 285 | setBGMVolume() {} 286 | setMICVolume() {} 287 | snapshot() {} 288 | start() {} 289 | startPreview() {} 290 | stop() {} 291 | stopBGM() {} 292 | stopPreview() {} 293 | switchCamera() {} 294 | toggleTorch() {} 295 | } 296 | 297 | export function createLivePusherContext() { 298 | return new LivePUsherContext() 299 | } 300 | 301 | class LivePlayerContext { 302 | exitFullScreen() {} 303 | mute() {} 304 | pause() {} 305 | play() {} 306 | requestFullScreen() {} 307 | resume() {} 308 | snapshot() {} 309 | stop() {} 310 | } 311 | 312 | export function createLivePlayerContext() { 313 | return new LivePlayerContext() 314 | } 315 | 316 | class RecorderManager { 317 | onError() {} 318 | onFrameRecorded() {} 319 | onInterruptionBegin() {} 320 | onInterruptionEnd() {} 321 | onPause() {} 322 | onResume() {} 323 | onStart() {} 324 | onStop() {} 325 | pause() {} 326 | resume() {} 327 | start() {} 328 | stop() {} 329 | } 330 | 331 | export function getRecorderManager() { 332 | return new RecorderManager() 333 | } 334 | 335 | class CameraContext { 336 | onCameraFrame() { 337 | return new CameraFrameListener() 338 | } 339 | setZoom() {} 340 | startRecord() {} 341 | stopRecord() {} 342 | takePhoto() {} 343 | } 344 | 345 | class CameraFrameListener { 346 | start() {} 347 | end() {} 348 | } 349 | 350 | export function createCameraContext() { 351 | return new CameraContext() 352 | } 353 | 354 | class MediaContainer { 355 | addTrack() {} 356 | destroy() {} 357 | export() {} 358 | extractDataSource() {} 359 | removeTrack() {} 360 | } 361 | 362 | export function createMediaContainer() { 363 | return new MediaContainer() 364 | } 365 | 366 | class CanvasContext { 367 | arc() {} 368 | arcTo() {} 369 | beginPath() {} 370 | bezierCurveTo() {} 371 | clearRect() {} 372 | clip() {} 373 | closePath() {} 374 | createCircularGradient() {} 375 | createLinearGradient() {} 376 | createPattern() {} 377 | draw() {} 378 | drawImage() {} 379 | fill() {} 380 | fillRect() {} 381 | fillText() {} 382 | lineTo() {} 383 | measureText() {} 384 | moveTo() {} 385 | quadraticCurveTo() {} 386 | rect() {} 387 | restore() {} 388 | rotate() {} 389 | save() {} 390 | scale() {} 391 | setFillStyle() {} 392 | setFontSize() {} 393 | setGlobalAlpha() {} 394 | setLineCap() {} 395 | setLineDash() {} 396 | setLineJoin() {} 397 | setLineWidth() {} 398 | setMiterLimit() {} 399 | setShadow() {} 400 | setStrokeStyle() {} 401 | setTextAlign() {} 402 | setTextBaseline() {} 403 | setTransform() {} 404 | stroke() {} 405 | strokeRect() {} 406 | strokeText() {} 407 | transform() {} 408 | translate() {} 409 | } 410 | 411 | export function createCanvasContext() { 412 | return new CanvasContext() 413 | } 414 | 415 | class OffscreenCanvas { 416 | getContext() {} 417 | } 418 | 419 | export function createOffscreenCanvas() { 420 | return new OffscreenCanvas() 421 | } 422 | 423 | class FileSystemManager { 424 | access() {} 425 | accessSync() {} 426 | appendFile() {} 427 | appendFileSync() {} 428 | copyFile() {} 429 | copyFileSync() {} 430 | getFileInfo() {} 431 | getSavedFileList() {} 432 | mkdir() {} 433 | mkdirSync() {} 434 | readdir() {} 435 | readdirSync() {} 436 | readFile() {} 437 | readFileSync() {} 438 | removeSavedFile() {} 439 | rename() {} 440 | renameSync() {} 441 | rmdir() {} 442 | rmdirSync() {} 443 | saveFile() {} 444 | saveFileSync() {} 445 | stat() {} 446 | statSync() {} 447 | unlink() {} 448 | unlinkSync() {} 449 | unzip() {} 450 | writeFile() {} 451 | writeFileSync() {} 452 | } 453 | 454 | export function getFileSystemManager() { 455 | return new FileSystemManager() 456 | } 457 | 458 | export function getAccountInfoSync() { 459 | return { 460 | miniProgram: { 461 | aappId: "" 462 | } 463 | } 464 | } 465 | 466 | class Worker { 467 | onMessage() {} 468 | postMessage() {} 469 | terminate() {} 470 | } 471 | 472 | export function createWorker() { 473 | return new Worker() 474 | } 475 | 476 | class SelectorQuery { 477 | exec() {} 478 | in() {} 479 | select() {} 480 | selectAll() {} 481 | selectViewport() {} 482 | } 483 | 484 | export function createSelectorQuery() { 485 | return new SelectorQuery() 486 | } 487 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import intercetorApi from "./wxapi/index" 2 | // the actual apis 3 | import * as ProxyApis from "./api/index" 4 | // eslint-disable-next-line 5 | import {AnyObj} from "./api/index.d" 6 | import fakeApis from "./fake/index" 7 | import {ismp} from "./utils/index" 8 | import * as MapContextApi from "./wxapi/mapContext" 9 | 10 | /** 11 | * 1. 当为 mp 环境,将 mp promise 化,覆盖掉当前的 wx api 内容 12 | * 2. 如果是 h5 环境,则使用 H5 的相关接口实现 13 | */ 14 | 15 | const KboneAPI:AnyObj = {} 16 | 17 | Object.assign(KboneAPI, ProxyApis, fakeApis, {ismp}) 18 | 19 | // for Vue 20 | KboneAPI.install = function(Vue: any) { 21 | Vue.prototype.$api = KboneAPI 22 | } 23 | 24 | export default KboneAPI 25 | 26 | declare let wx: any 27 | 28 | if (ismp) { 29 | intercetorApi(KboneAPI, wx) 30 | Object.assign(KboneAPI, MapContextApi) 31 | } 32 | -------------------------------------------------------------------------------- /src/interaction/actionsheet.ts: -------------------------------------------------------------------------------- 1 | import {processCss} from "../utils/index" 2 | 3 | 4 | /** 5 | * 用来对齐 wx.showActionSheet 6 | */ 7 | export default class ActionSheet { 8 | maskStyle = { 9 | left: 0, 10 | bottom: 0, 11 | position: "fixed", 12 | right: 0, 13 | "z-index": 1000, 14 | top: 0, 15 | background: "rgba(0, 0, 0, 0.6)", 16 | display: "none" 17 | } 18 | 19 | actionStyle = { 20 | position: "fixed", 21 | left: 0, 22 | bottom: 0, 23 | transform: "translate(0, 100%)", 24 | "-webkit-transform": "translate(0, 100%)", 25 | "-webkit-backface-visibility": "hidden", 26 | "backface-visibility": "hidden", 27 | "z-index": 5000, 28 | width: "100%", 29 | "background-color": "#EAE7E8", 30 | "-webkit-transition": "-webkit-transform 0.3s", 31 | transition: "transform 0.3s, -webkit-transform 0.3s", 32 | "border-top-left-radius": "12px", 33 | "border-top-right-radius": "12px", 34 | overflow: "hidden", 35 | } 36 | 37 | menuStyle = { 38 | color: "rgba(0, 0, 0, 0.9)", 39 | "background-color": "#FFFFFF", 40 | } 41 | 42 | cancelStyle = { 43 | "margin-top": "8px", 44 | "background-color": "#FFFFFF", 45 | "padding-bottom": "env(safe-area-inset-bottom)", 46 | } 47 | 48 | preStyle = ` 49 | .weui-actionsheet__cell { 50 | position: relative; 51 | padding: 16px; 52 | text-align: center; 53 | font-size: 17px; 54 | line-height: 1.41176471; 55 | } 56 | .weui-actionsheet__cell:before { 57 | content: " "; 58 | position: absolute; 59 | left: 0; 60 | top: 0; 61 | right: 0; 62 | height: 1px; 63 | border-top: 1px solid rgba(0, 0, 0, 0.1); 64 | color: rgba(0, 0, 0, 0.1); 65 | -webkit-transform-origin: 0 0; 66 | transform-origin: 0 0; 67 | -webkit-transform: scaleY(0.5); 68 | transform: scaleY(0.5); 69 | } 70 | ` 71 | 72 | params:{ 73 | [key in string]: any 74 | } = { 75 | itemList: [], 76 | itemColor: "#000000", 77 | // eslint-disable-next-line 78 | success: (params: {})=>{}, 79 | // eslint-disable-next-line 80 | fail: ()=>{}, 81 | // eslint-disable-next-line 82 | complete: (params: {})=>{} 83 | } 84 | 85 | actionTag: HTMLDivElement 86 | menuTag: HTMLDivElement 87 | cellTag: HTMLDivElement 88 | cancelTag: HTMLDivElement 89 | cancelTextTag: HTMLDivElement 90 | wrapper: HTMLDivElement 91 | mask: HTMLDivElement 92 | preStyleTag: HTMLStyleElement 93 | cells: Array = [] 94 | resolveHandler: (value?: unknown) => void = () => {} 95 | rejectHandler: (value?: unknown) => void = () => {} 96 | 97 | render(params = {}) { 98 | const options = Object.assign( 99 | {}, this.params, params 100 | ) 101 | 102 | this.mask = document.createElement("div") 103 | this.mask.setAttribute("style", processCss(this.maskStyle)) 104 | 105 | this.actionTag = document.createElement("div") 106 | this.actionTag.setAttribute("style", processCss(this.actionStyle)) 107 | 108 | this.menuTag = document.createElement("div") 109 | this.menuTag.setAttribute("style", processCss(this.menuStyle)) 110 | 111 | this.cancelTag = document.createElement("div") 112 | this.cancelTag.setAttribute("style", processCss(this.cancelStyle)) 113 | this.cancelTag.onclick = () => { 114 | this.hide() 115 | options.complete() 116 | options.fail({ 117 | errMsg: "fail cancel" 118 | }) 119 | this.rejectHandler({ 120 | errMsg: "fail cancel" 121 | }) 122 | } 123 | this.mask.onclick = () => { 124 | this.hide() 125 | options.complete() 126 | options.fail({ 127 | errMsg: "fail cancel" 128 | }) 129 | this.rejectHandler({ 130 | errMsg: "fail cancel" 131 | }) 132 | } 133 | this.cancelTextTag = document.createElement("div") 134 | this.cancelTextTag.classList.add("weui-actionsheet__cell") 135 | this.cancelTextTag.textContent = "取消" 136 | 137 | this.wrapper = document.createElement("div") 138 | 139 | this.actionTag.appendChild(this.menuTag) 140 | this.cancelTag.appendChild(this.cancelTextTag) 141 | this.actionTag.appendChild(this.cancelTag) 142 | 143 | this.wrapper.appendChild(this.mask) 144 | this.wrapper.appendChild(this.actionTag) 145 | 146 | document.body.appendChild(this.wrapper) 147 | } 148 | 149 | /** 150 | * 添加 modal 的 after 样式 151 | */ 152 | preAppendStyles() { 153 | this.preStyleTag = document.createElement("style") 154 | this.preStyleTag.textContent = this.preStyle 155 | document.querySelector("head").appendChild(this.preStyleTag) 156 | } 157 | 158 | show(params = {}) { 159 | const options = Object.assign( 160 | {}, this.params, params 161 | ) 162 | 163 | if (!this.preStyleTag) { 164 | this.preAppendStyles() 165 | } 166 | 167 | if (!this.wrapper) { 168 | this.render(options) 169 | } 170 | 171 | const itemList = options.itemList 172 | for (let index = 0; index < itemList.length; index++) { 173 | if (this.cells[index]) { 174 | const tag = this.cells[index] 175 | tag.textContent = itemList[index] 176 | } else { 177 | const cellTag = document.createElement("div") 178 | cellTag.classList.add("weui-actionsheet__cell") 179 | cellTag.textContent = itemList[index] 180 | cellTag.onclick = () => { 181 | this.hide() 182 | options.success({ 183 | tapIndex: index 184 | }) 185 | options.complete({ 186 | tapIndex: index 187 | }) 188 | this.resolveHandler({ 189 | tapIndex: index 190 | }) 191 | } 192 | this.cells.push(cellTag) 193 | this.menuTag.appendChild(cellTag) 194 | } 195 | } 196 | 197 | if (this.cells.length > itemList.length) { 198 | for (let index = this.cells.length - 1; index < itemList.length; index++) { 199 | this.menuTag.removeChild(this.cells[index]) 200 | } 201 | 202 | this.cells.splice(itemList.length) 203 | } 204 | 205 | this.actionTag.style.transform = "translate(0, 0)" 206 | this.actionTag.style["-webkit-transform"] = "translate(0, 0)" 207 | this.mask.style.display = "block" 208 | 209 | return new Promise((resolve, reject) => { 210 | this.resolveHandler = resolve 211 | this.rejectHandler = reject 212 | }) 213 | } 214 | hide() { 215 | this.actionTag.style.transform = "translate(0, 100%)" 216 | this.actionTag.style["-webkit-transform"] = "translate(0, 100%)" 217 | this.mask.style.display = "none" 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/interaction/index.ts: -------------------------------------------------------------------------------- 1 | import Toast from "./toast" 2 | import Modal from "./modal" 3 | import ActionSheet from "./actionsheet" 4 | import {ismp} from "../utils/index" 5 | 6 | const toast = new Toast() 7 | const modal = new Modal() 8 | const actionSheet = new ActionSheet() 9 | 10 | if (!ismp) { 11 | // 非 mp 状态下,先创建 actinoSheet 节点,准备动画 12 | actionSheet.render() 13 | } 14 | 15 | export function showToast(options = {}) { 16 | return toast.show(options) 17 | } 18 | // eslint-disable-next-line 19 | export function hideToast(options: {[key: string]: any} = {}){ 20 | return toast.hide(0) 21 | .then(() => { 22 | // eslint-disable-next-line 23 | (typeof options.success === "function") && options.success() 24 | // eslint-disable-next-line 25 | (typeof options.complete === "function") && options.complete() 26 | }) 27 | } 28 | 29 | 30 | export function showModal(options = {}) { 31 | return modal.show(options) 32 | } 33 | 34 | export function showLoading(options:{[key in string]: any} = {}) { 35 | options.duration = 0 36 | options.icon = "loading" 37 | return toast.show(options) 38 | } 39 | 40 | export function hideLoading(options: {[key: string]: any} = {}) { 41 | return toast.hide(0) 42 | .then(() => { 43 | // eslint-disable-next-line 44 | (typeof options.success === "function") && options.success() 45 | // eslint-disable-next-line 46 | (typeof options.complete === "function") && options.complete() 47 | }) 48 | } 49 | 50 | export function showActionSheet(options = {}) { 51 | return actionSheet.show(options) 52 | } 53 | -------------------------------------------------------------------------------- /src/interaction/modal.ts: -------------------------------------------------------------------------------- 1 | import {processCss} from "../utils/index" 2 | 3 | /** 4 | * 用来对齐 wx.showModal API 5 | */ 6 | 7 | export default class Modal { 8 | maskStyle = { 9 | left: 0, 10 | bottom: 0, 11 | position: "fixed", 12 | right: 0, 13 | "z-index": 1000, 14 | top: 0, 15 | background: "rgba(0, 0, 0, 0.6)", 16 | } 17 | 18 | dialogStyle = { 19 | width: "320px", 20 | margin: "0 auto", 21 | position: "fixed", 22 | "z-index": 5000, 23 | top: "50%", 24 | left: "16px", 25 | right: "16px", 26 | "-webkit-transform": "translate(0, -50%)", 27 | transform: "translate(0, -50%)", 28 | "background-color": "#FFFFFF", 29 | "text-align": "center", 30 | "border-radius": "12px", 31 | overflow: "hidden", 32 | display: "flex", 33 | "-webkit-box-orient": "vertical", 34 | "-webkit-box-direction": "normal", 35 | "-ms-flex-direction": "column", 36 | "flex-direction": "column", 37 | } 38 | 39 | titleStyle = { 40 | "font-weight": 700, 41 | "font-size": "17px", 42 | "line-height": 1.4, 43 | padding: "32px 24px 16px", 44 | } 45 | 46 | contentStyle = { 47 | flex: 1, 48 | "overflow-y": "auto", 49 | "-webkit-overflow-scrolling": "touch", 50 | padding: "0 24px", 51 | "margin-bottom": "32px", 52 | "font-size": "17px", 53 | "line-height": 1.4, 54 | "word-wrap": "break-word", 55 | "-webkit-hyphens": "auto", 56 | hyphens: "auto", 57 | color: "rgba(0, 0, 0, 0.5)", 58 | } 59 | 60 | footerWrapStyle = { 61 | position: "relative", 62 | "line-height": "56px", 63 | "min-height": "56px", 64 | "font-size": "17px", 65 | display: "flex", 66 | } 67 | 68 | btnStyle = { 69 | display: "block", 70 | flex: 1, 71 | color: "#576B95", 72 | "font-weight": 700, 73 | "text-decoration": "none", 74 | "-webkit-tap-highlight-color": "rgba(0, 0, 0, 0)", 75 | position: "relative", 76 | } 77 | 78 | preStyle = ` 79 | .weui-dialog__ft:after { 80 | content: " "; 81 | position: absolute; 82 | left: 0; 83 | top: 0; 84 | right: 0; 85 | height: 1px; 86 | border-top: 1px solid rgba(0, 0, 0, 0.1); 87 | color: rgba(0, 0, 0, 0.1); 88 | -webkit-transform-origin: 0 0; 89 | transform-origin: 0 0; 90 | -webkit-transform: scaleY(0.5); 91 | transform: scaleY(0.5); 92 | } 93 | .weui-dialog__btn:after { 94 | content: " "; 95 | position: absolute; 96 | left: 0; 97 | top: 0; 98 | width: 1px; 99 | bottom: 0; 100 | border-left: 1px solid rgba(0, 0, 0, 0.1); 101 | color: rgba(0, 0, 0, 0.1); 102 | -webkit-transform-origin: 0 0; 103 | transform-origin: 0 0; 104 | -webkit-transform: scaleX(0.5); 105 | transform: scaleX(0.5); 106 | } 107 | ` 108 | 109 | params = { 110 | title: "", 111 | content: "", 112 | showCancel: true, 113 | cancelText: "取消", 114 | cancelColor: "#000000", 115 | confirmText: "确定", 116 | confirmColor: "#576B95", 117 | // eslint-disable-next-line 118 | success: (params: {})=>{}, 119 | // eslint-disable-next-line 120 | fail: ()=>{}, 121 | // eslint-disable-next-line 122 | complete: (params: {})=>{} 123 | } 124 | 125 | dialogTag: HTMLDivElement 126 | titleTag: HTMLDivElement 127 | contentTag: HTMLDivElement 128 | footerTag: HTMLDivElement 129 | confirmTag: HTMLDivElement 130 | cancelTag: HTMLDivElement 131 | wrapper: HTMLDivElement 132 | mask: HTMLDivElement 133 | preStyleTag: HTMLStyleElement 134 | resolveHandler: (value?: unknown) => void 135 | 136 | render(params = {}) { 137 | const options = Object.assign( 138 | {}, this.params, params 139 | ) 140 | 141 | this.mask = document.createElement("div") 142 | this.mask.setAttribute("style", processCss(this.maskStyle)) 143 | 144 | this.dialogTag = document.createElement("div") 145 | this.dialogTag.setAttribute("style", processCss(this.dialogStyle)) 146 | 147 | this.titleTag = document.createElement("div") 148 | this.titleTag.setAttribute("style", processCss(this.titleStyle)) 149 | this.titleTag.textContent = options.title 150 | 151 | this.contentTag = document.createElement("div") 152 | this.contentTag.setAttribute("style", processCss(this.contentStyle)) 153 | this.contentTag.textContent = options.content 154 | 155 | this.footerTag = document.createElement("div") 156 | this.footerTag.setAttribute("style", processCss(this.footerWrapStyle)) 157 | this.footerTag.classList.add("weui-dialog__ft") 158 | 159 | this.cancelTag = document.createElement("div") 160 | this.cancelTag.setAttribute("style", processCss({ 161 | ...this.btnStyle, 162 | color: options.cancelColor 163 | })) 164 | this.cancelTag.textContent = options.cancelText 165 | this.cancelTag.classList.add("weui-dialog__btn") 166 | this.cancelTag.onclick = () => { 167 | options.success({ 168 | confirm: false, 169 | cancel: true 170 | }) 171 | options.complete({ 172 | confirm: false, 173 | cancel: true 174 | }) 175 | this.resolveHandler({ 176 | confirm: false, 177 | cancel: true 178 | }) 179 | this.hide() 180 | } 181 | 182 | // 确认 Tag 在右边 183 | this.confirmTag = document.createElement("div") 184 | this.confirmTag.setAttribute("style", processCss({ 185 | ...this.btnStyle, 186 | color: options.confirmColor 187 | })) 188 | this.confirmTag.textContent = options.confirmText 189 | this.confirmTag.classList.add("weui-dialog__btn") 190 | this.confirmTag.onclick = () => { 191 | options.success({ 192 | confirm: true, 193 | cancel: false 194 | }) 195 | options.complete({ 196 | confirm: true, 197 | cancel: false 198 | }) 199 | this.resolveHandler({ 200 | confirm: true, 201 | cancel: false 202 | }) 203 | this.hide() 204 | } 205 | 206 | this.wrapper = document.createElement("div") 207 | this.wrapper.style.display = "none" 208 | 209 | this.dialogTag.appendChild(this.titleTag) 210 | this.dialogTag.appendChild(this.contentTag) 211 | this.footerTag.appendChild(this.cancelTag) 212 | this.footerTag.appendChild(this.confirmTag) 213 | this.dialogTag.appendChild(this.footerTag) 214 | 215 | this.wrapper.appendChild(this.mask) 216 | this.wrapper.appendChild(this.dialogTag) 217 | 218 | document.body.appendChild(this.wrapper) 219 | } 220 | /** 221 | * 添加 modal 的 after 样式 222 | */ 223 | preAppendStyles() { 224 | this.preStyleTag = document.createElement("style") 225 | this.preStyleTag.textContent = this.preStyle 226 | document.querySelector("head").appendChild(this.preStyleTag) 227 | } 228 | show(params = {}) { 229 | const options = Object.assign( 230 | {}, this.params, params 231 | ) 232 | 233 | if (!this.preStyleTag) { 234 | this.preAppendStyles() 235 | } 236 | 237 | if (!this.wrapper) { 238 | this.render(options) 239 | } 240 | 241 | this.wrapper.style.display = "block" 242 | 243 | return new Promise((resolve) => { 244 | this.resolveHandler = resolve 245 | }) 246 | } 247 | hide() { 248 | this.wrapper.style.display = "none" 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/interaction/toast.ts: -------------------------------------------------------------------------------- 1 | import {processCss} from "../utils/index" 2 | 3 | /** 4 | * 初始条件: 5 | * 1. 通过 render 创建 DOM 节点 6 | * 2. this.show 显示当前的 Toast 内容 7 | * 3. this.hide 隐藏当前的 Toast 内容,不删除节点 8 | */ 9 | 10 | class Toast { 11 | maskStyle = { 12 | left: 0, 13 | bottom: 0, 14 | position: "fixed", 15 | right: 0, 16 | "z-index": 1000, 17 | top: 0, 18 | } 19 | 20 | toastStyle = { 21 | width: "120px", 22 | height: "120px", 23 | top: "40%", 24 | left: "50%", 25 | "-webkit-transform": "translate(-50%,-50%)", 26 | transform: "translate(-50%,-50%)", 27 | background: "rgba(17,17,17,.7)", 28 | "border-radius": "5px", 29 | color: "#fff", 30 | display: "flex", 31 | "-webkit-box-direction": "normal", 32 | "-ms-flex-direction": "column", 33 | "flex-direction": "column", 34 | "-webkit-box-align": "center", 35 | "-ms-flex-align": "center", 36 | "align-items": "center", 37 | "-webkit-box-pack": "center", 38 | "-ms-flex-pack": "center", 39 | "justify-content": "center", 40 | "text-align": "center", 41 | position: "fixed", 42 | "z-index": 5000, 43 | "line-height": "1.6", 44 | } 45 | 46 | successIcon = { 47 | color: "#fff", 48 | width: "55px", 49 | height: "55px", 50 | display: "block", 51 | "mask-image": "url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.657%2018.435L3%2012.778l1.414-1.414%204.95%204.95L20.678%205l1.414%201.414-12.02%2012.021a1%201%200%2001-1.415%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E)", 52 | "-webkit-mask-image": "url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.657%2018.435L3%2012.778l1.414-1.414%204.95%204.95L20.678%205l1.414%201.414-12.02%2012.021a1%201%200%2001-1.415%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E)", 53 | "vertical-align": "middle", 54 | "-webkit-mask-position": "50% 50%", 55 | "mask-position": "50% 50%", 56 | "-webkit-mask-repeat": "no-repeat", 57 | "mask-repeat": "no-repeat", 58 | "-webkit-mask-size": "100%", 59 | "mask-size": "100%", 60 | "background-color": "currentColor", 61 | } 62 | 63 | loadingIcon = { 64 | margin: "8px 0", 65 | width: "38px", 66 | height: "38px", 67 | "vertical-align": "baseline", 68 | display: "inline-block", 69 | "-webkit-animation": "weuiLoading 1s steps(12) infinite", 70 | animation: "weuiLoading 1s steps(12) infinite", 71 | // eslint-disable-next-line 72 | background: `transparent url("data:image/svg+xml;charset=utf8, %3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 100 100'%3E%3Cpath fill='none' d='M0 0h100v100H0z'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23E9E9E9' rx='5' ry='5' transform='translate(0 -30)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23989697' rx='5' ry='5' transform='rotate(30 105.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%239B999A' rx='5' ry='5' transform='rotate(60 75.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23A3A1A2' rx='5' ry='5' transform='rotate(90 65 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23ABA9AA' rx='5' ry='5' transform='rotate(120 58.66 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23B2B2B2' rx='5' ry='5' transform='rotate(150 54.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23BAB8B9' rx='5' ry='5' transform='rotate(180 50 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23C2C0C1' rx='5' ry='5' transform='rotate(-150 45.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23CBCBCB' rx='5' ry='5' transform='rotate(-120 41.34 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23D2D2D2' rx='5' ry='5' transform='rotate(-90 35 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23DADADA' rx='5' ry='5' transform='rotate(-60 24.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23E2E2E2' rx='5' ry='5' transform='rotate(-30 -5.98 65)'/%3E%3C/svg%3E") no-repeat;`, 73 | "background-size": "100%", 74 | } 75 | 76 | customIcon = { 77 | margin: "8px auto", 78 | width: "38px", 79 | height: "38px", 80 | background: "transparent no-repeat", 81 | "background-size": "100%" 82 | } 83 | 84 | titleStyle = { 85 | display: "-webkit-box", 86 | "-webkit-box-orient": "vertical", 87 | "text-align": "center", 88 | } 89 | 90 | preStyle = ` 91 | @-webkit-keyframes weuiLoading { 92 | 0% { 93 | transform: rotate3d(0, 0, 1, 0deg); 94 | } 95 | 96 | 100% { 97 | transform: rotate3d(0, 0, 1, 360deg); 98 | } 99 | } 100 | 101 | @keyframes weuiLoading { 102 | 0% { 103 | transform: rotate3d(0, 0, 1, 0deg); 104 | } 105 | 106 | 100% { 107 | transform: rotate3d(0, 0, 1, 360deg); 108 | } 109 | } 110 | ` 111 | params = { 112 | title: "", 113 | icon: "success", 114 | image: "", 115 | duration: 1500, 116 | mask: false, 117 | // eslint-disable-next-line 118 | success: ()=>{}, 119 | // eslint-disable-next-line 120 | fail: ()=>{}, 121 | // eslint-disable-next-line 122 | complete: ()=>{} 123 | } 124 | 125 | wrapper: HTMLDivElement 126 | toast: HTMLDivElement 127 | mask: HTMLDivElement 128 | toastContent: HTMLDivElement 129 | icon: HTMLElement 130 | preStyleTag: HTMLStyleElement 131 | hideTimeout: NodeJS.Timeout 132 | 133 | render(params = {}) { 134 | const options = Object.assign( 135 | {}, this.params, params 136 | ) 137 | this.toast = document.createElement("div") 138 | this.toast.setAttribute("style", processCss({ 139 | ...this.toastStyle, 140 | ...(options.icon === "none" ? { 141 | height: "auto", 142 | padding: "10px 15px" 143 | } : {}) 144 | })) 145 | 146 | this.mask = document.createElement("div") 147 | this.mask.setAttribute("style", processCss(this.maskStyle)) 148 | this.mask.style.display = options.mask ? "block" : "none" 149 | 150 | this.icon = document.createElement("i") 151 | if (options.image) { 152 | this.icon.setAttribute("style", processCss({ 153 | ...this.customIcon, 154 | "background-image": `url(${options.image})` 155 | })) 156 | } else { 157 | switch (options.icon) { 158 | case "success": 159 | this.icon.setAttribute("style", processCss(this.successIcon)) 160 | break 161 | case "loading": 162 | this.icon.setAttribute("style", processCss(this.loadingIcon)) 163 | break 164 | case "none": 165 | this.icon.style.display = "none" 166 | break 167 | default: 168 | break 169 | } 170 | } 171 | 172 | this.toastContent = document.createElement("div") 173 | this.toastContent.textContent = options.title 174 | 175 | this.wrapper = document.createElement("div") 176 | this.wrapper.style.display = "none" 177 | 178 | // composition Element 179 | this.toast.appendChild(this.icon) 180 | this.toast.appendChild(this.toastContent) 181 | this.wrapper.appendChild(this.mask) 182 | this.wrapper.appendChild(this.toast) 183 | 184 | document.body.appendChild(this.wrapper) 185 | } 186 | /** 187 | * 给 loading 添加全局动画样式 188 | */ 189 | preAppendStyles() { 190 | this.preStyleTag = document.createElement("style") 191 | this.preStyleTag.textContent = this.preStyle 192 | document.querySelector("head").appendChild(this.preStyleTag) 193 | } 194 | show(params = {}) { 195 | const options = Object.assign( 196 | {}, this.params, params 197 | ) 198 | 199 | if (!this.preStyleTag) { 200 | this.preAppendStyles() 201 | } 202 | 203 | if (!this.wrapper) { 204 | this.render(options) 205 | } 206 | 207 | this.toastContent.textContent = options.title 208 | this.mask.style.display = options.mask ? "block" : "none" 209 | 210 | if (options.image) { 211 | this.icon.setAttribute("style", processCss({ 212 | ...this.customIcon, 213 | "background-image": `url(${options.image})` 214 | })) 215 | } else { 216 | switch (options.icon) { 217 | case "success": 218 | this.icon.setAttribute("style", processCss(this.successIcon)) 219 | break 220 | case "loading": 221 | this.icon.setAttribute("style", processCss(this.loadingIcon)) 222 | break 223 | case "none": 224 | this.icon.style.display = "none" 225 | break 226 | default: 227 | break 228 | } 229 | } 230 | 231 | this.toast.setAttribute("style", processCss({ 232 | ...this.toastStyle, 233 | ...(options.icon === "none" ? { 234 | height: "auto", 235 | padding: "10px 15px" 236 | } : {}) 237 | })) 238 | 239 | this.wrapper.style.display = "block" 240 | 241 | if (options.duration > 0) { 242 | this.hide(options.duration) 243 | } 244 | 245 | options.success() 246 | options.complete() 247 | return Promise.resolve() 248 | } 249 | hide(duration = 1500) { 250 | if (this.hideTimeout) clearTimeout(this.hideTimeout) 251 | if (!this.wrapper) return Promise.resolve() 252 | // eslint-disable-next-line 253 | return new Promise((resolve,reject) =>{ 254 | this.hideTimeout = setTimeout(() => { 255 | this.wrapper.style.display = "none" 256 | resolve() 257 | }, duration) 258 | }) 259 | } 260 | } 261 | 262 | export default Toast 263 | -------------------------------------------------------------------------------- /src/network/request.ts: -------------------------------------------------------------------------------- 1 | import {validateUrl, getType, addQueryStringToUrl, convertObjectValueToString, urlEncodeFormData} from "../utils/index" 2 | // eslint-disable-next-line 3 | import {AnyObj} from "../api/index.d" 4 | 5 | 6 | const params = { 7 | url: "", 8 | method: "GET", 9 | dataType: "json", 10 | responseType: "text", 11 | header: { 12 | "content-type": "application/json" 13 | }, 14 | // eslint-disable-next-line 15 | success: ({}) => { }, 16 | fail: () => { }, 17 | // eslint-disable-next-line 18 | complete: ({}) => { }, 19 | } 20 | 21 | export function request(args:AnyObj = {}) { 22 | const options = Object.assign({}, params, args) 23 | let url = options.url 24 | const method = options.method.toUpperCase() 25 | 26 | if (validateUrl(url) === false) { 27 | console.error("request", options, `invalid url "${url}"`) 28 | return undefined 29 | } 30 | 31 | // 检查 header 32 | let headers = convertObjectValueToString(options.header) 33 | headers = Object.keys(headers).reduce((pre: { [key in string]: string }, cur: string) => { 34 | if (cur.toLowerCase() === "content-type") { 35 | pre[cur.toLowerCase()] = headers[cur] 36 | } else { 37 | pre[cur] = headers[cur] 38 | } 39 | return pre 40 | }, {}) 41 | 42 | let responseType = "text" 43 | if (options.responseType) { 44 | responseType = options.responseType.toLowerCase() 45 | } 46 | 47 | let body = options.data 48 | if (method === "GET" || method === "HEAD") { 49 | url = addQueryStringToUrl(url, body) 50 | } else if (typeof body !== "string" && getType(body) !== "ArrayBuffer") { 51 | if (headers["content-type"].indexOf("application/x-www-form-urlencoded") > -1) { 52 | body = urlEncodeFormData(body, true) 53 | } else if (headers["content-type"].indexOf("application/json") > -1 || typeof body === "object") { 54 | body = JSON.stringify(body) 55 | } else { 56 | body = body.toString() 57 | } 58 | } 59 | 60 | const result: AnyObj = {} 61 | return fetch(url, { 62 | method, 63 | headers, 64 | body 65 | }) 66 | .then((response: Response) => { 67 | result.statusCode = response.status 68 | result.header = {} 69 | response.headers.forEach((val: string, key: string) => { 70 | result.header[key] = val 71 | }) 72 | if (response.ok === false) { 73 | throw response 74 | } 75 | 76 | if (responseType === "arraybuffer") { 77 | return response.arrayBuffer() 78 | } 79 | 80 | if (options.dataType === "json") { 81 | return response.json() 82 | } 83 | 84 | if (responseType === "text") { 85 | return response.text() 86 | } 87 | // eslint-disable-next-line 88 | return Promise.resolve() 89 | }) 90 | .then((data: string | ArrayBuffer | unknown) => { 91 | result.data = data 92 | options.success(result) 93 | options.complete(result) 94 | return result 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import {envVar} from "../enums/index" 2 | 3 | // eslint-disable-next-line 4 | declare let Page: any 5 | // eslint-disable-next-line 6 | declare let Component: any 7 | 8 | export const ismp = typeof Page === "function" && typeof Component === "function" 9 | 10 | export function getEnv() { 11 | if (ismp) { 12 | return envVar.wx 13 | } else { 14 | return envVar.web 15 | } 16 | } 17 | 18 | export function processCss(styles: {[key: string]: string | number}) { 19 | let css = "" 20 | // eslint-disable-next-line 21 | for(const prop in styles) { 22 | css += `${prop}: ${styles[prop]};` 23 | if (styles[prop] === "flex") { 24 | css += "display: -ms-flexbox;" 25 | } 26 | } 27 | 28 | return css 29 | } 30 | 31 | export function validateUrl(url: string, type = "http") { 32 | if (type === "http") { 33 | return /^(http|https):\/\/.*/i.test(url) 34 | } else if (type === "websocket") { 35 | return /^(ws|wss):\/\/.*/i.test(url) 36 | } 37 | return undefined 38 | } 39 | 40 | export function getType(data: {} | any) { 41 | return Object.prototype.toString.call(data).slice(8, -1) 42 | } 43 | 44 | // 把一个 Object 中的 value 都转换成 String 类型 45 | export function convertObjectValueToString(object: { 46 | [key in string]: any 47 | }) { 48 | return Object.keys(object).reduce((ret: { 49 | [key in string]: any 50 | }, key) => { 51 | if (typeof object[key] === "string") { 52 | ret[key] = object[key] 53 | } else if (typeof object[key] === "number") { 54 | ret[key] = object[key] + "" 55 | } else { 56 | ret[key] = Object.prototype.toString.apply(object[key]) 57 | } 58 | return ret 59 | }, {}) 60 | } 61 | 62 | export function addQueryStringToUrl(url: string, data: { 63 | [key in string]: any 64 | }) { 65 | if (typeof url === "string" && typeof data === "object" && data !== null && Object.keys(data).length > 0) { 66 | const parts = url.split("?") 67 | const path = parts[0] 68 | const query = (parts[1] || "").split("&").reduce((pre: { 69 | [key in string]: any 70 | }, cur) => { 71 | if (typeof cur === "string" && cur.length > 0) { 72 | const parts = cur.split("=") 73 | const key = parts[0] 74 | const value = parts[1] 75 | pre[key] = value 76 | } 77 | return pre 78 | }, {}) 79 | 80 | // 把 data 中的数据 encodeURIComponent 81 | const encodedData = Object.keys(data).reduce((ret: { 82 | [key in string]: any 83 | }, key) => { 84 | if (typeof data[key] === "object") { 85 | ret[encodeURIComponent(key)] = encodeURIComponent(JSON.stringify(data[key])) 86 | } else { 87 | ret[encodeURIComponent(key)] = encodeURIComponent(data[key]) 88 | } 89 | return ret 90 | }, {}) 91 | return path + "?" + urlEncodeFormData(Object.assign(query, encodedData)) 92 | } else { 93 | return url 94 | } 95 | } 96 | 97 | export function urlEncodeFormData(data: { 98 | [key in string]: any 99 | }, needEncode = false) { 100 | if (typeof data !== "object") { 101 | return data 102 | } 103 | const dataArray = [] 104 | 105 | for (const k in data) { 106 | // eslint-disable-next-line 107 | if (data.hasOwnProperty(k)) { 108 | if (needEncode) { 109 | try { 110 | dataArray.push(`${encodeURIComponent(k)}=${encodeURIComponent(data[k])}`) 111 | } catch (e) { 112 | dataArray.push(`${k}=${data[k]}`) 113 | } 114 | } else { 115 | dataArray.push(`${k}=${data[k]}`) 116 | } 117 | } 118 | } 119 | return dataArray.join("&") 120 | } 121 | 122 | export function bindApis() { 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/wxapi/api.ts: -------------------------------------------------------------------------------- 1 | export const asyncApis = [ 2 | "canvasGetImageData", 3 | "canvasPutImageData", 4 | "canvasToTempFilePath", 5 | "setEnableDebug", 6 | "startAccelerometer", 7 | "stopAccelerometer", 8 | "getBatteryInfo", 9 | "getClipboardData", 10 | "setClipboardData", 11 | "startCompass", 12 | "stopCompass", 13 | "addPhoneContact", 14 | "startGyroscope", 15 | "stopGyroscope", 16 | "startBeaconDiscovery", 17 | "stopBeaconDiscovery", 18 | "getBeacons", 19 | "startLocalServiceDiscovery", 20 | "stopLocalServiceDiscovery", 21 | "startDeviceMotionListening", 22 | "stopDeviceMotionListening", 23 | "getNetworkType", 24 | "makePhoneCall", 25 | "scanCode", 26 | "getSystemInfo", 27 | "vibrateShort", 28 | "vibrateLong", 29 | "getExtConfig", 30 | "chooseLocation", 31 | "getLocation", 32 | "openLocation", 33 | "chooseMessageFile", 34 | "loadFontFace", 35 | "chooseImage", 36 | "previewImage", 37 | "getImageInfo", 38 | "saveImageToPhotosAlbum", 39 | "compressImage", 40 | "chooseVideo", 41 | "saveVideoToPhotosAlbum", 42 | "downloadFile", 43 | "request", 44 | "connectSocket", 45 | "closeSocket", 46 | "sendSocketMessage", 47 | "uploadFile", 48 | "login", 49 | "checkSession", 50 | "chooseAddress", 51 | "authorize", 52 | "addCard", 53 | "openCard", 54 | "chooseInvoice", 55 | "chooseInvoiceTitle", 56 | "getUserInfo", 57 | "requestPayment", 58 | "getWeRunData", 59 | "showModal", 60 | "showToast", 61 | "hideToast", 62 | "showLoading", 63 | "hideLoading", 64 | "showActionSheet", 65 | "pageScrollTo", 66 | "startPullDownRefresh", 67 | "stopPullDownRefresh", 68 | "setBackgroundColor", 69 | "setBackgroundTextStyle", 70 | "setTabBarBadge", 71 | "removeTabBarBadge", 72 | "showTabBarRedDot", 73 | "hideTabBarRedDot", 74 | "showTabBar", 75 | "hideTabBar", 76 | "setTabBarStyle", 77 | "setTabBarItem", 78 | "setTopBarText", 79 | "saveFile", 80 | "openDocument", 81 | "getSavedFileList", 82 | "getSavedFileInfo", 83 | "removeSavedFile", 84 | "getFileInfo", 85 | "getStorage", 86 | "setStorage", 87 | "removeStorage", 88 | "clearStorage", 89 | "getStorageInfo", 90 | "closeBLEConnection", 91 | "closeBluetoothAdapter", 92 | "createBLEConnection", 93 | "getBLEDeviceCharacteristics", 94 | "getBLEDeviceServices", 95 | "getBluetoothAdapterState", 96 | "getBluetoothDevices", 97 | "getConnectedBluetoothDevices", 98 | "notifyBLECharacteristicValueChange", 99 | "openBluetoothAdapter", 100 | "readBLECharacteristicValue", 101 | "startBluetoothDevicesDiscovery", 102 | "stopBluetoothDevicesDiscovery", 103 | "writeBLECharacteristicValue", 104 | "getHCEState", 105 | "sendHCEMessage", 106 | "startHCE", 107 | "stopHCE", 108 | "getScreenBrightness", 109 | "setKeepScreenOn", 110 | "setScreenBrightness", 111 | "connectWifi", 112 | "getConnectedWifi", 113 | "getWifiList", 114 | "setWifiList", 115 | "startWifi", 116 | "stopWifi", 117 | "getBackgroundAudioPlayerState", 118 | "playBackgroundAudio", 119 | "pauseBackgroundAudio", 120 | "seekBackgroundAudio", 121 | "stopBackgroundAudio", 122 | "getAvailableAudioSources", 123 | "startRecord", 124 | "stopRecord", 125 | "setInnerAudioOption", 126 | "playVoice", 127 | "pauseVoice", 128 | "stopVoice", 129 | "getSetting", 130 | "openSetting", 131 | "getShareInfo", 132 | "hideShareMenu", 133 | "showShareMenu", 134 | "updateShareMenu", 135 | "checkIsSoterEnrolledInDevice", 136 | "checkIsSupportSoterAuthentication", 137 | "startSoterAuthentication", 138 | "navigateBackMiniProgram", 139 | "navigateToMiniProgram", 140 | "setNavigationBarTitle", 141 | "showNavigationBarLoading", 142 | "hideNavigationBarLoading", 143 | "setNavigationBarColor", 144 | "redirectTo", 145 | "reLaunch", 146 | "navigateTo", 147 | "switchTab", 148 | "navigateBack" 149 | ] 150 | -------------------------------------------------------------------------------- /src/wxapi/index.ts: -------------------------------------------------------------------------------- 1 | import {asyncApis} from "./api" 2 | // eslint-disable-next-line 3 | import {AnyObj} from "../api/index.d" 4 | 5 | // eslint-disable-next-line 6 | function _promisify(func: ({}) => {}) { 7 | return (args = {}) => 8 | new Promise((resolve, reject) => { 9 | func( 10 | Object.assign(args, { 11 | success: resolve, 12 | fail: reject 13 | }) 14 | ) 15 | }) 16 | } 17 | 18 | // eslint-disable-next-line 19 | function hasCallback(args: {[key: string]: any} ) { 20 | if (!args || typeof args !== "object") return false 21 | const callback = ["success", "fail", "complete"] 22 | for (const m of callback) { 23 | if (typeof args[m] === "function") return true 24 | } 25 | return false 26 | } 27 | 28 | export function wrapFn(func: any) { 29 | return function(args: AnyObj) { 30 | if (hasCallback(args)) { 31 | func(args) 32 | } else { 33 | return _promisify(func)(args) 34 | } 35 | } 36 | } 37 | 38 | // eslint-disable-next-line 39 | function PromiseAll(wx: {[key: string]: any} = {}, wxp: {[key: string]: any} = {}){ 40 | Object.keys(wx).forEach(key => { 41 | const fn = wx[key] 42 | if (typeof fn === "function" && asyncApis.indexOf(key) >= 0) { 43 | wxp[key] = wrapFn(fn) 44 | } else { 45 | wxp[key] = fn 46 | } 47 | }) 48 | } 49 | 50 | 51 | /** 52 | * Promise 所有 api,并绑定到 kboneAPI 上 53 | */ 54 | export default function intercetorApi(KboneAPI: object, wx: {[key: string]: any} = {}) { 55 | PromiseAll(wx, KboneAPI) 56 | } 57 | -------------------------------------------------------------------------------- /src/wxapi/mapContext.ts: -------------------------------------------------------------------------------- 1 | function createMapContext(id: string) { 2 | // @ts-ignore 3 | return document.querySelector("#" + id).$$getContext() 4 | } 5 | 6 | function createVideoContext(id: string) { 7 | // @ts-ignore 8 | return document.querySelector("#" + id).$$getContext() 9 | } 10 | 11 | export { 12 | createMapContext, 13 | createVideoContext 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "allowSyntheticDefaultImports": true, 5 | "target": "es6", 6 | "noImplicitAny": true, 7 | "sourceMap": true, 8 | "typeRoots": ["node_modules/@types", "./src/types"], 9 | "lib": ["es2017", "dom", "es2017.string"] 10 | } 11 | } --------------------------------------------------------------------------------