├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── docs └── images │ ├── Bitbucket下载.png │ ├── GitHub下载.png │ ├── 上传.png │ ├── 体验版二维码.jpg │ ├── 信任并运行.png │ ├── 审核管理.jpg │ ├── 导入小程序向导.png │ ├── 导入源码.png │ ├── 小程序二维码.jpg │ ├── 小程序管理后台-开发设置.png │ ├── 小程序管理后台-服务器域名.png │ ├── 成员管理.jpg │ ├── 搜索受邀者.jpg │ ├── 效果图.png │ ├── 添加资格.jpg │ ├── 登入微信开发者工具.jpg │ ├── 选择小程序.jpg │ ├── 选择开发版.jpg │ ├── 配置服务器域名.png │ └── 预览.jpg ├── miniprogram ├── app.json ├── app.ts ├── app.wxss ├── images │ └── location_on_FILL1_wght400_GRAD0_opsz48.svg ├── miniprogram_npm │ ├── @zlyboy │ │ └── wx-formdata │ │ │ ├── index.js │ │ │ └── index.js.map │ └── crypto-js │ │ ├── index.js │ │ └── index.js.map ├── package-lock.json ├── package.json ├── pages │ ├── index │ │ ├── index.json │ │ ├── index.ts │ │ ├── index.wxml │ │ └── index.wxss │ ├── locationPicker │ │ ├── locationPicker.json │ │ ├── locationPicker.ts │ │ ├── locationPicker.wxml │ │ └── locationPicker.wxss │ ├── privacyTips │ │ ├── privacyTips.json │ │ ├── privacyTips.ts │ │ ├── privacyTips.wxml │ │ └── privacyTips.wxss │ ├── userInfo │ │ ├── userInfo.json │ │ ├── userInfo.ts │ │ ├── userInfo.wxml │ │ └── userInfo.wxss │ └── userManager │ │ ├── userManager.json │ │ ├── userManager.ts │ │ ├── userManager.wxml │ │ └── userManager.wxss ├── services │ ├── cloudStorage.ts │ ├── course.ts │ ├── cxOrz_chaoxing-sign-cli_LICENSE │ ├── login.ts │ └── sign.ts ├── sitemap.json └── utils │ ├── types.ts │ └── util.ts ├── package-lock.json ├── package.json ├── project.config.json ├── tsconfig.json └── typings ├── index.d.ts └── types ├── index.d.ts └── wx ├── index.d.ts ├── lib.wx.api.d.ts ├── lib.wx.app.d.ts ├── lib.wx.behavior.d.ts ├── lib.wx.cloud.d.ts ├── lib.wx.component.d.ts ├── lib.wx.event.d.ts └── lib.wx.page.d.ts /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Eslint config file 3 | * Documentation: https://eslint.org/docs/user-guide/configuring/ 4 | * Install the Eslint extension before using this feature. 5 | */ 6 | module.exports = { 7 | env: { 8 | es6: true, 9 | browser: true, 10 | node: true, 11 | }, 12 | ecmaFeatures: { 13 | modules: true, 14 | }, 15 | parserOptions: { 16 | ecmaVersion: 2018, 17 | sourceType: 'module', 18 | }, 19 | globals: { 20 | wx: true, 21 | App: true, 22 | Page: true, 23 | getCurrentPages: true, 24 | getApp: true, 25 | Component: true, 26 | requirePlugin: true, 27 | requireMiniProgram: true, 28 | }, 29 | // extends: 'eslint:recommended', 30 | rules: {}, 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows 2 | [Dd]esktop.ini 3 | Thumbs.db 4 | $RECYCLE.BIN/ 5 | 6 | # macOS 7 | .DS_Store 8 | .fseventsd 9 | .Spotlight-V100 10 | .TemporaryItems 11 | .Trashes 12 | 13 | # Node.js 14 | node_modules/ 15 | 16 | project.private.config.json 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 快进到退学 2 | 「快进到退学」是[cxOrz/chaoxing-sign-cli: 超星学习通签到:支持普通签到、拍照签到、手势签到、位置签到、二维码签到,支持自动监测、QQ机器人签到与推送。](https://github.com/cxOrz/chaoxing-sign-cli)项目的微信小程序版本实现。本项目直接与超星学习通服务端进行通信,无后端依赖,可去中心化部署。 3 | 4 | ![效果图](/docs/images/效果图.png) 5 | 6 | ## 部署教程 7 | ### 注册微信小程序开发者账号并配置 8 | 打开[小程序](https://mp.weixin.qq.com/wxopen/waregister?action=step1),按照流程注册小程序开发者账号并实名。 9 | 10 | 在小程序管理后台内,选择左侧的「开发 - 开发管理」,再点击上方的「开发设置」。 11 | 12 | ![小程序管理后台-开发设置](/docs/images/小程序管理后台-开发设置.png) 13 | 14 | 点击服务器域名中的「开始配置」配置域名。 15 | 16 | ![小程序管理后台-服务器域名](/docs/images/小程序管理后台-服务器域名.png) 17 | 18 | **v1.3.2及以下版本**: 19 | 在「request合法域名」一栏中填入`https://mobilelearn.chaoxing.com;https://mooc1-1.chaoxing.com;https://pan-yz.chaoxing.com;https://passport2.chaoxing.com;`,点击「保存并提交」。 20 | 21 | **v1.4.0及以上版本**: 22 | 在「request合法域名」一栏中填入`https://mobilelearn.chaoxing.com;https://mooc1-1.chaoxing.com;https://pan-yz.chaoxing.com;https://passport2.chaoxing.com;https://passport2-api.chaoxing.com;`,点击「保存并提交」。 23 | 24 | ![配置服务器域名](/docs/images/配置服务器域名.png) 25 | 26 | ### 下载源码 27 | 点击下方任意一个打得开的链接下载最新版源码,然后解压到任意位置。 28 | 29 | - [Bitbucket下载](https://bitbucket.org/dropping-out-speedrun/dropping-out-speedrun/downloads/?tab=tags):点击zip 30 | 31 | ![Bitbucket下载](/docs/images/Bitbucket下载.png) 32 | 33 | - [GitHub下载](https://github.com/DroppingOutSpeedrun/dropping-out-speedrun/tags):点击zip 34 | 35 | ![GitHub下载](/docs/images/GitHub下载.png) 36 | 37 | ### 使用微信开发者工具进行部署 38 | 打开[微信开发者工具下载地址与更新日志 | 微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html),找到「稳定版」一栏。一般下载「Windows 64」版本,新款苹果电脑下载「macOS ARM64」版本,旧款苹果电脑下载「macOS x64」版本,不是苹果电脑但打不开「Windows 64」版本则下载「Windows 32」版本。 39 | 40 | 安装好微信开发者工具之后打开,然后使用微信扫描二维码,并在手机上确认登入。 41 | 42 | ![登入微信开发者工具](/docs/images/登入微信开发者工具.jpg) 43 | 44 | 点击导入,选择源码所在位置,打开到能看到miniprogram等文件夹时,点击选择文件夹。 45 | 46 | ![导入源码](/docs/images/导入源码.png) 47 | 48 | 点击AppID下拉菜单,选择一个AppID。然后将后端服务设置为「不使用云服务」,其他设置保持默认即可。最后点击确定。 49 | 50 | ![导入小程序向导](/docs/images/导入小程序向导.png) 51 | 52 | 选择「信任并运行」 53 | 54 | ![信任并运行](/docs/images/信任并运行.png) 55 | 56 | 点击「预览」,然后使用微信扫描二维码即可运行 57 | 58 | ![预览](/docs/images/预览.jpg) 59 | 60 | ### 将小程序分享给他人使用 61 | 选择「上传」,然后再点击上传 62 | 63 | ![上传](/docs/images/上传.png) 64 | 65 | 在微信中搜索「小程序助手」,选择小程序 66 | 67 | ![选择小程序](/docs/images/选择小程序.jpg) 68 | 69 | 点击「成员管理」 70 | 71 | ![成员管理](/docs/images/成员管理.jpg) 72 | 73 | 点击「体验成员」,再点击「新增体验成员」 74 | 75 | ![添加资格](/docs/images/添加资格.jpg) 76 | 77 | 输入受邀者微信号,搜索受邀者 78 | 79 | ![搜索受邀者](/docs/images/搜索受邀者.jpg) 80 | 81 | 回到主页,选择「审核管理」 82 | 83 | ![审核管理](/docs/images/审核管理.jpg) 84 | 85 | 点击刚发布的开发版 86 | 87 | ![选择开发版](/docs/images/选择开发版.jpg) 88 | 89 | 点击「体验版二维码」 90 | 91 | ![体验版二维码](/docs/images/体验版二维码.jpg) 92 | 93 | 将此二维码分享给受邀者即可 94 | 95 | ![小程序二维码](/docs/images/小程序二维码.jpg) 96 | 97 | ## 签到类型支持 98 | - 点击签到 99 | - 拍照签到 100 | - 手势签到 101 | - 位置签到 102 | - 二维码签到(支持附带位置信息的二维码签到) 103 | - 签到码签到 104 | 105 | ## 信息安全与隐私 106 | 本小程序不会与超星学习通以外的服务器进行通信,只在本地储存用户信息,通过[微信内置的AES-128加密储存API](https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorage.html#Object-object)对敏感信息进行加密储存。 107 | 108 | ## Developing 109 | Clone this project. 110 | From GitHub: 111 | ```shell 112 | git clone https://github.com/DroppingOutSpeedrun/dropping-out-speedrun.git 113 | ``` 114 | 115 | Or from BitBucket: 116 | ```shell 117 | git clone https://bitbucket.org/dropping-out-speedrun/dropping-out-speedrun.git 118 | ``` 119 | 120 | You have to install [Node.js](https://nodejs.org/) and [pnpm](https://pnpm.io/) (not sure npm could be used) to install dependencies: 121 | ```shell 122 | cd ./dropping-out-speedrun/miniprogram/ 123 | pnpm install 124 | ``` 125 | 126 | Start reading by services is a good begining: 127 | - `getCookieByFanya()` in `services/login.ts` 128 | - `getCourseInfoArray()` in `services/course.ts` 129 | - `getActivities()` in `services/course.ts` 130 | - `preSign()` in `services/sign.ts` 131 | - `generalSign()` in `services/sign.ts` 132 | 133 | ## LICENSE 134 | Dropping Out Speedrun 135 | Copyright (C) 2023 Dropping Out Speedrun 136 | 137 | This program is free software: you can redistribute it and/or modify 138 | it under the terms of the GNU General Public License as published by 139 | the Free Software Foundation, either version 3 of the License, or 140 | (at your option) any later version. 141 | 142 | This program is distributed in the hope that it will be useful, 143 | but WITHOUT ANY WARRANTY; without even the implied warranty of 144 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 145 | GNU General Public License for more details. 146 | 147 | You should have received a copy of the GNU General Public License 148 | along with this program. If not, see . 149 | -------------------------------------------------------------------------------- /docs/images/Bitbucket下载.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/Bitbucket下载.png -------------------------------------------------------------------------------- /docs/images/GitHub下载.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/GitHub下载.png -------------------------------------------------------------------------------- /docs/images/上传.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/上传.png -------------------------------------------------------------------------------- /docs/images/体验版二维码.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/体验版二维码.jpg -------------------------------------------------------------------------------- /docs/images/信任并运行.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/信任并运行.png -------------------------------------------------------------------------------- /docs/images/审核管理.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/审核管理.jpg -------------------------------------------------------------------------------- /docs/images/导入小程序向导.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/导入小程序向导.png -------------------------------------------------------------------------------- /docs/images/导入源码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/导入源码.png -------------------------------------------------------------------------------- /docs/images/小程序二维码.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/小程序二维码.jpg -------------------------------------------------------------------------------- /docs/images/小程序管理后台-开发设置.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/小程序管理后台-开发设置.png -------------------------------------------------------------------------------- /docs/images/小程序管理后台-服务器域名.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/小程序管理后台-服务器域名.png -------------------------------------------------------------------------------- /docs/images/成员管理.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/成员管理.jpg -------------------------------------------------------------------------------- /docs/images/搜索受邀者.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/搜索受邀者.jpg -------------------------------------------------------------------------------- /docs/images/效果图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/效果图.png -------------------------------------------------------------------------------- /docs/images/添加资格.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/添加资格.jpg -------------------------------------------------------------------------------- /docs/images/登入微信开发者工具.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/登入微信开发者工具.jpg -------------------------------------------------------------------------------- /docs/images/选择小程序.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/选择小程序.jpg -------------------------------------------------------------------------------- /docs/images/选择开发版.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/选择开发版.jpg -------------------------------------------------------------------------------- /docs/images/配置服务器域名.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/配置服务器域名.png -------------------------------------------------------------------------------- /docs/images/预览.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DroppingOutSpeedrun/dropping-out-speedrun/25574b3903c1362962f7bb2d4c1b8b2bf06308ed/docs/images/预览.jpg -------------------------------------------------------------------------------- /miniprogram/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/index/index", 4 | "pages/userInfo/userInfo", 5 | "pages/userManager/userManager", 6 | "pages/privacyTips/privacyTips", 7 | "pages/locationPicker/locationPicker" 8 | ], 9 | "window": { 10 | "backgroundTextStyle": "light", 11 | "navigationBarBackgroundColor": "#fff", 12 | "navigationBarTitleText": "Weixin", 13 | "navigationBarTextStyle": "black" 14 | }, 15 | "style": "v2", 16 | "sitemapLocation": "sitemap.json", 17 | "lazyCodeLoading": "requiredComponents", 18 | "useExtendedLib": { 19 | "weui": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /miniprogram/app.ts: -------------------------------------------------------------------------------- 1 | // app.ts 2 | App({ 3 | globalData: {}, 4 | onLaunch() {}, 5 | }); 6 | -------------------------------------------------------------------------------- /miniprogram/app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | -------------------------------------------------------------------------------- /miniprogram/images/location_on_FILL1_wght400_GRAD0_opsz48.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /miniprogram/miniprogram_npm/@zlyboy/wx-formdata/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | var __MODS__ = {}; 3 | var __DEFINE__ = function(modId, func, req) { var m = { exports: {}, _tempexports: {} }; __MODS__[modId] = { status: 0, func: func, req: req, m: m }; }; 4 | var __REQUIRE__ = function(modId, source) { if(!__MODS__[modId]) return require(source); if(!__MODS__[modId].status) { var m = __MODS__[modId].m; m._exports = m._tempexports; var desp = Object.getOwnPropertyDescriptor(m, "exports"); if (desp && desp.configurable) Object.defineProperty(m, "exports", { set: function (val) { if(typeof val === "object" && val !== m._exports) { m._exports.__proto__ = val.__proto__; Object.keys(val).forEach(function (k) { m._exports[k] = val[k]; }); } m._tempexports = val }, get: function () { return m._tempexports; } }); __MODS__[modId].status = 1; __MODS__[modId].func(__MODS__[modId].req, m, m.exports); } return __MODS__[modId].m.exports; }; 5 | var __REQUIRE_WILDCARD__ = function(obj) { if(obj && obj.__esModule) { return obj; } else { var newObj = {}; if(obj != null) { for(var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k)) newObj[k] = obj[k]; } } newObj.default = obj; return newObj; } }; 6 | var __REQUIRE_DEFAULT__ = function(obj) { return obj && obj.__esModule ? obj.default : obj; }; 7 | __DEFINE__(1682419587715, function(require, module, exports) { 8 | const mimeMap = require('./mimeMap.js') 9 | 10 | function FormData(){ 11 | let fileManager = wx.getFileSystemManager(); 12 | let data = {}; 13 | let files = []; 14 | 15 | this.append = (name, value)=>{ 16 | data[name] = value; 17 | return true; 18 | } 19 | 20 | this.appendFile = (name, path, fileName)=>{ 21 | let buffer = fileManager.readFileSync(path); 22 | if(Object.prototype.toString.call(buffer).indexOf("ArrayBuffer") < 0){ 23 | return false; 24 | } 25 | 26 | if(!fileName){ 27 | fileName = getFileNameFromPath(path); 28 | } 29 | 30 | files.push({ 31 | name: name, 32 | buffer: buffer, 33 | fileName: fileName 34 | }); 35 | return true; 36 | } 37 | 38 | this.getData = ()=>convert(data, files) 39 | } 40 | 41 | function getFileNameFromPath(path){ 42 | let idx=path.lastIndexOf("/"); 43 | return path.substr(idx+1); 44 | } 45 | 46 | function convert(data, files){ 47 | let boundaryKey = 'wxmpFormBoundary' + randString(); // 数据分割符,一般是随机的字符串 48 | let boundary = '--' + boundaryKey; 49 | let endBoundary = boundary + '--'; 50 | 51 | let postArray = []; 52 | //拼接参数 53 | if(data && Object.prototype.toString.call(data) == "[object Object]"){ 54 | for(let key in data){ 55 | postArray = postArray.concat(formDataArray(boundary, key, data[key])); 56 | } 57 | } 58 | //拼接文件 59 | if(files && Object.prototype.toString.call(files) == "[object Array]"){ 60 | for(let i in files){ 61 | let file = files[i]; 62 | postArray = postArray.concat(formDataArray(boundary, file.name, file.buffer, file.fileName)); 63 | } 64 | } 65 | //结尾 66 | let endBoundaryArray = []; 67 | endBoundaryArray.push(...endBoundary.toUtf8Bytes()); 68 | postArray = postArray.concat(endBoundaryArray); 69 | return { 70 | contentType: 'multipart/form-data; boundary=' + boundaryKey, 71 | buffer: new Uint8Array(postArray).buffer 72 | } 73 | } 74 | 75 | function randString() { 76 | let res = ""; 77 | for (let i = 0; i < 17; i++) { 78 | let n = parseInt(Math.random() * 62); 79 | if (n <= 9) { 80 | res += n; 81 | } 82 | else if (n <= 35) { 83 | res += String.fromCharCode(n + 55); 84 | } 85 | else { 86 | res += String.fromCharCode(n + 61); 87 | } 88 | } 89 | return res; 90 | } 91 | 92 | function formDataArray(boundary, name, value, fileName){ 93 | let dataString = ''; 94 | let isFile = !!fileName; 95 | 96 | dataString += boundary + '\r\n'; 97 | dataString += 'Content-Disposition: form-data; name="' + name + '"'; 98 | if (isFile){ 99 | dataString += '; filename="' + fileName + '"' + '\r\n'; 100 | dataString += 'Content-Type: ' + getFileMime(fileName) + '\r\n\r\n'; 101 | } 102 | else{ 103 | dataString += '\r\n\r\n'; 104 | dataString += value; 105 | } 106 | 107 | var dataArray = []; 108 | dataArray.push(...dataString.toUtf8Bytes()); 109 | 110 | if (isFile) { 111 | let fileArray = new Uint8Array(value); 112 | dataArray = dataArray.concat(Array.prototype.slice.call(fileArray)); 113 | } 114 | dataArray.push(..."\r".toUtf8Bytes()); 115 | dataArray.push(..."\n".toUtf8Bytes()); 116 | 117 | return dataArray; 118 | } 119 | 120 | function getFileMime(fileName){ 121 | let idx = fileName.lastIndexOf("."); 122 | let mime = mimeMap[fileName.substr(idx)]; 123 | return mime?mime:"application/octet-stream" 124 | } 125 | 126 | String.prototype.toUtf8Bytes = function(){ 127 | var str = this; 128 | var bytes = []; 129 | for (var i = 0; i < str.length; i++) { 130 | bytes.push(...str.utf8CodeAt(i)); 131 | if (str.codePointAt(i) > 0xffff) { 132 | i++; 133 | } 134 | } 135 | return bytes; 136 | } 137 | 138 | String.prototype.utf8CodeAt = function(i) { 139 | var str = this; 140 | var out = [], p = 0; 141 | var c = str.charCodeAt(i); 142 | if (c < 128) { 143 | out[p++] = c; 144 | } else if (c < 2048) { 145 | out[p++] = (c >> 6) | 192; 146 | out[p++] = (c & 63) | 128; 147 | } else if ( 148 | ((c & 0xFC00) == 0xD800) && (i + 1) < str.length && 149 | ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) { 150 | // Surrogate Pair 151 | c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF); 152 | out[p++] = (c >> 18) | 240; 153 | out[p++] = ((c >> 12) & 63) | 128; 154 | out[p++] = ((c >> 6) & 63) | 128; 155 | out[p++] = (c & 63) | 128; 156 | } else { 157 | out[p++] = (c >> 12) | 224; 158 | out[p++] = ((c >> 6) & 63) | 128; 159 | out[p++] = (c & 63) | 128; 160 | } 161 | return out; 162 | }; 163 | 164 | 165 | module.exports = FormData; 166 | 167 | 168 | }, function(modId) {var map = {"./mimeMap.js":1682419587716}; return __REQUIRE__(map[modId], modId); }) 169 | __DEFINE__(1682419587716, function(require, module, exports) { 170 | module.exports = { 171 | "0.001": "application/x-001", 172 | "0.323": "text/h323", 173 | "0.907": "drawing/907", 174 | ".acp": "audio/x-mei-aac", 175 | ".aif": "audio/aiff", 176 | ".aiff": "audio/aiff", 177 | ".asa": "text/asa", 178 | ".asp": "text/asp", 179 | ".au": "audio/basic", 180 | ".awf": "application/vnd.adobe.workflow", 181 | ".bmp": "application/x-bmp", 182 | ".c4t": "application/x-c4t", 183 | ".cal": "application/x-cals", 184 | ".cdf": "application/x-netcdf", 185 | ".cel": "application/x-cel", 186 | ".cg4": "application/x-g4", 187 | ".cit": "application/x-cit", 188 | ".cml": "text/xml", 189 | ".cmx": "application/x-cmx", 190 | ".crl": "application/pkix-crl", 191 | ".csi": "application/x-csi", 192 | ".cut": "application/x-cut", 193 | ".dbm": "application/x-dbm", 194 | ".dcd": "text/xml", 195 | ".der": "application/x-x509-ca-cert", 196 | ".dib": "application/x-dib", 197 | ".doc": "application/msword", 198 | ".drw": "application/x-drw", 199 | ".dwf": "Model/vnd.dwf", 200 | ".dwg": "application/x-dwg", 201 | ".dxf": "application/x-dxf", 202 | ".emf": "application/x-emf", 203 | ".ent": "text/xml", 204 | ".eps": "application/x-ps", 205 | ".etd": "application/x-ebx", 206 | ".fax": "image/fax", 207 | ".fif": "application/fractals", 208 | ".frm": "application/x-frm", 209 | ".gbr": "application/x-gbr", 210 | ".gif": "image/gif", 211 | ".gp4": "application/x-gp4", 212 | ".hmr": "application/x-hmr", 213 | ".hpl": "application/x-hpl", 214 | ".hrf": "application/x-hrf", 215 | ".htc": "text/x-component", 216 | ".html": "text/html", 217 | ".htx": "text/html", 218 | ".ico": "image/x-icon", 219 | ".iff": "application/x-iff", 220 | ".igs": "application/x-igs", 221 | ".img": "application/x-img", 222 | ".isp": "application/x-internet-signup", 223 | ".java": "java/*", 224 | ".jpe": "image/jpeg", 225 | ".jpeg": "image/jpeg", 226 | ".jpg": "application/x-jpg", 227 | ".jsp": "text/html", 228 | ".lar": "application/x-laplayer-reg", 229 | ".lavs": "audio/x-liquid-secure", 230 | ".lmsff": "audio/x-la-lms", 231 | ".ltr": "application/x-ltr", 232 | ".m2v": "video/x-mpeg", 233 | ".m4e": "video/mpeg4", 234 | ".man": "application/x-troff-man", 235 | ".mdb": "application/msaccess", 236 | ".mfp": "application/x-shockwave-flash", 237 | ".mhtml": "message/rfc822", 238 | ".mid": "audio/mid", 239 | ".mil": "application/x-mil", 240 | ".mnd": "audio/x-musicnet-download", 241 | ".mocha": "application/x-javascript", 242 | ".mp1": "audio/mp1", 243 | ".mp2v": "video/mpeg", 244 | ".mp4": "video/mpeg4", 245 | ".mpd": "application/vnd.ms-project", 246 | ".mpeg": "video/mpg", 247 | ".mpga": "audio/rn-mpeg", 248 | ".mps": "video/x-mpeg", 249 | ".mpv": "video/mpg", 250 | ".mpw": "application/vnd.ms-project", 251 | ".mtx": "text/xml", 252 | ".net": "image/pnetvue", 253 | ".nws": "message/rfc822", 254 | ".out": "application/x-out", 255 | ".p12": "application/x-pkcs12", 256 | ".p7c": "application/pkcs7-mime", 257 | ".p7r": "application/x-pkcs7-certreqresp", 258 | ".pc5": "application/x-pc5", 259 | ".pcl": "application/x-pcl", 260 | ".pdf": "application/pdf", 261 | ".pdx": "application/vnd.adobe.pdx", 262 | ".pgl": "application/x-pgl", 263 | ".pko": "application/vnd.ms-pki.pko", 264 | ".plg": "text/html", 265 | ".plt": "application/x-plt", 266 | ".png": "application/x-png", 267 | ".ppa": "application/vnd.ms-powerpoint", 268 | ".pps": "application/vnd.ms-powerpoint", 269 | ".ppt": "application/x-ppt", 270 | ".prf": "application/pics-rules", 271 | ".prt": "application/x-prt", 272 | ".ps": "application/postscript", 273 | ".pwz": "application/vnd.ms-powerpoint", 274 | ".ra": "audio/vnd.rn-realaudio", 275 | ".ras": "application/x-ras", 276 | ".rdf": "text/xml", 277 | ".red": "application/x-red", 278 | ".rjs": "application/vnd.rn-realsystem-rjs", 279 | ".rlc": "application/x-rlc", 280 | ".rm": "application/vnd.rn-realmedia", 281 | ".rmi": "audio/mid", 282 | ".rmm": "audio/x-pn-realaudio", 283 | ".rms": "application/vnd.rn-realmedia-secure", 284 | ".rmx": "application/vnd.rn-realsystem-rmx", 285 | ".rp": "image/vnd.rn-realpix", 286 | ".rsml": "application/vnd.rn-rsml", 287 | ".rtf": "application/msword", 288 | ".rv": "video/vnd.rn-realvideo", 289 | ".sat": "application/x-sat", 290 | ".sdw": "application/x-sdw", 291 | ".slb": "application/x-slb", 292 | ".slk": "drawing/x-slk", 293 | ".smil": "application/smil", 294 | ".snd": "audio/basic", 295 | ".sor": "text/plain", 296 | ".spl": "application/futuresplash", 297 | ".ssm": "application/streamingmedia", 298 | ".stl": "application/vnd.ms-pki.stl", 299 | ".sty": "application/x-sty", 300 | ".swf": "application/x-shockwave-flash", 301 | ".tg4": "application/x-tg4", 302 | ".tif": "image/tiff", 303 | ".tiff": "image/tiff", 304 | ".top": "drawing/x-top", 305 | ".tsd": "text/xml", 306 | ".uin": "application/x-icq", 307 | ".vcf": "text/x-vcard", 308 | ".vdx": "application/vnd.visio", 309 | ".vpg": "application/x-vpeg005", 310 | ".vsd": "application/x-vsd", 311 | ".vst": "application/vnd.visio", 312 | ".vsw": "application/vnd.visio", 313 | ".vtx": "application/vnd.visio", 314 | ".wav": "audio/wav", 315 | ".wb1": "application/x-wb1", 316 | ".wb3": "application/x-wb3", 317 | ".wiz": "application/msword", 318 | ".wk4": "application/x-wk4", 319 | ".wks": "application/x-wks", 320 | ".wma": "audio/x-ms-wma", 321 | ".wmf": "application/x-wmf", 322 | ".wmv": "video/x-ms-wmv", 323 | ".wmz": "application/x-ms-wmz", 324 | ".wpd": "application/x-wpd", 325 | ".wpl": "application/vnd.ms-wpl", 326 | ".wr1": "application/x-wr1", 327 | ".wrk": "application/x-wrk", 328 | ".ws2": "application/x-ws", 329 | ".wsdl": "text/xml", 330 | ".xdp": "application/vnd.adobe.xdp", 331 | ".xfd": "application/vnd.adobe.xfd", 332 | ".xhtml": "text/html", 333 | ".xls": "application/x-xls", 334 | ".xml": "text/xml", 335 | ".xq": "text/xml", 336 | ".xquery": "text/xml", 337 | ".xsl": "text/xml", 338 | ".xwd": "application/x-xwd", 339 | ".sis": "application/vnd.symbian.install", 340 | ".x_t": "application/x-x_t", 341 | ".apk": "application/vnd.android.package-archive", 342 | "0.301": "application/x-301", 343 | "0.906": "application/x-906", 344 | ".a11": "application/x-a11", 345 | ".ai": "application/postscript", 346 | ".aifc": "audio/aiff", 347 | ".anv": "application/x-anv", 348 | ".asf": "video/x-ms-asf", 349 | ".asx": "video/x-ms-asf", 350 | ".avi": "video/avi", 351 | ".biz": "text/xml", 352 | ".bot": "application/x-bot", 353 | ".c90": "application/x-c90", 354 | ".cat": "application/vnd.ms-pki.seccat", 355 | ".cdr": "application/x-cdr", 356 | ".cer": "application/x-x509-ca-cert", 357 | ".cgm": "application/x-cgm", 358 | ".class": "java/*", 359 | ".cmp": "application/x-cmp", 360 | ".cot": "application/x-cot", 361 | ".crt": "application/x-x509-ca-cert", 362 | ".css": "text/css", 363 | ".dbf": "application/x-dbf", 364 | ".dbx": "application/x-dbx", 365 | ".dcx": "application/x-dcx", 366 | ".dgn": "application/x-dgn", 367 | ".dll": "application/x-msdownload", 368 | ".dot": "application/msword", 369 | ".dtd": "text/xml", 370 | ".dwf": "application/x-dwf", 371 | ".dxb": "application/x-dxb", 372 | ".edn": "application/vnd.adobe.edn", 373 | ".eml": "message/rfc822", 374 | ".epi": "application/x-epi", 375 | ".eps": "application/postscript", 376 | ".exe": "application/x-msdownload", 377 | ".fdf": "application/vnd.fdf", 378 | ".fo": "text/xml", 379 | ".g4": "application/x-g4", 380 | ".tif": "image/tiff", 381 | ".gl2": "application/x-gl2", 382 | ".hgl": "application/x-hgl", 383 | ".hpg": "application/x-hpgl", 384 | ".hqx": "application/mac-binhex40", 385 | ".hta": "application/hta", 386 | ".htm": "text/html", 387 | ".htt": "text/webviewhtml", 388 | ".icb": "application/x-icb", 389 | ".ico": "application/x-ico", 390 | ".ig4": "application/x-g4", 391 | ".iii": "application/x-iphone", 392 | ".ins": "application/x-internet-signup", 393 | ".IVF": "video/x-ivf", 394 | ".jfif": "image/jpeg", 395 | ".jpe": "application/x-jpe", 396 | ".jpg": "image/jpeg", 397 | ".js": "application/x-javascript", 398 | ".la1": "audio/x-liquid-file", 399 | ".latex": "application/x-latex", 400 | ".lbm": "application/x-lbm", 401 | ".ls": "application/x-javascript", 402 | ".m1v": "video/x-mpeg", 403 | ".m3u": "audio/mpegurl", 404 | ".mac": "application/x-mac", 405 | ".math": "text/xml", 406 | ".mdb": "application/x-mdb", 407 | ".mht": "message/rfc822", 408 | ".mi": "application/x-mi", 409 | ".midi": "audio/mid", 410 | ".mml": "text/xml", 411 | ".mns": "audio/x-musicnet-stream", 412 | ".movie": "video/x-sgi-movie", 413 | ".mp2": "audio/mp2", 414 | ".mp3": "audio/mp3", 415 | ".mpa": "video/x-mpg", 416 | ".mpe": "video/x-mpeg", 417 | ".mpg": "video/mpg", 418 | ".mpp": "application/vnd.ms-project", 419 | ".mpt": "application/vnd.ms-project", 420 | ".mpv2": "video/mpeg", 421 | ".mpx": "application/vnd.ms-project", 422 | ".mxp": "application/x-mmxp", 423 | ".nrf": "application/x-nrf", 424 | ".odc": "text/x-ms-odc", 425 | ".p10": "application/pkcs10", 426 | ".p7b": "application/x-pkcs7-certificates", 427 | ".p7m": "application/pkcs7-mime", 428 | ".p7s": "application/pkcs7-signature", 429 | ".pci": "application/x-pci", 430 | ".pcx": "application/x-pcx", 431 | ".pdf": "application/pdf", 432 | ".pfx": "application/x-pkcs12", 433 | ".pic": "application/x-pic", 434 | ".pl": "application/x-perl", 435 | ".pls": "audio/scpls", 436 | ".png": "image/png", 437 | ".pot": "application/vnd.ms-powerpoint", 438 | ".ppm": "application/x-ppm", 439 | ".ppt": "application/vnd.ms-powerpoint", 440 | ".pr": "application/x-pr", 441 | ".prn": "application/x-prn", 442 | ".ps": "application/x-ps", 443 | ".ptn": "application/x-ptn", 444 | ".r3t": "text/vnd.rn-realtext3d", 445 | ".ram": "audio/x-pn-realaudio", 446 | ".rat": "application/rat-file", 447 | ".rec": "application/vnd.rn-recording", 448 | ".rgb": "application/x-rgb", 449 | ".rjt": "application/vnd.rn-realsystem-rjt", 450 | ".rle": "application/x-rle", 451 | ".rmf": "application/vnd.adobe.rmf", 452 | ".rmj": "application/vnd.rn-realsystem-rmj", 453 | ".rmp": "application/vnd.rn-rn_music_package", 454 | ".rmvb": "application/vnd.rn-realmedia-vbr", 455 | ".rnx": "application/vnd.rn-realplayer", 456 | ".rpm": "audio/x-pn-realaudio-plugin", 457 | ".rt": "text/vnd.rn-realtext", 458 | ".rtf": "application/x-rtf", 459 | ".sam": "application/x-sam", 460 | ".sdp": "application/sdp", 461 | ".sit": "application/x-stuffit", 462 | ".sld": "application/x-sld", 463 | ".smi": "application/smil", 464 | ".smk": "application/x-smk", 465 | ".sol": "text/plain", 466 | ".spc": "application/x-pkcs7-certificates", 467 | ".spp": "text/xml", 468 | ".sst": "application/vnd.ms-pki.certstore", 469 | ".stm": "text/html", 470 | ".svg": "text/xml", 471 | ".tdf": "application/x-tdf", 472 | ".tga": "application/x-tga", 473 | ".tif": "application/x-tif", 474 | ".tld": "text/xml", 475 | ".torrent": "application/x-bittorrent", 476 | ".txt": "text/plain", 477 | ".uls": "text/iuls", 478 | ".vda": "application/x-vda", 479 | ".vml": "text/xml", 480 | ".vsd": "application/vnd.visio", 481 | ".vss": "application/vnd.visio", 482 | ".vst": "application/x-vst", 483 | ".vsx": "application/vnd.visio", 484 | ".vxml": "text/xml", 485 | ".wax": "audio/x-ms-wax", 486 | ".wb2": "application/x-wb2", 487 | ".wbmp": "image/vnd.wap.wbmp", 488 | ".wk3": "application/x-wk3", 489 | ".wkq": "application/x-wkq", 490 | ".wm": "video/x-ms-wm", 491 | ".wmd": "application/x-ms-wmd", 492 | ".wml": "text/vnd.wap.wml", 493 | ".wmx": "video/x-ms-wmx", 494 | ".wp6": "application/x-wp6", 495 | ".wpg": "application/x-wpg", 496 | ".wq1": "application/x-wq1", 497 | ".wri": "application/x-wri", 498 | ".ws": "application/x-ws", 499 | ".wsc": "text/scriptlet", 500 | ".wvx": "video/x-ms-wvx", 501 | ".xdr": "text/xml", 502 | ".xfdf": "application/vnd.adobe.xfdf", 503 | ".xls": "application/vnd.ms-excel", 504 | ".xlw": "application/x-xlw", 505 | ".xpl": "audio/scpls", 506 | ".xql": "text/xml", 507 | ".xsd": "text/xml", 508 | ".xslt": "text/xml", 509 | ".x_b": "application/x-x_b", 510 | ".sisx": "application/vnd.symbian.install", 511 | ".ipa": "application/vnd.iphone", 512 | ".xap": "application/x-silverlight-app", 513 | ".zip": "application/x-zip-compressed", 514 | } 515 | }, function(modId) { var map = {}; return __REQUIRE__(map[modId], modId); }) 516 | return __REQUIRE__(1682419587715); 517 | })() 518 | //miniprogram-npm-outsideDeps=[] 519 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /miniprogram/miniprogram_npm/@zlyboy/wx-formdata/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["formData.js","mimeMap.js"],"names":[],"mappings":";;;;;;;AAAA;AACA;AACA;AACA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;ADIA,ACHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"index.js","sourcesContent":["const mimeMap = require('./mimeMap.js')\r\n\r\nfunction FormData(){\r\n let fileManager = wx.getFileSystemManager();\r\n let data = {};\r\n let files = [];\r\n\r\n this.append = (name, value)=>{\r\n data[name] = value;\r\n return true;\r\n }\r\n\r\n this.appendFile = (name, path, fileName)=>{\r\n let buffer = fileManager.readFileSync(path);\r\n if(Object.prototype.toString.call(buffer).indexOf(\"ArrayBuffer\") < 0){\r\n return false;\r\n }\r\n\r\n if(!fileName){\r\n fileName = getFileNameFromPath(path);\r\n }\r\n\r\n files.push({\r\n name: name,\r\n buffer: buffer,\r\n fileName: fileName\r\n });\r\n return true;\r\n }\r\n\r\n this.getData = ()=>convert(data, files)\r\n}\r\n\r\nfunction getFileNameFromPath(path){\r\n let idx=path.lastIndexOf(\"/\");\r\n return path.substr(idx+1);\r\n}\r\n\r\nfunction convert(data, files){\r\n let boundaryKey = 'wxmpFormBoundary' + randString(); // 数据分割符,一般是随机的字符串\r\n let boundary = '--' + boundaryKey;\r\n let endBoundary = boundary + '--';\r\n\r\n let postArray = [];\r\n //拼接参数\r\n if(data && Object.prototype.toString.call(data) == \"[object Object]\"){\r\n for(let key in data){\r\n postArray = postArray.concat(formDataArray(boundary, key, data[key]));\r\n }\r\n }\r\n //拼接文件\r\n if(files && Object.prototype.toString.call(files) == \"[object Array]\"){\r\n for(let i in files){\r\n let file = files[i];\r\n postArray = postArray.concat(formDataArray(boundary, file.name, file.buffer, file.fileName));\r\n }\r\n }\r\n //结尾\r\n let endBoundaryArray = [];\r\n endBoundaryArray.push(...endBoundary.toUtf8Bytes());\r\n postArray = postArray.concat(endBoundaryArray);\r\n return {\r\n contentType: 'multipart/form-data; boundary=' + boundaryKey,\r\n buffer: new Uint8Array(postArray).buffer\r\n }\r\n}\r\n\r\nfunction randString() {\r\n let res = \"\";\r\n for (let i = 0; i < 17; i++) {\r\n let n = parseInt(Math.random() * 62);\r\n if (n <= 9) {\r\n res += n;\r\n }\r\n else if (n <= 35) {\r\n res += String.fromCharCode(n + 55);\r\n }\r\n else {\r\n res += String.fromCharCode(n + 61);\r\n }\r\n }\r\n return res;\r\n}\r\n\r\nfunction formDataArray(boundary, name, value, fileName){\r\n let dataString = '';\r\n let isFile = !!fileName;\r\n\r\n dataString += boundary + '\\r\\n';\r\n dataString += 'Content-Disposition: form-data; name=\"' + name + '\"';\r\n if (isFile){\r\n dataString += '; filename=\"' + fileName + '\"' + '\\r\\n';\r\n dataString += 'Content-Type: ' + getFileMime(fileName) + '\\r\\n\\r\\n';\r\n }\r\n else{\r\n dataString += '\\r\\n\\r\\n';\r\n dataString += value;\r\n }\r\n\r\n var dataArray = [];\r\n dataArray.push(...dataString.toUtf8Bytes());\r\n\r\n if (isFile) {\r\n let fileArray = new Uint8Array(value);\r\n dataArray = dataArray.concat(Array.prototype.slice.call(fileArray));\r\n }\r\n dataArray.push(...\"\\r\".toUtf8Bytes());\r\n dataArray.push(...\"\\n\".toUtf8Bytes());\r\n\r\n return dataArray;\r\n}\r\n\r\nfunction getFileMime(fileName){\r\n let idx = fileName.lastIndexOf(\".\");\r\n let mime = mimeMap[fileName.substr(idx)];\r\n return mime?mime:\"application/octet-stream\"\r\n}\r\n\r\nString.prototype.toUtf8Bytes = function(){\r\n var str = this;\r\n var bytes = [];\r\n for (var i = 0; i < str.length; i++) {\r\n bytes.push(...str.utf8CodeAt(i));\r\n if (str.codePointAt(i) > 0xffff) {\r\n i++;\r\n }\r\n }\r\n return bytes;\r\n}\r\n\r\nString.prototype.utf8CodeAt = function(i) {\r\n var str = this;\r\n var out = [], p = 0;\r\n var c = str.charCodeAt(i);\r\n if (c < 128) {\r\n out[p++] = c;\r\n } else if (c < 2048) {\r\n out[p++] = (c >> 6) | 192;\r\n out[p++] = (c & 63) | 128;\r\n } else if (\r\n ((c & 0xFC00) == 0xD800) && (i + 1) < str.length &&\r\n ((str.charCodeAt(i + 1) & 0xFC00) == 0xDC00)) {\r\n // Surrogate Pair\r\n c = 0x10000 + ((c & 0x03FF) << 10) + (str.charCodeAt(++i) & 0x03FF);\r\n out[p++] = (c >> 18) | 240;\r\n out[p++] = ((c >> 12) & 63) | 128;\r\n out[p++] = ((c >> 6) & 63) | 128;\r\n out[p++] = (c & 63) | 128;\r\n } else {\r\n out[p++] = (c >> 12) | 224;\r\n out[p++] = ((c >> 6) & 63) | 128;\r\n out[p++] = (c & 63) | 128;\r\n }\r\n return out;\r\n};\r\n\r\n\r\nmodule.exports = FormData;\r\n\r\n","module.exports = {\r\n \"0.001\": \"application/x-001\",\r\n \"0.323\": \"text/h323\",\r\n \"0.907\": \"drawing/907\",\r\n \".acp\": \"audio/x-mei-aac\",\r\n \".aif\": \"audio/aiff\",\r\n \".aiff\": \"audio/aiff\",\r\n \".asa\": \"text/asa\",\r\n \".asp\": \"text/asp\",\r\n \".au\": \"audio/basic\",\r\n \".awf\": \"application/vnd.adobe.workflow\",\r\n \".bmp\": \"application/x-bmp\",\r\n \".c4t\": \"application/x-c4t\",\r\n \".cal\": \"application/x-cals\",\r\n \".cdf\": \"application/x-netcdf\",\r\n \".cel\": \"application/x-cel\",\r\n \".cg4\": \"application/x-g4\",\r\n \".cit\": \"application/x-cit\",\r\n \".cml\": \"text/xml\",\r\n \".cmx\": \"application/x-cmx\",\r\n \".crl\": \"application/pkix-crl\",\r\n \".csi\": \"application/x-csi\",\r\n \".cut\": \"application/x-cut\",\r\n \".dbm\": \"application/x-dbm\",\r\n \".dcd\": \"text/xml\",\r\n \".der\": \"application/x-x509-ca-cert\",\r\n \".dib\": \"application/x-dib\",\r\n \".doc\": \"application/msword\",\r\n \".drw\": \"application/x-drw\",\r\n \".dwf\": \"Model/vnd.dwf\",\r\n \".dwg\": \"application/x-dwg\",\r\n \".dxf\": \"application/x-dxf\",\r\n \".emf\": \"application/x-emf\",\r\n \".ent\": \"text/xml\",\r\n \".eps\": \"application/x-ps\",\r\n \".etd\": \"application/x-ebx\",\r\n \".fax\": \"image/fax\",\r\n \".fif\": \"application/fractals\",\r\n \".frm\": \"application/x-frm\",\r\n \".gbr\": \"application/x-gbr\",\r\n \".gif\": \"image/gif\",\r\n \".gp4\": \"application/x-gp4\",\r\n \".hmr\": \"application/x-hmr\",\r\n \".hpl\": \"application/x-hpl\",\r\n \".hrf\": \"application/x-hrf\",\r\n \".htc\": \"text/x-component\",\r\n \".html\": \"text/html\",\r\n \".htx\": \"text/html\",\r\n \".ico\": \"image/x-icon\",\r\n \".iff\": \"application/x-iff\",\r\n \".igs\": \"application/x-igs\",\r\n \".img\": \"application/x-img\",\r\n \".isp\": \"application/x-internet-signup\",\r\n \".java\": \"java/*\",\r\n \".jpe\": \"image/jpeg\",\r\n \".jpeg\": \"image/jpeg\",\r\n \".jpg\": \"application/x-jpg\",\r\n \".jsp\": \"text/html\",\r\n \".lar\": \"application/x-laplayer-reg\",\r\n \".lavs\": \"audio/x-liquid-secure\",\r\n \".lmsff\": \"audio/x-la-lms\",\r\n \".ltr\": \"application/x-ltr\",\r\n \".m2v\": \"video/x-mpeg\",\r\n \".m4e\": \"video/mpeg4\",\r\n \".man\": \"application/x-troff-man\",\r\n \".mdb\": \"application/msaccess\",\r\n \".mfp\": \"application/x-shockwave-flash\",\r\n \".mhtml\": \"message/rfc822\",\r\n \".mid\": \"audio/mid\",\r\n \".mil\": \"application/x-mil\",\r\n \".mnd\": \"audio/x-musicnet-download\",\r\n \".mocha\": \"application/x-javascript\",\r\n \".mp1\": \"audio/mp1\",\r\n \".mp2v\": \"video/mpeg\",\r\n \".mp4\": \"video/mpeg4\",\r\n \".mpd\": \"application/vnd.ms-project\",\r\n \".mpeg\": \"video/mpg\",\r\n \".mpga\": \"audio/rn-mpeg\",\r\n \".mps\": \"video/x-mpeg\",\r\n \".mpv\": \"video/mpg\",\r\n \".mpw\": \"application/vnd.ms-project\",\r\n \".mtx\": \"text/xml\",\r\n \".net\": \"image/pnetvue\",\r\n \".nws\": \"message/rfc822\",\r\n \".out\": \"application/x-out\",\r\n \".p12\": \"application/x-pkcs12\",\r\n \".p7c\": \"application/pkcs7-mime\",\r\n \".p7r\": \"application/x-pkcs7-certreqresp\",\r\n \".pc5\": \"application/x-pc5\",\r\n \".pcl\": \"application/x-pcl\",\r\n \".pdf\": \"application/pdf\",\r\n \".pdx\": \"application/vnd.adobe.pdx\",\r\n \".pgl\": \"application/x-pgl\",\r\n \".pko\": \"application/vnd.ms-pki.pko\",\r\n \".plg\": \"text/html\",\r\n \".plt\": \"application/x-plt\",\r\n \".png\": \"application/x-png\",\r\n \".ppa\": \"application/vnd.ms-powerpoint\",\r\n \".pps\": \"application/vnd.ms-powerpoint\",\r\n \".ppt\": \"application/x-ppt\",\r\n \".prf\": \"application/pics-rules\",\r\n \".prt\": \"application/x-prt\",\r\n \".ps\": \"application/postscript\",\r\n \".pwz\": \"application/vnd.ms-powerpoint\",\r\n \".ra\": \"audio/vnd.rn-realaudio\",\r\n \".ras\": \"application/x-ras\",\r\n \".rdf\": \"text/xml\",\r\n \".red\": \"application/x-red\",\r\n \".rjs\": \"application/vnd.rn-realsystem-rjs\",\r\n \".rlc\": \"application/x-rlc\",\r\n \".rm\": \"application/vnd.rn-realmedia\",\r\n \".rmi\": \"audio/mid\",\r\n \".rmm\": \"audio/x-pn-realaudio\",\r\n \".rms\": \"application/vnd.rn-realmedia-secure\",\r\n \".rmx\": \"application/vnd.rn-realsystem-rmx\",\r\n \".rp\": \"image/vnd.rn-realpix\",\r\n \".rsml\": \"application/vnd.rn-rsml\",\r\n \".rtf\": \"application/msword\",\r\n \".rv\": \"video/vnd.rn-realvideo\",\r\n \".sat\": \"application/x-sat\",\r\n \".sdw\": \"application/x-sdw\",\r\n \".slb\": \"application/x-slb\",\r\n \".slk\": \"drawing/x-slk\",\r\n \".smil\": \"application/smil\",\r\n \".snd\": \"audio/basic\",\r\n \".sor\": \"text/plain\",\r\n \".spl\": \"application/futuresplash\",\r\n \".ssm\": \"application/streamingmedia\",\r\n \".stl\": \"application/vnd.ms-pki.stl\",\r\n \".sty\": \"application/x-sty\",\r\n \".swf\": \"application/x-shockwave-flash\",\r\n \".tg4\": \"application/x-tg4\",\r\n \".tif\": \"image/tiff\",\r\n \".tiff\": \"image/tiff\",\r\n \".top\": \"drawing/x-top\",\r\n \".tsd\": \"text/xml\",\r\n \".uin\": \"application/x-icq\",\r\n \".vcf\": \"text/x-vcard\",\r\n \".vdx\": \"application/vnd.visio\",\r\n \".vpg\": \"application/x-vpeg005\",\r\n \".vsd\": \"application/x-vsd\",\r\n \".vst\": \"application/vnd.visio\",\r\n \".vsw\": \"application/vnd.visio\",\r\n \".vtx\": \"application/vnd.visio\",\r\n \".wav\": \"audio/wav\",\r\n \".wb1\": \"application/x-wb1\",\r\n \".wb3\": \"application/x-wb3\",\r\n \".wiz\": \"application/msword\",\r\n \".wk4\": \"application/x-wk4\",\r\n \".wks\": \"application/x-wks\",\r\n \".wma\": \"audio/x-ms-wma\",\r\n \".wmf\": \"application/x-wmf\",\r\n \".wmv\": \"video/x-ms-wmv\",\r\n \".wmz\": \"application/x-ms-wmz\",\r\n \".wpd\": \"application/x-wpd\",\r\n \".wpl\": \"application/vnd.ms-wpl\",\r\n \".wr1\": \"application/x-wr1\",\r\n \".wrk\": \"application/x-wrk\",\r\n \".ws2\": \"application/x-ws\",\r\n \".wsdl\": \"text/xml\",\r\n \".xdp\": \"application/vnd.adobe.xdp\",\r\n \".xfd\": \"application/vnd.adobe.xfd\",\r\n \".xhtml\": \"text/html\",\r\n \".xls\": \"application/x-xls\",\r\n \".xml\": \"text/xml\",\r\n \".xq\": \"text/xml\",\r\n \".xquery\": \"text/xml\",\r\n \".xsl\": \"text/xml\",\r\n \".xwd\": \"application/x-xwd\",\r\n \".sis\": \"application/vnd.symbian.install\",\r\n \".x_t\": \"application/x-x_t\",\r\n \".apk\": \"application/vnd.android.package-archive\",\r\n \"0.301\": \"application/x-301\",\r\n \"0.906\": \"application/x-906\",\r\n \".a11\": \"application/x-a11\",\r\n \".ai\": \"application/postscript\",\r\n \".aifc\": \"audio/aiff\",\r\n \".anv\": \"application/x-anv\",\r\n \".asf\": \"video/x-ms-asf\",\r\n \".asx\": \"video/x-ms-asf\",\r\n \".avi\": \"video/avi\",\r\n \".biz\": \"text/xml\",\r\n \".bot\": \"application/x-bot\",\r\n \".c90\": \"application/x-c90\",\r\n \".cat\": \"application/vnd.ms-pki.seccat\",\r\n \".cdr\": \"application/x-cdr\",\r\n \".cer\": \"application/x-x509-ca-cert\",\r\n \".cgm\": \"application/x-cgm\",\r\n \".class\": \"java/*\",\r\n \".cmp\": \"application/x-cmp\",\r\n \".cot\": \"application/x-cot\",\r\n \".crt\": \"application/x-x509-ca-cert\",\r\n \".css\": \"text/css\",\r\n \".dbf\": \"application/x-dbf\",\r\n \".dbx\": \"application/x-dbx\",\r\n \".dcx\": \"application/x-dcx\",\r\n \".dgn\": \"application/x-dgn\",\r\n \".dll\": \"application/x-msdownload\",\r\n \".dot\": \"application/msword\",\r\n \".dtd\": \"text/xml\",\r\n \".dwf\": \"application/x-dwf\",\r\n \".dxb\": \"application/x-dxb\",\r\n \".edn\": \"application/vnd.adobe.edn\",\r\n \".eml\": \"message/rfc822\",\r\n \".epi\": \"application/x-epi\",\r\n \".eps\": \"application/postscript\",\r\n \".exe\": \"application/x-msdownload\",\r\n \".fdf\": \"application/vnd.fdf\",\r\n \".fo\": \"text/xml\",\r\n \".g4\": \"application/x-g4\",\r\n \".tif\": \"image/tiff\",\r\n \".gl2\": \"application/x-gl2\",\r\n \".hgl\": \"application/x-hgl\",\r\n \".hpg\": \"application/x-hpgl\",\r\n \".hqx\": \"application/mac-binhex40\",\r\n \".hta\": \"application/hta\",\r\n \".htm\": \"text/html\",\r\n \".htt\": \"text/webviewhtml\",\r\n \".icb\": \"application/x-icb\",\r\n \".ico\": \"application/x-ico\",\r\n \".ig4\": \"application/x-g4\",\r\n \".iii\": \"application/x-iphone\",\r\n \".ins\": \"application/x-internet-signup\",\r\n \".IVF\": \"video/x-ivf\",\r\n \".jfif\": \"image/jpeg\",\r\n \".jpe\": \"application/x-jpe\",\r\n \".jpg\": \"image/jpeg\",\r\n \".js\": \"application/x-javascript\",\r\n \".la1\": \"audio/x-liquid-file\",\r\n \".latex\": \"application/x-latex\",\r\n \".lbm\": \"application/x-lbm\",\r\n \".ls\": \"application/x-javascript\",\r\n \".m1v\": \"video/x-mpeg\",\r\n \".m3u\": \"audio/mpegurl\",\r\n \".mac\": \"application/x-mac\",\r\n \".math\": \"text/xml\",\r\n \".mdb\": \"application/x-mdb\",\r\n \".mht\": \"message/rfc822\",\r\n \".mi\": \"application/x-mi\",\r\n \".midi\": \"audio/mid\",\r\n \".mml\": \"text/xml\",\r\n \".mns\": \"audio/x-musicnet-stream\",\r\n \".movie\": \"video/x-sgi-movie\",\r\n \".mp2\": \"audio/mp2\",\r\n \".mp3\": \"audio/mp3\",\r\n \".mpa\": \"video/x-mpg\",\r\n \".mpe\": \"video/x-mpeg\",\r\n \".mpg\": \"video/mpg\",\r\n \".mpp\": \"application/vnd.ms-project\",\r\n \".mpt\": \"application/vnd.ms-project\",\r\n \".mpv2\": \"video/mpeg\",\r\n \".mpx\": \"application/vnd.ms-project\",\r\n \".mxp\": \"application/x-mmxp\",\r\n \".nrf\": \"application/x-nrf\",\r\n \".odc\": \"text/x-ms-odc\",\r\n \".p10\": \"application/pkcs10\",\r\n \".p7b\": \"application/x-pkcs7-certificates\",\r\n \".p7m\": \"application/pkcs7-mime\",\r\n \".p7s\": \"application/pkcs7-signature\",\r\n \".pci\": \"application/x-pci\",\r\n \".pcx\": \"application/x-pcx\",\r\n \".pdf\": \"application/pdf\",\r\n \".pfx\": \"application/x-pkcs12\",\r\n \".pic\": \"application/x-pic\",\r\n \".pl\": \"application/x-perl\",\r\n \".pls\": \"audio/scpls\",\r\n \".png\": \"image/png\",\r\n \".pot\": \"application/vnd.ms-powerpoint\",\r\n \".ppm\": \"application/x-ppm\",\r\n \".ppt\": \"application/vnd.ms-powerpoint\",\r\n \".pr\": \"application/x-pr\",\r\n \".prn\": \"application/x-prn\",\r\n \".ps\": \"application/x-ps\",\r\n \".ptn\": \"application/x-ptn\",\r\n \".r3t\": \"text/vnd.rn-realtext3d\",\r\n \".ram\": \"audio/x-pn-realaudio\",\r\n \".rat\": \"application/rat-file\",\r\n \".rec\": \"application/vnd.rn-recording\",\r\n \".rgb\": \"application/x-rgb\",\r\n \".rjt\": \"application/vnd.rn-realsystem-rjt\",\r\n \".rle\": \"application/x-rle\",\r\n \".rmf\": \"application/vnd.adobe.rmf\",\r\n \".rmj\": \"application/vnd.rn-realsystem-rmj\",\r\n \".rmp\": \"application/vnd.rn-rn_music_package\",\r\n \".rmvb\": \"application/vnd.rn-realmedia-vbr\",\r\n \".rnx\": \"application/vnd.rn-realplayer\",\r\n \".rpm\": \"audio/x-pn-realaudio-plugin\",\r\n \".rt\": \"text/vnd.rn-realtext\",\r\n \".rtf\": \"application/x-rtf\",\r\n \".sam\": \"application/x-sam\",\r\n \".sdp\": \"application/sdp\",\r\n \".sit\": \"application/x-stuffit\",\r\n \".sld\": \"application/x-sld\",\r\n \".smi\": \"application/smil\",\r\n \".smk\": \"application/x-smk\",\r\n \".sol\": \"text/plain\",\r\n \".spc\": \"application/x-pkcs7-certificates\",\r\n \".spp\": \"text/xml\",\r\n \".sst\": \"application/vnd.ms-pki.certstore\",\r\n \".stm\": \"text/html\",\r\n \".svg\": \"text/xml\",\r\n \".tdf\": \"application/x-tdf\",\r\n \".tga\": \"application/x-tga\",\r\n \".tif\": \"application/x-tif\",\r\n \".tld\": \"text/xml\",\r\n \".torrent\": \"application/x-bittorrent\",\r\n \".txt\": \"text/plain\",\r\n \".uls\": \"text/iuls\",\r\n \".vda\": \"application/x-vda\",\r\n \".vml\": \"text/xml\",\r\n \".vsd\": \"application/vnd.visio\",\r\n \".vss\": \"application/vnd.visio\",\r\n \".vst\": \"application/x-vst\",\r\n \".vsx\": \"application/vnd.visio\",\r\n \".vxml\": \"text/xml\",\r\n \".wax\": \"audio/x-ms-wax\",\r\n \".wb2\": \"application/x-wb2\",\r\n \".wbmp\": \"image/vnd.wap.wbmp\",\r\n \".wk3\": \"application/x-wk3\",\r\n \".wkq\": \"application/x-wkq\",\r\n \".wm\": \"video/x-ms-wm\",\r\n \".wmd\": \"application/x-ms-wmd\",\r\n \".wml\": \"text/vnd.wap.wml\",\r\n \".wmx\": \"video/x-ms-wmx\",\r\n \".wp6\": \"application/x-wp6\",\r\n \".wpg\": \"application/x-wpg\",\r\n \".wq1\": \"application/x-wq1\",\r\n \".wri\": \"application/x-wri\",\r\n \".ws\": \"application/x-ws\",\r\n \".wsc\": \"text/scriptlet\",\r\n \".wvx\": \"video/x-ms-wvx\",\r\n \".xdr\": \"text/xml\",\r\n \".xfdf\": \"application/vnd.adobe.xfdf\",\r\n \".xls\": \"application/vnd.ms-excel\",\r\n \".xlw\": \"application/x-xlw\",\r\n \".xpl\": \"audio/scpls\",\r\n \".xql\": \"text/xml\",\r\n \".xsd\": \"text/xml\",\r\n \".xslt\": \"text/xml\",\r\n \".x_b\": \"application/x-x_b\",\r\n \".sisx\": \"application/vnd.symbian.install\",\r\n \".ipa\": \"application/vnd.iphone\",\r\n \".xap\": \"application/x-silverlight-app\",\r\n \".zip\": \"application/x-zip-compressed\",\r\n}"]} -------------------------------------------------------------------------------- /miniprogram/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprogram", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "miniprogram", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@zlyboy/wx-formdata": "^1.0.2", 13 | "crypto-js": "^4.1.1" 14 | }, 15 | "devDependencies": { 16 | "@types/crypto-js": "^4.1.1" 17 | } 18 | }, 19 | "node_modules/@types/crypto-js": { 20 | "version": "4.1.1", 21 | "resolved": "https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.1.1.tgz", 22 | "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", 23 | "dev": true 24 | }, 25 | "node_modules/@zlyboy/wx-formdata": { 26 | "version": "1.0.2", 27 | "resolved": "https://registry.npmmirror.com/@zlyboy/wx-formdata/-/wx-formdata-1.0.2.tgz", 28 | "integrity": "sha512-acHVmcIQasoLjXXSnDi40s6ggtGE+/q/5VcdPH83uqhBHScVk6fCDtnPDD0BkN0Mka7SzIpRhd+ybbUDy1QFIg==" 29 | }, 30 | "node_modules/crypto-js": { 31 | "version": "4.1.1", 32 | "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.1.1.tgz", 33 | "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /miniprogram/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprogram", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@zlyboy/wx-formdata": "^1.0.2", 14 | "crypto-js": "^4.1.1" 15 | }, 16 | "devDependencies": { 17 | "@types/crypto-js": "^4.1.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /miniprogram/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "快进到退学", 3 | "enablePullDownRefresh": true, 4 | "usingComponents": {} 5 | } 6 | -------------------------------------------------------------------------------- /miniprogram/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | {{message}} 7 | 8 | 13 | 14 | 15 | {{activityInfo.courseInfo.course.name}}({{activityInfo.courseInfo.class.name}}):{{activityInfo.activity.name}}({{signMethods[activityId]}}) 16 | 17 | {{activityInfo.activity.endTimeForHuman}} 18 | 19 | 20 | 21 | 22 | 23 | {{user.name}} 24 | {{results[activityId][user.id]}} 25 | 26 | {{user.id}} 27 | 28 | 29 | 30 | 31 | 38 | 39 | 40 | 41 | 48 | 53 | 54 | 55 | 56 | 57 | 选择签到方式 58 | 59 | 60 | 61 | 66 | {{item}} 67 | 68 | 69 | 70 | 取消 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /miniprogram/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | #container { 3 | padding-top: 15px; 4 | padding-bottom: calc(40px + constant(safe-area-inset-bottom)); 5 | padding-bottom: calc(40px + env(safe-area-inset-bottom)); 6 | } 7 | 8 | #no-activity { 9 | text-align: center; 10 | } 11 | 12 | .weui-cell__desc { 13 | font-size: 0.7058823529411765rem; 14 | color: var(--weui-FG-2); 15 | line-height: 1.4; 16 | padding-top: 4px; 17 | } 18 | -------------------------------------------------------------------------------- /miniprogram/pages/locationPicker/locationPicker.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "位置签到信息", 3 | "usingComponents": {} 4 | } 5 | -------------------------------------------------------------------------------- /miniprogram/pages/locationPicker/locationPicker.ts: -------------------------------------------------------------------------------- 1 | // pages/locationPicker/locationPicker.ts 2 | Page({ 3 | 4 | /** 5 | * 页面的初始数据 6 | */ 7 | data: { 8 | longitude: '', 9 | latitude: '', 10 | altitude: '', 11 | random: true, 12 | address: '', 13 | markers: [] as { [key: string]: string | number }[], 14 | coordinateErrorMessage: null as null | string, 15 | addressErrorMessage: null as null | string, 16 | }, 17 | 18 | emptyMethod() {}, 19 | 20 | copyBaiduCoordinatePicker() { 21 | wx.setClipboardData({ 22 | data: 'https://api.map.baidu.com/lbsapi/getpoint/index.html', 23 | }); 24 | }, 25 | 26 | setMarkers() { 27 | this.setData({ markers: [{ 28 | id: 0, 29 | longitude: Number.parseFloat(this.data.longitude) || 113.324520, 30 | latitude: Number.parseFloat(this.data.latitude) || 23.099994, 31 | iconPath: '../../images/location_on_FILL1_wght400_GRAD0_opsz48.svg', 32 | width: 45, 33 | height: 45, 34 | }] }); 35 | }, 36 | 37 | sign() { 38 | this.setData({ coordinateErrorMessage: null, addressErrorMessage: null }); 39 | 40 | const rawLongitude = this.data.longitude; 41 | const rawLatitude = this.data.latitude; 42 | const rawAltitude = this.data.altitude; 43 | 44 | const decimalOfLongitude = rawLongitude.slice(rawLongitude.indexOf('.') + 1); 45 | const decimalOfLatitude = rawLatitude.slice(rawLatitude.indexOf('.') + 1); 46 | 47 | if (decimalOfLongitude.length > 13 || decimalOfLatitude.length > 13) { 48 | return this.setData({ errorMessage: '经纬度小数长度不能超过13' }); 49 | } 50 | 51 | const longitude = Number.parseFloat(rawLongitude); 52 | const latitude = Number.parseFloat(rawLatitude); 53 | const altitude = Number.parseFloat(rawAltitude); 54 | 55 | if (Number.isNaN(longitude)) { 56 | return this.setData({ coordinateErrorMessage: '经度应当填写数字' }); 57 | } 58 | 59 | if (Number.isNaN(latitude)) { 60 | return this.setData({ coordinateErrorMessage: '纬度应当填写数字' }); 61 | } 62 | 63 | this.getOpenerEventChannel().emit( 64 | 'callback', 65 | this.data.random, 66 | longitude, 67 | latitude, 68 | altitude, 69 | this.data.address, 70 | ); 71 | wx.navigateBack(); 72 | }, 73 | 74 | cancel() { 75 | this.data.longitude = '-181'; 76 | this.data.latitude = '-91'; 77 | this.data.altitude = ''; 78 | this.data.random = false; 79 | this.data.address = ''; 80 | this.sign(); 81 | }, 82 | 83 | /** 84 | * 生命周期函数--监听页面加载 85 | */ 86 | onLoad() { 87 | if (!this.getOpenerEventChannel()) { 88 | console.warn('openerEventChannel not found'); 89 | wx.navigateBack(); 90 | } 91 | 92 | this.setMarkers(); 93 | }, 94 | 95 | /** 96 | * 生命周期函数--监听页面初次渲染完成 97 | */ 98 | onReady() { 99 | 100 | }, 101 | 102 | /** 103 | * 生命周期函数--监听页面显示 104 | */ 105 | onShow() { 106 | 107 | }, 108 | 109 | /** 110 | * 生命周期函数--监听页面隐藏 111 | */ 112 | onHide() { 113 | 114 | }, 115 | 116 | /** 117 | * 生命周期函数--监听页面卸载 118 | */ 119 | onUnload() { 120 | 121 | }, 122 | 123 | /** 124 | * 页面相关事件处理函数--监听用户下拉动作 125 | */ 126 | onPullDownRefresh() { 127 | 128 | }, 129 | 130 | /** 131 | * 页面上拉触底事件的处理函数 132 | */ 133 | onReachBottom() { 134 | 135 | }, 136 | 137 | /** 138 | * 用户点击右上角分享 139 | */ 140 | onShareAppMessage() { 141 | 142 | } 143 | }); 144 | -------------------------------------------------------------------------------- /miniprogram/pages/locationPicker/locationPicker.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 请使用百度地图拾取坐标系统获取坐标。 14 | 15 | 如果是无需定位的二维码签到,请点击取消。 16 | 17 | 18 | 20 | 经纬度 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | 62 | 63 | 72 | 73 | 76 | {{coordinateErrorMessage}} 77 | 78 | 79 | 80 | 地址 81 | 82 | 83 | 84 | 93 | 94 | 95 | 96 | 99 | {{addressErrorMessage}} 100 | 101 | 102 | 103 | 104 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /miniprogram/pages/locationPicker/locationPicker.wxss: -------------------------------------------------------------------------------- 1 | /* pages/locationPicker/locationPicker.wxss */ 2 | #container { 3 | padding-top: 0px; 4 | padding-bottom: calc(40px + constant(safe-area-inset-bottom)); 5 | padding-bottom: calc(40px + env(safe-area-inset-bottom)); 6 | } 7 | 8 | .link { 9 | display: inline; 10 | color: var(--weui-LINK); 11 | } 12 | 13 | .weui-cells__group_form .weui-cells__tips_warn { 14 | color: var(--weui-RED); 15 | } 16 | -------------------------------------------------------------------------------- /miniprogram/pages/privacyTips/privacyTips.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "", 3 | "usingComponents": {} 4 | } 5 | -------------------------------------------------------------------------------- /miniprogram/pages/privacyTips/privacyTips.ts: -------------------------------------------------------------------------------- 1 | // pages/remeberPasswordTips/remeberPasswordTips.ts 2 | Page({ 3 | 4 | /** 5 | * 页面的初始数据 6 | */ 7 | data: { 8 | 9 | }, 10 | 11 | copyWeChatDocumentLink() { 12 | wx.setClipboardData({ 13 | data: 'https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorage.html#Object-object', 14 | }); 15 | }, 16 | 17 | /** 18 | * 生命周期函数--监听页面加载 19 | */ 20 | onLoad() { 21 | 22 | }, 23 | 24 | /** 25 | * 生命周期函数--监听页面初次渲染完成 26 | */ 27 | onReady() { 28 | 29 | }, 30 | 31 | /** 32 | * 生命周期函数--监听页面显示 33 | */ 34 | onShow() { 35 | 36 | }, 37 | 38 | /** 39 | * 生命周期函数--监听页面隐藏 40 | */ 41 | onHide() { 42 | 43 | }, 44 | 45 | /** 46 | * 生命周期函数--监听页面卸载 47 | */ 48 | onUnload() { 49 | 50 | }, 51 | 52 | /** 53 | * 页面相关事件处理函数--监听用户下拉动作 54 | */ 55 | onPullDownRefresh() { 56 | 57 | }, 58 | 59 | /** 60 | * 页面上拉触底事件的处理函数 61 | */ 62 | onReachBottom() { 63 | 64 | }, 65 | 66 | /** 67 | * 用户点击右上角分享 68 | */ 69 | onShareAppMessage() { 70 | 71 | } 72 | }); 73 | -------------------------------------------------------------------------------- /miniprogram/pages/privacyTips/privacyTips.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 信息安全与隐私提醒 5 | 6 | 7 | 本页面只会在您从未登入用户时主动弹出,您仍可通过「添加账号」页面底部的超链接点击查看此页面。 8 | 9 | 10 | 本小程序在任何情况下都不会将您的信息上传到超星学习通以外的地方,也不会将您的信息储存或共享给微信以外的程序! 11 | 12 | 13 | 14 | 本小程序会储存一些信息用于签到。明文储存: 15 | 16 | 账户登录状态信息(Cookie) 17 | 账户中的课程和班级信息 18 | 19 | 加密储存: 20 | 21 | 账户ID 22 | 账户所属人的姓名 23 | 24 | 如果您选择了「记住密码」,还将加密储存: 25 | 26 | 账户ID 27 | 账号 28 | 密码 29 | 30 | 31 | 上述需要储存的信息,会交由微信储存在单独为本小程序准备的存储空间中,操作系统和微信会共同保证这些明文储存和加密储存的信息隐私不被其他小程序、其他app或其他人读取。而需要加密的信息,会交由微信使用业界通用的AES-128算法加密后储存微信加密说明文档)。但即使有加密,储存账号密码的行为仍然具有一定的风险不选择「记住密码」,本小程序则不会储存学习通账户的账号和密码。 32 | 33 | 34 | 35 | 36 | 在点击「登录」按钮前,您可以选择是否开启「记住密码」。在登录后,您可以在「用户管理」中选择用户,点击「登出」,即可从存储空间中删除该账号相关的所有信息。如果您怀疑密码已经泄露,可以在超星学习通内修改密码。 37 | 38 | 39 | 登入用户代表您已理解以上内容。 40 | 41 | 42 | 43 | 46 | 真啰嗦 47 | 48 | 49 | -------------------------------------------------------------------------------- /miniprogram/pages/privacyTips/privacyTips.wxss: -------------------------------------------------------------------------------- 1 | /* pages/remeberPasswordTips/remeberPasswordTips.wxss */ 2 | .container { 3 | padding-top: 15px; 4 | padding-bottom: calc(40px + constant(safe-area-inset-bottom)); 5 | padding-bottom: calc(40px + env(safe-area-inset-bottom)); 6 | } 7 | 8 | .link { 9 | display: inline; 10 | color: var(--weui-LINK); 11 | } 12 | 13 | .weui-article__ul { 14 | list-style: disc; 15 | margin-left: 1.2em; 16 | margin-bottom: 24px; 17 | } 18 | 19 | .weui-article__li { 20 | display: list-item; 21 | margin: .5em 0; 22 | } 23 | -------------------------------------------------------------------------------- /miniprogram/pages/userInfo/userInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "用户登入", 3 | "usingComponents": {} 4 | } 5 | -------------------------------------------------------------------------------- /miniprogram/pages/userInfo/userInfo.ts: -------------------------------------------------------------------------------- 1 | import { loginByFanya, loginByV11 } from "../../services/login"; 2 | import { 3 | Credential, 4 | GetCookieFailedResult, 5 | LoginResult, 6 | NameFailedResult, 7 | LoginType, 8 | } from "../../utils/types"; 9 | import { isString } from "../../utils/util"; 10 | 11 | // pages/userInfo/userInfo.ts 12 | Page({ 13 | 14 | /** 15 | * 页面的初始数据 16 | */ 17 | data: { 18 | id: -1, 19 | name: '', 20 | username: '', 21 | password: '', 22 | totalOfCourse: 0, 23 | remeber: false, 24 | firstTimeRemeber: true, 25 | hideUserManagements: true, 26 | errorMessage: null as null | string, 27 | }, 28 | emptyMethod() {}, 29 | /** 30 | * Remove user by channel 31 | */ 32 | removeUser() { 33 | const { id } = this.data; 34 | 35 | console.debug(`remove user ${id}`); 36 | 37 | this.getOpenerEventChannel().emit('removeUser', id); 38 | this.getOpenerEventChannel().emit('removeCredential', id); 39 | }, 40 | /** 41 | * Add user by channel 42 | */ 43 | addUser( 44 | loginType: LoginType, 45 | result: LoginResult | GetCookieFailedResult | NameFailedResult 46 | ) { 47 | if (!result.status) { 48 | console.error("failed to login user", result); 49 | this.setData({ errorMessage: JSON.stringify(result.data) }); 50 | return; 51 | } 52 | 53 | const { user } = result; 54 | 55 | this.getOpenerEventChannel().emit('addUser', user); 56 | if (this.data.remeber) { 57 | console.debug(`cache username and password for user ${user.id}`); 58 | 59 | const credential: Credential = { 60 | id: user.id, 61 | loginType, 62 | username: this.data.username, 63 | password: this.data.password, 64 | }; 65 | this.getOpenerEventChannel().emit('addCredential', credential); 66 | } else { 67 | // maybe user is updating cookie 68 | this.getOpenerEventChannel().emit('removeCredential', user.id); 69 | } 70 | 71 | this.setData({ errorMessage: null }); 72 | wx.navigateBack(); 73 | }, 74 | loginFanya() { 75 | const { username, password } = this.data; 76 | loginByFanya(username, password).then((result) => 77 | this.addUser('fanya', result) 78 | ).catch((e) => { 79 | console.error(e); 80 | this.setData({ errorMessage: JSON.stringify(e) }); 81 | }); 82 | }, 83 | loginV11() { 84 | const { username, password } = this.data; 85 | loginByV11(username, password).then((result) => 86 | this.addUser('v11', result) 87 | ).catch((e) => { 88 | console.error(e); 89 | this.setData({ errorMessage: JSON.stringify(e) }); 90 | }); 91 | }, 92 | /** 93 | * Jump to privacyTips if no credential saved 94 | */ 95 | toPrivacyTips() { 96 | if (this.data.firstTimeRemeber && this.data.remeber) { 97 | wx.navigateTo({ url: '../privacyTips/privacyTips' }); 98 | } 99 | }, 100 | /** 101 | * Refresh courseInfoArray by channel 102 | */ 103 | refreshCourseInfoArray() { 104 | console.debug(`refresh courseInfoArray of user ${this.data.id}`); 105 | this.getOpenerEventChannel().emit('refreshCourseInfoArray', this.data.id); 106 | wx.showToast({ title: '已请求刷新', icon: 'none' }); 107 | }, 108 | 109 | /** 110 | * 生命周期函数--监听页面加载 111 | */ 112 | onLoad(options) { 113 | if (!isString(options.channelOpened) || !this.getOpenerEventChannel()) { 114 | wx.navigateBack(); 115 | } 116 | 117 | const rawChannelOpened = options.channelOpened as string; 118 | try { 119 | const channelOpened = JSON.parse(rawChannelOpened); 120 | 121 | console.debug(`channelOpened =`, channelOpened); 122 | if (!channelOpened) { 123 | wx.navigateBack(); 124 | } 125 | } catch (e) { 126 | if (e instanceof TypeError) { 127 | console.warn('failed to parse channelOpened from opener'); 128 | wx.navigateBack(); 129 | } else { 130 | wx.navigateBack(); 131 | throw e; 132 | } 133 | } 134 | 135 | if (options.id) { 136 | try { 137 | const id = JSON.parse(options.id); 138 | 139 | console.debug(`id =`, id); 140 | 141 | const totalsOfCourse = getApp().globalData.totalsOfCourse; 142 | console.debug(`totalsOfCourse =`, totalsOfCourse); 143 | let totalOfCourse = totalsOfCourse[id]; 144 | 145 | if (typeof totalOfCourse !== 'number' || Number.isNaN(totalOfCourse)) { 146 | console.warn('failed to read totalsOfCourse from globalData'); 147 | totalOfCourse = 0; 148 | } 149 | 150 | this.setData({ 151 | id, 152 | totalOfCourse, 153 | hideUserManagements: false, 154 | }); 155 | } catch (e) { 156 | if (e instanceof TypeError) { 157 | console.warn('failed to parse id from opener'); 158 | wx.navigateBack(); 159 | } else { 160 | wx.navigateBack(); 161 | throw e; 162 | } 163 | } 164 | } 165 | 166 | if (options.name) { 167 | try { 168 | const name = JSON.parse(options.name); 169 | 170 | this.setData({ 171 | name, 172 | }); 173 | } catch (e) { 174 | if (e instanceof TypeError) { 175 | console.warn('failed to parse name from opener'); 176 | wx.navigateBack(); 177 | } else { 178 | wx.navigateBack(); 179 | throw e; 180 | } 181 | } 182 | } 183 | 184 | if (options.credential) { 185 | try { 186 | const credential: Credential = JSON.parse(options.credential); 187 | 188 | const totalsOfCourse = getApp().globalData.totalsOfCourse; 189 | console.debug(`totalsOfCourse =`, totalsOfCourse); 190 | let totalOfCourse = totalsOfCourse[credential.id]; 191 | 192 | if (typeof totalOfCourse !== 'number' || Number.isNaN(totalOfCourse)) { 193 | console.warn('failed to read totalsOfCourse from globalData'); 194 | totalOfCourse = 0; 195 | } 196 | 197 | this.setData({ 198 | remeber: true, 199 | id: credential.id, 200 | username: credential.username, 201 | password: credential.password, 202 | totalOfCourse, 203 | hideUserManagements: false, 204 | }); 205 | } catch (e) { 206 | if (e instanceof TypeError) { 207 | console.warn('failed to parse credential from opener'); 208 | wx.navigateBack(); 209 | } else { 210 | wx.navigateBack(); 211 | throw e; 212 | } 213 | } 214 | } 215 | 216 | if (options.firstTimeRemeber) { 217 | try { 218 | const firstTimeRemeber: boolean = JSON.parse(options.firstTimeRemeber); 219 | this.setData({ firstTimeRemeber }); 220 | } catch (e) { 221 | if (e instanceof TypeError) { 222 | console.warn('failed to parse firstTimeRemeber from opener'); 223 | wx.navigateBack(); 224 | } else { 225 | wx.navigateBack(); 226 | throw e; 227 | } 228 | } 229 | } 230 | }, 231 | 232 | /** 233 | * 生命周期函数--监听页面初次渲染完成 234 | */ 235 | onReady() { 236 | 237 | }, 238 | 239 | /** 240 | * 生命周期函数--监听页面显示 241 | */ 242 | onShow() { 243 | 244 | }, 245 | 246 | /** 247 | * 生命周期函数--监听页面隐藏 248 | */ 249 | onHide() { 250 | 251 | }, 252 | 253 | /** 254 | * 生命周期函数--监听页面卸载 255 | */ 256 | onUnload() { 257 | 258 | }, 259 | 260 | /** 261 | * 页面相关事件处理函数--监听用户下拉动作 262 | */ 263 | onPullDownRefresh() { 264 | 265 | }, 266 | 267 | /** 268 | * 页面上拉触底事件的处理函数 269 | */ 270 | onReachBottom() { 271 | 272 | }, 273 | 274 | /** 275 | * 用户点击右上角分享 276 | */ 277 | onShareAppMessage() { 278 | 279 | } 280 | }); 281 | -------------------------------------------------------------------------------- /miniprogram/pages/userInfo/userInfo.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用户信息 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 登录信息 27 | 28 | 29 | 30 | 31 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 54 | 55 | 56 | 57 | 65 | 68 | {{errorMessage}} 69 | 70 | 71 | 72 | 73 | 74 | 课程数量:{{totalOfCourse}} 75 | 76 | 信息安全与隐私提醒 77 | 78 | 79 | 80 | 81 | 86 | 91 | 97 | 102 | 登出 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /miniprogram/pages/userInfo/userInfo.wxss: -------------------------------------------------------------------------------- 1 | /* pages/userInfo/userInfo.wxss */ 2 | #container { 3 | padding-top: 0px; 4 | padding-bottom: calc(40px + constant(safe-area-inset-bottom)); 5 | padding-bottom: calc(40px + env(safe-area-inset-bottom)); 6 | } 7 | 8 | .weui-cells__group_form .weui-cells__tips_warn { 9 | color: var(--weui-RED); 10 | } 11 | -------------------------------------------------------------------------------- /miniprogram/pages/userManager/userManager.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "用户管理", 3 | "enablePullDownRefresh": true, 4 | "usingComponents": {} 5 | } 6 | -------------------------------------------------------------------------------- /miniprogram/pages/userManager/userManager.ts: -------------------------------------------------------------------------------- 1 | import { loginByFanya } from "../../services/login"; 2 | import { Credential, GetCookieFailedResult, LoginResult, NameFailedResult, User } from "../../utils/types"; 3 | import { isString, parametersToStringifyString, toCredential } from "../../utils/util"; 4 | 5 | // pages/userManager/userManager.ts 6 | Page({ 7 | 8 | /** 9 | * 页面的初始数据 10 | */ 11 | data: { 12 | users: [] as User[], 13 | credentials: [] as Credential[], 14 | idToCredentials: {} as { [id: number]: Credential }, 15 | }, 16 | /** 17 | * Convert credentials to Object for wxml 18 | * @param credentials credentials from storage 19 | */ 20 | toIdToCredentials(credentials: Credential[]) { 21 | return credentials.reduce((c, credential) => ({ 22 | ...c, 23 | [credential.id]: credential, 24 | }), {}); 25 | }, 26 | /** 27 | * Open userInfo from wxml 28 | * @param e id, name and credential from wxml 29 | */ 30 | openUserInfo(e: WechatMiniprogram.BaseEvent) { 31 | const { id, name, credential } = e.currentTarget.dataset as { 32 | [key: string]: string 33 | }; 34 | 35 | let existedParameters = {}; 36 | try { 37 | existedParameters = JSON.parse(e.target.dataset.parameters); 38 | } catch (e) { 39 | if (!(e instanceof SyntaxError)) { 40 | console.warn( 41 | 'unknown error occurred during parsing parameters for openUserInfo', 42 | e, 43 | ); 44 | } 45 | } 46 | 47 | const parameters = { 48 | ...existedParameters, 49 | channelOpened: this.getOpenerEventChannel() !== null, 50 | firstTimeRemeber: !(this.data.credentials.length > 0), 51 | ...(credential ? { credential } : {}), 52 | ...(id ? { id } : {}), 53 | ...(name ? { name } : {}), 54 | }; 55 | 56 | console.debug('parsed parameters =', parameters); 57 | 58 | wx.navigateTo({ 59 | url: `../userInfo/userInfo?${parametersToStringifyString(parameters)}`, 60 | events: { 61 | /** 62 | * Add user from channel 63 | * @param user user to be added 64 | */ 65 | addUser: (user: User) => { 66 | const filteredUsers = this.data.users.filter((u) => u.id !== user.id); 67 | const newUsers = filteredUsers.concat(user); 68 | 69 | console.debug('newUsers =', newUsers); 70 | 71 | this.setData({ users: newUsers }); 72 | this.getOpenerEventChannel().emit('addUser', user); 73 | }, 74 | /** 75 | * Add credential from channel 76 | * @param credential credential to be added 77 | */ 78 | addCredential: (credential: Credential) => { 79 | const filteredCredentials = this.data.credentials 80 | .filter((c) => c.id !== credential.id); 81 | const newCredentials = filteredCredentials.concat(credential); 82 | 83 | this.setData({ 84 | credentials: newCredentials, 85 | idToCredentials: this.toIdToCredentials(newCredentials), 86 | }); 87 | wx.setStorage({ key: 'credentials', encrypt: true, data: newCredentials }); 88 | }, 89 | /** 90 | * Remove user from channel 91 | * @param id id of user to be removed from caches, users and nameOfUsers 92 | */ 93 | removeUser: (id: number) => { 94 | console.debug(`remove user ${id}`); 95 | 96 | const users = this.data.users.filter((u) => u.id !== id); 97 | this.setData({ users }); 98 | this.getOpenerEventChannel().emit('removeUser', id); 99 | }, 100 | /** 101 | * Remove credential from channel 102 | * @param id id of credential to be removed from credentials 103 | */ 104 | removeCredential: (id: number) => { 105 | console.debug(`remove credential ${id}`); 106 | 107 | const credentials = this.data.credentials.filter((c) => c.id !== id); 108 | wx.setStorage({ key: 'credentials', encrypt: true, data: credentials }) 109 | .then(() => 110 | this.setData({ 111 | credentials: credentials, 112 | idToCredentials: this.toIdToCredentials(credentials), 113 | }) 114 | ); 115 | }, 116 | /** 117 | * Refresh courseInfoArray by ID from channel 118 | * @param id ID of user to be refreshed 119 | */ 120 | refreshCourseInfoArray: (id: number) => this.getOpenerEventChannel() 121 | .emit('refreshCourseInfoArray', id), 122 | } 123 | }); 124 | }, 125 | 126 | copyGitHubLink() { 127 | wx.setClipboardData({ 128 | data: 'https://github.com/DroppingOutSpeedrun/Dropping-Out-Speedrun', 129 | }); 130 | }, 131 | 132 | copyBitbucketLink() { 133 | wx.setClipboardData({ 134 | data: 'https://bitbucket.org/dropping-out-speedrun/dropping-out-speedrun', 135 | }); 136 | }, 137 | 138 | /** 139 | * 生命周期函数--监听页面加载 140 | */ 141 | onLoad(options) { 142 | if (!this.getOpenerEventChannel()) { 143 | console.warn('openerEventChannel not found'); 144 | wx.navigateBack(); 145 | } 146 | 147 | wx.getStorage({ key: 'credentials', encrypt: true }).then((result) => { 148 | console.debug('get credentials from storage', result); 149 | 150 | const credentials = result.data; 151 | if (!Array.isArray(credentials)) { 152 | console.warn('failed to parse credentials from storage', result); 153 | wx.showToast({ title: '读取账号密码信息时出错', icon: 'error' }); 154 | return; 155 | } 156 | 157 | try { 158 | const prasedCredentials = credentials.map((credential) => 159 | toCredential(credential) 160 | ); 161 | this.setData({ 162 | credentials: prasedCredentials, 163 | idToCredentials: this.toIdToCredentials(prasedCredentials), 164 | }); 165 | } catch (e) { 166 | if (e instanceof TypeError) { 167 | console.warn('failed to parse credentials'); 168 | wx.removeStorage({ key: 'credentials' }); 169 | } else { 170 | throw e; 171 | } 172 | } 173 | }).catch((e) => { 174 | if (isString(e.errMsg) && e.errMsg.includes('data not found')) { 175 | console.debug('credentials is empty yet'); 176 | } else { 177 | throw e; 178 | } 179 | }); 180 | 181 | if (options.users) { 182 | try { 183 | const users = JSON.parse(options.users); 184 | console.debug('users from opener', users); 185 | this.setData({ users }); 186 | } catch (e) { 187 | if (e instanceof TypeError) { 188 | console.warn('failed to parse users from opener'); 189 | } else { 190 | throw e; 191 | } 192 | } 193 | } 194 | }, 195 | 196 | /** 197 | * 生命周期函数--监听页面初次渲染完成 198 | */ 199 | onReady() { 200 | 201 | }, 202 | 203 | /** 204 | * 生命周期函数--监听页面显示 205 | */ 206 | onShow() { 207 | }, 208 | 209 | /** 210 | * 生命周期函数--监听页面隐藏 211 | */ 212 | onHide() { 213 | 214 | }, 215 | 216 | /** 217 | * 生命周期函数--监听页面卸载 218 | */ 219 | onUnload() { 220 | 221 | }, 222 | 223 | /** 224 | * 页面相关事件处理函数--监听用户下拉动作 225 | */ 226 | onPullDownRefresh() { 227 | console.debug('start refreshing cookies'); 228 | let promises: Promise[] = []; 229 | this.data.credentials.forEach(({ loginType, username, password }) => { 230 | switch (loginType) { 231 | case 'v11': 232 | promises = promises.concat(loginByFanya(username, password)); 233 | break; 234 | case 'fanya': 235 | default: 236 | if (loginType !== 'fanya') { 237 | console.warn('unknown `loginType` detected, use `fanya` in default'); 238 | } 239 | promises = promises.concat(loginByFanya(username, password)); 240 | break; 241 | } 242 | }); 243 | 244 | Promise.allSettled(promises).then((results) => results.forEach((result) => { 245 | if (result.status !== 'fulfilled') { 246 | console.error('failed to process this promise', result); 247 | wx.showToast({ 248 | title: `部分用户的登录信息刷新失败:${result.reason}`, 249 | icon: 'error', 250 | }); 251 | return; 252 | } 253 | 254 | const { status, message, data } = result.value; 255 | 256 | if (!status) { 257 | console.error(message, data); 258 | wx.showToast({ title: message, icon: 'error' }); 259 | return; 260 | } 261 | 262 | const user: User = (result.value as any).user; 263 | this.getOpenerEventChannel().emit('addUser', user); 264 | })).finally(() => wx.stopPullDownRefresh()); 265 | }, 266 | 267 | /** 268 | * 页面上拉触底事件的处理函数 269 | */ 270 | onReachBottom() { 271 | 272 | }, 273 | 274 | /** 275 | * 用户点击右上角分享 276 | */ 277 | onShareAppMessage() { 278 | 279 | } 280 | }); 281 | -------------------------------------------------------------------------------- /miniprogram/pages/userManager/userManager.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | 20 | {{user.name}} 21 | 22 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 源代码(Bitbucket) 35 | 36 | 37 | 源代码(GitHub) 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /miniprogram/pages/userManager/userManager.wxss: -------------------------------------------------------------------------------- 1 | /* pages/userManager/userManager.wxss */ 2 | #container { 3 | padding-top: 15px; 4 | padding-bottom: calc(40px + constant(safe-area-inset-bottom)); 5 | padding-bottom: calc(40px + env(safe-area-inset-bottom)); 6 | } 7 | 8 | #footer { 9 | padding-top: 20px; 10 | text-align: center; 11 | } 12 | 13 | .link { 14 | display: inline; 15 | color: var(--weui-LINK); 16 | } 17 | -------------------------------------------------------------------------------- /miniprogram/services/cloudStorage.ts: -------------------------------------------------------------------------------- 1 | import { CloudStorageTokenResult, Cookie, UploadFileResult } from "../utils/types"; 2 | import { isString, toCookieString } from "../utils/util"; 3 | // No @types/zlyboy__wx-formdata for @zlyboy/wx-formdata 4 | const FormData = require('@zlyboy/wx-formdata'); 5 | 6 | export const getCloudStorageToken = ( 7 | cookie: Cookie, 8 | ): Promise => new Promise((resolve, reject) => 9 | wx.request({ 10 | url: `https://pan-yz.chaoxing.com/api/token/uservalid`, 11 | header: { 12 | Cookie: toCookieString({ 13 | uf: cookie.uf, 14 | _d: cookie._d, 15 | UID: cookie.id, 16 | vc3: cookie.vc3, 17 | }), 18 | }, 19 | success: ({ data }) => { 20 | const rawData = data as any; 21 | 22 | if (!isString(rawData._token)) { 23 | resolve({ 24 | status: false, 25 | data, 26 | message: 'failed to parse token from data', 27 | cookie, 28 | token: '', 29 | }); 30 | return; 31 | } 32 | 33 | resolve({ 34 | status: true, 35 | data, 36 | message: '', 37 | cookie, 38 | token: rawData._token, 39 | }); 40 | }, 41 | fail: (e) => reject(e), 42 | })); 43 | 44 | export const uploadFile = ( 45 | cookie: Cookie, 46 | token: string, 47 | filePath: string, 48 | ): Promise => { 49 | const form = new FormData(); 50 | const filename = filePath.slice(filePath.lastIndexOf('/') + 1); 51 | form.appendFile('file', filePath, filename); 52 | form.append('puid', cookie.id); 53 | 54 | const data = form.getData(); 55 | return new Promise((resolve, reject) => wx.request({ 56 | method: 'POST', 57 | url: `https://pan-yz.chaoxing.com/upload?_from=mobilelearn&_token=${token}`, 58 | header: { 59 | Cookie: toCookieString({ 60 | uf: cookie.uf, 61 | _d: cookie._d, 62 | UID: cookie.id, 63 | vc3: cookie.vc3, 64 | }), 65 | 'Content-Type': data.contentType, 66 | }, 67 | data: data.buffer, 68 | success: (data) => { 69 | const rawData = data as any; 70 | 71 | if ( 72 | !rawData.data || !isString(rawData.data.objectId) 73 | || !(rawData.data.objectId.length > 0) 74 | ) { 75 | resolve({ 76 | status: false, 77 | data, 78 | message: '', 79 | cookie, 80 | token: '', 81 | filePath: '', 82 | fileId: '', 83 | }); 84 | } 85 | 86 | resolve({ 87 | status: true, 88 | data, 89 | message: '', 90 | cookie, 91 | token, 92 | filePath, 93 | fileId: rawData.data.objectId, 94 | }); 95 | }, 96 | fail: (e) => reject(e), 97 | })); 98 | }; 99 | -------------------------------------------------------------------------------- /miniprogram/services/course.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Cookie, 3 | CourseInfo, 4 | RawActivityWithKnownOtherId, 5 | CourseInfoArrayResult, 6 | ActivitiesResult, 7 | } from '../utils/types'; 8 | import { 9 | isString, 10 | toActiveListObject, 11 | toActivity, 12 | toCookieString, 13 | toRawActivityWithKnownOtherId, 14 | } from '../utils/util'; 15 | 16 | export const getCourseInfoArray = (cookie: Cookie): Promise => 17 | new Promise((resolve, reject) => 18 | wx.request({ 19 | method: 'POST', 20 | url: 'https://mooc1-1.chaoxing.com/visit/courselistdata', 21 | header: { 22 | Accept: 'text/html, */*; q=0.01', 23 | 'Accept-Encoding': 'gzip, deflate', 24 | 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 25 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8;', 26 | Cookie: toCookieString({ _uid: cookie.id, _d: cookie._d, vc3: cookie.vc3 }), 27 | }, 28 | data: 'courseType=1&courseFolderId=0&courseFolderSize=0', 29 | success: ({ data }) => { 30 | if (!isString(data) || !data.includes('course_')) { 31 | resolve({ 32 | status: false, 33 | data, 34 | message: 'cannot found any course in data', 35 | cookie, 36 | courseInfoArray: [], 37 | }); 38 | return; 39 | } 40 | 41 | let courseInfoArray: CourseInfo[] = []; 42 | 43 | const courseIdIdentifier = 'course_'; 44 | const idSpliter = '_'; 45 | const courseNameIdentifier = 'title="'; 46 | const classNameIdentifier = '班级:'; 47 | for (let courseIdBegining = 0; ; courseIdBegining++) { 48 | courseIdBegining = data.indexOf(courseIdIdentifier, courseIdBegining); 49 | if (courseIdBegining < 0) { 50 | break; 51 | } 52 | 53 | const idSpliterIndex = data.indexOf( 54 | idSpliter, 55 | courseIdBegining + courseIdIdentifier.length, 56 | ); 57 | const couseIdEnding = data.indexOf('"', idSpliterIndex + idSpliter.length); 58 | 59 | const courseNameSearchBeginingIndex = data.indexOf( 60 | 'class="course-name', 61 | couseIdEnding, 62 | ); 63 | const courseNameBeginingIndex = data.indexOf( 64 | courseNameIdentifier, 65 | courseNameSearchBeginingIndex, 66 | ); 67 | const courseNameEndingIndex = data.indexOf( 68 | '"', 69 | courseNameBeginingIndex + courseNameIdentifier.length, 70 | ); 71 | 72 | const rawCourseId = data.slice( 73 | courseIdBegining + courseIdIdentifier.length, 74 | idSpliterIndex, 75 | ); 76 | const rawClassId = data.slice( 77 | idSpliterIndex + idSpliter.length, 78 | couseIdEnding, 79 | ); 80 | 81 | const courseId = Number.parseInt(rawCourseId, 10); 82 | const classId = Number.parseInt(rawClassId, 10); 83 | 84 | const courseName = data.slice( 85 | courseNameBeginingIndex + courseNameIdentifier.length, 86 | courseNameEndingIndex, 87 | ); 88 | 89 | const classNameBeginingIndex = data.indexOf( 90 | classNameIdentifier, 91 | courseNameEndingIndex, 92 | ); 93 | const classNameEndingIndex = data.indexOf( 94 | ' reject(e), 121 | })); 122 | 123 | export const getActivities = ( 124 | cookie: Cookie, 125 | courseInfo: CourseInfo, 126 | ): Promise => new Promise((resolve, reject) => wx.request({ 127 | url: `https://mobilelearn.chaoxing.com/v2/apis/active/student/activelist?fid=0&courseId=${courseInfo.course.id}&classId=${courseInfo.class.id}&_=${new Date().getTime()}`, 128 | header: { Cookie: toCookieString({ 129 | uf: cookie.uf, 130 | _d: cookie._d, 131 | UID: cookie.id, 132 | vc3: cookie.vc3 133 | }) }, 134 | success: ({ data }) => { 135 | try { 136 | resolve({ 137 | status: true, 138 | data, 139 | message: '', 140 | cookie, 141 | courseInfo, 142 | activities: toActiveListObject(data).data.activeList 143 | .reduce((filtered, activity) => { 144 | try { 145 | return filtered.concat(toRawActivityWithKnownOtherId(activity)); 146 | } catch (e) { 147 | // append sign event only 148 | return filtered; 149 | } 150 | }, []) 151 | .map((rawActivity) => toActivity(rawActivity)), 152 | }); 153 | } catch (e) { 154 | resolve({ 155 | status: false, 156 | data, 157 | message: e instanceof TypeError 158 | ? `maybe hit the Chaoxing API limit` 159 | : `unknown error: ${JSON.stringify(e)}`, 160 | cookie, 161 | courseInfo, 162 | activities: [], 163 | }); 164 | } 165 | }, 166 | fail: (e) => reject(e), 167 | })); 168 | -------------------------------------------------------------------------------- /miniprogram/services/cxOrz_chaoxing-sign-cli_LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 cxOrz 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. 22 | -------------------------------------------------------------------------------- /miniprogram/services/login.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Cookie, GetCookieFailedResult, GetCookieSuccessResult, NameSuccessResult, 3 | NameFailedResult, LoginResult, 4 | } from '../utils/types'; 5 | import { parseCookiesFromWx, toCookieString } from '../utils/util'; 6 | import crypto from 'crypto-js'; 7 | 8 | const toCookieResult = ( 9 | data: any, cookies: string[], 10 | ): GetCookieSuccessResult | GetCookieFailedResult => { 11 | const rawData = data as any; 12 | 13 | if (typeof rawData.status === 'boolean' && rawData.status) { 14 | try { 15 | return { 16 | status: true, 17 | data: rawData, 18 | message: '', 19 | cookie: parseCookiesFromWx(cookies), 20 | }; 21 | } catch (e) { 22 | return { 23 | status: false, 24 | data, 25 | message: e instanceof TypeError 26 | ? `failed to parse cookie: ${e.message}` 27 | : `unknown error: ${JSON.stringify(e)}`, 28 | cookie: {}, 29 | }; 30 | } 31 | } else { 32 | return { 33 | status: false, 34 | data, 35 | message: 'Chaoxing returned false of status', 36 | cookie: {}, 37 | }; 38 | } 39 | } 40 | 41 | export const getCookieByFanya = ( 42 | username: string, 43 | password: string, 44 | ): Promise => { 45 | const wordArray = crypto.enc.Utf8.parse('u2oh6Vu^HWe40fj'); 46 | const encryptedPassword = crypto.DES.encrypt(password, wordArray, { 47 | mode: crypto.mode.ECB, 48 | padding: crypto.pad.Pkcs7, 49 | }); 50 | const encryptedPasswordString = encryptedPassword.ciphertext.toString(); 51 | 52 | return new Promise((resolve, reject) => wx.request({ 53 | method: 'POST', 54 | header: { 55 | 'Content-Type': 'application/x-www-form-urlencoded', 56 | 'X-Requested-With': 'XMLHttpRequest', 57 | }, 58 | url: 'https://passport2.chaoxing.com/fanyalogin', 59 | data: `uname=${username}&password=${encryptedPasswordString}&fid=-1&t=true&refer=https%253A%252F%252Fi.chaoxing.com&forbidotherlogin=0&validate=`, 60 | success: ({ data, cookies }) => resolve(toCookieResult(data, cookies)), 61 | fail: (e) => reject(e), 62 | })); 63 | } 64 | 65 | // https://www.myitmx.com/123.html 66 | export const getCookieByV11 = ( 67 | username: string, 68 | password: string, 69 | ): Promise => ( 70 | new Promise((resolve, reject) => wx.request({ 71 | url: `https://passport2-api.chaoxing.com/v11/loginregister?code=${password}&cx_xxt_passport=json&uname=${username}&loginType=1&roleSelect=true`, 72 | success: ({ data, cookies }) => resolve(toCookieResult(data, cookies)), 73 | fail: (e) => reject(e), 74 | })) 75 | ) 76 | 77 | export const getName = ( 78 | cookie: Cookie, 79 | ): Promise => 80 | new Promise((resolve, reject) => wx.request({ 81 | url: 'https://passport2.chaoxing.com/mooc/accountManage', 82 | header: { 83 | Cookie: toCookieString({ 84 | uf: cookie.uf, 85 | _d: cookie._d, 86 | UID: cookie.id, 87 | vc3: cookie.vc3, 88 | }), 89 | }, 90 | success: ({ data }) => { 91 | if (typeof data !== 'string') { 92 | resolve({ 93 | status: false, 94 | data, 95 | message: 'cannot resolve data: data is not a string', 96 | cookie, 97 | name: '', 98 | }); 99 | return; 100 | } 101 | 102 | // TODO: possible different data from server? 103 | const nameEndIndex = data.indexOf('姓名'); 104 | if (nameEndIndex < 0) { 105 | resolve({ 106 | status: false, 107 | data, 108 | message: 'cannot find the name in data', 109 | cookie, 110 | name: '', 111 | }); 112 | return; 113 | } 114 | 115 | const endTagBeginingIndex = data.lastIndexOf('<', nameEndIndex); 116 | const startTagEndingIndex = data.lastIndexOf('>', endTagBeginingIndex); 117 | const name = data.slice(startTagEndingIndex + 1, endTagBeginingIndex); 118 | // https://stackoverflow.com/a/16369725 119 | const trimedName = name.replace(/^\s*$(?:\r\n?|\n)/gm, '').trim(); 120 | 121 | resolve({ 122 | status: true, 123 | data, 124 | message: '', 125 | cookie, 126 | name: trimedName, 127 | }); 128 | }, 129 | fail: (e) => reject(e), 130 | })); 131 | 132 | export const toLoginResult = ( 133 | result: GetCookieSuccessResult | GetCookieFailedResult 134 | ): Promise => 135 | new Promise((resolve) => { 136 | if (!result.status) { 137 | resolve(result); 138 | return; 139 | } 140 | 141 | getName(result.cookie).then((result) => resolve( 142 | result.status 143 | ? { 144 | ...result, 145 | user: { ...result.cookie, name: result.name }, 146 | } 147 | : result, 148 | )) 149 | }) 150 | 151 | export const loginByFanya = ( 152 | username: string, 153 | password: string, 154 | ): Promise => 155 | new Promise((resolve, reject) => 156 | getCookieByFanya(username, password).then((result) => 157 | toLoginResult(result).then((r) => resolve(r)) 158 | ).catch((error) => reject(error)) 159 | ); 160 | 161 | export const loginByV11 = ( 162 | username: string, 163 | password: string, 164 | ): Promise => 165 | new Promise((resolve, reject) => 166 | getCookieByV11(username, password).then((result) => 167 | toLoginResult(result).then((r) => resolve(r)) 168 | ).catch((error) => reject(error)) 169 | ); 170 | -------------------------------------------------------------------------------- /miniprogram/services/sign.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Cookie, CookieStringResult, IsPhotoSignResult, LocationSignResult, 3 | PhotoSignResult, PreSignResult, QrCodeSignResult, SignResult, User 4 | } from '../utils/types'; 5 | import { cookieForSign, parametersToString, toCookieString } from '../utils/util'; 6 | 7 | export const preSign = ( 8 | cookie: Cookie, activityId: number, courseId: number, classId: number, 9 | ): Promise => new Promise( 10 | (resolve, reject) => wx.request({ 11 | url: `https://mobilelearn.chaoxing.com/newsign/preSign?courseId=${courseId}&classId=${classId}&activePrimaryId=${activityId}&general=1&sys=1&ls=1&appType=15&&tid=&uid=${cookie.id}&ut=s`, 12 | header: { 13 | Cookie: toCookieString({ 14 | uf: cookie.uf, 15 | _d: cookie._d, 16 | UID: cookie.id, 17 | vc3: cookie.vc3, 18 | }) 19 | }, 20 | // we don't care about the returned data actually 21 | success: (data) => resolve({ 22 | status: true, 23 | data, 24 | message: '', 25 | cookie, 26 | activityId, 27 | courseId, 28 | classId, 29 | }), 30 | fail: (e) => reject(e), 31 | }) 32 | ); 33 | 34 | export const isPhotoSign = ( 35 | activityId: number, 36 | cookie: Cookie 37 | ): Promise => new Promise((resolve, reject) => 38 | wx.request({ 39 | url: `https://mobilelearn.chaoxing.com/v2/apis/active/getPPTActiveInfo?activeId=${activityId}`, 40 | header: { 41 | Cookie: toCookieString({ 42 | uf: cookie.uf, 43 | _d: cookie._d, 44 | UID: cookie.id, 45 | vc3: cookie.vc3, 46 | }), 47 | }, 48 | success: ({ data }) => { 49 | const rawData = data as any; 50 | 51 | if (!rawData.data || typeof rawData.data.ifphoto !== 'number') { 52 | resolve({ 53 | status: false, 54 | message: 'failed to parse ifphoto from data', 55 | data, 56 | cookie, 57 | activityId, 58 | isPhoto: false, 59 | }); 60 | return; 61 | } 62 | 63 | resolve({ 64 | status: true, 65 | message: '', 66 | data, 67 | cookie, 68 | activityId, 69 | isPhoto: rawData.data.ifphoto === 1, 70 | }); 71 | }, 72 | fail: (e) => reject(e), 73 | })); 74 | 75 | const sign = ( 76 | parameters: { [key: string]: any }, 77 | cookie: { [key: string]: any }, 78 | ): Promise => new Promise((resolve, reject) => wx.request({ 79 | url: `https://mobilelearn.chaoxing.com/pptSign/stuSignajax${Object.keys(parameters).length > 0 ? `?${parametersToString(parameters)}` : ''}`, 80 | header: { Cookie: toCookieString(cookie) }, 81 | success: ({ data }) => resolve({ 82 | status: data === 'success' || data === '您已签到过了', 83 | message: '', 84 | data, 85 | cookieString: toCookieString(cookie), 86 | }), 87 | fail: (e) => reject(e), 88 | })); 89 | 90 | export const generalSign = ( 91 | user: User, 92 | activityId: number, 93 | ): Promise => sign( 94 | { 95 | activeId: activityId.toString(), 96 | uid: user.id, 97 | clientip: '', 98 | latitude: '-1', 99 | longitude: '-1', 100 | appType: '15', 101 | fid: user.fid, 102 | name: encodeURIComponent(user.name), 103 | }, 104 | cookieForSign(user), 105 | ).then((result) => ({ ...result, cookie: user, user, activityId })); 106 | 107 | export const qrCodeSign = ( 108 | user: User, 109 | activityId: number, 110 | enc: string, 111 | longitude: number, 112 | latitude: number, 113 | altitude: number, 114 | address: string, 115 | ): Promise => sign( 116 | { 117 | enc, 118 | name: encodeURIComponent(user.name), 119 | activeId: activityId.toString(), 120 | uid: user.id, 121 | clientip: '', 122 | ...( 123 | !Number.isNaN(latitude) && !Number.isNaN(longitude) 124 | ? { 125 | location: encodeURIComponent(`{"result":"1","address":"${address}","latitude":${latitude},"longitude":${longitude},"altitude":${!Number.isNaN(altitude) ? altitude : -1}}`), 126 | } 127 | : {} 128 | ), 129 | useragent: '', 130 | latitude: '-1', 131 | longitude: '-1', 132 | fid: user.fid, 133 | appType: '15', 134 | }, 135 | cookieForSign(user), 136 | ).then((result) => ({ ...result, cookie: user, user, activityId, enc })); 137 | 138 | export const locationSign = ( 139 | user: User, 140 | activityId: number, 141 | longitude: number, 142 | latitude: number, 143 | address: string, 144 | ): Promise => sign( 145 | { 146 | name: encodeURIComponent(user.name), 147 | address, 148 | activeId: activityId.toString(), 149 | uid: user.id, 150 | clientip: '', 151 | latitude: latitude.toString(), 152 | longitude: longitude.toString(), 153 | fid: user.fid, 154 | appType: '15', 155 | ifTiJiao: '1', 156 | }, 157 | cookieForSign(user), 158 | ).then((result) => ({ 159 | ...result, 160 | cookie: user, 161 | user, 162 | activityId, 163 | longitude, 164 | latitude, 165 | address, 166 | })); 167 | 168 | export const photoSign = ( 169 | user: User, 170 | activityId: number, 171 | fileId: string, 172 | ): Promise => sign( 173 | { 174 | activeId: activityId.toString(), 175 | uid: user.id, 176 | clientip: '', 177 | useragent: '', 178 | latitude: '-1', 179 | longitude: '-1', 180 | appType: '15', 181 | fid: user.fid, 182 | objectId: fileId, 183 | name: encodeURIComponent(user.name), 184 | }, 185 | cookieForSign(user), 186 | ).then((result) => ({ ...result, cookie: user, user, activityId, fileId })); 187 | -------------------------------------------------------------------------------- /miniprogram/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /miniprogram/utils/types.ts: -------------------------------------------------------------------------------- 1 | export interface RawActivity { 2 | id: number; 3 | status: number; 4 | startTime: number; 5 | endTime: number | ''; 6 | nameOne: string; 7 | nameFour: string; 8 | } 9 | 10 | export interface RawActivityWithKnownOtherId extends RawActivity { 11 | otherId: '0' | '2' | '3' | '4' | '5'; 12 | } 13 | 14 | export interface RawActivityListObject { 15 | data: { activeList: RawActivity[] }; 16 | } 17 | 18 | export type SignMethod = 'qrCode' | 'location' | 'gesture' | 'code' | 'clickOrPhoto' 19 | | 'unknown'; 20 | 21 | export type SignMethodForHuman = '二维码' | '位置' | '手势' | '签到码' | '点击/拍照' 22 | | '未知'; 23 | 24 | export type SignMethodForChoice = 'qrCode' | 'location' | 'gesture' | 'code' 25 | | 'click' | 'photo' | 'unknown'; 26 | 27 | export type SignMethodForHumanChoice = '二维码' | '位置' | '手势' | '签到码' 28 | | '点击' | '拍照' | '未知'; 29 | 30 | export interface Activity { 31 | id: number 32 | name: string; 33 | signMethod: SignMethod; 34 | startTime: number; 35 | endTime: number; 36 | endTimeForHuman: string; 37 | raw: RawActivityWithKnownOtherId; 38 | } 39 | 40 | export interface Course { 41 | id: number; 42 | name: string; 43 | } 44 | 45 | export interface Class extends Course {} 46 | 47 | export interface CourseInfo { 48 | course: Course; 49 | // TODO: array? 50 | class: Class; 51 | } 52 | 53 | export interface Cookie { 54 | expire: number; 55 | id: number; 56 | uf: string; 57 | vc3: string; 58 | _d: string; 59 | fid: string; 60 | } 61 | 62 | export interface User extends Cookie { 63 | name: string; 64 | } 65 | 66 | export type LoginType = 'fanya' | 'v11'; 67 | 68 | export interface Credential { 69 | id: number; 70 | loginType: LoginType; 71 | username: string; 72 | password: string; 73 | } 74 | 75 | export interface CookieWithCourseInfo { 76 | cookie: Cookie; 77 | courseInfoArray: CourseInfo[]; 78 | } 79 | 80 | export interface Result { 81 | status: boolean; 82 | message: string; 83 | data: any; 84 | } 85 | 86 | export interface CookieResult extends Result { 87 | cookie: Cookie; 88 | } 89 | 90 | export interface GetCookieSuccessResult extends Result { 91 | status: true; 92 | cookie: Cookie; 93 | } 94 | 95 | export interface GetCookieFailedResult extends Result { 96 | status: false; 97 | cookie: {}; 98 | } 99 | 100 | export interface NameSuccessResult extends CookieResult { 101 | status: true; 102 | name: string; 103 | } 104 | 105 | export interface NameFailedResult extends CookieResult { 106 | status: false; 107 | name: ''; 108 | } 109 | 110 | export interface LoginResult extends CookieResult { 111 | user: User; 112 | } 113 | 114 | export interface CourseInfoArrayResult extends CookieResult { 115 | courseInfoArray: CourseInfo[]; 116 | } 117 | 118 | export interface ActivitiesResult extends CookieResult { 119 | courseInfo: CourseInfo; 120 | activities: Activity[]; 121 | } 122 | 123 | export interface CookieStringResult extends Result { 124 | cookieString: string; 125 | } 126 | 127 | interface ActivityIdResult extends CookieResult { 128 | activityId: number; 129 | } 130 | 131 | export interface PreSignResult extends ActivityIdResult { 132 | courseId: number; 133 | classId: number; 134 | } 135 | 136 | export interface IsPhotoSignResult extends ActivityIdResult { 137 | isPhoto: boolean; 138 | } 139 | 140 | export interface SignResult extends ActivityIdResult { 141 | user: User; 142 | } 143 | 144 | export interface QrCodeSignResult extends SignResult { 145 | enc: string; 146 | } 147 | 148 | export interface LocationSignResult extends SignResult { 149 | longitude: number; 150 | latitude: number; 151 | address: string; 152 | } 153 | 154 | export interface PhotoSignResult extends SignResult { 155 | fileId: string; 156 | } 157 | 158 | export interface CloudStorageTokenResult extends CookieResult { 159 | token: string; 160 | } 161 | 162 | export interface UploadFileResult extends CookieResult { 163 | token: string; 164 | filePath: string; 165 | fileId: string; 166 | } 167 | -------------------------------------------------------------------------------- /miniprogram/utils/util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | User, Cookie, RawActivity, RawActivityListObject, Activity, SignMethod, 3 | RawActivityWithKnownOtherId, Credential, CookieWithCourseInfo, CourseInfo, 4 | } from './types'; 5 | 6 | export const isString = (text: unknown): text is string => 7 | typeof text === 'string' || text instanceof String; 8 | 9 | const isCookie = (obj: any): obj is Cookie => 10 | typeof obj.expire === 'number' && typeof obj.id === 'number' && isString(obj.uf) 11 | && isString(obj.vc3) && isString(obj._d) && isString(obj.fid); 12 | 13 | const isCourseInfo = (obj: any): obj is CourseInfo => 14 | obj.course && typeof obj.course.id === 'number' && isString(obj.course.name) 15 | && obj.class && typeof obj.class.id === 'number' && isString(obj.class.name); 16 | 17 | const isUser = (obj: any): obj is User => isString(obj.name) && isCookie(obj); 18 | 19 | const isCredential = (obj: any): obj is Credential => typeof obj.id === 'number' 20 | && isString(obj.username) && isString(obj.password); 21 | 22 | const isRawActivity = (obj: any): obj is RawActivity => 23 | typeof obj.status === 'number' && typeof obj.startTime === 'number' 24 | && isString(obj.nameFour) 25 | && (typeof obj.endTime === 'number' || obj.endTime === '') 26 | && typeof obj.id === 'number' && isString(obj.nameOne); 27 | 28 | const isRawActivityListObject = (obj: any): obj is RawActivityListObject => { 29 | if (!( 30 | typeof obj.data === 'object' && obj.data !== null 31 | && Array.isArray(obj.data.activeList) 32 | )) { 33 | return false; 34 | } 35 | 36 | for (const active of obj.data.activeList) { 37 | if (!isRawActivity(active)) { 38 | console.log('active problem', active); 39 | return false; 40 | } 41 | } 42 | 43 | return true; 44 | } 45 | 46 | const toCookie = (obj: any): Cookie => { 47 | if (!isCookie(obj)) { 48 | throw TypeError('passed object is not a cookie'); 49 | } 50 | 51 | return obj; 52 | } 53 | 54 | export const toCache = (obj: any): CookieWithCourseInfo => { 55 | if (!Array.isArray(obj.courseInfoArray)) { 56 | throw TypeError('courseInfoArray in passed object is not a array'); 57 | } 58 | 59 | for (const courseInfo of obj.courseInfoArray) { 60 | if (!isCourseInfo(courseInfo)) { 61 | throw TypeError( 62 | 'courseInfo in courseInfoArray of passed object is not a array', 63 | ); 64 | } 65 | } 66 | 67 | return { ...obj, cookie: toCookie(obj.cookie) }; 68 | } 69 | 70 | export const toUser = (obj: any): User => { 71 | if (!isUser(obj)) { 72 | throw TypeError('passed object is not a User'); 73 | } 74 | 75 | return obj; 76 | } 77 | 78 | export const toCredential = (obj: any): Credential => { 79 | if (!isCredential(obj)) { 80 | throw TypeError('passed object is not a Credential'); 81 | } 82 | 83 | return obj; 84 | } 85 | 86 | export const toActiveListObject = (obj: any): RawActivityListObject => { 87 | if (!isRawActivityListObject(obj)) { 88 | throw TypeError('passed object is not a RawActivityListObject'); 89 | } 90 | 91 | return obj; 92 | } 93 | 94 | const isRawActivityWithKnownOtherId = (rawActivity: any): 95 | rawActivity is RawActivityWithKnownOtherId => isString(rawActivity.otherId) 96 | && ['0', '2', '3', '4', '5'].includes(rawActivity.otherId) 97 | && isRawActivity(rawActivity); 98 | 99 | export const toRawActivityWithKnownOtherId = (rawActivity: any): 100 | RawActivityWithKnownOtherId => { 101 | if (!isRawActivityWithKnownOtherId(rawActivity)) { 102 | throw TypeError('passed rawActivity have no otherId'); 103 | } 104 | 105 | return rawActivity; 106 | } 107 | 108 | export const isSignMethod = (text: string): text is SignMethod => [ 109 | 'clickOrPhoto', 'qrCode', 'gesture', 'location', 'code', 110 | ].includes(text); 111 | 112 | export const toActivity = (rawActivity: RawActivityWithKnownOtherId): Activity => { 113 | let signMethod: SignMethod = 'unknown'; 114 | switch (rawActivity.otherId) { 115 | case '0': 116 | signMethod = 'clickOrPhoto'; 117 | break; 118 | case '2': 119 | signMethod = 'qrCode'; 120 | break; 121 | case '3': 122 | signMethod = 'gesture'; 123 | break; 124 | case '4': 125 | signMethod = 'location'; 126 | break; 127 | case '5': 128 | signMethod = 'code'; 129 | break; 130 | } 131 | 132 | return { 133 | id: rawActivity.id, 134 | name: rawActivity.nameOne, 135 | signMethod, 136 | startTime: rawActivity.startTime, 137 | endTime: rawActivity.endTime === '' ? -1 : rawActivity.endTime, 138 | endTimeForHuman: rawActivity.nameFour, 139 | raw: rawActivity, 140 | }; 141 | }; 142 | 143 | export const parseCookiesFromWx = (cookiesFromWx: Array): Cookie => { 144 | let cookie: Cookie = { 145 | // I belive that Cookie will expire within 4 years 146 | expire: (new Date()).getTime() + 1000 * 60 * 60 * 24 * 365 * 4, 147 | id: -1, 148 | uf: '', 149 | vc3: '', 150 | _d: '', 151 | fid: '', 152 | }; 153 | // Do not check expire 154 | const keys = Object.keys(cookie) 155 | .filter((key) => key !== 'expire' && key !== 'id') as Array; 156 | 157 | const now = (new Date()).getTime(); 158 | cookiesFromWx.forEach((c) => { 159 | const pairs = c.split('; '); 160 | const important = pairs[0]; 161 | const equalIndex = important.indexOf('='); 162 | const key = important.slice(0, equalIndex); 163 | const value = important.slice(equalIndex + 1); 164 | 165 | if (key === '_uid') { 166 | const id = Number.parseInt(value, 10); 167 | cookie = { ...cookie, id }; 168 | } 169 | cookie = { ...cookie, [key]: value }; 170 | 171 | for (const pair of pairs) { 172 | if (pair.startsWith('Expires=')) { 173 | const date = pair.slice('Expires='.length); 174 | const parsedDate = Date.parse(date); 175 | if (!Number.isNaN(parsedDate)) { 176 | if (parsedDate < cookie.expire && parsedDate > now) { 177 | cookie.expire = parsedDate; 178 | } 179 | } 180 | 181 | break; 182 | } 183 | } 184 | }); 185 | 186 | keys.forEach((key) => { 187 | const value = cookie[key]; 188 | 189 | if (isString(value)) { 190 | if (!((cookie[key] as string).length > 0)) { 191 | throw TypeError(`Failed to parse cookie ${key}: ${value} to string`); 192 | } 193 | } else if (typeof value === 'number') { 194 | if (Number.isNaN(value)) { 195 | throw TypeError(`failed to parse cookie ${key}: ${value} to number`); 196 | } 197 | } else { 198 | throw TypeError(`failed to parse cookie ${key}: ${value}`); 199 | } 200 | }); 201 | return cookie; 202 | } 203 | 204 | export const userToCookie = (user: User): Cookie => { 205 | const keys = Object.keys(user) as (keyof User)[]; 206 | 207 | return keys.reduce((existedCookie, key) => key !== 'name' 208 | ? { ...existedCookie, [key]: user[key] } 209 | : existedCookie, {} as any); 210 | } 211 | 212 | export const toCookieString = (cookie: { [key: string]: number | string }): string => 213 | Object.entries(cookie).map(([key, value]) => `${key}=${value}; `) 214 | .concat('SameSite=Strict; ').join(''); 215 | 216 | export const parametersToString = ( 217 | parameters: { [key: string]: number | string } 218 | ): string => Object.entries(parameters) 219 | .map(([key, value]) => `${key}=${value}`).join('&'); 220 | 221 | export const parametersToStringifyString = ( 222 | parameters: { [key: string]: any } 223 | ): string => Object.entries(parameters) 224 | .map(([key, value]) => `${key}=${JSON.stringify(value)}`).join('&'); 225 | 226 | export const cookieForSign = (cookie: Cookie): { [key: string]: any } => ({ 227 | uf: cookie.uf, _d: cookie._d, UID: cookie.id, vc3: cookie.vc3 228 | }); 229 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprogram-ts-less-quickstart", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "miniprogram-ts-less-quickstart", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "crypto-js": "^4.1.1" 13 | }, 14 | "devDependencies": { 15 | "miniprogram-api-typings": "^2.8.3-1" 16 | } 17 | }, 18 | "node_modules/crypto-js": { 19 | "version": "4.1.1", 20 | "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.1.1.tgz", 21 | "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" 22 | }, 23 | "node_modules/miniprogram-api-typings": { 24 | "version": "2.12.0", 25 | "resolved": "https://registry.npmmirror.com/miniprogram-api-typings/-/miniprogram-api-typings-2.12.0.tgz", 26 | "integrity": "sha512-ibvbqeslVFur0IAvTxLMvsbtvVcMo6gwvOnj0YZHV7aeDLu091VQRrETT2QuiG9P6aZWRcxeNGJChRKVPCp9VQ==", 27 | "dev": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miniprogram-ts-less-quickstart", 3 | "version": "1.0.0", 4 | "scripts": {}, 5 | "keywords": [], 6 | "author": "", 7 | "license": "ISC", 8 | "dependencies": { 9 | }, 10 | "devDependencies": { 11 | "miniprogram-api-typings": "^2.8.3-1" 12 | }, 13 | "main": ".eslintrc.js", 14 | "description": "" 15 | } 16 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件", 3 | "packOptions": { 4 | "ignore": [], 5 | "include": [] 6 | }, 7 | "miniprogramRoot": "miniprogram/", 8 | "compileType": "miniprogram", 9 | "projectname": "快进到退学", 10 | "setting": { 11 | "useCompilerPlugins": [ 12 | "typescript" 13 | ], 14 | "babelSetting": { 15 | "ignore": [], 16 | "disablePlugins": [], 17 | "outputPath": "" 18 | }, 19 | "condition": false, 20 | "es6": false, 21 | "enhance": false, 22 | "minified": true 23 | }, 24 | "simulatorType": "wechat", 25 | "simulatorPluginLibVersion": {}, 26 | "condition": {}, 27 | "srcMiniprogramRoot": "miniprogram/", 28 | "libVersion": "2.31.0", 29 | "editorSetting": { 30 | "tabIndent": "insertSpaces", 31 | "tabSize": 2 32 | } 33 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strictNullChecks": true, 4 | "noImplicitAny": true, 5 | "module": "CommonJS", 6 | "target": "ES2020", 7 | "allowJs": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "experimentalDecorators": true, 11 | "noImplicitThis": true, 12 | "noImplicitReturns": true, 13 | "alwaysStrict": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "strict": true, 18 | "strictPropertyInitialization": true, 19 | "lib": ["ES2020"], 20 | "typeRoots": [ 21 | "./typings" 22 | ] 23 | }, 24 | "include": [ 25 | "./**/*.ts" 26 | ], 27 | "exclude": [ 28 | "node_modules" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface IAppOption { 4 | globalData: { 5 | userInfo?: WechatMiniprogram.UserInfo, 6 | } 7 | userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback, 8 | } -------------------------------------------------------------------------------- /typings/types/index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /typings/types/wx/index.d.ts: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) 2023 Tencent, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ***************************************************************************** */ 22 | 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | 31 | declare namespace WechatMiniprogram { 32 | type IAnyObject = Record 33 | type Optional = F extends (arg: infer P) => infer R ? (arg?: P) => R : F 34 | type OptionalInterface = { [K in keyof T]: Optional } 35 | interface AsyncMethodOptionLike { 36 | success?: (...args: any[]) => void 37 | } 38 | type PromisifySuccessResult< 39 | P, 40 | T extends AsyncMethodOptionLike 41 | > = P extends { 42 | success: any 43 | } 44 | ? void 45 | : P extends { fail: any } 46 | ? void 47 | : P extends { complete: any } 48 | ? void 49 | : Promise>[0]> 50 | 51 | // TODO: Extract real definition from `lib.dom.d.ts` to replace this 52 | type IIRFilterNode = any 53 | type WaveShaperNode = any 54 | type ConstantSourceNode = any 55 | type OscillatorNode = any 56 | type GainNode = any 57 | type BiquadFilterNode = any 58 | type PeriodicWaveNode = any 59 | type BufferSourceNode = any 60 | type ChannelSplitterNode = any 61 | type ChannelMergerNode = any 62 | type DelayNode = any 63 | type DynamicsCompressorNode = any 64 | type ScriptProcessorNode = any 65 | type PannerNode = any 66 | type AnalyserNode = any 67 | type AudioListener = any 68 | type WebGLTexture = any 69 | type WebGLRenderingContext = any 70 | 71 | // TODO: fill worklet type 72 | type WorkletFunction = (...args: any) => any 73 | type AnimationObject = any 74 | type SharedValue = T 75 | type DerivedValue = T 76 | } 77 | 78 | declare let console: WechatMiniprogram.Console 79 | 80 | declare let wx: WechatMiniprogram.Wx 81 | /** 引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */ 82 | interface Require { 83 | ( 84 | /** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */ 85 | module: string, 86 | /** 用于异步获取其他分包中的模块的引用结果,详见 [分包异步化]((subpackages/async)) */ 87 | callback?: (moduleExport: any) => void, 88 | /** 异步获取分包失败时的回调,详见 [分包异步化]((subpackages/async)) */ 89 | errorCallback?: (err: any) => void 90 | ): any 91 | /** 以 Promise 形式异步引入模块。返回模块通过 `module.exports` 或 `exports` 暴露的接口。 */ 92 | async( 93 | /** 需要引入模块文件相对于当前文件的相对路径,或 npm 模块名,或 npm 模块路径。不支持绝对路径 */ 94 | module: string 95 | ): Promise 96 | } 97 | declare const require: Require 98 | /** 引入插件。返回插件通过 `main` 暴露的接口。 */ 99 | interface RequirePlugin { 100 | ( 101 | /** 需要引入的插件的 alias */ 102 | module: string, 103 | /** 用于异步获取其他分包中的插件的引用结果,详见 [分包异步化]((subpackages/async)) */ 104 | callback?: (pluginExport: any) => void 105 | ): any 106 | /** 以 Promise 形式异步引入插件。返回插件通过 `main` 暴露的接口。 */ 107 | async( 108 | /** 需要引入的插件的 alias */ 109 | module: string 110 | ): Promise 111 | } 112 | declare const requirePlugin: RequirePlugin 113 | /** 插件引入当前使用者小程序。返回使用者小程序通过 [插件配置中 `export` 暴露的接口](https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/using.html#%E5%AF%BC%E5%87%BA%E5%88%B0%E6%8F%92%E4%BB%B6)。 114 | * 115 | * 该接口只在插件中存在 116 | * 117 | * 最低基础库: `2.11.1` */ 118 | declare function requireMiniProgram(): any 119 | /** 当前模块对象 */ 120 | declare let module: { 121 | /** 模块向外暴露的对象,使用 `require` 引用该模块时可以获取 */ 122 | exports: any 123 | } 124 | /** `module.exports` 的引用 */ 125 | declare let exports: any 126 | 127 | /** [clearInterval(number intervalID)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/clearInterval.html) 128 | * 129 | * 取消由 setInterval 设置的定时器。 */ 130 | declare function clearInterval( 131 | /** 要取消的定时器的 ID */ 132 | intervalID: number 133 | ): void 134 | /** [clearTimeout(number timeoutID)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/clearTimeout.html) 135 | * 136 | * 取消由 setTimeout 设置的定时器。 */ 137 | declare function clearTimeout( 138 | /** 要取消的定时器的 ID */ 139 | timeoutID: number 140 | ): void 141 | /** [number setInterval(function callback, number delay, any rest)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/setInterval.html) 142 | * 143 | * 设定一个定时器。按照指定的周期(以毫秒计)来执行注册的回调函数 */ 144 | declare function setInterval( 145 | /** 回调函数 */ 146 | callback: (...args: any[]) => any, 147 | /** 执行回调函数之间的时间间隔,单位 ms。 */ 148 | delay?: number, 149 | /** param1, param2, ..., paramN 等附加参数,它们会作为参数传递给回调函数。 */ 150 | rest?: any 151 | ): number 152 | /** [number setTimeout(function callback, number delay, any rest)](https://developers.weixin.qq.com/miniprogram/dev/api/base/timer/setTimeout.html) 153 | * 154 | * 设定一个定时器。在定时到期以后执行注册的回调函数 */ 155 | declare function setTimeout( 156 | /** 回调函数 */ 157 | callback: (...args: any[]) => any, 158 | /** 延迟的时间,函数的调用会在该延迟之后发生,单位 ms。 */ 159 | delay?: number, 160 | /** param1, param2, ..., paramN 等附加参数,它们会作为参数传递给回调函数。 */ 161 | rest?: any 162 | ): number 163 | -------------------------------------------------------------------------------- /typings/types/wx/lib.wx.app.d.ts: -------------------------------------------------------------------------------- 1 | /*! ***************************************************************************** 2 | Copyright (c) 2023 Tencent, Inc. All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | ***************************************************************************** */ 22 | 23 | declare namespace WechatMiniprogram.App { 24 | interface ReferrerInfo { 25 | /** 来源小程序或公众号或App的 appId 26 | * 27 | * 以下场景支持返回 referrerInfo.appId: 28 | * - 1020(公众号 profile 页相关小程序列表): appId 29 | * - 1035(公众号自定义菜单):来源公众号 appId 30 | * - 1036(App 分享消息卡片):来源应用 appId 31 | * - 1037(小程序打开小程序):来源小程序 appId 32 | * - 1038(从另一个小程序返回):来源小程序 appId 33 | * - 1043(公众号模板消息):来源公众号 appId 34 | */ 35 | appId: string 36 | /** 来源小程序传过来的数据,scene=1037或1038时支持 */ 37 | extraData?: any 38 | } 39 | 40 | type SceneValues = 41 | | 1000 42 | | 1001 43 | | 1005 44 | | 1006 45 | | 1007 46 | | 1008 47 | | 1010 48 | | 1011 49 | | 1012 50 | | 1013 51 | | 1014 52 | | 1017 53 | | 1019 54 | | 1020 55 | | 1022 56 | | 1023 57 | | 1024 58 | | 1025 59 | | 1026 60 | | 1027 61 | | 1028 62 | | 1029 63 | | 1030 64 | | 1031 65 | | 1032 66 | | 1034 67 | | 1035 68 | | 1036 69 | | 1037 70 | | 1038 71 | | 1039 72 | | 1042 73 | | 1043 74 | | 1044 75 | | 1045 76 | | 1046 77 | | 1047 78 | | 1048 79 | | 1049 80 | | 1052 81 | | 1053 82 | | 1054 83 | | 1056 84 | | 1057 85 | | 1058 86 | | 1059 87 | | 1060 88 | | 1064 89 | | 1065 90 | | 1067 91 | | 1068 92 | | 1069 93 | | 1071 94 | | 1072 95 | | 1073 96 | | 1074 97 | | 1077 98 | | 1078 99 | | 1079 100 | | 1081 101 | | 1082 102 | | 1084 103 | | 1088 104 | | 1089 105 | | 1090 106 | | 1091 107 | | 1092 108 | | 1095 109 | | 1096 110 | | 1097 111 | | 1099 112 | | 1100 113 | | 1101 114 | | 1102 115 | | 1103 116 | | 1104 117 | | 1106 118 | | 1107 119 | | 1113 120 | | 1114 121 | | 1119 122 | | 1120 123 | | 1121 124 | | 1124 125 | | 1125 126 | | 1126 127 | | 1129 128 | | 1131 129 | | 1133 130 | | 1135 131 | | 1144 132 | | 1145 133 | | 1146 134 | | 1148 135 | | 1150 136 | | 1151 137 | | 1152 138 | | 1153 139 | | 1154 140 | | 1155 141 | | 1157 142 | | 1158 143 | | 1160 144 | | 1167 145 | | 1168 146 | | 1169 147 | | 1171 148 | | 1173 149 | | 1175 150 | | 1176 151 | | 1177 152 | | 1178 153 | | 1179 154 | | 1181 155 | | 1183 156 | | 1184 157 | | 1185 158 | | 1186 159 | | 1187 160 | | 1189 161 | | 1191 162 | | 1192 163 | | 1193 164 | | 1194 165 | | 1195 166 | | 1196 167 | | 1197 168 | | 1198 169 | | 1200 170 | | 1201 171 | | 1202 172 | | 1203 173 | | 1206 174 | | 1207 175 | | 1208 176 | | 1212 177 | | 1215 178 | | 1216 179 | | 1223 180 | | 1228 181 | | 1231 182 | 183 | interface LaunchShowOption { 184 | /** 打开小程序的路径 */ 185 | path: string 186 | /** 打开小程序的query */ 187 | query: IAnyObject 188 | /** 打开小程序的场景值 189 | * - 1000:其他 190 | * - 1001:发现栏小程序主入口,「最近使用」列表(基础库 2.2.4 版本起包含「我的小程序」列表) 191 | * - 1005:微信首页顶部搜索框的搜索结果页 192 | * - 1006:发现栏小程序主入口搜索框的搜索结果页 193 | * - 1007:单人聊天会话中的小程序消息卡片 194 | * - 1008:群聊会话中的小程序消息卡片 195 | * - 1010:收藏夹 196 | * - 1011:扫描二维码 197 | * - 1012:长按图片识别二维码 198 | * - 1013:扫描手机相册中选取的二维码 199 | * - 1014:小程序订阅消息(与 1107 相同) 200 | * - 1017:前往小程序体验版的入口页 201 | * - 1019:微信钱包(微信客户端 7.0.0 版本改为支付入口) 202 | * - 1020:公众号 profile 页相关小程序列表(已废弃) 203 | * - 1022:聊天顶部置顶小程序入口(微信客户端 6.6.1 版本起废弃) 204 | * - 1023:安卓系统桌面图标 205 | * - 1024:小程序 profile 页 206 | * - 1025:扫描一维码 207 | * - 1026:发现栏小程序主入口,「附近的小程序」列表 208 | * - 1027:微信首页顶部搜索框搜索结果页「使用过的小程序」列表 209 | * - 1028:我的卡包 210 | * - 1029:小程序中的卡券详情页 211 | * - 1030:自动化测试下打开小程序 212 | * - 1031:长按图片识别一维码 213 | * - 1032:扫描手机相册中选取的一维码 214 | * - 1034:微信支付完成页 215 | * - 1035:公众号自定义菜单 216 | * - 1036:App 分享消息卡片 217 | * - 1037:小程序打开小程序 218 | * - 1038:从另一个小程序返回 219 | * - 1039:摇电视 220 | * - 1042:添加好友搜索框的搜索结果页 221 | * - 1043:公众号模板消息 222 | * - 1044:带 shareTicket 的小程序消息卡片 [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html) 223 | * - 1045:朋友圈广告 224 | * - 1046:朋友圈广告详情页 225 | * - 1047:扫描小程序码 226 | * - 1048:长按图片识别小程序码 227 | * - 1049:扫描手机相册中选取的小程序码 228 | * - 1052:卡券的适用门店列表 229 | * - 1053:搜一搜的结果页 230 | * - 1054:顶部搜索框小程序快捷入口(微信客户端版本 6.7.4 起废弃) 231 | * - 1056:聊天顶部音乐播放器右上角菜单 232 | * - 1057:钱包中的银行卡详情页 233 | * - 1058:公众号文章 234 | * - 1059:体验版小程序绑定邀请页 235 | * - 1060:微信支付完成页(与 1034 相同) 236 | * - 1064:微信首页连 Wi-Fi 状态栏 237 | * - 1065:URL scheme [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html) 238 | * - 1067:公众号文章广告 239 | * - 1068:附近小程序列表广告(已废弃) 240 | * - 1069:移动应用通过 openSDK 进入微信,打开小程序 241 | * - 1071:钱包中的银行卡列表页 242 | * - 1072:二维码收款页面 243 | * - 1073:客服消息列表下发的小程序消息卡片 244 | * - 1074:公众号会话下发的小程序消息卡片 245 | * - 1077:摇周边 246 | * - 1078:微信连 Wi-Fi 成功提示页 247 | * - 1079:微信游戏中心 248 | * - 1081:客服消息下发的文字链 249 | * - 1082:公众号会话下发的文字链 250 | * - 1084:朋友圈广告原生页 251 | * - 1088:会话中查看系统消息,打开小程序 252 | * - 1089:微信聊天主界面下拉,「最近使用」栏(基础库 2.2.4 版本起包含「我的小程序」栏) 253 | * - 1090:长按小程序右上角菜单唤出最近使用历史 254 | * - 1091:公众号文章商品卡片 255 | * - 1092:城市服务入口 256 | * - 1095:小程序广告组件 257 | * - 1096:聊天记录,打开小程序 258 | * - 1097:微信支付签约原生页,打开小程序 259 | * - 1099:页面内嵌插件 260 | * - 1100:红包封面详情页打开小程序 261 | * - 1101:远程调试热更新(开发者工具中,预览 -> 自动预览 -> 编译并预览) 262 | * - 1102:公众号 profile 页服务预览 263 | * - 1103:发现栏小程序主入口,「我的小程序」列表(基础库 2.2.4 版本起废弃) 264 | * - 1104:微信聊天主界面下拉,「我的小程序」栏(基础库 2.2.4 版本起废弃) 265 | * - 1106:聊天主界面下拉,从顶部搜索结果页,打开小程序 266 | * - 1107:订阅消息,打开小程序 267 | * - 1113:安卓手机负一屏,打开小程序(三星) 268 | * - 1114:安卓手机侧边栏,打开小程序(三星) 269 | * - 1119:【企业微信】工作台内打开小程序 270 | * - 1120:【企业微信】个人资料页内打开小程序 271 | * - 1121:【企业微信】聊天加号附件框内打开小程序 272 | * - 1124:扫“一物一码”打开小程序 273 | * - 1125:长按图片识别“一物一码” 274 | * - 1126:扫描手机相册中选取的“一物一码” 275 | * - 1129:微信爬虫访问 [详情](https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/sitemap.html) 276 | * - 1131:浮窗(8.0 版本起仅包含被动浮窗) 277 | * - 1133:硬件设备打开小程序 [详情](https://developers.weixin.qq.com/doc/oplatform/Miniprogram_Frame/) 278 | * - 1135:小程序 profile 页相关小程序列表,打开小程序 279 | * - 1144:公众号文章 - 视频贴片 280 | * - 1145:发现栏 - 发现小程序 281 | * - 1146:地理位置信息打开出行类小程序 282 | * - 1148:卡包-交通卡,打开小程序 283 | * - 1150:扫一扫商品条码结果页打开小程序 284 | * - 1151:发现栏 - 我的订单 285 | * - 1152:订阅号视频打开小程序 286 | * - 1153:“识物”结果页打开小程序 287 | * - 1154:朋友圈内打开“单页模式” 288 | * - 1155:“单页模式”打开小程序 289 | * - 1157:服务号会话页打开小程序 290 | * - 1158:群工具打开小程序 291 | * - 1160:群待办 292 | * - 1167:H5 通过开放标签打开小程序 [详情](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html) 293 | * - 1168:移动应用直接运行小程序 294 | * - 1169:发现栏小程序主入口,各个生活服务入口(例如快递服务、出行服务等) 295 | * - 1171:微信运动记录(仅安卓) 296 | * - 1173:聊天素材用小程序打开 [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/material/support_material.html) 297 | * - 1175:视频号主页商店入口 298 | * - 1176:视频号直播间主播打开小程序 299 | * - 1177:视频号直播商品 300 | * - 1178:在电脑打开手机上打开的小程序 301 | * - 1179:#话题页打开小程序 302 | * - 1181:网站应用打开 PC 小程序 303 | * - 1183:PC 微信 - 小程序面板 - 发现小程序 - 搜索 304 | * - 1184:视频号链接打开小程序 305 | * - 1185:群公告 306 | * - 1186:收藏 - 笔记 307 | * - 1187:浮窗(8.0 版本起) 308 | * - 1189:表情雨广告 309 | * - 1191:视频号活动 310 | * - 1192:企业微信联系人 profile 页 311 | * - 1193:视频号主页服务菜单打开小程序 312 | * - 1194:URL Link [详情](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-link.html) 313 | * - 1195:视频号主页商品 tab 314 | * - 1196:个人状态打开小程序 315 | * - 1197:视频号主播从直播间返回小游戏 316 | * - 1198:视频号开播界面打开小游戏 317 | * - 1200:视频号广告打开小程序 318 | * - 1201:视频号广告详情页打开小程序 319 | * - 1202:企微客服号会话打开小程序卡片 320 | * - 1203:微信小程序压测工具的请求 321 | * - 1206:视频号小游戏直播间打开小游戏 322 | * - 1207:企微客服号会话打开小程序文字链 323 | * - 1208:聊天打开商品卡片 324 | * - 1212:青少年模式申请页打开小程序 325 | * - 1215:广告预约打开小程序 326 | * - 1216:视频号订单中心打开小程序 327 | * - 1223:安卓桌面 Widget 打开小程序 328 | * - 1228:视频号原生广告组件打开小程序 329 | * - 1231:动态消息提醒入口打开小程序 330 | */ 331 | scene: SceneValues 332 | /** shareTicket,详见 [获取更多转发信息]((转发#获取更多转发信息)) */ 333 | shareTicket: string 334 | /** 当场景为由从另一个小程序或公众号或App打开时,返回此字段 */ 335 | referrerInfo?: ReferrerInfo 336 | } 337 | 338 | interface PageNotFoundOption { 339 | /** 不存在页面的路径 */ 340 | path: string 341 | /** 打开不存在页面的 query */ 342 | query: IAnyObject 343 | /** 是否本次启动的首个页面(例如从分享等入口进来,首个页面是开发者配置的分享页面) */ 344 | isEntryPage: boolean 345 | } 346 | 347 | interface Option { 348 | /** 生命周期回调—监听小程序初始化 349 | * 350 | * 小程序初始化完成时触发,全局只触发一次。 351 | */ 352 | onLaunch(options: LaunchShowOption): void 353 | /** 生命周期回调—监听小程序显示 354 | * 355 | * 小程序启动,或从后台进入前台显示时 356 | */ 357 | onShow(options: LaunchShowOption): void 358 | /** 生命周期回调—监听小程序隐藏 359 | * 360 | * 小程序从前台进入后台时 361 | */ 362 | onHide(): void 363 | /** 错误监听函数 364 | * 365 | * 小程序发生脚本错误,或者 api 366 | */ 367 | onError(/** 错误信息,包含堆栈 */ error: string): void 368 | /** 页面不存在监听函数 369 | * 370 | * 小程序要打开的页面不存在时触发,会带上页面信息回调该函数 371 | * 372 | * **注意:** 373 | * 1. 如果开发者没有添加 `onPageNotFound` 监听,当跳转页面不存在时,将推入微信客户端原生的页面不存在提示页面。 374 | * 2. 如果 `onPageNotFound` 回调中又重定向到另一个不存在的页面,将推入微信客户端原生的页面不存在提示页面,并且不再回调 `onPageNotFound`。 375 | * 376 | * 最低基础库: 1.9.90 377 | */ 378 | onPageNotFound(options: PageNotFoundOption): void 379 | /** 380 | * 小程序有未处理的 Promise 拒绝时触发。也可以使用 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 绑定监听。注意事项请参考 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html)。 381 | * **参数**:与 [wx.onUnhandledRejection](https://developers.weixin.qq.com/miniprogram/dev/api/base/app/app-event/wx.onUnhandledRejection.html) 一致 382 | */ 383 | onUnhandledRejection: OnUnhandledRejectionCallback 384 | /** 385 | * 系统切换主题时触发。也可以使用 wx.onThemeChange 绑定监听。 386 | * 387 | * 最低基础库: 2.11.0 388 | */ 389 | onThemeChange: OnThemeChangeCallback 390 | } 391 | 392 | type Instance = Option & T 393 | type Options = Partial