This commit is contained in:
2023-11-07 17:15:33 +08:00
commit fe37e2e1df
418 changed files with 53012 additions and 0 deletions
@@ -0,0 +1,6 @@
## 0.0.3(2022-11-11)
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
## 0.0.2(2021-04-16)
- 修改插件package信息
## 0.0.1(2021-03-15)
- 初始化项目
@@ -0,0 +1,81 @@
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.3",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
"配置中心"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"directories": {
"example": "../../../scripts/dist"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}
+93
View File
@@ -0,0 +1,93 @@
# 为什么使用uni-config-center
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ ├─index.js
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b // plugin-b对应的目录
├─index.js
└─config.json // plugin-b对应的配置文件
```
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
uni-config-center就是用了统一管理这些配置文件的,使用uni-config-center后的目录结构如下
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ └─index.js
├─plugin-b // plugin-b对应的目录
│ └─index.js
└─uni-config-center
├─index.js // config-center入口文件
├─plugin-a
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b
└─config.json // plugin-b对应的配置文件
```
使用uni-config-center后的优势
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
- 支持对config.json设置schema,插件使用者在HBuilderX内编写config.json文件时会有更好的提示(后续HBuilderX会提供支持)
# 用法
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖,请参考:[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
```js
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id', // 插件id
defaultConfig: { // 默认配置
tokenExpiresIn: 7200,
tokenExpiresThreshold: 600,
},
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
// defaudltConfig 默认配置
// userConfig 用户配置
return Object.assign(defaultConfig, userConfig)
}
})
// 以如下配置为例
// {
// "tokenExpiresIn": 7200,
// "passwordErrorLimit": 6,
// "bindTokenToDevice": false,
// "passwordErrorRetryTime": 3600,
// "app-plus": {
// "tokenExpiresIn": 2592000
// },
// "service": {
// "sms": {
// "codeExpiresIn": 300
// }
// }
// }
// 获取配置
uniIdConfig.config() // 获取全部配置,注意:uni-config-center内不存在对应插件目录时会返回空对象
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置,返回:7200
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置,返回:300
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置,如果不存在则取传入的默认值,返回:600
// 获取文件绝对路径
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
// 引用文件(require)
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined,文件内有其他错误导致require失败时会抛出错误。
// 判断是否包含某文件
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件,true: 文件存在,false: 文件不存在
```
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
{
"name": "uni-config-center",
"version": "0.0.3",
"description": "配置中心",
"main": "index.js",
"keywords": [],
"author": "DCloud",
"license": "Apache-2.0"
}
@@ -0,0 +1,54 @@
{
"passwordSecret": "passwordSecret-demo",
"tokenSecret": "tokenSecret-demo",
"tokenExpiresIn": 604800,
"tokenExpiresThreshold": 259200,
"tokenMaxLimit": 10,
"passwordErrorLimit": 6,
"bindTokenToDevice": false,
"passwordErrorRetryTime": 3600,
"autoSetInviteCode": true,
"forceInviteCode": false,
"preferedAppPlatform": "app-plus",
"preferedWebPlatform": "h5",
"removeDcloudAppid": false,
"app-plus": {
"tokenExpiresIn": 604800,
"oauth": {
"weixin": {
"appid": "",
"appsecret": ""
}
}
},
"mp-weixin": {
"oauth": {
"weixin": {
"appid": "",
"appsecret": ""
}
}
},
"mp-alipay": {
"oauth": {
"alipay": {
"appid": "",
"privateKey": ""
}
}
},
"service": {
"sms": {
"name": "重要",
"codeExpiresIn": 180,
"smsKey": "你的smsKey",
"smsSecret": "你的smsSecret",
"templateId": "你的短信模板ID"
},
"univerify": {
"appid": "your appid",
"apiKey": "your apiKey",
"apiSecret": "your apiSecret"
}
}
}
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
证书内容
-----END CERTIFICATE-----
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
证书内容
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
证书内容
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
证书内容
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
证书内容
-----END CERTIFICATE-----
@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
证书内容
-----END CERTIFICATE-----
@@ -0,0 +1,5 @@
### 文件名:
#### 1、`alipayCertPublicKey_RSA2.crt`
#### 2、`alipayRootCert.crt`
#### 3、`appCertPublicKey.crt`
![证书位置](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-cf0c5e69-620c-4f3c-84ab-f4619262939f/61c1582a-2ca7-43d3-92eb-b82136cc583f.png)
@@ -0,0 +1,128 @@
const fs = require('fs');
const path = require('path');
module.exports = {
/**
* 统一支付回调地址,格式为 "服务空间SpaceID":"URL化完整地址"
* 这里的本地开发并不是指 http://localhost:8080/ 的地址,而是另外一个服务空间的ULR化地址(如果你本地开发和线上环境共用同一个服务空间则只需要填线上环境的即可)
* 回调的云函数地址,建议填 /http/vk-pay,因为vk-pay云函数已经写好了回调处理的逻辑,否则你需要自己写全部的回调逻辑。
* 其中vk-pay是可以改名的,只需要修改 uniCloud/cloudfunctions/vk-pay/package.json 文件中的 "path": "/http/vk-pay", 把 /http/vk-pay 改成 /http/xxxx 即可(需要重新上传云函数vk-pay)。
*/
"notifyUrl": {
// 本地开发环境,如果你本地开发和线上环境共用同一个服务空间则只需要填线上环境的即可
"a4f90532-ac60-4a43-81c1-a5c4s3fbs66": "https://a4f90532-ac60-4a43-81c1-a5c4s3fbs66.bspapp.com/http/vk-pay",
// 线上正式环境
"b5a9s861-ba20-dca5-56a2-bd3fbsasxz6": "https://b5a9s861-ba20-dca5-56a2-bd3fbsasxz6.bspapp.com/http/vk-pay"
},
/**
* 微信
* 公共参数说明
* appId 微信后台的appId
* secret 微信后台的secret
* mchId 微信支付的商户id
* key 微信支付的api密钥(V2版本)
* pfx 微信支付的p12证书(退款需要)
*/
"wxpay": {
// 微信 - 小程序支付(微信支付申请JSAPI支付)
"mp-weixin": {
"appId": "",
"secret": "",
"mchId": "",
"key": "",
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12')
},
// 微信 - APP支付(微信支付申请APP支付)
"app-plus": {
"appId": "",
"secret": "",
"mchId": "",
"key": "",
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12')
},
// 微信 - H5电脑网站二维码支付(微信支付申请Native支付)
"h5": {
"appId": "",
"secret": "",
"mchId": "",
"key": "",
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12')
},
// 微信 - 公众号支付(微信支付申请JSAPI支付)
"h5-weixin": {
"appId": "",
"secret": "",
"mchId": "",
"key": "",
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12')
},
// 微信 - 手机外部浏览器H5支付(微信支付申请H5支付)
"mweb": {
"appId": "",
"secret": "",
"mchId": "",
"key": "",
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12'),
// 场景信息,必填
"sceneInfo": {
"h5_info": {
"type": "Wap", // 此值固定Wap
"wap_url": "https://www.xxxxxx.com", // 你的H5首页地址,必须和你发起支付的页面的域名一致。
"wap_name": "网站名称", // 你的H5网站名称
}
}
}
},
/**
* 支付宝(证书记得选java版本)
* 公共参数说明
* mchId 支付宝商户id 2088开头的那个(此参数可以去除)
* appId 支付宝开放平台的应用appId
* secret 支付宝开放平台的应用secret
* privateKey 应用私钥
* alipayPublicCertPath 支付宝公钥证书路径地址 与之对应的 alipayPublicCertContent 为支付宝公钥证书内容(值可以是字符串也可以是Buffer)
* alipayRootCertPath 支付宝根证书路径地址 与之对应的 alipayRootCertContent 为支付宝根证书内容(值可以是字符串也可以是Buffer)
* appCertPath 应用证书路径地址 与之对应的 appCertPathContent 为应用证书内容(值可以是字符串也可以是Buffer)
* sandbox 是否沙箱模式 true 沙箱模式 false 正常模式
*/
"alipay": {
// 支付宝 - 小程序支付配置
"mp-alipay": {
"mchId": "",
"appId": "",
"privateKey": "",
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'),
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'),
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'),
"sandbox": false
},
// 支付宝 - APP支付配置
"app-plus": {
"mchId": "",
"appId": "",
"privateKey": "",
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'),
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'),
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'),
"sandbox": false
},
// 支付宝 - H5支付配置(包含:网站二维码、手机H5,需申请支付宝当面付接口权限)
"h5": {
"mchId": "",
"appId": "",
"privateKey": "",
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'),
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'),
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'),
"sandbox": false
},
// 支付宝 - 转账到支付宝等资金转出接口,其中 appCertSn 和 alipayRootCertSn 通过工具获取
"transfer": {
"mchId": "",
"appId": "",
"privateKey": "",
"appCertSn": "", // 应用证书的序列号
"alipayRootCertSn": "", // 支付宝根证书的序列号
"sandbox": false
}
}
}
@@ -0,0 +1,3 @@
### 文件名:
#### 1、`wxpay.p12`
![证书位置](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-cf0c5e69-620c-4f3c-84ab-f4619262939f/61c1582a-2ca7-43d3-92eb-b82136cc583f.png)
@@ -0,0 +1,140 @@
var uniIdConfig;
var uniPayConfig;
try {
uniIdConfig = require('../uni-id/config.json');
} catch(err) {
console.error("配置文件:uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json 编译错误,请检查!↓↓↓请查看下方的错误提示↓↓↓", err.name, err.message, err);
throw new Error("配置文件:uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json 编译错误,请检查!↑↑↑请查看上方的错误提示↑↑↑");
}
try {
uniPayConfig = require('../uni-pay/config.js');
} catch(err) {
console.error("配置文件:uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js 编译错误,请检查!↓↓↓请查看下方的错误提示↓↓↓", err.name, err.message, err);
throw new Error("配置文件:uniCloud/cloudfunctions/common/uni-config-center/uni-pay/config.js 编译错误,请检查!↑↑↑请查看上方的错误提示↑↑↑");
}
module.exports = {
"uni":uniIdConfig,
"uni-pay": uniPayConfig,
"vk":{
"system":{
// 若serviceShutdow:true,则所有云函数无法访问(适用于需要临时关闭后端服务的情况,如迁移数据)
// 注意:本地调试时,需要重新启动本地服务才能生效。
"serviceShutdown":false,
"serviceShutdownDescription":"系统维护中,预计2小时恢复!",
// 云函数默认时区(中国为8)
"targetTimezone": 8
},
// 加密的密钥
"crypto": {
"aes": "dad4c0cb88c7d005bc5e844b8e79a9f0dad4c0cb88c7d005bc5e844b8e79a9f0", // 对称加密的密钥,建议长度在64位-128位(此处建议修改成自己的密钥)
},
// 当 context 内的下面值为空时,赋予默认的值(主要用于解决云函数 URL 后的默认 APPID 问题。
"context": {
"APPID": "", // 默认 dcloud_appid
"PLATFORM": "h5", // 可选项 h5、mp-weixin、app-plus、mp-alipay
"LOCALE": "zh-Hans", // 默认语言
"CLIENTIP":"127.0.0.1", // 默认客户端IP(主要用于解决本地调试无ip的问题)
},
"service": {
// 邮箱发送服务
"email": {
"codeExpiresIn": 180, // 邮件验证码有效期(单位秒)
// qq邮箱参数配置
"qq": {
"host": "smtp.qq.com",
"port": 465,
"secure": true,
"auth": {
"user": "你的邮箱@qq.com",
"pass": "邮箱授权码"
}
},
// 163邮箱配置
"163":{
"host": "smtp.163.com",
"port": 465,
"secure": true,
"auth": {
"user": "",
"pass": ""
}
}
},
// 日志服务
"log":{
// 用户登录日志
"login":{
"status":true // 是否开启用户登录日志
}
},
// 短信服务
"sms": {
// 阿里云短信服务
"aliyun": {
"enable": false, // 是否使用阿里云短信代替unicloud短信发送短信验证码
"accessKeyId": "", // 短信密钥key
"accessKeySecret": "", // 短信密钥secret
"signName": "", // 默认签名
"templateCode": {
"verifyCode": "" // 验证码短信模板 - 配合uni-id需要
}
}
},
// 开放平台api
"openapi":{
// 百度开放平台 (主要用于身份证识别,营业执照识别等API)
// API Key申请地址:https://cloud.baidu.com/doc/OCR/s/rk3h7xzck 点击右上角注册
"baidu":{
"appid" : "", // 对应的API Key
"appsecret" : "" // 对应的Secret Key
}
}
},
"db":{
"unicloud":{
"maxLimit" : 1000, // 最大limit限制(目前腾讯云最大1000,阿里云最大1000)
"cancelAddTime" : false,// 取消vk.baseDao.add 时自动生成_add_time和_add_time_str
"getTableData":{
"sortArr":[{"name":"_add_time","type":"desc"}], // vk.baseDao.getTableData 默认排序规则
}
}
},
// 其他小程序的密钥 当需要多个小程序绑定同一服务空间,并调用小程序服务端API时需要填写 暂只支持微信小程序
"oauth": {
// 微信小程序
"weixin": {
// 密钥列表
"list": [
{ "appid": "", "appsecret": "" },
{ "appid": "", "appsecret": "" }
]
},
// 支付宝
"alipay": {
// 密钥列表
"list": [
{ "appid": "", "privateKey": "" },
{ "appid": "", "privateKey": "" }
]
},
// qq
"qq": {
// 密钥列表
"list": [
{ "appid": "", "appsecret": "" },
{ "appid": "", "appsecret": "" }
]
},
// 抖音
"toutiao": {
// 密钥列表
"list": [
{ "appid": "", "appsecret": "" },
{ "appid": "", "appsecret": "" }
]
}
}
}
};
@@ -0,0 +1,73 @@
## 重要说明
#### 框架目前的配置(不含`uni-id`)在 `cloudfunctions/common/uni-config-center/vk-unicloud/index.js` 中配置
#### `uni-id` 的配置是在 `cloudfunctions/common/uni-config-center/uni-id/config.json` 中配置
#### 每次修改完 配置 中的内容后如果不生效,则尝试以下操作。
```
1. 上传 uni-config-center 公共模块
2. 重新上传 router 云函数
如果是连接本地云函数,则需要重启本地服务才会生效。(hbx的原因)
```
### 由于`uni-id`配置无法打注释,故下方为`uni-id`配置的介绍
```js
{
"passwordSecret": "passwordSecret-demo",// 加密密码所用的密钥,修改会导致所用户之前的密码失效。如一定要修改,请查看https://uniapp.dcloud.io/uniCloud/uni-id?id=modifysecret
"tokenSecret": "tokenSecret-demo", // 生成token所用的密钥,修改会导致所有用户之前的token失效。
"tokenExpiresIn": 604800, // 全平台token过期时间,未指定过期时间的平台会使用此值,604800代表7天
"tokenExpiresThreshold": 3600, // 新增于uni-id 1.1.7版本,checkToken时如果token有效期小于此值则自动获取新token,如果不配置此参数则不开启自动获取新token功能
"passwordErrorLimit": 6, // 密码错误最大重试次数
"bindTokenToDevice": false, // 是否将token和设备绑定,设置为true会进行ua校验
"passwordErrorRetryTime": 3600, // 密码错误重试次数超限之后的冻结时间
"autoSetInviteCode": true, // 是否在用户注册时自动设置邀请码
"forceInviteCode": false, // 是否强制用户注册时必填邀请码,默认为false(需要注意的是目前只有短信验证码注册才可以填写邀请码)
"app-plus": {
"tokenExpiresIn": 604800, // app端 token过期时间
"oauth" : {
// App微信登录所用到的appid、appsecret需要在微信开放平台获取,注意:不是公众平台而是开放平台
"weixin" : {
"appid" : "",
"appsecret" : ""
}
}
},
"mp-weixin": {
"oauth" : {
// 微信小程序登录所用的appid、appsecret需要在对应的小程序管理控制台获取
"weixin" : {
"appid" : "",
"appsecret" : ""
}
}
},
"mp-alipay": {
"oauth" : {
// 支付宝小程序登录用到的appid、privateKey请参考支付宝小程序的文档进行设置或者获取,https://opendocs.alipay.com/open/291/105971#LDsXr
"alipay" : {
"appid" : "",
"privateKey" : ""
}
}
},
"service": {
// unicloud短信
"sms": {
"name": "重要", // 应用名称,对应短信模版的name
"codeExpiresIn": 180, // 验证码过期时间,单位为秒,注意一定要是60的整数倍
"smsKey": "你的smsKey", // 短信密钥key,开通短信服务处可以看到 https://dev.dcloud.net.cn/uniSms
"smsSecret": "你的smsSecret", // 短信密钥secret,开通短信服务处可以看到 https://dev.dcloud.net.cn/uniSms
"templateId":"你的短信模板ID" // 短信模板ID
},
// 一键登录
"univerify": {
"appid":"your appid", // uniapp的appid
"apiKey": "your apiKey", // 一键登录的apiKey
"apiSecret": "your apiSecret" // 一键登录的apiSecret
}
}
}
```
+111
View File
@@ -0,0 +1,111 @@
## 3.3.28(2022-07-27)
- 修复 app端微信登录返回的accessToken过期时间(expired)不正确的Bug
## 3.3.27(2022-07-27)
- 短信发送失败、微信登录失败等场景下输出原始错误方便排查错误
## 3.3.26(2022-07-08)
- 兼容配置放在uni-id下的逻辑,但是仍推荐使用uni-config-center
## 3.3.25(2022-06-30)
- 修复config文件不合法时未抛出具体错误的Bug
## 3.3.24(2022-06-28)
- 修复3.3.12引出的使用多应用配置时报错的Bug
## 3.3.23(2022-06-13)
- 修复上版本引出的部分依赖未找到的Bug
## 3.3.22(2022-06-13)
- 新增 preferedWebPlatform 配置用于解决HBuilderX 3.4.9版本起web端platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-web-platform)
## 3.3.21(2022-05-24)
- 修复createInstance传入clientInfo无效的Bug
## 3.3.20(2022-05-19)
- 调整以下错误码(账号已注册[uni-id-account-exists]、账号不存在[uni-id-account-not-exists]、账号已绑定[uni-id-account-bound])
## 3.3.19(2022-05-19)
- 修复 addUser 部分情况下会创建出重复账号的Bug
## 3.3.18(2022-05-12)
- 调整绑定、解绑邮箱手机号接口,只要传递code参数就进行验证码校验即使传递的值为undefined
## 3.3.17(2022-05-09)
- register_env内增加os_name字段用于区分注册时的客户端系统类型
## 3.3.16(2022-05-09)
- 修复 addUser接口添加的用户无法使用密码登录的Bug [详情](https://ask.dcloud.net.cn/question/144670)
## 3.3.15(2022-05-08)
- 修复config文件语法错误时报`this.t is not a function`的Bug 感谢@寒暄
## 3.3.14(2022-05-08)
- 新增 getWeixinUserInfo接口 用于获取app平台微信登录用户的用户信息 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#get-weixin-user-info)
- 新增 addUser接口 用于手动添加用户 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#add-user)
- 新增 resetPwdBySms接口 用于使用短信验证码重置密码 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#reset-pwd-by-sms)
- 新增 refreshToken接口 用于主动刷新用户token [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#refresh-token)
- 调整 用户注册时记录用户注册环境到 register_env 字段 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#user-table)
- 调整 用户注册时将注册 ip 移至 register_env 内
## 3.3.13(2022-03-04)
- createInstance方法支持传递clientInfo [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id.html#create-instance)
- 修复`this.t is not a function`报错
## 3.3.12(2022-01-15)
- 新增 preferedAppPlatform 配置用于解决uni-app vue2版本vue3版本获取platform不一致的问题 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=prefered-app-platform)
- 修复 checkToken 未返回自定义token内容的Bug
## 3.3.11(2022-01-11)
- 修复用户名密码登录时多个应用出现重复用户名登录报错的Bug
## 3.3.10(2022-01-07)
- 新增 自定义国际化语言支持 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=custom-i8n)
- 修复 一键登录时未校验重复手机号是否已验证的Bug
- 修复 Apple登录时用户邮箱为空时报错的Bug
- 修复 登录接口未传username时错误提示不正确的Bug
## 3.3.9(2021-11-09)
- 去除重复的context.xxx未找到的提示语
## 3.3.8(2021-10-28)
- 新增 用户账户封禁接口 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=ban-account)
- 新增 用户账户注销接口 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=close-account)
- 修复 未传appid时用户重复注册的Bug
## 3.3.7(2021-10-08)
- 移除部分接口的废弃提示
## 3.3.6(2021-09-08)
- 修复 邀请码可能重复的Bug
## 3.3.5(2021-08-10)
- 修复版本号错误
## 3.3.4(2021-08-10)
- 微信、QQ、支付宝登录新增type参数用于指定当前是登录还是注册
## 3.3.3(2021-08-04)
- 修复使用数组形式的配置文件报错的Bug
## 3.3.2(2021-08-03)
- 修复上3.3.0版本引出的createInstance接口传入配置不生效的Bug 感谢[hmh](https://gitee.com/hmh)
## 3.3.1(2021-07-30)
- 修复 将设置用户允许登录的应用列表时传入空数组报错的Bug
## 3.3.0(2021-07-30)
- 新增 不同端应用配置隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config)
- 新增 不同端用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user)
+ 此版本升级需要开发者处理一下用户数据,请参考 [补齐用户dcloud_appid字段](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=makeup-dcloud-appid)
- 新增 QQ登录、注册相关功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=qq)
- 调整 不再支持绑定手机、邮箱时不填验证码直接绑定
## 3.2.1(2021-07-09)
- 撤销3.2.0版本所做的调整
## 3.2.0(2021-07-09)
- 【重要】支持不同端(管理端、用户端等)用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user)
- 支持不同端(管理端、用户端等)配置文件隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config)
## 3.1.3(2021-07-08)
- 移除插件内误传的node_modules
## 3.1.2(2021-07-08)
- 修复 微信小程序绑定微信账号时报错的Bug
## 3.1.1(2021-07-01)
- 使用新的错误码规范,兼容旧版 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=errcode)
- 修复微信登录、绑定时未返回用户accessToken的Bug
## 3.1.0(2021-04-19)
- 增加对用户名、邮箱、密码字段的两端去空格
- 默认忽略用户名、邮箱的大小写 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=case-sensitive)
- 修复 customToken导出async方法报错的Bug
## 3.0.12(2021-04-13)
- 调整bindTokenToDevice默认值为false
## 3.0.11(2021-04-12)
- 修复3.0.7版本引出的多个用户访问时可能出现30201报错的Bug
## 3.0.10(2021-04-08)
- 优化错误提示
## 3.0.9(2021-04-08)
- bindMobile接口支持通过一键登录的方式绑定
- 优化错误提示
## 3.0.8(2021-03-19)
- 修复 3.0.7版本某些情况下生成token报错的Bug
## 3.0.7(2021-03-19)
- 新增 支持uni-config-center,更新uni-id无须再担心配置被覆盖 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=uni-config-center)
- 新增 自定义token内容,可以缓存角色权限之外的更多信息到客户端 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=custom-token)
- 新增 支持传入context获取uni-id实例,防止单实例多并发时全局context混乱 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=create-instance)
## 3.0.6(2021-03-05)
- 新增[uniID.wxBizDataCrypt](https://uniapp.dcloud.io/uniCloud/uni-id?id=%e5%be%ae%e4%bf%a1%e6%95%b0%e6%8d%ae%e8%a7%a3%e5%af%86)方法
- 优化loginByApple方法,提高接口响应速度
## 3.0.5(2021-02-03)
- 调整为uni_modules目录规范
+85
View File
@@ -0,0 +1,85 @@
{
"id": "uni-id",
"displayName": "uni-id",
"version": "3.3.29",
"description": "简单、统一、可扩展的用户中心",
"keywords": [
"uniid",
"uni-id",
"用户管理",
"用户中心",
"短信验证码"
],
"repository": "https://gitee.com/dcloud/uni-id.git",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"uni_modules": {
"dependencies": ["uni-config-center"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
**文档已移至[uni-id文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id)**
> 一般uni-id升级大版本时为不兼容更新,从低版本迁移到高版本请参考:[uni-id迁移指南](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=migration)
## 重要升级说明
**uni-id 3.x版本,搭配的uniCloud admin版本需大于1.2.10。**
### 缓存角色权限
自`uni-id 3.0.0`起,支持在token内缓存用户的角色权限,默认开启此功能,各登录接口的needPermission参数不再生效。如需关闭请在config内配置`"removePermissionAndRoleFromToken": true`。
为什么要缓存角色权限?要知道云数据库是按照读写次数来收取费用的,并且读写数据库会拖慢接口响应速度。未配置`"removePermissionAndRoleFromToken": true`的情况下,可以在调用checkToken接口时不查询数据库获取用户角色权限。
详细checkToken流程如下:
![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/ed45d350-5a4d-11eb-b997-9918a5dda011.jpg)
可以看出,旧版token(removePermissionAndRoleFromToken为true时生成的)在checkToken时如需返回权限需要进行两次数据库查询。新版token不需要查库即可返回权限信息。
**注意**
- 由于角色权限缓存在token内,可能会存在权限已经更新但是用户token未过期之前依然是旧版角色权限的情况。可以调短一些token过期时间来减少这种情况的影响。
- admin角色token内不包含permission,如需自行判断用户是否有某个权限,要注意admin角色需要额外判断一下,写法如下
```js
const {
role,
permission
} = await uniID.checkToken(event.uniIdToken)
if(role.includes('admin') || permission.includes('your permission id')) {
// 当前角色拥有'your permission id'对应的权限
}
```
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,52 @@
{
"passwordSecret": "passwordSecret-demo",
"tokenSecret": "tokenSecret-demo",
"tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600,
"passwordErrorLimit": 6,
"bindTokenToDevice": true,
"passwordErrorRetryTime": 3600,
"autoSetInviteCode": false,
"forceInviteCode": false,
"app-plus": {
"tokenExpiresIn": 2592000,
"oauth" : {
"weixin" : {
"appid" : "weixin appid",
"appsecret" : "weixin appsecret"
},
"apple": {
"bundleId": "your APP bundleId"
}
}
},
"mp-weixin": {
"oauth" : {
"weixin" : {
"appid" : "weixin appid",
"appsecret" : "weixin appsecret"
}
}
},
"mp-alipay": {
"oauth" : {
"alipay" : {
"appid" : "alipay appid",
"privateKey" : "alipay privateKey"
}
}
},
"service": {
"sms": {
"name": "DCloud",
"codeExpiresIn": 300,
"smsKey": "your sms key",
"smsSecret": "your sms secret"
},
"univerify": {
"appid":"your appid",
"apiKey": "your apiKey",
"apiSecret": "your apiSecret"
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
{
"name": "uni-id",
"version": "3.3.28",
"description": "uni-id for uniCloud",
"main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id",
"repository": {
"type": "git",
"url": "git+https://gitee.com/dcloud/uni-id.git"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,320 @@
<template>
<view class="vk-data-verification-code">
<view @click="sendSmsCode" :style="customStyle">
<slot :tips="tips" :sec-num="secNum">{{ tips }}</slot>
</view>
</view>
</template>
<script>
const localeObj = {
"zh-Hans":{
"startText":"获取验证码",
"changeText":"X秒重新获取",
"endText":"重新获取",
"tryAgainInSeconds":"秒后再重试",
"pleaseEnterTheCorrectMobileNumber":"请输入正确的手机号码",
"sending":"发送中...",
"verificationCodeSent":"验证码已发送",
"triggerDayLevelFlowControl":"触发天级流控",
"pleaseTryAgainTomorrow":"短信发送频繁,请明日再试!",
"pleaseTryAgainIn1Hour":"短信发送频繁,请过1小时后再试!",
"triggerMinuteLevelFlowControl":"触发分钟级流控",
"pleaseTryAgainLater":"短信发送频繁,请稍后再试!",
},
"zh-Hant":{
"startText":"獲取驗證碼",
"changeText":"X秒重新獲取",
"endText":"重新獲取",
"tryAgainInSeconds":"秒後再重試",
"pleaseEnterTheCorrectMobileNumber":"請輸入正確的手機號碼",
"sending":"發送中...",
"verificationCodeSent":"驗證碼已發送",
"triggerDayLevelFlowControl":"觸發天級流控",
"pleaseTryAgainTomorrow":"短信發送頻繁,請明日再試!",
"pleaseTryAgainIn1Hour":"短信發送頻繁,請過1小時後再試!",
"triggerMinuteLevelFlowControl":"觸發分鐘級流控",
"pleaseTryAgainLater":"短信發送頻繁,請稍後再試!",
},
"en":{
"startText":"Get code",
"changeText":"X second reacquire",
"endText":"Reacquire",
"tryAgainInSeconds":"Try again in seconds",
"pleaseEnterTheCorrectMobileNumber":"Please enter the correct mobile number",
"sending":"Sending...",
"verificationCodeSent":"Verification code sent",
"triggerDayLevelFlowControl":"Trigger day level flow control",
"pleaseTryAgainTomorrow":"SMS sent frequently, please try again tomorrow!",
"pleaseTryAgainIn1Hour":"SMS sent frequently, please try again in 1 hour",
"triggerMinuteLevelFlowControl":"Trigger minute level flow control",
"pleaseTryAgainLater":"SMS sent frequently, please try again later",
}
};
/**
* verificationCode 验证码输入框
* @property {String} mode 模式,mobile 手机验证码 custom 自定义验证码 默认 mobile
* @property {String} mobile 接收短信的手机号
* @property {String} type 短信类型,如 register、login、bind、unbind
* @property {Number String} seconds 倒计时所需的秒数(默认60)
* @property {Object String} customStyle 自定义样式
* @property {String} start-text 开始前的提示语,见官网说明(默认获取验证码)
* @property {String} change-text 倒计时期间的提示语,必须带有字母"x",见官网说明(默认X秒重新获取)
* @property {String} end-text 倒计结束的提示语,见官网说明(默认重新获取)
* @property {Boolean} keep-running 是否在H5刷新或各端返回再进入时继续倒计时(默认false)
* @event {Function} change 倒计时期间,每秒触发一次
* @event {Function} start 开始倒计时触发
* @event {Function} end 结束倒计时触发
* @event {Function} send 自定义发送事件 当mode=custom时会有此事件
* @example <vk-data-verification-code seconds="60" :mobile="form1.mobile" type="register" custom-style="font-size: 28rpx;"></vk-data-verification-code>
*/
export default {
name: 'vk-data-verification-code',
emits: ['start', 'end', 'change','send','codeChange','error','success'],
props: {
// 模式,mobile 手机验证码 custom 自定义验证码 默认 mobile
mode: {
type: String,
default: 'mobile'
},
// 接收短信的手机号
mobile: {
type: String
},
// 短信类型,如 register、login、bind、unbind
type: {
type: String,
default: 'login'
},
customStyle: {
type: [String,Object]
},
// 倒计时总秒数
seconds: {
type: [String, Number],
default: 60
},
// 尚未开始时提示
startText: {
type: String,
default: ''
},
// 正在倒计时中的提示
changeText: {
type: String,
default: ''
},
// 倒计时结束时的提示
endText: {
type: String,
default: ''
},
// 是否在H5刷新或各端返回再进入时继续倒计时
keepRunning: {
type: Boolean,
default: false
},
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
uniqueKey: {
type: String,
default: ''
},
// 是否需要检测手机号对应的账号是否存在,默认false:不检测 设置为true:会检测,如果检测到用户不存在,则不发短信。
checkUserExist: {
type: Boolean,
default: false
},
},
data() {
return {
secNum: this.seconds,
timer: null,
canGetCode: true, // 是否可以执行验证码操作
tips: '',
locale:{}, // 语言
};
},
created(){
if (typeof vk !== "undefined") {
let locale = vk.pubfn.getLocale();
this.locale = localeObj[locale];
} else {
this.locale = localeObj["zh-Hans"];
}
},
mounted() {
this.checkKeepRunning();
},
watch: {
seconds: {
immediate: true,
handler(n) {
this.secNum = n;
}
}
},
// 计算属性
computed:{
startTextCom(){
return this.startText || this.locale.startText || "获取验证码";
},
changeTextCom(){
return this.changeText || this.locale.changeText || "X秒重新获取";
},
endTextCom(){
return this.endText || this.locale.endText || "重新获取";
}
},
methods: {
checkKeepRunning() {
// 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'));
if (!lastTimestamp) return this.changeEvent(this.startTextCom);
// 当前秒的时间戳
let nowTimestamp = Math.floor(+new Date() / 1000);
// 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
if (this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
// 剩余尚未执行完的倒计秒数
this.secNum = lastTimestamp - nowTimestamp;
// 清除本地保存的变量
uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp');
// 开始倒计时
this.start();
} else {
// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
this.changeEvent(this.startTextCom);
}
},
// 开始倒计时
start() {
// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
this.$emit('start');
this.canGetCode = false;
// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
this.changeEvent(this.changeTextCom.replace(/x|X/, this.secNum));
this.setTimeToStorage();
this.timer = setInterval(() => {
if (--this.secNum) {
// 用当前倒计时的秒数替换提示字符串中的"x"字母
this.changeEvent(this.changeTextCom.replace(/x|X/, this.secNum));
} else {
clearInterval(this.timer);
this.timer = null;
this.changeEvent(this.endTextCom);
this.secNum = this.seconds;
this.$emit('end');
this.canGetCode = true;
}
}, 1000);
},
// 重置,可以让用户再次获取验证码
reset(text) {
this.canGetCode = true;
clearInterval(this.timer);
this.secNum = this.seconds;
this.changeEvent(text || this.endTextCom);
},
changeEvent(text) {
this.codeChange(text);
this.$emit('change', text);
},
// 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
setTimeToStorage() {
if (!this.keepRunning || !this.timer) return;
// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
if (this.secNum > 0 && this.secNum <= this.seconds) {
// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
let nowTimestamp = Math.floor(+new Date() / 1000);
// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
uni.setStorage({
key: this.uniqueKey + '_$uCountDownTimestamp',
data: nowTimestamp + Number(this.secNum)
});
}
},
// 倒计时进行时
codeChange(text) {
this.tips = text;
this.$emit('codeChange', text);
},
// 发送短信验证码
sendSmsCode() {
let that = this;
let vk = uni.vk;
let {
mobile,
type,
canGetCode,
checkUserExist,
secNum,
mode,
} = that;
if (!canGetCode) {
vk.toast(`${secNum}${that.locale.tryAgainInSeconds}`, 'none');
return;
}
if (mode === "custom") {
that.$emit("send", { type: type });
return;
}
if (!vk.pubfn.test(mobile, 'mobile')) {
vk.toast(that.locale.pleaseEnterTheCorrectMobileNumber, 'none');
return;
}
that.tips = that.locale.sending;
vk.userCenter.sendSmsCode({
needAlert: false,
data: {
mobile,
type,
checkUserExist
},
success: function(data) {
vk.toast(that.locale.verificationCodeSent);
that.start();
that.$emit('success', data);
},
fail: function(err) {
that.tips = that.startTextCom;
if (err.errMsg && err.errMsg.indexOf('触发天级流控') > -1) {
vk.alert(that.locale.pleaseTryAgainTomorrow,()=>{ that.$emit('error', err); });
} else if (err.errMsg && err.errMsg.indexOf('触发小时级流控') > -1) {
vk.alert(that.locale.pleaseTryAgainIn1Hour,()=>{ that.$emit('error', err); });
} else if (err.errMsg && err.errMsg.indexOf('触发分钟级流控') > -1) {
vk.alert(that.locale.pleaseTryAgainLater,()=>{ that.$emit('error', err); });
} else if (err.msg) {
vk.alert(err.msg,()=>{ that.$emit('error', err); });
} else {
vk.alert(that.locale.pleaseTryAgainLater,()=>{ that.$emit('error', err); });
}
}
});
}
},
// 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
// #ifndef VUE3
beforeDestroy() {
this.setTimeToStorage();
clearTimeout(this.timer);
this.timer = null;
},
// #endif
// #ifdef VUE3
beforeUnmount() {
this.setTimeToStorage();
clearTimeout(this.timer);
this.timer = null;
}
// #endif
};
</script>
<style lang="scss" scoped></style>
+7
View File
@@ -0,0 +1,7 @@
/**
* vk-unicloud框架客户端(前端)
* author VK
*/
import vk from './vk_modules/vk-unicloud-page'
export default vk;
+86
View File
@@ -0,0 +1,86 @@
{
"id": "vk-unicloud",
"displayName": "vk-unicloud-router开发框架核心库 - 已集成uni-id 框架内置了众多API。",
"version": "2.15.3",
"description": "此为vk-unicloud-router框架核心库(新手建议下载完整框架项目)已集成uni-id支持云函数url化。众多现成API,内置小白也能轻松上手的数据库API。使你项目刚起步进度就是百分之50",
"keywords": [
"vk-unicloud-router",
"云函数路由、云对象路由",
"vk云开发",
"内置uni-id、uview、数据库baseDao",
"内置众多API、工具包"
],
"author": "VK",
"repository": "https://gitee.com/vk-uni/vk-uni-cloud-router",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "370725567"
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": "https://vkdoc.fsq.pub/",
"type": "unicloud-template-function"
},
"uni_modules": {
"dependencies": ["uni-config-center","uni-id"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}
+572
View File
@@ -0,0 +1,572 @@
### uniCloud云函数路由框架研究Q群:22466457 如有问题或建议可以在群内讨论。
### 插件名称:`vk-unicloud-router`
### 作者:VK
## 序
##### 如果你热爱编程,想快速入门云开发,欢迎使用`vk-unicloud`系列开发框架
##### 无需转变开发习惯,0成本上手云开发。
##### 框架内置了众多API、工具包,为你的业务扫平障碍。使你的项目刚起步进度就是50%(微信登录、短信、验证码、缓存、生成小程序码等等)
##### 从此你又get一个新技能,只需用js,轻松搞定前后台整体业务。
##### 完整框架项目地址:
##### `client端`框架地址: [https://ext.dcloud.net.cn/plugin?id=2204](https://ext.dcloud.net.cn/plugin?id=2204)
##### `admin端`框架地址:[https://ext.dcloud.net.cn/plugin?id=5043](https://ext.dcloud.net.cn/plugin?name=vk-unicloud-admin)
##### `client端`框架文档:[点击查看](https://vkdoc.fsq.pub/client/)
##### `admin端`框架文档:[点击查看](https://vkdoc.fsq.pub/admin/)
##### 框架学习Q群:`22466457` 欢迎萌新和大佬来使用和共同改进框架
## 主要功能 以及 对开发者的价值
#### 1、实现云函数路由模式(同时支持对云对象的路由)
* 1.1、路由模式不使用任何第三方包,兼容性强,运行稳定
* 1.2、减少云函数数量,云函数有个数限制(用一个云函数即可实现多个云函数效果)
* 1.3、部分通用的代码块可以放在公共区执行,具有公共函数的效果,增加代码复用率,便于维护。
* 1.4、可以在开发环境和生产环境中任意切换,开发时,用开发环境,开发过程中不会影响到线上环境。(具体切换方法在文档最后)
* 1.5、美化云函数请求过程中的日志排版,使请求日志一目了然。(再也不用为日志分散,难找而头疼)
* 1.6、可以强制关闭云端服务(适用于需要临时关闭后端服务的情况,如腾讯云迁移数据到阿里云时需要先关闭服务,否则迁移过程中会有新数据产生)
#### 2、实现 `全局过滤器`,过滤非法请求。您无需关心用户角色权限问题。
#### 3、众多现成的 `JS API`,让您无需重复造轮子。(持续增加中)
#### 4、众多现成的 `云函数 API`,为您的业务扫平障碍,让您可以专注于自身业务开发。(微信登录、短信、验证码、缓存、生成小程序码等等)(持续增加中)
#### 5、全面支持 `url化` 的云函数请求,您无需关心url化后参数的获取问题
#### 6、已集成 `uni-id` 当前版本: `3.3.28`(已支持真实发送邮箱验证码和短信验证码)
* 6.1、由于每个应用基本都会用上用户系统,官方建议所有 `uniCloud` 应用均使用 `uni-id` 作为用户体系,
有了统一的账户规范,并且围绕这套账户规范,有各种各样插件,那么开发者可以随意整合这些插件,让数据连同。
* 6.2、同时方便其他用户使用 `unicloud` 插件发布者发布的前后端一体插件,只需要导入一个云函数即可。
(如导入一个社区插件,可能要导入几十个云函数,而使用此云函数路由后,只需导入一个云函数,且已集成 `uni-id`,打通账号体系)。
#### 7、在 `uni-id` 基础上升级为 `vk.userCenter` 用户中心API 通过this.vk.userCenter 即可调用 [传送门](https://vkdoc.fsq.pub/client/vk.userCenter.html)
#### 8、封装 `uni.callFunction` 和 `uni.request` 使之合二为一 通过this.vk.callFunction 调用
#### 9、`vk.baseDao` 数据库API,使小白也能轻松上手对数据库的调用。 [传送门](https://vkdoc.fsq.pub/client/uniCloud/db/api.html)
#### 10、`vk.baseDao.selects` 数据库万能连表查询API [传送门](https://vkdoc.fsq.pub/client/uniCloud/db/selects.html)
#### 11、`商品SKU选择器组件`(打造uni插件市场功能最全的SKU选择器组件)[传送门](https://ext.dcloud.net.cn/plugin?id=2848)
#### 12、对 `Vuex` 进行了深度封装(支持持久化),现在可以很方便的使用Vuex进行读取和储存。[传送门](https://vkdoc.fsq.pub/client/pages/vuex.html)
#### 13、自定义过滤器(在业务云函数执行之前(或之后),统一拦截,进行过滤后再放行,支持设置多个过滤器,并按指定顺序执行)[传送门](https://vkdoc.fsq.pub/client/uniCloud/middleware/filter.html)
#### 14、支持设置需要登录的页面列表或不需要登录的页面列表(通过本地token检测,无网络请求,支持通配符写法)[传送门](https://vkdoc.fsq.pub/client/pages/config.html)
#### 15、支持设置可以被分享的页面列表或禁止分享的页面列表(支持通配符写法)[传送门](https://vkdoc.fsq.pub/client/pages/config.html)
#### 16、`vk.localStorage` 封装本地缓存的curd,同时具有监听缓存被更新和被删除的功能。[传送门](https://vkdoc.fsq.pub/client/pages/localStorage.html)
#### 17、美化云函数请求过程中的日志排版,使请求日志一目了然。
![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-cf0c5e69-620c-4f3c-84ab-f4619262939f/4a44fbf8-6b5e-43a6-b443-51a108dec125.png)
#### 18、可以 `强制关闭` 云端服务(适用于需要临时关闭后端服务的情况,如腾讯云迁移数据到阿里云时需要先关闭服务,否则迁移过程中会有新数据产生)
#### 19、全局数据缓存API,现在可以很方便的在云函数中使用缓存。 [传送门](https://vkdoc.fsq.pub/client/uniCloud/cache/cache.html)
#### 20【全网首家】云函数URL化之URL重写 [传送门](https://vkdoc.fsq.pub/client/uniCloud/cloudfunctions/urlrewrite.html)
#### 21、千人群交流开发心得,助你避坑,作者亲自在群内解答框架使用问题,让你轻松上手 `uniCloud云开发`。
#### 22、其他好处…
### 目前已集成的服务端API
#### 1、微信小程序服务端API [传送门](https://vkdoc.fsq.pub/client/uniCloud/plus/weixin.html)
#### 2、微信公众号服务端API [传送门](https://vkdoc.fsq.pub/client/uniCloud/plus/weixin-h5.html)
#### 3、百度开放平台服务端API [传送门](https://vkdoc.fsq.pub/client/uniCloud/plus/baidu.html)
#### 4、短信发送 [传送门](https://vkdoc.fsq.pub/client/uniCloud/plus/sms.html)
#### 5、邮箱发送 [传送门](https://vkdoc.fsq.pub/client/uniCloud/plus/mail.html)
#### 6、Redis [传送门](https://vkdoc.fsq.pub/vk-redis/)
##### 插件部分API功能体验地址
![插件首页体验地址](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-vk-cloud-router-test/51de83e0-e9ae-11ea-81ea-f115fe74321c.png?x-oss-process=image/resize,h_250 "插件首页体验地址")
## 快速上手 - 安装步骤
## 后端(云函数端)安装步骤
打开 `uniCloud/cloudfunctions` 目录
#### 前置工作:修改云函数全局配置文件(用到微信登录的必须要配置)[微信小程序配置详细教程](https://vkdoc.fsq.pub/client/question/q12.html)
#### 注意:
* 1、开发微信小程序和APP微信登录需要额外配置 manifest.json 以及 uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json 这2个配置文件,且改动配置后需要重新上传 公共模块 和 router 函数。
* 2、若你的电脑没有安装 Node.js,则无法使用 npm 命令。
* 3、Node.js 安装包及源码下载地址为:[https://nodejs.org/en/download/](https://nodejs.org/en/download/)
* 4、Node.js 安装教程:[https://www.runoob.com/nodejs/nodejs-install-setup.html](https://www.runoob.com/nodejs/nodejs-install-setup.html)
查看 `uni-id/config.json` 代码格式 [点击查看](https://vkdoc.fsq.pub/client/uniCloud/config/uni-id.html)
#### 正式安装
直接右键`uniCloud`目录 点击 `运行云服务空间初始化向导` 即可
## 前端(页面端)安装步骤
### Vue2.0版本
`main.js` 引入 `vk-unicloud-page` 库
```js
// 引入 vk框架前端
import vk from './uni_modules/vk-unicloud';
Vue.use(vk);
```
完整 `main.js` 示例
```js
import Vue from 'vue'
import App from './App'
import store from './store'
import config from '@/app.config.js'
// 引入 vk框架前端
import vk from './uni_modules/vk-unicloud';
Vue.use(vk);
// 初始化 vk框架
Vue.prototype.vk.init({
Vue, // Vue实例
config, // 全局配置
});
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
store,
...App
});
app.$mount();
```
### Vue3.0版本
`main.js` 引入 `vk-unicloud-page` 库
完整 `main.js` 示例
```js
import App from './App'
import store from './store'
import config from '@/app.config.js'
// 引入 vk框架前端
import vk from './uni_modules/vk-unicloud';
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
// 使用 vuex
app.use(store)
// 使用 vk框架前端
app.use(vk);
// 初始化 vk框架
app.config.globalProperties.vk.init({
Vue: app, // Vue实例
config, // 配置
});
return { app }
}
```
### 自 client端框架 2.6.0 起,不再内置任何 UI 框架,你可以选择自己喜欢的 UI 组件库进行开发。
### 集成 `vk-uview-ui`(vue2.0版)
___若不想集成 `vk-uview-ui` 可跳过此处___
适合开发:H5、App(vue版本)、微信小程序、支付宝小程序(其他小程序未验证)
> 插件市场导入 `vk-uview-ui` 框架:[点击前往](https://ext.dcloud.net.cn/plugin?id=6692)
* 1、main.js 引入 vk-uview-ui
```js
import uView from './uni_modules/vk-uview-ui';
Vue.use(uView);
```
* 2、App.vue 引入基础样式(注意style标签需声明scss属性支持)
```html
<style lang="scss">
@import "./uni_modules/vk-uview-ui/index.scss";
</style>
```
* 3、uni.scss 引入全局 scss 变量文件
```css
@import "@/uni_modules/vk-uview-ui/theme.scss";
```
### 集成 `vk-uview-ui`(vue3.0版)
___若不想集成 `vk-uview-ui` 可跳过此处___
适合开发:H5、App(vue版本)、微信小程序(其他小程序未验证)
> 插件市场导入 `vk-uview-ui` 框架:[点击前往](https://ext.dcloud.net.cn/plugin?id=6692)
不建议把老项目 升级到 Vue3.0 (升级非常麻烦,建议新项目才考虑是否使用Vue3.0)
___注意:目前(2020-11-18) `uniapp` 的 `Vue3.0` 版本只兼容:H5、App、微信小程序___
* 1、前置步骤:修改 `manifest.json` 内的 `vue` 版本为 `vue3`
* 2、项目根目录新增 `index.html` 文件,文件代码为
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
<title></title>
<!--preload-links-->
<!--app-context-->
<!-- 配置H5的 web图标static/logo.png -->
<link rel="icon" href="./static/logo.png" />
</head>
<body>
<div id="app">
<!--app-html-->
</div>
<script type="module" src="/main.js"></script>
</body>
</html>
```
* 3、main.js 引入 vk-uview-ui
```js
// 引入 uView UI
import uView from './uni_modules/vk-uview-ui';
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
// 使用 uView UI
app.use(uView)
return { app }
}
```
* 4、App.vue 引入基础样式(注意style标签需声明scss属性支持)
```html
<style lang="scss">
@import "./uni_modules/vk-uview-ui/index.scss";
</style>
```
* 5、uni.scss 引入全局 scss 变量文件
```css
@import "@/uni_modules/vk-uview-ui/theme.scss";
```
### 集成 `uview-ui`(nvue2.0版)
___若不想集成 `uview-ui` 可跳过此处___
适合开发:App(nvue版本),如果你只开发App,推荐用这个UI
> 插件市场导入 `uview-ui` 框架:[点击前往](https://ext.dcloud.net.cn/plugin?id=1593)
* 1、main.js 引入 uview-ui
```js
import uView from './uni_modules/uview-ui';
Vue.use(uView);
```
* 2、App.vue 引入基础样式(注意style标签需声明scss属性支持)
```html
<style lang="scss">
@import "./uni_modules/uview-ui/index.scss";
</style>
```
* 3、uni.scss 引入全局 scss 变量文件
```css
@import "@/uni_modules/uview-ui/theme.scss";
```
### 集成 `uview-ui`(nvue3.0版)
别想了,uniapp官方也还没支持。
### 组件库并不限制只能从以上选择,理论上支持任何UI组件库进行开发。
### 卸载 `uView1` 的步骤
* 1、main.js 删除 uView1
```js
import uView from 'uview-ui'
Vue.use(uView);
```
* 2、App.vue 删除基础样式
```html
<style lang="scss">
@import "uview-ui/index.scss";
</style>
```
* 3、uni.scss 删除全局 scss 变量文件
```css
@import "uview-ui/theme.scss";
```
* 4、pages.json 删除 easycom 规则
```js
"easycom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue",
},
```
* 5、package.json 删除 uview-ui 依赖
```js
"dependencies": {
"uview-ui": "^1.8.3"
},
```
* 6、package-lock.json 删除 uview-ui 依赖
```js
"dependencies": {
"uview-ui": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/uview-ui/-/uview-ui-1.8.3.tgz",
"integrity": "sha512-DqKc+qRrOZLPcyfWv4b0HspSS9n1Cd6BbgKiYEv9rjTAnWoqJV7rXsvWqZdr5iKGP5EMNbNS741GLNw4sIHbpw=="
}
}
```
### 完整安装文档:[查看完整安装文档](https://vkdoc.fsq.pub/client/quickstart.html)
## 云函数框架目录结构
```
.
├── common─────────────────# 自定义官方公共模块包
│ └── config──────────────────# 全局配置公共模块
│ └── uni-id──────────────────# uni-id官方公共模块
│ └── vk-unicloud─────────────# vk-unicloud公共模块
├── router─────────────────# 正式环境云函数主入口(函数路由器)
│ └── dao──────────────────# dao层(数据库交互公共API)
│ └── middleware───────────# 中间层(过滤器、拦截器)
│ └── node_modules─────────# npm包
│ └── service──────────────# 逻辑层(用于写业务逻辑)
│ ── └── 逻辑层目录结构在下方单独展示
│ └── util─────────────────# 工具包
│ ── └── pubFunction.js───────# 公共函数包
│ ── └── smsUtil.js───────────# 全局过滤器
│ └── config.js────────────# 入口函数初始化配置
│ └── index.js─────────────# 入口函数
│ └── package.json─────────# 第三方依赖配置文件(若使用npm,自动生成)
└─└── package-lock.json────# 第三方依赖配置文件(若使用npm,自动生成)
.
├── router-test───────────────# 函数路由(开发测试环境)
│ └── ...─────────# ...
│ └── ...─────────# ...
└── └── ...─────────# ...
```
### 逻辑层目录结构
#### `router/service` 目录为逻辑层
#### 以下的目录并非强制性,只是建议,便于统一开发规范。
```
.
├── service──────────────────────# 逻辑层(用于写业务逻辑)
│ └── admin────────────────# 后台管理端逻辑(admin框架开发中。。。)
│ ── └── xxxxxxxxxxxxxxx──────#
│ ── └── xxxxxxxxxxxxxxx──────#
│ ── └── xxxxxxxxxxxxxxx──────#
│ ── └── xxxxxxxxxxxxxxx──────#
│ └── client───────────────# 客户端逻辑(H5、小程序、APP)
│ ── └── user─────────────────# 用户服务(自己系统的逻辑)
│ ──── └── kh───────────────────# kh函数为必须登录后才能访问的函数
│ ──── └── pub──────────────────# pub函数为所有人都可以访问,不限制
│ ──── └── util─────────────────# 用户服务专用的工具包
│ ── └── order────────────────# 订单服务(自己系统的逻辑)
│ ──── └── kh───────────────────# kh函数为必须登录后才能访问的函数
│ ──── └── pub──────────────────# pub函数为所有人都可以访问,不限制
│ ──── └── util─────────────────# 订单服务专用的工具包
│ ── └── xxxxxxxxxxxxxxx──────#
│ ── └── xxxxxxxxxxxxxxx──────#
│ └── common───────────────# 公共逻辑(公共接口)
│ ── └── xxxxxxxxxxxxxxxx─────#
│ ── └── xxxxxxxxxxxxxxxx─────#
│ ── └── xxxxxxxxxxxxxxxx─────#
│ └── user─────────────────# 统一用户中心服务(已集成uniID)(用户中心作为核心,且为了方便插件升级,故与admin和client平级)
│ ── └── kh───────────────────# kh函数为必须登录后才能访问的函数(客户端用户)
│ ── └── pub──────────────────# pub函数为所有人都可以访问,不限制
│ ── └── sys──────────────────# sys函数为后端管理人员才能访问的函数(商家后台工作人员)
│ ── └── util─────────────────# 统一用户中心服务专用的工具包
│ └── plugs───────────────# 插件逻辑(插件专用)(会陆续新增一些实用性插件给开发者使用,如微信小程序发送订阅消息等等)
│ ──└── plugs-A───────────────────# 插件A
│ ────└── admin───────────────────# 插件A admin端
│ ────└── client──────────────────# 插件A client端
│ ──└── plugs-B───────────────────# 插件B
│ ────└── admin───────────────────# 插件B admin端
│ ────└── client──────────────────# 插件B client端
│ └── template─────────────# 云函数模板(插件内的云函数写法模板)
│ ── └── db_api───────────────# 数据库接口调用模板
│ └── muban.js─────────────# 云函数模板(新建一个云函数应复制粘贴这个文件)
│ └── muban_easy.js────────# 云函数模板(简易版)
└─────────────────────────────────
```
## 云函数service模板文件示例
```js
module.exports = {
/**
* 此函数名称
* @url user/sys/test1 前端调用的url参数地址
* @description 此函数描述
* @param {Object} data 请求参数
* @param {String} uniIdToken 用户token
* @param {String} userInfo 当前登录用户信息(同理,是可信任的)(只有kh函数才带此参数)
* @param {Object} util 公共工具包
* @param {Object} filterResponse 过滤器返回的数据
* @param {Object} originalParam 原始请求参数(包含了原始event和context)
* data 请求参数 说明
* @param {String} uid 当前登录用户id,若用户已登录且token有效,则data中会带uid参数
* (此参数是后端过滤器通过token获取并添加到data中的,是可信任的)(只有kh函数才带此参数)
* res 返回参数说明
* @param {Number} code 错误码,0表示成功
* @param {String} msg 详细信息
*/
main: async (event) => {
let { data = {}, userInfo, util, filterResponse, originalParam } = event;
let { customUtil, uniID, config, pubFun, vk , db, _ } = util;
let { uid } = data;
let res = { code : 0, msg : '' };
// 业务逻辑开始-----------------------------------------------------------
// 可写与数据库的交互逻辑等等
// 业务逻辑结束-----------------------------------------------------------
return res;
}
}
```
#### 成功时返回
```js
// 注意:所有成功请求必须返回code:0 code为其他值,代表失败,如兑换失败,您的积分不足!
// 如成功时返回 res.code = 0 表示执行成功;
return {
code : 0,
msg : '兑换成功!'
}
```
#### 失败时返回
```js
return {
code : -1,
msg : '兑换失败,您的积分不足!'
}
```
## this.vk.callFunction函数的参数说明
```js
/**
* 云函数请求封装 - 统一入口
* @description 通过云函数路由,1个云函数实现多个云函数的效果。
* @param {String} url 请求路径,该路径实为router云函数的service目录下的路径
* @param {Object} data 请求参数
* @param {String} title 遮罩层提示语,为空或不传则代表不显示遮罩层。
* @param {Boolean} isRequest 是否使用云函数url化地址访问云函数,默认false
* @param {Boolean} needAlert 为true代表请求错误时,会有alert弹窗提示。默认为true
* @param {Function} success 请求成功时,执行的回调函数
* @param {Function} fail 请求失败时,执行的回调函数
* @param {Function} complete 无论请求成功与否,都会执行的回调函数
*/
```
## 前端调用云函数示例
```js
this.vk.callFunction({
url: 'user/kh/setAvatar',
title:'请求中...',
data:{
avatar: "https://xxxxxxx.jpg"
},
success(data) {
// 修改成功
}
});
```
## 云函数url化方式调用云函数示例
[点击查看『云函数url化』方式调用云函数示例](https://vkdoc.fsq.pub/client/pages/callFunctionForUrl.html)
## 前端非法token拦截器
[点击查看『前端非法token拦截器』](https://vkdoc.fsq.pub/client/pages/interceptor1.html)
## vk.baseDao数据库API
[点击查看『vk.baseDao』数据库API](https://vkdoc.fsq.pub/client/uniCloud/db/api.html)
## 设置全局请求参数
[点击查看『设置全局请求参数』](https://vkdoc.fsq.pub/client/pages/updateRequestGlobalParam.html)
## 列表渲染前后端一体模板
[点击查看『列表渲染』前后端一体模板](https://vkdoc.fsq.pub/client/pages/list.html)
## 商品SKU选择器组件
[点击查看『商品SKU选择器组件』](https://gitee.com/vk-uni/vk-uni-cloud-router/wikis/pages?sort_id=2912199&doc_id=975983)
## 如何切换 正式环境 和 开发环境
[点击查看如何『切换正式环境和开发环境』](https://vkdoc.fsq.pub/client/question/q1.html)
## 本插件更新步骤
[点击查看本插件『更新步骤』](https://vkdoc.fsq.pub/client/changeGuide.html)
## uniCloud云函数路由框架研究Q群:`22466457` 如有问题或建议可以在群内讨论。
### 如果你觉得框架对你有用,可以在下方进行评论,也可以进行赞赏。
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,16 @@
{
"name": "vk-unicloud",
"version": "2.15.3",
"description": "【云函数端SDK】VK云函数路由模式uniCloud开发框架,在router目录下执行 npm i vk-unicloud 进行安装和升级",
"main": "index.js",
"homepage": "https://gitee.com/vk-uni/vk-uni-cloud-router.git",
"repository": {
"type": "git",
"url": "git+https://gitee.com/vk-uni/vk-uni-cloud-router.git"
},
"files": [
"dist"
],
"author": "VK",
"license": "Apache-2.0"
}
@@ -0,0 +1,190 @@
/**
* vk-unicloud框架客户端(前端)
* author VK
*/
import userCenter from './libs/vk-unicloud/vk-unicloud-user-center'
import callFunctionUtil from './libs/vk-unicloud/vk-unicloud-callFunctionUtil'
import pubfn from './libs/function/index'
import modal from './libs/function/modal'
import navigate from './libs/function/vk.navigate'
import localStorage from './libs/function/vk.localStorage'
import sessionStorage from './libs/function/vk.sessionStorage'
import aliyunOSSUtil from './libs/function/aliyunOSSUtil'
import openapi from './libs/openapi/index'
import requestUtil from './libs/function/vk.request'
import importObject from './libs/function/vk.importObject'
import filters from './libs/function/vk.filters'
import mixin from './libs/mixin/mixin.js'
import initPermission from './libs/function/permission'
import storeMixin from './libs/store/mixin/mixin'
import initGlobalError from './libs/store/libs/error'
import consoleLog from './libs/install/console.log'
import updateManager from './libs/function/updateManager.js'
// #ifdef H5
import h5 from './libs/function/vk.h5'
// #endif
var vk = {
userCenter,
callFunctionUtil,
/**
* 发起一个云函数请求
*/
callFunction: callFunctionUtil.callFunction,
getToken: callFunctionUtil.getToken,
checkToken: callFunctionUtil.checkToken,
uploadFile: callFunctionUtil.uploadFile,
getConfig: callFunctionUtil.getConfig,
emitRefreshToken: callFunctionUtil.emitRefreshToken,
onRefreshToken: callFunctionUtil.onRefreshToken,
offRefreshToken: callFunctionUtil.offRefreshToken,
pubfn,
// #ifdef H5
h5,
// #endif
alert: modal.alert,
toast: modal.toast,
confirm: modal.confirm,
prompt: modal.prompt,
showActionSheet: modal.showActionSheet,
showLoading: modal.showLoading,
hideLoading: modal.hideLoading,
setLoading: modal.setLoading,
navigate,
// 保留当前页面,并进行页面跳转
navigateTo: navigate.navigateTo,
// 关闭当前页面,并进行页面跳转
redirectTo: navigate.redirectTo,
// 并关闭所有页面,并进行页面跳转
reLaunch: navigate.reLaunch,
// 并关闭所有非tab页面,并进行tab面跳转
switchTab: navigate.switchTab,
// 页面返回
navigateBack: navigate.navigateBack,
// 跳转到首页
navigateToHome: navigate.navigateToHome,
// 跳转到登录页
navigateToLogin: navigate.navigateToLogin,
// 跳转到小程序
navigateToMiniProgram: navigate.navigateToMiniProgram,
// 触发全局的自定义事件,附加参数都会传给监听器回调函数。
$emit: navigate.$emit,
// 监听全局的自定义事件,事件由 uni.$emit 触发,回调函数会接收事件触发函数的传入参数。
$on: navigate.$on,
// 触发全局的自定义事件,附加参数都会传给监听器回调函数。
$once: navigate.$once,
// 移除全局自定义事件监听器。
$off: navigate.$off,
// 本地持久
localStorage: localStorage,
// 本地会话缓存
sessionStorage: sessionStorage,
// 获取应用语言列表
getLocaleList: pubfn.getLocaleList,
// 获取应用当前语言
getLocale: pubfn.getLocale,
// 获取应用当前语言对象
getLocaleObject: pubfn.getLocaleObject,
// 设置应用当前语言
setLocale: pubfn.setLocale,
// 本地持久缓存
...localStorage,
// 本地会话缓存
...sessionStorage,
// 阿里云oss
aliyunOSSUtil,
// 更新管理器
updateManager,
// 开放API
openapi,
// 请求库
requestUtil,
// 发起URL请求
request: requestUtil.request,
// 导出云对象
importObject
};
// vk实例初始化
vk.init = function(obj = {}) {
let {
Vue, // Vue实例
config, // 配置
store, // vuex简写法则(新版本无需传store)
} = obj;
if (typeof store !== "undefined") {
// 兼容旧版本
Vue.use(store);
} else {
// 新版本无需传store
Vue.mixin(storeMixin);
if (config.globalError) {
Vue.use(initGlobalError);
}
}
// 自定义云函数路由配置
vk.callFunctionUtil.setConfig(config);
// 重写 console.log
Vue.use(consoleLog);
};
vk.getGlobalObject = function() {
if (typeof globalThis === "object") return globalThis;
if (typeof self === "object") return self;
if (typeof window === "object") return window;
if (typeof global === "object") return global;
};
// 加载拓展功能
vk.use = function(obj, util) {
for (let name in obj) {
if (obj[name] && typeof obj[name].init === "function") {
obj[name].init(util);
}
vk[name] = obj[name];
}
};
const install = Vue => {
// 全局混入
Vue.mixin(mixin);
// #ifndef VUE3
// 加载全局过滤器开始
for (let i in filters) {
Vue.filter(i, filters[i]);
}
Vue.prototype.vk = vk;
Vue.prototype.$fn = vk.pubfn;
// #endif
// #ifdef VUE3
// 将vk挂载到Vue实例
Vue.config.globalProperties.vk = vk;
Vue.config.globalProperties.$fn = vk.pubfn;
// #endif
// 将vk挂载到uni对象
if (typeof uni == "object") uni.vk = vk;
// 将vk挂载到全局
let vkGlobalThis = vk.getGlobalObject();
if (typeof vkGlobalThis == "object") vkGlobalThis.vk = vk;
let util = { vk };
// 加载插件
vk.use({
callFunctionUtil: vk.callFunctionUtil,
openapi: vk.openapi
}, util);
initPermission(Vue);
}
export default {
install
}
@@ -0,0 +1,274 @@
/**
* 阿里云oss工具类
*/
var aliyunOSSUtil = {};
var counterNum = 0;
/**
* 上传至阿里云oss
vk.callFunctionUtil.uploadFile({
filePath:tempFilePath,
fileType:"image",
provider:"aliyun",
index,
onUploadProgress:function(res){
// 上传过程中
if (res.progress > 0) {
if(list[index]){
list[index].progress = res.progress;
}
}
},
success:function(res){
// 上传成功
},
fail:function(res){
// 上传失败
}
});
*/
aliyunOSSUtil.uploadFile = function(obj) {
let {
filePath,
fileType = "image",
name = "file",
header = {
"x-oss-forbid-overwrite": true,
},
index = 0,
file = {},
needSave = false,
category_id,
title,
cloudPathRemoveChinese = true, // 删除文件名中的中文
cloudDirectory,
} = obj;
let vk = getApp().globalData.vk;
if (title) vk.showLoading(title);
let fileNameObj = createFileName(obj);
let aliyunOSS = getConfig();
let fileName = fileNameObj.fileFullName;
let formData = vk.pubfn.copyObject(aliyunOSS.uploadData);
formData["key"] = fileName; // 存储在oss的文件路径
/**
* 特别说明
* 若已知本地图片,则使用formData["name"] = filePath
* 若已知base64,则是用formData["file"] = file;// file base64字符串转成blob对象
*/
if (filePath.indexOf(";base64,") > -1) {
formData["file"] = dataURLtoBlob(filePath);
} else {
formData["name"] = filePath;
}
let Logger = {};
Logger.startTime = new Date().getTime();
Logger.filePath = filePath;
return new Promise((resolve, reject) => {
let uploadTask = uni.uploadFile({
url: aliyunOSS.action,
filePath,
name,
header,
formData: formData,
success: function(res) {
if (title) vk.hideLoading();
if (![200, 201].includes(res.statusCode)) {
if (typeof obj.fail === "function") obj.fail(res);
Logger.error = res;
} else {
// 上传成功
res.fileID = fileNameObj.url;
res.url = fileNameObj.url;
Logger.result = res;
if (typeof obj.success === "function") obj.success(res);
resolve(res);
if (needSave) {
// 保存文件记录到数据库
vk.userCenter.addUploadRecord({
data: {
url: res.fileID,
name: file.name,
size: file.size,
file_id: res.fileID,
provider: "aliyun",
category_id
},
filePath,
fileType,
success: function() {
if (typeof obj.addSuccess == "function") obj.addSuccess(res);
},
fail: function(res) {
if (typeof obj.addFail === "function") obj.addFail(res);
}
});
}
}
},
fail: function(res) {
if (title) vk.hideLoading();
Logger.error = res;
if (res.errMsg && res.errMsg.indexOf('fail url not in domain list') > -1) {
vk.toast('上传域名未在白名单中');
}
if (typeof obj.fail === "function") obj.fail(res);
},
complete: function() {
let vk = getApp().globalData.vk;
let config = vk.callFunctionUtil.config;
if (config.debug) {
Logger.endTime = new Date().getTime();
Logger.runTime = (Logger.endTime - Logger.startTime);
let colorArr = config.logger.colorArr;
let colorStr = colorArr[counterNum % colorArr.length];
counterNum++;
console.log("%c--------【开始】【阿里云oss文件上传】--------", 'color: ' + colorStr + ';font-size: 12px;font-weight: bold;');
console.log("【本地文件】: ", Logger.filePath);
console.log("【返回数据】: ", Logger.result);
console.log("【预览地址】: ", Logger.result.fileID);
console.log("【上传耗时】: ", Logger.runTime, "毫秒");
console.log("【上传时间】: ", vk.pubfn.timeFormat(Logger.startTime, "yyyy-MM-dd hh:mm:ss"));
if (Logger.error) console.error("【error】:", Logger.error);
console.log("%c--------【结束】【阿里云oss文件上传】--------", 'color: ' + colorStr + ';font-size: 12px;font-weight: bold;');
}
}
});
uploadTask.onProgressUpdate((res) => {
if (res.progress > 0) {
if (typeof obj.onUploadProgress === "function") obj.onUploadProgress(res);
}
});
});
};
export default aliyunOSSUtil;
// 获取配置
function getConfig() {
let vk = getApp().globalData.vk;
let aliyunOSS = vk.getConfig("service.aliyunOSS");
let configData = {};
if (aliyunOSS && aliyunOSS.uploadData && aliyunOSS.uploadData.OSSAccessKeyId) {
try {
// 只有开启按userId分组且开启了vk的vuex功能,才可以自动按userId分组
if (aliyunOSS.groupUserId && typeof vk.getVuex === "function") {
let userInfo = vk.getVuex("$user.userInfo");
if (vk.pubfn.isNotNull(userInfo) && userInfo._id) {
aliyunOSS.dirname += `/${userInfo._id}`;
}
}
} catch (err) {}
configData = {
uploadData: {
OSSAccessKeyId: aliyunOSS.uploadData.OSSAccessKeyId,
policy: aliyunOSS.uploadData.policy,
signature: aliyunOSS.uploadData.signature,
success_action_status: 200,
key: "test.png"
},
action: aliyunOSS.action,
dirname: aliyunOSS.dirname,
host: aliyunOSS.host,
};
}
return configData;
}
// 生成文件名
function createFileName(obj = {}) {
let {
file,
filePath,
index = 0,
cloudPathRemoveChinese = true,
cloudDirectory
} = obj;
let vk = getApp().globalData.vk;
let aliyunOSS = getConfig();
let dirname = aliyunOSS.dirname;
let host = aliyunOSS.host;
let fileObj = {};
let suffix = getFileSuffix(obj);
let oldName = index + "." + suffix;
if (file && file.name) {
let suffixName = file.name.substring(file.name.lastIndexOf(".") + 1);
if (suffixName && suffixName.length < 5) oldName = file.name;
// 只保留["数字","英文",".","-"]
if (cloudPathRemoveChinese) {
oldName = oldName.replace(/[^a-zA-Z.-d]/g, '');
}
if (oldName.indexOf(".") == 0) oldName = "0" + oldName;
}
let date = new Date();
let dateYYYYMMDD = vk.pubfn.timeFormat(date, "yyyy/MM/dd");
let dateTime = date.getTime().toString(); // 时间戳
// 时间戳后8位
let dateTimeEnd8 = dateTime.substring(dateTime.length - 8, dateTime.length);
let randomNumber = vk.pubfn.random(8); // 8位随机数
// 文件路径 = 固定路径名 + 业务路径
// 业务路径
let servicePath = "";
if (cloudDirectory) {
// 如果自定义了上传目录,则使用自定义的上传目录
if (cloudDirectory.lastIndexOf("/") !== cloudDirectory.length-1) {
cloudDirectory = cloudDirectory + "/";
}
servicePath = cloudDirectory;
} else {
// 否则,使用年月日作为上传目录
servicePath = dateYYYYMMDD + "/";
}
// 文件名 = 时间戳后8位 - 随机数8位 + 后缀名
let fileNickName = dateTimeEnd8 + randomNumber + "-" + oldName;
// 文件相对路径 = 业务目录 + 文件名
let fileRelativePath = servicePath + fileNickName;
// 文件名全称(包含文件路径) = 根目录 + 文件相对路径
let fileFullName = dirname + "/" + fileRelativePath;
// 外网地址 = 外网域名 + 文件名全称
let url = host + "/" + fileFullName;
fileObj.url = url;
fileObj.fileFullName = fileFullName;
fileObj.fileNickName = fileNickName;
return fileObj;
}
function getFileSuffix(obj = {}) {
let {
file,
filePath,
suffix = "png"
} = obj;
if (filePath) {
let suffixName = filePath.substring(filePath.lastIndexOf(".") + 1);
if (suffixName && suffixName.length < 5) suffix = suffixName;
}
if (file) {
if (file.path) {
let suffixName = file.path.substring(file.path.lastIndexOf(".") + 1);
if (suffixName && suffixName.length < 5) suffix = suffixName;
}
if (file.name) {
let suffixName = file.name.substring(file.name.lastIndexOf(".") + 1);
if (suffixName && suffixName.length < 5) suffix = suffixName;
}
}
return suffix;
}
function dataURLtoBlob(dataurl) {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
@@ -0,0 +1,34 @@
let timeoutArr = [];
/**
* 防抖函数
* 防抖原理:一定时间内,只有最后一次或第一次调用,回调函数才会执行
* @param {Function} fn 要执行的回调函数
* @param {Number} time 延时的时间
* @param {Boolean} isImmediate 是否立即执行 默认true
* @param {String} timeoutName 定时器ID
* @return null
vk.pubfn.debounce(() => {
}, 1000);
*/
function debounce(fn, time = 500, isImmediate = true, timeoutName = "default") {
// 清除定时器
if(!timeoutArr[timeoutName]) timeoutArr[timeoutName] = null;
if (timeoutArr[timeoutName] !== null) clearTimeout(timeoutArr[timeoutName]);
// 立即执行一次
if (isImmediate) {
var callNow = !timeoutArr[timeoutName];
timeoutArr[timeoutName] = setTimeout(() => {
timeoutArr[timeoutName] = null;
}, time);
if (callNow){
if(typeof fn === 'function') return fn();
}
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时time毫秒后执行fn回调方法
timeoutArr[timeoutName] = setTimeout(() => {
if(typeof fn === 'function') return fn();
}, time);
}
}
export default debounce
@@ -0,0 +1,23 @@
// 判断arr是否为一个数组,返回一个bool值
function isArray (arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
// 深度克隆
function deepClone (obj) {
// 对常见的“非”值,直接返回原来值
if([null, undefined, NaN, false].includes(obj)) return obj;
if(typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for(let i in obj) {
if(obj.hasOwnProperty(i)){
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}
export default deepClone;
@@ -0,0 +1,30 @@
import deepClone from "./deepClone";
// JS对象深度合并
function deepMerge(target = {}, source = {}) {
target = deepClone(target);
if (typeof target !== 'object' || typeof source !== 'object') return false;
for (var prop in source) {
if (!source.hasOwnProperty(prop)) continue;
if (prop in target) {
if (typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
target[prop] = source[prop];
}
}
return target;
}
export default deepMerge;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,297 @@
/**
* 函数 - 弹窗
*/
const localeObj = {
title: {
"zh-Hans": "提示",
"zh-Hant": "提示",
"en": "Tips"
},
confirmText: {
"zh-Hans": "确定",
"zh-Hant": "確定",
"en": "OK"
},
cancelText: {
"zh-Hans": "取消",
"zh-Hant": "取消",
"en": "Cancel"
},
placeholderText: {
"zh-Hans": "请输入",
"zh-Hant": "請輸入",
"en": "Please enter"
}
};
export default {
/**
vk.alert("内容");
vk.alert("内容","提示","好的",function(){
});
*/
alert: function(a = " ", b, c, d) {
let vk = uni.vk;
let locale = vk.getLocale();
let obj = {
title: localeObj.title[locale],
confirmText: localeObj.confirmText[locale],
placeholderText: localeObj.placeholderText[locale],
content: a,
showCancel: false
};
if (typeof d === 'function') {
obj.title = b;
obj.confirmText = c;
obj.success = d;
} else if (typeof c === 'function') {
obj.title = b;
obj.success = c;
} else if (typeof b === 'function') {
obj.success = b;
} else if (b != undefined) {
obj.title = b;
if (c != undefined) {
obj.confirmText = c;
}
}
if (typeof obj.content === 'number') {
obj.content = obj.content + "";
} else if (typeof obj.content === 'object') {
obj.content = JSON.stringify(obj.content);
}
return uni.showModal(obj);
},
/**
vk.confirm("内容","提示","确定","取消",(res) => {
if(res.confirm){
}
});
*/
confirm: function(a, b, c, d, e) {
let vk = uni.vk;
let locale = vk.getLocale();
let obj = {
showCancel: true,
cancelColor: "#999",
title: localeObj.title[locale],
confirmText: localeObj.confirmText[locale],
cancelText: localeObj.cancelText[locale],
placeholderText: localeObj.placeholderText[locale],
};
if (typeof a === "object") {
obj = a;
} else {
if (typeof a === "string") {
obj.content = a;
}
if (typeof e === 'function') {
obj.title = b;
obj.confirmText = c;
obj.cancelText = d;
obj.success = e;
} else if (typeof d === 'function') {
obj.title = b;
obj.confirmText = c;
obj.success = d;
} else if (typeof c === 'function') {
obj.title = b;
obj.success = c;
} else if (typeof b === 'function') {
obj.success = b;
} else if (b != undefined) {
obj.title = b;
if (c != undefined) {
obj.confirmText = c;
}
}
}
return uni.showModal(obj);
},
/**
vk.prompt("请输入","提示","确定","取消",function(res){
if(res.confirm){
console.log(res.content);
}
},"输入框内初始内容");
*/
prompt: function(a, b, c, d, e, f) {
let vk = uni.vk;
let locale = vk.getLocale();
let obj = {
showCancel: true,
editable: true,
cancelColor: "#999",
title: localeObj.title[locale],
confirmText: localeObj.confirmText[locale],
cancelText: localeObj.cancelText[locale],
placeholderText: localeObj.placeholderText[locale],
};
if (typeof a === "object") {
obj = a;
} else {
if (typeof a === "string") {
obj.placeholderText = a;
}
if (typeof e === 'function') {
obj.title = b;
obj.confirmText = c;
obj.cancelText = d;
obj.success = e;
obj.content = f;
} else if (typeof d === 'function') {
obj.title = b;
obj.confirmText = c;
obj.success = d;
obj.content = e;
} else if (typeof c === 'function') {
obj.title = b;
obj.success = c;
obj.content = d;
} else if (typeof b === 'function') {
obj.success = b;
obj.content = c;
}
}
return uni.showModal(obj);
},
/**
vk.toast("提示内容","none");
*/
toast: function(a, b, c, d, e) {
let vk = uni.vk;
if (typeof a === 'number') {
a = a.toString();
} else if (typeof a === 'object') {
a = JSON.stringify(a);
}
let title = a;
let icon = "none";
let image = "";
let mask = false;
let duration = 1500;
let fn;
if (typeof e !== "undefined") {
if (typeof e == "function") fn = e;
if (typeof e == "number") duration = e;
if (typeof e == "boolean") mask = e;
}
if (typeof d !== "undefined") {
if (typeof d == "function") fn = d;
if (typeof d == "number") duration = d;
if (typeof d == "boolean") mask = d;
}
if (typeof c !== "undefined") {
if (typeof c == "function") fn = c;
if (typeof c == "number") duration = c;
if (typeof c == "boolean") mask = c;
}
if (typeof b !== "undefined") {
if (typeof b == "function") fn = b;
if (typeof b == "number") duration = b;
if (typeof b == "boolean") mask = b;
if (typeof b == 'string') {
if (b == "ok") b = "success";
if (b == "success" || b == "loading" || b == "none" || b == "error") {
icon = b;
} else {
image = b;
}
}
}
return uni.showToast({
title: title,
icon: icon,
image: image,
mask: mask,
duration: duration,
success: function(res) {
if (typeof fn === 'function') {
setTimeout(function() {
fn(res);
}, duration);
}
}
});
},
/**
* 操作菜单
vk.showActionSheet({
title:"",
list:["位置","@好友"],
color:"rgb(0, 0, 0)",
success:function(res){
if(res.index==0){
}else if(res.index==1){
}
}
});
*/
showActionSheet: function(object) {
let vk = uni.vk;
let title = object.title;
let list = object.list;
let color = object.color || "#000000";
let success = object.success;
let fail = object.fail;
let complete = object.complete;
return uni.showActionSheet({
itemList: list,
itemColor: color,
success: function(res) {
let index = res.tapIndex;
let text = list[index];
let g = { index, text };
//console.log(g);
if (typeof success == "function") success(g);
},
fail: function(res) {
//console.log(res);
if (typeof fail == "function") fail(res);
},
complete: function(res) {
if (typeof complete == "function") complete(res);
}
});
},
showLoading: function(obj) {
if (typeof obj == "string") {
let title = obj;
obj = {
title: title,
mask: true
};
}
uni.showLoading(obj);
},
hideLoading: function() {
uni.hideLoading();
},
// 设置当前页面的loading变量的值
setLoading: function(loading = true, obj = true) {
try {
let vk = uni.vk;
if (typeof obj === "boolean") {
let pages = getCurrentPages();
let page = pages[pages.length - 1];
let that = page.$vm;
that.loading = loading;
} else if (typeof obj === "object") {
let { data, name, that } = obj;
if (!data) data = that;
vk.pubfn.setData(data, name, loading);
} else if (typeof obj === "string") {
let pages = getCurrentPages();
let page = pages[pages.length - 1];
let that = page.$vm;
let name = obj;
vk.pubfn.setData(that, name, loading);
}
} catch (err) {}
},
}
@@ -0,0 +1,33 @@
function $hasRole(...args) {
const userRoles = this.$store.state.$user.userInfo.role || [];
return args.some(arg => {
if (Array.isArray(arg)) {
return arg.every(role => userRoles.includes(role));
} else {
return userRoles.includes(arg);
}
});
}
function $hasPermission(...args) {
const userPermission = this.$store.state.$user.permission || []
return args.some(arg => {
if (Array.isArray(arg)) {
return arg.every(permission => userPermission.includes(permission));
} else {
return userPermission.includes(arg);
}
});
}
export default function(Vue) {
// #ifndef VUE3
Vue.prototype.$hasRole = $hasRole;
Vue.prototype.$hasPermission = $hasPermission;
// #endif
// #ifdef VUE3
Vue.config.globalProperties.$hasRole = $hasRole;
Vue.config.globalProperties.$hasPermission = $hasPermission;
// #endif
}
@@ -0,0 +1,60 @@
/**
* 对象转url参数
* @param {*} data,对象
* @param {*} isPrefix,是否自动加上"?"
* 此函数参考uView
*/
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
let newData = JSON.parse(JSON.stringify(data));
let prefix = isPrefix ? '?' : ''
let _result = []
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets';
for (let key in newData) {
let value = newData[key]
// 去掉为空的参数
if (['', undefined, null].indexOf(value) >= 0) {
continue;
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case 'indices':
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(key + '[' + i + ']=' + value[i])
}
break;
case 'brackets':
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach(_value => {
_result.push(key + '[]=' + _value)
})
break;
case 'repeat':
// 结果: ids=1&ids=2&ids=3
value.forEach(_value => {
_result.push(key + '=' + _value)
})
break;
case 'comma':
// 结果: ids=1,2,3
let commaStr = "";
value.forEach(_value => {
commaStr += (commaStr ? "," : "") + _value;
})
_result.push(key + '=' + commaStr)
break;
default:
value.forEach(_value => {
_result.push(key + '[]=' + _value)
})
}
} else {
_result.push(key + '=' + value)
}
}
return _result.length ? prefix + _result.join('&') : ''
}
export default queryParams;
@@ -0,0 +1,51 @@
// #ifdef H5
uni.setClipboardData = function setClipboardData({
data,
success,
fail,
complete
} = {}) {
try {
const x = window.scrollX
const y = window.scrollY
const input = document.createElement('input')
input.setAttribute('readonly', 'readonly')
input.setAttribute('value', data)
input.style.position = 'absolute'
input.style.width = '100px'
input.style.left = '-10000px'
document.body.appendChild(input)
input.focus()
window.scrollTo(x, y)
input.setSelectionRange(0, input.value.length)
document.execCommand('copy')
document.body.removeChild(input)
setTimeout(() => {
const res = {
errMsg: 'setClipboardData:ok',
data
}
success && success(res)
complete && complete(res)
})
} catch (e) {
setTimeout(() => {
const res = {
errMsg: 'setClipboardData:fail'
}
fail && fail(res)
complete && complete(res)
})
}
}
// #endif
// JS对象深度合并
function setClipboardData(obj = {}) {
uni.setClipboardData({
...obj
});
}
export default setClipboardData;
@@ -0,0 +1,37 @@
let timeoutArr = [];
let flagArr = [];
/**
* 节流函数
* 节流原理:在一定时间内,只能触发一次
* @param {Function} fn 要执行的回调函数
* @param {Number} time 延时的时间
* @param {Boolean} isImmediate 是否立即执行
* @param {String} timeoutName 定时器ID
* @return null
vk.pubfn.throttle(function() {
}, 1000);
*/
function throttle(fn, time = 500, isImmediate = true, timeoutName = "default") {
if(!timeoutArr[timeoutName]) timeoutArr[timeoutName] = null;
if (isImmediate) {
if (!flagArr[timeoutName]) {
flagArr[timeoutName] = true;
// 如果是立即执行,则在time毫秒内开始时执行
if(typeof fn === 'function') fn();
timeoutArr[timeoutName] = setTimeout(() => {
flagArr[timeoutName] = false;
}, time);
}
} else {
if (!flagArr[timeoutName]) {
flagArr[timeoutName] = true;
// 如果是非立即执行,则在time毫秒内的结束处执行
timeoutArr[timeoutName] = setTimeout(() => {
flagArr[timeoutName] = false;
if(typeof fn === 'function') fn();
}, time);
}
}
};
export default throttle
@@ -0,0 +1,642 @@
/**
* 时间工具类
*/
var util = {};
util.getTargetTimezone = function(val) {
if (typeof val === "number") {
return val;
}
let vk = uni.vk;
let defaultValue = 8;
let targetTimezone = defaultValue;
try {
const config = vk.callFunctionUtil.getConfig();
targetTimezone = config.targetTimezone;
if (typeof targetTimezone !== "number") {
targetTimezone = defaultValue;
}
} catch (err) {}
return targetTimezone;
};
// 尽可能的将参数转成正确的时间对象
util.getDateObject = function(date, targetTimezone) {
if (!date) return "";
let nowDate;
// 如果是字符串,且纯数字,则强制转数值
if (typeof date === "string" && !isNaN(date) && date.length >= 10) date = Number(date);
if (typeof date === "number") {
if (date.toString().length == 10) date *= 1000;
nowDate = new Date(date); // 转时间对象
} else if (typeof date === "object") {
nowDate = new Date(date.getTime()); // 新建一个时间对象
} else if (typeof date === "string") {
targetTimezone = util.getTargetTimezone(targetTimezone);
let targetTimezoneStr = targetTimezone;
let targetTimezoneF = targetTimezone >= 0 ? "+" : "";
if (targetTimezone >= 0 && targetTimezone < 10) {
targetTimezoneStr = `0${targetTimezone}`;
} else if (targetTimezone < 0 && targetTimezone > -10) {
targetTimezoneStr = `-0${targetTimezone*-1}`;
}
let arr1 = date.split(" ");
let arr1_1 = arr1[0] || "";
let arr1_2 = arr1[1] || "";
let arr2;
if (arr1_1.indexOf("-") > -1) {
arr2 = arr1_1.split("-");
} else {
arr2 = arr1_1.split("/");
}
let arr3 = arr1_2.split(":");
let dateObj = {
year: Number(arr2[0]),
month: Number(arr2[1]) || 1,
day: Number(arr2[2]) || 1,
hour: Number(arr3[0]) || 0,
minute: Number(arr3[1]) || 0,
second: Number(arr3[2]) || 0,
};
for (let key in dateObj) {
if (dateObj[key] >= 0 && dateObj[key] < 10) dateObj[key] = `0${dateObj[key]}`;
}
// 格式 2022-10-01T00:00:00+08:00
let dateStr = `${dateObj.year}-${dateObj.month}-${dateObj.day}T${dateObj.hour}:${dateObj.minute}:${dateObj.second}${targetTimezoneF}${targetTimezoneStr}:00`;
nowDate = new Date(dateStr);
}
return nowDate;
};
// 获取时间在不同时区下的时间对象
util.getTimeByTimeZone = function(date, targetTimezone) {
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
let timezoneOffset = nowDate.getTimezoneOffset();
let offset = timezoneOffset * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let targetTime = nowDate.getTime() + offset;
nowDate = new Date(targetTime);
return nowDate;
};
/**
* 日期格式化
* @param {Date || Number} date 需要格式化的时间
* vk.pubfn.timeFormat(new Date(),"yyyy-MM-dd hh:mm:ss");
*/
util.timeFormat = function(date, fmt = 'yyyy-MM-dd hh:mm:ss', targetTimezone) {
try {
if (!date) return "";
targetTimezone = util.getTargetTimezone(targetTimezone);
let nowDate = util.getTimeByTimeZone(date, targetTimezone);
let opt = {
"M+": nowDate.getMonth() + 1, //月份
"d+": nowDate.getDate(), //日
"h+": nowDate.getHours(), //小时
"m+": nowDate.getMinutes(), //分
"s+": nowDate.getSeconds(), //秒
"q+": Math.floor((nowDate.getMonth() + 3) / 3), //季度
"S": nowDate.getMilliseconds(), //毫秒
"Z": `${targetTimezone >= 0 ? '+' : '-'}${Math.abs(targetTimezone).toString().padStart(2, '0')}:00` // 时区
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (nowDate.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (let k in opt) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (opt[k]) : (("00" + opt[k]).substr(("" + opt[k]).length)));
}
}
return fmt;
} catch (err) {
// 若格式错误,则原值显示
return time;
}
};
/**
* 解析日期对象属性
* @param {Date || Number} date 需要转换的时间
* vk.pubfn.getDateInfo(new Date());
*/
util.getDateInfo = function(date = new Date(), targetTimezone) {
let nowDate = util.getTimeByTimeZone(date, targetTimezone);
let year = nowDate.getFullYear() + '';
let month = (nowDate.getMonth() + 1 < 10 ? '0' + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1);
let day = (nowDate.getDate() < 10 ? '0' + (nowDate.getDate()) : nowDate.getDate());
let hour = (nowDate.getHours() < 10 ? '0' + (nowDate.getHours()) : nowDate.getHours());
let minute = (nowDate.getMinutes() < 10 ? '0' + (nowDate.getMinutes()) : nowDate.getMinutes());
let second = (nowDate.getSeconds() < 10 ? '0' + (nowDate.getSeconds()) : nowDate.getSeconds());
let millisecond = nowDate.getMilliseconds(); //毫秒
let week = nowDate.getDay(); // 周
let quarter = Math.floor((nowDate.getMonth() + 3) / 3); //季度
return {
year: Number(year),
month: Number(month),
day: Number(day),
hour: Number(hour),
minute: Number(minute),
second: Number(second),
millisecond: Number(millisecond),
week: Number(week),
quarter: Number(quarter),
};
};
/**
* 获取时间范围
* @param {Date} date 日期对象 可以指定时间计算节点,默认使用当前时间进行计算
* 返回的是时间戳
* 返回数据如下:
{
todayStart 今日开始时间(时间戳)
todayEnd 今日结束时间(时间戳)
today12End 今日上午结束时间(时间戳)
monthStart 本月开始时间(时间戳)
monthEnd 本月结束时间(时间戳)
yearStart 本年开始时间(时间戳)
yearEnd 本年结束时间(时间戳)
weekStart 本周开始时间(时间戳)
weekEnd 本周结束时间(时间戳)
hourStart 当前小时开始时间(时间戳)
hourEnd 当前小时结束时间(时间戳)
yesterdayStart 昨天开始时间(时间戳)
yesterday12End 昨天上午结束时间(时间戳)
yesterdayEnd 昨天结束时间(时间戳)
lastMonthStart 上月开始时间(时间戳)
lastMonthEnd 上月结束时间(时间戳)
now 现在的时间点(含月年日时分秒)
months 本年度每月的开始和结束时间 months[1] 代表1月
days 本月每天的开始和结束时间 days[1] 代表1日
}
* vk.pubfn.getCommonTime();
*/
util.getCommonTime = function(date = new Date(), targetTimezone) {
let res = {};
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
const { year, month, day, hour, minute, second, millisecond, week, quarter } = util.getDateInfo(nowDate, targetTimezone);
const oneDayTime = 24 * 60 * 60 * 1000;
// 现在的时间
res.now = {
year,
month,
day,
hour,
minute,
second,
millisecond,
week,
quarter,
date_str: util.timeFormat(nowDate, "yyyy-MM-dd hh:mm:ss", targetTimezone),
date_day_str: util.timeFormat(nowDate, "yyyy-MM-dd", targetTimezone),
date_month_str: util.timeFormat(nowDate, "yyyy-MM", targetTimezone),
};
// 获取本月最大天数
let month_last_day = new Date(year, month, 0).getDate();
// 获取今年12月最大天数
let year_last_day = new Date(year, 12, 0).getDate();
// 今日开始时间
res.todayStart = new Date(`${year}/${month}/${day}`).getTime() - timeDif;
// 今日12点时间
res.today12End = new Date(`${year}/${month}/${day}`).getTime() + (12 * 60 * 60 * 1000 - 1) - timeDif;
// 今日结束时间
res.todayEnd = new Date(`${year}/${month}/${day}`).getTime() + (oneDayTime - 1) - timeDif;
// 本月开始时间
res.monthStart = new Date(`${year}/${month}/1`).getTime() - timeDif;
// 本月结束时间
res.monthEnd = new Date(`${year}/${month}/${month_last_day}`).getTime() + (oneDayTime - 1) - timeDif;
// 本年开始时间
res.yearStart = new Date(`${year}/1/1`).getTime() - timeDif;
// 本年结束时间
res.yearEnd = new Date(`${year}/12/${year_last_day}`).getTime() + (oneDayTime - 1) - timeDif;
// 当前小时开始时间
res.hourStart = new Date(`${year}/${month}/${day} ${hour}:00:00`).getTime() - timeDif;
// 当前小时结束时间
res.hourEnd = new Date(`${year}/${month}/${day} ${hour}:59:59`).getTime() - timeDif;
// 计算上月开始-----------------------------------------------------------
let year_last = year;
let month_last = month - 1;
if (month_last === 0) {
month_last = 12;
year_last = year - 1;
}
let month_last_day_last = new Date(year_last, month_last, 0).getDate();
// 上月开始时间
res.lastMonthStart = new Date(`${year_last}/${month_last}/1`).getTime() - timeDif;
// 上月结束时间
res.lastMonthEnd = new Date(`${year_last}/${month_last}/${month_last_day_last}`).getTime() + (oneDayTime - 1) - timeDif;
// 计算上月结束-----------------------------------------------------------
// 昨天开始时间
res.yesterdayStart = res.todayStart - 1000 * 3600 * 24;
// 昨天上午结束时间
res.yesterday12End = res.today12End - 1000 * 3600 * 24;
// 昨天结束时间
res.yesterdayEnd = res.todayEnd - 1000 * 3600 * 24;
let weekObj = util.getWeekOffsetStartAndEnd(0, nowDate, targetTimezone);
// 本周开始时间
res.weekStart = weekObj.startTime;
// 本周结束时间
res.weekEnd = weekObj.endTime;
// 本年1-12月的起止时间
res.months = [];
res.months[0] = {
startTime: res.monthStart,
endTime: res.monthEnd,
startTimeStr: util.timeFormat(res.monthStart, "yyyy-MM-dd hh:mm:ss", targetTimezone),
endTimeStr: util.timeFormat(res.monthEnd, "yyyy-MM-dd hh:mm:ss", targetTimezone),
monthStart: res.monthStart, // 兼容旧版
monthEnd: res.monthEnd, // 兼容旧版
};
for (let i = 1; i <= 12; i++) {
// 获取此月最大天数
let month_last_day = new Date(year, i, 0).getDate();
// 此月开始时间
let startTime = new Date(`${year}/${i}/1`).getTime() - timeDif;
// 此月结束时间
let endTime = new Date(`${year}/${i}/${month_last_day}`).getTime() + (oneDayTime - 1) - timeDif;
res.months[i] = {
startTime,
endTime,
startTimeStr: util.timeFormat(startTime, "yyyy-MM-dd hh:mm:ss", targetTimezone),
endTimeStr: util.timeFormat(endTime, "yyyy-MM-dd hh:mm:ss", targetTimezone),
monthStart: startTime, // 兼容旧版
monthEnd: endTime, // 兼容旧版
};
}
// 本月1号到最后一天每天的起止时间
res.days = [];
res.days[0] = {
startTime: res.todayStart,
endTime: res.todayEnd,
startTimeStr: util.timeFormat(res.todayStart, "yyyy-MM-dd hh:mm:ss", targetTimezone),
endTimeStr: util.timeFormat(res.todayEnd, "yyyy-MM-dd hh:mm:ss", targetTimezone)
};
for (let i = 1; i <= month_last_day; i++) {
let nowTime = res.monthStart + ((i - 1) * oneDayTime);
let { startTime, endTime } = util.getDayOffsetStartAndEnd(0, nowTime, targetTimezone);
res.days[i] = {
startTime,
endTime,
startTimeStr: util.timeFormat(startTime, "yyyy-MM-dd hh:mm:ss", targetTimezone),
endTimeStr: util.timeFormat(endTime, "yyyy-MM-dd hh:mm:ss", targetTimezone)
};
}
for (let key in res) {
if (typeof res[key] === "number" && res[key].toString().length === 13) {
res[`${key}Str`] = util.timeFormat(res[key], "yyyy-MM-dd hh:mm:ss", targetTimezone);
}
}
return res;
};
/**
* 获得指定年份和月份后的该月的开始时间和结束时间
* 返回数据如下:(时间戳形式)
{
startTime 该月开始时间
endTime 该月结束时间
}
vk.pubfn.getMonthStartAndEnd({
year:2021
month:1
});
*/
util.getMonthStartAndEnd = function(obj, targetTimezone) {
targetTimezone = util.getTargetTimezone(targetTimezone);
let res = {
startTime: null,
endTime: null
};
let { year, month } = obj;
if (year > 0 && month > 0) {
const dif = new Date().getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let month_last_day = new Date(year, month, 0).getDate();
// 开始时间
res.startTime = new Date(`${year}/${month}/1`).getTime() - timeDif;
// 结束时间
res.endTime = new Date(`${year}/${month}/${month_last_day}`).getTime() + (24 * 60 * 60 * 1000 - 1) - timeDif;
}
return res;
}
/**
* 获得相对当前时间的偏移 count 小时的起止日期(返回小时的开始和结束时间戳)
* @param {Number} count 默认0(0代表当前小时 -1代表上一个小时 1代表下一个小时以此类推)
* @param {Date || Number} date 指定从哪个时间节点开始计算
* vk.pubfn.getHourOffsetStartAndEnd(0);
*/
util.getHourOffsetStartAndEnd = function(count = 0, date = new Date(), targetTimezone) {
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
let res = {};
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
// 一天的毫秒数
let offsetMillisecond = 1000 * 60 * 60;
// 相对于当前日期count个天的日期
nowDate = new Date(nowDate.getTime() + (offsetMillisecond * 1 * count));
let dateInfo = util.getDateInfo(nowDate);
// 获得当天的起始时间
res.startTime = new Date(`${dateInfo.year}/${dateInfo.month}/${dateInfo.day} ${dateInfo.hour}:00:00`).getTime() - timeDif;
// 获得当天的结束时间
res.endTime = new Date(`${dateInfo.year}/${dateInfo.month}/${dateInfo.day} ${dateInfo.hour}:00:00`).getTime() + (offsetMillisecond - 1) - timeDif;
return res;
}
/**
* 获得相对当前时间的偏移 count 天的起止日期(返回日的开始和结束)
* @param {Number} count 默认0(0代表今日 -1代表昨日 1代表明日以此类推)
* @param {Date || Number} date 指定从哪个时间节点开始计算
* vk.pubfn.getDayOffsetStartAndEnd(0);
*/
util.getDayOffsetStartAndEnd = function(count = 0, date = new Date(), targetTimezone) {
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
let res = {};
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
// 一天的毫秒数
let offsetMillisecond = 1000 * 60 * 60 * 24;
// 相对于当前日期count个天的日期
nowDate = new Date(nowDate.getTime() + (offsetMillisecond * 1 * count));
let dateInfo = util.getDateInfo(nowDate);
// 获得当天的起始时间
res.startTime = new Date(`${dateInfo.year}/${dateInfo.month}/${dateInfo.day}`).getTime() - timeDif;
// 获得当天的结束时间
res.endTime = new Date(`${dateInfo.year}/${dateInfo.month}/${dateInfo.day}`).getTime() + (offsetMillisecond - 1) - timeDif;
return res;
}
/**
* 获得相对当前周count个周的起止日期(返回周的开始和结束)
* @param {Number} count 默认0(0代表本周 -1代表上周 1代表下周以此类推)
* vk.pubfn.getWeekOffsetStartAndEnd(0);
*/
util.getWeekOffsetStartAndEnd = function(count = 0, date = new Date(), targetTimezone) {
let res = {};
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let nowDay = nowDate.getDay() === 0 ? 7 : nowDate.getDay();
nowDate.setDate(nowDate.getDate() - nowDay + 1 + count * 7);
let dateInfo1 = util.getDateInfo(nowDate);
nowDate.setDate(nowDate.getDate() + 7);
let dateInfo2 = util.getDateInfo(nowDate);
// 开始时间
res.startTime = new Date(`${dateInfo1.year}/${dateInfo1.month}/${dateInfo1.day}`).getTime() - timeDif;
// 结束时间
res.endTime = new Date(`${dateInfo2.year}/${dateInfo2.month}/${dateInfo2.day}`).getTime() - 1 - timeDif;
return res;
}
/**
* 获得相对当前时间的偏移 count 个月的起止日期(月的开始和结束时间戳)
* @param {Number} count 默认0(0代表本月 -1代表上月 1代表下月以此类推)
* @param {Date || Number} date 指定从哪个时间节点开始计算
* vk.pubfn.getMonthOffsetStartAndEnd(0);
*/
util.getMonthOffsetStartAndEnd = function(count = 0, date = new Date(), targetTimezone) {
let res = {};
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let dateInfo = util.getDateInfo(nowDate);
let month = dateInfo.month + count;
let year = dateInfo.year;
if (month > 12) {
year = year + Math.floor(month / 12);
month = Math.abs(month) % 12;
} else if (month <= 0) {
year = year - 1 - Math.floor(Math.abs(month) / 12);
month = 12 - Math.abs(month) % 12;
}
let month_last_day = new Date(year, month, 0).getDate();
// 开始时间
res.startTime = new Date(`${year}/${month}/1`).getTime() - timeDif;
// 结束时间
res.endTime = new Date(`${year}/${month}/${month_last_day}`).getTime() + (24 * 60 * 60 * 1000 - 1) - timeDif;
return res;
}
/**
* 获得相对当前时间的偏移 count 个季度的起止日期(季度的开始和结束时间戳)
* @param {Number} count 默认0(0代表本季度 -1代表上个季度 1代表下个季度以此类推)
* @param {Date || Number} date 指定从哪个时间节点开始计算
* vk.pubfn.getQuarterOffsetStartAndEnd(0);
*/
util.getQuarterOffsetStartAndEnd = function(count = 0, date = new Date(), targetTimezone) {
let res = {};
let nowDate = util.getDateObject(date);
nowDate.setMonth(nowDate.getMonth() + count * 3);
targetTimezone = util.getTargetTimezone(targetTimezone);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let dateInfo = util.getDateInfo(nowDate);
let month = dateInfo.month;
if ([1, 2, 3].indexOf(month) > -1) {
// 第1季度
month = 1;
} else if ([4, 5, 6].indexOf(month) > -1) {
// 第2季度
month = 4;
} else if ([7, 8, 9].indexOf(month) > -1) {
// 第3季度
month = 7;
} else if ([10, 11, 12].indexOf(month) > -1) {
// 第4季度
month = 10;
}
nowDate.setMonth(month - 1); // 因为0代表1月,所以这里要减1
let dateInfo1 = util.getDateInfo(nowDate);
nowDate.setMonth(nowDate.getMonth() + 3);
let dateInfo2 = util.getDateInfo(nowDate);
// 开始时间
res.startTime = new Date(`${dateInfo1.year}/${dateInfo1.month}/1`).getTime() - timeDif;
// 结束时间
res.endTime = new Date(`${dateInfo2.year}/${dateInfo2.month}/1`).getTime() - 1 - timeDif;
return res;
}
/**
* 获得相对当前时间的偏移 count 年的起止日期(年的开始和结束时间戳)
* @param {Number} count 默认0 (0代表今年 -1代表去年 1代表明年以此类推)
* @param {Date || Number} date 指定从哪个时间节点开始计算
* vk.pubfn.getYearOffsetStartAndEnd(0);
*/
util.getYearOffsetStartAndEnd = function(count = 0, date = new Date(), targetTimezone) {
let res = {};
let nowDate = util.getDateObject(date);
targetTimezone = util.getTargetTimezone(targetTimezone);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let dateInfo = util.getDateInfo(nowDate);
let year = dateInfo.year + count;
// 开始时间
res.startTime = new Date(`${year}/1/1`).getTime() - timeDif;
// 结束时间
res.endTime = new Date(`${year}/12/31`).getTime() + (24 * 60 * 60 * 1000 - 1) - timeDif;
return res;
};
/**
* 获得指定时间偏移 year年 month月 day天 hours时 minutes分 seconds秒前或后的时间戳
* 返回时间戳
vk.pubfn.getOffsetTime(new Date(), {
year:0,
month:0,
day:0,
hours:0,
minutes:0,
seconds:0,
mode:"after", // after 之后 before 之前
});
vk.pubfn.getOffsetTime(new Date(), {
y:0,
m:0,
d:0,
hh:0,
mm:0,
ss:0,
mode:"after" // after 之后 before 之前
});
*/
util.getOffsetTime = function(date = new Date(), obj, targetTimezone) {
let nowDate = util.getDateObject(date);
let dateInfo = util.getDateInfo(nowDate);
targetTimezone = util.getTargetTimezone(targetTimezone);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
let year = obj.year || obj.y || 0;
let month = obj.month || obj.m || 0;
let day = obj.day || obj.d || 0;
let hour = obj.hour || obj.hours || obj.hh || 0;
let minute = obj.minute || obj.minutes || obj.mm || 0;
let second = obj.second || obj.seconds || obj.ss || 0;
let { mode = "after" } = obj;
if (mode == "before") {
year *= -1;
month *= -1;
day *= -1;
hour *= -1;
minute *= -1;
second *= -1;
}
let offsetObj = {
year: dateInfo.year + year,
month: dateInfo.month + month,
day: dateInfo.day + day,
hour: dateInfo.hour + hour,
minute: dateInfo.minute + minute,
second: dateInfo.second + second
};
nowDate = new Date(offsetObj.year, offsetObj.month - 1, offsetObj.day, offsetObj.hour, offsetObj.minute, offsetObj.second);
return nowDate.getTime() - timeDif;
}
/**
* 判断是否是闰年
* @param {Number | Date} year 需要计算的年份或时间,默认使用当前时间的年份
* vk.pubfn.timeUtil.isLeapYear(year);
*/
util.isLeapYear = function(year) {
if (typeof year === "undefined") {
let { now } = util.getCommonTime();
year = now.year;
} else if (typeof year === "object") {
let { now } = util.getCommonTime(year);
year = now.year;
}
if (((year % 4) == 0) && ((year % 100) != 0) || ((year % 400) == 0)) {
return true;
} else {
return false;
}
}
/**
* 判断是否是清明节
* @param {Date} date 日期对象 可以指定时间计算节点,默认使用当前时间进行计算
vk.pubfn.timeUtil.isQingming(new Date());
*/
util.isQingming = function(data = new Date()) {
let { now } = util.getCommonTime(data);
let { year, month, day } = now;
let key = false;
// 清明节的日期是不固定的,规律是:闰年开始的前2年是4月4日,闰年开始的第3年和第4年是4月5日
if (util.isLeapYear(year) || util.isLeapYear(year - 1)) {
if (month === 4 && day === 4) {
key = true;
}
} else {
if (month === 4 && day === 5) {
key = true;
}
}
return key;
}
/**
* 日期对象转换(已弃用,建议不要使用此API)
* @param {Date || Number} date 需要转换的时间
* @param {Number} type 转换方式
* type = 0 返回 2020-08-03 12:12:12
* type = 1 返回 20200803121212
* type = 2 返回 { YYYY, MM, DD, hh, mm, ss }
*/
util.getFullTime = function(date, type = 0, targetTimezone) {
if (!date) return "";
targetTimezone = util.getTargetTimezone(targetTimezone);
let nowDate = util.getDateObject(date);
const dif = nowDate.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
const east8time = nowDate.getTime() + timeDif;
nowDate = new Date(east8time);
let YYYY = nowDate.getFullYear() + '';
let MM = (nowDate.getMonth() + 1 < 10 ? '0' + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1);
let DD = (nowDate.getDate() < 10 ? '0' + (nowDate.getDate()) : nowDate.getDate());
let hh = (nowDate.getHours() < 10 ? '0' + (nowDate.getHours()) : nowDate.getHours());
let mm = (nowDate.getMinutes() < 10 ? '0' + (nowDate.getMinutes()) : nowDate.getMinutes());
let ss = (nowDate.getSeconds() < 10 ? '0' + (nowDate.getSeconds()) : nowDate.getSeconds());
if (type === 2) {
return {
YYYY: Number(YYYY),
MM: Number(MM),
DD: Number(DD),
hh: Number(hh),
mm: Number(mm),
ss: Number(ss),
year: Number(YYYY),
month: Number(MM),
day: Number(DD),
hour: Number(hh),
minute: Number(mm),
second: Number(ss),
};
} else if (type === 1) {
return YYYY + "" + MM + "" + DD + "" + hh + "" + mm + "" + ss;
} else {
return YYYY + "-" + MM + "-" + DD + " " + hh + ":" + mm + ":" + ss;
}
};
export default util;
@@ -0,0 +1,93 @@
import deepClone from './deepClone'
var util = {};
/**
* 将树形结构转成数组结构
* @param {Array} treeData 数据源
* @param {Object} treeProps 树结构配置 { id : "menu_id", children : "children" }
* vk.pubfn.treeToArray(treeData);
*/
util.treeToArray = function(treeData, treeProps) {
let newTreeData = deepClone(treeData);
return util.treeToArrayFn(newTreeData, treeProps);
};
util.treeToArrayFn = function(treeData, treeProps = {}, arr=[], current_parent_id) {
let { id="_id", parent_id="parent_id", children = "children", deleteChildren = true } = treeProps;
for(let i in treeData){
let item = treeData[i];
if(current_parent_id) item[parent_id] = current_parent_id;
arr.push(item);
if(item[children] && item[children].length>0){
arr = util.treeToArrayFn(item[children], treeProps, arr, item[id]);
}
if(deleteChildren){
delete item[children];
}
}
return arr;
};
/**
* 数组结构转树形结构
let tree = vk.pubfn.arrayToTree(arrayData,{
id:"code",
parent_id:"parent_code",
});
*/
util.arrayToTree = function(originalArrayData,treeProps) {
let arrayData = deepClone(originalArrayData);
let {
id="_id",
parent_id="parent_id",
children = "children",
deleteParentId = false,
need_field
} = treeProps;
let result = [];
let temp = {};
for (let i = 0; i < arrayData.length; i++) {
temp[arrayData[i][id]] = arrayData[i]; // 以id作为索引存储元素,可以无需遍历直接定位元素
}
for (let j = 0; j < arrayData.length; j++) {
let currentElement = arrayData[j];
let newCurrentElement = {};
if(need_field){
need_field = uniqueArr(need_field.concat([id,parent_id,children]));
for(let keyName in currentElement){
if(need_field.indexOf(keyName) === -1){
delete currentElement[keyName];
}
}
}
let tempCurrentElementParent = temp[currentElement[parent_id]]; // 临时变量里面的当前元素的父元素
if (tempCurrentElementParent) {
// 如果存在父元素
if (!tempCurrentElementParent[children]) {
// 如果父元素没有chindren键
tempCurrentElementParent[children] = []; // 设上父元素的children键
}
if(deleteParentId){
delete currentElement[parent_id];
}
tempCurrentElementParent[children].push(currentElement); // 给父元素加上当前元素作为子元素
} else {
// 不存在父元素,意味着当前元素是一级元素
result.push(currentElement);
}
}
return result;
};
// 最简单数组去重法
function uniqueArr(array) {
let n = []; //一个新的临时数组
//遍历当前数组
for (let i = 0; i < array.length; i++) {
//如果当前数组的第i已经保存进了临时数组,那么跳过,
//否则把当前项push到临时数组里面
if (n.indexOf(array[i]) == -1) n.push(array[i]);
}
return n;
}
export default util;
@@ -0,0 +1,61 @@
var updateManager = {};
/**
* 本API返回全局唯一的版本更新管理器对象: updateManager,用于管理小程序更新。
* vk.updateManager.updateReady();
*/
updateManager.updateReady = function(obj) {
// #ifdef MP
updateManagerByMP(obj);
// #endif
};
export default updateManager;
function updateManagerByMP(obj = {}) {
let {
title = "更新提示",
content = "新版本已经准备好,点击更新!",
autoUpdate = true,
showCancel = false,
confirmText = "一键更新"
} = obj;
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate(function(res) {
// 请求完新版本信息的回调
// console.log(res.hasUpdate);
});
updateManager.onUpdateReady(function(res) {
uni.showModal({
title,
content,
showCancel,
confirmText,
success(res) {
if (res.confirm) {
if (typeof obj.success === "function") {
obj.success({
applyUpdate: updateManager.applyUpdate
});
}
if (typeof obj.complete === "function") obj.complete();
if (autoUpdate) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
}
},
});
});
updateManager.onUpdateFailed(function(res) {
// 新的版本下载失败
console.error("onUpdateFailed", res);
if (typeof obj.fail === "function") obj.fail(res);
if (typeof obj.complete === "function") obj.complete();
});
}
@@ -0,0 +1,63 @@
/**
* vk.filters
* 全局过滤器
*/
import pubfn from './index.js'
var util = {};
// 将时间显示成1秒前、1天前
util.dateDiff = function(starttime, showType) {
return pubfn.dateDiff(starttime, showType);
}
util.dateDiff2 = function(starttime, showType) {
return pubfn.dateDiff2(starttime, showType);
};
// 计数器1
util.numStr = function(n) {
return pubfn.numStr(n);
};
util.timeStr = function(date) {
return pubfn.timeFormat(date);
};
/**
* 日期对象转字符串
* @description 最终转成 2020-08-01 12:12:12
* @param {Date || Number} date 需要转换的时间
* date参数支持
* 支持:时间戳
* 支持:2020-08
* 支持:2020-08-24
* 支持:2020-08-24 12
* 支持:2020-08-24 12:12
* 支持:2020-08-24 12:12:12
*/
util.timeFilter = function(date, fmt) {
return pubfn.timeFormat(date, fmt);
};
// 金额过滤器
util.priceFilter = function(n, defaultValue = " - ") {
return pubfn.priceFilter(n, defaultValue);
};
// 金额过滤器 - 只显示小数点左边
util.priceLeftFilter = function(n) {
return pubfn.priceLeftFilter(n);
};
// 金额过滤器 - 只显示小数点右边
util.priceRightFilter = function(n) {
return pubfn.priceRightFilter(n);
};
// 百分比过滤器
util.percentageFilter = function(n, needShowSymbol, defaultValue = " - ") {
return pubfn.percentageFilter(n, needShowSymbol, defaultValue);
};
// 折扣过滤器
util.discountFilter = function(n, needShowSymbol, defaultValue = " - ") {
return pubfn.discountFilter(n, needShowSymbol, defaultValue);
};
// 大小过滤器 sizeFilter(1024,3,["B","KB","MB","GB"])
util.sizeFilter = function(...obj) {
let res = pubfn.calcSize(...obj);
return res.title;
};
export default util;
@@ -0,0 +1,60 @@
// #ifdef H5
/**
* H5专用的一些函数,会涉及到window等H5专用的内置对象。
*/
var h5 = {};
// 引入 微信公众号 js_sdk 包
import wx from './weixin-js-sdk'
h5.wx = wx;
/**
* 获取当前H5所在的环境
* let env = vk.h5.getEnv();
*/
h5.getEnv = function() {
let ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger' && (ua.match(/miniprogram/i) == 'miniprogram')) {
// 微信小程序
return "mp-weixin";
}
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
// 微信公众号
return "h5-weixin";
}
if (ua.match(/alipay/i) == 'alipay' && ua.match(/miniprogram/i) == 'miniprogram') {
return "mp-alipay";
}
if (ua.match(/alipay/i) == 'alipay') {
return "h5-alipay";
}
// 外部 H5
return "h5";
};
/**
* 获取当前项目根目录访问地址
* https://www.aaa.com/pages/index/index?a=1&b=1
* http://192.168.66.66:8080/pages/index/index?a=1&b=1
* 获取当前H5的url地址的https://www.aaa.com 部分
* let getRootPath = vk.h5.getRootPath();
* let getRootPath = vk.h5.getRootPath({ projectName:true });
*/
h5.getRootPath = function(obj={}) {
let {
projectName: _projectName = false
} = obj;
let wwwPath = window.location.href;
let pathName = window.location.pathname;
let pos = wwwPath.indexOf(pathName);
let localhostPaht = wwwPath.substring(0, pos);
let projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
let rootPath = localhostPaht;
if (_projectName) {
rootPath = localhostPaht + projectName;
}
return rootPath;
};
export default h5;
// #endif
@@ -0,0 +1,59 @@
/**
* 导出云对象实例
* @param {String} name 云对象路径,如:client/pub
* @example const pubObject = uni.vk.importObject('client/pub'); // 导入云对象
* 注意,只能在声明 async 的函数内使用,如:
async test(){
let res = await pubObject.getList({
title: "请求中",
data: {
a: 1,
b: "2"
}
});
}
*/
var importObject = function(name, importObjectOptions = {}) {
const newObj = new Proxy(importObject, {
get: function(target, key, receiver) {
/**
* 导出云对象内的某个方法
* @param {Object} data 请求参数,如 { a:1, b:"2" } 云对象内可通过 let { a, b } = data; 获取参数
* @param {String} title 遮罩层提示语,为空或不传则代表不显示遮罩层。
* @param {Boolean} needAlert 为true代表请求错误时,会有弹窗提示。默认为true
* @param {Object} loading 与title二选一,格式为 { name: "loading", that: that } name是变量名,that是数据源,当发起请求时,自动that[name] = true; 请求结束后,自动that[name] = false;
*/
return async function(options) {
// 如果importObjectOptions中指定了easy为true,代表options的值就是请求参数
if (importObjectOptions.easy) {
options = {
data: options
}
}
// 如果importObjectOptions中指定了data,代表有默认请求参数,需要加到请求参数中
if (importObjectOptions.data) {
if (typeof importObjectOptions.data === "function") {
options.data = Object.assign({}, importObjectOptions.data(), options.data)
} else {
options.data = Object.assign({}, importObjectOptions.data, options.data)
}
}
return uni.vk.callFunction({
...importObjectOptions,
...options,
url: `${name}.${key}`
});
}
},
// set: function(target, key, value, receiver) {
// console.log("set");
// console.log("target",target);
// console.log("key",key);
// console.log("value",value);
// console.log("receiver", receiver);
// },
});
return newObj;
};
export default importObject;
@@ -0,0 +1,131 @@
/**
* vk依赖扩展 - localStorage 本地缓存
* pub - 公共的可随时删除的缓存
* kh - 跟登录账号有关联的缓存
* sys - 系统级缓存 - 一般不删除
*
* 储存缓存
* vk.setStorageSync(key, data);
* 获取缓存
* vk.getStorageSync(key);
*/
var localStorage = {};
/**
* 将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。
* @param {String} key 本地缓存中的指定的 key
* @param {Any} data 需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象
*/
localStorage.setStorageSync = function(key, data) {
uni.setStorageSync(key, data);
watchLocalStorage({ type: "set", key, data });
};
/**
* 从本地缓存中异步获取指定 key 对应的内容。
* @param {String} key 本地缓存中的指定的 key
*/
localStorage.getStorageSync = function(key) {
let data = uni.getStorageSync(key);
// get 没有必要监听
// watchLocalStorage({ type:"get", key, data });
return data;
};
/**
* 同步获取当前 storage 的相关信息。
*/
localStorage.getStorageInfoSync = function() {
let info = uni.getStorageInfoSync();
let sizeInfo = calcSize(Number(info.currentSize), 1024, 3, ["KB", "MB", "GB"]);
info.sizeInfo = sizeInfo;
return info;
};
/**
* 从本地缓存中同步移除指定 key。
* @param {String} key 本地缓存中的指定的 key
*/
localStorage.removeStorageSync = function(key) {
uni.removeStorageSync(key);
watchLocalStorage({ type: "remove", key });
};
/**
* 从本地缓存中异步移除指定 key。
*/
localStorage.removeStorage = function(obj) {
uni.removeStorage({
key: obj.key,
success: (res) => {
watchLocalStorage({ type: "remove", key: obj.key });
if (obj.success) obj.success(res);
},
fail: obj.fail,
complete: obj.complete
});
};
/**
* 同步清理本地数据缓存。若key有值,则清除键值为指定字符串开头的缓存
* @param {String} key 本地缓存中的指定的 key
* vk.clearStorageSync();
*/
localStorage.clearStorageSync = function(key) {
if (key) {
let { keys } = uni.getStorageInfoSync();
if (keys) {
keys.map((keyName) => {
if (keyName.indexOf(key) == 0) {
localStorage.removeStorage({
key: keyName
});
}
});
}
} else {
uni.clearStorage();
watchLocalStorage({ type: "clear" });
}
};
export default localStorage;
/**
* 单位进制换算
* length 换算前大小
* ary 进制,如KB-MB-GB,进制1024
* precision 数值精度
* arr 进制的数组,如["B","KB","MB","GB"]
* calcSize(length,1024,3,["B","KB","MB","GB"]);
*/
function calcSize(length = 0, ary, precision, arr) {
var size = parseFloat(length);
var mySize = 0;
var type = "";
if (size < ary || arr.length <= 1) {
type = arr[0];
mySize = parseFloat(size.toFixed(precision));
} else {
for (var i = 1; i < arr.length; i++) {
var g = arr[i];
size = size / ary;
if (size < ary) {
type = g;
mySize = parseFloat(size.toFixed(precision));
break;
}
}
}
return {
size: mySize,
type: type,
title: mySize + " " + type
}
};
function watchLocalStorage(obj) {
if (typeof localStorage.watch === "function") {
localStorage.watch(obj);
}
}
@@ -0,0 +1,462 @@
/**
* 函数 - 页面导航
*/
// #ifndef VUE3
var config;
try {
config = require('@/app.config.js');
if (typeof config.default === "object") {
config = config.default;
}
} catch (e) {
config = {};
}
// #endif
// #ifdef VUE3
import config from '@/app.config.js'
// #endif
var util = {};
var lastNavigate = {
url: "",
time: 0
};
/**
* 保留当前页面,跳转到应用内的某个页面,使用vk.navigateBack可以返回到原页面。
* vk.navigateTo(url);
*/
util.navigateTo = function(obj) {
let vk = uni.vk;
if (typeof obj == "string") {
let url = obj;
obj = {
url: url
};
} else if (typeof obj == "undefined") {
obj = {};
}
if (!obj.url) {
vk.toast("url不能为空!");
return false;
}
let time = Date.now();
if (lastNavigate.url === obj.url && (time - lastNavigate.time) < 200) {
return false;
}
lastNavigate = { url: obj.url, time };
util.checkNeedLogin({
url: obj.url,
success: function(res) {
if (res.needLogin) {
obj.url = vk.pubfn.getPageFullPath(obj.url);
vk.navigate.setOriginalPage(obj);
obj.url = config.login.url;
// login拦截器开始-----------------------------------------------------------
let { interceptor = {} } = config;
if (typeof interceptor.login === "function") {
let key = interceptor.login({
vk,
params: obj,
res: {
...res,
code: 30204,
msg: "本地token校验未通过"
}
});
if (typeof key === "undefined" || key !== true) return false;
}
// login拦截器结束-----------------------------------------------------------
} else {
vk.navigate.setOriginalPage(null);
}
util._navigateTo(obj);
}
});
};
util._navigateTo = function(obj) {
let { interceptor = {} } = config;
if (typeof interceptor.navigateTo === "function") {
let vk = uni.vk;
obj.pagePath = vk.pubfn.getPageFullPath(obj.url);
let key = interceptor.navigateTo({
...obj,
vk
});
if (typeof key == "boolean" && key === false) return false;
}
let {
url,
animationType = "pop-in",
animationDuration = 300,
events,
mode = "navigateTo"
} = obj;
// 此处写法仅为支持vue3,vue3不支持uni[apiName]的形式调用
let navigateFn;
if (mode === "navigateTo") {
navigateFn = uni.navigateTo;
} else if (mode === "redirectTo") {
navigateFn = uni.redirectTo;
} else if (mode === "reLaunch") {
navigateFn = uni.reLaunch;
} else if (mode === "switchTab") {
navigateFn = uni.switchTab;
} else {
navigateFn = uni.navigateTo;
}
// 此处写法仅为支持vue3,vue3不支持uni[apiName]的形式调用
navigateFn({
url: url,
animationType: animationType,
animationDuration: animationDuration,
events: events, // 参考 https://uniapp.dcloud.io/api/router?id=navigateto
success: function(res) {
if (typeof obj.success == "function") obj.success(res);
},
fail: function(err) {
if (err.errMsg.indexOf("not found") > -1) {
let vk = uni.vk;
let errUrl = vk.pubfn.getPageFullPath(url);
vk.toast(`页面 ${errUrl} 不存在`, "none");
console.error(err);
return false;
}
uni.switchTab({
url: url,
success: obj.success,
fail: function() {
uni.redirectTo({
url: url,
success: obj.success,
fail: function(err) {
console.error(err);
if (typeof obj.fail == "function") obj.fail(err);
}
});
}
});
},
complete: function(res) {
if (typeof obj.complete == "function") obj.complete(res);
}
});
};
/**
* 关闭当前页面,跳转到应用内的某个页面。
* vk.redirectTo(url);
*/
util.redirectTo = function(obj) {
obj = util.paramsInit(obj);
obj.mode = "redirectTo";
util.navigateTo(obj);
};
/**
* 关闭所有页面,打开到应用内的某个页面。
* vk.reLaunch(url);
*/
util.reLaunch = function(obj) {
obj = util.paramsInit(obj);
obj.mode = "reLaunch";
util.navigateTo(obj);
};
/**
* 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。
* vk.switchTab(url);
*/
util.switchTab = function(obj) {
obj = util.paramsInit(obj);
obj.mode = "switchTab";
util.navigateTo(obj);
};
/**
* 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。
* vk.navigateBack();
*/
util.navigateBack = function(obj) {
let vk = uni.vk;
if (typeof obj == "number") {
let delta = obj;
obj = {
delta: delta
};
} else if (typeof obj == "undefined") {
obj = {};
}
let {
delta = 1,
animationType = "pop-out",
animationDuration = 300
} = obj;
uni.navigateBack({
delta: delta,
animationType: animationType,
animationDuration: animationDuration,
success: function() {
if (typeof obj.success == "function") obj.success();
},
fail: function(res) {
console.error(res);
if (typeof obj.fail == "function") obj.fail();
},
complete: function() {
if (typeof obj.complete == "function") obj.complete();
}
});
};
/**
* 跳转到登录前的页面
* vk.navigate.originalTo();
*/
util.originalTo = function() {
let vk = uni.vk;
let originalPage = vk.navigate.getOriginalPage();
vk.navigate.setOriginalPage(null);
util.redirectTo(originalPage);
};
/**
* 获取登录前的页面
* vk.navigate.getOriginalPage();
*/
util.getOriginalPage = function() {
if (typeof uni.vk.getVuex === "function") {
// 有安装vuex则使用vuex
return uni.vk.getVuex('$app.originalPage');
} else {
// 未安装则使用本地缓存
return uni.vk.getStorageSync('vk.navigate.originalPage');
}
};
/**
* 设置登录前的页面
* vk.navigate.setOriginalPage(originalPage);
*/
util.setOriginalPage = function(originalPage) {
uni.vk.navigate.originalPage = originalPage; // 兼容老版本
if (typeof uni.vk.getVuex === "function") {
// 有安装vuex则使用vuex
return uni.vk.setVuex('$app.originalPage', originalPage);
} else {
// 未安装则使用本地缓存
return uni.vk.setStorageSync('vk.navigate.originalPage', originalPage);
}
};
/**
* 跳转到首页
* vk.navigateToHome();
*/
util.navigateToHome = function(obj = {}) {
let vk = uni.vk;
let {
mode = "reLaunch"
} = obj;
vk[mode](config.index.url);
};
/**
* 跳转到登录页
* vk.navigateToLogin();
*/
util.navigateToLogin = function(obj = {}) {
let vk = uni.vk;
let {
mode = "reLaunch"
} = obj;
vk[mode](config.login.url);
};
/**
* 检测是否满足条件(内部方法)
util.checkWildcardTest({
url:url,
pagesRule:config.checkTokenPages,
success:function(res){
if(res.success){
}
}
})
*/
util.checkWildcardTest = function(obj) {
let vk = uni.vk;
let {
url,
pagesRule
} = obj;
// ../ 转成绝对路径
url = vk.pubfn.getPageFullPath(url);
let key = false;
if (vk.pubfn.isNotNull(pagesRule)) {
let { mode = 0, list = [] } = pagesRule;
if (mode > 0) {
let regExpKey = false;
let path = util.getPagePath(url);
for (let i = 0; i < list.length; i++) {
let pageRegExp = list[i];
regExpKey = vk.pubfn.wildcardTest(path, pageRegExp);
if (regExpKey) {
break;
}
}
if (mode === 1 && regExpKey) {
key = true;
} else if (mode === 2 && !regExpKey) {
key = true;
}
}
}
return {
url,
key
};
};
/**
* 检测是否需要登录(内部方法)
util.checkNeedLogin({
url:url,
success:function(res){
if(res.needLogin){
}
}
})
*/
util.checkNeedLogin = function(obj) {
let vk = uni.vk;
let { url, success } = obj;
let needLogin = false; // 用户是否需要重新登录
let pageNeedLogin = false; // 该页面是否需要登录才能访问
let pagesRule = config.checkTokenPages;
if (pagesRule) {
let res = util.checkWildcardTest({
url,
pagesRule
});
pageNeedLogin = res.key;
if (pageNeedLogin) {
// 本地判断token有效期(联网会浪费性能)
if (!vk.checkToken()) {
needLogin = true;
}
}
}
success({
url,
needLogin,
pageNeedLogin
});
};
// 获取?前面的地址
util.getPagePath = function(url) {
let pathIndex = url.indexOf("?");
let path = url;
if (pathIndex > -1) {
path = path.substring(0, pathIndex);
}
return path;
};
util.paramsInit = function(obj) {
let vk = uni.vk;
if (typeof obj == "string") {
let url = obj;
obj = {
url: url
};
} else if (typeof obj == "undefined") {
obj = {};
}
if (!obj.url) {
vk.toast("url不能为空!");
return false;
}
return obj;
}
/**
* 跳转到小程序
vk.navigateToMiniProgram({
appId: 'appId',
path: 'pages/index/index',
extraData:{
//发送数据携带的参数
},
success(res) {
// 打开成功
}
})
*/
util.navigateToMiniProgram = function(obj) {
let vk = uni.vk;
// #ifdef H5
vk.toast("不支持打开小程序", "none");
// #endif
// #ifndef H5
uni.navigateToMiniProgram(obj);
// #endif
};
/**
* 检测是否可以分享(内部方法)
util.checkAllowShare({
url:url,
});
*/
util.checkAllowShare = function(obj) {
let vk = uni.vk;
let { url, success } = obj;
let pagesRule = config.checkSharePages || {};
if (pagesRule && pagesRule.mode > 0) {
let res = util.checkWildcardTest({
url,
pagesRule
});
// #ifdef MP
let menus = pagesRule.menus || ['shareAppMessage', 'shareTimeline'];
if (res.key) {
//console.log("允许分享");
uni.showShareMenu({
withShareTicket: false,
menus
});
} else {
//console.log("禁止分享");
uni.hideShareMenu({
menus
})
}
// #endif
}
};
util.$emit = function(...obj) {
return uni.$emit(...obj);
};
util.$on = function(...obj) {
return uni.$on(...obj);
};
util.$once = function(...obj) {
return uni.$once(...obj);
};
util.$off = function(...obj) {
return uni.$off(...obj);
};
export default util;
@@ -0,0 +1,434 @@
var requestUtil = {};
requestUtil.config = {
// 请求配置
request: {
// 公共请求参数(每次请求都会带上的参数)
dataParam: {}
},
requestGlobalParamKeyName: "vk_url_request_global_param",
debug: process.env.NODE_ENV !== "production",
// 日志风格
logger: {
colorArr: [
"#0095ff",
"#67C23A"
]
},
};
var counterNum = 0;
/**
* request 请求库
* 注意: 该请求库目前主要是框架自身使用,且近期变动频率较高,目前请勿使用在你自己的业务中。
* @param {String} url 服务器接口地址
* @param {Object/String/ArrayBuffer} data 请求的参数 App(自定义组件编译模式)不支持ArrayBuffer类型
* @param {Object} header 设置请求的 header,header 中不能设置 Referer。 App、H5端会自动带上cookie,且H5端不可手动修改
* @param {String} method 默认 POST 可选 GET
* @param {Number} timeout 超时时间,单位 ms 默认3000(30秒)
* @param {String} dataType 默认json 如果设为 json,会尝试对返回的数据做一次 JSON.parse
* @param {String} responseType 默认 text 设置响应的数据类型。合法值:text、arraybuffer App和支付宝小程序不支持
* @param {Boolean} sslVerify 默认 true 是否需要验证ssl证书 仅App安卓端支持(HBuilderX 2.3.3+) 仅App安卓端支持(HBuilderX 2.3.3+)
* @param {Boolean} withCredentials 默认 false 跨域请求时是否携带凭证(cookies) 仅H5支持(HBuilderX 2.6.15+)
* @param {Boolean} firstIpv4 默认 false DNS解析时优先使用ipv4 仅 App-Android 支持 (HBuilderX 2.8.0+)
* @param {String} success 成功回调
* @param {String} fail 失败回调
* @param {String} complete 完成回调
* 以下为特殊参数
* @param {String} errorCodeName 服务器返回的错误码的字段名,若不为空,则会对返回结果进行判断
* @param {String} errorMsgName 服务器返回的错误码的字符串含义,若不为空,且errorCodeName对应的值不为0,则会alert弹窗
* @param {Boolean} needAlert 服务器返回的错误时,是否需要alert弹出提示
*
* 调用示例
vk.request({
url: `https://www.xxx.com/api/xxxx`,
method:"POST",
header:{
"content-type": "application/json; charset=utf-8",
},
data:{
},
success: function(data){
},
fail: function(err){
}
});
*/
requestUtil.request = function(obj = {}) {
let vk = uni.vk;
// 去除值为 undefined 的参数
if (typeof obj.data === "object") {
obj.data = vk.pubfn.copyObject(obj.data);
}
// 注入自定义全局参数开始-----------------------------------------------------------
let config = requestUtil.config;
let globalParam = uni.getStorageSync(config.requestGlobalParamKeyName) || {};
// 根据正则规格自动匹配全局请求参数
for (let i in globalParam) {
let customDate = globalParam[i];
if (customDate.regExp) {
if (typeof customDate.regExp === "object") {
// 数组形式
for (let i = 0; i < customDate.regExp.length; i++) {
let regExp = new RegExp(customDate.regExp[i]);
if (regExp.test(obj.url)) {
obj.data = Object.assign(customDate.data, obj.data);
break;
}
}
} else {
// 字符串形式
let regExp = new RegExp(customDate.regExp);
if (regExp.test(obj.url)) {
obj.data = Object.assign(customDate.data, obj.data);
}
}
}
}
// 根据指定globalParamName匹配全局请求参数
let customDate = requestUtil.getRequestGlobalParam(obj.globalParamName);
if (customDate && JSON.stringify(customDate) !== "{}") {
if (customDate.regExp) {
obj.data = Object.assign(customDate.data, obj.data); // 新版本
} else {
obj.data = Object.assign(customDate, obj.data); // 兼容旧版本
}
}
// 注入自定义全局参数结束-----------------------------------------------------------
if (!obj.method) obj.method = "POST"; // 默认POST请求
if (typeof obj.dataType === "undefined") obj.dataType = "json";
if (obj.dataType == "default" || obj.dataType === "") delete obj.dataType;
if (typeof obj.header === "undefined" && typeof obj.headers !== "undefined") {
obj.header = obj.headers;
}
// 自动注入token到请求头开始-----------------------------------------------------------
// 注意:自2.15.1开始,需要手动指定uniIdToken: true 才会自动添加token到请求头里
if (typeof vk.getToken === "function" && obj.uniIdToken) {
let uni_id_token;
if (obj.uniIdToken === true) {
uni_id_token = vk.getToken();
} else if (typeof obj.uniIdToken === "boolean") {
uni_id_token = obj.uniIdToken;
}
if (uni_id_token) {
if (!obj.header) obj.header = {};
obj.header["uni-id-token"] = uni_id_token;
}
}
// 自动注入token到请求头结束-----------------------------------------------------------
let interceptor = obj.interceptor;
delete obj.interceptor;
if (interceptor && typeof interceptor.invoke === "function") {
let interceptorRes = interceptor.invoke(obj);
if (interceptorRes === false) {
return;
}
}
if (typeof obj.timeout === "undefined") obj.timeout = 30000; // 超时时间,单位 ms(默认30秒)
let Logger = {};
if (config.debug) {
Logger.params = (typeof obj.data == "object") ? vk.pubfn.copyObject(obj.data) : obj.data;
Logger.startTime = new Date().getTime();
let url = obj.url;
let path = obj.url.split("?")[0];
if (path.indexOf("://") > -1) {
path = path.substring(path.indexOf("://") + 3);
Logger.domainName = path.substring(0, path.indexOf("/"));
} else {
Logger.domainName = "";
}
Logger.action = path.substring(path.indexOf("/") + 1);
Logger.url = url;
}
if (obj.title) vk.showLoading(obj.title);
if (obj.loading) vk.setLoading(true, obj.loading);
let promiseAction = new Promise(function(resolve, reject) {
uni.request({
...obj,
success: (res) => {
if (interceptor && typeof interceptor.success === "function") {
let interceptorRes = interceptor.success(res);
if (interceptorRes === false) {
return;
}
}
requestSuccess({
res,
params: obj,
Logger,
resolve,
reject
});
},
fail: (res) => {
if (interceptor && typeof interceptor.fail === "function") {
let interceptorRes = interceptor.fail(res);
if (interceptorRes === false) {
return;
}
}
requestFail({
res,
params: obj,
Logger,
reject
});
},
complete: (res) => {
if (interceptor && typeof interceptor.complete === "function") {
let interceptorRes = interceptor.complete(res);
if (interceptorRes === false) {
return;
}
}
requestComplete({
res,
params: obj,
Logger
});
}
});
});
promiseAction.catch(error => {});
return promiseAction;
}
// 请求成功回调
function requestSuccess(obj = {}) {
let vk = uni.vk;
let config = requestUtil.config;
let {
res = {},
params,
Logger,
resolve,
reject
} = obj;
let {
title,
needOriginalRes,
dataType,
errorCodeName,
errorMsgName,
success,
loading
} = params;
let data = res.data || {};
if (vk.pubfn.isNotNullAll(errorCodeName, data[errorCodeName])) {
data.code = data[errorCodeName];
delete data[errorCodeName];
}
if (vk.pubfn.isNotNullAll(errorMsgName, data[errorMsgName])) {
data.msg = data[errorMsgName];
if (typeof data[errorMsgName] === "string") {
delete data[errorMsgName];
}
}
if (config.debug) Logger.result = (typeof data == "object") ? vk.pubfn.copyObject(data) : data;
if ([1301, 1302, 30201, 30202, 30203, 30204].indexOf(data.code) > -1 && data.msg.indexOf("token") > -1) {
// 执行 login 拦截器函数(跳转到页面页面)
let { interceptor = {} } = vk.callFunctionUtil;
if (typeof interceptor.login === "function") {
interceptor.login({
res: data,
params,
vk
});
}
reject(data);
} else if (res.statusCode >= 400 || data.code) {
requestFail({
res: data,
params,
Logger,
reject
});
} else {
if (title) vk.hideLoading();
if (loading) vk.setLoading(false, loading);
if (needOriginalRes) data.originalRes = vk.pubfn.copyObject(res);
if (data.vk_uni_token) vk.callFunctionUtil.saveToken(data.vk_uni_token);
if (data.userInfo && data.needUpdateUserInfo) vk.callFunctionUtil.updateUserInfo(data);
if (typeof success === "function") success(data);
if (typeof resolve === "function") resolve(data);
}
}
// 请求失败回调
function requestFail(obj = {}) {
let vk = uni.vk;
let config = requestUtil.config;
let {
res = {},
params,
Logger,
reject
} = obj;
let {
title,
needAlert,
fail,
loading
} = params;
if (typeof needAlert === "undefined") {
needAlert = (typeof fail === "function") ? false : true;
}
let errMsg = "";
let sysErr = false;
if (typeof res.code !== "undefined") {
errMsg = res.msg;
} else {
sysErr = true;
errMsg = JSON.stringify(res);
}
if (errMsg.indexOf("fail timeout") > -1) {
sysErr = true;
errMsg = "请求超时,请重试!";
}
if (config.debug) Logger.error = res;
if (title) vk.hideLoading();
if (loading) vk.setLoading(false, loading);
let runKey = true;
// 自定义拦截器开始-----------------------------------------------------------
let { interceptor = {} } = vk.callFunctionUtil.getConfig();
if (interceptor.request && typeof interceptor.request.fail == "function") {
runKey = interceptor.request.fail({
vk,
res: res,
params: params
});
if (runKey === undefined) runKey = true;
}
// 自定义拦截器结束-----------------------------------------------------------
if (runKey) {
if (needAlert && vk.pubfn.isNotNull(errMsg)) {
if (sysErr) {
vk.toast("网络开小差了!", "none");
} else {
vk.alert(errMsg);
}
}
if (typeof fail === "function") fail(res);
if (typeof reject === "function") reject(res);
}
}
// 请求完成回调
function requestComplete(obj = {}) {
let vk = uni.vk;
let config = requestUtil.config;
let {
res = {},
params,
Logger
} = obj;
let {
title,
needOriginalRes,
complete
} = params;
if (config.debug) {
Logger.endTime = new Date().getTime();
Logger.runTime = (Logger.endTime - Logger.startTime);
let colorArr = config.logger.colorArr;
let colorStr = colorArr[counterNum % colorArr.length];
counterNum++;
console.log("%c--------【开始】【服务器请求】【" + Logger.action + "】--------", 'color: ' + colorStr + ';font-size: 12px;font-weight: bold;');
console.log("【请求地址】: ", Logger.url);
console.log("【请求参数】: ", Logger.params);
console.log("【返回数据】: ", Logger.result);
console.log("【请求状态】: ", res.statusCode, "【http状态码】");
console.log("【总体耗时】: ", Logger.runTime, "毫秒【含页面渲染】");
console.log("【请求时间】: ", vk.pubfn.timeFormat(Logger.startTime, "yyyy-MM-dd hh:mm:ss"));
if (Logger.error) {
let errorLog = console.warn || console.error;
if (Logger.error.err && Logger.error.err.stack) {
console.error("【Error】: ", Logger.error);
console.error("【Stack】: ", Logger.error.err.stack);
} else {
errorLog("【Error】: ", Logger.error);
}
}
console.log("%c--------【结束】【服务器请求】【" + Logger.action + "】--------", 'color: ' + colorStr + ';font-size: 12px;font-weight: bold;');
}
let data = res.data;
if (needOriginalRes) data.originalRes = vk.pubfn.copyObject(res);
if (typeof complete === "function") complete(data);
}
/**
* 修改请求配置中的公共请求参数
* 若把shop-manage改成*则代表全局
vk.requestUtil.updateRequestGlobalParam({
"shop-manage":{
regExp:"^xxx/kh/",
data:{
shop_id : shop_id
}
}
});
对应的request中增加参数globalParamName:"shop-manage"
vk.request({
url: 'xxx/xxxxxx',
title: '请求中...',
globalParamName:"shop-manage",// 如果设置了正则规则,则不需要此参数
data: {},
success(data) {
}
});
*/
requestUtil.updateRequestGlobalParam = (data = {}, setKey) => {
let vk = uni.vk;
let config = requestUtil.config;
if (setKey) {
// 覆盖对象
config.request.dataParam = data;
} else {
// 覆盖参数(有就覆盖,没有则新增)
config.request.dataParam = Object.assign(config.request.dataParam, data);
}
vk.setStorageSync(config.requestGlobalParamKeyName, config.request.dataParam);
}
/**
* 获取请求配置中的公共请求参数
vk.requestUtil.getRequestGlobalParam();
*/
requestUtil.getRequestGlobalParam = (globalParamName = "*") => {
let vk = uni.vk;
let config = requestUtil.config;
let data = config.request.dataParam;
if (!data || JSON.stringify(data) === "{}") {
data = uni.getStorageSync(config.requestGlobalParamKeyName) || {};
config.request.dataParam = data;
}
let param = data[globalParamName] || {};
return JSON.parse(JSON.stringify(param));
}
/**
* 删除请求配置中的公共请求参数
* globalParamName 不传代表删除所有
vk.requestUtil.deleteRequestGlobalParam(globalParamName);
*/
requestUtil.deleteRequestGlobalParam = (globalParamName) => {
let vk = uni.vk;
let config = requestUtil.config;
let globalParam = uni.getStorageSync(config.requestGlobalParamKeyName) || {};
if (globalParamName) {
delete globalParam[globalParamName];
} else {
globalParam = {};
}
config.request.dataParam = globalParam;
vk.setStorageSync(config.requestGlobalParamKeyName, globalParam);
}
export default requestUtil;
@@ -0,0 +1,146 @@
/**
* vk依赖扩展 - sessionStorage 本地会话缓存(仅H5可用)
* sessionStorage 属性允许你访问一个,对应当前源的 session Storage 对象。
* 它与 localStorage 相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置
* 而存储在 sessionStorage 里面的数据在页面会话结束时会被清除。
* pub - 公共的可随时删除的缓存
* kh - 跟登录账号有关联的缓存
* sys - 系统级缓存 - 一般不删除
*
* 储存缓存
* vk.setSessionStorageSync(key, data);
* 获取缓存
* vk.getSessionStorageSync(key);
*/
var storage = {};
/**
* 将 data 存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容,这是一个同步接口。
* @param {String} key 本地缓存中的指定的 key
* @param {Any} data 需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象
* vk.setSessionStorageSync(key, data);
*/
storage.setSessionStorageSync = function(key, data = "") {
// #ifdef H5
let type = typeof data;
let value = {
type: typeof data,
data: data
};
sessionStorage.setItem(key, JSON.stringify(value));
watchSessionStorage({ type: "set", key, data });
// #endif
// #ifndef H5
console.warn("非H5环境不支持此API");
// #endif
};
/**
* 从本地缓存中异步获取指定 key 对应的内容。
* @param {String} key 本地缓存中的指定的 key
* vk.getSessionStorageSync(key);
*/
storage.getSessionStorageSync = function(key) {
// #ifdef H5
let data;
try {
let value = JSON.parse(sessionStorage.getItem(key));
data = value.data;
} catch (err) {
data = sessionStorage.getItem(key);
}
// get 没有必要监听
// watchSessionStorage({ type:"get", key, data });
return data;
// #endif
// #ifndef H5
console.warn("非H5环境不支持此API");
// #endif
};
/**
* 从本地缓存中同步移除指定 key。
* @param {String} key 本地缓存中的指定的 key
* vk.removeSessionStorageSync(key);
*/
storage.removeSessionStorageSync = function(key) {
// #ifdef H5
sessionStorage.removeItem(key);
watchSessionStorage({ type: "remove", key });
// #endif
// #ifndef H5
console.warn("非H5环境不支持此API");
// #endif
};
/**
* 同步清理本地数据缓存。若key有值,则清除键值为指定字符串开头的缓存
* @param {String} key 本地缓存中的指定的 key
* vk.clearSessionStorageSync();
*/
storage.clearSessionStorageSync = function(key) {
// #ifdef H5
if (key) {
let keys = Object.keys(sessionStorage);
if (keys) {
keys.map((keyName) => {
if (keyName.indexOf(key) == 0) {
sessionStorage.removeItem(keyName);
}
});
}
} else {
sessionStorage.clear();
watchSessionStorage({ type: "clear" });
}
// #endif
// #ifndef H5
console.warn("非H5环境不支持此API");
// #endif
};
export default storage;
/**
* 单位进制换算
* length 换算前大小
* ary 进制,如KB-MB-GB,进制1024
* precision 数值精度
* arr 进制的数组,如["B","KB","MB","GB"]
* calcSize(length,1024,3,["B","KB","MB","GB"]);
*/
function calcSize(length = 0, ary, precision, arr) {
var size = parseFloat(length);
var mySize = 0;
var type = "";
if (size < ary || arr.length <= 1) {
type = arr[0];
mySize = parseFloat(size.toFixed(precision));
} else {
for (var i = 1; i < arr.length; i++) {
var g = arr[i];
size = size / ary;
if (size < ary) {
type = g;
mySize = parseFloat(size.toFixed(precision));
break;
}
}
}
return {
size: mySize,
type: type,
title: mySize + " " + type
}
};
function watchSessionStorage(obj) {
if (typeof storage.watch === "function") {
storage.watch(obj);
}
}
@@ -0,0 +1,890 @@
function n(o, e) {
if (!o.jWeixin) {
var n,
c = {
config: "preVerifyJSAPI",
onMenuShareTimeline: "menu:share:timeline",
onMenuShareAppMessage: "menu:share:appmessage",
onMenuShareQQ: "menu:share:qq",
onMenuShareWeibo: "menu:share:weiboApp",
onMenuShareQZone: "menu:share:QZone",
previewImage: "imagePreview",
getLocation: "geoLocation",
openProductSpecificView: "openProductViewWithPid",
addCard: "batchAddCard",
openCard: "batchViewCard",
chooseWXPay: "getBrandWCPayRequest",
openEnterpriseRedPacket: "getRecevieBizHongBaoRequest",
startSearchBeacons: "startMonitoringBeacons",
stopSearchBeacons: "stopMonitoringBeacons",
onSearchBeacons: "onBeaconsInRange",
consumeAndShareCard: "consumedShareCard",
openAddress: "editAddress"
},
a = (function() {
var e = {};
for (var n in c) e[c[n]] = n;
return e;
})(),
i = o.document,
t = i.title,
r = navigator.userAgent.toLowerCase(),
s = navigator.platform.toLowerCase(),
d = !(!s.match("mac") && !s.match("win")),
u = -1 != r.indexOf("wxdebugger"),
l = -1 != r.indexOf("micromessenger"),
p = -1 != r.indexOf("android"),
f = -1 != r.indexOf("iphone") || -1 != r.indexOf("ipad"),
m = (n =
r.match(/micromessenger\/(\d+\.\d+\.\d+)/) ||
r.match(/micromessenger\/(\d+\.\d+)/)) ?
n[1] :
"",
g = {
initStartTime: L(),
initEndTime: 0,
preVerifyStartTime: 0,
preVerifyEndTime: 0
},
h = {
version: 1,
appId: "",
initTime: 0,
preVerifyTime: 0,
networkType: "",
isPreVerifyOk: 1,
systemType: f ? 1 : p ? 2 : -1,
clientVersion: m,
url: encodeURIComponent(location.href)
},
v = {},
S = { _completes: [] },
y = { state: 0, data: {} };
O(function() {
g.initEndTime = L();
});
var I = !1,
_ = [],
w = {
config: function(e) {
B("config", (v = e));
var t = !1 !== v.check;
O(function() {
if (t)
M(
c.config, {
verifyJsApiList: C(v.jsApiList),
verifyOpenTagList: C(v.openTagList)
},
(function() {
(S._complete = function(e) {
(g.preVerifyEndTime = L()), (y.state = 1), (y.data = e);
}),
(S.success = function(e) {
h.isPreVerifyOk = 0;
}),
(S.fail = function(e) {
S._fail ? S._fail(e) : (y.state = -1);
});
var t = S._completes;
return (
t.push(function() {
!(function() {
if (
!(
d ||
u ||
v.debug ||
m < "6.0.2" ||
h.systemType < 0
)
) {
var i = new Image();
(h.appId = v.appId),
(h.initTime = g.initEndTime - g
.initStartTime),
(h.preVerifyTime =
g.preVerifyEndTime - g
.preVerifyStartTime),
w.getNetworkType({
isInnerInvoke: !0,
success: function(e) {
h.networkType =
e
.networkType;
var n =
"https://open.weixin.qq.com/sdk/report?v=" +
h.version +
"&o=" +
h
.isPreVerifyOk +
"&s=" +
h
.systemType +
"&c=" +
h
.clientVersion +
"&a=" +
h.appId +
"&n=" +
h
.networkType +
"&i=" +
h.initTime +
"&p=" +
h
.preVerifyTime +
"&u=" +
h.url;
i.src = n;
}
});
}
})();
}),
(S.complete = function(e) {
for (var n = 0, i = t.length; n < i; ++n) t[n]
();
S._completes = [];
}),
S
);
})()
),
(g.preVerifyStartTime = L());
else {
y.state = 1;
for (var e = S._completes, n = 0, i = e.length; n < i; ++n)
e[n]();
S._completes = [];
}
}),
w.invoke ||
((w.invoke = function(e, n, i) {
o.WeixinJSBridge && WeixinJSBridge.invoke(e, x(n), i);
}),
(w.on = function(e, n) {
o.WeixinJSBridge && WeixinJSBridge.on(e, n);
}));
},
ready: function(e) {
0 != y.state ? e() : (S._completes.push(e), !l && v.debug && e());
},
error: function(e) {
m < "6.0.2" || (-1 == y.state ? e(y.data) : (S._fail = e));
},
checkJsApi: function(e) {
M(
"checkJsApi", { jsApiList: C(e.jsApiList) },
((e._complete = function(e) {
if (p) {
var n = e.checkResult;
n && (e.checkResult = JSON.parse(n));
}
e = (function(e) {
var n = e.checkResult;
for (var i in n) {
var t = a[i];
t && ((n[t] = n[i]), delete n[i]);
}
return e;
})(e);
}),
e)
);
},
onMenuShareTimeline: function(e) {
P(
c.onMenuShareTimeline, {
complete: function() {
M(
"shareTimeline", {
title: e.title || t,
desc: e.title || t,
img_url: e.imgUrl || "",
link: e.link || location.href,
type: e.type || "link",
data_url: e.dataUrl || ""
},
e
);
}
},
e
);
},
onMenuShareAppMessage: function(n) {
P(
c.onMenuShareAppMessage, {
complete: function(e) {
"favorite" === e.scene ?
M("sendAppMessage", {
title: n.title || t,
desc: n.desc || "",
link: n.link || location.href,
img_url: n.imgUrl || "",
type: n.type || "link",
data_url: n.dataUrl || ""
}) :
M(
"sendAppMessage", {
title: n.title || t,
desc: n.desc || "",
link: n.link || location.href,
img_url: n.imgUrl || "",
type: n.type || "link",
data_url: n.dataUrl || ""
},
n
);
}
},
n
);
},
onMenuShareQQ: function(e) {
P(
c.onMenuShareQQ, {
complete: function() {
M(
"shareQQ", {
title: e.title || t,
desc: e.desc || "",
img_url: e.imgUrl || "",
link: e.link || location.href
},
e
);
}
},
e
);
},
onMenuShareWeibo: function(e) {
P(
c.onMenuShareWeibo, {
complete: function() {
M(
"shareWeiboApp", {
title: e.title || t,
desc: e.desc || "",
img_url: e.imgUrl || "",
link: e.link || location.href
},
e
);
}
},
e
);
},
onMenuShareQZone: function(e) {
P(
c.onMenuShareQZone, {
complete: function() {
M(
"shareQZone", {
title: e.title || t,
desc: e.desc || "",
img_url: e.imgUrl || "",
link: e.link || location.href
},
e
);
}
},
e
);
},
updateTimelineShareData: function(e) {
M(
"updateTimelineShareData", { title: e.title, link: e.link, imgUrl: e.imgUrl },
e
);
},
updateAppMessageShareData: function(e) {
M(
"updateAppMessageShareData", {
title: e.title,
desc: e.desc,
link: e.link,
imgUrl: e
.imgUrl
},
e
);
},
startRecord: function(e) {
M("startRecord", {}, e);
},
stopRecord: function(e) {
M("stopRecord", {}, e);
},
onVoiceRecordEnd: function(e) {
P("onVoiceRecordEnd", e);
},
playVoice: function(e) {
M("playVoice", { localId: e.localId }, e);
},
pauseVoice: function(e) {
M("pauseVoice", { localId: e.localId }, e);
},
stopVoice: function(e) {
M("stopVoice", { localId: e.localId }, e);
},
onVoicePlayEnd: function(e) {
P("onVoicePlayEnd", e);
},
uploadVoice: function(e) {
M(
"uploadVoice", {
localId: e.localId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
},
e
);
},
downloadVoice: function(e) {
M(
"downloadVoice", {
serverId: e.serverId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
},
e
);
},
translateVoice: function(e) {
M(
"translateVoice", {
localId: e.localId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
},
e
);
},
chooseImage: function(e) {
M(
"chooseImage", {
scene: "1|2",
count: e.count || 9,
sizeType: e.sizeType || ["original", "compressed"],
sourceType: e.sourceType || ["album", "camera"]
},
((e._complete = function(e) {
if (p) {
var n = e.localIds;
try {
n && (e.localIds = JSON.parse(n));
} catch (e) {}
}
}),
e)
);
},
getLocation: function(e) {},
previewImage: function(e) {
M(c.previewImage, { current: e.current, urls: e.urls }, e);
},
uploadImage: function(e) {
M(
"uploadImage", {
localId: e.localId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
},
e
);
},
downloadImage: function(e) {
M(
"downloadImage", {
serverId: e.serverId,
isShowProgressTips: 0 == e.isShowProgressTips ? 0 : 1
},
e
);
},
getLocalImgData: function(e) {
!1 === I ?
((I = !0),
M(
"getLocalImgData", { localId: e.localId },
((e._complete = function(e) {
if (((I = !1), 0 < _.length)) {
var n = _.shift();
wx.getLocalImgData(n);
}
}),
e)
)) :
_.push(e);
},
getNetworkType: function(e) {
M(
"getNetworkType", {},
((e._complete = function(e) {
e = (function(e) {
var n = e.errMsg;
e.errMsg = "getNetworkType:ok";
var i = e.subtype;
if ((delete e.subtype, i)) e.networkType = i;
else {
var t = n.indexOf(":"),
o = n.substring(t + 1);
switch (o) {
case "wifi":
case "edge":
case "wwan":
e.networkType = o;
break;
default:
e.errMsg = "getNetworkType:fail";
}
}
return e;
})(e);
}),
e)
);
},
openLocation: function(e) {
M(
"openLocation", {
latitude: e.latitude,
longitude: e.longitude,
name: e.name || "",
address: e.address || "",
scale: e.scale || 28,
infoUrl: e.infoUrl || ""
},
e
);
},
getLocation: function(e) {
M(
c.getLocation, { type: (e = e || {}).type || "wgs84" },
((e._complete = function(e) {
delete e.type;
}),
e)
);
},
hideOptionMenu: function(e) {
M("hideOptionMenu", {}, e);
},
showOptionMenu: function(e) {
M("showOptionMenu", {}, e);
},
closeWindow: function(e) {
M("closeWindow", {}, (e = e || {}));
},
hideMenuItems: function(e) {
M("hideMenuItems", { menuList: e.menuList }, e);
},
showMenuItems: function(e) {
M("showMenuItems", { menuList: e.menuList }, e);
},
hideAllNonBaseMenuItem: function(e) {
M("hideAllNonBaseMenuItem", {}, e);
},
showAllNonBaseMenuItem: function(e) {
M("showAllNonBaseMenuItem", {}, e);
},
scanQRCode: function(e) {
M(
"scanQRCode", {
needResult: (e = e || {}).needResult || 0,
scanType: e.scanType || ["qrCode", "barCode"]
},
((e._complete = function(e) {
if (f) {
var n = e.resultStr;
if (n) {
var i = JSON.parse(n);
e.resultStr = i && i.scan_code && i.scan_code.scan_result;
}
}
}),
e)
);
},
openAddress: function(e) {
M(
c.openAddress, {},
((e._complete = function(e) {
e = (function(e) {
return (
(e.postalCode = e.addressPostalCode),
delete e.addressPostalCode,
(e.provinceName = e.proviceFirstStageName),
delete e.proviceFirstStageName,
(e.cityName = e.addressCitySecondStageName),
delete e.addressCitySecondStageName,
(e.countryName = e.addressCountiesThirdStageName),
delete e.addressCountiesThirdStageName,
(e.detailInfo = e.addressDetailInfo),
delete e.addressDetailInfo,
e
);
})(e);
}),
e)
);
},
openProductSpecificView: function(e) {
M(
c.openProductSpecificView, {
pid: e.productId,
view_type: e.viewType || 0,
ext_info: e.extInfo
},
e
);
},
addCard: function(e) {
for (var n = e.cardList, i = [], t = 0, o = n.length; t < o; ++t) {
var r = n[t],
a = { card_id: r.cardId, card_ext: r.cardExt };
i.push(a);
}
M(
c.addCard, { card_list: i },
((e._complete = function(e) {
var n = e.card_list;
if (n) {
for (var i = 0, t = (n = JSON.parse(n)).length; i < t; ++i) {
var o = n[i];
(o.cardId = o.card_id),
(o.cardExt = o.card_ext),
(o.isSuccess = !!o.is_succ),
delete o.card_id,
delete o.card_ext,
delete o.is_succ;
}
(e.cardList = n), delete e.card_list;
}
}),
e)
);
},
chooseCard: function(e) {
M(
"chooseCard", {
app_id: v.appId,
location_id: e.shopId || "",
sign_type: e.signType || "SHA1",
card_id: e.cardId || "",
card_type: e.cardType || "",
card_sign: e.cardSign,
time_stamp: e.timestamp + "",
nonce_str: e.nonceStr
},
((e._complete = function(e) {
(e.cardList = e.choose_card_info), delete e.choose_card_info;
}),
e)
);
},
openCard: function(e) {
for (var n = e.cardList, i = [], t = 0, o = n.length; t < o; ++t) {
var r = n[t],
a = { card_id: r.cardId, code: r.code };
i.push(a);
}
M(c.openCard, { card_list: i }, e);
},
consumeAndShareCard: function(e) {
M(
c.consumeAndShareCard, { consumedCardId: e.cardId, consumedCode: e.code },
e
);
},
chooseWXPay: function(e) {
M(c.chooseWXPay, V(e), e);
},
openEnterpriseRedPacket: function(e) {
M(c.openEnterpriseRedPacket, V(e), e);
},
startSearchBeacons: function(e) {
M(c.startSearchBeacons, { ticket: e.ticket }, e);
},
stopSearchBeacons: function(e) {
M(c.stopSearchBeacons, {}, e);
},
onSearchBeacons: function(e) {
P(c.onSearchBeacons, e);
},
openEnterpriseChat: function(e) {
M(
"openEnterpriseChat", { useridlist: e.userIds, chatname: e.groupName },
e
);
},
launchMiniProgram: function(e) {
M(
"launchMiniProgram", {
targetAppId: e.targetAppId,
path: (function(e) {
if ("string" == typeof e && 0 < e.length) {
var n = e.split("?")[0],
i = e.split("?")[1];
return (n += ".html"), void 0 !== i ? n + "?" + i : n;
}
})(e.path),
envVersion: e.envVersion
},
e
);
},
openBusinessView: function(e) {
M(
"openBusinessView", {
businessType: e.businessType,
queryString: e.queryString || "",
envVersion: e.envVersion
},
((e._complete = function(n) {
if (p) {
var e = n.extraData;
if (e)
try {
n.extraData = JSON.parse(e);
} catch (e) {
n.extraData = {};
}
}
}),
e)
);
},
miniProgram: {
navigateBack: function(e) {
(e = e || {}),
O(function() {
M(
"invokeMiniProgramAPI", {
name: "navigateBack",
arg: {
delta: e
.delta || 1
}
},
e
);
});
},
navigateTo: function(e) {
O(function() {
M(
"invokeMiniProgramAPI", { name: "navigateTo", arg: { url: e.url } },
e
);
});
},
redirectTo: function(e) {
O(function() {
M(
"invokeMiniProgramAPI", { name: "redirectTo", arg: { url: e.url } },
e
);
});
},
switchTab: function(e) {
O(function() {
M(
"invokeMiniProgramAPI", { name: "switchTab", arg: { url: e.url } },
e
);
});
},
reLaunch: function(e) {
O(function() {
M(
"invokeMiniProgramAPI", { name: "reLaunch", arg: { url: e.url } },
e
);
});
},
postMessage: function(e) {
O(function() {
M(
"invokeMiniProgramAPI", { name: "postMessage", arg: e.data || {} },
e
);
});
},
getEnv: function(e) {
O(function() {
e({ miniprogram: "miniprogram" === o.__wxjs_environment });
});
}
}
},
T = 1,
k = {};
return (
i.addEventListener(
"error",
function(e) {
if (!p) {
var n = e.target,
i = n.tagName,
t = n.src;
if ("IMG" == i || "VIDEO" == i || "AUDIO" == i || "SOURCE" == i)
if (-1 != t.indexOf("wxlocalresource://")) {
e.preventDefault(), e.stopPropagation();
var o = n["wx-id"];
if ((o || ((o = T++), (n["wx-id"] = o)), k[o])) return;
(k[o] = !0),
wx.ready(function() {
wx.getLocalImgData({
localId: t,
success: function(e) {
n.src = e.localData;
}
});
});
}
}
},
!0
),
i.addEventListener(
"load",
function(e) {
if (!p) {
var n = e.target,
i = n.tagName;
n.src;
if ("IMG" == i || "VIDEO" == i || "AUDIO" == i || "SOURCE" == i) {
var t = n["wx-id"];
t && (k[t] = !1);
}
}
},
!0
),
e && (o.wx = o.jWeixin = w),
w
);
}
function M(n, e, i) {
o.WeixinJSBridge ?
WeixinJSBridge.invoke(n, x(e), function(e) {
A(n, e, i);
}) :
B(n, i);
}
function P(n, i, t) {
o.WeixinJSBridge ?
WeixinJSBridge.on(n, function(e) {
t && t.trigger && t.trigger(e), A(n, e, i);
}) :
B(n, t || i);
}
function x(e) {
return (
((e = e || {}).appId = v.appId),
(e.verifyAppId = v.appId),
(e.verifySignType = "sha1"),
(e.verifyTimestamp = v.timestamp + ""),
(e.verifyNonceStr = v.nonceStr),
(e.verifySignature = v.signature),
e
);
}
function V(e) {
return {
timeStamp: e.timestamp + "",
nonceStr: e.nonceStr,
package: e.package,
paySign: e.paySign,
signType: e.signType || "SHA1"
};
}
function A(e, n, i) {
("openEnterpriseChat" != e && "openBusinessView" !== e) ||
(n.errCode = n.err_code),
delete n.err_code,
delete n.err_desc,
delete n.err_detail;
var t = n.errMsg;
t ||
((t = n.err_msg),
delete n.err_msg,
(t = (function(e, n) {
var i = e,
t = a[i];
t && (i = t);
var o = "ok";
if (n) {
var r = n.indexOf(":");
"confirm" == (o = n.substring(r + 1)) && (o = "ok"),
"failed" == o && (o = "fail"),
-1 != o.indexOf("failed_") && (o = o.substring(7)),
-1 != o.indexOf("fail_") && (o = o.substring(5)),
("access denied" !=
(o = (o = o.replace(/_/g, " ")).toLowerCase()) &&
"no permission to execute" != o) ||
(o = "permission denied"),
"config" == i && "function not exist" == o && (o = "ok"),
"" == o && (o = "fail");
}
return (n = i + ":" + o);
})(e, t)),
(n.errMsg = t)),
(i = i || {})._complete && (i._complete(n), delete i._complete),
(t = n.errMsg || ""),
v.debug && !i.isInnerInvoke && alert(JSON.stringify(n));
var o = t.indexOf(":");
switch (t.substring(o + 1)) {
case "ok":
i.success && i.success(n);
break;
case "cancel":
i.cancel && i.cancel(n);
break;
default:
i.fail && i.fail(n);
}
i.complete && i.complete(n);
}
function C(e) {
if (e) {
for (var n = 0, i = e.length; n < i; ++n) {
var t = e[n],
o = c[t];
o && (e[n] = o);
}
return e;
}
}
function B(e, n) {
if (!(!v.debug || (n && n.isInnerInvoke))) {
var i = a[e];
i && (e = i),
n && n._complete && delete n._complete,
console.log('"' + e + '",', n || "");
}
}
function L() {
return new Date().getTime();
}
function O(e) {
l &&
(o.WeixinJSBridge ?
e() :
i.addEventListener &&
i.addEventListener("WeixinJSBridgeReady", e, !1));
}
};
export default n(window);
@@ -0,0 +1,18 @@
let debug = process.env.NODE_ENV !== 'production';
const install = Vue => {
let vk = Vue.prototype ? Vue.prototype.vk : Vue.config.globalProperties.vk;
if (vk) {
vk.log = console.log;
if (typeof vk.getConfig === "function") {
debug = vk.getConfig().debug;
}
if(!debug){
console.log = function(...obj){};
}
}
}
export default {
install
}
@@ -0,0 +1,40 @@
var isOnLaunch = true;
export default {
data() {
return {}
},
onLoad() {
// 将vk实例挂载到app.globalData上,方便在非Vue页面自身函数中调用vk实例(因为获取不到this)
let app = getApp({ allowDefault: true });
if (app && app.globalData && !app.globalData.vk) {
app.globalData.vk = this.vk;
}
if (this.vk) {
const url = this.vk.pubfn.getCurrentPageRoute();
// 检测是否可以分享(小程序专属)
this.vk.navigate.checkAllowShare({ url });
// 检测是否需要登录,只有首次启动的页面才需要检测,其他页面通过 vk.navigateTo 跳转前会自动判断。
if (isOnLaunch && !this.vk.checkToken() && getCurrentPages().length == 1) {
isOnLaunch = false; // 重新标记为非首次页面
const currentPage = this.vk.pubfn.getCurrentPage() || {};
let pagePath = currentPage.pagePath || `/${currentPage.route}` || url;
let fullPath = currentPage.fullPath;
let options = currentPage.options;
this.vk.pubfn.checkLogin({ url: pagePath, fullPath, options, isOnLaunch: true }); // 检测是否需要登录
}
}
},
created() {
// 将vk实例挂载到app.globalData上,方便在非Vue页面自身函数中调用vk实例(因为获取不到this)
let app = getApp({ allowDefault: true });
if (app && app.globalData && !app.globalData.vk) {
app.globalData.vk = this.vk;
}
},
methods: {
$getData(data, key, defaultValue) {
let { vk } = this;
return vk.pubfn.getData(data, key, defaultValue);
}
}
}
@@ -0,0 +1,170 @@
/**
* 百度API
*/
var baidu = {};
var vk = {};
var baiduOpenApiAccessTokenCacheName = "vk_openapi_baidu_open_token";
baidu.init = function(util={}) {
vk = util.vk;
};
// 百度开放平台api
baidu.open = {};
// 文字识别
baidu.open.ocr = {};
/**
* 营业执照识别
* 以下data参数三选一即可
* @param {File} file 文件对象
* @param {String} image 图像base64编码后进行urlencode
* @param {String} url 图片完整URL
*
vk.openapi.baidu.open.ocr.business_license({
title:"识别中...",
data: {
file: res.tempFiles[0]
},
success: (res) => {
that.data = res.data;
},
});
*/
baidu.open.ocr.business_license = function(obj={}){
obj.action = "ocr/v1/business_license";
let file = obj.data.file;
if(file){
vk.pubfn.fileToBase64({ file }).then(base64 => {
obj.data.image = base64;
delete obj.data.file;
baidu.request(obj);
});
}else{
baidu.request(obj);
}
}
/**
* 身份证识别
* 以下data参数三选一即可
* @param {File} file 文件对象
* @param {String} image 图像base64编码后进行urlencode
* @param {String} url 图片完整URL
vk.openapi.baidu.open.ocr.idcard({
title:"识别中...",
data: {
image:base64,
id_card_side:"front", // front:身份证含照片的一面 back:身份证带国徽的一面
},
success: (res) => {
that.data = res.data;
},
});
*/
baidu.open.ocr.idcard = function(obj={}){
obj.action = "ocr/v1/idcard";
let file = obj.data.file;
if(!obj.data.id_card_side) obj.data.id_card_side = "front";
if(file){
vk.pubfn.fileToBase64({ file }).then(base64 => {
obj.data.image = base64;
delete obj.data.file;
baidu.request(obj);
});
}else{
baidu.request(obj);
}
}
/**
* 百度开放平台通用请求接口
* @param {String} action 接口名称
* @param {String} actionVersion 接口版本名称 默认2.0
* @param {String} title loading文字
* @param {object} data 请求参数
* @param {String} success 成功回调
* @param {String} fail 失败回调
* @param {String} complete 完成回调
* 使用示例
vk.openapi.baidu.request({
action: 'ocr/v1/business_license',
title:"识别中...",
data: {
image:base64
},
success: (data) => {
that.data = data;
},
});
*/
baidu.request = function(obj={}){
let {
title
} = obj;
if(title) vk.showLoading(title);
let baiduApiAccessToken = vk.getStorageSync(baiduOpenApiAccessTokenCacheName);
if(baiduApiAccessToken && baiduApiAccessToken.expTime > new Date().getTime()){
// 获取缓存中的token
obj.accessToken = baiduApiAccessToken.accessToken;
request(obj);
}else{
// 发起请求获取token
vk.callFunction({
url: 'plugs/baidu/client/pub/getAccessToken',
success:function(tokenRes) {
vk.setStorageSync(baiduOpenApiAccessTokenCacheName,{
accessToken : tokenRes.access_token,
expTime : 2590000000 + new Date().getTime(),
});
obj.accessToken = tokenRes.access_token;
request(obj);
},
fail:function(res){
if(title) vk.hideLoading();
if(typeof obj.fail === "function") obj.fail(res);
if(typeof obj.complete === "function") obj.complete(res);
}
});
}
}
function request(obj={}){
let {
action,
actionVersion = "2.0",
accessToken,
header = { "content-type": "application/x-www-form-urlencoded" },
data,
title
} = obj;
vk.request({
url: `https://aip.baidubce.com/rest/${actionVersion}/${action}?access_token=${accessToken}`,
method:"POST",
header:{
"content-type": "application/x-www-form-urlencoded",
},
errorCodeName:"error_code",
errorMsgName:"error_msg",
data,
needAlert:true,
success: function(data){
if(title) vk.hideLoading();
if(data.code){
if(typeof obj.fail === "function") obj.fail(data);
}else{
if(typeof obj.success === "function") obj.success(data);
}
},
fail: function(data){
if(title) vk.hideLoading();
// 如果是因为token失效报的错误,则清空下本地token缓存
if(data && data.code === 110) vk.removeStorageSync(baiduOpenApiAccessTokenCacheName);
if(typeof obj.fail === "function") obj.fail(data);
},
complete: function(data){
if(typeof obj.complete === "function") obj.complete(data);
}
});
}
export default baidu;
@@ -0,0 +1,14 @@
/**
* 开放API
*/
import baidu from './baidu'
var openapi = {};
openapi.baidu = baidu;
openapi.init = function(util){
openapi.baidu.init(util);
}
// 微信小程序API
export default openapi;
@@ -0,0 +1,25 @@
const install = Vue => {
const debug = process.env.NODE_ENV !== 'production';
let vk = Vue.prototype ? Vue.prototype.vk : Vue.config.globalProperties.vk;
if (debug && vk) {
const oldErrorHandler = Vue.config.errorHandler;
Vue.config.errorHandler = function errorHandler(err, vm, info) {
const route = vm.$page && vm.$page.route;
let date = new Date();
let log = {
err: err.toString(),
info,
route,
time: date.getTime(),
timeStr: date.toLocaleTimeString()
};
if(vk.vuex) vk.vuex.dispatch('$error/add', log);
return oldErrorHandler(err, vm, info);
}
}
}
export default {
install
}
@@ -0,0 +1,67 @@
export default {
beforeCreate() {
// 将vuex方法挂在到vk实例中 beforeCreate created
let { vk, $store } = this;
if (typeof $store !== "undefined" && typeof vk.getVuexStore === "undefined") {
vk.getVuexStore = function() {
return $store;
};
vk.vuex = (name, value) => {
$store.commit('updateStore', { name, value });
};
/**
* (推荐) 设置vuex
* vk.vuex.set('$user.userInfo.avatar', avatar);
*/
vk.vuex.set = (name, value) => {
$store.commit('updateStore', { name, value });
};
/**
* (推荐) 读取vuex(具有解除对象内存印射功能,且任意一层数据为undefined,不会报错)
* vk.vuex.get('$user.userInfo.avatar');
*/
vk.vuex.get = (name, defaultValue) => {
let value = vk.pubfn.getData($store.state, name);
if (typeof value === "undefined") return (typeof defaultValue !== "undefined") ? defaultValue : "";
return JSON.parse(JSON.stringify(value));
};
vk.vuex.getters = (name) => {
return $store.getters[name];
};
/**
* 触发vuex的指定actions(异步)
* $user是模块名,addFootprint是对应模块的action名称
* vk.vuex.dispatch('$user/addFootprint', data);
*/
vk.vuex.dispatch = $store.dispatch;
/**
* 触发vuex的指定mutations
* $user是模块名,setFootprint是对应模块的action名称
* vk.vuex.commit('$user/setFootprint', data);
*/
vk.vuex.commit = $store.commit;
/* 另一种方式 */
/**
* vk.setVuex('$user.userInfo.avatar', avatar);
*/
vk.setVuex = vk.vuex.set;
/**
* vk.getVuex('$user.userInfo.avatar');
*/
vk.getVuex = vk.vuex.get;
/* 兼容老版本 */
vk.state = vk.vuex.get;
try {
if (!vk.checkToken()) {
vk.callFunctionUtil.deleteUserInfo({
log: false
});
}
} catch (err) {}
}
},
computed: {
}
}