diff --git a/App.vue b/App.vue new file mode 100644 index 0000000000000000000000000000000000000000..395f9ac5fb1322f8a067dbe3e5180dbd85de04f5 --- /dev/null +++ b/App.vue @@ -0,0 +1,47 @@ + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f50dcc9ec408de148d00379d2fba631ff2be2fd6 --- /dev/null +++ b/README.md @@ -0,0 +1,891 @@ +> 本文为uni-im v3.x 的文档,如果旧项目需要继续使用老版本的uni-im v2.x,另见:[https://gitcode.net/dcloud/hello-uni-im/-/blob/v2/README.md](https://gitcode.net/dcloud/hello-uni-im/-/blob/v2/README.md) + +**uni-im 已开放需求征集和投票** [点此前往](https://vote.dcloud.net.cn/#/?name=uni-im) + +# 简介 +uni-im是云端一体的、全平台的、免费的、开源即时通讯系统。 +- 基于uni-app,App、小程序、web全端兼容 +- 基于uniCloud,前后端都使用js开发 +- 基于[uni-push2](https://uniapp.dcloud.net.cn/unipush-v2.html),专业稳定的全端推送系统 +- 基于[uni-id](https://uniapp.dcloud.net.cn/uniCloud/uni-id/summary.html),完善的账户体系 +- 支持服务端为非uniCloud(比如:应用服务端的开发语言是php、java、go、.net、python、c#等)或 不基于uni-id-pages 开发的项目接入 + +案例: + + + +如图:在插件市场任意插件详情页面,点击咨询作者按钮,即可看到基于uni-im搭建的客服系统。 + +下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) + +## 特点优势 +- 性价比高;前后端代码均免费开源,相比同类产品使用uni-im仅需花费极少的托管在uniCloud(serverless服务器)产生的费用[详情查看](#cost) +- 全端可用 +- App端支持nvue,更好的长列表性能。list组件性能优势[详情参考](https://uniapp.dcloud.net.cn/component/list.html) +- 中心化响应式数据管理,切换会话无需重新加载数据,更流畅的体验 +- App端聚合多个手机厂商推送通道,app不在线也可以收到消息 + +优先开发哪些,取决于开发者的反馈。同时也欢迎开发者共建这个开源项目。 +> uni-im相关功能建议或问题,可以加入由uni-im(本插件)搭建的交流群[点此加入](https://im.dcloud.net.cn/#/?joinGroup=63ef49711d358337456f4d67) + +## 使用uniCloud产生的费用说明@cost + +uni-im本身并不收费,实际使用中需要依赖uniCloud云服务,会产生费用;而uniCloud的价格很实惠: +- 调用10000次云函数仅需0.0133元 +- 调用10000次数据库查询仅需0.015元 +> 更多计费参考:[阿里云版uniCloud按量计费文档](https://uniapp.dcloud.net.cn/uniCloud/price.html#aliyun-postpay) + +### 举例说明: +- 单聊场景,向用户发送一条消息的过程: +1. 调用uni-im-co云对象的sendMsg方法(产生1次云函数请求) +2. 查询当前对话的会话记录(产生1次云数据库读操作) +3. 根据步骤2的查询结果,如果已经有会话记录,就更新会话,否则就创建一条会话记录(产生1次云数据库写操作) +4. 查询发送消息的用户信息,用于接收消息时在通知栏显示发送者昵称和头像(产生1次云数据库读操作) +5. 记录发送的消息内容到数据库,用于保存消息历史记录(产生1次云数据库写操作) +6. 以`user_id`为标识通过`uni-push2`向用户发送消息会产生0.00000283元uniCloud使用费用[详情查看](https://uniapp.dcloud.net.cn/unipush-v2.html#cost) + +合计:1次云函数请求、2次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 2 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000017元 + +- 群聊场景,向用户发送一条消息的过程: +1. 调用uni-im-co云对象的sendMsg方法(产生1次云函数请求) +2. 查询当前用户是否为群成员,防止非群成员发送消息(产生1次云数据库读操作) +3. 查询当前对话的会话记录(产生1次云数据库读操作) +4. 根据步骤3的查询结果,如果已经有会话记录,就更新会话,否则就创建一条会话记录(产生1次云数据库写操作) +5. 查询发送消息的用户信息,用于接收消息时在通知栏显示发送者昵称和头像(产生1次云数据库读操作) +6. 记录发送的消息内容到数据库,用于保存消息历史记录(产生1次云数据库写操作) +7. 以群id为参数,调用uni-im-co云对象的sendMsgToGroup方法,这是一个递归方法每次向500名群成员推送消息(如果群成员数量为0-500只需执行1次,500-1000需执行2次,以此类推),(会产生最少1次数据库读操作,和1次以`user_id`为标识通过`uni-push2`向用户发送消息会产生0.00000283元uniCloud使用费用[详情查看](https://uniapp.dcloud.net.cn/unipush-v2.html#cost)) + +合计:向500人群发送消息,会产生:1次云函数请求、4次数据库读操作、2次数据库写操作、1次uni-push2推送操作,即 (1 * 0.0133 + 4 * 0.015 + 2 * 0.05 + 1 * 0.0283)/10000 ≈ 0.000020元 + +相比市面上同类型产品,使用uni-im仅需花费如此便宜的uniCloud(serverless服务器)费用;在价格这块uni-im性价比极高。 + +# 快速部署体验 +## 前提条件 +1. 开通uniCloud并创建服务空间 [控制面板](https://unicloud.dcloud.net.cn/) + 传统的IM产品服务端代码托管在服务商名下的服务器内,你只拥有代码和产生的数据的使用权,并非所有权;而uni-im的前后端代码都是开源的,并且托管在您名下的uniCloud([serverless](https://uniapp.dcloud.net.cn/uniCloud/#%E4%BB%80%E4%B9%88%E6%98%AFserverless)服务器)内。 +2. 开通`uni-push2.0`(注意:**无论是APP、小程序、web端都需要开通,否则消息将无法实时更新**)[点此前往开通](https://uniapp.dcloud.net.cn/unipush-v2.html#%E7%AC%AC%E4%B8%80%E6%AD%A5-%E5%BC%80%E9%80%9A) + +## 体验步骤 +1. 打开`uni-im`插件下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +2. 点击`使用HBuilderX导入示例项目` +3. 对项目根目录uniCloud点右键选择“云服务空间初始化向导”界面按提示部署项目(注意:选择绑定的服务空间,须在uni-push2.0的[web控制台](https://dev.dcloud.net.cn/pages/app/push2/info)关联) +4. `运行项目`到2个不同的浏览器,因为在同一个浏览器打开相同网络地址(ip或者域名)的uni-im项目,socket会相互占线。 + 所以需要使用两个浏览器(或者使用浏览器`打开新的无痕式窗口`功能充当第二个浏览器)分别`注册账号并登录`, + 到此部署已经结束 +5. 向对应的用户发起会话,通过访问路径:`/uni_modules/uni-im/pages/chat/chat?user_id=` + `对应的用户id` 即可 + +## 部署到自己的项目 +1. 打开`uni-im`插件下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +2. 点击`使用HBuilderX导入插件`,选择你的项目,点击确定(同时会自动导入依赖的uni_modules`uni-id-pages`)按提示操作自动配置`pages.json` +3. 打开项目根目录的App.vue文件,初始化uni-id-pages和uniIm模块 +示例如下: + +```html + +``` + +如果你是部署到微信小程序端,由于小程序端不支持“动态组件”需要通过引入vite插件[rollup-plugin-uniapp-cementing.js](https://gitcode.net/dcloud/hello-uni-im/-/blob/v3/rollup-plugin-uniapp-cementing.js)实现“动态组件静态化” +示例: +在下面跟目录创建:`vite.config.js`,内容如下: +```js +import { defineConfig } from 'vite'; +import uni from '@dcloudio/vite-plugin-uni'; +import cementingPlugin from './rollup-plugin-uniapp-cementing.js' + +export default defineConfig({ + plugins: [ + cementingPlugin({ + // 需要静态化的页面路径(支持通配符*) + include: [ + './uni_modules/uni-im/components/uni-im-msg/uni-im-msg.vue', + ], + components: { + // 声明组件,格式 {"$组件名":{"$cementing":"$组件路径"}} + MsgByType: { + msgUserCard: '@/uni_modules/uni-im/components/uni-im-msg/types/userinfo-card.vue', + msgVideo: '@/uni_modules/uni-im/components/uni-im-msg/types/video.vue', + msgFile: '@/uni_modules/uni-im/components/uni-im-msg/types/file.vue', + msgHistory: '@/uni_modules/uni-im/components/uni-im-msg/types/history.vue', + msgRichText: '@/uni_modules/uni-im/components/uni-im-msg/types/rich-text.vue', + msgCode: '@/uni_modules/uni-im/components/uni-im-msg/types/code.vue', + msgText: '@/uni_modules/uni-im/components/uni-im-msg/types/text.vue', + msgSound: '@/uni_modules/uni-im/components/uni-im-msg/types/sound.vue', + msgImage: '@/uni_modules/uni-im/components/uni-im-msg/types/image.vue', + }, + MsgExtra: { + UniImMsgReader: '@/uni_modules/uni-im-msg-reader/components/uni-im-msg-reader/uni-im-msg-reader.vue', + } + }, + debug: true + }), + uni(), + ], + build:{target: 'es2015'}, +}); +``` + +4. 配置Schema扩展Js的公共模块或扩展库 +由于uni-im的数据库的触发器依赖了`uni-im-utils`,需要在目录`uniCloud/database`右键 -> 选择“配置Schema扩展Js的公共模块或扩展库” -> 在选择项目的公共模块中找到`uni-im-utils`并勾选 -> 点击确定,完成配置。 + +5. 部署到uniCloud +对项目根目录uniCloud点右键,选择“云服务空间初始化向导” 按提示部署项目(注意:选择绑定的服务空间,须在uni-push2.0的[web控制台](https://dev.dcloud.net.cn/pages/app/push2/info)关联) + +6. 登录uni-im + + uni-im的服务端代码托管在uniCloud下,账户体系是[uni-id 4.0+](https://uniapp.dcloud.net.cn/uniCloud/uni-id/summary.html)的; + uni-app生态下绝大部分项目的架构与uni-im相同,所以不需要考虑账号打通问题,用户登录项目后,不需要额外登录uni-im。 + + 而有些传统项目,服务端的开发语言是php、java、go、.net、python、c#等,是自己设计的账号体系; + 用户登录所获得的token,与uni-im所需的token不是同一个账号体系; + 需要在传统服务器端,通过[uni-id的外部系统联登](./uni-id/cloud-object.md#external)同步你项目的账号数据到uni-im用户体系并获得uni-id的token,按如下示例代码完成登录。 + + ```js + import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js'; + uni.request({ + url: 'https://www.example.com/login', //仅为示例,并非真实接口地址。 + data: { + username: 'test', + password: '123456' + }, + success:async (res) => { + console.log(res.data); + // 得到你自己项目的token和uni-id的token + let {token,uniIdToken} = res.data + // 存储你自己项目的token到storage(仅供参考,根据你自己的登录逻辑而定) + uni.setStorageSync('token',token) + + // 存储uni-id的token和token过期时间到storage(必须按以下格式存储) + uni.setStorageSync('uni_id_token_expired',uniIdToken.tokenExpired) + uni.setStorageSync('uni_id_token',uniIdToken.token) + // 获取push的ClientId同步到uni-id + uni.getPushClientId({ + success: async function(e) { + // console.log(e) + let pushClientId = e.cid + // console.log(pushClientId); + let res = await uniIdCo.setPushCid({ + pushClientId + }) + // console.log('getPushClientId', res); + }, + fail(e) { + console.log(e) + } + }) + // 更新本地用户信息 + await uniIdMutations.updateUserInfo() + // 通知其他模块登录成功 + uni.$emit('uni-id-pages-login-success') + } + }); + + ``` + + 其他情况: + + - 客户端如果不是uni-app的,如果是网页,可iframe内嵌。如果是原生app,可嵌入[uni小程序sdk](https://nativesupport.dcloud.net.cn/README) + + - 不基于`uni-id-pages`的客户端代码,仅基于`uni-id-co`的项目,需要在登录成功和用户信息更新时,同步更新uniId store内的当前用户信息(uni-im显示当前用户头像、昵称时会用到)示例代码: + + ```js + //导入uniCloud客户端账户体系,用户信息状态管理模块 + import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js'; + await uniIdMutations.updateUserInfo() + ``` + - 基于老版uni-id(版本号:3.x) 开发的项目,需要如下改造: + 1. 在登录成功和token续期后,绑定当前账号与设备推送标识的关联关系。示例代码: + + ```js + const uniIdCo = uniCloud.importObject("uni-id-co", {customUI: true}) + uni.getPushClientId({ + success: async function(e) { + console.log(e) + let pushClientId = e.cid + let res = await uniIdCo.setPushCid({ + pushClientId + }) + console.log('getPushClientId', res); + }, + fail(e) { + console.error(e) + } + }) + ``` + 2. 在登录成功和用户信息更新时,同步更新uniId store内的当前用户信息(uni-im显示当前用户头像、昵称时会用到)示例代码: + ```js + //导入uniCloud客户端账户体系,用户信息状态管理模块 + import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js'; + await uniIdMutations.updateUserInfo() + ``` + +7. 确保账户对接成功后,打开“用户列表页”,路径:`/uni_modules/uni-im/pages/userList/userList`可以看到所有的注册用户 +8. 点击某个用户,会自动创建与该用户的会话,并打开“聊天对话页”(路径:`/uni_modules/uni-im/pages/chat/chat`),然后就可以开始聊天了。 +9. 还可以导入uni-im的示例项目作为管理员端与用户聊天。 +10. 如果你是2个不同appId的应用相互通讯(比如:淘宝的买家端和卖家端通讯)的场景,请打开聊天对话文件(路径:`/uni_modules/uni-im/pages/chat/chat`)搜索`msg.appId = this.systemInfo.appId`修改`this.systemInfo.appId`为相对的appId + +不基于uni-id-pages开发的项目还要注意以下两个问题: +1. 退出登录;需要在执行退出登录/切换账号时,调用uni-id的退出登录接口。否则会出现退出登录后的设备仍然能收到im消息,或导致此设备再登录其他账号不能正常收到消息的问题;示例代码如下: +```js +import {mutations as uniIdMutations} from '@/uni_modules/uni-id-pages/common/store.js' +uniIdMutations.logout() +``` +2. token有效期问题,保证你的项目token有效期和uni-id的token有效期保持一致。这涉及两个操作: +- 配置uni-id的token过期时间与你的项目token有效期一致。配置路径:`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json`,关于配置说明[详情查看](https://uniapp.dcloud.net.cn/uniCloud/uni-id/summary.html#config) +- 如果你的项目有token续期逻辑,需要在续期后调用uni-id的token续期接口,示例代码: +```js +const uniIdCo = uniCloud.importObject("uni-id-co", {customUI: true}) +await uniIdCo.refreshToken() +``` + +常见问题: +1. 为什么不能实时接收到推送的消息,需要刷新或者关闭重新打开才能收到? +答: uni-im通过`uni-push2`实现消息实时送达,请检查是否已开通并正确配置,且在配置正常后重新登录 + +2. 怎么样快速上手 +答:先下载示例项目,部署并正确配置push后,体验没问题了再部署到自己的项目。 + + +## 限制普通用户向其他用户发起会话 +客服场景下,我们希望管理员客服可以向任意用户发起会话。而普通用户的会话对象只能是客服。 +- 客户端限制 +删除或隐藏“用户列表页”和“会话列表页”,仅保留“聊天对话页”。并绘制按钮,如:“联系客服”,点击后打开“聊天对话页” +逻辑代码如下: +```js +uni.navigateTo({ + url:'/uni_modules/uni-im/pages/chat/chat?user_id=' + 对应的用户id +}) +``` + +- 服务端限制 + +1. 添加`uni-im`配置文件,打开:`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/`;新建`uni-im`文件夹和`config.json`文件,示例如下: +```json +{ + "customer_service_uids":["user-id-01","user-id-02"] +} +``` + +2. 配置`customer_service_uids`的值为管理员客服的user_id(支持多个以数组的形式指定),如果会话双方均不属于此域则无法通讯。不配置或为false则表示不限制。 + +# 开发文档 +## 目录结构 +
+
+uni_modules
+    ├─其他module
+    └─uni-im
+      ├── changelog.md
+      ├── components                              组件目录
+      │   ├── uni-im-load-state                   加载状态提示
+      │   ├── uni-im-chat-input                   对话界面输入框(web-pc专用)
+      │   ├── uni-im-contextmenu                  自定义右键菜单(web-pc专用)                    
+      │   ├── uni-im-conversation                 会话
+      │   ├── uni-im-conversation-list            会话列表
+      │   ├── uni-im-filtered-conversation-list   过滤后的会话(搜索聊天记录时展示)
+      │   ├── uni-im-group-notification           群公告
+      │   ├── uni-im-icons                        图标
+      │   ├── uni-im-img                          图片
+      │   ├── uni-im-msg                          聊天消息
+      │   │   ├── types                           各类消息类型
+      │   │   │   ├── code.vue                    代码
+      │   │   │   ├── file.vue                    文件
+      │   │   │   ├── history.vue                 历史(转发的聊天记录)
+      │   │   │   ├── image.vue                   图片
+      │   │   │   ├── rich-text.vue               富文本
+      │   │   │   ├── sound.vue                   声音
+      │   │   │   ├── system.vue                  系统
+      │   │   │   ├── text.vue                    纯文本
+      │   │   │   ├── userinfo-card.vue           用户信息卡片
+      │   │   │   └── video.vue                   视频
+      │   │   └── popup-control.vue               弹出式消息操控(集成:撤回、复制、回复、转发、多选)
+      │   ├── uni-im-msg-list                     消息列表
+      │   ├── uni-im-share-msg                    分享消息界面
+      │   ├── uni-im-sound                        录音组件
+      │   └── uni-im-view-msg                     用于浏览分享的历史聊天记录
+      ├── license.md                              源码使用许可协议
+      ├── package.json                            包管理文件
+      ├── pages                                   
+      │   ├── chat                                
+      │   │   ├── chat.nvue                       聊天对话页
+      │   │   ├── chat-filtered.nvue              简版对话页面(搜索聊天记录时展示)
+      │   │   ├── cmp
+      │   │   │   ├── chat-fragment.nvue         渲染会话中一个片段的消息列表,用于显示某条消息搜索结果的上下文列表
+      │   │   │   └── simple-message.nvue
+      │   │   ├── emojiCodes.js                  emoji表情列表
+      │   │   └── info.nvue                      对话详情(显示好友信息,可在此页面操作删除好友)
+      │   ├── common
+      │   │   ├── video                          播放视频专用
+      │   │   └── view-code-page                 全屏代码浏览
+      │   ├── contacts
+      │   │   ├── addPeopleGroups                查找并添加用户或群
+      │   │   ├── contacts.nvue                  联系人页面
+      │   │   ├── createGroup                    创建群聊
+      │   │   ├── groupList                      我的群列表
+      │   │   └── notification                   系统通知
+      │   ├── group
+      │   │   ├── groupQRCode.nvue               群二维码页面(未完成)
+      │   │   └── info.nvue                      群信息页面(管理群)
+      │   ├── index                              首页(展示会话列表)
+      │   └── userList                           所有用户列表页(仅管理员账号可用)
+      ├── sdk                                    核心库
+      │   ├── ext
+      │   │   ├── MsgManager.class.js             消息管理类库
+      │   │   ├── indexDB.js                      indexDB数据库
+      │   │   └── index.js
+      │   ├── index.js
+      │   ├── init                                初始化相关
+      │   │   ├── EasyWebNotification.js          
+      │   │   ├── checkVersion.js                 版本检查      
+      │   │   ├── clearData.js                    清空数据      
+      │   │   ├── getCloudMsg.js                  获取掉线期间缺失的云端消息
+      │   │   ├── imData.js                       初始化基本数据
+      │   │   ├── sqlite.js                       sqlite数据库
+      │   │   ├── msgEvent.js                     消息事件
+      │   │   ├── onAppActivateStateChange.js     应用状态相关                     
+      │   │   ├── onNotification.js               web-pc系统消息相关    
+      │   │   ├── onSocketStateChange.js          socket 连接状态相关      
+      │   │   ├── onlyOneWebTab.js                限制web-pc端只能单选项卡打开本应用相关
+      │   │   └── index.js                        
+      │   ├── methods                             封装的方法
+      │   │   ├── conversation
+      │   │   │   ├── Conversation.class.js       会话类
+      │   │   │   └── index.js
+      │   │   ├── extensions.js                   扩展相关
+      │   │   ├── friend.js                       好友
+      │   │   ├── group.js                        群组
+      │   │   ├── users.js                        用户
+      │   │   ├── msgTypes.js                     消息类型
+      │   │   ├── notification.js                 系统消息
+      │   │   └── index.js
+      │   ├── state
+      │   │   ├── data.js                         响应式数据相关
+      │   │   └── index.js
+      │   └── utils
+      │       ├── appEvent.js                     应用生命周期
+      │       ├── createObservable.js             创建响应式对象(支持nvue)
+      │       ├── highlight                       代码高亮库
+      │       │   ├── github-dark.min.css
+      │       │   └── highlight-uni.min.js
+      │       ├── html-parser.js                  htmlString转化为nodes专用库
+      │       ├── index.js
+      │       ├── markdown-it.min.js              markdown相关库
+      │       ├── md5.min.js                      md5哈希加密算法(用于本地直接生成会话id)
+      │       ├── shortcut-key.js                 web-pc快捷键相关
+      │       └── toFriendlyTime.js               时间戳转友好的时间表达
+      ├── static                                  静态资源目录
+      └── uniCloud
+          ├── cloudfunctions
+          │   ├── common
+          │   │   ├── uni-im-ext
+          │   │   │   ├── index.js
+          │   │   │   └── package.json
+          │   │   └── uni-im-utils
+          │   │       ├── index.js
+          │   │       ├── md5.js
+          │   │       ├── package-lock.json
+          │   │       └── package.json
+          │   └── uni-im-co
+          │       ├── conversation.js
+          │       ├── filtered-conversation.js
+          │       ├── friend.js
+          │       ├── group.js
+          │       ├── index.obj.js
+          │       ├── msg.js
+          │       ├── package-lock.json
+          │       ├── package.json
+          │       ├── push.js
+          │       └── uni-im-co.param.js
+          └── database
+              ├── uni-id-users.schema.ext.js             用户表触发器 
+              ├── uni-im-conversation.schema.ext.js      聊天会话表触发器        
+              ├── uni-im-conversation.schema.json        聊天会话表的表结构    
+              ├── uni-im-friend-invite.schema.ext.js     邀请加为好友表触发器     
+              ├── uni-im-friend-invite.schema.json       邀请加为好友表表结构     
+              ├── uni-im-friend.schema.ext.js            好友关系表触发器
+              ├── uni-im-friend.schema.json              好友关系表表结构
+              ├── uni-im-group-join.schema.ext.js        申请加入群聊表触发器 
+              ├── uni-im-group-join.schema.json          申请加入群聊表表结构 
+              ├── uni-im-group-member.index.json         群成员表索引
+              ├── uni-im-group-member.schema.ext.js      群成员表触发器      
+              ├── uni-im-group-member.schema.json        群成员表表结构
+              ├── uni-im-group.schema.ext.js             群信息表触发器 
+              ├── uni-im-group.schema.json               群信息表表结构
+              ├── uni-im-msg.schema.ext.js               聊天消息表触发器
+              ├── uni-im-msg.schema.json                 聊天消息表表结构
+              └── uni-im-notification.schema.json        推送消息记录表(仅记录系统消息)
+
+
+
+名词解释 +- 聊天会话ID +根据通讯双方用户id,或群聊id,生成的唯一索引值;用于更加方便查找聊天记录等。 +- 聊天会话 +以会话ID为索引的一组数据,记录:未读消息数量、会话更新时间、会话类型、会话所属用户的id、对话的用户id、对话的群id、群信息、最后一条消息概述(文本消息的前15个字,消息为多媒体时只描述类型) + +## uni-im-co 云函数(云对象) +API列表 + +|API |描述 | +|-- |-- | +|getConversationList|获取会话列表[见下方](#cogetconversationlist) | +|sendMsg |发送聊天消息[见下方](#cosendmsg) | +|sendPushMsg |触发器专用消息推送方法 | +|sendMsgToGroup |向群用户递归推送消息[见下方](#cosendmsgtogroup) | +|addFriendInvite |向用户发起加好友邀请[见下方](#coaddfriendinvite) | +|chooseUserIntoGroup|选择用户加入群聊(不传群id时为创建群)[见下方](#cosendmsgtogroup) | +|revokeMsg |撤回已经发送的消息[见下方](#corevokemsg) | + + +### 获取会话列表 getConversationList@coGetConversationList +**参数说明** + +|参数名 |类型 |必填 |说明 | +|-- |-- |-- |-- | +|limit |number |否 |数量,默认值:500 | +|maxUpdateTime|number |否 |最大更新时间(实现高性能分页) | +|page |number |是 |页码 | + +**返回值** + +|参数名 |类型 |说明 | +|-- |-- |-- | +|errCode|string|number |错误码,0表示成功 | +|errMsg |string |错误信息 | +|data |array |会话数据 | + +### 发送聊天消息 sendMsg@coSendMsg + +|参数名 |类型 |必填 |说明 | +|-- |-- |-- |-- | +|appId |string |是 |接收消息的appId;如果你是2个不同appId的应用相互发,请修改此值为相对的appId | +|to_uid |string |否 |接收消息的用户id | +|group_id |string |否 |接收消息的群id | +|body |string |是 |消息内容,`type = text`时为文本内容.`type = image`时为图片网络地址 | +|type |string |是 |消息类型,暂时仅支持:text(表示文本类型)、image(表示图片类型) | +|isRetries|Boolean|否 |是否为重发 | + +**返回值** + +|参数名 |类型 |说明 | +|-- |-- |-- | +|errCode |string|number |错误码,0表示成功 | +|errMsg |string |错误信息 | +|data |object | | +| |- create_time |无 |创建时间 | + +**接口形式** + +```js +const uniImCo = uniCloud.importObject('uni-im-co', { + customUI: true +}); +await uniImCo.sendMsg({ + to_uid:"630cacf46293d20001f3c368", + type:"text", + body:"您好!" +}) +``` + +### 向群用户递归推送消息 sendMsgToGroup@coSendMsgToGroup +注意:这是一个递归云对象,500个设备为一组批量向用户推送消息(该方法仅支持云对象的方法,或者触发器调用) + +|参数名 |类型 |必填 |说明 | +|-- |-- |-- |-- | +|appId |string |是 |接收消息的应用appId | +|pushParam |object |是 |参数同uni-push2.0的sendMessage方法,详情参考[https://uniapp.dcloud.net.cn/uniCloud/uni-cloud-push/api.html#sendmessage](https://uniapp.dcloud.net.cn/uniCloud/uni-cloud-push/api.html#sendmessage) | +|before_id |string |否 |从哪个用户id开始(用于实现高性能分页) | +|push_clientids |array |否 |个推设备id列表 | + +**返回值** + +|参数名 |类型 |说明 | +|-- |-- |-- | +|errCode|string|number |错误码,0表示成功 | +|errMsg |string |错误信息 | + +### 撤回已发送的消息 revokeMsg@coRevokeMsg +|参数名 |类型 |必填 |说明 | +|-- |-- |-- |-- | +|msgId |string |是 |消息id | + +**返回值** + +|参数名 |类型 |说明 | +|-- |-- |-- | +|errCode|string|number |错误码,0表示成功 | +|errMsg |string |错误信息 | + + +### 向用户发起加好友邀请 addFriendInvite@coAddFriendInvite + +|参数名 |类型 |必填 |说明 | +|-- |-- |-- |-- | +|to_uid |string |是 |被邀请的用户id | +|message|string |否 |请求信息 | + +**返回值** + +|参数名 |类型 |说明 | +|-- |-- |-- | +|errCode|string|number |错误码,0表示成功 | +|errMsg |string |错误信息 | + +### 选择用户加入群聊 chooseUserIntoGroup@coSendMsgToGroup + +|参数名 |类型 |必填 |说明 | +|-- |-- |-- |-- | +|group_id |string |否 |群id(为空则创建群) | +|user_ids |string |是 |用户id数组 | + +**返回值** + +|参数名 |类型 |说明 | +|-- |-- |-- | +|errCode |string|number |错误码,0表示成功 | +|errMsg |string |错误信息 | +|data |object |返回信息 | +| |- group_id|string |群id | + + +## 服务端配置@uni-im-cloud-config +路径:`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-im/config.json` + +|字段名 |数据类型 |说明 | +|-- |-- |-- | +|customer_service_uids|string/boolean |客服用户id,不限制则填`false`即可;仅conversation_grade的值为100时有效 | +|conversation_grade |int |控制发起会话的条件,详情[会话控制](#conversation-grade) | + +### 会话控制@conversation_grade + +|值 |说明 | +|-- |-- | +|0 |不限制 | +|100|仅限当前用户向:客服、好友、群成员发起会话 | +|200|仅限当前用户向:好友或群成员发起会话 | +|300|限制向:系统管理员 或 群管理员 或 好友 发起会话| + + +## 客户端sdk@clientSkd + +入口文件路径:`@/uni_modules/uni-im/sdk/index.js` + +uni-im2.0 废弃了1.0通过Vuex的状态管理方式,不再需要关心vuex的用法,直接当做一个全局的响应式js变量即可。 + +### state + +|名称 |类型 |说明 | +|-- |-- |-- | +|isDisabled |boolean |是否禁用(当同一个浏览器,多个页签打开引起的占线时使用)| +|conversation |object |会话对象 | +| |- dataList|array |会话数据列表 | +| |- hasMore |boolean |是否还有更多会话数据 | +| |- loading |boolean |是否正在加载中 | +|currentConversationId |string |正在对话的会话id | +|heartbeat |timestamp |心跳(精确到秒)详情:[心跳概念说明](#heartbeat-explain) | +|friend |object |好友对象 | +| |- dataList|array |好友数据列表 | +| |- hasMore |boolean |是否还有更多好友数据 | +|group |object |聊天群对象 | +| |- dataList|array |聊天群数据列表 | +| |- hasMore |boolean |是否还有更多群聊数据 | +|notification |object |系统通知对象 | +| |- dataList|array |系统通知数据列表 | +| |- hasMore |boolean |是否还有更多系统通知数据 | +|users |object |存储所有出现过的用户信息,包括群好友信息 | +|isWidescreen |boolean |是否为pc宽屏 | +|isTouchable |boolean |是否为触摸屏 | +|systemInfo |object |系统信息详情参考:[https://uniapp.dcloud.net.cn/api/system/info.html#系统信息的概念](https://uniapp.dcloud.net.cn/api/system/info.html#%E7%B3%BB%E7%BB%9F%E4%BF%A1%E6%81%AF%E7%9A%84%E6%A6%82%E5%BF%B5) | +|indexDB |object/boolean |indexDB对象(仅web端有效) | +|audioContext |object/boolean |audio对象 | +|dataBaseIsOpen |boolean |判断本地sqlite数据库是否已经打开(仅app端有用) | +|socketIsClose |boolean |记录socket是否关闭 | + +### 心跳概念说明heartbeat@heartbeat-explain + +uni-im的会话列表和消息列表,需要显示实时的发生时间。而一个应用开启太多的定时器,会消耗较大的系统性能。 +所以uni-im提供了一个每秒钟更新一次的响应式数据`heartbeat`,由uniImInit方法:启用一个定时器刷新,挂载在全局,所有应用场景引用这一个变量即可 + +#### methods + +|名称 |说明 | +|-- |-- | +|conversation |会话对象 | +| |- get |这是一个异步方法,用于获取会话数据(默认先查本地会话,本地没有则请求云端拉取) | +| |- getCached |这是一个同步方法,同步获取已经缓存在本地的会话信息,适用于在能够确定会话信息已经拉到本地的上下文中调用。常用于计算属性内 | +| |- loadMore |加载更多会话数据 | +| |- unreadCount |统计“本地会话列表”的所有消息的未读数 | +| |- clearUnreadCount|清空所有会话的未读消息数 | +|notification |系统消息 | +| |- get |获取系统消息 | +| |- loadMore |加载更多系统消息 | +| |- unreadCount |统计未读数 | +|friend |好友列表 | +| |- get |获取好友数据 | +| |- loadMore |加载更多系统消息 | +|group |群列表 | +| |- get |获取群聊数据 | +| |- loadMore |加载更多群聊数据 | +|mergeUsersInfo |添加用户信息到本地用户信息库 | +|clearUnreadCount |设置某个会话的未读消息数为已读 | + + +使用示例: +```js +//引入uniImMethods +import uniIm from '@/uni_modules/uni-im/sdk/index.js'; +``` + +- 获取会话数据 + +1. 获取全部会话数据 +```js +let param = null +let conversationList = await uniIm.conversation.get(param) +``` +2. 获取指定会话的id会话数据 +```js +//xxx表示会话id +let param = "xxx" +let conversationList = await uniIm.conversation.get(param) +``` +3. 获取指定好友id的会话数据(如果本地不存在则从云端拉取,仍然不存在则本地自动创建) +```js +//xxx表示好友id +let param = {"friend_uid":"xxx"}, +let conversationList = await uniIm.conversation.get(param) +``` +4. 获取指定群聊id的会话数据(如果本地不存在则从云端拉取,仍然不存在则本地自动创建) +```js +//xxx表示群聊id +let param = {"group_id":"xxx"} +let conversationList = await uniIm.conversation.get(param) +``` + +5. 隐藏会话 +```js +let conversation = await uniIm.conversation.get(param) +await conversation.hide() +``` + +- 加载会话数据 + +1. 加载更多会话数据(分页加载,新数据的会话更新时间,小于列表中最小的会话更新时间) +```js +let param = null +let conversationList = await uniIm.conversation.loadMore(param) +``` + +2. 加载指定会话id的会话数据 +```js +// xxx表示会话id +let param = 'xxx' +let conversationData = await uniIm.conversation.loadMore(param) +``` + +**返回值** + +|属性名 |类型 |说明 | +|-- |-- |-- | +|id |string |当前会话id | +|title |string |普通会话为对方的用户名或昵称、群会话为群昵称 | +|avatar_file |uniCloud file|普通会话为对方的用户头像、群会话为群头像 | +|unread_count |number |未读消息数 | +|user_id |string |对话的用户id(群聊会话时为空) | +|group_id |string |对话的群聊id(普通会话时为空) | +|update_time |timestamp |更新时间(每次会话会更新) | +|msgList |array |当前会话聊天数据列表 | +|chatInputContent |string |当前会话的文本框文字内容 | + + +- 统计所有消息的未读数 +```js +let unreadCount = await uniIm.conversation.unreadCount() +``` + +- 删除指定id的会话数据 +```js +// xxx表示会话id +let param = 'xxxx' +await uniIm.conversation.remove(param) +``` + +- 获取系统消息数据 + +1. 不限类型 +```js +let param = null +await uniIm.notification.get(param) +``` + +2. 指定类型(单个) +```js +// uni-im-group-join-request 表示加群通知 +let param = {type:"uni-im-friend-invite"} +await uniIm.notification.get(param) +``` + +3. 指定类型(多个) +```js +// uni-im-group-join-request uni-im-friend-invite 表示加群通知、好友加请求通知 +let param = {type:["uni-im-friend-invite","uni-im-friend-invite"]} +await uniIm.notification.get(param) +``` + +4. 排除类型(单个) +```js +// uni-im-group-join-request 表示加群通知 +let param = {excludeType:"uni-im-friend-invite"} +await uniIm.notification.get(param) +``` + +5. 排除类型(多个) +```js +// uni-im-group-join-request uni-im-friend-invite 表示加群通知、好友加请求通知 +let param = {excludeType:["uni-im-friend-invite","uni-im-friend-invite"]} +await uniIm.notification.get(param) +``` + +- 加载系统消息数据 +参数与`获取系统消息数据`一致 + +- 获取好友数据 +```js +await uniIm.friend.get() +``` + +- 加载更多好友数据 +1. 分页加载 +```js +await uniIm.friend.loadMore() +``` +2. 加载指定好友数据 +```js +let param = {"friend_uid":"xxx"} +await uniIm.friend.loadMore(param) +``` +- 删除好友数据 +```js +let param = {"friend_uid":"xxx"} +await uniIm.friend.remove(param) +``` + +- 获取群聊数据 +```js +await uniIm.group.get() +``` + +- 加载更多群聊数据 +1. 分页加载 +```js +await uniIm.group.loadMore() +``` +2. 加载指定群聊数据 +```js +let param = {"group_id":"xxx"} +await uniIm.group.loadMore(param) +``` +- 删除群聊数据 +```js +let param = {"group_id":"xxx"} +await uniIm.group.remove(param) +``` + +- 添加用户信息到本地用户信息库 +```js +// xxx表示用户数据 +let usersInfo = {xxx} +await uniIm.users.merge(usersInfo) +``` + +#### 工具类库@utils +utils封装了uni-im常用方法的模块,路径:`/uni_modules/uni-im/sdk/utils/index.js` + +|名称 |类型 |说明 |入参 |返回值 | +|-- |-- |-- |-- |-- | +|getConversationId|function |获取会话id |对话的用户id或群id 详见[详见](#get-conversation-id) |无 | +|toFriendlyTime |function |用于将时间戳转友好时间提示(距离当前2小时内的时间戳,每隔一秒钟会刷新一次) |时间戳:timestamp |格式化后的时间字符串。如:x年x月x日,昨天,下午,1小时前等 | + +##### 获取会话id@get-conversation-id +1. 获取单聊会话id +```js +let friend_uid = "xxx" +uniIm.getConversationId(friend_uid,'single') +``` +2. 获取群聊会话id +```js +let group_id = "xxx" +uniIm.getConversationId(group_id,'group') +``` + +## 重要更新说明: +- [V2.0.14,V2.0.13](https://ext.dcloud.net.cn/plugin?id=9711&update_log) 更新解决了:uni-id-users表的触发器`uni-id-users.schema.ext.js`的兼容性问题。 +这个问题可能会和你的项目产生冲突,请升级或者下载最新版的uni-im复制`uni_modules/uni-im/unicloud/database/uni-id-users.schema.ext.js`文件复制到你的项目中以覆盖原文件。 + + +## 项目升级 +uni-im遵循uni-app的插件模块化规范,即:[uni_modules](https://uniapp.dcloud.io/uni_modules)。 + +在项目根目录下的`uni_modules`目录下,以插件ID即`uni-im`为插件文件夹命名,在该目录右键也会看到“从插件市场更新”选项,点击即可更新该插件。也可以用插件市场web界面下载覆盖。 + + +## 许可协议 +uni-im源码使用许可协议 + +2022年10月 + +本许可协议,是数字天堂(北京)网络技术有限公司(以下简称DCloud)对其所拥有著作权的“DCloud uni-im”(以下简称软件),提供的使用许可协议。 + +您对“软件”的复制、使用、修改及分发受本许可协议的条款的约束,如您不接受本协议,则不能使用、复制、修改本软件。 + +授权许可范围 + +a) 授予您永久性的、全球性的、免费的、非独占的、不可撤销的本软件的源码使用许可,您可以使用这些源码制作自己的应用。 + +b) 您只能在DCloud产品体系内使用本软件及其源码。您不能将源码修改后运行在DCloud产品体系之外的环境,比如客户端脱离uni-app,或服务端脱离uniCloud。 + +c) DCloud未向您授权商标使用许可。您在根据本软件源码制作自己的应用时,需以自己的名义发布软件,而不是以DCloud名义发布。 + +d) 本协议不构成代理关系。 + +DCloud的责任限制 +“软件”在提供时不带任何明示或默示的担保。在任何情况下,DCloud不对任何人因使用“软件”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + +您的责任限制 + +a) 您需要在授权许可范围内使用软件。 + +b) 您在分发自己的应用时,不得侵犯DCloud商标和名誉权利。 + +c) 您不得进行破解、反编译、套壳等侵害DCloud知识产权的行为。您不得利用DCloud系统漏洞谋利或侵害DCloud利益,如您发现DCloud系统漏洞应第一时间通知DCloud。您不得进行攻击DCloud的服务器、网络等妨碍DCloud运营的行为。您不得利用DCloud的产品进行与DCloud争夺开发者的行为。 + +d) 如您违反本许可协议,需承担因此给DCloud造成的损失。 + +本协议签订地点为中华人民共和国北京市海淀区。 + +根据发展,DCloud可能会对本协议进行修改。修改时,DCloud会在产品或者网页中显著的位置发布相关信息以便及时通知到用户。如果您选择继续使用本框架,即表示您同意接受这些修改。 + +条款结束 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c3ff205f658fda3bafb49b1b8d13a02d0093b954 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/license.md b/license.md new file mode 100644 index 0000000000000000000000000000000000000000..52be38ad49f1c176bada5847e20f0151079f10d8 --- /dev/null +++ b/license.md @@ -0,0 +1,36 @@ +uni-im源码使用许可协议 + +2022年10月 + +本许可协议,是数字天堂(北京)网络技术有限公司(以下简称DCloud)对其所拥有著作权的“DCloud uni-im”(以下简称软件),提供的使用许可协议。 + +您对“软件”的复制、使用、修改及分发受本许可协议的条款的约束,如您不接受本协议,则不能使用、复制、修改本软件。 + +授权许可范围 + +a) 授予您永久性的、全球性的、免费的、非独占的、不可撤销的本软件的源码使用许可,您可以使用这些源码制作自己的应用。 + +b) 您只能在DCloud产品体系内使用本软件及其源码。您不能将源码修改后运行在DCloud产品体系之外的环境,比如客户端脱离uni-app,或服务端脱离uniCloud。 + +c) DCloud未向您授权商标使用许可。您在根据本软件源码制作自己的应用时,需以自己的名义发布软件,而不是以DCloud名义发布。 + +d) 本协议不构成代理关系。 + +DCloud的责任限制 +“软件”在提供时不带任何明示或默示的担保。在任何情况下,DCloud不对任何人因使用“软件”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + +您的责任限制 + +a) 您需要在授权许可范围内使用软件。 + +b) 您在分发自己的应用时,不得侵犯DCloud商标和名誉权利。 + +c) 您不得进行破解、反编译、套壳等侵害DCloud知识产权的行为。您不得利用DCloud系统漏洞谋利或侵害DCloud利益,如您发现DCloud系统漏洞应第一时间通知DCloud。您不得进行攻击DCloud的服务器、网络等妨碍DCloud运营的行为。您不得利用DCloud的产品进行与DCloud争夺开发者的行为。 + +d) 如您违反本许可协议,需承担因此给DCloud造成的损失。 + +本协议签订地点为中华人民共和国北京市海淀区。 + +根据发展,DCloud可能会对本协议进行修改。修改时,DCloud会在产品或者网页中显著的位置发布相关信息以便及时通知到用户。如果您选择继续使用本框架,即表示您同意接受这些修改。 + +条款结束 \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000000000000000000000000000000000000..44c2d4f08d22e98480e8febc6565bc499e59cc64 --- /dev/null +++ b/main.js @@ -0,0 +1,22 @@ +import App from './App' + +// #ifndef VUE3 +import Vue from 'vue' +Vue.config.productionTip = false +App.mpType = 'app' +const app = new Vue({ + ...App +}) +app.$mount() +// #endif + + +// #ifdef VUE3 +import {createSSRApp} from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app, + } +} +// #endif diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..4b0180607e7fcabcc9777503b3f53de630546050 --- /dev/null +++ b/manifest.json @@ -0,0 +1,159 @@ +{ + "name" : "uni-im", + "appid" : "__UNI__C650F93", + "description" : "uni-im是云端一体的、全平台的、免费的、开源即时通讯系统", + "versionName" : "3.0.0", + "versionCode" : "300", + "transformPx" : false, + "app-plus" : { + "nvueStyleCompiler" : "uni-app", + "usingComponents" : true, + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + "modules" : { + "Record" : {}, + "Camera" : {}, + "Push" : {}, + "VideoPlayer" : {} + }, + "distribute" : { + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ] + }, + "sdkConfigs" : { + "push" : { + "unipush" : { + "version" : "2", + "offline" : true + } + }, + "oauth" : {}, + "ad" : {}, + "maps" : {}, + "speech" : {}, + "statics" : {}, + "share" : {} + }, + "icons" : {}, + "ios" : { + "idfa" : false + } + }, + "nvueCompiler" : "uni-app", + "uniStatistics" : { + "enable" : false + } + }, + "quickapp" : {}, + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : true, + "es6" : false + }, + "usingComponents" : true, + "unipush" : { + "enable" : true + }, + "uniStatistics" : { + "enable" : false + } + }, + "mp-alipay" : { + "usingComponents" : true, + "uniStatistics" : { + "enable" : false + } + }, + "mp-baidu" : { + "usingComponents" : true, + "uniStatistics" : { + "enable" : false + } + }, + "mp-toutiao" : { + "usingComponents" : true, + "uniStatistics" : { + "enable" : false + } + }, + "uniStatistics" : { + "enable" : false, + "version" : "2" + }, + "vueVersion" : "3", + "h5" : { + "unipush" : { + "enable" : true + }, + "devServer" : { + "port" : 8080, + "disableHostCheck" : true, + "https" : false + }, + "optimization" : { + "treeShaking" : { + "enable" : false + } + }, + "uniStatistics" : { + "enable" : false + } + }, + "fallbackLocale" : "zh-Hans", + "mp-jd" : { + "uniStatistics" : { + "enable" : false + } + }, + "mp-kuaishou" : { + "uniStatistics" : { + "enable" : false + } + }, + "mp-lark" : { + "uniStatistics" : { + "enable" : false + } + }, + "mp-qq" : { + "uniStatistics" : { + "enable" : false + } + }, + "quickapp-webview-huawei" : { + "uniStatistics" : { + "enable" : false + } + }, + "quickapp-webview-union" : { + "uniStatistics" : { + "enable" : false + } + } +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b3f8cd09d35f0e9ccdb7c1046fd4ebf089669940 --- /dev/null +++ b/pages.json @@ -0,0 +1,299 @@ +{ + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + } + // #ifndef MP-WEIXIN + ,{ + "path": "uni_modules/uni-im/pages/chat/chat", + "style": { + "navigationBarTitleText": "", + "enablePullDownRefresh": false, + "app-plus": { + "titleNView": { + "buttons": [{ + "color": "#333", + "colorPressed": "#111111", + "float": "right", + "text": "...", + "fontSize": 26, + "onclick": "more" + }] + } + } + } + } + // #endif + ,{ + "path": "uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify", + "style": { + "enablePullDownRefresh": false, + "navigationBarTitleText": "实名认证" + } +} +], + "subPackages": [ + { + "root": "uni_modules/uni-im/pages", + "pages": [{ + "path": "index/index", + "style": { + "navigationBarTitleText": "会话列表", + "enablePullDownRefresh": false, + "app-plus": { + "titleNView": { + "buttons": [{ + "color": "#999999", + "colorPressed": "#111111", + "float": "right", + "text": "我的", + "fontSize": 14, + "onclick": "toLogin" + }] + } + } + } + }, + { + "path": "userList/userList", + "style": { + "navigationBarTitleText": "用户列表", + "enablePullDownRefresh": true + } + }, + // #ifdef MP-WEIXIN + { + "path": "chat/chat", + "style": { + "navigationBarTitleText": "", + "enablePullDownRefresh": false, + "app-plus": { + "titleNView": { + "buttons": [{ + "color": "#333", + "colorPressed": "#111111", + "float": "right", + "text": "...", + "fontSize": 26, + "onclick": "more" + }] + } + } + } + }, + // #endif + { + "path": "common/video/video", + "style": { + "navigationBarTitleText": "", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } + + }, + { + "path": "group/info", + "style": { + "navigationBarTitleText": "群信息", + "enablePullDownRefresh": false, + "app-plus": { + "titleNView": { + "buttons": [{ + "color": "#333", + "colorPressed": "#111111", + "float": "right", + "text": "管理", + "fontSize": 16, + "onclick": "more" + }] + } + } + } + + }, { + "path": "contacts/notification/notification", + "style": { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + + }, { + "path": "contacts/contacts", + "style": { + "navigationBarTitleText": "通讯录", + "enablePullDownRefresh": false + } + + }, { + "path": "contacts/addPeopleGroups/addPeopleGroups", + "style": { + "navigationStyle": "custom", + "enablePullDownRefresh": false + } + + }, { + "path": "contacts/createGroup/createGroup", + "style": { + "navigationBarTitleText": "创建群聊", + "enablePullDownRefresh": false, + "maxWidth": 950 + } + + }, { + "path": "group/groupQRCode", + "style": { + "navigationBarTitleText": "群聊二维码", + "enablePullDownRefresh": false + } + + }, { + "path": "contacts/groupList/groupList", + "style": { + "navigationBarTitleText": "我的群聊", + "enablePullDownRefresh": false + } + + }, { + "path": "chat/info", + "style": { + "navigationBarTitleText": "聊天设置", + "enablePullDownRefresh": false + } + } + ] + }, + { + "root": "uni_modules/uni-id-pages/pages", + "pages": [{ + "path": "userinfo/userinfo", + "style": { + "navigationBarTitleText": "个人资料" + } + }, + { + "path": "login/login-withoutpwd" + }, + { + "path": "login/login-withpwd" + }, + { + "path": "userinfo/deactivate/deactivate", + "style": { + "navigationBarTitleText": "注销账号" + } + }, + { + "path": "userinfo/bind-mobile/bind-mobile", + "style": { + "navigationBarTitleText": "绑定手机号码" + } + }, + { + "path": "login/login-smscode", + "style": { + "navigationBarTitleText": "手机验证码登录" + } + }, + { + "path": "register/register", + "style": { + "navigationBarTitleText": "注册" + } + }, + { + "path": "retrieve/retrieve", + "style": { + "navigationBarTitleText": "重置密码" + } + }, { + "path": "common/webview/webview", + "style": { + "enablePullDownRefresh": false, + "navigationBarTitleText": "" + } + }, { + "path": "userinfo/change_pwd/change_pwd", + "style": { + "enablePullDownRefresh": false, + "navigationBarTitleText": "修改密码" + } + }, { + "path": "register/register-by-email", + "style": { + "navigationBarTitleText": "邮箱验证码注册" + } + }, { + "path": "retrieve/retrieve-by-email", + "style": { + "navigationBarTitleText": "通过邮箱重置密码" + } + }, + { + "path": "userinfo/set-pwd/set-pwd", + "style": { + "enablePullDownRefresh": false, + "navigationBarTitleText": "设置密码" + } + } + // #ifdef H5 + , + { + "path": "userinfo/cropImage/cropImage" + }, + { + "path": "register/register-admin", + "style": { + "enablePullDownRefresh": false, + "navigationBarTitleText": "注册管理员账号" + } + } + // #endif + ] + } + ], + // #ifndef MP-WEIXIN + "tabBar": { + "color": "#999999", + "selectedColor": "#38BC48", + "borderStyle": "black", + "backgroundColor": "#FFFFFF", + "list": [{ + "pagePath": "uni_modules/uni-im/pages/index/index", + "text": "会话", + "iconPath": "uni_modules/uni-im/static/tabbarIcon/chat.png", + "selectedIconPath": "uni_modules/uni-im/static/tabbarIcon/chatex.png" + }, + { + "pagePath": "uni_modules/uni-im/pages/userList/userList", + "text": "用户列表", + "iconPath": "uni_modules/uni-im/static/tabbarIcon/contacts.png", + "selectedIconPath": "uni_modules/uni-im/static/tabbarIcon/contactsex.png" + }, + { + "pagePath": "uni_modules/uni-im/pages/contacts/contacts", + "text": "通讯录", + "iconPath": "uni_modules/uni-im/static/tabbarIcon/contacts.png", + "selectedIconPath": "uni_modules/uni-im/static/tabbarIcon/contactsex.png" + } + ] + }, + // #endif + "uniIdRouter": { + "loginPage": "uni_modules/uni-id-pages/pages/login/login-withpwd", + "needLogin": [ + "uni_modules/uni-im/pages/userList/userList", + "uni_modules/uni-im/pages/contacts/contacts", + "pages/index/index" + ] + }, + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-app", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + } +} diff --git a/pages/index/index.vue b/pages/index/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..c61330c1cf9e7ef7ffae880741369649262eb9c2 --- /dev/null +++ b/pages/index/index.vue @@ -0,0 +1,70 @@ + + + + + \ No newline at end of file diff --git a/rollup-plugin-uniapp-cementing.js b/rollup-plugin-uniapp-cementing.js new file mode 100644 index 0000000000000000000000000000000000000000..e32040ff4d8d25ed6ea7ba8e388c49c491dafd33 --- /dev/null +++ b/rollup-plugin-uniapp-cementing.js @@ -0,0 +1,219 @@ +const PLUGIN_NAME = 'rollup-plugin-uniapp-cementing'; +const CEMENTING_MODULE = '_uni_cementing_component.vue' + +import fs from 'fs'; +import path from 'path'; +const SEP = '****************************************************************' +const DEBUG_FILE = 'debug.log' + +let logReset = function() { + fs.writeFileSync( + path.join(__dirname, DEBUG_FILE), + `${SEP}\n* ${new Date().toLocaleString()}\n${SEP}\n` + ) +} + +let logTitle = function(title) { + let msg = `${SEP}\n* ${title}\n${SEP}` + log(msg) +} + +let log = function(...args) { + let msgs = [] + for (let msg of args) { + if (typeof msg === 'string') { + msgs.push(msg) + } else { + msgs.push(JSON.stringify(msg, null, 2)) + } + } + fs.writeFileSync( + path.join(__dirname, DEBUG_FILE), + `${msgs.join(' ')}\n`, { + flag: 'a' + } + ) +} + +import { createFilter } from '@rollup/pluginutils'; +import MagicString from 'magic-string'; +import { walk } from 'estree-walker'; + +function walkTo(node, matchers = []) { + let cur = node + let matcher = matchers.shift() + while (matcher) { + let found + walk(cur, { + enter: function(node, parent, prop, index) { + if (found) return + if (typeof matcher == 'function' && matcher(node) || matcher === node.type) { + found = node + } + } + }) + if (!found) return + cur = found + if (matchers.length == 0) return cur + matcher = matchers.shift() + } +} + +function cementingPlugin(options = {}) { + const { + platforms = ['app', /^mp(\-.*)?$/], + include = ['*.vue', '*.nvue'], + exclude, + resolve = __dirname, + debug, + components = {} + } = options + + let enabled = false + for (let platform of platforms) { + if (platform instanceof RegExp) { + if (platform.exec(process.env['UNI_PLATFORM'])) { + enabled = true + } + } else if (platform === process.env['UNI_PLATFORM']) { + enabled = true + } + } + if (!enabled) return false + if (!debug) { + logReset = logTitle = log = () => {} + } + + let filter = createFilter(include, exclude, { resolve }) + + return { + name: PLUGIN_NAME, + enforce: 'pre', + + buildStart(options) { + logReset() + // log(options) + }, + + // 对使用了动态组件 的组件进行静态化处理。 + transform(code, id) { + if (!filter(id)) return + logTitle('transform: ' + id) + + // 检查组件代码是否使用了动态组件 + let re = /(|<\/component>)/sg + let m = re.exec(code) + if (!m) return + // log('使用了动态组件的代码:', code) + + // 把 + + + + diff --git a/uni_modules/uni-data-checkbox/package.json b/uni_modules/uni-data-checkbox/package.json new file mode 100644 index 0000000000000000000000000000000000000000..fc15e8b2b69ef323b2848ffb817cc78c6ce918f5 --- /dev/null +++ b/uni_modules/uni-data-checkbox/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-data-checkbox", + "displayName": "uni-data-checkbox 数据选择器", + "version": "1.0.5", + "description": "通过数据驱动的单选框和复选框", + "keywords": [ + "uni-ui", + "checkbox", + "单选", + "多选", + "单选多选" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.1" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-load-more","uni-scss"], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uni_modules/uni-data-checkbox/readme.md b/uni_modules/uni-data-checkbox/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..6eb253d426b901544142e92748696e89fe0bb2f8 --- /dev/null +++ b/uni_modules/uni-data-checkbox/readme.md @@ -0,0 +1,18 @@ + + +## DataCheckbox 数据驱动的单选复选框 +> **组件名:uni-data-checkbox** +> 代码块: `uDataCheckbox` + + +本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括: + +1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能 +2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验 +3. 本组件合并了单选多选 +4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性 + +在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uni_modules/uni-easyinput/changelog.md b/uni_modules/uni-easyinput/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..6677b079c7b732529ee72f885ea92262a9c56687 --- /dev/null +++ b/uni_modules/uni-easyinput/changelog.md @@ -0,0 +1,113 @@ +## 1.1.18(2024-04-11) +- 修复 easyinput组件双向绑定问题 +## 1.1.17(2024-03-28) +- 修复 在头条小程序下丢失事件绑定的问题 +## 1.1.16(2024-03-20) +- 修复 在密码输入情况下 清除和小眼睛覆盖bug 在edge浏览器下显示双眼睛bug +## 1.1.15(2024-02-21) +- 新增 左侧插槽:left +## 1.1.14(2024-02-19) +- 修复 onBlur的emit传值错误 +## 1.1.12(2024-01-29) +- 补充 adjust-position文档属性补充 +## 1.1.11(2024-01-29) +- 补充 adjust-position属性传递值:(Boolean)当键盘弹起时,是否自动上推页面 +## 1.1.10(2024-01-22) +- 去除 移除无用的log输出 +## 1.1.9(2023-04-11) +- 修复 vue3 下 keyboardheightchange 事件报错的bug +## 1.1.8(2023-03-29) +- 优化 trim 属性默认值 +## 1.1.7(2023-03-29) +- 新增 cursor-spacing 属性 +## 1.1.6(2023-01-28) +- 新增 keyboardheightchange 事件,可监听键盘高度变化 +## 1.1.5(2022-11-29) +- 优化 主题样式 +## 1.1.4(2022-10-27) +- 修复 props 中背景颜色无默认值的bug +## 1.1.0(2022-06-30) + +- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容 +- 新增 clear 事件,点击右侧叉号图标触发 +- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发 +- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等 + +## 1.0.5(2022-06-07) + +- 优化 clearable 显示策略 + +## 1.0.4(2022-06-07) + +- 优化 clearable 显示策略 + +## 1.0.3(2022-05-20) + +- 修复 关闭图标某些情况下无法取消的 bug + +## 1.0.2(2022-04-12) + +- 修复 默认值不生效的 bug + +## 1.0.1(2022-04-02) + +- 修复 value 不能为 0 的 bug + +## 1.0.0(2021-11-19) + +- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput) + +## 0.1.4(2021-08-20) + +- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug + +## 0.1.3(2021-08-11) + +- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 + +## 0.1.2(2021-07-30) + +- 优化 vue3 下事件警告的问题 + +## 0.1.1 + +- 优化 errorMessage 属性支持 Boolean 类型 + +## 0.1.0(2021-07-13) + +- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) + +## 0.0.16(2021-06-29) + +- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug + +## 0.0.15(2021-06-21) + +- 修复 passwordIcon 属性拼写错误的 bug + +## 0.0.14(2021-06-18) + +- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标 +- 修复 confirmType 属性不生效的问题 + +## 0.0.13(2021-06-04) + +- 修复 disabled 状态可清出内容的 bug + +## 0.0.12(2021-05-12) + +- 新增 组件示例地址 + +## 0.0.11(2021-05-07) + +- 修复 input-border 属性不生效的问题 + +## 0.0.10(2021-04-30) + +- 修复 ios 遮挡文字、显示一半的问题 + +## 0.0.9(2021-02-05) + +- 调整为 uni_modules 目录规范 +- 优化 兼容 nvue 页面 diff --git a/uni_modules/uni-easyinput/components/uni-easyinput/common.js b/uni_modules/uni-easyinput/components/uni-easyinput/common.js new file mode 100644 index 0000000000000000000000000000000000000000..d2cf040d679442eab1a77a54265de42cfcbbf601 --- /dev/null +++ b/uni_modules/uni-easyinput/components/uni-easyinput/common.js @@ -0,0 +1,54 @@ +/** + * @desc 函数防抖 + * @param func 目标函数 + * @param wait 延迟执行毫秒数 + * @param immediate true - 立即执行, false - 延迟执行 + */ +export const debounce = function(func, wait = 1000, immediate = true) { + let timer; + return function() { + let context = this, + args = arguments; + if (timer) clearTimeout(timer); + if (immediate) { + let callNow = !timer; + timer = setTimeout(() => { + timer = null; + }, wait); + if (callNow) func.apply(context, args); + } else { + timer = setTimeout(() => { + func.apply(context, args); + }, wait) + } + } +} +/** + * @desc 函数节流 + * @param func 函数 + * @param wait 延迟执行毫秒数 + * @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发 + */ +export const throttle = (func, wait = 1000, type = 1) => { + let previous = 0; + let timeout; + return function() { + let context = this; + let args = arguments; + if (type === 1) { + let now = Date.now(); + + if (now - previous > wait) { + func.apply(context, args); + previous = now; + } + } else if (type === 2) { + if (!timeout) { + timeout = setTimeout(() => { + timeout = null; + func.apply(context, args) + }, wait) + } + } + } +} diff --git a/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue b/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue new file mode 100644 index 0000000000000000000000000000000000000000..d41411b048cbe5ac6400c6ffbbb4e51f49f72c99 --- /dev/null +++ b/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue @@ -0,0 +1,693 @@ + + + + + diff --git a/uni_modules/uni-easyinput/package.json b/uni_modules/uni-easyinput/package.json new file mode 100644 index 0000000000000000000000000000000000000000..62bbff56a1048ebe66c6452088208e82ff089eb3 --- /dev/null +++ b/uni_modules/uni-easyinput/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-easyinput", + "displayName": "uni-easyinput 增强输入框", + "version": "1.1.18", + "description": "Easyinput 组件是对原生input组件的增强", + "keywords": [ + "uni-ui", + "uniui", + "input", + "uni-easyinput", + "输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y", + "alipay": "n" + }, + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-easyinput/readme.md b/uni_modules/uni-easyinput/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..f1faf8fbbba31f9f404116b32078b1603487d279 --- /dev/null +++ b/uni_modules/uni-easyinput/readme.md @@ -0,0 +1,11 @@ + + +### Easyinput 增强输入框 +> **组件名:uni-easyinput** +> 代码块: `uEasyinput` + + +easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uni_modules/uni-forms/changelog.md b/uni_modules/uni-forms/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..3d998bcf591d5c76c44166f0cda6b89021bf4160 --- /dev/null +++ b/uni_modules/uni-forms/changelog.md @@ -0,0 +1,94 @@ +## 1.4.10(2023-11-03) +- 优化 labelWidth 描述错误 +## 1.4.9(2023-02-10) +- 修复 required 参数无法动态绑定 +## 1.4.8(2022-08-23) +- 优化 根据 rules 自动添加 required 的问题 +## 1.4.7(2022-08-22) +- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540) +## 1.4.6(2022-07-13) +- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug +## 1.4.5(2022-07-05) +- 新增 更多表单示例 +- 优化 子表单组件过期提示的问题 +- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式 +## 1.4.4(2022-07-04) +- 更新 删除组件日志 +## 1.4.3(2022-07-04) +- 修复 由 1.4.0 引发的 label 插槽不生效的bug +## 1.4.2(2022-07-04) +- 修复 子组件找不到 setValue 报错的bug +## 1.4.1(2022-07-04) +- 修复 uni-data-picker 在 uni-forms-item 中报错的bug +- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +## 1.4.0(2022-06-30) +- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题 +- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力 +- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃 +- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效 +- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法 +- 新增 子表单的 setRules 方法,配合自定义校验函数使用 +- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则 +- 优化 动态表单校验方式,废弃拼接name的方式 +## 1.3.3(2022-06-22) +- 修复 表单校验顺序无序问题 +## 1.3.2(2021-12-09) +- +## 1.3.1(2021-11-19) +- 修复 label 插槽不生效的bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms) +## 1.2.7(2021-08-13) +- 修复 没有添加校验规则的字段依然报错的Bug +## 1.2.6(2021-08-11) +- 修复 重置表单错误信息无法清除的问题 +## 1.2.5(2021-08-11) +- 优化 组件文档 +## 1.2.4(2021-08-11) +- 修复 表单验证只生效一次的问题 +## 1.2.3(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.2(2021-07-26) +- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug +- 修复 1.2.1 引起的示例在小程序平台报错的Bug +## 1.2.1(2021-07-22) +- 修复 动态校验表单,默认值为空的情况下校验失效的Bug +- 修复 不指定name属性时,运行报错的Bug +- 优化 label默认宽度从65调整至70,使required为true且四字时不换行 +- 优化 组件示例,新增动态校验示例代码 +- 优化 组件文档,使用方式更清晰 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-06-25) +- 修复 pattern 属性在微信小程序平台无效的问题 +## 1.1.1(2021-06-22) +- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug +## 1.1.0(2021-06-22) +- 修复 只写setRules方法而导致校验不生效的Bug +- 修复 由上个办法引发的错误提示文字错位的Bug +## 1.0.48(2021-06-21) +- 修复 不设置 label 属性 ,无法设置label插槽的问题 +## 1.0.47(2021-06-21) +- 修复 不设置label属性,label-width属性不生效的bug +- 修复 setRules 方法与rules属性冲突的问题 +## 1.0.46(2021-06-04) +- 修复 动态删减数据导致报错的问题 +## 1.0.45(2021-06-04) +- 新增 modelValue 属性 ,value 即将废弃 +## 1.0.44(2021-06-02) +- 新增 uni-forms-item 可以设置单独的 rules +- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤 +- 优化 submit 事件重命名为 validate +## 1.0.43(2021-05-12) +- 新增 组件示例地址 +## 1.0.42(2021-04-30) +- 修复 自定义检验器失效的问题 +## 1.0.41(2021-03-05) +- 更新 校验器 +- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug +## 1.0.40(2021-03-04) +- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug +## 1.0.39(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 校验器传入 int 等类型 ,返回String类型的Bug diff --git a/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue new file mode 100644 index 0000000000000000000000000000000000000000..59b0528d471fde6420dcd7d56c1bae8579a2658e --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue @@ -0,0 +1,627 @@ + + + + + diff --git a/uni_modules/uni-forms/components/uni-forms/uni-forms.vue b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue new file mode 100644 index 0000000000000000000000000000000000000000..b484cc9dfa0b1b4e579ae1a1534067628e2b8a5e --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/uni_modules/uni-forms/components/uni-forms/utils.js b/uni_modules/uni-forms/components/uni-forms/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..31d57f41031ee6b5ebd01d0a091a17611ae270c3 --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/utils.js @@ -0,0 +1,293 @@ +/** + * 简单处理对象拷贝 + * @param {Obejct} 被拷贝对象 + * @@return {Object} 拷贝对象 + */ +export const deepCopy = (val) => { + return JSON.parse(JSON.stringify(val)) +} +/** + * 过滤数字类型 + * @param {String} format 数字类型 + * @@return {Boolean} 返回是否为数字类型 + */ +export const typeFilter = (format) => { + return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp'; +} + +/** + * 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined + * @param {String} key 字段名 + * @param {any} value 字段值 + * @param {Object} rules 表单校验规则 + */ +export const getValue = (key, value, rules) => { + const isRuleNumType = rules.find(val => val.format && typeFilter(val.format)); + const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool'); + // 输入类型为 number + if (!!isRuleNumType) { + if (!value && value !== 0) { + value = null + } else { + value = isNumber(Number(value)) ? Number(value) : value + } + } + + // 输入类型为 boolean + if (!!isRuleBoolType) { + value = isBoolean(value) ? value : false + } + + return value; +} + +/** + * 获取表单数据 + * @param {String|Array} name 真实名称,需要使用 realName 获取 + * @param {Object} data 原始数据 + * @param {any} value 需要设置的值 + */ +export const setDataValue = (field, formdata, value) => { + formdata[field] = value + return value || '' +} + +/** + * 获取表单数据 + * @param {String|Array} field 真实名称,需要使用 realName 获取 + * @param {Object} data 原始数据 + */ +export const getDataValue = (field, data) => { + return objGet(data, field) +} + +/** + * 获取表单类型 + * @param {String|Array} field 真实名称,需要使用 realName 获取 + */ +export const getDataValueType = (field, data) => { + const value = getDataValue(field, data) + return { + type: type(value), + value + } +} + +/** + * 获取表单可用的真实name + * @param {String|Array} name 表单name + * @@return {String} 表单可用的真实name + */ +export const realName = (name, data = {}) => { + const base_name = _basePath(name) + if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) { + const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_') + return realname + } + return base_name[0] || name +} + +/** + * 判断是否表单可用的真实name + * @param {String|Array} name 表单name + * @@return {String} 表单可用的真实name + */ +export const isRealName = (name) => { + const reg = /^_formdata_#*/ + return reg.test(name) +} + +/** + * 获取表单数据的原始格式 + * @@return {Object|Array} object 需要解析的数据 + */ +export const rawData = (object = {}, name) => { + let newData = JSON.parse(JSON.stringify(object)) + let formData = {} + for(let i in newData){ + let path = name2arr(i) + objSet(formData,path,newData[i]) + } + return formData +} + +/** + * 真实name还原为 array + * @param {*} name + */ +export const name2arr = (name) => { + let field = name.replace('_formdata_#', '') + field = field.split('#').map(v => (isNumber(v) ? Number(v) : v)) + return field +} + +/** + * 对象中设置值 + * @param {Object|Array} object 源数据 + * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] + * @param {String} value 需要设置的值 + */ +export const objSet = (object, path, value) => { + if (typeof object !== 'object') return object; + _basePath(path).reduce((o, k, i, _) => { + if (i === _.length - 1) { + // 若遍历结束直接赋值 + o[k] = value + return null + } else if (k in o) { + // 若存在对应路径,则返回找到的对象,进行下一次遍历 + return o[k] + } else { + // 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象 + o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {} + return o[k] + } + }, object) + // 返回object + return object; +} + +// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用 +function _basePath(path) { + // 若是数组,则直接返回 + if (Array.isArray(path)) return path + // 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']' + return path.replace(/\[/g, '.').replace(/\]/g, '').split('.') +} + +/** + * 从对象中获取值 + * @param {Object|Array} object 源数据 + * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] + * @param {String} defaultVal 如果无法从调用链中获取值的默认值 + */ +export const objGet = (object, path, defaultVal = 'undefined') => { + // 先将path处理成统一格式 + let newPath = _basePath(path) + // 递归处理,返回最后结果 + let val = newPath.reduce((o, k) => { + return (o || {})[k] + }, object); + return !val || val !== undefined ? val : defaultVal +} + + +/** + * 是否为 number 类型 + * @param {any} num 需要判断的值 + * @return {Boolean} 是否为 number + */ +export const isNumber = (num) => { + return !isNaN(Number(num)) +} + +/** + * 是否为 boolean 类型 + * @param {any} bool 需要判断的值 + * @return {Boolean} 是否为 boolean + */ +export const isBoolean = (bool) => { + return (typeof bool === 'boolean') +} +/** + * 是否有必填字段 + * @param {Object} rules 规则 + * @return {Boolean} 是否有必填字段 + */ +export const isRequiredField = (rules) => { + let isNoField = false; + for (let i = 0; i < rules.length; i++) { + const ruleData = rules[i]; + if (ruleData.required) { + isNoField = true; + break; + } + } + return isNoField; +} + + +/** + * 获取数据类型 + * @param {Any} obj 需要获取数据类型的值 + */ +export const type = (obj) => { + var class2type = {}; + + // 生成class2type映射 + "Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) { + class2type["[object " + item + "]"] = item.toLowerCase(); + }) + if (obj == null) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[Object.prototype.toString.call(obj)] || "object" : + typeof obj; +} + +/** + * 判断两个值是否相等 + * @param {any} a 值 + * @param {any} b 值 + * @return {Boolean} 是否相等 + */ +export const isEqual = (a, b) => { + //如果a和b本来就全等 + if (a === b) { + //判断是否为0和-0 + return a !== 0 || 1 / a === 1 / b; + } + //判断是否为null和undefined + if (a == null || b == null) { + return a === b; + } + //接下来判断a和b的数据类型 + var classNameA = toString.call(a), + classNameB = toString.call(b); + //如果数据类型不相等,则返回false + if (classNameA !== classNameB) { + return false; + } + //如果数据类型相等,再根据不同数据类型分别判断 + switch (classNameA) { + case '[object RegExp]': + case '[object String]': + //进行字符串转换比较 + return '' + a === '' + b; + case '[object Number]': + //进行数字转换比较,判断是否为NaN + if (+a !== +a) { + return +b !== +b; + } + //判断是否为0或-0 + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + return +a === +b; + } + //如果是对象类型 + if (classNameA == '[object Object]') { + //获取a和b的属性长度 + var propsA = Object.getOwnPropertyNames(a), + propsB = Object.getOwnPropertyNames(b); + if (propsA.length != propsB.length) { + return false; + } + for (var i = 0; i < propsA.length; i++) { + var propName = propsA[i]; + //如果对应属性对应值不相等,则返回false + if (a[propName] !== b[propName]) { + return false; + } + } + return true; + } + //如果是数组类型 + if (classNameA == '[object Array]') { + if (a.toString() == b.toString()) { + return true; + } + return false; + } +} diff --git a/uni_modules/uni-forms/components/uni-forms/validate.js b/uni_modules/uni-forms/components/uni-forms/validate.js new file mode 100644 index 0000000000000000000000000000000000000000..c29ef7fa2db0193f572a7b2d546ed08c5c056046 --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/validate.js @@ -0,0 +1,486 @@ +var pattern = { + email: /^\S+?@\S+?\.\S+?$/, + idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, + url: new RegExp( + "^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", + 'i') +}; + +const FORMAT_MAPPING = { + "int": 'integer', + "bool": 'boolean', + "double": 'number', + "long": 'number', + "password": 'string' + // "fileurls": 'array' +} + +function formatMessage(args, resources = '') { + var defaultMessage = ['label'] + defaultMessage.forEach((item) => { + if (args[item] === undefined) { + args[item] = '' + } + }) + + let str = resources + for (let key in args) { + let reg = new RegExp('{' + key + '}') + str = str.replace(reg, args[key]) + } + return str +} + +function isEmptyValue(value, type) { + if (value === undefined || value === null) { + return true; + } + + if (typeof value === 'string' && !value) { + return true; + } + + if (Array.isArray(value) && !value.length) { + return true; + } + + if (type === 'object' && !Object.keys(value).length) { + return true; + } + + return false; +} + +const types = { + integer(value) { + return types.number(value) && parseInt(value, 10) === value; + }, + string(value) { + return typeof value === 'string'; + }, + number(value) { + if (isNaN(value)) { + return false; + } + return typeof value === 'number'; + }, + "boolean": function(value) { + return typeof value === 'boolean'; + }, + "float": function(value) { + return types.number(value) && !types.integer(value); + }, + array(value) { + return Array.isArray(value); + }, + object(value) { + return typeof value === 'object' && !types.array(value); + }, + date(value) { + return value instanceof Date; + }, + timestamp(value) { + if (!this.integer(value) || Math.abs(value).toString().length > 16) { + return false + } + return true; + }, + file(value) { + return typeof value.url === 'string'; + }, + email(value) { + return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255; + }, + url(value) { + return typeof value === 'string' && !!value.match(pattern.url); + }, + pattern(reg, value) { + try { + return new RegExp(reg).test(value); + } catch (e) { + return false; + } + }, + method(value) { + return typeof value === 'function'; + }, + idcard(value) { + return typeof value === 'string' && !!value.match(pattern.idcard); + }, + 'url-https'(value) { + return this.url(value) && value.startsWith('https://'); + }, + 'url-scheme'(value) { + return value.startsWith('://'); + }, + 'url-web'(value) { + return false; + } +} + +class RuleValidator { + + constructor(message) { + this._message = message + } + + async validateRule(fieldKey, fieldValue, value, data, allData) { + var result = null + + let rules = fieldValue.rules + + let hasRequired = rules.findIndex((item) => { + return item.required + }) + if (hasRequired < 0) { + if (value === null || value === undefined) { + return result + } + if (typeof value === 'string' && !value.length) { + return result + } + } + + var message = this._message + + if (rules === undefined) { + return message['default'] + } + + for (var i = 0; i < rules.length; i++) { + let rule = rules[i] + let vt = this._getValidateType(rule) + + Object.assign(rule, { + label: fieldValue.label || `["${fieldKey}"]` + }) + + if (RuleValidatorHelper[vt]) { + result = RuleValidatorHelper[vt](rule, value, message) + if (result != null) { + break + } + } + + if (rule.validateExpr) { + let now = Date.now() + let resultExpr = rule.validateExpr(value, allData, now) + if (resultExpr === false) { + result = this._getMessage(rule, rule.errorMessage || this._message['default']) + break + } + } + + if (rule.validateFunction) { + result = await this.validateFunction(rule, value, data, allData, vt) + if (result !== null) { + break + } + } + } + + if (result !== null) { + result = message.TAG + result + } + + return result + } + + async validateFunction(rule, value, data, allData, vt) { + let result = null + try { + let callbackMessage = null + const res = await rule.validateFunction(rule, value, allData || data, (message) => { + callbackMessage = message + }) + if (callbackMessage || (typeof res === 'string' && res) || res === false) { + result = this._getMessage(rule, callbackMessage || res, vt) + } + } catch (e) { + result = this._getMessage(rule, e.message, vt) + } + return result + } + + _getMessage(rule, message, vt) { + return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default']) + } + + _getValidateType(rule) { + var result = '' + if (rule.required) { + result = 'required' + } else if (rule.format) { + result = 'format' + } else if (rule.arrayType) { + result = 'arrayTypeFormat' + } else if (rule.range) { + result = 'range' + } else if (rule.maximum !== undefined || rule.minimum !== undefined) { + result = 'rangeNumber' + } else if (rule.maxLength !== undefined || rule.minLength !== undefined) { + result = 'rangeLength' + } else if (rule.pattern) { + result = 'pattern' + } else if (rule.validateFunction) { + result = 'validateFunction' + } + return result + } +} + +const RuleValidatorHelper = { + required(rule, value, message) { + if (rule.required && isEmptyValue(value, rule.format || typeof value)) { + return formatMessage(rule, rule.errorMessage || message.required); + } + + return null + }, + + range(rule, value, message) { + const { + range, + errorMessage + } = rule; + + let list = new Array(range.length); + for (let i = 0; i < range.length; i++) { + const item = range[i]; + if (types.object(item) && item.value !== undefined) { + list[i] = item.value; + } else { + list[i] = item; + } + } + + let result = false + if (Array.isArray(value)) { + result = (new Set(value.concat(list)).size === list.length); + } else { + if (list.indexOf(value) > -1) { + result = true; + } + } + + if (!result) { + return formatMessage(rule, errorMessage || message['enum']); + } + + return null + }, + + rangeNumber(rule, value, message) { + if (!types.number(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let { + minimum, + maximum, + exclusiveMinimum, + exclusiveMaximum + } = rule; + let min = exclusiveMinimum ? value <= minimum : value < minimum; + let max = exclusiveMaximum ? value >= maximum : value > maximum; + + if (minimum !== undefined && min) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ? + 'exclusiveMinimum' : 'minimum' + ]) + } else if (maximum !== undefined && max) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ? + 'exclusiveMaximum' : 'maximum' + ]) + } else if (minimum !== undefined && maximum !== undefined && (min || max)) { + return formatMessage(rule, rule.errorMessage || message['number'].range) + } + + return null + }, + + rangeLength(rule, value, message) { + if (!types.string(value) && !types.array(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let min = rule.minLength; + let max = rule.maxLength; + let val = value.length; + + if (min !== undefined && val < min) { + return formatMessage(rule, rule.errorMessage || message['length'].minLength) + } else if (max !== undefined && val > max) { + return formatMessage(rule, rule.errorMessage || message['length'].maxLength) + } else if (min !== undefined && max !== undefined && (val < min || val > max)) { + return formatMessage(rule, rule.errorMessage || message['length'].range) + } + + return null + }, + + pattern(rule, value, message) { + if (!types['pattern'](rule.pattern, value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + return null + }, + + format(rule, value, message) { + var customTypes = Object.keys(types); + var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType); + + if (customTypes.indexOf(format) > -1) { + if (!types[format](value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + } + + return null + }, + + arrayTypeFormat(rule, value, message) { + if (!Array.isArray(value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + + for (let i = 0; i < value.length; i++) { + const element = value[i]; + let formatResult = this.format(rule, element, message) + if (formatResult !== null) { + return formatResult + } + } + + return null + } +} + +class SchemaValidator extends RuleValidator { + + constructor(schema, options) { + super(SchemaValidator.message); + + this._schema = schema + this._options = options || null + } + + updateSchema(schema) { + this._schema = schema + } + + async validate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, false, allData) + } + return result.length ? result[0] : null + } + + async validateAll(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, true, allData) + } + return result + } + + async validateUpdate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidateUpdate(data, false, allData) + } + return result.length ? result[0] : null + } + + async invokeValidate(data, all, allData) { + let result = [] + let schema = this._schema + for (let key in schema) { + let value = schema[key] + let errorMessage = await this.validateRule(key, value, data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + async invokeValidateUpdate(data, all, allData) { + let result = [] + for (let key in data) { + let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + _checkFieldInSchema(data) { + var keys = Object.keys(data) + var keys2 = Object.keys(this._schema) + if (new Set(keys.concat(keys2)).size === keys2.length) { + return '' + } + + var noExistFields = keys.filter((key) => { + return keys2.indexOf(key) < 0; + }) + var errorMessage = formatMessage({ + field: JSON.stringify(noExistFields) + }, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid']) + return [{ + key: 'invalid', + errorMessage + }] + } +} + +function Message() { + return { + TAG: "", + default: '验证错误', + defaultInvalid: '提交的字段{field}在数据库中并不存在', + validateFunction: '验证无效', + required: '{label}必填', + 'enum': '{label}超出范围', + timestamp: '{label}格式无效', + whitespace: '{label}不能为空', + typeError: '{label}类型无效', + date: { + format: '{label}日期{value}格式无效', + parse: '{label}日期无法解析,{value}无效', + invalid: '{label}日期{value}无效' + }, + length: { + minLength: '{label}长度不能少于{minLength}', + maxLength: '{label}长度不能超过{maxLength}', + range: '{label}必须介于{minLength}和{maxLength}之间' + }, + number: { + minimum: '{label}不能小于{minimum}', + maximum: '{label}不能大于{maximum}', + exclusiveMinimum: '{label}不能小于等于{minimum}', + exclusiveMaximum: '{label}不能大于等于{maximum}', + range: '{label}必须介于{minimum}and{maximum}之间' + }, + pattern: { + mismatch: '{label}格式不匹配' + } + }; +} + + +SchemaValidator.message = new Message(); + +export default SchemaValidator diff --git a/uni_modules/uni-forms/package.json b/uni_modules/uni-forms/package.json new file mode 100644 index 0000000000000000000000000000000000000000..464b4e6c9fc7d61db50c3d7f449ceb5b09c32bdf --- /dev/null +++ b/uni_modules/uni-forms/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-forms", + "displayName": "uni-forms 表单", + "version": "1.4.10", + "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据", + "keywords": [ + "uni-ui", + "表单", + "校验", + "表单校验", + "表单验证" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uni_modules/uni-forms/readme.md b/uni_modules/uni-forms/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..63d5a043ee8c43c48d21de1af4855cbef4e0ce50 --- /dev/null +++ b/uni_modules/uni-forms/readme.md @@ -0,0 +1,23 @@ + + +## Forms 表单 + +> **组件名:uni-forms** +> 代码块: `uForms`、`uni-forms-item` +> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。 + + +uni-app的内置组件已经有了 `
`组件,用于提交表单内容。 + +然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 ``组件封装了 ``组件,内置了表单验证功能。 + +`` 提供了 `rules`属性来描述校验规则、``子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。 + +每个要校验的表单项,不管input还是checkbox,都必须放在``组件中,且一个``组件只能放置一个表单项。 + +``组件内部预留了显示error message的区域,默认是在表单项的底部。 + +另外,``组件下面的各个表单项,可以通过``包裹为不同的分组。同一``下的不同表单项目将聚拢在一起,同其他group保持垂直间距。``仅影响视觉效果。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uni_modules/uni-icons/changelog.md b/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..620ab027739bf7751dee53f0fac9242d0f5d582a --- /dev/null +++ b/uni_modules/uni-icons/changelog.md @@ -0,0 +1,40 @@ +## 2.0.9(2024-01-12) +fix: 修复图标大小默认值错误的问题 +## 2.0.8(2023-12-14) +- 修复 项目未使用 ts 情况下,打包报错的bug +## 2.0.7(2023-12-14) +- 修复 size 属性为 string 时,不加单位导致尺寸异常的bug +## 2.0.6(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.5(2023-12-11) +- 优化 兼容老版本icon类型,如 top ,bottom 等 +## 2.0.4(2023-12-06) +- 优化 uni-app x 下示例项目图标排序 +## 2.0.3(2023-12-06) +- 修复 nvue下引入组件报错的bug +## 2.0.2(2023-12-05) +-优化 size 属性支持单位 +## 2.0.1(2023-12-05) +- 新增 uni-app x 支持定义图标 +## 1.3.5(2022-01-24) +- 优化 size 属性可以传入不带单位的字符串数值 +## 1.3.4(2022-01-24) +- 优化 size 支持其他单位 +## 1.3.3(2022-01-17) +- 修复 nvue 有些图标不显示的bug,兼容老版本图标 +## 1.3.2(2021-12-01) +- 优化 示例可复制图标名称 +## 1.3.1(2021-11-23) +- 优化 兼容旧组件 type 值 +## 1.3.0(2021-11-19) +- 新增 更多图标 +- 优化 自定义图标使用方式 +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons) +## 1.1.7(2021-11-08) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue b/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue new file mode 100644 index 0000000000000000000000000000000000000000..a975b4a9b5ffb852944c6df52ee8ab63d120a18e --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/uni-icons.uvue @@ -0,0 +1,91 @@ + + + + + diff --git a/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000000000000000000000000000000000000..9634a9cd07f26415c7a665d214daf88bae5f0852 --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/uni_modules/uni-icons/components/uni-icons/uniicons.css b/uni_modules/uni-icons/components/uni-icons/uniicons.css new file mode 100644 index 0000000000000000000000000000000000000000..0a6b6fea1168a93017c3173e4b5ce62f3a9efa0b --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/uniicons.css @@ -0,0 +1,664 @@ + +.uniui-cart-filled:before { + content: "\e6d0"; +} + +.uniui-gift-filled:before { + content: "\e6c4"; +} + +.uniui-color:before { + content: "\e6cf"; +} + +.uniui-wallet:before { + content: "\e6b1"; +} + +.uniui-settings-filled:before { + content: "\e6ce"; +} + +.uniui-auth-filled:before { + content: "\e6cc"; +} + +.uniui-shop-filled:before { + content: "\e6cd"; +} + +.uniui-staff-filled:before { + content: "\e6cb"; +} + +.uniui-vip-filled:before { + content: "\e6c6"; +} + +.uniui-plus-filled:before { + content: "\e6c7"; +} + +.uniui-folder-add-filled:before { + content: "\e6c8"; +} + +.uniui-color-filled:before { + content: "\e6c9"; +} + +.uniui-tune-filled:before { + content: "\e6ca"; +} + +.uniui-calendar-filled:before { + content: "\e6c0"; +} + +.uniui-notification-filled:before { + content: "\e6c1"; +} + +.uniui-wallet-filled:before { + content: "\e6c2"; +} + +.uniui-medal-filled:before { + content: "\e6c3"; +} + +.uniui-fire-filled:before { + content: "\e6c5"; +} + +.uniui-refreshempty:before { + content: "\e6bf"; +} + +.uniui-location-filled:before { + content: "\e6af"; +} + +.uniui-person-filled:before { + content: "\e69d"; +} + +.uniui-personadd-filled:before { + content: "\e698"; +} + +.uniui-arrowthinleft:before { + content: "\e6d2"; +} + +.uniui-arrowthinup:before { + content: "\e6d3"; +} + +.uniui-arrowthindown:before { + content: "\e6d4"; +} + +.uniui-back:before { + content: "\e6b9"; +} + +.uniui-forward:before { + content: "\e6ba"; +} + +.uniui-arrow-right:before { + content: "\e6bb"; +} + +.uniui-arrow-left:before { + content: "\e6bc"; +} + +.uniui-arrow-up:before { + content: "\e6bd"; +} + +.uniui-arrow-down:before { + content: "\e6be"; +} + +.uniui-arrowthinright:before { + content: "\e6d1"; +} + +.uniui-down:before { + content: "\e6b8"; +} + +.uniui-bottom:before { + content: "\e6b8"; +} + +.uniui-arrowright:before { + content: "\e6d5"; +} + +.uniui-right:before { + content: "\e6b5"; +} + +.uniui-up:before { + content: "\e6b6"; +} + +.uniui-top:before { + content: "\e6b6"; +} + +.uniui-left:before { + content: "\e6b7"; +} + +.uniui-arrowup:before { + content: "\e6d6"; +} + +.uniui-eye:before { + content: "\e651"; +} + +.uniui-eye-filled:before { + content: "\e66a"; +} + +.uniui-eye-slash:before { + content: "\e6b3"; +} + +.uniui-eye-slash-filled:before { + content: "\e6b4"; +} + +.uniui-info-filled:before { + content: "\e649"; +} + +.uniui-reload:before { + content: "\e6b2"; +} + +.uniui-micoff-filled:before { + content: "\e6b0"; +} + +.uniui-map-pin-ellipse:before { + content: "\e6ac"; +} + +.uniui-map-pin:before { + content: "\e6ad"; +} + +.uniui-location:before { + content: "\e6ae"; +} + +.uniui-starhalf:before { + content: "\e683"; +} + +.uniui-star:before { + content: "\e688"; +} + +.uniui-star-filled:before { + content: "\e68f"; +} + +.uniui-calendar:before { + content: "\e6a0"; +} + +.uniui-fire:before { + content: "\e6a1"; +} + +.uniui-medal:before { + content: "\e6a2"; +} + +.uniui-font:before { + content: "\e6a3"; +} + +.uniui-gift:before { + content: "\e6a4"; +} + +.uniui-link:before { + content: "\e6a5"; +} + +.uniui-notification:before { + content: "\e6a6"; +} + +.uniui-staff:before { + content: "\e6a7"; +} + +.uniui-vip:before { + content: "\e6a8"; +} + +.uniui-folder-add:before { + content: "\e6a9"; +} + +.uniui-tune:before { + content: "\e6aa"; +} + +.uniui-auth:before { + content: "\e6ab"; +} + +.uniui-person:before { + content: "\e699"; +} + +.uniui-email-filled:before { + content: "\e69a"; +} + +.uniui-phone-filled:before { + content: "\e69b"; +} + +.uniui-phone:before { + content: "\e69c"; +} + +.uniui-email:before { + content: "\e69e"; +} + +.uniui-personadd:before { + content: "\e69f"; +} + +.uniui-chatboxes-filled:before { + content: "\e692"; +} + +.uniui-contact:before { + content: "\e693"; +} + +.uniui-chatbubble-filled:before { + content: "\e694"; +} + +.uniui-contact-filled:before { + content: "\e695"; +} + +.uniui-chatboxes:before { + content: "\e696"; +} + +.uniui-chatbubble:before { + content: "\e697"; +} + +.uniui-upload-filled:before { + content: "\e68e"; +} + +.uniui-upload:before { + content: "\e690"; +} + +.uniui-weixin:before { + content: "\e691"; +} + +.uniui-compose:before { + content: "\e67f"; +} + +.uniui-qq:before { + content: "\e680"; +} + +.uniui-download-filled:before { + content: "\e681"; +} + +.uniui-pyq:before { + content: "\e682"; +} + +.uniui-sound:before { + content: "\e684"; +} + +.uniui-trash-filled:before { + content: "\e685"; +} + +.uniui-sound-filled:before { + content: "\e686"; +} + +.uniui-trash:before { + content: "\e687"; +} + +.uniui-videocam-filled:before { + content: "\e689"; +} + +.uniui-spinner-cycle:before { + content: "\e68a"; +} + +.uniui-weibo:before { + content: "\e68b"; +} + +.uniui-videocam:before { + content: "\e68c"; +} + +.uniui-download:before { + content: "\e68d"; +} + +.uniui-help:before { + content: "\e679"; +} + +.uniui-navigate-filled:before { + content: "\e67a"; +} + +.uniui-plusempty:before { + content: "\e67b"; +} + +.uniui-smallcircle:before { + content: "\e67c"; +} + +.uniui-minus-filled:before { + content: "\e67d"; +} + +.uniui-micoff:before { + content: "\e67e"; +} + +.uniui-closeempty:before { + content: "\e66c"; +} + +.uniui-clear:before { + content: "\e66d"; +} + +.uniui-navigate:before { + content: "\e66e"; +} + +.uniui-minus:before { + content: "\e66f"; +} + +.uniui-image:before { + content: "\e670"; +} + +.uniui-mic:before { + content: "\e671"; +} + +.uniui-paperplane:before { + content: "\e672"; +} + +.uniui-close:before { + content: "\e673"; +} + +.uniui-help-filled:before { + content: "\e674"; +} + +.uniui-paperplane-filled:before { + content: "\e675"; +} + +.uniui-plus:before { + content: "\e676"; +} + +.uniui-mic-filled:before { + content: "\e677"; +} + +.uniui-image-filled:before { + content: "\e678"; +} + +.uniui-locked-filled:before { + content: "\e668"; +} + +.uniui-info:before { + content: "\e669"; +} + +.uniui-locked:before { + content: "\e66b"; +} + +.uniui-camera-filled:before { + content: "\e658"; +} + +.uniui-chat-filled:before { + content: "\e659"; +} + +.uniui-camera:before { + content: "\e65a"; +} + +.uniui-circle:before { + content: "\e65b"; +} + +.uniui-checkmarkempty:before { + content: "\e65c"; +} + +.uniui-chat:before { + content: "\e65d"; +} + +.uniui-circle-filled:before { + content: "\e65e"; +} + +.uniui-flag:before { + content: "\e65f"; +} + +.uniui-flag-filled:before { + content: "\e660"; +} + +.uniui-gear-filled:before { + content: "\e661"; +} + +.uniui-home:before { + content: "\e662"; +} + +.uniui-home-filled:before { + content: "\e663"; +} + +.uniui-gear:before { + content: "\e664"; +} + +.uniui-smallcircle-filled:before { + content: "\e665"; +} + +.uniui-map-filled:before { + content: "\e666"; +} + +.uniui-map:before { + content: "\e667"; +} + +.uniui-refresh-filled:before { + content: "\e656"; +} + +.uniui-refresh:before { + content: "\e657"; +} + +.uniui-cloud-upload:before { + content: "\e645"; +} + +.uniui-cloud-download-filled:before { + content: "\e646"; +} + +.uniui-cloud-download:before { + content: "\e647"; +} + +.uniui-cloud-upload-filled:before { + content: "\e648"; +} + +.uniui-redo:before { + content: "\e64a"; +} + +.uniui-images-filled:before { + content: "\e64b"; +} + +.uniui-undo-filled:before { + content: "\e64c"; +} + +.uniui-more:before { + content: "\e64d"; +} + +.uniui-more-filled:before { + content: "\e64e"; +} + +.uniui-undo:before { + content: "\e64f"; +} + +.uniui-images:before { + content: "\e650"; +} + +.uniui-paperclip:before { + content: "\e652"; +} + +.uniui-settings:before { + content: "\e653"; +} + +.uniui-search:before { + content: "\e654"; +} + +.uniui-redo-filled:before { + content: "\e655"; +} + +.uniui-list:before { + content: "\e644"; +} + +.uniui-mail-open-filled:before { + content: "\e63a"; +} + +.uniui-hand-down-filled:before { + content: "\e63c"; +} + +.uniui-hand-down:before { + content: "\e63d"; +} + +.uniui-hand-up-filled:before { + content: "\e63e"; +} + +.uniui-hand-up:before { + content: "\e63f"; +} + +.uniui-heart-filled:before { + content: "\e641"; +} + +.uniui-mail-open:before { + content: "\e643"; +} + +.uniui-heart:before { + content: "\e639"; +} + +.uniui-loop:before { + content: "\e633"; +} + +.uniui-pulldown:before { + content: "\e632"; +} + +.uniui-scan:before { + content: "\e62a"; +} + +.uniui-bars:before { + content: "\e627"; +} + +.uniui-checkbox:before { + content: "\e62b"; +} + +.uniui-checkbox-filled:before { + content: "\e62c"; +} + +.uniui-shop:before { + content: "\e62f"; +} + +.uniui-headphones:before { + content: "\e630"; +} + +.uniui-cart:before { + content: "\e631"; +} diff --git a/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/uni_modules/uni-icons/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..14696d038d828073edac09ea4e5ba1dec2f58115 Binary files /dev/null and b/uni_modules/uni-icons/components/uni-icons/uniicons.ttf differ diff --git a/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts b/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts new file mode 100644 index 0000000000000000000000000000000000000000..86318df9482a6ca01c39ae83e73e165ac9d3e93c --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/uniicons_file.ts @@ -0,0 +1,664 @@ + +export type IconsData = { + id : string + name : string + font_family : string + css_prefix_text : string + description : string + glyphs : Array +} + +export type IconsDataItem = { + font_class : string + unicode : string +} + + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] as IconsDataItem[] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js b/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js new file mode 100644 index 0000000000000000000000000000000000000000..49e42cd62c2639731ff1715bbb1bb87d2136509c --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/uniicons_file_vue.js @@ -0,0 +1,649 @@ + +export const fontData = [ + { + "font_class": "arrow-down", + "unicode": "\ue6be" + }, + { + "font_class": "arrow-left", + "unicode": "\ue6bc" + }, + { + "font_class": "arrow-right", + "unicode": "\ue6bb" + }, + { + "font_class": "arrow-up", + "unicode": "\ue6bd" + }, + { + "font_class": "auth", + "unicode": "\ue6ab" + }, + { + "font_class": "auth-filled", + "unicode": "\ue6cc" + }, + { + "font_class": "back", + "unicode": "\ue6b9" + }, + { + "font_class": "bars", + "unicode": "\ue627" + }, + { + "font_class": "calendar", + "unicode": "\ue6a0" + }, + { + "font_class": "calendar-filled", + "unicode": "\ue6c0" + }, + { + "font_class": "camera", + "unicode": "\ue65a" + }, + { + "font_class": "camera-filled", + "unicode": "\ue658" + }, + { + "font_class": "cart", + "unicode": "\ue631" + }, + { + "font_class": "cart-filled", + "unicode": "\ue6d0" + }, + { + "font_class": "chat", + "unicode": "\ue65d" + }, + { + "font_class": "chat-filled", + "unicode": "\ue659" + }, + { + "font_class": "chatboxes", + "unicode": "\ue696" + }, + { + "font_class": "chatboxes-filled", + "unicode": "\ue692" + }, + { + "font_class": "chatbubble", + "unicode": "\ue697" + }, + { + "font_class": "chatbubble-filled", + "unicode": "\ue694" + }, + { + "font_class": "checkbox", + "unicode": "\ue62b" + }, + { + "font_class": "checkbox-filled", + "unicode": "\ue62c" + }, + { + "font_class": "checkmarkempty", + "unicode": "\ue65c" + }, + { + "font_class": "circle", + "unicode": "\ue65b" + }, + { + "font_class": "circle-filled", + "unicode": "\ue65e" + }, + { + "font_class": "clear", + "unicode": "\ue66d" + }, + { + "font_class": "close", + "unicode": "\ue673" + }, + { + "font_class": "closeempty", + "unicode": "\ue66c" + }, + { + "font_class": "cloud-download", + "unicode": "\ue647" + }, + { + "font_class": "cloud-download-filled", + "unicode": "\ue646" + }, + { + "font_class": "cloud-upload", + "unicode": "\ue645" + }, + { + "font_class": "cloud-upload-filled", + "unicode": "\ue648" + }, + { + "font_class": "color", + "unicode": "\ue6cf" + }, + { + "font_class": "color-filled", + "unicode": "\ue6c9" + }, + { + "font_class": "compose", + "unicode": "\ue67f" + }, + { + "font_class": "contact", + "unicode": "\ue693" + }, + { + "font_class": "contact-filled", + "unicode": "\ue695" + }, + { + "font_class": "down", + "unicode": "\ue6b8" + }, + { + "font_class": "bottom", + "unicode": "\ue6b8" + }, + { + "font_class": "download", + "unicode": "\ue68d" + }, + { + "font_class": "download-filled", + "unicode": "\ue681" + }, + { + "font_class": "email", + "unicode": "\ue69e" + }, + { + "font_class": "email-filled", + "unicode": "\ue69a" + }, + { + "font_class": "eye", + "unicode": "\ue651" + }, + { + "font_class": "eye-filled", + "unicode": "\ue66a" + }, + { + "font_class": "eye-slash", + "unicode": "\ue6b3" + }, + { + "font_class": "eye-slash-filled", + "unicode": "\ue6b4" + }, + { + "font_class": "fire", + "unicode": "\ue6a1" + }, + { + "font_class": "fire-filled", + "unicode": "\ue6c5" + }, + { + "font_class": "flag", + "unicode": "\ue65f" + }, + { + "font_class": "flag-filled", + "unicode": "\ue660" + }, + { + "font_class": "folder-add", + "unicode": "\ue6a9" + }, + { + "font_class": "folder-add-filled", + "unicode": "\ue6c8" + }, + { + "font_class": "font", + "unicode": "\ue6a3" + }, + { + "font_class": "forward", + "unicode": "\ue6ba" + }, + { + "font_class": "gear", + "unicode": "\ue664" + }, + { + "font_class": "gear-filled", + "unicode": "\ue661" + }, + { + "font_class": "gift", + "unicode": "\ue6a4" + }, + { + "font_class": "gift-filled", + "unicode": "\ue6c4" + }, + { + "font_class": "hand-down", + "unicode": "\ue63d" + }, + { + "font_class": "hand-down-filled", + "unicode": "\ue63c" + }, + { + "font_class": "hand-up", + "unicode": "\ue63f" + }, + { + "font_class": "hand-up-filled", + "unicode": "\ue63e" + }, + { + "font_class": "headphones", + "unicode": "\ue630" + }, + { + "font_class": "heart", + "unicode": "\ue639" + }, + { + "font_class": "heart-filled", + "unicode": "\ue641" + }, + { + "font_class": "help", + "unicode": "\ue679" + }, + { + "font_class": "help-filled", + "unicode": "\ue674" + }, + { + "font_class": "home", + "unicode": "\ue662" + }, + { + "font_class": "home-filled", + "unicode": "\ue663" + }, + { + "font_class": "image", + "unicode": "\ue670" + }, + { + "font_class": "image-filled", + "unicode": "\ue678" + }, + { + "font_class": "images", + "unicode": "\ue650" + }, + { + "font_class": "images-filled", + "unicode": "\ue64b" + }, + { + "font_class": "info", + "unicode": "\ue669" + }, + { + "font_class": "info-filled", + "unicode": "\ue649" + }, + { + "font_class": "left", + "unicode": "\ue6b7" + }, + { + "font_class": "link", + "unicode": "\ue6a5" + }, + { + "font_class": "list", + "unicode": "\ue644" + }, + { + "font_class": "location", + "unicode": "\ue6ae" + }, + { + "font_class": "location-filled", + "unicode": "\ue6af" + }, + { + "font_class": "locked", + "unicode": "\ue66b" + }, + { + "font_class": "locked-filled", + "unicode": "\ue668" + }, + { + "font_class": "loop", + "unicode": "\ue633" + }, + { + "font_class": "mail-open", + "unicode": "\ue643" + }, + { + "font_class": "mail-open-filled", + "unicode": "\ue63a" + }, + { + "font_class": "map", + "unicode": "\ue667" + }, + { + "font_class": "map-filled", + "unicode": "\ue666" + }, + { + "font_class": "map-pin", + "unicode": "\ue6ad" + }, + { + "font_class": "map-pin-ellipse", + "unicode": "\ue6ac" + }, + { + "font_class": "medal", + "unicode": "\ue6a2" + }, + { + "font_class": "medal-filled", + "unicode": "\ue6c3" + }, + { + "font_class": "mic", + "unicode": "\ue671" + }, + { + "font_class": "mic-filled", + "unicode": "\ue677" + }, + { + "font_class": "micoff", + "unicode": "\ue67e" + }, + { + "font_class": "micoff-filled", + "unicode": "\ue6b0" + }, + { + "font_class": "minus", + "unicode": "\ue66f" + }, + { + "font_class": "minus-filled", + "unicode": "\ue67d" + }, + { + "font_class": "more", + "unicode": "\ue64d" + }, + { + "font_class": "more-filled", + "unicode": "\ue64e" + }, + { + "font_class": "navigate", + "unicode": "\ue66e" + }, + { + "font_class": "navigate-filled", + "unicode": "\ue67a" + }, + { + "font_class": "notification", + "unicode": "\ue6a6" + }, + { + "font_class": "notification-filled", + "unicode": "\ue6c1" + }, + { + "font_class": "paperclip", + "unicode": "\ue652" + }, + { + "font_class": "paperplane", + "unicode": "\ue672" + }, + { + "font_class": "paperplane-filled", + "unicode": "\ue675" + }, + { + "font_class": "person", + "unicode": "\ue699" + }, + { + "font_class": "person-filled", + "unicode": "\ue69d" + }, + { + "font_class": "personadd", + "unicode": "\ue69f" + }, + { + "font_class": "personadd-filled", + "unicode": "\ue698" + }, + { + "font_class": "personadd-filled-copy", + "unicode": "\ue6d1" + }, + { + "font_class": "phone", + "unicode": "\ue69c" + }, + { + "font_class": "phone-filled", + "unicode": "\ue69b" + }, + { + "font_class": "plus", + "unicode": "\ue676" + }, + { + "font_class": "plus-filled", + "unicode": "\ue6c7" + }, + { + "font_class": "plusempty", + "unicode": "\ue67b" + }, + { + "font_class": "pulldown", + "unicode": "\ue632" + }, + { + "font_class": "pyq", + "unicode": "\ue682" + }, + { + "font_class": "qq", + "unicode": "\ue680" + }, + { + "font_class": "redo", + "unicode": "\ue64a" + }, + { + "font_class": "redo-filled", + "unicode": "\ue655" + }, + { + "font_class": "refresh", + "unicode": "\ue657" + }, + { + "font_class": "refresh-filled", + "unicode": "\ue656" + }, + { + "font_class": "refreshempty", + "unicode": "\ue6bf" + }, + { + "font_class": "reload", + "unicode": "\ue6b2" + }, + { + "font_class": "right", + "unicode": "\ue6b5" + }, + { + "font_class": "scan", + "unicode": "\ue62a" + }, + { + "font_class": "search", + "unicode": "\ue654" + }, + { + "font_class": "settings", + "unicode": "\ue653" + }, + { + "font_class": "settings-filled", + "unicode": "\ue6ce" + }, + { + "font_class": "shop", + "unicode": "\ue62f" + }, + { + "font_class": "shop-filled", + "unicode": "\ue6cd" + }, + { + "font_class": "smallcircle", + "unicode": "\ue67c" + }, + { + "font_class": "smallcircle-filled", + "unicode": "\ue665" + }, + { + "font_class": "sound", + "unicode": "\ue684" + }, + { + "font_class": "sound-filled", + "unicode": "\ue686" + }, + { + "font_class": "spinner-cycle", + "unicode": "\ue68a" + }, + { + "font_class": "staff", + "unicode": "\ue6a7" + }, + { + "font_class": "staff-filled", + "unicode": "\ue6cb" + }, + { + "font_class": "star", + "unicode": "\ue688" + }, + { + "font_class": "star-filled", + "unicode": "\ue68f" + }, + { + "font_class": "starhalf", + "unicode": "\ue683" + }, + { + "font_class": "trash", + "unicode": "\ue687" + }, + { + "font_class": "trash-filled", + "unicode": "\ue685" + }, + { + "font_class": "tune", + "unicode": "\ue6aa" + }, + { + "font_class": "tune-filled", + "unicode": "\ue6ca" + }, + { + "font_class": "undo", + "unicode": "\ue64f" + }, + { + "font_class": "undo-filled", + "unicode": "\ue64c" + }, + { + "font_class": "up", + "unicode": "\ue6b6" + }, + { + "font_class": "top", + "unicode": "\ue6b6" + }, + { + "font_class": "upload", + "unicode": "\ue690" + }, + { + "font_class": "upload-filled", + "unicode": "\ue68e" + }, + { + "font_class": "videocam", + "unicode": "\ue68c" + }, + { + "font_class": "videocam-filled", + "unicode": "\ue689" + }, + { + "font_class": "vip", + "unicode": "\ue6a8" + }, + { + "font_class": "vip-filled", + "unicode": "\ue6c6" + }, + { + "font_class": "wallet", + "unicode": "\ue6b1" + }, + { + "font_class": "wallet-filled", + "unicode": "\ue6c2" + }, + { + "font_class": "weibo", + "unicode": "\ue68b" + }, + { + "font_class": "weixin", + "unicode": "\ue691" + } +] + +// export const fontData = JSON.parse(fontDataJson) diff --git a/uni_modules/uni-icons/package.json b/uni_modules/uni-icons/package.json new file mode 100644 index 0000000000000000000000000000000000000000..397be839435b3245cedfa85fa948c6f7a6b66a23 --- /dev/null +++ b/uni_modules/uni-icons/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "2.0.9", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.14" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y", + "app-uvue": "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", + "钉钉": "y", + "快手": "y", + "飞书": "y", + "京东": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uni_modules/uni-icons/readme.md b/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..86234ba1ce8b8fbfa52c3f08ba6c5847f1934807 --- /dev/null +++ b/uni_modules/uni-icons/readme.md @@ -0,0 +1,8 @@ +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + +用于展示 icons 图标 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/uni_modules/uni-id-common/changelog.md b/uni_modules/uni-id-common/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..a8ee8f24f621611c671b94ca13487a7401cb48be --- /dev/null +++ b/uni_modules/uni-id-common/changelog.md @@ -0,0 +1,32 @@ +## 1.0.16(2023-04-25) +- 新增maxTokenLength配置,用于限制数据库用户记录token数组的最大长度 +## 1.0.15(2023-04-06) +- 修复部分语言国际化出错的Bug +## 1.0.14(2023-03-07) +- 修复 admin用户包含其他角色时未包含在token的Bug +## 1.0.13(2022-07-21) +- 修复 创建token时未传角色权限信息生成的token不正确的bug +## 1.0.12(2022-07-15) +- 提升与旧版本uni-id的兼容性(补充读取配置文件时回退平台app-plus、h5),但是仍推荐使用新平台名进行配置(app、web) +## 1.0.11(2022-07-14) +- 修复 部分情况下报`read property 'reduce' of undefined`的错误 +## 1.0.10(2022-07-11) +- 将token存储在用户表的token字段内,与旧版本uni-id保持一致 +## 1.0.9(2022-07-01) +- checkToken兼容token内未缓存角色权限的情况,此时将查库获取角色权限 +## 1.0.8(2022-07-01) +- 修复clientDB默认依赖时部分情况下获取不到uni-id配置的Bug +## 1.0.7(2022-06-30) +- 修复config文件不合法时未抛出具体错误的Bug +## 1.0.6(2022-06-28) +- 移除插件内的数据表schema +## 1.0.5(2022-06-27) +- 修复使用多应用配置时报`Cannot read property 'appId' of undefined`的Bug +## 1.0.4(2022-06-27) +- 修复使用自定义token内容功能报错的Bug [详情](https://ask.dcloud.net.cn/question/147945) +## 1.0.2(2022-06-23) +- 对齐旧版本uni-id默认配置 +## 1.0.1(2022-06-22) +- 补充对uni-config-center的依赖 +## 1.0.0(2022-06-21) +- 提供uni-id token创建、校验、刷新接口,简化旧版uni-id公共模块 diff --git a/uni_modules/uni-id-common/package.json b/uni_modules/uni-id-common/package.json new file mode 100644 index 0000000000000000000000000000000000000000..dda009e408cd17503c4a7d67033f43ce13b239aa --- /dev/null +++ b/uni_modules/uni-id-common/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-id-common", + "displayName": "uni-id-common", + "version": "1.0.16", + "description": "包含uni-id token生成、校验、刷新功能的云函数公共模块", + "keywords": [ + "uni-id-common", + "uniCloud", + "token", + "权限" + ], + "repository": "https://gitcode.net/dcloud/uni-id-common", + "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": { + "Vue": { + "vue2": "u", + "vue3": "u" + }, + "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" + } + } + } + } +} diff --git a/uni_modules/uni-id-common/readme.md b/uni_modules/uni-id-common/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..91b3aaa811950cb82e592587f13993b517d52b9e --- /dev/null +++ b/uni_modules/uni-id-common/readme.md @@ -0,0 +1,3 @@ +# uni-id-common + +文档请参考:[uni-id-common](https://uniapp.dcloud.net.cn/uniCloud/uni-id-common.html) \ No newline at end of file diff --git a/uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/index.js b/uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/index.js new file mode 100644 index 0000000000000000000000000000000000000000..bcbc2c7e509871edeb565918da6d1d0f791c88a3 --- /dev/null +++ b/uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/index.js @@ -0,0 +1 @@ +"use strict";var e,t=(e=require("crypto"))&&"object"==typeof e&&"default"in e?e.default:e;const n={TOKEN_EXPIRED:"uni-id-token-expired",CHECK_TOKEN_FAILED:"uni-id-check-token-failed",PARAM_REQUIRED:"uni-id-param-required",ACCOUNT_EXISTS:"uni-id-account-exists",ACCOUNT_NOT_EXISTS:"uni-id-account-not-exists",ACCOUNT_CONFLICT:"uni-id-account-conflict",ACCOUNT_BANNED:"uni-id-account-banned",ACCOUNT_AUDITING:"uni-id-account-auditing",ACCOUNT_AUDIT_FAILED:"uni-id-account-audit-failed",ACCOUNT_CLOSED:"uni-id-account-closed"};function i(e){return!!e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof e.then}function r(e){if(!e)return;const t=e.match(/^(\d+).(\d+).(\d+)/);return t?t.slice(1,4).map(e=>parseInt(e)):void 0}function o(e,t){const n=r(e),i=r(t);return n?i?function(e,t){const n=Math.max(e.length,t.length);for(let i=0;ir)return 1;if(n=e)throw new Error("Config error, tokenExpiresThreshold should be less than tokenExpiresIn");t>e/2&&console.warn(`Please check whether the tokenExpiresThreshold configuration is set too large, tokenExpiresThreshold: ${t}, tokenExpiresIn: ${e}`)}get customToken(){return this.uniId.interceptorMap.get("customToken")}isTokenInDb(e){return o(e,"1.0.10")>=0}async getUserRecord(){if(this.userRecord)return this.userRecord;const e=await C.doc(this.uid).get();if(this.userRecord=e.data[0],!this.userRecord)throw{errCode:n.ACCOUNT_NOT_EXISTS};switch(this.userRecord.status){case void 0:case 0:break;case 1:throw{errCode:n.ACCOUNT_BANNED};case 2:throw{errCode:n.ACCOUNT_AUDITING};case 3:throw{errCode:n.ACCOUNT_AUDIT_FAILED};case 4:throw{errCode:n.ACCOUNT_CLOSED}}if(this.oldTokenPayload){if(this.isTokenInDb(this.oldTokenPayload.uniIdVersion)){if(-1===(this.userRecord.token||[]).indexOf(this.oldToken))throw{errCode:n.CHECK_TOKEN_FAILED}}if(this.userRecord.valid_token_date&&this.userRecord.valid_token_date>1e3*this.oldTokenPayload.iat)throw{errCode:n.TOKEN_EXPIRED}}return this.userRecord}async updateUserRecord(e){await C.doc(this.uid).update(e)}async getUserPermission(){if(this.userPermission)return this.userPermission;const e=(await this.getUserRecord()).role||[];if(0===e.length)return this.userPermission={role:[],permission:[]},this.userPermission;if(e.includes("admin"))return this.userPermission={role:e,permission:[]},this.userPermission;const t=await T.where({role_id:_.in(e)}).get(),n=(i=t.data.reduce((e,t)=>(t.permission&&e.push(...t.permission),e),[]),Array.from(new Set(i)));var i;return this.userPermission={role:e,permission:n},this.userPermission}async _createToken({uid:e,role:t,permission:i}={}){if(!t||!i){const e=await this.getUserPermission();t=e.role,i=e.permission}let r={uid:e,role:t,permission:i};if(this.uniId.interceptorMap.has("customToken")){const n=this.uniId.interceptorMap.get("customToken");if("function"!=typeof n)throw new Error("Invalid custom token file");r=await n({uid:e,role:t,permission:i})}const o=Date.now(),{tokenSecret:s,tokenExpiresIn:c,maxTokenLength:a=10}=this.config,u=g({...r,uniIdVersion:"1.0.16"},s,{expiresIn:c}),d=await this.getUserRecord(),l=(d.token||[]).filter(e=>{try{const t=this._checkToken(e);if(d.valid_token_date&&d.valid_token_date>1e3*t.iat)return!1}catch(e){if(e.errCode===n.TOKEN_EXPIRED)return!1}return!0});return l.push(u),l.length>a&&l.splice(0,l.length-a),await this.updateUserRecord({last_login_ip:this.clientInfo.clientIP,last_login_date:o,token:l}),{token:u,tokenExpired:o+1e3*c}}async createToken({uid:e,role:t,permission:i}={}){if(!e)throw{errCode:n.PARAM_REQUIRED,errMsgValue:{param:"uid"}};this.uid=e;const{token:r,tokenExpired:o}=await this._createToken({uid:e,role:t,permission:i});return{errCode:0,token:r,tokenExpired:o}}async refreshToken({token:e}={}){if(!e)throw{errCode:n.PARAM_REQUIRED,errMsgValue:{param:"token"}};this.oldToken=e;const t=this._checkToken(e);this.uid=t.uid,this.oldTokenPayload=t;const{uid:i}=t,{role:r,permission:o}=await this.getUserPermission(),{token:s,tokenExpired:c}=await this._createToken({uid:i,role:r,permission:o});return{errCode:0,token:s,tokenExpired:c}}_checkToken(e){const{tokenSecret:t}=this.config;let i;try{i=k(e,t)}catch(e){if("TokenExpiredError"===e.name)throw{errCode:n.TOKEN_EXPIRED};throw{errCode:n.CHECK_TOKEN_FAILED}}return i}async checkToken(e,{autoRefresh:t=!0}={}){if(!e)throw{errCode:n.PARAM_REQUIRED,errMsgValue:{param:"token"}};this.oldToken=e;const i=this._checkToken(e);this.uid=i.uid,this.oldTokenPayload=i;const{tokenExpiresThreshold:r}=this.config,{uid:o,role:s,permission:c}=i,a={role:s,permission:c};if(!s&&!c){const{role:e,permission:t}=await this.getUserPermission();a.role=e,a.permission=t}if(!r||!t){const e={code:0,errCode:0,...i,...a};return delete e.uniIdVersion,e}const u=Date.now();let d={};1e3*i.exp-u<1e3*r&&(d=await this._createToken({uid:o}));const l={code:0,errCode:0,...i,...a,...d};return delete l.uniIdVersion,l}}var E=Object.freeze({__proto__:null,checkToken:async function(e,{autoRefresh:t=!0}={}){return new m({uniId:this}).checkToken(e,{autoRefresh:t})},createToken:async function({uid:e,role:t,permission:n}={}){return new m({uniId:this}).createToken({uid:e,role:t,permission:n})},refreshToken:async function({token:e}={}){return new m({uniId:this}).refreshToken({token:e})}});const w=require("uni-config-center")({pluginId:"uni-id"});class x{constructor({context:e,clientInfo:t,config:n}={}){this._clientInfo=e?function(e){return{appId:e.APPID,platform:e.PLATFORM,locale:e.LOCALE,clientIP:e.CLIENTIP,deviceId:e.DEVICEID}}(e):t,this.config=n||this._getOriginConfig(),this.interceptorMap=new Map,w.hasFile("custom-token.js")&&this.setInterceptor("customToken",require(w.resolve("custom-token.js")));this._i18n=uniCloud.initI18n({locale:this._clientInfo.locale,fallbackLocale:"zh-Hans",messages:JSON.parse(JSON.stringify(d))}),d[this._i18n.locale]||this._i18n.setLocale("zh-Hans")}setInterceptor(e,t){this.interceptorMap.set(e,t)}_t(...e){return this._i18n.t(...e)}_parseOriginConfig(e){return Array.isArray(e)?e:e[0]?Object.values(e):e}_getOriginConfig(){if(w.hasFile("config.json")){let e;try{e=w.config()}catch(e){throw new Error("Invalid uni-id config file\n"+e.message)}return this._parseOriginConfig(e)}try{return this._parseOriginConfig(require("uni-id/config.json"))}catch(e){throw new Error("Invalid uni-id config file")}}_getAppConfig(){const e=this._getOriginConfig();return Array.isArray(e)?e.find(e=>e.dcloudAppid===this._clientInfo.appId)||e.find(e=>e.isDefaultConfig):e}_getPlatformConfig(){const e=this._getAppConfig();if(!e)throw new Error(`Config for current app (${this._clientInfo.appId}) was not found, please check your config file or client appId`);let t;switch("app-plus"===this._clientInfo.platform&&(this._clientInfo.platform="app"),"h5"===this._clientInfo.platform&&(this._clientInfo.platform="web"),this._clientInfo.platform){case"web":t="h5";break;case"app":t="app-plus"}const n=[{tokenExpiresIn:7200,tokenExpiresThreshold:1200,passwordErrorLimit:6,passwordErrorRetryTime:3600},e];t&&e[t]&&n.push(e[t]),n.push(e[this._clientInfo.platform]);const i=Object.assign(...n);return["tokenSecret","tokenExpiresIn"].forEach(e=>{if(!i||!i[e])throw new Error(`Config parameter missing, ${e} is required`)}),i}_getConfig(){return this._getPlatformConfig()}}for(const e in E)x.prototype[e]=E[e];function y(e){const t=new x(e);return new Proxy(t,{get(e,t){if(t in e&&0!==t.indexOf("_")){if("function"==typeof e[t])return(n=e[t],function(){let e;try{e=n.apply(this,arguments)}catch(e){if(a(e))return c.call(this,e),e;throw e}return i(e)?e.then(e=>(a(e)&&c.call(this,e),e),e=>{if(a(e))return c.call(this,e),e;throw e}):(a(e)&&c.call(this,e),e)}).bind(e);if("context"!==t&&"config"!==t)return e[t]}var n}})}x.prototype.createInstance=y;const A={createInstance:y};module.exports=A; diff --git a/uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/package.json b/uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/package.json new file mode 100644 index 0000000000000000000000000000000000000000..3b4ec05c23d318f0f3a47fd48dc6852e512bb601 --- /dev/null +++ b/uni_modules/uni-id-common/uniCloud/cloudfunctions/common/uni-id-common/package.json @@ -0,0 +1,16 @@ +{ + "name": "uni-id-common", + "version": "1.0.16", + "description": "uni-id token生成、校验、刷新", + "main": "index.js", + "homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html", + "repository": { + "type": "git", + "url": "git+https://gitee.com/dcloud/uni-id-common.git" + }, + "author": "DCloud", + "license": "Apache-2.0", + "dependencies": { + "uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center" + } +} \ No newline at end of file diff --git a/uni_modules/uni-id-pages/changelog.md b/uni_modules/uni-id-pages/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..92fc3a2f01bd0e1a797f6458f03e6000f67d4da4 --- /dev/null +++ b/uni_modules/uni-id-pages/changelog.md @@ -0,0 +1,179 @@ +## 1.1.19(2024-03-20) +- uni-id-co 修复 实人认证的认证照片在阿里云服务空间没有保存到指定路径下的Bug +- uni-id-co 修复 云对象开发依赖未移除的Bug +## 1.1.18(2024-02-20) +- 修复 PC设置头像无效的问题 +## 1.1.17(2023-12-14) +- uni-id-co 移除一键登录、短信的调用凭据 +## 1.1.16(2023-10-18) +- 修复 当不满足一键登录时页面回退无法继续登录的问题 +## 1.1.15(2023-07-13) +- uni-id-co 修复 QQ登录时不存在头像时报错的问题 +## 1.1.14(2023-05-19) +- 修复 退出登录不会跳转至登录页的问题 +## 1.1.13(2023-05-10) +- 修复 启用摇树优化 报错的问题 +## 1.1.12(2023-05-05) +- uni-id-co 新增 调用 add-user 接口创建用户时允许触发 beforeRegister 钩子方法,beforeRegister 钩子[详见](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#before-register) +- uni-id-co 新增 自无 unionid 到有 unionid 状态进行登录时为用户补充 unionid 字段 +- uni-id-co 修复 i18n 在特定场景下报错的 bug +- uni-id-co 修复 跨平台解绑微信/QQ时无法解绑的 bug +- uni-id-co 修复 微信小程序等平台创建验证码时无法展示的 bug +- uni-id-co 修复 更新 push_clientid 时因 device_id 没有变化导致无法更新 +## 1.1.11(2023-03-24) +- 修复 tabbar页面因为token无效而强制跳转至登录页面(url参数包含`uniIdRedirectUrl`)后无法返回的问题 +## 1.1.10(2023-03-24) +- 修复 PC微信扫码登录跳转地址错误 +- uni-id-co 新增 请求鉴权支持 uni-cloud-s2s 模块验证签名 [uni-cloud-s2s文档](https://uniapp.dcloud.net.cn/uniCloud/uni-cloud-s2s.html) +## 1.1.9(2023-03-24) +- 修复 跳转至登录页面的url参数包含`uniIdRedirectUrl`后无法返回的问题 +## 1.1.8(2023-03-02) +- 修复 调试模式下没有对微信授权手机号登录方式进行配置检测 +## 1.1.7(2023-02-27) +- 【重要】新增 实名认证功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#frv) +## 1.1.6(2023-02-24) +- uni-id-co 新增 注册用户时允许配置默认角色 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#config-defult-role) +- uni-id-co 优化 `updateUserInfoByExternal`接口,允许修改头像、性别 +- uni-id-co 修复 请求签名密钥字段 `requestAuthSecret` 缺少为空判断 +- uni-id-co 修复 `externalRegister`接口头像未使用`avatar_file`字段保存 +- 修复 web微信登录回调地址不正确 +## 1.1.5(2023-02-23) +- 更新 微信小程序端 更新头像信息,如果是使用微信的头像则不再调用裁剪接口 +## 1.1.4(2023-02-21) +- 修复 部分情况下 `uniIdRedirectUrl` 参数无效的问题 +## 1.1.3(2023-02-20) +- 修复 非微信小程序端报`TypeError: uni.hideHomeButton is not a function`的问题 +## 1.1.2(2023-02-10) +- 新增 微信小程序端 首页需强制登录时,隐藏返回首页按钮 +- uni-id-co 新增 外部联登后修改用户信息接口(updateUserInfoByExternal) [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-update-userinfo) +- uni-id-co 优化外部联登接口(登录、注册)逻辑 +## 1.1.1(2023-02-02) +- 新增 微信小程序端 支持选择使用微信资料的“头像”和“昵称” 设置用户资料 [详情参考](https://wdoc-76491.picgzc.qpic.cn/MTY4ODg1MDUyNzQyMDUxNw_21263_rTNhg68FTngQGdvQ_1647431233?w=1280&h=695.7176470588236) +## 1.1.0(2023-01-31) +- 【重要】优化 小程序端资源包大小(运行时大小为:731KB,发行后为:583KB;注:可以直接将本插件作为分包使用) +- 更新 微信小程序端 上传头像功能 用`wx.cropImage`实现图片裁剪 +- 修复 选择一键登录时会先显示 非密码登录页面的问题 +- 修复 一键登录 点击右上角的关闭按钮没有返回上一页的问题 +## 1.0.41(2023-01-16) +- 优化 压缩依赖的文件资源大小 +## 1.0.40(2023-01-16) +- 更新依赖的 验证码插件`uni-captcha`版本的版本为 0.6.4 修复 部分情况下APP端无法获取验证码的问题 [详情参考](https://ext.dcloud.net.cn/plugin?id=4048) +- 修复 客户端token过期后,点击退出登录按钮报错的问题 +- uni-id-co 修复 updateUser 接口`手机号`和`邮箱`参数值为空字符串时,修改无效的问题 +## 1.0.39(2022-12-28) +- uni-id-co 修复 URL化时第三方登录无法获取 uniPlatform 参数 +- uni-id-co 修复 validator error +## 1.0.38(2022-12-26) +- uni-id-co 优化 手机号与邮箱验证规则为空字符串时不校验 +## 1.0.37(2022-12-09) +- 优化admin端样式 +## 1.0.36(2022-12-08) +- uni-id-co 修复 `updateUser` 接口部分参数为空时数据修改异常 +## 1.0.35(2022-11-30) +- uni-id-co 新增 匹配到的用户不可在当前应用登录时的错误码 `uni-id-account-not-exists-in-current-app` [错误码说明](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#errcode) +## 1.0.34(2022-11-29) +- 优化 toast 错误提示时间为3秒 +- uni-id-co 修复 无法从 clientInfo 中获取 uniIdToken +## 1.0.33(2022-11-25) +- uni-id-co 新增 外部系统联登接口,可为外部系统创建与uni-id相对应的账号,使该账号可以使用依赖uniId的系统及功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external) +- uni-id-co 新增 URL化请求时鉴权签名验证 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#http-reqeust-auth) +- uni-id-co 修复 微信登录时用户未设置头像的报错问题 +## 1.0.32(2022-11-21) +- 新增 设置密码页面 +- 新增 登录后跳转设置密码页面配置项`setPasswordAfterLogin` [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-pwd-after-login) +- uni-id-co 新增 设置密码接口 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-pwd) +## 1.0.31(2022-11-16) +- uni-id-co 修复 验证码可能无法收到的bug +## 1.0.30(2022-11-11) +- uni-id-co 修复 用户只有openid时绑定微信/QQ报错 +## 1.0.29(2022-11-10) +- uni-id-co 支持URL化方式请求 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#adapter-http) +## 1.0.28(2022-11-09) +- uni-id-co 升级密码加密算法,支持hmac-sha256加密 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#password-safe) +- uni-id-co 新增 开发者可以自定义密码加密规则 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#custom-password-encrypt) +- uni-id-co 新增 支持将其他系统用户迁移至uni-id [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#move-users-to-uni-id) +## 1.0.27(2022-10-26) +- uni-id-co 新增 secureNetworkHandshakeByWeixin 接口,用于建立和微信小程序的安全网络连接 +## 1.0.26(2022-10-18) +- 修复 uni-id-pages 导入时异常的Bug +## 1.0.25(2022-10-14) +- uni-id-co 增加 微信授权手机号登录方式 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin-mobile) +- uni-id-co 增加 解绑第三方平台账号 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-third-account) +- uni-id-co 微信绑定手机号支持通过`getPhoneNumber`事件回调的`code`绑定 [文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-mp-weixin) +- 修复 sendSmsCode 接口未在参数内传递 templateId 时 未能从配置文件读取 templateId 的Bug +## 1.0.24(2022-10-08) +- 修复 报uni-id-users表schema内错误的bug +## 1.0.23(2022-10-08) +- 修复 vue3下vite编译发行打包失败 +- 修复 某些情况下注册账号,报TypeErroe:Cannot read properties of undefined (reading ’showToast‘)的错误 +## 1.0.22(2022-09-23) +- 修复 某些情况下,修改密码报“两次输入密码不一致”的bug +## 1.0.21(2022-09-21) +- 修复 store.hasLogin的值在某些情况下会出错的bug +## 1.0.20(2022-09-21) +- 新增 store 账号信息状态管理,详情:用户中心页面 路径:`/uni_modules/uni-id-pages/pages/userinfo/userinfo` +## 1.0.19(2022-09-20) +- 修复 小程序端,使用将自定义节点设置成[虚拟节点](https://uniapp.dcloud.net.cn/tutorial/vue-api.html#%E5%85%B6%E4%BB%96%E9%85%8D%E7%BD%AE)的uni-ui组件,样式错乱的问题 +## 1.0.18(2022-09-20) +- 修复 微信小程序端 WXSS 编译报错的bug +## 1.0.17(2022.09-19) +- 修复 无法退出登录的bug +## 1.0.16(2022-09-19) +- 修复 在 Edge 浏览器下 input[type="password"] 会出现浏览器自带的密码查看按钮 +- 优化 退出登录重定向页面为 uniIdRouter.loginPage +- 新增 注册账号页面支持返回登录页面 +## 1.0.15(2022-09-19) +- 更新表结构,解决在uni-admin中部分clientDB操作没有权限的问题 +## 1.0.14(2022-09-16) +- 修改 配置项`isAdmin`默认值为`false` +## 1.0.13(2022-09-16) +- 新增 管理员注册页面 +- 新增 配置项`isAdmin`区分是否为管理端 +- 新增 登录成功后自动跳转;跳转优先级:路由携带(`uniIdRedirectUrl`参数) > 返回上一路由 > 跳转首页 +- uni-id-co 优化 注册管理员时管理员存在提示文案 +## 1.0.12(2022-09-07) +- 修复 getSupportedLoginType判断是否支持微信公众号、PC网页微信扫码登录方式报错的Bug +- 优化 适配pc端样式 +- 新增 邮箱验证码注册 +- 新增 邮箱验证码找回密码 +- 新增 退出登录(全局)回调事件:`uni-id-pages-logout`,支持通过[uni.$on](https://uniapp.dcloud.net.cn/api/window/communication.html#on)监听; +- 调整 抽离退出登录方法至`/uni_modules/uni-id-pages/common/common.js`中,方便在项目其他页面中调用 +- 调整 用户中心(路径:`/uni_modules/uni-id-pages/pages/userinfo/userinfo`)默认不再显示退出登录按钮。支持页面传参数`showLoginManage=true`恢复显示 +## 1.0.11(2022-09-01) +- 修复 iOS端,一键登录功能卡在showLoading的问题 +- 更新 合并密码强度与长度配置 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#config) +- uni-id-co 修复 调用 removeAuthorizedApp 接口报错的Bug +- uni-id-co 新增 管理端接口 updateUser [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user) +- uni-id-co 调整 为兼容旧版本,未配置密码强度时提供最简单的密码规则校验(长度大于6即可) +- uni-id-co 调整 注册、登录时如果携带了token则尝试对此token进行登出操作 +- uni-id-co 调整 管理端接口 addUser 增加 mobile、email等参数 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user) +## 1.0.10(2022-08-25) +- 修复 导入uni-id-pages插件时未自动导入uni-open-bridge-common的Bug +## 1.0.9(2022-08-23) +- 修复 uni-id-co 缺失uni-open-bridge-common依赖的Bug +## 1.0.8(2022-08-23) +- 新增 H5端支持微信登录(含微信公众号内的网页授权登录 和 普通浏览器内网页生成二维码,实现手机微信扫码登录)[详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#weixinlogin) +- 新增 登录成功(全局)回调事件:`uni-id-pages-login-success`,支持通过[uni.$on](https://uniapp.dcloud.net.cn/api/window/communication.html#on)监听; +- 新增 密码强度(是否必须包含大小写字母、数字和特殊符号以及长度)配置 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#config) +- 调整 uni-id-co 密码规则调整,废除之前的简单校验,允许配置密码强度 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#password-strength) +- 调整 uni-id-co 存储用户 openid 时同时以客户端 AppId 为 Key 的副本,参考:[微信登录](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin)、[QQ登录](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-qq) +- 调整 uni-id-co 依赖 uni-open-bridge-common 存储用户 session_key、access_token 等信息 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#save-user-token) +- 新增 uni-id-co 增加 beforeRegister 钩子用户在注册前向用户记录内添加一些数据 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#before-register) +## 1.0.7(2022-07-19) +- 修复 uni-id-co接口 logout时没有删除token的Bug +## 1.0.6(2022-07-13) +- 新增 允许覆盖内置校验规则 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#custom-validator) +- 修复 app端clientInfo.appVersionCode为数字导致校验无法通过的Bug +## 1.0.5(2022-07-11) +修复 微信小程序调用uni-id-co接口报错的Bug [详情](https://ask.dcloud.net.cn/question/148877) +## 1.0.4(2022-07-06) +- uni-id-co增加clientInfo字段类型校验 +- 监听token更新时机,同步客户端push_clientid至uni-id-device表,改为:同步客户端push_clientid至uni-id-device表和opendb-device表 +## 1.0.3(2022-07-05) +新增监听token更新时机,同步客户端push_clientid至uni-id-device表 +## 1.0.2(2022-07-04) +修复微信小程序登录时无unionid报错的Bug [详情](https://ask.dcloud.net.cn/question/148016) +## 1.0.1(2022-06-28) +添加相关uni-id表 +## 1.0.0(2022-06-23) +正式版 diff --git a/uni_modules/uni-id-pages/common/check-id-card.js b/uni_modules/uni-id-pages/common/check-id-card.js new file mode 100644 index 0000000000000000000000000000000000000000..c12f2a174aac137cd00d097cd8e28629d2a1a5ca --- /dev/null +++ b/uni_modules/uni-id-pages/common/check-id-card.js @@ -0,0 +1,16 @@ +function checkIdCard (idCardNumber) { + if (!idCardNumber || typeof idCardNumber !== 'string' || idCardNumber.length !== 18) return false + + const coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + const checkCode = [1, 0, 'x', 9, 8, 7, 6, 5, 4, 3, 2] + const code = idCardNumber.substring(17) + + let sum = 0 + for (let i = 0; i < 17; i++) { + sum += Number(idCardNumber.charAt(i)) * coefficient[i] + } + + return checkCode[sum % 11].toString() === code.toLowerCase() +} + +export default checkIdCard diff --git a/uni_modules/uni-id-pages/common/login-page.mixin.js b/uni_modules/uni-id-pages/common/login-page.mixin.js new file mode 100644 index 0000000000000000000000000000000000000000..35e50302ca90e4120124a9438924d30b50e6312c --- /dev/null +++ b/uni_modules/uni-id-pages/common/login-page.mixin.js @@ -0,0 +1,95 @@ +import { + mutations +} from '@/uni_modules/uni-id-pages/common/store.js' +import config from '@/uni_modules/uni-id-pages/config.js' +const mixin = { + data() { + return { + config, + uniIdRedirectUrl: '', + isMounted: false + } + }, + onUnload() { + // #ifdef H5 + document.onkeydown = false + // #endif + }, + mounted() { + this.isMounted = true + }, + onLoad(e) { + if (e.is_weixin_redirect) { + uni.showLoading({ + mask: true + }) + + if (window.location.href.includes('#')) { + // 将url通过 ? 分割获取后面的参数字符串 再通过 & 将每一个参数单独分割出来 + const paramsArr = window.location.href.split('?')[1].split('&') + paramsArr.forEach(item => { + const arr = item.split('=') + if (arr[0] == 'code') { + e.code = arr[1] + } + }) + } + this.$nextTick(n => { + // console.log(this.$refs.uniFabLogin); + this.$refs.uniFabLogin.login({ + code: e.code + }, 'weixin') + }) + } + + if (e.uniIdRedirectUrl) { + this.uniIdRedirectUrl = decodeURIComponent(e.uniIdRedirectUrl) + } + + // #ifdef MP-WEIXIN + if (getCurrentPages().length === 1) { + uni.hideHomeButton() + console.log('已隐藏:返回首页按钮'); + } + // #endif + }, + computed: { + needAgreements() { + if (this.isMounted) { + if (this.$refs.agreements) { + return this.$refs.agreements.needAgreements + } else { + return false + } + } + }, + agree: { + get() { + if (this.isMounted) { + if (this.$refs.agreements) { + return this.$refs.agreements.isAgree + } else { + return true + } + } + }, + set(agree) { + if (this.$refs.agreements) { + this.$refs.agreements.isAgree = agree + } else { + console.log('不存在 隐私政策协议组件'); + } + } + } + }, + methods: { + loginSuccess(e) { + mutations.loginSuccess({ + ...e, + uniIdRedirectUrl: this.uniIdRedirectUrl + }) + } + } +} + +export default mixin diff --git a/uni_modules/uni-id-pages/common/login-page.scss b/uni_modules/uni-id-pages/common/login-page.scss new file mode 100644 index 0000000000000000000000000000000000000000..452b2d8d282d8e3f644ecc0899f88c9014eaae53 --- /dev/null +++ b/uni_modules/uni-id-pages/common/login-page.scss @@ -0,0 +1,126 @@ +// 隐藏 edge 浏览器的密码查看按钮 + +/* #ifdef H5 */ +.input-box ::v-deep{ + .uni-input-input[type="password"] { + &::-ms-reveal { + display: none; + } + } +} +/* #endif */ + +.uni-content { + padding: 0 60rpx; +} + +.login-logo { + display: none; +} + +/* #ifndef APP-NVUE */ +@media screen and (min-width: 690px) { + .uni-content { + /* #ifndef H5 */ + padding: 0; + max-width: 300px; + margin-left: calc(50% - 200px); + /* #endif */ + /* #ifdef H5 */ + margin: 0 auto; + position: relative; + top: 100px; + padding: 30px 40px 80px 40px; + max-width: 450px; + max-height: 450px; + border-radius: 10px; + box-shadow: 0 0 20px #efefef; + background-color: #FFF; + /* #endif */ + } + /* #ifdef H5 */ + .login-logo { + display: flex; + justify-content: center; + } + + .login-logo image { + width: 60px; + height: 60px; + } + + .register-back{ + display: none; + } + + uni-button{ + padding-bottom: 1px; + } + + /* #endif */ +} + +.uni-content view { + box-sizing: border-box; +} +/* #endif */ + + + +.title { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + padding: 18px 0; + font-weight: 800; + flex-direction: column; +} + +.tip { + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + color: #BDBDC0; + font-size: 11px; + margin: 6px 0; +} + + +/* #ifndef APP-NVUE */ +// 解决小程序端开启虚拟节点virtualHost引起的 class = input-box丢失的问题 [详情参考](https://uniapp.dcloud.net.cn/matter.html#%E5%90%84%E5%AE%B6%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6%E4%B8%8D%E5%90%8C-%E5%8F%AF%E8%83%BD%E5%AD%98%E5%9C%A8%E7%9A%84%E5%B9%B3%E5%8F%B0%E5%85%BC%E5%AE%B9%E9%97%AE%E9%A2%98) +.uni-content ::v-deep .uni-easyinput__content, +/* #endif */ + +.input-box { + height: 44px; + background-color: #F8F8F8 !important; + border-radius: 0; + font-size: 14px; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex: 1; +} + +.link { + color: #04498c; + cursor: pointer; +} + +.uni-content ::v-deep .uni-forms-item__inner { + padding-bottom: 8px; +} + +.uni-btn { + text-align: center; + height: 40px; + line-height: 40px; + margin: 15px 0 10px 0; + color: #FFF !important; + border-radius: 5px; + font-size: 16px; +} + +.uni-body.uni_modules-uni-id-pages-pages-login-login-withoutpwd{ + height: auto !important; +} diff --git a/uni_modules/uni-id-pages/common/password.js b/uni_modules/uni-id-pages/common/password.js new file mode 100644 index 0000000000000000000000000000000000000000..3e57c7add997f5a19c1b60914f7534d3cfb8c6db --- /dev/null +++ b/uni_modules/uni-id-pages/common/password.js @@ -0,0 +1,85 @@ +// 导入配置 +import config from '@/uni_modules/uni-id-pages/config.js' + +const {passwordStrength} = config + +// 密码强度表达式 +const passwordRules = { + // 密码必须包含大小写字母、数字和特殊符号 + super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/, + // 密码必须包含字母、数字和特殊符号 + strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/, + // 密码必须为字母、数字和特殊符号任意两种的组合 + medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/, + // 密码必须包含字母和数字 + weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/ +} + +const ERROR = { + normal: { + noPwd: '请输入密码', + noRePwd: '再次输入密码', + rePwdErr: '两次输入密码不一致' + }, + passwordStrengthError: { + super: '密码必须包含大小写字母、数字和特殊符号,密码长度必须在8-16位之间', + strong: '密码必须包含字母、数字和特殊符号,密码长度必须在8-16位之间', + medium: '密码必须为字母、数字和特殊符号任意两种的组合,密码长度必须在8-16位之间', + weak: '密码必须包含字母,密码长度必须在6-16位之间' + } +} + +function validPwd(password) { + //强度校验 + if (passwordStrength && passwordRules[passwordStrength]) { + if (!new RegExp(passwordRules[passwordStrength]).test(password)) { + return ERROR.passwordStrengthError[passwordStrength] + } + } + return true +} + +function getPwdRules(pwdName = 'password', rePwdName = 'password2') { + const rules = {} + rules[pwdName] = { + rules: [{ + required: true, + errorMessage: ERROR.normal.noPwd, + }, + { + validateFunction: function(rule, value, data, callback) { + const checkRes = validPwd(value) + if (checkRes !== true) { + callback(checkRes) + } + return true + } + } + ] + } + + if (rePwdName) { + rules[rePwdName] = { + rules: [{ + required: true, + errorMessage: ERROR.normal.noRePwd, + }, + { + validateFunction: function(rule, value, data, callback) { + if (value != data[pwdName]) { + callback(ERROR.normal.rePwdErr) + } + return true + } + } + ] + } + } + return rules +} + +export default { + ERROR, + validPwd, + getPwdRules +} diff --git a/uni_modules/uni-id-pages/common/store.js b/uni_modules/uni-id-pages/common/store.js new file mode 100644 index 0000000000000000000000000000000000000000..d8f2108befbc97069809233cb10e63fbf6b5cfc6 --- /dev/null +++ b/uni_modules/uni-id-pages/common/store.js @@ -0,0 +1,174 @@ +import pagesJson from '@/pages.json' +import config from '@/uni_modules/uni-id-pages/config.js' + +const uniIdCo = uniCloud.importObject("uni-id-co") +const db = uniCloud.database(); +const usersTable = db.collection('uni-id-users') + +let hostUserInfo = uni.getStorageSync('uni-id-pages-userInfo')||{} +// console.log( hostUserInfo); +const data = { + userInfo: hostUserInfo, + hasLogin: Object.keys(hostUserInfo).length != 0 +} + +// console.log('data', data); +// 定义 mutations, 修改属性 +export const mutations = { + // data不为空,表示传递要更新的值(注意不是覆盖是合并),什么也不传时,直接查库获取更新 + async updateUserInfo(data = false) { + if (data) { + usersTable.where('_id==$env.uid').update(data).then(e => { + // console.log(e); + if (e.result.updated) { + uni.showToast({ + title: "更新成功", + icon: 'none', + duration: 3000 + }); + this.setUserInfo(data) + } else { + uni.showToast({ + title: "没有改变", + icon: 'none', + duration: 3000 + }); + } + }) + + } else { + const uniIdCo = uniCloud.importObject("uni-id-co", { + customUI: true + }) + try { + let res = await usersTable.where("'_id' == $cloudEnv_uid") + .field('mobile,nickname,username,email,avatar_file') + .get() + + const realNameRes = await uniIdCo.getRealNameInfo() + + // console.log('fromDbData',res.result.data); + this.setUserInfo({ + ...res.result.data[0], + realNameAuth: realNameRes + }) + } catch (e) { + this.setUserInfo({},{cover:true}) + console.error(e.message, e.errCode); + } + } + }, + async setUserInfo(data, {cover}={cover:false}) { + // console.log('set-userInfo', data); + let userInfo = cover?data:Object.assign(store.userInfo,data) + store.userInfo = Object.assign({},userInfo) + store.hasLogin = Object.keys(store.userInfo).length != 0 + // console.log('store.userInfo', store.userInfo); + uni.setStorageSync('uni-id-pages-userInfo', store.userInfo) + return data + }, + async logout() { + // 1. 已经过期就不需要调用服务端的注销接口 2.即使调用注销接口失败,不能阻塞客户端 + if(uniCloud.getCurrentUserInfo().tokenExpired > Date.now()){ + try{ + await uniIdCo.logout() + }catch(e){ + console.error(e); + } + } + uni.removeStorageSync('uni_id_token'); + uni.setStorageSync('uni_id_token_expired', 0) + uni.redirectTo({ + url: `/${pagesJson.uniIdRouter && pagesJson.uniIdRouter.loginPage ? pagesJson.uniIdRouter.loginPage: 'uni_modules/uni-id-pages/pages/login/login-withoutpwd'}`, + }); + uni.$emit('uni-id-pages-logout') + this.setUserInfo({},{cover:true}) + }, + + loginBack (e = {}) { + const {uniIdRedirectUrl = ''} = e + let delta = 0; //判断需要返回几层 + let pages = getCurrentPages(); + // console.log(pages); + pages.forEach((page, index) => { + if (pages[pages.length - index - 1].route.split('/')[3] == 'login') { + delta++ + } + }) + // console.log('判断需要返回几层:', delta); + if (uniIdRedirectUrl) { + return uni.redirectTo({ + url: uniIdRedirectUrl, + fail: (err1) => { + uni.switchTab({ + url:uniIdRedirectUrl, + fail: (err2) => { + console.log(err1,err2) + } + }) + } + }) + } + // #ifdef H5 + if (e.loginType == 'weixin') { + // console.log('window.history', window.history); + return window.history.go(-3) + } + // #endif + + if (delta) { + const page = pagesJson.pages[0] + return uni.reLaunch({ + url: `/${page.path}` + }) + } + + uni.navigateBack({ + delta + }) + }, + loginSuccess(e = {}){ + const { + showToast = true, toastText = '登录成功', autoBack = true, uniIdRedirectUrl = '', passwordConfirmed + } = e + // console.log({toastText,autoBack}); + if (showToast) { + uni.showToast({ + title: toastText, + icon: 'none', + duration: 3000 + }); + } + this.updateUserInfo() + + uni.$emit('uni-id-pages-login-success') + + if (config.setPasswordAfterLogin && !passwordConfirmed) { + return uni.redirectTo({ + url: uniIdRedirectUrl ? `/uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd?uniIdRedirectUrl=${uniIdRedirectUrl}&loginType=${e.loginType}`: `/uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd?loginType=${e.loginType}`, + fail: (err) => { + console.log(err) + } + }) + } + + if (autoBack) { + this.loginBack({uniIdRedirectUrl}) + } + } + +} + +// #ifdef VUE2 +import Vue from 'vue' +// 通过Vue.observable创建一个可响应的对象 +export const store = Vue.observable(data) +// #endif + +// #ifdef VUE3 +import { + reactive +} from 'vue' +// 通过Vue.observable创建一个可响应的对象 +export const store = reactive(data) +// #endif diff --git a/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue b/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue new file mode 100644 index 0000000000000000000000000000000000000000..5f99a39a096a155c15be81b8faac8ee337cb334b --- /dev/null +++ b/uni_modules/uni-id-pages/components/cloud-image/cloud-image.vue @@ -0,0 +1,73 @@ + + + \ No newline at end of file diff --git a/uni_modules/uni-id-pages/components/uni-id-pages-agreements/uni-id-pages-agreements.vue b/uni_modules/uni-id-pages/components/uni-id-pages-agreements/uni-id-pages-agreements.vue new file mode 100644 index 0000000000000000000000000000000000000000..861e3887e275f542c85201a5cb8a2d97bc0af4aa --- /dev/null +++ b/uni_modules/uni-id-pages/components/uni-id-pages-agreements/uni-id-pages-agreements.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/uni_modules/uni-id-pages/components/uni-id-pages-avatar/uni-id-pages-avatar.vue b/uni_modules/uni-id-pages/components/uni-id-pages-avatar/uni-id-pages-avatar.vue new file mode 100644 index 0000000000000000000000000000000000000000..5b823552cb2b179127ea46c88611dd603ecbbdeb --- /dev/null +++ b/uni_modules/uni-id-pages/components/uni-id-pages-avatar/uni-id-pages-avatar.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/uni_modules/uni-id-pages/components/uni-id-pages-bind-mobile/uni-id-pages-bind-mobile.vue b/uni_modules/uni-id-pages/components/uni-id-pages-bind-mobile/uni-id-pages-bind-mobile.vue new file mode 100644 index 0000000000000000000000000000000000000000..c67b2609af442938b92414b74606504e91301369 --- /dev/null +++ b/uni_modules/uni-id-pages/components/uni-id-pages-bind-mobile/uni-id-pages-bind-mobile.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/uni_modules/uni-id-pages/components/uni-id-pages-email-form/uni-id-pages-email-form.vue b/uni_modules/uni-id-pages/components/uni-id-pages-email-form/uni-id-pages-email-form.vue new file mode 100644 index 0000000000000000000000000000000000000000..179452f1c07759aeb0aafdf1d699353bfc9aeb18 --- /dev/null +++ b/uni_modules/uni-id-pages/components/uni-id-pages-email-form/uni-id-pages-email-form.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/uni_modules/uni-id-pages/components/uni-id-pages-fab-login/uni-id-pages-fab-login.vue b/uni_modules/uni-id-pages/components/uni-id-pages-fab-login/uni-id-pages-fab-login.vue new file mode 100644 index 0000000000000000000000000000000000000000..913f4435b33f4a43fc1d3ffe747036cfe831fdc4 --- /dev/null +++ b/uni_modules/uni-id-pages/components/uni-id-pages-fab-login/uni-id-pages-fab-login.vue @@ -0,0 +1,568 @@ + + + + diff --git a/uni_modules/uni-id-pages/components/uni-id-pages-sms-form/uni-id-pages-sms-form.vue b/uni_modules/uni-id-pages/components/uni-id-pages-sms-form/uni-id-pages-sms-form.vue new file mode 100644 index 0000000000000000000000000000000000000000..0a4eba3a3ac5bf3a49352b5e030aa2400e95bfdb --- /dev/null +++ b/uni_modules/uni-id-pages/components/uni-id-pages-sms-form/uni-id-pages-sms-form.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/uni_modules/uni-id-pages/config.js b/uni_modules/uni-id-pages/config.js new file mode 100644 index 0000000000000000000000000000000000000000..e847774904015c7b21e0bc94379681b158bed1ea --- /dev/null +++ b/uni_modules/uni-id-pages/config.js @@ -0,0 +1,67 @@ +export default { + // 调试模式 + debug: false, + /* + 登录类型 未列举到的或运行环境不支持的,将被自动隐藏。 + 如果需要在不同平台有不同的配置,直接用条件编译即可 + */ + isAdmin: false, // 区分管理端与用户端 + loginTypes: [ + // "qq", + // "xiaomi", + // "sinaweibo", + // "taobao", + // "facebook", + // "google", + // "alipay", + // "douyin", + + // #ifdef APP + 'univerify', + // #endif + 'weixin', + 'username', + // #ifdef APP + 'apple', + // #endif + 'smsCode' + ], + // 政策协议 + agreements: { + serviceUrl: 'https://xxx', // 用户服务协议链接 + privacyUrl: 'https://xxx', // 隐私政策条款链接 + // 哪些场景下显示,1.注册(包括登录并注册,如:微信登录、苹果登录、短信验证码登录)、2.登录(如:用户名密码登录) + scope: [ + 'register', 'login', 'realNameVerify' + ] + }, + // 提供各类服务接入(如微信登录服务)的应用id + appid: { + weixin: { + // 微信公众号的appid,来源:登录微信公众号(https://mp.weixin.qq.com)-> 设置与开发 -> 基本配置 -> 公众号开发信息 -> AppID + h5: 'xxxxxx', + // 微信开放平台的appid,来源:登录微信开放平台(https://open.weixin.qq.com) -> 管理中心 -> 网站应用 -> 选择对应的应用名称,点击查看 -> AppID + web: 'xxxxxx' + } + }, + /** + * 密码强度 + * super(超强:密码必须包含大小写字母、数字和特殊符号,长度范围:8-16位之间) + * strong(强: 密密码必须包含字母、数字和特殊符号,长度范围:8-16位之间) + * medium (中:密码必须为字母、数字和特殊符号任意两种的组合,长度范围:8-16位之间) + * weak(弱:密码必须包含字母和数字,长度范围:6-16位之间) + * 为空或false则不验证密码强度 + */ + passwordStrength: 'medium', + /** + * 登录后允许用户设置密码(只针对未设置密码得用户) + * 开启此功能将 setPasswordAfterLogin 设置为 true 即可 + * "setPasswordAfterLogin": false + * + * 如果允许用户跳过设置密码 将 allowSkip 设置为 true + * "setPasswordAfterLogin": { + * "allowSkip": true + * } + * */ + setPasswordAfterLogin: false +} diff --git a/uni_modules/uni-id-pages/init.js b/uni_modules/uni-id-pages/init.js new file mode 100644 index 0000000000000000000000000000000000000000..c0d109753e3da6ca4840f2aed222713b0afd2e22 --- /dev/null +++ b/uni_modules/uni-id-pages/init.js @@ -0,0 +1,95 @@ +// 导入配置 +import config from '@/uni_modules/uni-id-pages/config.js' +// uni-id的云对象 +const uniIdCo = uniCloud.importObject('uni-id-co', { + customUI: true +}) +// 用户配置的登录方式、是否打开调试模式 +const { + loginTypes, + debug +} = config + +export default async function () { + // 有打开调试模式的情况下 + if (debug) { + // 1. 检查本地uni-id-pages中配置的登录方式,服务器端是否已经配置正确。否则提醒并引导去配置 + // 调用云对象,获取服务端已正确配置的登录方式 + const { + supportedLoginType + } = await uniIdCo.getSupportedLoginType() + console.log('supportedLoginType: ' + JSON.stringify(supportedLoginType)) + // 登录方式,服务端和客户端的映射关系 + const data = { + smsCode: 'mobile-code', + univerify: 'univerify', + username: 'username-password', + weixin: 'weixin', + qq: 'qq', + xiaomi: 'xiaomi', + sinaweibo: 'sinaweibo', + taobao: 'taobao', + facebook: 'facebook', + google: 'google', + alipay: 'alipay', + apple: 'apple', + weixinMobile: 'weixin' + } + // 遍历客户端配置的登录方式,与服务端比对。并在错误时抛出错误提示 + const list = loginTypes.filter(type => !supportedLoginType.includes(data[type])) + if (list.length) { + console.error( + `错误:前端启用的登录方式:${list.join(',')};没有在服务端完成配置。配置文件路径:"/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json"` + ) + } + } + + // #ifdef APP-PLUS + // 如果uni-id-pages配置的登录功能有一键登录,有则执行预登录(异步) + if (loginTypes.includes('univerify')) { + uni.preLogin({ + provider: 'univerify', + complete: e => { + // console.log(e); + } + }) + } + // #endif + + // 3. 绑定clientDB错误事件 + // clientDB对象 + const db = uniCloud.database() + db.on('error', onDBError) + // clientDB的错误提示 + function onDBError ({ + code, // 错误码详见https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=returnvalue + message + }) { + // console.error('onDBError', {code,message}); + } + // 解绑clientDB错误事件 + // db.off('error', onDBError) + + // 4. 同步客户端push_clientid至device表 + if (uniCloud.onRefreshToken) { + uniCloud.onRefreshToken(() => { + // console.log('onRefreshToken'); + if (uni.getPushClientId) { + uni.getPushClientId({ + success: async function (e) { + // console.log(e) + const pushClientId = e.cid + // console.log(pushClientId); + const res = await uniIdCo.setPushCid({ + pushClientId + }) + // console.log('getPushClientId', res); + }, + fail (e) { + // console.log(e) + } + }) + } + }) + } +} diff --git a/uni_modules/uni-id-pages/package.json b/uni_modules/uni-id-pages/package.json new file mode 100644 index 0000000000000000000000000000000000000000..6a7762aceced663c372dd2894c64dee7a2716492 --- /dev/null +++ b/uni_modules/uni-id-pages/package.json @@ -0,0 +1,102 @@ +{ + "id": "uni-id-pages", + "displayName": "uni-id-pages", + "version": "1.1.19", + "description": "云端一体简单、统一、可扩展的用户中心页面模版", + "keywords": [ + "用户管理", + "用户中心", + "短信验证码", + "login", + "登录" + ], + "repository": "https://gitcode.net/dcloud/hello_uni-id-pages", + "engines": { + "HBuilderX": "^3.4.17" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "", + "type": "unicloud-template-page" + }, + "uni_modules": { + "dependencies": [ + "uni-captcha", + "uni-config-center", + "uni-data-checkbox", + "uni-easyinput", + "uni-forms", + "uni-icons", + "uni-id-common", + "uni-list", + "uni-load-more", + "uni-popup", + "uni-scss", + "uni-transition", + "uni-open-bridge-common", + "uni-cloud-s2s" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "u", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "钉钉": "u", + "快手": "u", + "飞书": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + }, + "dependencies": { + } +} diff --git a/uni_modules/uni-id-pages/pages/common/webview/webview.vue b/uni_modules/uni-id-pages/pages/common/webview/webview.vue new file mode 100644 index 0000000000000000000000000000000000000000..c122623c61babbcb034aa0a8a88adb22f95f5731 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/common/webview/webview.vue @@ -0,0 +1,35 @@ + + + + diff --git a/uni_modules/uni-id-pages/pages/login/login-smscode.vue b/uni_modules/uni-id-pages/pages/login/login-smscode.vue new file mode 100644 index 0000000000000000000000000000000000000000..0651fd9662faadd8115a19ea97a7b20e63f9426a --- /dev/null +++ b/uni_modules/uni-id-pages/pages/login/login-smscode.vue @@ -0,0 +1,120 @@ + + + + diff --git a/uni_modules/uni-id-pages/pages/login/login-withoutpwd.vue b/uni_modules/uni-id-pages/pages/login/login-withoutpwd.vue new file mode 100644 index 0000000000000000000000000000000000000000..64a6bcf4482315af2912ea7071629b9e2d3413dc --- /dev/null +++ b/uni_modules/uni-id-pages/pages/login/login-withoutpwd.vue @@ -0,0 +1,257 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/login/login-withpwd.vue b/uni_modules/uni-id-pages/pages/login/login-withpwd.vue new file mode 100644 index 0000000000000000000000000000000000000000..e785554b03a32460604af996432123d8c01492c0 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/login/login-withpwd.vue @@ -0,0 +1,176 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/register/register-admin.vue b/uni_modules/uni-id-pages/pages/register/register-admin.vue new file mode 100644 index 0000000000000000000000000000000000000000..ade23f3f3f98dbd9be983b72c0eebb52c8386fd1 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/register/register-admin.vue @@ -0,0 +1,178 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/register/register-by-email.vue b/uni_modules/uni-id-pages/pages/register/register-by-email.vue new file mode 100644 index 0000000000000000000000000000000000000000..bb51d436a328d7717e9c39e4f3462805596f929b --- /dev/null +++ b/uni_modules/uni-id-pages/pages/register/register-by-email.vue @@ -0,0 +1,216 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/register/register.vue b/uni_modules/uni-id-pages/pages/register/register.vue new file mode 100644 index 0000000000000000000000000000000000000000..8ea32342d78c2928b98deb6eb5c1fc3dd86a29c4 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/register/register.vue @@ -0,0 +1,181 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/register/validator.js b/uni_modules/uni-id-pages/pages/register/validator.js new file mode 100644 index 0000000000000000000000000000000000000000..907f901070ff7838b1907572d9a1381b387a850f --- /dev/null +++ b/uni_modules/uni-id-pages/pages/register/validator.js @@ -0,0 +1,56 @@ +import passwordMod from '@/uni_modules/uni-id-pages/common/password.js' +export default { + "username": { + "rules": [{ + required: true, + errorMessage: '请输入用户名', + }, + { + minLength: 3, + maxLength: 32, + errorMessage: '用户名长度在 {minLength} 到 {maxLength} 个字符', + }, + { + validateFunction: function(rule, value, data, callback) { + // console.log(value); + if (/^1\d{10}$/.test(value) || /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(value)) { + callback('用户名不能是:手机号或邮箱') + }; + if (/^\d+$/.test(value)) { + callback('用户名不能为纯数字') + }; + if(/[\u4E00-\u9FA5\uF900-\uFA2D]{1,}/.test(value)){ + callback('用户名不能包含中文') + } + return true + } + } + ], + "label": "用户名" + }, + "nickname": { + "rules": [{ + minLength: 3, + maxLength: 32, + errorMessage: '昵称长度在 {minLength} 到 {maxLength} 个字符', + }, + { + validateFunction: function(rule, value, data, callback) { + // console.log(value); + if (/^1\d{10}$/.test(value) || /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(value)) { + callback('昵称不能是:手机号或邮箱') + }; + if (/^\d+$/.test(value)) { + callback('昵称不能为纯数字') + }; + if(/[\u4E00-\u9FA5\uF900-\uFA2D]{1,}/.test(value)){ + callback('昵称不能包含中文') + } + return true + } + } + ], + "label": "昵称" + }, + ...passwordMod.getPwdRules() +} diff --git a/uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email.vue b/uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email.vue new file mode 100644 index 0000000000000000000000000000000000000000..bf30c7689fa1daab92631d977571fd4d9f47b9b5 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email.vue @@ -0,0 +1,218 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/retrieve/retrieve.vue b/uni_modules/uni-id-pages/pages/retrieve/retrieve.vue new file mode 100644 index 0000000000000000000000000000000000000000..73cfa36b6830a5c3c88d0b99e956aca8533f0d2e --- /dev/null +++ b/uni_modules/uni-id-pages/pages/retrieve/retrieve.vue @@ -0,0 +1,241 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/bind-mobile/bind-mobile.vue b/uni_modules/uni-id-pages/pages/userinfo/bind-mobile/bind-mobile.vue new file mode 100644 index 0000000000000000000000000000000000000000..032cf8c93062809e0a8488fd611ffcb3f4c63fd0 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/bind-mobile/bind-mobile.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd.vue b/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd.vue new file mode 100644 index 0000000000000000000000000000000000000000..75e0e044b06cc433eefd358a05d2af79d961ab3b --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/change_pwd/change_pwd.vue @@ -0,0 +1,130 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/cropImage.vue b/uni_modules/uni-id-pages/pages/userinfo/cropImage/cropImage.vue new file mode 100644 index 0000000000000000000000000000000000000000..ffc0e8b23d685bb66bbec0799db69518fce563ce --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/cropImage.vue @@ -0,0 +1,39 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/README.md b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/README.md new file mode 100644 index 0000000000000000000000000000000000000000..160c207dcda67881e8a02ef9ff5ce2f8e67422d7 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/README.md @@ -0,0 +1,227 @@ +> 插件来源:[https://ext.dcloud.net.cn/plugin?id=3594](https://ext.dcloud.net.cn/plugin?id=3594) +##### 以下是作者写的插件介绍: + +# Clipper 图片裁剪 +> uniapp 图片裁剪,可用于图片头像等裁剪处理 +> [查看更多](http://liangei.gitee.io/limeui/#/clipper)
+> Q群:458377637 + + +## 平台兼容 + +| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App | +| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- | +| √ | √ | √ | 未测 | √ | √ | √ | + + +## 代码演示 +### 基本用法 +`@success` 事件点击 👉 **确定** 后会返回生成的图片信息,包含 `url`、`width`、`height` + +```html + + + +``` + +```js +// 非uni_modules引入 +import lClipper from '@/components/lime-clipper/' +// uni_modules引入 +import lClipper from '@/uni_modules/lime-clipper/components/lime-clipper/' +export default { + components: {lClipper}, + data() { + return { + show: false, + url: '', + } + } +} +``` + + +### 传入图片 +`image-url`可传入**相对路径**、**临时路径**、**本地路径**、**网络图片**
+ +* **当为网络地址时** +* H5:👉 需要解决跨域问题。
+* 小程序:👉 需要配置 downloadFile 域名
+ + +```html + + + +``` + +```js +export default { + components: {lClipper}, + data() { + return { + imageUrl: 'https://img12.360buyimg.com/pop/s1180x940_jfs/t1/97205/26/1142/87801/5dbac55aEf795d962/48a4d7a63ff80b8b.jpg', + show: false, + url: '', + } + } +} +``` + + +### 确定按钮颜色 +样式变量名:`--l-clipper-confirm-color` +可放到全局样式的 `page` 里或节点的 `style` +```html + +``` +```css +// css 中为组件设置 CSS 变量 +.clipper { + --l-clipper-confirm-color: linear-gradient(to right, #ff6034, #ee0a24) +} +// 全局 +page { + --l-clipper-confirm-color: linear-gradient(to right, #ff6034, #ee0a24) +} +``` + + +### 使用插槽 +共五个插槽 `cancel` 取消按钮、 `photo` 选择图片按钮、 `rotate` 旋转按钮、 `confirm` 确定按钮和默认插槽。 + +```html + + + + 取消 + 选择图片 + 旋转 + 确定 + + + 显示取消按钮 + + + 显示选择图片按钮 + + + 显示旋转按钮 + + + 显示确定按钮 + + + 锁定裁剪框宽度 + + + 锁定裁剪框高度 + + + 锁定裁剪框比例 + + + 限制移动范围 + + + 禁止缩放 + + + 禁止旋转 + + + + + +``` + +```js +export default { + components: {lClipper}, + data() { + return { + show: false, + url: '', + isLockWidth: false, + isLockHeight: false, + isLockRatio: true, + isLimitMove: false, + isDisableScale: false, + isDisableRotate: false, + isShowCancelBtn: true, + isShowPhotoBtn: true, + isShowRotateBtn: true, + isShowConfirmBtn: true + } + } +} +``` + + +## API + +### Props + +| 参数 | 说明 | 类型 | 默认值 | +| ------------- | ------------ | ---------------- | ------------ | +| image-url | 图片路径 | string | | +| quality | 图片的质量,取值范围为 [0, 1],不在范围内时当作1处理 | number | `1` | +| source | `{album: '从相册中选择'}`key为图片来源类型,value为选项说明 | Object | | +| width | 裁剪框宽度,单位为 `rpx` | number | `400` | +| height | 裁剪框高度 | number | `400` | +| min-width | 裁剪框最小宽度 | number | `200` | +| min-height |裁剪框最小高度 | number | `200` | +| max-width | 裁剪框最大宽度 | number | `600` | +| max-height | 裁剪框最大宽度 | number | `600` | +| min-ratio | 图片最小缩放比 | number | `0.5` | +| max-ratio | 图片最大缩放比 | number | `2` | +| rotate-angle | 旋转按钮每次旋转的角度 | number | `90` | +| scale-ratio | 生成图片相对于裁剪框的比例, **比例越高生成图片越清晰** | number | `1` | +| is-lock-width | 是否锁定裁剪框宽度 | boolean | `false` | +| is-lock-height | 是否锁定裁剪框高度上 | boolean | `false` | +| is-lock-ratio | 是否锁定裁剪框比例 | boolean | `true` | +| is-disable-scale | 是否禁止缩放 | boolean | `false` | +| is-disable-rotate | 是否禁止旋转 | boolean | `false` | +| is-limit-move | 是否限制移动范围 | boolean | `false` | +| is-show-photo-btn | 是否显示选择图片按钮 | boolean | `true` | +| is-show-rotate-btn | 是否显示转按钮 | boolean | `true` | +| is-show-confirm-btn | 是否显示确定按钮 | boolean | `true` | +| is-show-cancel-btn | 是否显示关闭按钮 | boolean | `true` | + + + +### 事件 Events + +| 事件名 | 说明 | 回调 | +| ------- | ------------ | -------------- | +| success | 生成图片成功 | {`width`, `height`, `url`} | +| fail | 生成图片失败 | `error` | +| cancel | 关闭 | `false` | +| ready | 图片加载完成 | {`width`, `height`, `path`, `orientation`, `type`} | +| change | 图片大小改变时触发 | {`width`, `height`} | +| rotate | 图片旋转时触发 | `angle` | + +## 常见问题 +> 1、H5端使用网络图片需要解决跨域问题。
+> 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
+> 3、H5端生成图片是base64,有时显示只有一半可以使用原生标签``
+> 4、IOS APP 请勿使用HBX2.9.3.20201014的版本!这个版本无法生成图片。
+> 5、APP端无成功反馈、也无失败反馈时,请更新基座和HBX。
+ + +## 打赏 +如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+![输入图片说明](https://images.gitee.com/uploads/images/2020/1122/222521_bb543f96_518581.jpeg "微信图片编辑_20201122220352.jpg") \ No newline at end of file diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/images/photo.svg b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/images/photo.svg new file mode 100644 index 0000000000000000000000000000000000000000..4fd415f2e78b3cad9b5759be7be05a77734db6fe --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/images/photo.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/images/rotate.svg b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/images/rotate.svg new file mode 100644 index 0000000000000000000000000000000000000000..4cc7dbae8b0df01802f66f0b88f177f1adc96913 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/images/rotate.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/index.css b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/index.css new file mode 100644 index 0000000000000000000000000000000000000000..3794573a59ec8fb3efd065e9f5020b5a37e63ad2 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/index.css @@ -0,0 +1,160 @@ +.flex-auto { + flex: auto; +} +.bg-transparent { + background-color: rgba(0,0,0,0.9); + transition-duration: 0.35s; +} +.l-clipper { + width: 100vw; + height: calc(100vh - var(--window-top)); + background-color: rgba(0,0,0,0.9); + position: fixed; + top: var(--window-top); + left: 0; + z-index: 1; +} +.l-clipper-mask { + position: relative; + z-index: 2; + pointer-events: none; +} +.l-clipper__content { + pointer-events: none; + position: absolute; + border: 1rpx solid rgba(255,255,255,0.3); + box-sizing: border-box; + box-shadow: rgba(0,0,0,0.5) 0 0 0 80vh; + background: transparent; +} +.l-clipper__content::before, +.l-clipper__content::after { + content: ''; + position: absolute; + border: 1rpx dashed rgba(255,255,255,0.3); +} +.l-clipper__content::before { + width: 100%; + top: 33.33%; + height: 33.33%; + border-left: none; + border-right: none; +} +.l-clipper__content::after { + width: 33.33%; + left: 33.33%; + height: 100%; + border-top: none; + border-bottom: none; +} +.l-clipper__edge { + position: absolute; + width: 34rpx; + height: 34rpx; + border: 6rpx solid #fff; + pointer-events: auto; +} +.l-clipper__edge::before { + content: ''; + position: absolute; + width: 40rpx; + height: 40rpx; + background-color: transparent; +} +.l-clipper__edge:nth-child(1) { + left: -6rpx; + top: -6rpx; + border-bottom-width: 0 !important; + border-right-width: 0 !important; +} +.l-clipper__edge:nth-child(1):before { + top: -50%; + left: -50%; +} +.l-clipper__edge:nth-child(2) { + right: -6rpx; + top: -6rpx; + border-bottom-width: 0 !important; + border-left-width: 0 !important; +} +.l-clipper__edge:nth-child(2):before { + top: -50%; + left: 50%; +} +.l-clipper__edge:nth-child(3) { + left: -6rpx; + bottom: -6rpx; + border-top-width: 0 !important; + border-right-width: 0 !important; +} +.l-clipper__edge:nth-child(3):before { + bottom: -50%; + left: -50%; +} +.l-clipper__edge:nth-child(4) { + right: -6rpx; + bottom: -6rpx; + border-top-width: 0 !important; + border-left-width: 0 !important; +} +.l-clipper__edge:nth-child(4):before { + bottom: -50%; + left: 50%; +} +.l-clipper-image { + width: 100%; + border-style: none; + position: absolute; + top: 0; + left: 0; + z-index: 1; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transform-origin: center; +} +.l-clipper-canvas { + position: fixed; + z-index: 10; + left: -200vw; + top: -200vw; + pointer-events: none; +} +.l-clipper-tools { + position: fixed; + left: 0; + bottom: 10px; + width: 100%; + z-index: 99; + color: #fff; +} +.l-clipper-tools__btns { + font-weight: bold; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 20rpx 40rpx; + box-sizing: border-box; +} +.l-clipper-tools__btns .cancel { + width: 112rpx; + height: 60rpx; + text-align: center; + line-height: 60rpx; +} +.l-clipper-tools__btns .confirm { + width: 112rpx; + height: 60rpx; + line-height: 60rpx; + background-color: #07c160; + border-radius: 6rpx; + text-align: center; +} +.l-clipper-tools__btns image { + display: block; + width: 60rpx; + height: 60rpx; +} +.l-clipper-tools__btns { + flex-direction: row; +} diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/limeClipper.vue b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/limeClipper.vue new file mode 100644 index 0000000000000000000000000000000000000000..e6db3eac5148545b2d914c0615ea062ec1571123 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/limeClipper.vue @@ -0,0 +1,820 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/utils.js b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..c807d8cbbf3948081e07f5ec4fbb9c703af0f3f5 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/cropImage/limeClipper/utils.js @@ -0,0 +1,244 @@ +/** + * 判断手指触摸位置 + */ +export function determineDirection(clipX, clipY, clipWidth, clipHeight, currentX, currentY) { + /* + * (右下>>1 右上>>2 左上>>3 左下>>4) + */ + let corner; + /** + * 思路:(利用直角坐标系) + * 1.找出裁剪框中心点 + * 2.如点击坐标在上方点与左方点区域内,则点击为左上角 + * 3.如点击坐标在下方点与右方点区域内,则点击为右下角 + * 4.其他角同理 + */ + const mainPoint = [clipX + clipWidth / 2, clipY + clipHeight / 2]; // 中心点 + const currentPoint = [currentX, currentY]; // 触摸点 + + if (currentPoint[0] <= mainPoint[0] && currentPoint[1] <= mainPoint[1]) { + corner = 3; // 左上 + } else if (currentPoint[0] >= mainPoint[0] && currentPoint[1] <= mainPoint[1]) { + corner = 2; // 右上 + } else if (currentPoint[0] <= mainPoint[0] && currentPoint[1] >= mainPoint[1]) { + corner = 4; // 左下 + } else if (currentPoint[0] >= mainPoint[0] && currentPoint[1] >= mainPoint[1]) { + corner = 1; // 右下 + } + + return corner; +} + +/** + * 图片边缘检测检测时,计算图片偏移量 + */ +export function calcImageOffset(data, scale) { + let left = data.imageLeft; + let top = data.imageTop; + scale = scale || data.scale; + + let imageWidth = data.imageWidth; + let imageHeight = data.imageHeight; + if ((data.angle / 90) % 2) { + imageWidth = data.imageHeight; + imageHeight = data.imageWidth; + } + const { + clipX, + clipWidth, + clipY, + clipHeight + } = data; + + // 当前图片宽度/高度 + const currentImageSize = (size) => (size * scale) / 2; + const currentImageWidth = currentImageSize(imageWidth); + const currentImageHeight = currentImageSize(imageHeight); + + left = clipX + currentImageWidth >= left ? left : clipX + currentImageWidth; + left = clipX + clipWidth - currentImageWidth <= left ? left : clipX + clipWidth - currentImageWidth; + top = clipY + currentImageHeight >= top ? top : clipY + currentImageHeight; + top = clipY + clipHeight - currentImageHeight <= top ? top : clipY + clipHeight - currentImageHeight; + return { + left, + top, + scale + }; +} + +/** + * 图片边缘检测时,计算图片缩放比例 + */ +export function calcImageScale(data, scale) { + scale = scale || data.scale; + let { + imageWidth, + imageHeight, + clipWidth, + clipHeight, + angle + } = data + if ((angle / 90) % 2) { + imageWidth = imageHeight; + imageHeight = imageWidth; + } + if (imageWidth * scale < clipWidth) { + scale = clipWidth / imageWidth; + } + if (imageHeight * scale < clipHeight) { + scale = Math.max(scale, clipHeight / imageHeight); + } + return scale; +} + +/** + * 计算图片尺寸 + */ +export function calcImageSize(width, height, data) { + let imageWidth = width, + imageHeight = height; + let { + clipWidth, + clipHeight, + sysinfo, + width: originWidth, + height: originHeight + } = data + if (imageWidth && imageHeight) { + if (imageWidth / imageHeight > (clipWidth || originWidth) / (clipWidth || originHeight)) { + imageHeight = clipHeight || originHeight; + imageWidth = (width / height) * imageHeight; + } else { + imageWidth = clipWidth || originWidth; + imageHeight = (height / width) * imageWidth; + } + } else { + let sys = sysinfo || uni.getSystemInfoSync(); + imageWidth = sys.windowWidth; + imageHeight = 0; + } + return { + imageWidth, + imageHeight + }; +} + +/** + * 勾股定理求斜边 + */ +export function calcPythagoreanTheorem(width, height) { + return Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); +} + +/** + * 拖动裁剪框时计算 + */ +export function clipTouchMoveOfCalculate(data, event) { + const clientX = event.touches[0].clientX; + const clientY = event.touches[0].clientY; + + let { + clipWidth, + clipHeight, + clipY: oldClipY, + clipX: oldClipX, + clipStart, + isLockRatio, + maxWidth, + minWidth, + maxHeight, + minHeight + } = data; + maxWidth = maxWidth / 2; + minWidth = minWidth / 2; + minHeight = minHeight / 2; + maxHeight = maxHeight / 2; + + let width = clipWidth, + height = clipHeight, + clipY = oldClipY, + clipX = oldClipX, + // 获取裁剪框实际宽度/高度 + // 如果大于最大值则使用最大值 + // 如果小于最小值则使用最小值 + sizecorrect = () => { + width = width <= maxWidth ? (width >= minWidth ? width : minWidth) : maxWidth; + height = height <= maxHeight ? (height >= minHeight ? height : minHeight) : maxHeight; + }, + sizeinspect = () => { + sizecorrect(); + if ((width > maxWidth || width < minWidth || height > maxHeight || height < minHeight) && isLockRatio) { + return false; + } else { + return true; + } + }; + //if (clipStart.corner) { + height = clipStart.height + (clipStart.corner > 1 && clipStart.corner < 4 ? 1 : -1) * (clipStart.y - clientY); + //} + switch (clipStart.corner) { + case 1: + width = clipStart.width - clipStart.x + clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) return; + break; + case 2: + width = clipStart.width - clipStart.x + clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) { + return; + } else { + clipY = clipStart.clipY - (height - clipStart.height); + } + + break; + case 3: + width = clipStart.width + clipStart.x - clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) { + return; + } else { + clipY = clipStart.clipY - (height - clipStart.height); + clipX = clipStart.clipX - (width - clipStart.width); + } + + break; + case 4: + width = clipStart.width + clipStart.x - clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) { + return; + } else { + clipX = clipStart.clipX - (width - clipStart.width); + } + break; + default: + break; + } + return { + width, + height, + clipX, + clipY + }; +} + +/** + * 单指拖动图片计算偏移 + */ +export function imageTouchMoveOfCalcOffset(data, clientXForLeft, clientYForLeft) { + let left = clientXForLeft - data.touchRelative[0].x, + top = clientYForLeft - data.touchRelative[0].y; + return { + left, + top + }; +} diff --git a/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate.vue b/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate.vue new file mode 100644 index 0000000000000000000000000000000000000000..6d96421551c79cbcce792f58d9482d6c85dc2b53 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/deactivate/deactivate.vue @@ -0,0 +1,117 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg new file mode 100644 index 0000000000000000000000000000000000000000..1651d9861c3d461b9639d4f97fabe87228a90e6d --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/face-verify-icon.svg @@ -0,0 +1 @@ + diff --git a/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue new file mode 100644 index 0000000000000000000000000000000000000000..e402d75f855d9e2e5fbaf328d3a7f5ff8803b09c --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/realname-verify/realname-verify.vue @@ -0,0 +1,315 @@ + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd.vue b/uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd.vue new file mode 100644 index 0000000000000000000000000000000000000000..4897b36e8dfdc7569f91654868b04e68b586190f --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/set-pwd/set-pwd.vue @@ -0,0 +1,171 @@ + + + + + + diff --git a/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue b/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue new file mode 100644 index 0000000000000000000000000000000000000000..f4d5db715e9e74cc26664d94bac5de5479170d19 --- /dev/null +++ b/uni_modules/uni-id-pages/pages/userinfo/userinfo.vue @@ -0,0 +1,272 @@ + + + + diff --git a/uni_modules/uni-id-pages/readme.md b/uni_modules/uni-id-pages/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..1fa072633a3346c5b2dfaa0793ea5b852fbdace5 --- /dev/null +++ b/uni_modules/uni-id-pages/readme.md @@ -0,0 +1,15 @@ +# 文档已移至uni-id-pages文档[https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html) + + + +关于插件更新的说明: + +所有uni_modules,在HBuilderX里点右键都可以直接升级。或者在插件市场导入覆盖。 + +覆盖时HBuilderX会弹出代码差异比对,可以决定接受哪些更改、拒绝哪些更改。 + +当拒绝局部修改时,注意可能产生兼容性问题。 + +你需要二次开发uni-id-pages的前端页面, +- 如果改动不大,那么每次更新uni-id-pages时,在HBuilderX的对比界面对比一下就好 +- 如果改动较大,建议复制一套前端页面到自己工程的pages目录下,pages.json里只引用根目录pages下的页面,不引用uni_modules下的页面。然后每次uni-id-pages更新,你对比下比上一版uni-id-pages改了什么,看你是否需要再合并到你自己的pages里。pages.json里不引用uni_modules里的页面的话,打包时不会把这些页面打包进去,不影响发行后的包体积 \ No newline at end of file diff --git a/uni_modules/uni-id-pages/static/app-plus/apple.png b/uni_modules/uni-id-pages/static/app-plus/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..556f68666ecdb2a0414fb7f2a5a0d7fa484d6df8 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/apple.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/alipay.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/alipay.png new file mode 100644 index 0000000000000000000000000000000000000000..5256d7a60afcb882681f6a681ae0e10f977d1a4a Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/alipay.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/apple.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..99082a7945960df8152b4965d115ae3ffec96fc8 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/apple.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/douyin.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/douyin.png new file mode 100644 index 0000000000000000000000000000000000000000..f218f68d952a8e4753792b0529132b4522f5e8a1 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/douyin.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/facebook.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..1331b61ffef8e169b1a11974a5749a89427bdcce Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/facebook.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/google.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/google.png new file mode 100644 index 0000000000000000000000000000000000000000..9155046e524adfada43323cdf81d8817b881edce Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/google.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/qq.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/qq.png new file mode 100644 index 0000000000000000000000000000000000000000..f170691dc4d52d65bc604720d56a5a498170f1a4 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/qq.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/sinaweibo.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/sinaweibo.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd03812cf81febc94a44c5591ae5b27229dfe9 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/sinaweibo.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/taobao.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/taobao.png new file mode 100644 index 0000000000000000000000000000000000000000..1837f717fd423df04fc109c3b78877b2b109bc86 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/taobao.png differ diff --git a/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/univerify.png b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/univerify.png new file mode 100644 index 0000000000000000000000000000000000000000..aa0b9f5b56d36120c07016585195093aedf3ba59 Binary files /dev/null and b/uni_modules/uni-id-pages/static/app-plus/uni-fab-login/univerify.png differ diff --git a/uni_modules/uni-id-pages/static/limeClipper/photo.svg b/uni_modules/uni-id-pages/static/limeClipper/photo.svg new file mode 100644 index 0000000000000000000000000000000000000000..4fd415f2e78b3cad9b5759be7be05a77734db6fe --- /dev/null +++ b/uni_modules/uni-id-pages/static/limeClipper/photo.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/uni_modules/uni-id-pages/static/limeClipper/rotate.svg b/uni_modules/uni-id-pages/static/limeClipper/rotate.svg new file mode 100644 index 0000000000000000000000000000000000000000..4cc7dbae8b0df01802f66f0b88f177f1adc96913 --- /dev/null +++ b/uni_modules/uni-id-pages/static/limeClipper/rotate.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/uni_modules/uni-id-pages/static/login/uni-fab-login/sms.png b/uni_modules/uni-id-pages/static/login/uni-fab-login/sms.png new file mode 100644 index 0000000000000000000000000000000000000000..5533743a21e39f8a89ae2faee1d89ab9581bf29a Binary files /dev/null and b/uni_modules/uni-id-pages/static/login/uni-fab-login/sms.png differ diff --git a/uni_modules/uni-id-pages/static/login/uni-fab-login/user.png b/uni_modules/uni-id-pages/static/login/uni-fab-login/user.png new file mode 100644 index 0000000000000000000000000000000000000000..268420b7343d9b2c7c58233e024927363928dc5f Binary files /dev/null and b/uni_modules/uni-id-pages/static/login/uni-fab-login/user.png differ diff --git a/uni_modules/uni-id-pages/static/login/uni-fab-login/weixin.png b/uni_modules/uni-id-pages/static/login/uni-fab-login/weixin.png new file mode 100644 index 0000000000000000000000000000000000000000..af7175b6fed8670ce6e6d1a7a64a4812a4db8cfe Binary files /dev/null and b/uni_modules/uni-id-pages/static/login/uni-fab-login/weixin.png differ diff --git a/uni_modules/uni-id-pages/static/login/weixin.png b/uni_modules/uni-id-pages/static/login/weixin.png new file mode 100644 index 0000000000000000000000000000000000000000..df70aac5092588d7ccb87d75f024d0c9dc804607 Binary files /dev/null and b/uni_modules/uni-id-pages/static/login/weixin.png differ diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..bf6c8e921b13ac2363852fddc007e71605e091f2 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/constants.js @@ -0,0 +1,108 @@ +const db = uniCloud.database() +const dbCmd = db.command +const userCollectionName = 'uni-id-users' +const userCollection = db.collection(userCollectionName) +const verifyCollectionName = 'opendb-verify-codes' +const verifyCollection = db.collection(verifyCollectionName) +const deviceCollectionName = 'uni-id-device' +const deviceCollection = db.collection(deviceCollectionName) +const openDataCollectionName = 'opendb-open-data' +const openDataCollection = db.collection(openDataCollectionName) +const frvLogsCollectionName = 'opendb-frv-logs' +const frvLogsCollection = db.collection(frvLogsCollectionName) + +const USER_IDENTIFIER = { + _id: 'uid', + username: 'username', + mobile: 'mobile', + email: 'email', + wx_unionid: 'wechat-account', + 'wx_openid.app': 'wechat-account', + 'wx_openid.mp': 'wechat-account', + 'wx_openid.h5': 'wechat-account', + 'wx_openid.web': 'wechat-account', + qq_unionid: 'qq-account', + 'qq_openid.app': 'qq-account', + 'qq_openid.mp': 'qq-account', + ali_openid: 'alipay-account', + apple_openid: 'alipay-account', + identities: 'idp' +} + +const USER_STATUS = { + NORMAL: 0, + BANNED: 1, + AUDITING: 2, + AUDIT_FAILED: 3, + CLOSED: 4 +} + +const CAPTCHA_SCENE = { + REGISTER: 'register', + LOGIN_BY_PWD: 'login-by-pwd', + LOGIN_BY_SMS: 'login-by-sms', + RESET_PWD_BY_SMS: 'reset-pwd-by-sms', + RESET_PWD_BY_EMAIL: 'reset-pwd-by-email', + SEND_SMS_CODE: 'send-sms-code', + SEND_EMAIL_CODE: 'send-email-code', + BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms', + SET_PWD_BY_SMS: 'set-pwd-by-sms' +} + +const LOG_TYPE = { + LOGOUT: 'logout', + LOGIN: 'login', + REGISTER: 'register', + RESET_PWD_BY_SMS: 'reset-pwd', + RESET_PWD_BY_EMAIL: 'reset-pwd', + BIND_MOBILE: 'bind-mobile', + BIND_WEIXIN: 'bind-weixin', + BIND_QQ: 'bind-qq', + BIND_APPLE: 'bind-apple', + BIND_ALIPAY: 'bind-alipay', + UNBIND_WEIXIN: 'unbind-weixin', + UNBIND_QQ: 'unbind-qq', + UNBIND_ALIPAY: 'unbind-alipay', + UNBIND_APPLE: 'unbind-apple' +} + +const SMS_SCENE = { + LOGIN_BY_SMS: 'login-by-sms', + RESET_PWD_BY_SMS: 'reset-pwd-by-sms', + BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms', + SET_PWD_BY_SMS: 'set-pwd-by-sms' +} + +const EMAIL_SCENE = { + REGISTER: 'register', + LOGIN_BY_EMAIL: 'login-by-email', + RESET_PWD_BY_EMAIL: 'reset-pwd-by-email', + BIND_EMAIL: 'bind-email' +} + +const REAL_NAME_STATUS = { + NOT_CERTIFIED: 0, + WAITING_CERTIFIED: 1, + CERTIFIED: 2, + CERTIFY_FAILED: 3 +} + +const EXTERNAL_DIRECT_CONNECT_PROVIDER = 'externalDirectConnect' + +module.exports = { + db, + dbCmd, + userCollection, + verifyCollection, + deviceCollection, + openDataCollection, + frvLogsCollection, + USER_IDENTIFIER, + USER_STATUS, + CAPTCHA_SCENE, + LOG_TYPE, + SMS_SCENE, + EMAIL_SCENE, + REAL_NAME_STATUS, + EXTERNAL_DIRECT_CONNECT_PROVIDER +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js new file mode 100644 index 0000000000000000000000000000000000000000..c22a0f10516384a55351ebd2bff0747d93649e6c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/error.js @@ -0,0 +1,70 @@ +const ERROR = { + ACCOUNT_EXISTS: 'uni-id-account-exists', + ACCOUNT_NOT_EXISTS: 'uni-id-account-not-exists', + ACCOUNT_NOT_EXISTS_IN_CURRENT_APP: 'uni-id-account-not-exists-in-current-app', + ACCOUNT_CONFLICT: 'uni-id-account-conflict', + ACCOUNT_BANNED: 'uni-id-account-banned', + ACCOUNT_AUDITING: 'uni-id-account-auditing', + ACCOUNT_AUDIT_FAILED: 'uni-id-account-audit-failed', + ACCOUNT_CLOSED: 'uni-id-account-closed', + CAPTCHA_REQUIRED: 'uni-id-captcha-required', + PASSWORD_ERROR: 'uni-id-password-error', + PASSWORD_ERROR_EXCEED_LIMIT: 'uni-id-password-error-exceed-limit', + INVALID_USERNAME: 'uni-id-invalid-username', + INVALID_PASSWORD: 'uni-id-invalid-password', + INVALID_PASSWORD_SUPER: 'uni-id-invalid-password-super', + INVALID_PASSWORD_STRONG: 'uni-id-invalid-password-strong', + INVALID_PASSWORD_MEDIUM: 'uni-id-invalid-password-medium', + INVALID_PASSWORD_WEAK: 'uni-id-invalid-password-weak', + INVALID_MOBILE: 'uni-id-invalid-mobile', + INVALID_EMAIL: 'uni-id-invalid-email', + INVALID_NICKNAME: 'uni-id-invalid-nickname', + INVALID_PARAM: 'uni-id-invalid-param', + PARAM_REQUIRED: 'uni-id-param-required', + GET_THIRD_PARTY_ACCOUNT_FAILED: 'uni-id-get-third-party-account-failed', + GET_THIRD_PARTY_USER_INFO_FAILED: 'uni-id-get-third-party-user-info-failed', + MOBILE_VERIFY_CODE_ERROR: 'uni-id-mobile-verify-code-error', + EMAIL_VERIFY_CODE_ERROR: 'uni-id-email-verify-code-error', + ADMIN_EXISTS: 'uni-id-admin-exists', + PERMISSION_ERROR: 'uni-id-permission-error', + SYSTEM_ERROR: 'uni-id-system-error', + SET_INVITE_CODE_FAILED: 'uni-id-set-invite-code-failed', + INVALID_INVITE_CODE: 'uni-id-invalid-invite-code', + CHANGE_INVITER_FORBIDDEN: 'uni-id-change-inviter-forbidden', + BIND_CONFLICT: 'uni-id-bind-conflict', + UNBIND_FAIL: 'uni-id-unbind-failed', + UNBIND_NOT_SUPPORTED: 'uni-id-unbind-not-supported', + UNBIND_UNIQUE_LOGIN: 'uni-id-unbind-unique-login', + UNBIND_PASSWORD_NOT_EXISTS: 'uni-id-unbind-password-not-exists', + UNBIND_MOBILE_NOT_EXISTS: 'uni-id-unbind-mobile-not-exists', + UNSUPPORTED_REQUEST: 'uni-id-unsupported-request', + ILLEGAL_REQUEST: 'uni-id-illegal-request', + CONFIG_FIELD_REQUIRED: 'uni-id-config-field-required', + CONFIG_FIELD_INVALID: 'uni-id-config-field-invalid', + FRV_FAIL: 'uni-id-frv-fail', + FRV_PROCESSING: 'uni-id-frv-processing', + REAL_NAME_VERIFIED: 'uni-id-realname-verified', + ID_CARD_EXISTS: 'uni-id-idcard-exists', + INVALID_ID_CARD: 'uni-id-invalid-idcard', + INVALID_REAL_NAME: 'uni-id-invalid-realname', + UNKNOWN_ERROR: 'uni-id-unknown-error', + REAL_NAME_VERIFY_UPPER_LIMIT: 'uni-id-realname-verify-upper-limit' +} + +function isUniIdError (errCode) { + return Object.values(ERROR).includes(errCode) +} + +class UniCloudError extends Error { + constructor (options) { + super(options.message) + this.errMsg = options.message || '' + this.errCode = options.code + } +} + +module.exports = { + ERROR, + isUniIdError, + UniCloudError +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js new file mode 100644 index 0000000000000000000000000000000000000000..91e2db3b4bcf25cb21ee0ab40e0432baff75c592 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/sensitive-aes-cipher.js @@ -0,0 +1,64 @@ +const crypto = require('crypto') +const { ERROR } = require('./error') + +function checkSecret (secret) { + if (!secret) { + throw { + errCode: ERROR.CONFIG_FIELD_REQUIRED, + errMsgValue: { + field: 'sensitiveInfoEncryptSecret' + } + } + } + + if (secret.length !== 32) { + throw { + errCode: ERROR.CONFIG_FIELD_INVALID, + errMsgValue: { + field: 'sensitiveInfoEncryptSecret' + } + } + } +} +function encryptData (text = '') { + if (!text) return text + + const encryptSecret = this.config.sensitiveInfoEncryptSecret + + checkSecret(encryptSecret) + + const iv = encryptSecret.slice(-16) + + const cipher = crypto.createCipheriv('aes-256-cbc', encryptSecret, iv) + + const encrypted = Buffer.concat([ + cipher.update(Buffer.from(text, 'utf-8')), + cipher.final() + ]) + + return encrypted.toString('base64') +} + +function decryptData (text = '') { + if (!text) return text + + const encryptSecret = this.config.sensitiveInfoEncryptSecret + + checkSecret(encryptSecret) + + const iv = encryptSecret.slice(-16) + + const cipher = crypto.createDecipheriv('aes-256-cbc', encryptSecret, iv) + + const decrypted = Buffer.concat([ + cipher.update(Buffer.from(text, 'base64')), + cipher.final() + ]) + + return decrypted.toString('utf-8') +} + +module.exports = { + encryptData, + decryptData +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/universal.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/universal.js new file mode 100644 index 0000000000000000000000000000000000000000..084d7593650ac09cc1cac76d81eb1a9d7bf16ff2 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/universal.js @@ -0,0 +1,47 @@ +const { ERROR } = require('./error') + +function getHttpClientInfo () { + const requestId = this.getUniCloudRequestId() + const { clientIP, userAgent, source, secretType = 'none' } = this.getClientInfo() + const { clientInfo = {} } = JSON.parse(this.getHttpInfo().body) + + return { + ...clientInfo, + clientIP, + userAgent, + source, + secretType, + requestId + } +} + +function getHttpUniIdToken () { + const { uniIdToken = '' } = JSON.parse(this.getHttpInfo().body) + + return uniIdToken +} + +function verifyHttpMethod () { + const { headers, httpMethod } = this.getHttpInfo() + + if (!/^application\/json/.test(headers['content-type']) || httpMethod.toUpperCase() !== 'POST') { + throw { + errCode: ERROR.UNSUPPORTED_REQUEST, + errMsg: 'unsupported request' + } + } +} + +function universal () { + if (this.getClientInfo().source === 'http') { + verifyHttpMethod.call(this) + this.getParams()[0] = JSON.parse(this.getHttpInfo().body).params + this.getUniversalClientInfo = getHttpClientInfo.bind(this) + this.getUniversalUniIdToken = getHttpUniIdToken.bind(this) + } else { + this.getUniversalClientInfo = this.getClientInfo + this.getUniversalUniIdToken = this.getUniIdToken + } +} + +module.exports = universal diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..13876dd92f4983dddabb1117a9124066cb5d95c3 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/utils.js @@ -0,0 +1,270 @@ +function batchFindObjctValue (obj = {}, keys = []) { + const values = {} + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const keyPath = key.split('.') + let currentKey = keyPath.shift() + let result = obj + while (currentKey) { + if (!result) { + break + } + result = result[currentKey] + currentKey = keyPath.shift() + } + values[key] = result + } + return values +} + +function getType (val) { + return Object.prototype.toString.call(val).slice(8, -1).toLowerCase() +} + +function hasOwn (obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key) +} + +function isValidString (val) { + return val && getType(val) === 'string' +} + +function isPlainObject (obj) { + return getType(obj) === 'object' +} + +function isFn (fn) { + // 务必注意AsyncFunction + return typeof fn === 'function' +} + +// 获取文件后缀,只添加几种图片类型供客服消息接口使用 +const mime2ext = { + 'image/png': 'png', + 'image/jpeg': 'jpg', + 'image/gif': 'gif', + 'image/svg+xml': 'svg', + 'image/bmp': 'bmp', + 'image/webp': 'webp' +} + +function getExtension (contentType) { + return mime2ext[contentType] +} + +const isSnakeCase = /_(\w)/g +const isCamelCase = /[A-Z]/g + +function snake2camel (value) { + return value.replace(isSnakeCase, (_, c) => (c ? c.toUpperCase() : '')) +} + +function camel2snake (value) { + return value.replace(isCamelCase, str => '_' + str.toLowerCase()) +} + +function parseObjectKeys (obj, type) { + let parserReg, parser + switch (type) { + case 'snake2camel': + parser = snake2camel + parserReg = isSnakeCase + break + case 'camel2snake': + parser = camel2snake + parserReg = isCamelCase + break + } + for (const key in obj) { + if (hasOwn(obj, key)) { + if (parserReg.test(key)) { + const keyCopy = parser(key) + obj[keyCopy] = obj[key] + delete obj[key] + if (isPlainObject(obj[keyCopy])) { + obj[keyCopy] = parseObjectKeys(obj[keyCopy], type) + } else if (Array.isArray(obj[keyCopy])) { + obj[keyCopy] = obj[keyCopy].map((item) => { + return parseObjectKeys(item, type) + }) + } + } + } + } + return obj +} + +function snake2camelJson (obj) { + return parseObjectKeys(obj, 'snake2camel') +} + +function camel2snakeJson (obj) { + return parseObjectKeys(obj, 'camel2snake') +} + +function getOffsetDate (offset) { + return new Date( + Date.now() + (new Date().getTimezoneOffset() + (offset || 0) * 60) * 60000 + ) +} + +function getDateStr (date, separator = '-') { + date = date || new Date() + const dateArr = [] + dateArr.push(date.getFullYear()) + dateArr.push(('00' + (date.getMonth() + 1)).substr(-2)) + dateArr.push(('00' + date.getDate()).substr(-2)) + return dateArr.join(separator) +} + +function getTimeStr (date, separator = ':') { + date = date || new Date() + const timeArr = [] + timeArr.push(('00' + date.getHours()).substr(-2)) + timeArr.push(('00' + date.getMinutes()).substr(-2)) + timeArr.push(('00' + date.getSeconds()).substr(-2)) + return timeArr.join(separator) +} + +function getFullTimeStr (date) { + date = date || new Date() + return getDateStr(date) + ' ' + getTimeStr(date) +} + +function getDistinctArray (arr) { + return Array.from(new Set(arr)) +} + +/** + * 拼接url + * @param {string} base 基础路径 + * @param {string} path 在基础路径上拼接的路径 + * @returns + */ +function resolveUrl (base, path) { + if (/^https?:/.test(path)) { + return path + } + return base + path +} + +function getVerifyCode (len = 6) { + let code = '' + for (let i = 0; i < len; i++) { + code += Math.floor(Math.random() * 10) + } + return code +} + +function coverMobile (mobile) { + if (typeof mobile !== 'string') { + return mobile + } + return mobile.slice(0, 3) + '****' + mobile.slice(7) +} + +function getNonceStr (length = 16) { + let str = '' + while (str.length < length) { + str += Math.random().toString(32).substring(2) + } + return str.substring(0, length) +} + +try { + require('lodash.merge') +} catch (error) { + console.error('uni-id-co缺少依赖,请在uniCloud/cloudfunctions/uni-id-co目录执行 npm install 安装依赖') + throw error +} + +function isMatchUserApp (userAppList, matchAppList) { + if (userAppList === undefined || userAppList === null) { + return true + } + if (getType(userAppList) !== 'array') { + return false + } + if (userAppList.includes('*')) { + return true + } + if (getType(matchAppList) === 'string') { + matchAppList = [matchAppList] + } + return userAppList.some(item => matchAppList.includes(item)) +} + +function checkIdCard (idCardNumber) { + if (!idCardNumber || typeof idCardNumber !== 'string' || idCardNumber.length !== 18) return false + + const coefficient = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + const checkCode = [1, 0, 'x', 9, 8, 7, 6, 5, 4, 3, 2] + const code = idCardNumber.substring(17) + + let sum = 0 + for (let i = 0; i < 17; i++) { + sum += Number(idCardNumber.charAt(i)) * coefficient[i] + } + + return checkCode[sum % 11].toString() === code.toLowerCase() +} + +function catchAwait (fn, finallyFn) { + if (!fn) return [new Error('no function')] + + if (Promise.prototype.finally === undefined) { + // eslint-disable-next-line no-extend-native + Promise.prototype.finally = function (finallyFn) { + return this.then( + res => Promise.resolve(finallyFn()).then(() => res), + error => Promise.resolve(finallyFn()).then(() => { throw error }) + ) + } + } + + return fn + .then((data) => [undefined, data]) + .catch((error) => [error]) + .finally(() => typeof finallyFn === 'function' && finallyFn()) +} + +function dataDesensitization (value = '', options = {}) { + const { onlyLast = false } = options + const [firstIndex, middleIndex, lastIndex] = onlyLast ? [0, 0, -1] : [0, 1, -1] + + if (!value) return value + const first = value.slice(firstIndex, middleIndex) + const middle = value.slice(middleIndex, lastIndex) + const last = value.slice(lastIndex) + const star = Array.from(new Array(middle.length), (v) => '*').join('') + + return first + star + last +} + +function getCurrentDateTimestamp (date = Date.now(), targetTimezone = 8) { + const oneHour = 60 * 60 * 1000 + return parseInt((date + targetTimezone * oneHour) / (24 * oneHour)) * (24 * oneHour) - targetTimezone * oneHour +} + +module.exports = { + getType, + isValidString, + batchFindObjctValue, + isPlainObject, + isFn, + getDistinctArray, + getFullTimeStr, + resolveUrl, + getOffsetDate, + camel2snakeJson, + snake2camelJson, + getExtension, + getVerifyCode, + coverMobile, + getNonceStr, + isMatchUserApp, + checkIdCard, + catchAwait, + dataDesensitization, + getCurrentDateTimestamp +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js new file mode 100644 index 0000000000000000000000000000000000000000..55a05354656fdd65669219c7fe3ee7912731f1be --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js @@ -0,0 +1,443 @@ +const { + isValidString, + getType +} = require('./utils.js') +const { + ERROR +} = require('./error') + +const baseValidator = Object.create(null) + +baseValidator.username = function (username) { + const errCode = ERROR.INVALID_USERNAME + if (!isValidString(username)) { + return { + errCode + } + } + if (/^\d+$/.test(username)) { + // 用户名不能为纯数字 + return { + errCode + } + }; + if (!/^[a-zA-Z0-9_-]+$/.test(username)) { + // 用户名仅能使用数字、字母、“_”及“-” + return { + errCode + } + } +} + +baseValidator.password = function (password) { + const errCode = ERROR.INVALID_PASSWORD + if (!isValidString(password)) { + return { + errCode + } + } + if (password.length < 6) { + // 密码长度不能小于6 + return { + errCode + } + } +} + +baseValidator.mobile = function (mobile) { + const errCode = ERROR.INVALID_MOBILE + if (getType(mobile) !== 'string') { + return { + errCode + } + } + if (mobile && !/^1\d{10}$/.test(mobile)) { + return { + errCode + } + } +} + +baseValidator.email = function (email) { + const errCode = ERROR.INVALID_EMAIL + if (getType(email) !== 'string') { + return { + errCode + } + } + if (email && !/@/.test(email)) { + return { + errCode + } + } +} + +baseValidator.nickname = function (nickname) { + const errCode = ERROR.INVALID_NICKNAME + if (nickname.indexOf('@') !== -1) { + // 昵称不允许含@ + return { + errCode + } + }; + if (/^\d+$/.test(nickname)) { + // 昵称不能为纯数字 + return { + errCode + } + }; + if (nickname.length > 100) { + // 昵称不可超过100字符 + return { + errCode + } + } +} + +const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来 + +baseType.forEach((type) => { + baseValidator[type] = function (val) { + if (getType(val) === type) { + return + } + return { + errCode: ERROR.INVALID_PARAM + } + } +}) + +function tokenize(name) { + let i = 0 + const result = [] + let token = '' + while (i < name.length) { + const char = name[i] + switch (char) { + case '|': + case '<': + case '>': + token && result.push(token) + result.push(char) + token = '' + break + default: + token += char + break + } + i++ + if (i === name.length && token) { + result.push(token) + } + } + return result +} + +/** + * 处理validator名 + * @param {string} name + */ +function parseValidatorName(name) { + const tokenList = tokenize(name) + let i = 0 + let currentToken = tokenList[i] + const result = { + type: 'root', + children: [], + parent: null + } + let lastRealm = result + while (currentToken) { + switch (currentToken) { + case 'array': { + const currentRealm = { + type: 'array', + children: [], + parent: lastRealm + } + lastRealm.children.push(currentRealm) + lastRealm = currentRealm + break + } + case '<': + if (lastRealm.type !== 'array') { + throw new Error('Invalid validator token "<"') + } + break + case '>': + if (lastRealm.type !== 'array') { + throw new Error('Invalid validator token ">"') + } + lastRealm = lastRealm.parent + break + case '|': + if (lastRealm.type !== 'array' && lastRealm.type !== 'root') { + throw new Error('Invalid validator token "|"') + } + break + default: + lastRealm.children.push({ + type: currentToken + }) + break + } + i++ + currentToken = tokenList[i] + } + return result +} + +function getRuleCategory(rule) { + switch (rule.type) { + case 'array': + return 'array' + case 'root': + return 'root' + default: + return 'base' + } +} + + +// 特殊符号 https://www.ibm.com/support/pages/password-strength-rules ~!@#$%^&*_-+=`|\(){}[]:;"'<>,.?/ +// const specialChar = '~!@#$%^&*_-+=`|\(){}[]:;"\'<>,.?/' +// const specialCharRegExp = /^[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]$/ +// for (let i = 0, arr = specialChar.split(''); i < arr.length; i++) { +// const char = arr[i] +// if (!specialCharRegExp.test(char)) { +// throw new Error('check special character error: ' + char) +// } +// } + +// 密码强度表达式 +const passwordRules = { + // 密码必须包含大小写字母、数字和特殊符号 + super: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/, + // 密码必须包含字母、数字和特殊符号 + strong: /^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/, + // 密码必须为字母、数字和特殊符号任意两种的组合 + medium: /^(?![0-9]+$)(?![a-zA-Z]+$)(?![~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]+$)[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{8,16}$/, + // 密码必须包含字母和数字 + weak: /^(?=.*[0-9])(?=.*[a-zA-Z])[0-9a-zA-Z~!@#$%^&*_\-+=`|\\(){}[\]:;"'<>,.?/]{6,16}$/, + +} + +function createPasswordVerifier({ + passwordStrength = '' +} = {}) { + return function (password) { + const passwordRegExp = passwordRules[passwordStrength] + if (!passwordRegExp) { + throw new Error('Invalid password strength config: ' + passwordStrength) + } + const errCode = ERROR.INVALID_PASSWORD + if (!isValidString(password)) { + return { + errCode + } + } + if (!passwordRegExp.test(password)) { + return { + errCode: errCode + '-' + passwordStrength + } + } + } +} + +function isEmpty(value) { + return value === undefined || + value === null || + (typeof value === 'string' && value.trim() === '') +} + +class Validator { + constructor({ + passwordStrength = '' + } = {}) { + this.baseValidator = baseValidator + this.customValidator = Object.create(null) + if (passwordStrength) { + this.mixin( + 'password', + createPasswordVerifier({ + passwordStrength + }) + ) + } + } + + mixin(type, handler) { + this.customValidator[type] = handler + } + + getRealBaseValidator(type) { + return this.customValidator[type] || this.baseValidator[type] + } + + + _isMatchUnionType(val, rule) { + if (!rule.children || rule.children.length === 0) { + return true + } + const children = rule.children + for (let i = 0; i < children.length; i++) { + const child = children[i] + const category = getRuleCategory(child) + let pass = false + switch (category) { + case 'base': + pass = this._isMatchBaseType(val, child) + break + case 'array': + pass = this._isMatchArrayType(val, child) + break + default: + break + } + if (pass) { + return true + } + } + return false + } + + _isMatchBaseType(val, rule) { + const method = this.getRealBaseValidator(rule.type) + if (typeof method !== 'function') { + throw new Error(`invalid schema type: ${rule.type}`) + } + const validateRes = method(val) + if (validateRes && validateRes.errCode) { + return false + } + return true + } + + _isMatchArrayType(arr, rule) { + if (getType(arr) !== 'array') { + return false + } + if (rule.children && rule.children.length && arr.some(item => !this._isMatchUnionType(item, rule))) { + return false + } + return true + } + + get validator() { + const _this = this + return new Proxy({}, { + get: (_, prop) => { + if (typeof prop !== 'string') { + return + } + const realBaseValidator = this.getRealBaseValidator(prop) + if (realBaseValidator) { + return realBaseValidator + } + const rule = parseValidatorName(prop) + return function (val) { + if (!_this._isMatchUnionType(val, rule)) { + return { + errCode: ERROR.INVALID_PARAM + } + } + } + } + }) + } + + validate(value = {}, schema = {}) { + for (const schemaKey in schema) { + let schemaValue = schema[schemaKey] + if (getType(schemaValue) === 'string') { + schemaValue = { + required: true, + type: schemaValue + } + } + const { + required, + type + } = schemaValue + // value内未传入了schemaKey或对应值为undefined + if (isEmpty(value[schemaKey])) { + if (required) { + return { + errCode: ERROR.PARAM_REQUIRED, + errMsgValue: { + param: schemaKey + }, + schemaKey + } + } else { + //delete value[schemaKey] + continue + } + } + const validateMethod = this.validator[type] + if (!validateMethod) { + throw new Error(`invalid schema type: ${type}`) + } + const validateRes = validateMethod(value[schemaKey]) + if (validateRes) { + validateRes.schemaKey = schemaKey + return validateRes + } + } + } +} + +function checkClientInfo(clientInfo) { + const stringNotRequired = { + required: false, + type: 'string' + } + const numberNotRequired = { + required: false, + type: 'number' + } + const numberOrStringNotRequired = { + required: false, + type: 'number|string' + } + const schema = { + uniPlatform: 'string', + appId: 'string', + deviceId: stringNotRequired, + osName: stringNotRequired, + locale: stringNotRequired, + clientIP: stringNotRequired, + appName: stringNotRequired, + appVersion: stringNotRequired, + appVersionCode: numberOrStringNotRequired, + channel: numberOrStringNotRequired, + userAgent: stringNotRequired, + uniIdToken: stringNotRequired, + deviceBrand: stringNotRequired, + deviceModel: stringNotRequired, + osVersion: stringNotRequired, + osLanguage: stringNotRequired, + osTheme: stringNotRequired, + romName: stringNotRequired, + romVersion: stringNotRequired, + devicePixelRatio: numberNotRequired, + windowWidth: numberNotRequired, + windowHeight: numberNotRequired, + screenWidth: numberNotRequired, + screenHeight: numberNotRequired + } + const validateRes = new Validator().validate(clientInfo, schema) + if (validateRes) { + if (validateRes.errCode === ERROR.PARAM_REQUIRED) { + console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId') + throw new Error(`"clientInfo.${validateRes.schemaKey}" is required.`) + } else { + throw new Error(`Invalid client info: clienInfo.${validateRes.schemaKey}`) + } + } +} + +module.exports = { + Validator, + checkClientInfo +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js new file mode 100644 index 0000000000000000000000000000000000000000..59f91f92bb1fb35202e55de8661ae1270bdcc469 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/config/permission.js @@ -0,0 +1,90 @@ +// 各接口权限配置,未配置接口表示允许任何用户访问(包括未登录用户) +module.exports = { + // 管理接口 + addUser: { + // auth: true // 已登录用户方可操作,配置角色或权限时此项可不写 + role: ['admin'] // 允许进行此操作的角色,包含任一角色均可操作。 + // permission: [] // 允许进行此操作的权限,包含任一权限均可操作。 + // 权限角色均配置时,用户拥有任一权限或任一角色均可操作 + }, + updateUser: { + role: ['admin'] + }, + authorizeAppLogin: { + role: ['admin'] + }, + removeAuthorizedApp: { + role: ['admin'] + }, + setAuthorizedApp: { + role: ['admin'] + }, + + // 用户接口 + closeAccount: { + auth: true + }, + updatePwd: { + auth: true + }, + logout: { + auth: true + }, + bindMobileBySms: { + auth: true + }, + bindMobileByUniverify: { + auth: true + }, + bindMobileByMpWeixin: { + auth: true + }, + bindAlipay: { + auth: true + }, + bindApple: { + auth: true + }, + bindQQ: { + auth: true + }, + bindWeixin: { + auth: true + }, + acceptInvite: { + auth: true + }, + getInvitedUser: { + auth: true + }, + setPushCid: { + auth: true + }, + getAccountInfo: { + auth: true + }, + unbindWeixin: { + auth: true + }, + unbindAlipay: { + auth: true + }, + unbindQQ: { + auth: true + }, + unbindApple: { + auth: true + }, + setPwd: { + auth: true + }, + getFrvCertifyId: { + auth: true + }, + getFrvAuthResult: { + auth: true + }, + getRealNameInfo: { + auth: true + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js new file mode 100644 index 0000000000000000000000000000000000000000..7f6c25825193fb6ca4f45646ba604fe092cd7940 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js @@ -0,0 +1,694 @@ +const uniIdCommon = require('uni-id-common') +const uniCaptcha = require('uni-captcha') +const { + getType, + checkIdCard +} = require('./common/utils') +const { + checkClientInfo, + Validator +} = require('./common/validator') +const ConfigUtils = require('./lib/utils/config') +const { + isUniIdError, + ERROR +} = require('./common/error') +const middleware = require('./middleware/index') +const universal = require('./common/universal') + +const { + registerAdmin, + registerUser, + registerUserByEmail +} = require('./module/register/index') +const { + addUser, + updateUser +} = require('./module/admin/index') +const { + login, + loginBySms, + loginByUniverify, + loginByWeixin, + loginByAlipay, + loginByQQ, + loginByApple, + loginByWeixinMobile +} = require('./module/login/index') +const { + logout +} = require('./module/logout/index') +const { + bindMobileBySms, + bindMobileByUniverify, + bindMobileByMpWeixin, + bindAlipay, + bindApple, + bindQQ, + bindWeixin, + unbindWeixin, + unbindAlipay, + unbindQQ, + unbindApple +} = require('./module/relate/index') +const { + setPwd, + updatePwd, + resetPwdBySms, + resetPwdByEmail, + closeAccount, + getAccountInfo, + getRealNameInfo +} = require('./module/account/index') +const { + createCaptcha, + refreshCaptcha, + sendSmsCode, + sendEmailCode +} = require('./module/verify/index') +const { + refreshToken, + setPushCid, + secureNetworkHandshakeByWeixin +} = require('./module/utils/index') +const { + getInvitedUser, + acceptInvite +} = require('./module/fission') +const { + authorizeAppLogin, + removeAuthorizedApp, + setAuthorizedApp +} = require('./module/multi-end') +const { + getSupportedLoginType +} = require('./module/dev/index') +const { + externalRegister, + externalLogin, + updateUserInfoByExternal +} = require('./module/external') +const { + getFrvCertifyId, + getFrvAuthResult +} = require('./module/facial-recognition-verify') + +module.exports = { + async _before () { + // 支持 callFunction 与 URL化 + universal.call(this) + + const clientInfo = this.getUniversalClientInfo() + /** + * 检查clientInfo,无appId和uniPlatform时本云对象无法正常运行 + * 此外需要保证用到的clientInfo字段均经过类型检查 + * clientInfo由客户端上传并非完全可信,clientInfo内除clientIP、userAgent、source外均为客户端上传参数 + * 否则可能会出现一些意料外的情况 + */ + checkClientInfo(clientInfo) + let clientPlatform = clientInfo.uniPlatform + // 统一platform名称 + switch (clientPlatform) { + case 'app': + case 'app-plus': + clientPlatform = 'app' + break + case 'web': + case 'h5': + clientPlatform = 'web' + break + default: + break + } + + this.clientPlatform = clientPlatform + + // 挂载uni-id实例到this上,方便后续调用 + this.uniIdCommon = uniIdCommon.createInstance({ + clientInfo + }) + + // 包含uni-id配置合并等功能的工具集 + this.configUtils = new ConfigUtils({ + context: this + }) + this.config = this.configUtils.getPlatformConfig() + this.hooks = this.configUtils.getHooks() + + this.validator = new Validator({ + passwordStrength: this.config.passwordStrength + }) + + // 扩展 validator 增加 验证身份证号码合法性 + this.validator.mixin('idCard', function (idCard) { + if (!checkIdCard(idCard)) { + return { + errCode: ERROR.INVALID_ID_CARD + } + } + }) + this.validator.mixin('realName', function (realName) { + if ( + typeof realName !== 'string' || + realName.length < 2 || + !/^[\u4e00-\u9fa5]{1,10}(·?[\u4e00-\u9fa5]{1,10}){0,5}$/.test(realName) + ) { + return { + errCode: ERROR.INVALID_REAL_NAME + } + } + }) + /** + * 示例:覆盖密码验证规则 + */ + // this.validator.mixin('password', function (password) { + // if (typeof password !== 'string' || password.length < 10) { + // // 调整为密码长度不能小于10 + // return { + // errCode: ERROR.INVALID_PASSWORD + // } + // } + // }) + /** + * 示例:新增验证规则 + */ + // this.validator.mixin('timestamp', function (timestamp) { + // if (typeof timestamp !== 'number' || timestamp > Date.now()) { + // return { + // errCode: ERROR.INVALID_PARAM + // } + // } + // }) + // // 新增规则同样可以在数组验证规则中使用 + // this.validator.validate({ + // timestamp: 123456789 + // }, { + // timestamp: 'timestamp' + // }) + // this.validator.validate({ + // timestampList: [123456789, 123123123123] + // }, { + // timestampList: 'array' + // }) + // // 甚至更复杂的写法 + // this.validator.validate({ + // timestamp: [123456789123123123, 123123123123] + // }, { + // timestamp: 'timestamp|array' + // }) + + // 挂载uni-captcha到this上,方便后续调用 + this.uniCaptcha = uniCaptcha + Object.defineProperty(this, 'uniOpenBridge', { + get () { + return require('uni-open-bridge-common') + } + }) + + // 挂载中间件 + this.middleware = {} + for (const mwName in middleware) { + this.middleware[mwName] = middleware[mwName].bind(this) + } + + // 国际化 + const messages = require('./lang/index') + const fallbackLocale = 'zh-Hans' + const i18n = uniCloud.initI18n({ + locale: clientInfo.locale, + fallbackLocale, + messages: JSON.parse(JSON.stringify(messages)) + }) + if (!messages[i18n.locale]) { + i18n.setLocale(fallbackLocale) + } + this.t = i18n.t.bind(i18n) + + this.response = {} + + // 请求鉴权验证 + await this.middleware.verifyRequestSign() + + // 通用权限校验模块 + await this.middleware.accessControl() + }, + _after (error, result) { + if (error) { + // 处理中间件内抛出的标准响应对象 + if (error.errCode && getType(error) === 'object') { + const errCode = error.errCode + if (!isUniIdError(errCode)) { + return error + } + return { + errCode, + errMsg: error.errMsg || this.t(errCode, error.errMsgValue) + } + } + throw error + } + return Object.assign(this.response, result) + }, + /** + * 注册管理员 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-admin + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @returns + */ + registerAdmin, + /** + * 新增用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {Array} params.authorizedApp 允许登录的AppID列表 + * @param {Array} params.role 用户角色列表 + * @param {String} params.mobile 手机号 + * @param {String} params.email 邮箱 + * @param {Array} params.tags 用户标签 + * @param {Number} params.status 用户状态 + * @returns + */ + addUser, + /** + * 修改用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user + * @param {Object} params + * @param {String} params.id 要更新的用户id + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {Array} params.authorizedApp 允许登录的AppID列表 + * @param {Array} params.role 用户角色列表 + * @param {String} params.mobile 手机号 + * @param {String} params.email 邮箱 + * @param {Array} params.tags 用户标签 + * @param {Number} params.status 用户状态 + * @returns + */ + updateUser, + /** + * 授权用户登录应用 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#authorize-app-login + * @param {Object} params + * @param {String} params.uid 用户id + * @param {String} params.appId 授权的应用的AppId + * @returns + */ + authorizeAppLogin, + /** + * 移除用户登录授权 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#remove-authorized-app + * @param {Object} params + * @param {String} params.uid 用户id + * @param {String} params.appId 取消授权的应用的AppId + * @returns + */ + removeAuthorizedApp, + /** + * 设置用户允许登录的应用列表 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-authorized-app + * @param {Object} params + * @param {String} params.uid 用户id + * @param {Array} params.appIdList 允许登录的应用AppId列表 + * @returns + */ + setAuthorizedApp, + /** + * 注册普通用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.captcha 图形验证码 + * @param {String} params.nickname 昵称 + * @param {String} params.inviteCode 邀请码 + * @returns + */ + registerUser, + /** + * 通过邮箱+验证码注册用户 + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {String} params.code 邮箱验证码 + * @param {String} params.inviteCode 邀请码 + * @returns + */ + registerUserByEmail, + /** + * 用户名密码登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.mobile 手机号 + * @param {String} params.email 邮箱 + * @param {String} params.password 密码 + * @param {String} params.captcha 图形验证码 + * @returns + */ + login, + /** + * 短信验证码登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-sms + * @param {Object} params + * @param {String} params.mobile 手机号 + * @param {String} params.code 短信验证码 + * @param {String} params.captcha 图形验证码 + * @param {String} params.inviteCode 邀请码 + * @returns + */ + loginBySms, + /** + * App端一键登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-univerify + * @param {Object} params + * @param {String} params.access_token APP端一键登录返回的access_token + * @param {String} params.openid APP端一键登录返回的openid + * @param {String} params.inviteCode 邀请码 + * @returns + */ + loginByUniverify, + /** + * 微信登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin + * @param {Object} params + * @param {String} params.code 微信登录返回的code + * @param {String} params.inviteCode 邀请码 + * @returns + */ + loginByWeixin, + /** + * 支付宝登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-alipay + * @param {Object} params + * @param {String} params.code 支付宝小程序客户端登录返回的code + * @param {String} params.inviteCode 邀请码 + * @returns + */ + loginByAlipay, + /** + * QQ登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-qq + * @param {Object} params + * @param {String} params.code QQ小程序登录返回的code参数 + * @param {String} params.accessToken App端QQ登录返回的accessToken参数 + * @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒 + * @param {String} params.inviteCode 邀请码 + * @returns + */ + loginByQQ, + /** + * 苹果登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-apple + * @param {Object} params + * @param {String} params.identityToken 苹果登录返回的identityToken + * @param {String} params.nickname 用户昵称 + * @param {String} params.inviteCode 邀请码 + * @returns + */ + loginByApple, + loginByWeixinMobile, + /** + * 用户退出登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#logout + * @returns + */ + logout, + /** + * 通过短信验证码绑定手机号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-sms + * @param {Object} params + * @param {String} params.mobile 手机号 + * @param {String} params.code 短信验证码 + * @param {String} params.captcha 图形验证码 + * @returns + */ + bindMobileBySms, + /** + * 通过一键登录绑定手机号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-univerify + * @param {Object} params + * @param {String} params.openid APP端一键登录返回的openid + * @param {String} params.access_token APP端一键登录返回的access_token + * @returns + */ + bindMobileByUniverify, + /** + * 通过微信绑定手机号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-mp-weixin + * @param {Object} params + * @param {String} params.encryptedData 微信获取手机号返回的加密信息 + * @param {String} params.iv 微信获取手机号返回的初始向量 + * @returns + */ + bindMobileByMpWeixin, + /** + * 绑定微信 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-weixin + * @param {Object} params + * @param {String} params.code 微信登录返回的code + * @returns + */ + bindWeixin, + /** + * 绑定QQ + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-qq + * @param {Object} params + * @param {String} params.code 小程序端QQ登录返回的code + * @param {String} params.accessToken APP端QQ登录返回的accessToken + * @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒 + * @returns + */ + bindQQ, + /** + * 绑定支付宝账号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-alipay + * @param {Object} params + * @param {String} params.code 支付宝小程序登录返回的code参数 + * @returns + */ + bindAlipay, + /** + * 绑定苹果账号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-apple + * @param {Object} params + * @param {String} params.identityToken 苹果登录返回identityToken + * @returns + */ + bindApple, + /** + * 更新密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-pwd + * @param {object} params + * @param {string} params.oldPassword 旧密码 + * @param {string} params.newPassword 新密码 + * @returns {object} + */ + updatePwd, + /** + * 通过短信验证码重置密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-sms + * @param {object} params + * @param {string} params.mobile 手机号 + * @param {string} params.mobile 短信验证码 + * @param {string} params.password 密码 + * @param {string} params.captcha 图形验证码 + * @returns {object} + */ + resetPwdBySms, + /** + * 通过邮箱验证码重置密码 + * @param {object} params + * @param {string} params.email 邮箱 + * @param {string} params.code 邮箱验证码 + * @param {string} params.password 密码 + * @param {string} params.captcha 图形验证码 + * @returns {object} + */ + resetPwdByEmail, + /** + * 注销账户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account + * @returns + */ + closeAccount, + /** + * 获取账户账户简略信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-account-info + */ + getAccountInfo, + /** + * 创建图形验证码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#create-captcha + * @param {Object} params + * @param {String} params.scene 图形验证码使用场景 + * @returns + */ + createCaptcha, + /** + * 刷新图形验证码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-captcha + * @param {Object} params + * @param {String} params.scene 图形验证码使用场景 + * @returns + */ + refreshCaptcha, + /** + * 发送短信验证码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#send-sms-code + * @param {Object} params + * @param {String} params.mobile 手机号 + * @param {String} params.captcha 图形验证码 + * @param {String} params.scene 短信验证码使用场景 + * @returns + */ + sendSmsCode, + /** + * 发送邮箱验证码 + * @tutorial 需自行实现功能 + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.captcha 图形验证码 + * @param {String} params.scene 短信验证码使用场景 + * @returns + */ + sendEmailCode, + /** + * 刷新token + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-token + */ + refreshToken, + /** + * 接受邀请 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#accept-invite + * @param {Object} params + * @param {String} params.inviteCode 邀请码 + * @returns + */ + acceptInvite, + /** + * 获取受邀用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-invited-user + * @param {Object} params + * @param {Number} params.level 获取受邀用户的级数,1表示直接邀请的用户 + * @param {Number} params.limit 返回数据大小 + * @param {Number} params.offset 返回数据偏移 + * @param {Boolean} params.needTotal 是否需要返回总数 + * @returns + */ + getInvitedUser, + /** + * 更新device表的push_clien_id + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-push-cid + * @param {object} params + * @param {string} params.pushClientId 客户端pushClientId + * @returns + */ + setPushCid, + /** + * 获取支持的登录方式 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-supported-login-type + * @returns + */ + getSupportedLoginType, + + /** + * 解绑微信 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-weixin + * @returns + */ + unbindWeixin, + /** + * 解绑支付宝 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-alipay + * @returns + */ + unbindAlipay, + /** + * 解绑QQ + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-qq + * @returns + */ + unbindQQ, + /** + * 解绑Apple + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-apple + * @returns + */ + unbindApple, + /** + * 安全网络握手,目前仅处理微信小程序安全网络握手 + */ + secureNetworkHandshakeByWeixin, + /** + * 设置密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-pwd + * @returns + */ + setPwd, + /** + * 外部注册用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-register + * @param {object} params + * @param {string} params.externalUid 业务系统的用户id + * @param {string} params.nickname 昵称 + * @param {string} params.gender 性别 + * @param {string} params.avatar 头像 + * @returns {object} + */ + externalRegister, + /** + * 外部用户登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-login + * @param {object} params + * @param {string} params.userId uni-id体系用户id + * @param {string} params.externalUid 业务系统的用户id + * @returns {object} + */ + externalLogin, + /** + * 使用 userId 或 externalUid 获取用户信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-update-userinfo + * @param {object} params + * @param {string} params.userId uni-id体系的用户id + * @param {string} params.externalUid 业务系统的用户id + * @param {string} params.nickname 昵称 + * @param {string} params.gender 性别 + * @param {string} params.avatar 头像 + * @returns {object} + */ + updateUserInfoByExternal, + /** + * 获取认证ID + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-certify-id + * @param {Object} params + * @param {String} params.realName 真实姓名 + * @param {String} params.idCard 身份证号码 + * @returns + */ + getFrvCertifyId, + /** + * 查询认证结果 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-auth-result + * @param {Object} params + * @param {String} params.certifyId 认证ID + * @param {String} params.needAlivePhoto 是否获取认证照片,Y_O (原始图片)、Y_M(虚化,背景马赛克)、N(不返图) + * @returns + */ + getFrvAuthResult, + /** + * 获取实名信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-realname-info + * @param {Object} params + * @param {Boolean} params.decryptData 是否解密数据 + * @returns + */ + getRealNameInfo +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js new file mode 100644 index 0000000000000000000000000000000000000000..a39f1ce01bb830815d21050b1b1d81d0e3f2bda1 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/en.js @@ -0,0 +1,62 @@ +const word = { + login: 'login', + 'verify-mobile': 'verify phone number' +} + +const sentence = { + 'uni-id-account-exists': 'Account exists', + 'uni-id-account-not-exists': 'Account does not exists', + 'uni-id-account-not-exists-in-current-app': 'Account does not exists in current app', + 'uni-id-account-conflict': 'User account conflict', + 'uni-id-account-banned': 'Account has been banned', + 'uni-id-account-auditing': 'Account audit in progress', + 'uni-id-account-audit-failed': 'Account audit failed', + 'uni-id-account-closed': 'Account has been closed', + 'uni-id-captcha-required': 'Captcha required', + 'uni-id-password-error': 'Password error', + 'uni-id-password-error-exceed-limit': 'The number of password errors is excessive', + 'uni-id-invalid-username': 'Invalid username', + 'uni-id-invalid-password': 'invalid password', + 'uni-id-invalid-password-super': 'Passwords must have 8-16 characters and contain uppercase letters, lowercase letters, numbers, and symbols.', + 'uni-id-invalid-password-strong': 'Passwords must have 8-16 characters and contain letters, numbers and symbols.', + 'uni-id-invalid-password-medium': 'Passwords must have 8-16 characters and contain at least two of the following: letters, numbers, and symbols.', + 'uni-id-invalid-password-weak': 'Passwords must have 6-16 characters and contain letters and numbers.', + 'uni-id-invalid-mobile': 'Invalid mobile phone number', + 'uni-id-invalid-email': 'Invalid email address', + 'uni-id-invalid-nickname': 'Invalid nickname', + 'uni-id-invalid-param': 'Invalid parameter', + 'uni-id-param-required': 'Parameter required: {param}', + 'uni-id-get-third-party-account-failed': 'Get third party account failed', + 'uni-id-get-third-party-user-info-failed': 'Get third party user info failed', + 'uni-id-mobile-verify-code-error': 'Verify code error or expired', + 'uni-id-email-verify-code-error': 'Verify code error or expired', + 'uni-id-admin-exists': 'Administrator exists', + 'uni-id-permission-error': 'Permission denied', + 'uni-id-system-error': 'System error', + 'uni-id-set-invite-code-failed': 'Set invite code failed', + 'uni-id-invalid-invite-code': 'Invalid invite code', + 'uni-id-change-inviter-forbidden': 'Change inviter is not allowed', + 'uni-id-bind-conflict': 'This account has been bound', + 'uni-id-admin-exist-in-other-apps': 'Administrator is registered in other consoles', + 'uni-id-unbind-failed': 'Please bind first and then unbind', + 'uni-id-unbind-not-supported': 'Unbinding is not supported', + 'uni-id-unbind-mobile-not-exists': 'This is the only way to login at the moment, please bind your phone number and then try to unbind', + 'uni-id-unbind-password-not-exists': 'Please set a password first', + 'uni-id-unsupported-request': 'Unsupported request', + 'uni-id-illegal-request': 'Illegal request', + 'uni-id-config-field-required': 'Config field required: {field}', + 'uni-id-config-field-invalid': 'Config field: {field} is invalid', + 'uni-id-frv-fail': 'Real name certify failed', + 'uni-id-frv-processing': 'Waiting for face recognition', + 'uni-id-realname-verified': 'This account has been verified', + 'uni-id-idcard-exists': 'The ID number has been bound to the account', + 'uni-id-invalid-idcard': 'ID number is invalid', + 'uni-id-invalid-realname': 'The name can only be Chinese characters', + 'uni-id-unknown-error': 'unknown error', + 'uni-id-realname-verify-upper-limit': 'The number of real-name certify on the day has reached the upper limit' +} + +module.exports = { + ...word, + ...sentence +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/index.js new file mode 100644 index 0000000000000000000000000000000000000000..557575d7eea78351ef2e87f4b7b3d4b1fa0538ef --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/index.js @@ -0,0 +1,22 @@ +let lang = { + 'zh-Hans': require('./zh-hans'), + en: require('./en') +} + +function mergeLanguage (lang1, lang2) { + const localeList = Object.keys(lang1) + localeList.push(...Object.keys(lang2)) + const result = {} + for (let i = 0; i < localeList.length; i++) { + const locale = localeList[i] + result[locale] = Object.assign({}, lang1[locale], lang2[locale]) + } + return result +} + +try { + const langPath = require.resolve('uni-config-center/uni-id/lang/index.js') + lang = mergeLanguage(lang, require(langPath)) +} catch (error) { } + +module.exports = lang diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js new file mode 100644 index 0000000000000000000000000000000000000000..3dc1749e596f62f50be545a9e4ae0aa7d776de2a --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lang/zh-hans.js @@ -0,0 +1,64 @@ +const word = { + login: '登录', + 'verify-mobile': '验证手机号' +} + +const sentence = { + 'uni-id-token-expired': '登录状态失效,token已过期', + 'uni-id-check-token-failed': 'token校验未通过', + 'uni-id-account-exists': '此账号已注册', + 'uni-id-account-not-exists': '此账号未注册', + 'uni-id-account-not-exists-in-current-app': '此账号未在该应用注册', + 'uni-id-account-conflict': '用户账号冲突', + 'uni-id-account-banned': '此账号已封禁', + 'uni-id-account-auditing': '此账号正在审核中', + 'uni-id-account-audit-failed': '此账号审核失败', + 'uni-id-account-closed': '此账号已注销', + 'uni-id-captcha-required': '请输入图形验证码', + 'uni-id-password-error': '密码错误', + 'uni-id-password-error-exceed-limit': '密码错误次数过多,请稍后再试', + 'uni-id-invalid-username': '用户名不合法', + 'uni-id-invalid-password': '密码不合法', + 'uni-id-invalid-password-super': '密码必须包含大小写字母、数字和特殊符号,长度8-16位', + 'uni-id-invalid-password-strong': '密码必须包含字母、数字和特殊符号,长度8-16位不合法', + 'uni-id-invalid-password-medium': '密码必须为字母、数字和特殊符号任意两种的组合,长度8-16位', + 'uni-id-invalid-password-weak': '密码必须包含字母和数字,长度6-16位', + 'uni-id-invalid-mobile': '手机号码不合法', + 'uni-id-invalid-email': '邮箱不合法', + 'uni-id-invalid-nickname': '昵称不合法', + 'uni-id-invalid-param': '参数错误', + 'uni-id-param-required': '缺少参数: {param}', + 'uni-id-get-third-party-account-failed': '获取第三方账号失败', + 'uni-id-get-third-party-user-info-failed': '获取用户信息失败', + 'uni-id-mobile-verify-code-error': '手机验证码错误或已过期', + 'uni-id-email-verify-code-error': '邮箱验证码错误或已过期', + 'uni-id-admin-exists': '超级管理员已存在', + 'uni-id-permission-error': '权限错误', + 'uni-id-system-error': '系统错误', + 'uni-id-set-invite-code-failed': '设置邀请码失败', + 'uni-id-invalid-invite-code': '邀请码不可用', + 'uni-id-change-inviter-forbidden': '禁止修改邀请人', + 'uni-id-bind-conflict': '此账号已被绑定', + 'uni-id-admin-exist-in-other-apps': '超级管理员已在其他控制台注册', + 'uni-id-unbind-failed': '请先绑定后再解绑', + 'uni-id-unbind-not-supported': '不支持解绑', + 'uni-id-unbind-mobile-not-exists': '这是当前唯一登录方式,请绑定手机号后再尝试解绑', + 'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑', + 'uni-id-unsupported-request': '不支持的请求方式', + 'uni-id-illegal-request': '非法请求', + 'uni-id-frv-fail': '实名认证失败', + 'uni-id-frv-processing': '等待人脸识别', + 'uni-id-realname-verified': '该账号已实名认证', + 'uni-id-idcard-exists': '该证件号码已绑定账号', + 'uni-id-invalid-idcard': '身份证号码不合法', + 'uni-id-invalid-realname': '姓名只能是汉字', + 'uni-id-unknown-error': '未知错误', + 'uni-id-realname-verify-upper-limit': '当日实名认证次数已达上限', + 'uni-id-config-field-required': '缺少配置项: {field}', + 'uni-id-config-field-invalid': '配置项: {field}无效' +} + +module.exports = { + ...word, + ...sentence +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/README.md b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/README.md new file mode 100644 index 0000000000000000000000000000000000000000..69e66178b341a9490d5023ab401f6bd560f3b668 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/README.md @@ -0,0 +1,3 @@ +# 说明 + +此目录内为uni-id-co基础能力,不建议直接修改。如果你发现有些逻辑加入会更好,或者此部分代码有Bug可以向我们提交PR,仓库地址:[]()。如果有特殊的需求也可以在[论坛](https://ask.dcloud.net.cn/)提出,我们可以讨论下如何实现。 \ No newline at end of file diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/account/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fd4c7beda00b3ecdba548087be1c8e677aea7688 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/account/index.js @@ -0,0 +1,16 @@ +const AlipayBase = require('../alipayBase') +const protocols = require('./protocols') +module.exports = class Auth extends AlipayBase { + constructor (options) { + super(options) + this._protocols = protocols + } + + async code2Session (code) { + const result = await this._exec('alipay.system.oauth.token', { + grantType: 'authorization_code', + code + }) + return result + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/account/protocols.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/account/protocols.js new file mode 100644 index 0000000000000000000000000000000000000000..17c9860d6b588fe333c353842435225b6143b249 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/account/protocols.js @@ -0,0 +1,10 @@ +module.exports = { + code2Session: { + // args (fromArgs) { + // return fromArgs + // }, + returnValue: { + openid: 'userId' + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js new file mode 100644 index 0000000000000000000000000000000000000000..8b18706ac573fab33fdcb04a7969529d80190192 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/alipay/alipayBase.js @@ -0,0 +1,231 @@ +const { + camel2snakeJson, + snake2camelJson, + getOffsetDate, + getFullTimeStr +} = require('../../../common/utils') +const crypto = require('crypto') + +const ALIPAY_ALGORITHM_MAPPING = { + RSA: 'RSA-SHA1', + RSA2: 'RSA-SHA256' +} + +module.exports = class AlipayBase { + constructor (options = {}) { + if (!options.appId) throw new Error('appId required') + if (!options.privateKey) throw new Error('privateKey required') + const defaultOptions = { + gateway: 'https://openapi.alipay.com/gateway.do', + timeout: 5000, + charset: 'utf-8', + version: '1.0', + signType: 'RSA2', + timeOffset: -new Date().getTimezoneOffset() / 60, + keyType: 'PKCS8' + } + + if (options.sandbox) { + options.gateway = 'https://openapi.alipaydev.com/gateway.do' + } + + this.options = Object.assign({}, defaultOptions, options) + + const privateKeyType = + this.options.keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY' + + this.options.privateKey = this._formatKey( + this.options.privateKey, + privateKeyType + ) + if (this.options.alipayPublicKey) { + this.options.alipayPublicKey = this._formatKey( + this.options.alipayPublicKey, + 'PUBLIC KEY' + ) + } + } + + _formatKey (key, type) { + return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----` + } + + _formatUrl (url, params) { + let requestUrl = url + // 需要放在 url 中的参数列表 + const urlArgs = [ + 'app_id', + 'method', + 'format', + 'charset', + 'sign_type', + 'sign', + 'timestamp', + 'version', + 'notify_url', + 'return_url', + 'auth_token', + 'app_auth_token' + ] + + for (const key in params) { + if (urlArgs.indexOf(key) > -1) { + const val = encodeURIComponent(params[key]) + requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?' + }${key}=${val}` + // 删除 postData 中对应的数据 + delete params[key] + } + } + + return { execParams: params, url: requestUrl } + } + + _getSign (method, params) { + const bizContent = params.bizContent || null + delete params.bizContent + + const signParams = Object.assign({ + method, + appId: this.options.appId, + charset: this.options.charset, + version: this.options.version, + signType: this.options.signType, + timestamp: getFullTimeStr(getOffsetDate(this.options.timeOffset)) + }, params) + + if (bizContent) { + signParams.bizContent = JSON.stringify(camel2snakeJson(bizContent)) + } + + // params key 驼峰转下划线 + const decamelizeParams = camel2snakeJson(signParams) + + // 排序 + const signStr = Object.keys(decamelizeParams) + .sort() + .map((key) => { + let data = decamelizeParams[key] + if (Array.prototype.toString.call(data) !== '[object String]') { + data = JSON.stringify(data) + } + return `${key}=${data}` + }) + .join('&') + + // 计算签名 + const sign = crypto + .createSign(ALIPAY_ALGORITHM_MAPPING[this.options.signType]) + .update(signStr, 'utf8') + .sign(this.options.privateKey, 'base64') + + return Object.assign(decamelizeParams, { sign }) + } + + async _exec (method, params = {}, option = {}) { + // 计算签名 + const signData = this._getSign(method, params) + const { url, execParams } = this._formatUrl(this.options.gateway, signData) + const { status, data } = await uniCloud.httpclient.request(url, { + method: 'POST', + data: execParams, + // 按 text 返回(为了验签) + dataType: 'text', + timeout: this.options.timeout + }) + if (status !== 200) throw new Error('request fail') + /** + * 示例响应格式 + * {"alipay_trade_precreate_response": + * {"code": "10000","msg": "Success","out_trade_no": "111111","qr_code": "https:\/\/"}, + * "sign": "abcde=" + * } + * 或者 + * {"error_response": + * {"code":"40002","msg":"Invalid Arguments","sub_code":"isv.code-invalid","sub_msg":"授权码code无效"}, + * } + */ + const result = JSON.parse(data) + const responseKey = `${method.replace(/\./g, '_')}_response` + const response = result[responseKey] + const errorResponse = result.error_response + if (response) { + // 按字符串验签 + const validateSuccess = option.validateSign ? this._checkResponseSign(data, responseKey) : true + if (validateSuccess) { + if (!response.code || response.code === '10000') { + const errCode = 0 + const errMsg = response.msg || '' + return { + errCode, + errMsg, + ...snake2camelJson(response) + } + } + const msg = response.sub_code ? `${response.sub_code} ${response.sub_msg}` : `${response.msg || 'unkonwn error'}` + throw new Error(msg) + } else { + throw new Error('check sign error') + } + } else if (errorResponse) { + throw new Error(errorResponse.sub_msg || errorResponse.msg || 'request fail') + } + + throw new Error('request fail') + } + + _checkResponseSign (signStr, responseKey) { + if (!this.options.alipayPublicKey || this.options.alipayPublicKey === '') { + console.warn('options.alipayPublicKey is empty') + // 支付宝公钥不存在时不做验签 + return true + } + + // 带验签的参数不存在时返回失败 + if (!signStr) { return false } + + // 根据服务端返回的结果截取需要验签的目标字符串 + const validateStr = this._getSignStr(signStr, responseKey) + // 服务端返回的签名 + const serverSign = JSON.parse(signStr).sign + + // 参数存在,并且是正常的结果(不包含 sub_code)时才验签 + const verifier = crypto.createVerify(ALIPAY_ALGORITHM_MAPPING[this.options.signType]) + verifier.update(validateStr, 'utf8') + return verifier.verify(this.options.alipayPublicKey, serverSign, 'base64') + } + + _getSignStr (originStr, responseKey) { + // 待签名的字符串 + let validateStr = originStr.trim() + // 找到 xxx_response 开始的位置 + const startIndex = originStr.indexOf(`${responseKey}"`) + // 找到最后一个 “"sign"” 字符串的位置(避免) + const lastIndex = originStr.lastIndexOf('"sign"') + + /** + * 删除 xxx_response 及之前的字符串 + * 假设原始字符串为 + * {"xxx_response":{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"} + * 删除后变为 + * :{"code":"10000"},"sign":"jumSvxTKwn24G5sAIN"} + */ + validateStr = validateStr.substr(startIndex + responseKey.length + 1) + + /** + * 删除最后一个 "sign" 及之后的字符串 + * 删除后变为 + * :{"code":"10000"}, + * {} 之间就是待验签的字符串 + */ + validateStr = validateStr.substr(0, lastIndex) + + // 删除第一个 { 之前的任何字符 + validateStr = validateStr.replace(/^[^{]*{/g, '{') + + // 删除最后一个 } 之后的任何字符 + validateStr = validateStr.replace(/\}([^}]*)$/g, '}') + + return validateStr + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/apple/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/apple/account/index.js new file mode 100644 index 0000000000000000000000000000000000000000..24cd60fc5a57aa7479e4ad82b620db382aad9e6d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/apple/account/index.js @@ -0,0 +1,76 @@ +const rsaPublicKeyPem = require('../rsa-public-key-pem') +let authKeysCache = null + +module.exports = class Auth { + constructor (options) { + this.options = Object.assign({ + baseUrl: 'https://appleid.apple.com', + timeout: 10000 + }, options) + } + + async _fetch (url, options) { + const { baseUrl } = this.options + return uniCloud.httpclient.request(baseUrl + url, options) + } + + async verifyIdentityToken (identityToken) { + // 解密出kid,拿取key + const jwtHeader = identityToken.split('.')[0] + const { kid } = JSON.parse(Buffer.from(jwtHeader, 'base64').toString()) + let authKeys + if (authKeysCache) { + authKeys = authKeysCache + } else { + authKeys = await this.getAuthKeys() + authKeysCache = authKeys + } + const usedKey = authKeys.find(item => item.kid === kid) + + /** + * identityToken 格式 + * + * { + * iss: 'https://appleid.apple.com', + * aud: 'io.dcloud.hellouniapp', + * exp: 1610626724, + * iat: 1610540324, + * sub: '000628.30119d332d9b45a3be4a297f9391fd5c.0403', + * c_hash: 'oFfgewoG36cJX00KUbj45A', + * email: 'x2awmap99s@privaterelay.appleid.com', + * email_verified: 'true', + * is_private_email: 'true', + * auth_time: 1610540324, + * nonce_supported: true + * } + */ + const payload = require('jsonwebtoken').verify( + identityToken, + rsaPublicKeyPem(usedKey.n, usedKey.e), + { + algorithms: usedKey.alg + } + ) + + if (payload.iss !== 'https://appleid.apple.com' || payload.aud !== this.options.bundleId) { + throw new Error('Invalid identity token') + } + + return { + openid: payload.sub, + email: payload.email, + emailVerified: payload.email_verified === 'true', + isPrivateEmail: payload.is_private_email === 'true' + } + } + + async getAuthKeys () { + const { status, data } = await this._fetch('/auth/keys', { + method: 'GET', + dataType: 'json', + timeout: this.options.timeout + }) + if (status !== 200) throw new Error('request https://appleid.apple.com/auth/keys fail') + return data.keys + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/apple/rsa-public-key-pem.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/apple/rsa-public-key-pem.js new file mode 100644 index 0000000000000000000000000000000000000000..8fcc5690e136cb52dffdf807c5d300447fba7bb1 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/apple/rsa-public-key-pem.js @@ -0,0 +1,64 @@ +// http://stackoverflow.com/questions/18835132/xml-to-pem-in-node-js +/* eslint-disable camelcase */ +function rsaPublicKeyPem (modulus_b64, exponent_b64) { + const modulus = Buffer.from(modulus_b64, 'base64') + const exponent = Buffer.from(exponent_b64, 'base64') + + let modulus_hex = modulus.toString('hex') + let exponent_hex = exponent.toString('hex') + + modulus_hex = prepadSigned(modulus_hex) + exponent_hex = prepadSigned(exponent_hex) + + const modlen = modulus_hex.length / 2 + const explen = exponent_hex.length / 2 + + const encoded_modlen = encodeLengthHex(modlen) + const encoded_explen = encodeLengthHex(explen) + const encoded_pubkey = '30' + + encodeLengthHex( + modlen + + explen + + encoded_modlen.length / 2 + + encoded_explen.length / 2 + 2 + ) + + '02' + encoded_modlen + modulus_hex + + '02' + encoded_explen + exponent_hex + + const der_b64 = Buffer.from(encoded_pubkey, 'hex').toString('base64') + + const pem = '-----BEGIN RSA PUBLIC KEY-----\n' + + der_b64.match(/.{1,64}/g).join('\n') + + '\n-----END RSA PUBLIC KEY-----\n' + + return pem +} + +function prepadSigned (hexStr) { + const msb = hexStr[0] + if (msb < '0' || msb > '7') { + return '00' + hexStr + } else { + return hexStr + } +} + +function toHex (number) { + const nstr = number.toString(16) + if (nstr.length % 2) return '0' + nstr + return nstr +} + +// encode ASN.1 DER length field +// if <=127, short form +// if >=128, long form +function encodeLengthHex (n) { + if (n <= 127) return toHex(n) + else { + const n_hex = toHex(n) + const length_of_length_byte = 128 + n_hex.length / 2 // 0x80+numbytes + return toHex(length_of_length_byte) + n_hex + } +} + +module.exports = rsaPublicKeyPem diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js new file mode 100644 index 0000000000000000000000000000000000000000..499265e47418c45867520714974451ae36784bba --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/index.js @@ -0,0 +1,36 @@ +const WxAccount = require('./weixin/account/index') +const QQAccount = require('./qq/account/index') +const AliAccount = require('./alipay/account/index') +const AppleAccount = require('./apple/account/index') + +const createApi = require('./share/create-api') + +module.exports = { + initWeixin: function () { + const oauthConfig = this.configUtils.getOauthConfig({ provider: 'weixin' }) + return createApi(WxAccount, { + appId: oauthConfig.appid, + secret: oauthConfig.appsecret + }) + }, + initQQ: function () { + const oauthConfig = this.configUtils.getOauthConfig({ provider: 'qq' }) + return createApi(QQAccount, { + appId: oauthConfig.appid, + secret: oauthConfig.appsecret + }) + }, + initAlipay: function () { + const oauthConfig = this.configUtils.getOauthConfig({ provider: 'alipay' }) + return createApi(AliAccount, { + appId: oauthConfig.appid, + privateKey: oauthConfig.privateKey + }) + }, + initApple: function () { + const oauthConfig = this.configUtils.getOauthConfig({ provider: 'apple' }) + return createApi(AppleAccount, { + bundleId: oauthConfig.bundleId + }) + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js new file mode 100644 index 0000000000000000000000000000000000000000..35a1a335714e40e67290cabafc776ef3c6302afc --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/index.js @@ -0,0 +1,97 @@ +const { + UniCloudError +} = require('../../../../common/error') +const { + resolveUrl +} = require('../../../../common/utils') +const { + callQQOpenApi +} = require('../normalize') + +module.exports = class Auth { + constructor (options) { + this.options = Object.assign({ + baseUrl: 'https://graph.qq.com', + timeout: 5000 + }, options) + } + + async _requestQQOpenapi ({ name, url, data, options }) { + const defaultOptions = { + method: 'GET', + dataType: 'json', + dataAsQueryString: true, + timeout: this.options.timeout + } + const result = await callQQOpenApi({ + name: `auth.${name}`, + url: resolveUrl(this.options.baseUrl, url), + data, + options, + defaultOptions + }) + return result + } + + async getUserInfo ({ + accessToken, + openid + } = {}) { + const url = '/user/get_user_info' + const result = await this._requestQQOpenapi({ + name: 'getUserInfo', + url, + data: { + oauthConsumerKey: this.options.appId, + accessToken, + openid + } + }) + return { + nickname: result.nickname, + avatar: result.figureurl_qq_1 + } + } + + async getOpenidByToken ({ + accessToken + } = {}) { + const url = '/oauth2.0/me' + const result = await this._requestQQOpenapi({ + name: 'getOpenidByToken', + url, + data: { + accessToken, + unionid: 1, + fmt: 'json' + } + }) + if (result.clientId !== this.options.appId) { + throw new UniCloudError({ + code: 'APPID_NOT_MATCH', + message: 'appid not match' + }) + } + return { + openid: result.openid, + unionid: result.unionid + } + } + + async code2Session ({ + code + } = {}) { + const url = 'https://api.q.qq.com/sns/jscode2session' + const result = await this._requestQQOpenapi({ + name: 'getOpenidByToken', + url, + data: { + grant_type: 'authorization_code', + appid: this.options.appId, + secret: this.options.secret, + js_code: code + } + }) + return result + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/protocol.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/account/protocol.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js new file mode 100644 index 0000000000000000000000000000000000000000..ca0ebe60dc991fd0c3e7e314f28e85e6fb2aa919 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/qq/normalize.js @@ -0,0 +1,85 @@ +const { + UniCloudError +} = require('../../../common/error') +const { + camel2snakeJson, + snake2camelJson +} = require('../../../common/utils') + +function generateApiResult (apiName, data) { + if (data.ret || data.error) { + // 这三种都是qq的错误码规范 + const code = data.ret || data.error || data.errcode || -2 + const message = data.msg || data.error_description || data.errmsg || `${apiName} fail` + throw new UniCloudError({ + code, + message + }) + } else { + delete data.ret + delete data.msg + delete data.error + delete data.error_description + delete data.errcode + delete data.errmsg + return { + ...data, + errMsg: `${apiName} ok`, + errCode: 0 + } + } +} + +function nomalizeError (apiName, error) { + throw new UniCloudError({ + code: error.code || -2, + message: error.message || `${apiName} fail` + }) +} + +async function callQQOpenApi ({ + name, + url, + data, + options, + defaultOptions +}) { + options = Object.assign({}, defaultOptions, options, { data: camel2snakeJson(Object.assign({}, data)) }) + let result + try { + result = await uniCloud.httpclient.request(url, options) + } catch (e) { + return nomalizeError(name, e) + } + let resData = result.data + const contentType = result.headers['content-type'] + if ( + Buffer.isBuffer(resData) && + (contentType.indexOf('text/plain') === 0 || + contentType.indexOf('application/json') === 0) + ) { + try { + resData = JSON.parse(resData.toString()) + } catch (e) { + resData = resData.toString() + } + } else if (Buffer.isBuffer(resData)) { + resData = { + buffer: resData, + contentType + } + } + return snake2camelJson( + generateApiResult( + name, + resData || { + errCode: -2, + errMsg: 'Request failed' + } + ) + ) +} + +module.exports = { + callQQOpenApi +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js new file mode 100644 index 0000000000000000000000000000000000000000..abb1f4146924e3dc7d57af656ffe59ac892718bf --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/share/create-api.js @@ -0,0 +1,73 @@ +const { + isFn, + isPlainObject +} = require('../../../common/utils') + +// 注意:不进行递归处理 +function parseParams (params = {}, rule) { + if (!rule || !params) { + return params + } + const internalKeys = ['_pre', '_purify', '_post'] + // 转换之前的处理 + if (rule._pre) { + params = rule._pre(params) + } + // 净化参数 + let purify = { shouldDelete: new Set([]) } + if (rule._purify) { + const _purify = rule._purify + for (const purifyKey in _purify) { + _purify[purifyKey] = new Set(_purify[purifyKey]) + } + purify = Object.assign(purify, _purify) + } + if (isPlainObject(rule)) { + for (const key in rule) { + const parser = rule[key] + if (isFn(parser) && internalKeys.indexOf(key) === -1) { + params[key] = parser(params) + } else if (typeof parser === 'string' && internalKeys.indexOf(key) === -1) { + // 直接转换属性名称的删除旧属性名 + params[key] = params[parser] + purify.shouldDelete.add(parser) + } + } + } else if (isFn(rule)) { + params = rule(params) + } + + if (purify.shouldDelete) { + for (const item of purify.shouldDelete) { + delete params[item] + } + } + + // 转换之后的处理 + if (rule._post) { + params = rule._post(params) + } + + return params +} + +function createApi (ApiClass, options) { + const apiInstance = new ApiClass(options) + return new Proxy(apiInstance, { + get: function (obj, prop) { + if (typeof obj[prop] === 'function' && prop.indexOf('_') !== 0 && obj._protocols && obj._protocols[prop]) { + const protocol = obj._protocols[prop] + return async function (params) { + params = parseParams(params, protocol.args) + let result = await obj[prop](params) + result = parseParams(result, protocol.returnValue) + return result + } + } else { + return obj[prop] + } + } + }) +} + +module.exports = createApi diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js new file mode 100644 index 0000000000000000000000000000000000000000..734f6423b3fead834cd296796ec66281ffe0b5ec --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/account/index.js @@ -0,0 +1,111 @@ +const { + callWxOpenApi, + buildUrl +} = require('../normalize') + +module.exports = class Auth { + constructor (options) { + this.options = Object.assign({ + baseUrl: 'https://api.weixin.qq.com', + timeout: 5000 + }, options) + } + + async _requestWxOpenapi ({ name, url, data, options }) { + const defaultOptions = { + method: 'GET', + dataType: 'json', + dataAsQueryString: true, + timeout: this.options.timeout + } + const result = await callWxOpenApi({ + name: `auth.${name}`, + url: `${this.options.baseUrl}${buildUrl(url, data)}`, + data, + options, + defaultOptions + }) + return result + } + + async code2Session (code) { + const url = '/sns/jscode2session' + const result = await this._requestWxOpenapi({ + name: 'code2Session', + url, + data: { + grant_type: 'authorization_code', + appid: this.options.appId, + secret: this.options.secret, + js_code: code + } + }) + return result + } + + async getOauthAccessToken (code) { + const url = '/sns/oauth2/access_token' + const result = await this._requestWxOpenapi({ + name: 'getOauthAccessToken', + url, + data: { + grant_type: 'authorization_code', + appid: this.options.appId, + secret: this.options.secret, + code + } + }) + if (result.expiresIn) { + result.expired = Date.now() + result.expiresIn * 1000 + // delete result.expiresIn + } + return result + } + + async getUserInfo ({ + accessToken, + openid + } = {}) { + const url = '/sns/userinfo' + const { + nickname, + headimgurl: avatar + } = await this._requestWxOpenapi({ + name: 'getUserInfo', + url, + data: { + accessToken, + openid, + appid: this.options.appId, + secret: this.options.secret, + scope: 'snsapi_userinfo' + } + }) + return { + nickname, + avatar + } + } + + async getPhoneNumber (accessToken, code) { + const url = `/wxa/business/getuserphonenumber?access_token=${accessToken}` + const { phoneInfo } = await this._requestWxOpenapi({ + name: 'getPhoneNumber', + url, + data: { + code + }, + options: { + method: 'POST', + dataAsQueryString: false, + headers: { + 'content-type': 'application/json' + } + } + }) + + return { + purePhoneNumber: phoneInfo.purePhoneNumber + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js new file mode 100644 index 0000000000000000000000000000000000000000..9749c3838ddb96d7119d99ad65f438df9b612ea3 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/normalize.js @@ -0,0 +1,95 @@ +const { + UniCloudError +} = require('../../../common/error') +const { + camel2snakeJson, snake2camelJson +} = require('../../../common/utils') + +function generateApiResult (apiName, data) { + if (data.errcode) { + throw new UniCloudError({ + code: data.errcode || -2, + message: data.errmsg || `${apiName} fail` + }) + } else { + delete data.errcode + delete data.errmsg + return { + ...data, + errMsg: `${apiName} ok`, + errCode: 0 + } + } +} + +function nomalizeError (apiName, error) { + throw new UniCloudError({ + code: error.code || -2, + message: error.message || `${apiName} fail` + }) +} + +// 微信openapi接口接收蛇形(snake case)参数返回蛇形参数,这里进行转化,如果是formdata里面的参数需要在对应api实现时就转为蛇形 +async function callWxOpenApi ({ + name, + url, + data, + options, + defaultOptions +}) { + let result = {} + // 获取二维码的接口wxacode.get和wxacode.getUnlimited不可以传入access_token(可能有其他接口也不可以),否则会返回data format error + const dataCopy = camel2snakeJson(Object.assign({}, data)) + if (dataCopy && dataCopy.access_token) { + delete dataCopy.access_token + } + try { + options = Object.assign({}, defaultOptions, options, { data: dataCopy }) + result = await uniCloud.httpclient.request(url, options) + } catch (e) { + return nomalizeError(name, e) + } + + // 有几个接口成功返回buffer失败返回json,对这些接口统一成返回buffer,然后分别解析 + let resData = result.data + const contentType = result.headers['content-type'] + if ( + Buffer.isBuffer(resData) && + (contentType.indexOf('text/plain') === 0 || + contentType.indexOf('application/json') === 0) + ) { + try { + resData = JSON.parse(resData.toString()) + } catch (e) { + resData = resData.toString() + } + } else if (Buffer.isBuffer(resData)) { + resData = { + buffer: resData, + contentType + } + } + return snake2camelJson( + generateApiResult( + name, + resData || { + errCode: -2, + errMsg: 'Request failed' + } + ) + ) +} + +function buildUrl (url, data) { + let query = '' + if (data && data.accessToken) { + const divider = url.indexOf('?') > -1 ? '&' : '?' + query = `${divider}access_token=${data.accessToken}` + } + return `${url}${query}` +} + +module.exports = { + callWxOpenApi, + buildUrl +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..acb053adfd6ab490e06695939150fefcfabb8019 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/third-party/weixin/utils.js @@ -0,0 +1,87 @@ +const crypto = require('crypto') +const { + isPlainObject +} = require('../../../common/utils') + +// 退款通知解密key=md5(key) +function decryptData (encryptedData, key, iv = '') { + // 解密 + const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv) + // 设置自动 padding 为 true,删除填充补位 + decipher.setAutoPadding(true) + let decoded = decipher.update(encryptedData, 'base64', 'utf8') + decoded += decipher.final('utf8') + return decoded +} + +function md5 (str, encoding = 'utf8') { + return crypto + .createHash('md5') + .update(str, encoding) + .digest('hex') +} + +function sha256 (str, key, encoding = 'utf8') { + return crypto + .createHmac('sha256', key) + .update(str, encoding) + .digest('hex') +} + +function getSignStr (obj) { + return Object.keys(obj) + .filter(key => key !== 'sign' && obj[key] !== undefined && obj[key] !== '') + .sort() + .map(key => key + '=' + obj[key]) + .join('&') +} + +function getNonceStr (length = 16) { + let str = '' + while (str.length < length) { + str += Math.random().toString(32).substring(2) + } + return str.substring(0, length) +} + +// 简易版Object转XML,只可在微信支付时使用,不支持嵌套 +function buildXML (obj, rootName = 'xml') { + const content = Object.keys(obj).map(item => { + if (isPlainObject(obj[item])) { + return `<${item}>` + } else { + return `<${item}>` + } + }) + return `<${rootName}>${content.join('')}` +} + +function isXML (str) { + const reg = /^(<\?xml.*\?>)?(\r?\n)*(.|\r?\n)*<\/xml>$/i + return reg.test(str.trim()) +}; + +// 简易版XML转Object,只可在微信支付时使用,不支持嵌套 +function parseXML (xml) { + const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/ + const str = xmlReg.exec(xml)[1] + const obj = {} + const nodeReg = /<(.*?)>(?:){0,1}<\/.*?>/g + let matches = null + // eslint-disable-next-line no-cond-assign + while ((matches = nodeReg.exec(str))) { + obj[matches[1]] = matches[2] + } + return obj +} + +module.exports = { + decryptData, + md5, + sha256, + getSignStr, + getNonceStr, + buildXML, + parseXML, + isXML +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/account.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/account.js new file mode 100644 index 0000000000000000000000000000000000000000..8943dc0d9918681aee4de1b0bc62807ef5fb9470 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/account.js @@ -0,0 +1,98 @@ +const { + dbCmd, + userCollection +} = require('../../common/constants') +const { + USER_IDENTIFIER +} = require('../../common/constants') +const { + batchFindObjctValue, + getType, + isMatchUserApp +} = require('../../common/utils') + +/** + * 查询满足条件的用户 + * @param {Object} params + * @param {Object} params.userQuery 用户唯一标识组成的查询条件 + * @param {Object} params.authorizedApp 用户允许登录的应用 + * @returns userMatched 满足条件的用户列表 + */ +async function findUser (params = {}) { + const { + userQuery, + authorizedApp = [] + } = params + const condition = getUserQueryCondition(userQuery) + if (condition.length === 0) { + throw new Error('Invalid user query') + } + const authorizedAppType = getType(authorizedApp) + if (authorizedAppType !== 'string' && authorizedAppType !== 'array') { + throw new Error('Invalid authorized app') + } + + let finalQuery + + if (condition.length === 1) { + finalQuery = condition[0] + } else { + finalQuery = dbCmd.or(condition) + } + const userQueryRes = await userCollection.where(finalQuery).get() + return { + total: userQueryRes.data.length, + userMatched: userQueryRes.data.filter(item => { + return isMatchUserApp(item.dcloud_appid, authorizedApp) + }) + } +} + +function getUserIdentifier (userRecord = {}) { + const keys = Object.keys(USER_IDENTIFIER) + return batchFindObjctValue(userRecord, keys) +} + +function getUserQueryCondition (userRecord = {}) { + const userIdentifier = getUserIdentifier(userRecord) + const condition = [] + for (const key in userIdentifier) { + const value = userIdentifier[key] + if (!value) { + // 过滤所有value为假值的条件,在查询用户时没有意义 + continue + } + const queryItem = { + [key]: value + } + // 为兼容用户老数据用户名及邮箱需要同时查小写及原始大小写数据 + if (key === 'mobile') { + queryItem.mobile_confirmed = 1 + } else if (key === 'email') { + queryItem.email_confirmed = 1 + const email = userIdentifier.email + if (email.toLowerCase() !== email) { + condition.push({ + email: email.toLowerCase(), + email_confirmed: 1 + }) + } + } else if (key === 'username') { + const username = userIdentifier.username + if (username.toLowerCase() !== username) { + condition.push({ + username: username.toLowerCase() + }) + } + } else if (key === 'identities') { + queryItem.identities = dbCmd.elemMatch(value) + } + condition.push(queryItem) + } + return condition +} + +module.exports = { + findUser, + getUserIdentifier +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/captcha.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/captcha.js new file mode 100644 index 0000000000000000000000000000000000000000..07c1f349dc37ea5c8b56d46f9f0789501495a6f9 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/captcha.js @@ -0,0 +1,76 @@ +const { + ERROR +} = require('../../common/error') + +async function getNeedCaptcha ({ + uid, + username, + mobile, + email, + type = 'login', + limitDuration = 7200000, // 两小时 + limitTimes = 3 // 记录次数 +} = {}) { + const db = uniCloud.database() + const dbCmd = db.command + // 当用户最近“2小时内(limitDuration)”登录失败达到3次(limitTimes)时。要求用户提交验证码 + const now = Date.now() + const uniIdLogCollection = db.collection('uni-id-log') + const userIdentifier = { + user_id: uid, + username, + mobile, + email + } + + let totalKey = 0; let deleteKey = 0 + for (const key in userIdentifier) { + totalKey++ + if (!userIdentifier[key] || typeof userIdentifier[key] !== 'string') { + deleteKey++ + delete userIdentifier[key] + } + } + + if (deleteKey === totalKey) { + throw new Error('System error') // 正常情况下不会进入此条件,但是考虑到后续会有其他开发者修改此云对象,在此处做一个判断 + } + + const { + data: recentRecord + } = await uniIdLogCollection.where({ + ip: this.getUniversalClientInfo().clientIP, + ...userIdentifier, + type, + create_date: dbCmd.gt(now - limitDuration) + }) + .orderBy('create_date', 'desc') + .limit(limitTimes) + .get() + return recentRecord.length === limitTimes && recentRecord.every(item => item.state === 0) +} + +async function verifyCaptcha (params = {}) { + const { + captcha, + scene + } = params + if (!captcha) { + throw { + errCode: ERROR.CAPTCHA_REQUIRED + } + } + const payload = await this.uniCaptcha.verify({ + deviceId: this.getUniversalClientInfo().deviceId, + captcha, + scene + }) + if (payload.errCode) { + throw payload + } +} + +module.exports = { + getNeedCaptcha, + verifyCaptcha +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/config.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/config.js new file mode 100644 index 0000000000000000000000000000000000000000..ed77667a2a16a74285253aaa979d4f435ad9348e --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/config.js @@ -0,0 +1,135 @@ +const { + getWeixinPlatform +} = require('./weixin') +const createConfig = require('uni-config-center') + +const requiredConfig = { + 'web.weixin-h5': ['appid', 'appsecret'], + 'web.weixin-web': ['appid', 'appsecret'], + 'app.weixin': ['appid', 'appsecret'], + 'mp-weixin.weixin': ['appid', 'appsecret'], + 'app.qq': ['appid', 'appsecret'], + 'mp-alipay.alipay': ['appid', 'privateKey'], + 'app.apple': ['bundleId'] +} + +const uniIdConfig = createConfig({ + pluginId: 'uni-id' +}) + +class ConfigUtils { + constructor ({ + context + } = {}) { + this.context = context + this.clientInfo = context.getUniversalClientInfo() + const { + appId, + uniPlatform + } = this.clientInfo + this.appId = appId + switch (uniPlatform) { + case 'app': + case 'app-plus': + this.platform = 'app' + break + case 'web': + case 'h5': + this.platform = 'web' + break + default: + this.platform = uniPlatform + break + } + } + + getConfigArray () { + let configContent + try { + configContent = require('uni-config-center/uni-id/config.json') + } catch (error) { + throw new Error('Invalid config file\n' + error.message) + } + if (configContent[0]) { + return Object.values(configContent) + } + configContent.isDefaultConfig = true + return [configContent] + } + + getAppConfig () { + const configArray = this.getConfigArray() + return configArray.find(item => item.dcloudAppid === this.appId) || configArray.find(item => item.isDefaultConfig) + } + + getPlatformConfig () { + const appConfig = this.getAppConfig() + if (!appConfig) { + throw new Error( + `Config for current app (${this.appId}) was not found, please check your config file or client appId`) + } + const platform = this.platform + if ( + (this.platform === 'app' && appConfig['app-plus']) || + (this.platform === 'web' && appConfig.h5) + ) { + throw new Error( + `Client platform is ${this.platform}, but ${this.platform === 'web' ? 'h5' : 'app-plus'} was found in config. Please refer to: https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary?id=m-to-co` + ) + } + + const defaultConfig = { + tokenExpiresIn: 7200, + tokenExpiresThreshold: 1200, + passwordErrorLimit: 6, + passwordErrorRetryTime: 3600 + } + return Object.assign(defaultConfig, appConfig, appConfig[platform]) + } + + getOauthProvider ({ + provider + } = {}) { + const clientPlatform = this.platform + let oatuhProivder = provider + if (provider === 'weixin' && clientPlatform === 'web') { + const weixinPlatform = getWeixinPlatform.call(this.context) + if (weixinPlatform === 'h5' || weixinPlatform === 'web') { + oatuhProivder = 'weixin-' + weixinPlatform // weixin-h5 公众号,weixin-web pc端 + } + } + return oatuhProivder + } + + getOauthConfig ({ + provider + } = {}) { + const config = this.getPlatformConfig() + const clientPlatform = this.platform + const oatuhProivder = this.getOauthProvider({ + provider + }) + const requireConfigKey = requiredConfig[`${clientPlatform}.${oatuhProivder}`] || [] + if (!config.oauth || !config.oauth[oatuhProivder]) { + throw new Error(`Config param required: ${clientPlatform}.oauth.${oatuhProivder}`) + } + const oauthConfig = config.oauth[oatuhProivder] + requireConfigKey.forEach((item) => { + if (!oauthConfig[item]) { + throw new Error(`Config param required: ${clientPlatform}.oauth.${oatuhProivder}.${item}`) + } + }) + return oauthConfig + } + + getHooks () { + if (uniIdConfig.hasFile('hooks/index.js')) { + return require( + uniIdConfig.resolve('hooks/index.js') + ) + } + return {} + } +} + +module.exports = ConfigUtils diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/fission.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/fission.js new file mode 100644 index 0000000000000000000000000000000000000000..65d48e27e3d81e0bcad2340fb07c7a4b695295e1 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/fission.js @@ -0,0 +1,192 @@ +const { + dbCmd, + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +/** + * 获取随机邀请码,邀请码由大写字母加数字组成,由于存在手动输入邀请码的场景,从可选字符中去除 0、1、I、O + * @param {number} len 邀请码长度,默认6位 + * @returns {string} 随机邀请码 + */ +function getRandomInviteCode (len = 6) { + const charArr = ['2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'] + let code = '' + for (let i = 0; i < len; i++) { + code += charArr[Math.floor(Math.random() * charArr.length)] + } + return code +} + +/** + * 获取可用的邀请码,至多尝试十次以获取可用邀请码。从10亿可选值中随机,碰撞概率较低 + * 也有其他方案可以尝试,比如在数据库内设置一个从0开始计数的数字,每次调用此方法时使用updateAndReturn使数字加1并返回加1后的值,根据这个值生成对应的邀请码,比如(22222A + 1 == 22222B),此方式性能理论更好,但是不适用于旧项目 + * @param {object} param + * @param {string} param.inviteCode 初始随机邀请码 + */ +async function getValidInviteCode () { + let retry = 10 + let code + let codeValid = false + while (retry > 0 && !codeValid) { + retry-- + code = getRandomInviteCode() + const getUserRes = await userCollection.where({ + my_invite_code: code + }).limit(1).get() + if (getUserRes.data.length === 0) { + codeValid = true + break + } + } + if (!codeValid) { + throw { + errCode: ERROR.SET_INVITE_CODE_FAILED + } + } + return code +} + +/** + * 根据邀请码查询邀请人 + * @param {object} param + * @param {string} param.inviteCode 邀请码 + * @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请 + * @returns + */ +async function findUserByInviteCode ({ + inviteCode, + queryUid +} = {}) { + if (typeof inviteCode !== 'string') { + throw { + errCode: ERROR.SYSTEM_ERROR + } + } + // 根据邀请码查询邀请人 + let getInviterRes + if (queryUid) { + getInviterRes = await userCollection.where({ + _id: dbCmd.neq(queryUid), + inviter_uid: dbCmd.not(dbCmd.all([queryUid])), + my_invite_code: inviteCode + }).get() + } else { + getInviterRes = await userCollection.where({ + my_invite_code: inviteCode + }).get() + } + if (getInviterRes.data.length > 1) { + // 正常情况下不可能进入此条件,以防用户自行修改数据库出错,在此做出判断 + throw { + errCode: ERROR.SYSTEM_ERROR + } + } + const inviterRecord = getInviterRes.data[0] + if (!inviterRecord) { + throw { + errCode: ERROR.INVALID_INVITE_CODE + } + } + return inviterRecord +} + +/** + * 根据邀请码生成邀请信息 + * @param {object} param + * @param {string} param.inviteCode 邀请码 + * @param {string} param.queryUid 受邀人id,非空时校验不可被下家或自己邀请 + * @returns + */ +async function generateInviteInfo ({ + inviteCode, + queryUid +} = {}) { + const inviterRecord = await findUserByInviteCode({ + inviteCode, + queryUid + }) + // 倒叙拼接当前用户邀请链 + const inviterUid = inviterRecord.inviter_uid || [] + inviterUid.unshift(inviterRecord._id) + return { + inviterUid, + inviteTime: Date.now() + } +} + +/** + * 检查当前用户是否可以接受邀请,如果可以返回用户记录 + * @param {string} uid + */ +async function checkInviteInfo (uid) { + // 检查当前用户是否已有邀请人 + const getUserRes = await userCollection.doc(uid).field({ + my_invite_code: true, + inviter_uid: true + }).get() + const userRecord = getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + if (userRecord.inviter_uid && userRecord.inviter_uid.length > 0) { + throw { + errCode: ERROR.CHANGE_INVITER_FORBIDDEN + } + } + return userRecord +} + +/** + * 指定用户接受邀请码邀请 + * @param {object} param + * @param {string} param.uid 用户uid + * @param {string} param.inviteCode 邀请人的邀请码 + * @returns + */ +async function acceptInvite ({ + uid, + inviteCode +} = {}) { + await checkInviteInfo(uid) + const { + inviterUid, + inviteTime + } = await generateInviteInfo({ + inviteCode, + queryUid: uid + }) + + if (inviterUid === uid) { + throw { + errCode: ERROR.INVALID_INVITE_CODE + } + } + + // 更新当前用户的邀请人信息 + await userCollection.doc(uid).update({ + inviter_uid: inviterUid, + invite_time: inviteTime + }) + + // 更新当前用户邀请的用户的邀请人信息,这步可能较为耗时 + await userCollection.where({ + inviter_uid: uid + }).update({ + inviter_uid: dbCmd.push(inviterUid) + }) + + return { + errCode: 0, + errMsg: '' + } +} + +module.exports = { + acceptInvite, + generateInviteInfo, + getValidInviteCode +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/login.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/login.js new file mode 100644 index 0000000000000000000000000000000000000000..772d7c81b835ada3974c6d5158b6ddd0bac4673a --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/login.js @@ -0,0 +1,246 @@ +const { + findUser +} = require('./account') +const { + userCollection, + LOG_TYPE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + logout +} = require('./logout') +const PasswordUtils = require('./password') + +async function realPreLogin (params = {}) { + const { + user + } = params + const appId = this.getUniversalClientInfo().appId + const { + total, + userMatched + } = await findUser({ + userQuery: user, + authorizedApp: appId + }) + if (userMatched.length === 0) { + if (total > 0) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP + } + } + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } else if (userMatched.length > 1) { + throw { + errCode: ERROR.ACCOUNT_CONFLICT + } + } + const userRecord = userMatched[0] + checkLoginUserRecord(userRecord) + return userRecord +} + +async function preLogin (params = {}) { + const { + user + } = params + try { + const user = await realPreLogin.call(this, params) + return user + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + data: user, + type: LOG_TYPE.LOGIN + }) + throw error + } +} + +async function preLoginWithPassword (params = {}) { + const { + user, + password + } = params + try { + const userRecord = await realPreLogin.call(this, params) + const { + passwordErrorLimit, + passwordErrorRetryTime + } = this.config + const { + clientIP + } = this.getUniversalClientInfo() + // 根据ip地址,密码错误次数过多,锁定登录 + let loginIPLimit = userRecord.login_ip_limit || [] + // 清理无用记录 + loginIPLimit = loginIPLimit.filter(item => item.last_error_time > Date.now() - passwordErrorRetryTime * 1000) + let currentIPLimit = loginIPLimit.find(item => item.ip === clientIP) + if (currentIPLimit && currentIPLimit.error_times >= passwordErrorLimit) { + throw { + errCode: ERROR.PASSWORD_ERROR_EXCEED_LIMIT + } + } + const passwordUtils = new PasswordUtils({ + userRecord, + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }) + + const { + success: checkPasswordSuccess, + refreshPasswordInfo + } = passwordUtils.checkUserPassword({ + password + }) + if (!checkPasswordSuccess) { + // 更新用户ip对应的密码错误记录 + if (!currentIPLimit) { + currentIPLimit = { + ip: clientIP, + error_times: 1, + last_error_time: Date.now() + } + loginIPLimit.push(currentIPLimit) + } else { + currentIPLimit.error_times++ + currentIPLimit.last_error_time = Date.now() + } + await userCollection.doc(userRecord._id).update({ + login_ip_limit: loginIPLimit + }) + throw { + errCode: ERROR.PASSWORD_ERROR + } + } + const extraData = {} + if (refreshPasswordInfo) { + extraData.password = refreshPasswordInfo.passwordHash + extraData.password_secret_version = refreshPasswordInfo.version + } + + const currentIPLimitIndex = loginIPLimit.indexOf(currentIPLimit) + if (currentIPLimitIndex > -1) { + loginIPLimit.splice(currentIPLimitIndex, 1) + } + extraData.login_ip_limit = loginIPLimit + return { + user: userRecord, + extraData + } + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + data: user, + type: LOG_TYPE.LOGIN + }) + throw error + } +} + +function checkLoginUserRecord (user) { + switch (user.status) { + case undefined: + case 0: + break + case 1: + throw { + errCode: ERROR.ACCOUNT_BANNED + } + case 2: + throw { + errCode: ERROR.ACCOUNT_AUDITING + } + case 3: + throw { + errCode: ERROR.ACCOUNT_AUDIT_FAILED + } + case 4: + throw { + errCode: ERROR.ACCOUNT_CLOSED + } + default: + break + } +} + +async function thirdPartyLogin (params = {}) { + const { + user + } = params + return { + mobileConfirmed: !!user.mobile_confirmed, + emailConfirmed: !!user.email_confirmed + } +} + +async function postLogin (params = {}) { + const { + user, + extraData, + isThirdParty = false + } = params + const { + clientIP + } = this.getUniversalClientInfo() + const uniIdToken = this.getUniversalUniIdToken() + const uid = user._id + const updateData = { + last_login_date: Date.now(), + last_login_ip: clientIP, + ...extraData + } + const createTokenRes = await this.uniIdCommon.createToken({ + uid + }) + + const { + errCode, + token, + tokenExpired + } = createTokenRes + if (errCode) { + throw createTokenRes + } + + if (uniIdToken) { + try { + await logout.call(this) + } catch (error) {} + } + + await userCollection.doc(uid).update(updateData) + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: LOG_TYPE.LOGIN + }) + return { + errCode: 0, + newToken: { + token, + tokenExpired + }, + uid, + ...( + isThirdParty + ? thirdPartyLogin({ + user + }) + : {} + ), + passwordConfirmed: !!user.password + } +} + +module.exports = { + preLogin, + postLogin, + checkLoginUserRecord, + preLoginWithPassword +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/logout.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/logout.js new file mode 100644 index 0000000000000000000000000000000000000000..9005cef2c24fe968df4d13d3abf2b05e104dc490 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/logout.js @@ -0,0 +1,49 @@ +const { + dbCmd, + LOG_TYPE, + deviceCollection, + userCollection +} = require('../../common/constants') + +async function logout () { + const { + deviceId + } = this.getUniversalClientInfo() + const uniIdToken = this.getUniversalUniIdToken() + const payload = await this.uniIdCommon.checkToken( + uniIdToken, + { + autoRefresh: false + } + ) + if (payload.errCode) { + throw payload + } + const uid = payload.uid + + // 删除token + await userCollection.doc(uid).update({ + token: dbCmd.pull(uniIdToken) + }) + + // 仅当device表的device_id和user_id均对应时才进行更新 + await deviceCollection.where({ + device_id: deviceId, + user_id: uid + }).update({ + token_expired: 0 + }) + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: LOG_TYPE.LOGOUT + }) + return { + errCode: 0 + } +} + +module.exports = { + logout +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/password.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/password.js new file mode 100644 index 0000000000000000000000000000000000000000..92294df35f077258a1b1b0eee78a82f202940694 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/password.js @@ -0,0 +1,261 @@ +const { + getType +} = require('../../common/utils') +const crypto = require('crypto') +const createConfig = require('uni-config-center') +const shareConfig = createConfig({ + pluginId: 'uni-id' +}) +let customPassword = {} +if (shareConfig.hasFile('custom-password.js')) { + customPassword = shareConfig.requireFile('custom-password.js') || {} +} + +const passwordAlgorithmMap = { + UNI_ID_HMAC_SHA1: 'hmac-sha1', + UNI_ID_HMAC_SHA256: 'hmac-sha256', + UNI_ID_CUSTOM: 'custom' +} + +const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => { + res[passwordAlgorithmMap[item]] = item + return res +}, {}) + +const passwordExtMethod = { + [passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: { + verify ({ password }) { + const { password_secret_version: passwordSecretVersion } = this.userRecord + + const passwordSecret = this._getSecretByVersion({ + version: passwordSecretVersion + }) + + const { passwordHash } = this.encrypt({ + password, + passwordSecret + }) + + return passwordHash === this.userRecord.password + }, + encrypt ({ password, passwordSecret }) { + const { value: secret, version } = passwordSecret + const hmac = crypto.createHmac('sha1', secret.toString('ascii')) + + hmac.update(password) + + return { + passwordHash: hmac.digest('hex'), + version + } + } + }, + [passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: { + verify ({ password }) { + const parse = this._parsePassword() + const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex') + + return passwordHash === parse.hash + }, + encrypt ({ password, passwordSecret }) { + const { version } = passwordSecret + + // 默认使用 sha256 加密算法 + const salt = crypto.randomBytes(10).toString('hex') + const sha256Hash = crypto.createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt).update(password).digest('hex') + const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256] + // B 为固定值,对应 PasswordMethodMaps 中的 sha256算法 + // hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash] + const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}` + + return { + passwordHash, + version + } + } + }, + [passwordAlgorithmMap.UNI_ID_CUSTOM]: { + verify ({ password, passwordSecret }) { + if (!customPassword.verifyPassword) throw new Error('verifyPassword method not found in custom password file') + + // return true or false + return customPassword.verifyPassword({ + password, + passwordSecret, + userRecord: this.userRecord, + clientInfo: this.clientInfo + }) + }, + encrypt ({ password, passwordSecret }) { + if (!customPassword.encryptPassword) throw new Error('encryptPassword method not found in custom password file') + + // return object<{passwordHash: string, version: number}> + return customPassword.encryptPassword({ + password, + passwordSecret, + clientInfo: this.clientInfo + }) + } + } +} + +class PasswordUtils { + constructor ({ + userRecord = {}, + clientInfo, + passwordSecret + } = {}) { + if (!clientInfo) throw new Error('Invalid clientInfo') + if (!passwordSecret) throw new Error('Invalid password secret') + + this.clientInfo = clientInfo + this.userRecord = userRecord + this.passwordSecret = this.prePasswordSecret(passwordSecret) + } + + /** + * passwordSecret 预处理 + * @param passwordSecret + * @return {*[]} + */ + prePasswordSecret (passwordSecret) { + const newPasswordSecret = [] + if (getType(passwordSecret) === 'string') { + newPasswordSecret.push({ + value: passwordSecret, + type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1 + }) + } else if (getType(passwordSecret) === 'array') { + for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) { + newPasswordSecret.push({ + ...secret, + // 没有 type 设置默认 type hmac-sha1 + type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1 + }) + } + } else { + throw new Error('Invalid password secret') + } + + return newPasswordSecret + } + + /** + * 获取最新加密密钥 + * @return {*} + * @private + */ + _getLastestSecret () { + return this.passwordSecret[this.passwordSecret.length - 1] + } + + _getOldestSecret () { + return this.passwordSecret[0] + } + + _getSecretByVersion ({ version } = {}) { + if (!version && version !== 0) { + return this._getOldestSecret() + } + if (this.passwordSecret.length === 1) { + return this.passwordSecret[0] + } + return this.passwordSecret.find(item => item.version === version) + } + + /** + * 获取密码的验证/加密方法 + * @param passwordSecret + * @return {*[]} + * @private + */ + _getPasswordExt (passwordSecret) { + const ext = passwordExtMethod[passwordSecret.type] + if (!ext) { + throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`) + } + + const passwordExt = Object.create(null) + + for (const key in ext) { + passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => { + if (item !== key) { + res[item] = ext[item].bind(this) + } + return res + }, {}))) + } + + return passwordExt + } + + _parsePassword () { + const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key) + const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null + const salt = hashStr.substring(0, Number(cost)) + const hash = hashStr.substring(Number(cost)) + + return { + algorithm, + salt, + hash + } + } + + /** + * 生成加密后的密码 + * @param {String} password 密码 + */ + generatePasswordHash ({ password }) { + if (!password) throw new Error('Invalid password') + + const passwordSecret = this._getLastestSecret() + const ext = this._getPasswordExt(passwordSecret) + + const { passwordHash, version } = ext.encrypt({ + password, + passwordSecret + }) + + return { + passwordHash, + version + } + } + + /** + * 密码校验 + * @param {String} password + * @param {Boolean} autoRefresh + * @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}} + */ + checkUserPassword ({ password, autoRefresh = true }) { + if (!password) throw new Error('Invalid password') + + const { password_secret_version: passwordSecretVersion } = this.userRecord + const passwordSecret = this._getSecretByVersion({ + version: passwordSecretVersion + }) + const ext = this._getPasswordExt(passwordSecret) + + const success = ext.verify({ password, passwordSecret }) + + if (!success) { + return { + success: false + } + } + + let refreshPasswordInfo + if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) { + refreshPasswordInfo = this.generatePasswordHash({ password }) + } + + return { + success: true, + refreshPasswordInfo + } + } +} + +module.exports = PasswordUtils diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/qq.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/qq.js new file mode 100644 index 0000000000000000000000000000000000000000..18f5b559574e1e8d24ee41c66e443c80e4fbb890 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/qq.js @@ -0,0 +1,152 @@ +const { + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +function getQQPlatform () { + const platform = this.clientPlatform + switch (platform) { + case 'app': + case 'app-plus': + return 'app' + case 'mp-qq': + return 'mp' + default: + throw new Error('Unsupported qq platform') + } +} + +async function saveQQUserKey ({ + openid, + sessionKey, // QQ小程序用户sessionKey + accessToken, // App端QQ用户accessToken + accessTokenExpired // App端QQ用户accessToken过期时间 +} = {}) { + // 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。 + // 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4) + const appId = this.getUniversalClientInfo().appId + const qqPlatform = getQQPlatform.call(this) + const keyObj = { + dcloudAppid: appId, + openid, + platform: 'qq-' + qqPlatform + } + switch (qqPlatform) { + case 'mp': + await this.uniOpenBridge.setSessionKey(keyObj, { + session_key: sessionKey + }, 30 * 24 * 60 * 60) + break + case 'app': + case 'h5': + case 'web': + await this.uniOpenBridge.setUserAccessToken(keyObj, { + access_token: accessToken, + access_token_expired: accessTokenExpired + }, accessTokenExpired + ? Math.floor((accessTokenExpired - Date.now()) / 1000) + : 30 * 24 * 60 * 60 + ) + break + default: + break + } +} + +function generateQQCache ({ + sessionKey, // QQ小程序用户sessionKey + accessToken, // App端QQ用户accessToken + accessTokenExpired // App端QQ用户accessToken过期时间 +} = {}) { + const platform = getQQPlatform.call(this) + let cache + switch (platform) { + case 'app': + cache = { + access_token: accessToken, + access_token_expired: accessTokenExpired + } + break + case 'mp': + cache = { + session_key: sessionKey + } + break + default: + throw new Error('Unsupported qq platform') + } + return { + third_party: { + [`${platform}_qq`]: cache + } + } +} + +function getQQOpenid ({ + userRecord +} = {}) { + const qqPlatform = getQQPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + const qqOpenidObj = userRecord.qq_openid + if (!qqOpenidObj) { + return + } + return qqOpenidObj[`${qqPlatform}_${appId}`] || qqOpenidObj[qqPlatform] +} + +async function getQQCacheFallback ({ + userRecord, + key +} = {}) { + const platform = getQQPlatform.call(this) + const thirdParty = userRecord && userRecord.third_party + if (!thirdParty) { + return + } + const qqCache = thirdParty[`${platform}_qq`] + return qqCache && qqCache[key] +} + +async function getQQCache ({ + uid, + userRecord, + key +} = {}) { + const qqPlatform = getQQPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + + if (!userRecord) { + const getUserRes = await userCollection.doc(uid).get() + userRecord = getUserRes.data[0] + } + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + const openid = getQQOpenid.call(this, { + userRecord + }) + const getCacheMethod = qqPlatform === 'mp' ? 'getSessionKey' : 'getUserAccessToken' + const userKey = await this.uniOpenBridge[getCacheMethod]({ + dcloudAppid: appId, + platform: 'qq-' + qqPlatform, + openid + }) + if (userKey) { + return userKey[key] + } + return getQQCacheFallback({ + userRecord, + key + }) +} + +module.exports = { + getQQPlatform, + generateQQCache, + getQQCache, + saveQQUserKey +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/register.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/register.js new file mode 100644 index 0000000000000000000000000000000000000000..9d1a63ed09e73af3c4e7c91419584c3d4bc9aac9 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/register.js @@ -0,0 +1,229 @@ +const { + userCollection, + LOG_TYPE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + findUser +} = require('./account') +const { + getValidInviteCode, + generateInviteInfo +} = require('./fission') +const { + logout +} = require('./logout') +const PasswordUtils = require('./password') +const merge = require('lodash.merge') + +async function realPreRegister (params = {}) { + const { + user + } = params + const { + userMatched + } = await findUser({ + userQuery: user, + authorizedApp: this.getUniversalClientInfo().appId + }) + if (userMatched.length > 0) { + throw { + errCode: ERROR.ACCOUNT_EXISTS + } + } +} + +async function preRegister (params = {}) { + try { + await realPreRegister.call(this, params) + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.REGISTER + }) + throw error + } +} + +async function preRegisterWithPassword (params = {}) { + const { + user, + password + } = params + await preRegister.call(this, { + user + }) + const passwordUtils = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }) + const { + passwordHash, + version + } = passwordUtils.generatePasswordHash({ + password + }) + const extraData = { + password: passwordHash, + password_secret_version: version + } + return { + user, + extraData + } +} + +async function thirdPartyRegister ({ + user = {} +} = {}) { + return { + mobileConfirmed: !!(user.mobile && user.mobile_confirmed) || false, + emailConfirmed: !!(user.email && user.email_confirmed) || false + } +} + +async function postRegister (params = {}) { + const { + user, + extraData = {}, + isThirdParty = false, + inviteCode + } = params + const { + appId, + appName, + appVersion, + appVersionCode, + channel, + scene, + clientIP, + osName + } = this.getUniversalClientInfo() + const uniIdToken = this.getUniversalUniIdToken() + + merge(user, extraData) + + const registerChannel = channel || scene + user.register_env = { + appid: appId || '', + uni_platform: this.clientPlatform || '', + os_name: osName || '', + app_name: appName || '', + app_version: appVersion || '', + app_version_code: appVersionCode || '', + channel: registerChannel ? registerChannel + '' : '', // channel可能为数字,统一存为字符串 + client_ip: clientIP || '' + } + + user.register_date = Date.now() + user.dcloud_appid = [appId] + + if (user.username) { + user.username = user.username.toLowerCase() + } + if (user.email) { + user.email = user.email.toLowerCase() + } + + const { + autoSetInviteCode, // 注册时自动设置邀请码 + forceInviteCode, // 必须有邀请码才允许注册,注意此逻辑不可对admin生效 + userRegisterDefaultRole // 用户注册时配置的默认角色 + } = this.config + if (autoSetInviteCode) { + user.my_invite_code = await getValidInviteCode() + } + + // 如果用户注册默认角色配置存在且不为空数组 + if (userRegisterDefaultRole && userRegisterDefaultRole.length) { + // 将用户已有的角色和配置的默认角色合并成一个数组,并去重 + user.role = Array.from(new Set([...(user.role || []), ...userRegisterDefaultRole])) + } + + const isAdmin = user.role && user.role.includes('admin') + + if (forceInviteCode && !isAdmin && !inviteCode) { + throw { + errCode: ERROR.INVALID_INVITE_CODE + } + } + + if (inviteCode) { + const { + inviterUid, + inviteTime + } = await generateInviteInfo({ + inviteCode + }) + user.inviter_uid = inviterUid + user.invite_time = inviteTime + } + + if (uniIdToken) { + try { + await logout.call(this) + } catch (error) { } + } + + const beforeRegister = this.hooks.beforeRegister + let userRecord = user + if (beforeRegister) { + userRecord = await beforeRegister({ + userRecord, + clientInfo: this.getUniversalClientInfo() + }) + } + + const { + id: uid + } = await userCollection.add(userRecord) + + const createTokenRes = await this.uniIdCommon.createToken({ + uid + }) + + const { + errCode, + token, + tokenExpired + } = createTokenRes + + if (errCode) { + throw createTokenRes + } + + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: LOG_TYPE.REGISTER + }) + + return { + errCode: 0, + uid, + newToken: { + token, + tokenExpired + }, + ...( + isThirdParty + ? thirdPartyRegister({ + user: { + ...userRecord, + _id: uid + } + }) + : {} + ), + passwordConfirmed: !!userRecord.password + } +} + +module.exports = { + preRegister, + preRegisterWithPassword, + postRegister +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/relate.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/relate.js new file mode 100644 index 0000000000000000000000000000000000000000..77791f585d16b80840aec3d3f50dcf4e82ae8bea --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/relate.js @@ -0,0 +1,164 @@ +const { + findUser +} = require('./account') +const { + ERROR +} = require('../../common/error') +const { + userCollection, dbCmd, USER_IDENTIFIER +} = require('../../common/constants') +const { + getUserIdentifier +} = require('../../lib/utils/account') + +const { + batchFindObjctValue +} = require('../../common/utils') +const merge = require('lodash.merge') + +/** + * + * @param {object} param + * @param {string} param.uid 用户id + * @param {string} param.bindAccount 要绑定的三方账户、手机号或邮箱 + */ +async function preBind ({ + uid, + bindAccount, + logType +} = {}) { + const { + userMatched + } = await findUser({ + userQuery: bindAccount, + authorizedApp: this.getUniversalClientInfo().appId + }) + if (userMatched.length > 0) { + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: logType, + success: false + }) + throw { + errCode: ERROR.BIND_CONFLICT + } + } +} + +async function postBind ({ + uid, + extraData = {}, + bindAccount, + logType +} = {}) { + await userCollection.doc(uid).update(merge(bindAccount, extraData)) + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: logType + }) + return { + errCode: 0 + } +} + +async function preUnBind ({ + uid, + unBindAccount, + logType +}) { + const notUnBind = ['username', 'mobile', 'email'] + const userIdentifier = getUserIdentifier(unBindAccount) + const condition = Object.keys(userIdentifier).reduce((res, key) => { + if (userIdentifier[key]) { + if (notUnBind.includes(key)) { + throw { + errCode: ERROR.UNBIND_NOT_SUPPORTED + } + } + + res.push({ + [key]: userIdentifier[key] + }) + } + + return res + }, []) + const currentUnBindAccount = Object.keys(userIdentifier).reduce((res, key) => { + if (userIdentifier[key]) { + res.push(key) + } + return res + }, []) + const { data: users } = await userCollection.where(dbCmd.and( + { _id: uid }, + dbCmd.or(condition) + )).get() + + if (users.length <= 0) { + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: logType, + success: false + }) + throw { + errCode: ERROR.UNBIND_FAIL + } + } + + const [user] = users + const otherAccounts = batchFindObjctValue(user, Object.keys(USER_IDENTIFIER).filter(key => !notUnBind.includes(key) && !currentUnBindAccount.includes(key))) + let hasOtherAccountBind = false + + for (const key in otherAccounts) { + if (otherAccounts[key]) { + hasOtherAccountBind = true + break + } + } + + // 如果没有其他第三方登录方式 + if (!hasOtherAccountBind) { + // 存在用户名或者邮箱但是没有设置过没密码就提示设置密码 + if ((user.username || user.email) && !user.password) { + throw { + errCode: ERROR.UNBIND_PASSWORD_NOT_EXISTS + } + } + // 账号任何登录方式都没有就优先绑定手机号 + if (!user.mobile) { + throw { + errCode: ERROR.UNBIND_MOBILE_NOT_EXISTS + } + } + } +} + +async function postUnBind ({ + uid, + unBindAccount, + logType +}) { + await userCollection.doc(uid).update(unBindAccount) + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: logType + }) + return { + errCode: 0 + } +} + +module.exports = { + preBind, + postBind, + preUnBind, + postUnBind +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/sms.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/sms.js new file mode 100644 index 0000000000000000000000000000000000000000..7907cb349a0888fa53ac44ba6db9caf17b336dbd --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/sms.js @@ -0,0 +1,79 @@ +const { + setMobileVerifyCode +} = require('./verify-code') +const { + getVerifyCode +} = require('../../common/utils') + +/** + * 发送短信 + * @param {object} param + * @param {string} param.mobile 手机号 + * @param {object} param.code 可选,验证码 + * @param {object} param.scene 短信场景 + * @param {object} param.templateId 可选,短信模板id + * @returns + */ +async function sendSmsCode ({ + mobile, + code, + scene, + templateId +} = {}) { + const requiredParams = [ + 'name', + 'codeExpiresIn' + ] + const smsConfig = (this.config.service && this.config.service.sms) || {} + for (let i = 0; i < requiredParams.length; i++) { + const key = requiredParams[i] + if (!smsConfig[key]) { + throw new Error(`Missing config param: service.sms.${key}`) + } + } + if (!code) { + code = getVerifyCode() + } + let action + switch (scene) { + case 'login-by-sms': + action = this.t('login') + break + default: + action = this.t('verify-mobile') + break + } + const sceneConfig = (smsConfig.scene || {})[scene] || {} + if (!templateId) { + templateId = sceneConfig.templateId + } + if (!templateId) { + throw new Error('"templateId" is required') + } + const codeExpiresIn = sceneConfig.codeExpiresIn || smsConfig.codeExpiresIn + await setMobileVerifyCode.call(this, { + mobile, + code, + expiresIn: codeExpiresIn, + scene + }) + await uniCloud.sendSms({ + smsKey: smsConfig.smsKey, + smsSecret: smsConfig.smsSecret, + phone: mobile, + templateId, + data: { + name: smsConfig.name, + code, + action, + expMinute: '' + Math.round(codeExpiresIn / 60) + } + }) + return { + errCode: 0 + } +} + +module.exports = { + sendSmsCode +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/unified-login.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/unified-login.js new file mode 100644 index 0000000000000000000000000000000000000000..b974e06f06ff6b34ea9a92bfdefec2265c5b106b --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/unified-login.js @@ -0,0 +1,106 @@ +const { + checkLoginUserRecord, + postLogin +} = require('./login') +const { + postRegister +} = require('./register') +const { + findUser +} = require('./account') +const { + ERROR +} = require('../../common/error') + +async function realPreUnifiedLogin (params = {}) { + const { + user, + type + } = params + const appId = this.getUniversalClientInfo().appId + const { + total, + userMatched + } = await findUser({ + userQuery: user, + authorizedApp: appId + }) + if (userMatched.length === 0) { + if (type === 'login') { + if (total > 0) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP + } + } + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + return { + type: 'register', + user + } + } if (userMatched.length === 1) { + if (type === 'register') { + throw { + errCode: ERROR.ACCOUNT_EXISTS + } + } + const userRecord = userMatched[0] + checkLoginUserRecord(userRecord) + return { + type: 'login', + user: userRecord + } + } else if (userMatched.length > 1) { + throw { + errCode: ERROR.ACCOUNT_CONFLICT + } + } +} + +async function preUnifiedLogin (params = {}) { + try { + const result = await realPreUnifiedLogin.call(this, params) + return result + } catch (error) { + await this.middleware.uniIdLog({ + success: false + }) + throw error + } +} + +async function postUnifiedLogin (params = {}) { + const { + user, + extraData = {}, + isThirdParty = false, + type, + inviteCode + } = params + let result + if (type === 'login') { + result = await postLogin.call(this, { + user, + extraData, + isThirdParty + }) + } else if (type === 'register') { + result = await postRegister.call(this, { + user, + extraData, + isThirdParty, + inviteCode + }) + } + return { + ...result, + type + } +} + +module.exports = { + preUnifiedLogin, + postUnifiedLogin +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/univerify.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/univerify.js new file mode 100644 index 0000000000000000000000000000000000000000..7625b8f3c8572ece1acc593d0553c498d6a37a6c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/univerify.js @@ -0,0 +1,27 @@ +async function getPhoneNumber ({ + // eslint-disable-next-line camelcase + access_token, + openid +} = {}) { + const requiredParams = [] + const univerifyConfig = (this.config.service && this.config.service.univerify) || {} + for (let i = 0; i < requiredParams.length; i++) { + const key = requiredParams[i] + if (!univerifyConfig[key]) { + throw new Error(`Missing config param: service.univerify.${key}`) + } + } + return uniCloud.getPhoneNumber({ + provider: 'univerify', + appid: this.getUniversalClientInfo().appId, + apiKey: univerifyConfig.apiKey, + apiSecret: univerifyConfig.apiSecret, + // eslint-disable-next-line camelcase + access_token, + openid + }) +} + +module.exports = { + getPhoneNumber +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/update-user-info.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/update-user-info.js new file mode 100644 index 0000000000000000000000000000000000000000..c2ebb1b4dac7cd4cd39273b478452d2680104ec6 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/update-user-info.js @@ -0,0 +1,25 @@ +const { + userCollection +} = require('../../common/constants') +const { + USER_STATUS +} = require('../../common/constants') +async function setUserStatus (uid, status) { + const updateData = { + status + } + if (status !== USER_STATUS.NORMAL) { + updateData.valid_token_date = Date.now() + } + await userCollection.doc(uid).update({ + status + }) + // TODO 此接口尚不完善,例如注销后其他客户端可能存在有效token,支持Redis后此处会补充额外逻辑 + return { + errCode: 0 + } +} + +module.exports = { + setUserStatus +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/utils.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..f00ca0a05920d6bfa60db11045a337550362ee50 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/utils.js @@ -0,0 +1,18 @@ +let redisEnable = null +function getRedisEnable() { + // 未用到的时候不调用redis接口,节省一些连接数 + if (redisEnable !== null) { + return redisEnable + } + try { + uniCloud.redis() + redisEnable = true + } catch (error) { + redisEnable = false + } + return redisEnable +} + +module.exports = { + getRedisEnable +} \ No newline at end of file diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/verify-code.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/verify-code.js new file mode 100644 index 0000000000000000000000000000000000000000..31ba23614cc927b57edcdd633784515e36542394 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/verify-code.js @@ -0,0 +1,152 @@ +const { + dbCmd, + verifyCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + getVerifyCode +} = require('../../common/utils') + +async function setVerifyCode ({ + mobile, + email, + code, + expiresIn, + scene +} = {}) { + const now = Date.now() + const record = { + mobile, + email, + scene, + code: code || getVerifyCode(), + state: 0, + ip: this.getUniversalClientInfo().clientIP, + created_date: now, + expired_date: now + expiresIn * 1000 + } + await verifyCollection.add(record) + return { + errCode: 0 + } +} + +async function setEmailVerifyCode ({ + email, + code, + expiresIn, + scene +} = {}) { + email = email && email.trim() + if (!email) { + throw { + errCode: ERROR.INVALID_EMAIL + } + } + email = email.toLowerCase() + return setVerifyCode.call(this, { + email, + code, + expiresIn, + scene + }) +} + +async function setMobileVerifyCode ({ + mobile, + code, + expiresIn, + scene +} = {}) { + mobile = mobile && mobile.trim() + if (!mobile) { + throw { + errCode: ERROR.INVALID_MOBILE + } + } + return setVerifyCode.call(this, { + mobile, + code, + expiresIn, + scene + }) +} + +async function verifyEmailCode ({ + email, + code, + scene +} = {}) { + email = email && email.trim() + if (!email) { + throw { + errCode: ERROR.INVALID_EMAIL + } + } + email = email.toLowerCase() + const { + data: codeRecord + } = await verifyCollection.where({ + email, + scene, + code, + state: 0, + expired_date: dbCmd.gt(Date.now()) + }).limit(1).get() + + if (codeRecord.length === 0) { + throw { + errCode: ERROR.EMAIL_VERIFY_CODE_ERROR + } + } + await verifyCollection.doc(codeRecord[0]._id).update({ + state: 1 + }) + return { + errCode: 0 + } +} + +async function verifyMobileCode ({ + mobile, + code, + scene +} = {}) { + mobile = mobile && mobile.trim() + if (!mobile) { + throw { + errCode: ERROR.INVALID_MOBILE + } + } + const { + data: codeRecord + } = await verifyCollection.where({ + mobile, + scene, + code, + state: 0, + expired_date: dbCmd.gt(Date.now()) + }).limit(1).get() + + if (codeRecord.length === 0) { + throw { + errCode: ERROR.MOBILE_VERIFY_CODE_ERROR + } + } + + await verifyCollection.doc(codeRecord[0]._id).update({ + state: 1 + }) + return { + errCode: 0 + } +} + +module.exports = { + verifyEmailCode, + verifyMobileCode, + setEmailVerifyCode, + setMobileVerifyCode +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/weixin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/weixin.js new file mode 100644 index 0000000000000000000000000000000000000000..f6cc28eea51e0ad7e1e6268ede21243f5a2c89a6 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/lib/utils/weixin.js @@ -0,0 +1,234 @@ +const crypto = require('crypto') +const { + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + getRedisEnable +} = require('./utils') +const { + openDataCollection +} = require('../../common/constants') + +function decryptWeixinData ({ + encryptedData, + sessionKey, + iv +} = {}) { + const oauthConfig = this.configUtils.getOauthConfig({ + provider: 'weixin' + }) + const decipher = crypto.createDecipheriv( + 'aes-128-cbc', + Buffer.from(sessionKey, 'base64'), + Buffer.from(iv, 'base64') + ) + // 设置自动 padding 为 true,删除填充补位 + decipher.setAutoPadding(true) + let decoded + decoded = decipher.update(encryptedData, 'base64', 'utf8') + decoded += decipher.final('utf8') + decoded = JSON.parse(decoded) + if (decoded.watermark.appid !== oauthConfig.appid) { + throw new Error('Invalid wechat appid in decode content') + } + return decoded +} + +function getWeixinPlatform () { + const platform = this.clientPlatform + const userAgent = this.getUniversalClientInfo().userAgent + switch (platform) { + case 'app': + case 'app-plus': + return 'app' + case 'mp-weixin': + return 'mp' + case 'h5': + case 'web': + return userAgent.indexOf('MicroMessenger') > -1 ? 'h5' : 'web' + default: + throw new Error('Unsupported weixin platform') + } +} + +async function saveWeixinUserKey ({ + openid, + sessionKey, // 微信小程序用户sessionKey + accessToken, // App端微信用户accessToken + refreshToken, // App端微信用户refreshToken + accessTokenExpired // App端微信用户accessToken过期时间 +} = {}) { + // 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。 + // 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4) + + const appId = this.getUniversalClientInfo().appId + const weixinPlatform = getWeixinPlatform.call(this) + const keyObj = { + dcloudAppid: appId, + openid, + platform: 'weixin-' + weixinPlatform + } + switch (weixinPlatform) { + case 'mp': + await this.uniOpenBridge.setSessionKey(keyObj, { + session_key: sessionKey + }, 30 * 24 * 60 * 60) + break + case 'app': + case 'h5': + case 'web': + await this.uniOpenBridge.setUserAccessToken(keyObj, { + access_token: accessToken, + refresh_token: refreshToken, + access_token_expired: accessTokenExpired + }, 30 * 24 * 60 * 60) + break + default: + break + } +} + +async function saveSecureNetworkCache ({ + code, + openid, + unionid, + sessionKey +}) { + const { + appId + } = this.getUniversalClientInfo() + const key = `uni-id:${appId}:weixin-mp:code:${code}:secure-network-cache` + const value = JSON.stringify({ + openid, + unionid, + session_key: sessionKey + }) + // 此处存储的是code的缓存,设置有效期和token一致 + const expiredSeconds = this.config.tokenExpiresIn || 3 * 24 * 60 * 60 + + await openDataCollection.doc(key).set({ + value, + expired: Date.now() + expiredSeconds * 1000 + }) + const isRedisEnable = getRedisEnable() + if (isRedisEnable) { + const redis = uniCloud.redis() + await redis.set(key, value, 'EX', expiredSeconds) + } +} + +function generateWeixinCache ({ + sessionKey, // 微信小程序用户sessionKey + accessToken, // App端微信用户accessToken + refreshToken, // App端微信用户refreshToken + accessTokenExpired // App端微信用户accessToken过期时间 +} = {}) { + const platform = getWeixinPlatform.call(this) + let cache + switch (platform) { + case 'app': + case 'h5': + case 'web': + cache = { + access_token: accessToken, + refresh_token: refreshToken, + access_token_expired: accessTokenExpired + } + break + case 'mp': + cache = { + session_key: sessionKey + } + break + default: + throw new Error('Unsupported weixin platform') + } + return { + third_party: { + [`${platform}_weixin`]: cache + } + } +} + +function getWeixinOpenid ({ + userRecord +} = {}) { + const weixinPlatform = getWeixinPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + const wxOpenidObj = userRecord.wx_openid + if (!wxOpenidObj) { + return + } + return wxOpenidObj[`${weixinPlatform}_${appId}`] || wxOpenidObj[weixinPlatform] +} + +async function getWeixinCacheFallback ({ + userRecord, + key +} = {}) { + const platform = getWeixinPlatform.call(this) + const thirdParty = userRecord && userRecord.third_party + if (!thirdParty) { + return + } + const weixinCache = thirdParty[`${platform}_weixin`] + return weixinCache && weixinCache[key] +} + +async function getWeixinCache ({ + uid, + userRecord, + key +} = {}) { + const weixinPlatform = getWeixinPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + if (!userRecord) { + const getUserRes = await userCollection.doc(uid).get() + userRecord = getUserRes.data[0] + } + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + const openid = getWeixinOpenid.call(this, { + userRecord + }) + const getCacheMethod = weixinPlatform === 'mp' ? 'getSessionKey' : 'getUserAccessToken' + const userKey = await this.uniOpenBridge[getCacheMethod]({ + dcloudAppid: appId, + platform: 'weixin-' + weixinPlatform, + openid + }) + if (userKey) { + return userKey[key] + } + return getWeixinCacheFallback({ + userRecord, + key + }) +} + +async function getWeixinAccessToken () { + const weixinPlatform = getWeixinPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + + const cache = await this.uniOpenBridge.getAccessToken({ + dcloudAppid: appId, + platform: 'weixin-' + weixinPlatform + }) + + return cache.access_token +} +module.exports = { + decryptWeixinData, + getWeixinPlatform, + generateWeixinCache, + getWeixinCache, + saveWeixinUserKey, + getWeixinAccessToken, + saveSecureNetworkCache +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/access-control.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/access-control.js new file mode 100644 index 0000000000000000000000000000000000000000..6041ffa6aca936c506ae26c142247108d21ea572 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/access-control.js @@ -0,0 +1,59 @@ +const methodPermission = require('../config/permission') +const { + ERROR +} = require('../common/error') + +function isAccessAllowed (user, setting) { + const { + role: userRole = [], + permission: userPermission = [] + } = user + const { + role: settingRole = [], + permission: settingPermission = [] + } = setting + if (userRole.includes('admin')) { + return + } + if ( + settingRole.length > 0 && + settingRole.every(item => !userRole.includes(item)) + ) { + throw { + errCode: ERROR.PERMISSION_ERROR + } + } + if ( + settingPermission.length > 0 && + settingPermission.every(item => !userPermission.includes(item)) + ) { + throw { + errCode: ERROR.PERMISSION_ERROR + } + } +} + +module.exports = async function () { + const methodName = this.getMethodName() + if (!(methodName in methodPermission)) { + return + } + const { + auth, + role, + permission + } = methodPermission[methodName] + if (auth || role || permission) { + await this.middleware.auth() + } + if (role && role.length === 0) { + throw new Error('[AccessControl]Empty role array is not supported') + } + if (permission && permission.length === 0) { + throw new Error('[AccessControl]Empty permission array is not supported') + } + return isAccessAllowed(this.authInfo, { + role, + permission + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/auth.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..a111ee3b2e091564cd0d522c1ea7bf8930217e99 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/auth.js @@ -0,0 +1,17 @@ +module.exports = async function () { + if (this.authInfo) { // 多次执行auth时如果第一次成功后续不再执行 + return + } + const token = this.getUniversalUniIdToken() + const payload = await this.uniIdCommon.checkToken(token) + if (payload.errCode) { + throw payload + } + this.authInfo = payload + if (payload.token) { + this.response.newToken = { + token: payload.token, + tokenExpired: payload.tokenExpired + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/index.js new file mode 100644 index 0000000000000000000000000000000000000000..5be100091761ff3989d2140faf5d8cfa1799887c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/index.js @@ -0,0 +1,8 @@ +module.exports = { + auth: require('./auth'), + uniIdLog: require('./uni-id-log'), + validate: require('./validate'), + accessControl: require('./access-control'), + verifyRequestSign: require('./verify-request-sign'), + ...require('./rbac') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/rbac.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/rbac.js new file mode 100644 index 0000000000000000000000000000000000000000..32fe0f8097b9e528eb4ad304747cd68a95ebb1ac --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/rbac.js @@ -0,0 +1,39 @@ +const { + ERROR +} = require('../common/error') + +function hasRole (...roleList) { + const userRole = this.authInfo.role || [] + if (userRole.includes('admin')) { + return + } + const isMatch = roleList.every(roleItem => { + return userRole.includes(roleItem) + }) + if (!isMatch) { + throw { + errCode: ERROR.PERMISSION_ERROR + } + } +} + +function hasPermission (...permissionList) { + const userRole = this.authInfo.role || [] + const userPermission = this.authInfo.permission || [] + if (userRole.includes('admin')) { + return + } + const isMatch = permissionList.every(permissionItem => { + return userPermission.includes(permissionItem) + }) + if (!isMatch) { + throw { + errCode: ERROR.PERMISSION_ERROR + } + } +} + +module.exports = { + hasRole, + hasPermission +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/uni-id-log.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/uni-id-log.js new file mode 100644 index 0000000000000000000000000000000000000000..013f7ae6b0723422c0e66663b910ccbafbc0474b --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/uni-id-log.js @@ -0,0 +1,39 @@ +const db = uniCloud.database() +module.exports = async function ({ + data = {}, + success = true, + type = 'login' +} = {}) { + const now = Date.now() + const uniIdLogCollection = db.collection('uni-id-log') + const requiredDataKeyList = ['user_id', 'username', 'email', 'mobile'] + const dataCopy = {} + for (let i = 0; i < requiredDataKeyList.length; i++) { + const key = requiredDataKeyList[i] + if (key in data && typeof data[key] === 'string') { + dataCopy[key] = data[key] + } + } + const { + appId, + clientIP, + deviceId, + userAgent + } = this.getUniversalClientInfo() + const logData = { + appid: appId, + device_id: deviceId, + ip: clientIP, + type, + ua: userAgent, + create_date: now, + ...dataCopy + } + + if (success) { + logData.state = 1 + } else { + logData.state = 0 + } + return uniIdLogCollection.add(logData) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js new file mode 100644 index 0000000000000000000000000000000000000000..a58e8b473a6801d8dc943cccf30364f536b42872 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js @@ -0,0 +1,7 @@ +module.exports = function (value = {}, schema = {}) { + const validateRes = this.validator.validate(value, schema) + if (validateRes) { + delete validateRes.schemaKey + throw validateRes + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/verify-request-sign.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/verify-request-sign.js new file mode 100644 index 0000000000000000000000000000000000000000..30b973265372f119de018bbcc5ae2e4a453256d0 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/verify-request-sign.js @@ -0,0 +1,85 @@ +const crypto = require('crypto') +const createConfig = require('uni-config-center') +const { verifyHttpInfo } = require('uni-cloud-s2s') + +const { ERROR } = require('../common/error') +const s2sConfig = createConfig({ + pluginId: 'uni-cloud-s2s' +}) +const needSignFunctions = new Set([ + 'externalRegister', + 'externalLogin', + 'updateUserInfoByExternal' +]) + +module.exports = function () { + const methodName = this.getMethodName() + const { source } = this.getUniversalClientInfo() + + // 指定接口需要鉴权 + if (!needSignFunctions.has(methodName)) return + + // 非 HTTP 方式请求拒绝访问 + if (source !== 'http') { + throw { + errCode: ERROR.ILLEGAL_REQUEST + } + } + + // 支持 uni-cloud-s2s 验证请求 + if (s2sConfig.hasFile('config.json')) { + try { + if (!verifyHttpInfo(this.getHttpInfo())) { + throw { + errCode: ERROR.ILLEGAL_REQUEST + } + } + } catch (e) { + if (e.errSubject === 'uni-cloud-s2s') { + throw { + errCode: ERROR.ILLEGAL_REQUEST, + errMsg: e.errMsg + } + } + throw e + } + + return + } + + if (!this.config.requestAuthSecret || typeof this.config.requestAuthSecret !== 'string') { + throw { + errCode: ERROR.CONFIG_FIELD_REQUIRED, + errMsgValue: { + field: 'requestAuthSecret' + } + } + } + + const timeout = 20 * 1000 // 请求超过20秒不能再请求,防止重放攻击 + const { headers, body: _body } = this.getHttpInfo() + const { 'uni-id-nonce': nonce, 'uni-id-timestamp': timestamp, 'uni-id-signature': signature } = headers + const body = JSON.parse(_body).params || {} + const bodyStr = Object.keys(body) + .sort() + .filter(item => typeof body[item] !== 'object') + .map(item => `${item}=${body[item]}`) + .join('&') + + if (isNaN(Number(timestamp)) || (Number(timestamp) + timeout) < Date.now()) { + console.error('[timestamp error], timestamp:', timestamp, 'timeout:', timeout) + + throw { + errCode: ERROR.ILLEGAL_REQUEST + } + } + + const reSignature = crypto.createHmac('sha256', `${this.config.requestAuthSecret + nonce}`).update(`${timestamp}${bodyStr}`).digest('hex') + + if (signature !== reSignature.toUpperCase()) { + console.error('[signature error], signature:', signature, 'reSignature:', reSignature.toUpperCase(), 'requestAuthSecret:', this.config.requestAuthSecret) + throw { + errCode: ERROR.ILLEGAL_REQUEST + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/close-account.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/close-account.js new file mode 100644 index 0000000000000000000000000000000000000000..58fdb9d9930c5b395f9b98f8b88f81b74766d3d5 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/close-account.js @@ -0,0 +1,16 @@ +const { + setUserStatus +} = require('../../lib/utils/update-user-info') +const { + USER_STATUS +} = require('../../common/constants') + +/** + * 注销账户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account + * @returns + */ +module.exports = async function () { + const { uid } = this.authInfo + return setUserStatus(uid, USER_STATUS.CLOSED) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-account-info.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-account-info.js new file mode 100644 index 0000000000000000000000000000000000000000..b57fe00875567a68184011905e57de6261f73569 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-account-info.js @@ -0,0 +1,69 @@ +const { + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +function isUsernameSet (userRecord) { + return !!userRecord.username +} +function isNicknameSet (userRecord) { + return !!userRecord.nickname +} +function isPasswordSet (userRecord) { + return !!userRecord.password +} +function isMobileBound (userRecord) { + return !!(userRecord.mobile && userRecord.mobile_confirmed) +} +function isEmailBound (userRecord) { + return !!(userRecord.email && userRecord.email_confirmed) +} +function isWeixinBound (userRecord) { + return !!( + userRecord.wx_unionid || + Object.keys(userRecord.wx_openid || {}).length + ) +} +function isQQBound (userRecord) { + return !!( + userRecord.qq_unionid || + Object.keys(userRecord.qq_openid || {}).length + ) +} +function isAlipayBound (userRecord) { + return !!userRecord.ali_openid +} +function isAppleBound (userRecord) { + return !!userRecord.apple_openid +} + +/** + * 获取账户账户简略信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-account-info + */ +module.exports = async function () { + const { + uid + } = this.authInfo + const getUserRes = await userCollection.doc(uid).get() + const userRecord = getUserRes && getUserRes.data && getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + return { + errCode: 0, + isUsernameSet: isUsernameSet(userRecord), + isNicknameSet: isNicknameSet(userRecord), + isPasswordSet: isPasswordSet(userRecord), + isMobileBound: isMobileBound(userRecord), + isEmailBound: isEmailBound(userRecord), + isWeixinBound: isWeixinBound(userRecord), + isQQBound: isQQBound(userRecord), + isAlipayBound: isAlipayBound(userRecord), + isAppleBound: isAppleBound(userRecord) + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js new file mode 100644 index 0000000000000000000000000000000000000000..086f2f04f4fda8f5d7293aa998688aca05e68d7a --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/get-realname-info.js @@ -0,0 +1,45 @@ +const { userCollection } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { decryptData } = require('../../common/sensitive-aes-cipher') +const { dataDesensitization } = require('../../common/utils') + +/** + * 获取实名信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-realname-info + * @param {Object} params + * @param {Boolean} params.decryptData 是否解密数据 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + decryptData: { + required: false, + type: 'boolean' + } + } + + this.middleware.validate(params, schema) + + const { decryptData: isDecryptData = true } = params + + const { + uid + } = this.authInfo + const getUserRes = await userCollection.doc(uid).get() + const userRecord = getUserRes && getUserRes.data && getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + + const { realname_auth: realNameAuth = {} } = userRecord + + return { + errCode: 0, + type: realNameAuth.type, + authStatus: realNameAuth.auth_status, + realName: isDecryptData ? dataDesensitization(decryptData.call(this, realNameAuth.real_name), { onlyLast: true }) : realNameAuth.real_name, + identity: isDecryptData ? dataDesensitization(decryptData.call(this, realNameAuth.identity)) : realNameAuth.identity + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js new file mode 100644 index 0000000000000000000000000000000000000000..649d3428af0d47aa05a0cc4246b4ae2980e40239 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/index.js @@ -0,0 +1,9 @@ +module.exports = { + setPwd: require('./set-pwd'), + updatePwd: require('./update-pwd'), + resetPwdBySms: require('./reset-pwd-by-sms'), + resetPwdByEmail: require('./reset-pwd-by-email'), + closeAccount: require('./close-account'), + getAccountInfo: require('./get-account-info'), + getRealNameInfo: require('./get-realname-info') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js new file mode 100644 index 0000000000000000000000000000000000000000..eff026af6d0fa38f936fa551b5db31392657cadb --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-email.js @@ -0,0 +1,128 @@ +const { + ERROR +} = require('../../common/error') +const { + getNeedCaptcha, + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + verifyEmailCode +} = require('../../lib/utils/verify-code') +const { + userCollection, + EMAIL_SCENE, + CAPTCHA_SCENE, + LOG_TYPE +} = require('../../common/constants') +const { + findUser +} = require('../../lib/utils/account') +const PasswordUtils = require('../../lib/utils/password') + +/** + * 通过邮箱验证码重置密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-email + * @param {object} params + * @param {string} params.email 邮箱 + * @param {string} params.code 邮箱验证码 + * @param {string} params.password 密码 + * @param {string} params.captcha 图形验证码 + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + email: 'email', + code: 'string', + password: 'password', + captcha: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + email, + code, + password, + captcha + } = params + + const needCaptcha = await getNeedCaptcha.call(this, { + email, + type: LOG_TYPE.RESET_PWD_BY_EMAIL + }) + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.RESET_PWD_BY_EMAIL + }) + } + try { + // 验证手机号验证码,验证不通过时写入失败日志 + await verifyEmailCode({ + email, + code, + scene: EMAIL_SCENE.RESET_PWD_BY_EMAIL + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + email + }, + type: LOG_TYPE.RESET_PWD_BY_EMAIL, + success: false + }) + throw error + } + // 根据手机号查找匹配的用户 + const { + total, + userMatched + } = await findUser.call(this, { + userQuery: { + email + }, + authorizedApp: [this.getUniversalClientInfo().appId] + }) + if (userMatched.length === 0) { + if (total > 0) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP + } + } + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } else if (userMatched.length > 1) { + throw { + errCode: ERROR.ACCOUNT_CONFLICT + } + } + const { _id: uid } = userMatched[0] + const { + passwordHash, + version + } = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }).generatePasswordHash({ + password + }) + // 更新用户密码 + await userCollection.doc(uid).update({ + password: passwordHash, + password_secret_version: version, + valid_token_date: Date.now() + }) + + // 写入成功日志 + await this.middleware.uniIdLog({ + data: { + email + }, + type: LOG_TYPE.RESET_PWD_BY_SMS + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-sms.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-sms.js new file mode 100644 index 0000000000000000000000000000000000000000..1e3e5f67e1cbdfc465f5e8eceb40457a62480247 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/reset-pwd-by-sms.js @@ -0,0 +1,128 @@ +const { + ERROR +} = require('../../common/error') +const { + getNeedCaptcha, + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + verifyMobileCode +} = require('../../lib/utils/verify-code') +const { + userCollection, + SMS_SCENE, + CAPTCHA_SCENE, + LOG_TYPE +} = require('../../common/constants') +const { + findUser +} = require('../../lib/utils/account') +const PasswordUtils = require('../../lib/utils/password') + +/** + * 通过短信验证码重置密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-sms + * @param {object} params + * @param {string} params.mobile 手机号 + * @param {string} params.mobile 短信验证码 + * @param {string} params.password 密码 + * @param {string} params.captcha 图形验证码 + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + mobile: 'mobile', + code: 'string', + password: 'password', + captcha: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + mobile, + code, + password, + captcha + } = params + + const needCaptcha = await getNeedCaptcha.call(this, { + mobile, + type: LOG_TYPE.RESET_PWD_BY_SMS + }) + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.RESET_PWD_BY_SMS + }) + } + try { + // 验证手机号验证码,验证不通过时写入失败日志 + await verifyMobileCode({ + mobile, + code, + scene: SMS_SCENE.RESET_PWD_BY_SMS + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + mobile + }, + type: LOG_TYPE.RESET_PWD_BY_SMS, + success: false + }) + throw error + } + // 根据手机号查找匹配的用户 + const { + total, + userMatched + } = await findUser.call(this, { + userQuery: { + mobile + }, + authorizedApp: [this.getUniversalClientInfo().appId] + }) + if (userMatched.length === 0) { + if (total > 0) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS_IN_CURRENT_APP + } + } + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } else if (userMatched.length > 1) { + throw { + errCode: ERROR.ACCOUNT_CONFLICT + } + } + const { _id: uid } = userMatched[0] + const { + passwordHash, + version + } = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }).generatePasswordHash({ + password + }) + // 更新用户密码 + await userCollection.doc(uid).update({ + password: passwordHash, + password_secret_version: version, + valid_token_date: Date.now() + }) + + // 写入成功日志 + await this.middleware.uniIdLog({ + data: { + mobile + }, + type: LOG_TYPE.RESET_PWD_BY_SMS + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/set-pwd.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/set-pwd.js new file mode 100644 index 0000000000000000000000000000000000000000..1b484f3032fe637bc6b0797f9e812bf4e06ee7e3 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/set-pwd.js @@ -0,0 +1,83 @@ +const { userCollection, SMS_SCENE, LOG_TYPE, CAPTCHA_SCENE } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { verifyMobileCode } = require('../../lib/utils/verify-code') +const PasswordUtils = require('../../lib/utils/password') +const { getNeedCaptcha, verifyCaptcha } = require('../../lib/utils/captcha') + +module.exports = async function (params = {}) { + const schema = { + password: 'password', + code: 'string', + captcha: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + + const { password, code, captcha } = params + const uid = this.authInfo.uid + const getUserRes = await userCollection.doc(uid).get() + const userRecord = getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + + const needCaptcha = await getNeedCaptcha.call(this, { + mobile: userRecord.mobile + }) + + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.SET_PWD_BY_SMS + }) + } + + try { + // 验证手机号验证码,验证不通过时写入失败日志 + await verifyMobileCode({ + mobile: userRecord.mobile, + code, + scene: SMS_SCENE.SET_PWD_BY_SMS + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + mobile: userRecord.mobile + }, + type: LOG_TYPE.SET_PWD_BY_SMS, + success: false + }) + throw error + } + + const { + passwordHash, + version + } = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }).generatePasswordHash({ + password + }) + + // 更新用户密码 + await userCollection.doc(uid).update({ + password: passwordHash, + password_secret_version: version + }) + + await this.middleware.uniIdLog({ + data: { + mobile: userRecord.mobile + }, + type: LOG_TYPE.SET_PWD_BY_SMS + }) + + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/update-pwd.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/update-pwd.js new file mode 100644 index 0000000000000000000000000000000000000000..8a0271847158635ca16205731b795c8d2d71e1ad --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/account/update-pwd.js @@ -0,0 +1,69 @@ +const { + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const PasswordUtils = require('../../lib/utils/password') +/** + * 更新密码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-pwd + * @param {object} params + * @param {string} params.oldPassword 旧密码 + * @param {string} params.newPassword 新密码 + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + oldPassword: 'string', // 防止密码规则调整导致旧密码无法更新 + newPassword: 'password' + } + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + const getUserRes = await userCollection.doc(uid).get() + const userRecord = getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + const { + oldPassword, + newPassword + } = params + const passwordUtils = new PasswordUtils({ + userRecord, + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }) + + const { + success: checkPasswordSuccess + } = passwordUtils.checkUserPassword({ + password: oldPassword, + autoRefresh: false + }) + + if (!checkPasswordSuccess) { + throw { + errCode: ERROR.PASSWORD_ERROR + } + } + + const { + passwordHash, + version + } = passwordUtils.generatePasswordHash({ + password: newPassword + }) + + await userCollection.doc(uid).update({ + password: passwordHash, + password_secret_version: version, + valid_token_date: Date.now() // refreshToken时会校验,如果创建token时间在此时间点之前,则拒绝下发新token,返回token失效错误码 + }) + // 执行更新密码操作后客户端应将用户退出重新登录 + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/add-user.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/add-user.js new file mode 100644 index 0000000000000000000000000000000000000000..d13369a06eb261cb9a467cce5b7b0ce475e949de --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/add-user.js @@ -0,0 +1,131 @@ +const { + findUser +} = require('../../lib/utils/account') +const { + ERROR +} = require('../../common/error') +const { + userCollection +} = require('../../common/constants') +const PasswordUtils = require('../../lib/utils/password') + +/** + * 新增用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#add-user + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {Array} params.authorizedApp 允许登录的AppID列表 + * @param {Array} params.role 用户角色列表 + * @param {String} params.mobile 手机号 + * @param {String} params.email 邮箱 + * @param {Array} params.tags 用户标签 + * @param {Number} params.status 用户状态 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + username: 'username', + password: 'password', + authorizedApp: { + required: false, + type: 'array' + }, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录 + nickname: { + required: false, + type: 'nickname' + }, + role: { + require: false, + type: 'array' + }, + mobile: { + required: false, + type: 'mobile' + }, + email: { + required: false, + type: 'email' + }, + tags: { + required: false, + type: 'array' + }, + status: { + required: false, + type: 'number' + } + } + this.middleware.validate(params, schema) + const { + username, + password, + authorizedApp, + nickname, + role, + mobile, + email, + tags, + status + } = params + const { + userMatched + } = await findUser({ + userQuery: { + username, + mobile, + email + }, + authorizedApp + }) + if (userMatched.length) { + throw { + errCode: ERROR.ACCOUNT_EXISTS + } + } + const passwordUtils = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }) + const { + passwordHash, + version + } = passwordUtils.generatePasswordHash({ + password + }) + const data = { + username, + password: passwordHash, + password_secret_version: version, + dcloud_appid: authorizedApp || [], + nickname, + role: role || [], + mobile, + email, + tags: tags || [], + status + } + if (email) { + data.email_confirmed = 1 + } + if (mobile) { + data.mobile_confirmed = 1 + } + + // 触发 beforeRegister 钩子 + const beforeRegister = this.hooks.beforeRegister + let userRecord = data + if (beforeRegister) { + userRecord = await beforeRegister({ + userRecord, + clientInfo: this.getUniversalClientInfo() + }) + } + + await userCollection.add(userRecord) + return { + errCode: 0, + errMsg: '' + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6baff53690413a0b33e5fd8f3783c678d60cf212 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/index.js @@ -0,0 +1,4 @@ +module.exports = { + addUser: require('./add-user'), + updateUser: require('./update-user') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/update-user.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/update-user.js new file mode 100644 index 0000000000000000000000000000000000000000..cb427b1d1d3b5f223808d40905ff517304bab0bb --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/admin/update-user.js @@ -0,0 +1,138 @@ +const { + findUser +} = require('../../lib/utils/account') +const { + ERROR +} = require('../../common/error') +const { + userCollection +} = require('../../common/constants') +const PasswordUtils = require('../../lib/utils/password') + +/** + * 修改用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#update-user + * @param {Object} params + * @param {String} params.uid 要更新的用户id + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {Array} params.authorizedApp 允许登录的AppID列表 + * @param {Array} params.role 用户角色列表 + * @param {String} params.mobile 手机号 + * @param {String} params.email 邮箱 + * @param {Array} params.tags 用户标签 + * @param {Number} params.status 用户状态 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + uid: 'string', + username: 'username', + password: { + required: false, + type: 'password' + }, + authorizedApp: { + required: false, + type: 'array' + }, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录 + nickname: { + required: false, + type: 'nickname' + }, + role: { + require: false, + type: 'array' + }, + mobile: { + required: false, + type: 'mobile' + }, + email: { + required: false, + type: 'email' + }, + tags: { + required: false, + type: 'array' + }, + status: { + required: false, + type: 'number' + } + } + + this.middleware.validate(params, schema) + + const { + uid, + username, + password, + authorizedApp, + nickname, + role, + mobile, + email, + tags, + status + } = params + + // 更新的用户数据字段 + const data = { + username, + dcloud_appid: authorizedApp, + nickname, + role, + mobile, + email, + tags, + status + } + + const realData = Object.keys(data).reduce((res, key) => { + const item = data[key] + if (item !== undefined) { + res[key] = item + } + return res + }, {}) + + // 更新用户名时验证用户名是否重新 + if (username) { + const { + userMatched + } = await findUser({ + userQuery: { + username + }, + authorizedApp + }) + if (userMatched.filter(user => user._id !== uid).length) { + throw { + errCode: ERROR.ACCOUNT_EXISTS + } + } + } + if (password) { + const passwordUtils = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }) + const { + passwordHash, + version + } = passwordUtils.generatePasswordHash({ + password + }) + + realData.password = passwordHash + realData.password_secret_version = version + } + + await userCollection.doc(uid).update(realData) + + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/dev/get-supported-login-type.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/dev/get-supported-login-type.js new file mode 100644 index 0000000000000000000000000000000000000000..110139eee8534e4fea1aff9357de794c68750255 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/dev/get-supported-login-type.js @@ -0,0 +1,70 @@ +function isMobileCodeSupported () { + const config = this.config + return !!(config.service && config.service.sms && config.service.sms.smsKey) +} + +function isUniverifySupport () { + return true +} + +function isWeixinSupported () { + this.configUtils.getOauthConfig({ + provider: 'weixin' + }) + return true +} + +function isQQSupported () { + this.configUtils.getOauthConfig({ + provider: 'qq' + }) + return true +} + +function isAppleSupported () { + this.configUtils.getOauthConfig({ + provider: 'apple' + }) + return true +} + +function isAlipaySupported () { + this.configUtils.getOauthConfig({ + provider: 'alipay' + }) + return true +} + +const loginTypeTester = { + 'mobile-code': isMobileCodeSupported, + univerify: isUniverifySupport, + weixin: isWeixinSupported, + qq: isQQSupported, + apple: isAppleSupported, + alipay: isAlipaySupported +} + +/** + * 获取支持的登录方式 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-supported-login-type + * @returns + */ +module.exports = async function () { + const supportedLoginType = [ + 'username-password', + 'mobile-password', + 'email-password' + ] + for (const type in loginTypeTester) { + try { + if (loginTypeTester[type].call(this)) { + supportedLoginType.push(type) + } + } catch (error) { } + } + return { + errCode: 0, + errMsg: '', + supportedLoginType + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/dev/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/dev/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d6c28ccfc752a5b2099b4eb97009b875b46718c9 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/dev/index.js @@ -0,0 +1,3 @@ +module.exports = { + getSupportedLoginType: require('./get-supported-login-type') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/index.js new file mode 100644 index 0000000000000000000000000000000000000000..423bf35af96576655df3e0680b34d257e475a658 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/index.js @@ -0,0 +1,5 @@ +module.exports = { + externalRegister: require('./register'), + externalLogin: require('./login'), + updateUserInfoByExternal: require('./update-user-info') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/login.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/login.js new file mode 100644 index 0000000000000000000000000000000000000000..5d3a1788882536fdbca62328920ee18f3da16589 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/login.js @@ -0,0 +1,68 @@ +const { preLogin, postLogin } = require('../../lib/utils/login') +const { EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants') +const { ERROR } = require('../../common/error') + +/** + * 外部用户登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-login + * @param {object} params + * @param {string} params.uid uni-id体系用户id + * @param {string} params.externalUid 业务系统的用户id + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + uid: { + required: false, + type: 'string' + }, + externalUid: { + required: false, + type: 'string' + } + } + + this.middleware.validate(params, schema) + + const { + uid, + externalUid + } = params + + if (!uid && !externalUid) { + throw { + errCode: ERROR.PARAM_REQUIRED, + errMsgValue: { + param: 'uid or externalUid' + } + } + } + + let query + if (uid) { + query = { + _id: uid + } + } else { + query = { + identities: { + provider: EXTERNAL_DIRECT_CONNECT_PROVIDER, + uid: externalUid + } + } + } + + const user = await preLogin.call(this, { + user: query + }) + + const result = await postLogin.call(this, { + user + }) + + return { + errCode: result.errCode, + newToken: result.newToken, + uid: result.uid + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/register.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/register.js new file mode 100644 index 0000000000000000000000000000000000000000..d608719c5aba61c3a48bfa55451e60b83445d92f --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/register.js @@ -0,0 +1,93 @@ +const url = require('url') +const { preRegister, postRegister } = require('../../lib/utils/register') +const { EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants') + +/** + * 外部注册用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-register + * @param {object} params + * @param {string} params.externalUid 业务系统的用户id + * @param {string} params.nickname 昵称 + * @param {number} params.gender 性别 + * @param {string} params.avatar 头像 + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + externalUid: 'string', + nickname: { + required: false, + type: 'nickname' + }, + gender: { + required: false, + type: 'number' + }, + avatar: { + required: false, + type: 'string' + } + } + + this.middleware.validate(params, schema) + + const { + externalUid, + avatar, + gender, + nickname + } = params + + await preRegister.call(this, { + user: { + identities: { + provider: EXTERNAL_DIRECT_CONNECT_PROVIDER, + uid: externalUid + } + } + }) + + const extraData = {} + + if (avatar) { + // eslint-disable-next-line n/no-deprecated-api + const avatarPath = url.parse(avatar).pathname + const extName = avatarPath.indexOf('.') > -1 ? avatarPath.split('.').pop() : '' + + extraData.avatar_file = { + name: avatarPath, + extname: extName, + url: avatar + } + } + + const result = await postRegister.call(this, { + user: { + avatar, + gender, + nickname, + identities: [ + { + provider: EXTERNAL_DIRECT_CONNECT_PROVIDER, + userInfo: { + avatar, + gender, + nickname + }, + uid: externalUid + } + ] + }, + extraData + }) + + return { + errCode: result.errCode, + newToken: result.newToken, + externalUid, + avatar, + gender, + nickname, + uid: result.uid + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/update-user-info.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/update-user-info.js new file mode 100644 index 0000000000000000000000000000000000000000..db30e02270cbe12d0916ebc7c39d9105b9f93135 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/external/update-user-info.js @@ -0,0 +1,208 @@ +const url = require('url') +const { userCollection, EXTERNAL_DIRECT_CONNECT_PROVIDER } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { findUser } = require('../../lib/utils/account') +const PasswordUtils = require('../../lib/utils/password') + +/** + * 使用 uid 或 externalUid 获取用户信息 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#external-update-userinfo + * @param {object} params + * @param {string} params.uid uni-id体系的用户id + * @param {string} params.externalUid 业务系统的用户id + * @param {string} params.nickname 昵称 + * @param {string} params.gender 性别 + * @param {string} params.avatar 头像 + * @returns {object} + */ +module.exports = async function (params = {}) { + const schema = { + uid: { + required: false, + type: 'string' + }, + externalUid: { + required: false, + type: 'string' + }, + username: { + required: false, + type: 'string' + }, + password: { + required: false, + type: 'password' + }, + authorizedApp: { + required: false, + type: 'array' + }, // 指定允许登录的app,传空数组或不传时表示可以不可以在任何端登录 + nickname: { + required: false, + type: 'nickname' + }, + role: { + require: false, + type: 'array' + }, + mobile: { + required: false, + type: 'mobile' + }, + email: { + required: false, + type: 'email' + }, + tags: { + required: false, + type: 'array' + }, + status: { + required: false, + type: 'number' + }, + gender: { + required: false, + type: 'number' + }, + avatar: { + required: false, + type: 'string' + } + } + + this.middleware.validate(params, schema) + + const { + uid, + externalUid, + username, + password, + authorizedApp, + nickname, + role, + mobile, + email, + tags, + status, + avatar, + gender + } = params + + if (!uid && !externalUid) { + throw { + errCode: ERROR.PARAM_REQUIRED, + errMsgValue: { + param: 'uid or externalUid' + } + } + } + + let query + if (uid) { + query = { + _id: uid + } + } else { + query = { + identities: { + provider: EXTERNAL_DIRECT_CONNECT_PROVIDER, + uid: externalUid + } + } + } + + const users = await userCollection.where(query).get() + const user = users.data && users.data[0] + if (!user) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + + // 更新的用户数据字段 + const data = { + username, + dcloud_appid: authorizedApp, + nickname, + role, + mobile, + email, + tags, + status, + avatar, + gender + } + + const realData = Object.keys(data).reduce((res, key) => { + const item = data[key] + if (item !== undefined) { + res[key] = item + } + return res + }, {}) + + // 更新用户名时验证用户名是否重新 + if (username) { + const { + userMatched + } = await findUser({ + userQuery: { + username + }, + authorizedApp + }) + if (userMatched.filter(user => user._id !== uid).length) { + throw { + errCode: ERROR.ACCOUNT_EXISTS + } + } + } + if (password) { + const passwordUtils = new PasswordUtils({ + clientInfo: this.getUniversalClientInfo(), + passwordSecret: this.config.passwordSecret + }) + const { + passwordHash, + version + } = passwordUtils.generatePasswordHash({ + password + }) + + realData.password = passwordHash + realData.password_secret_version = version + } + + if (avatar) { + // eslint-disable-next-line n/no-deprecated-api + const avatarPath = url.parse(avatar).pathname + const extName = avatarPath.indexOf('.') > -1 ? avatarPath.split('.').pop() : '' + + realData.avatar_file = { + name: avatarPath, + extname: extName, + url: avatar + } + } + + if (user.identities.length) { + const identity = user.identities.find(item => item.provider === EXTERNAL_DIRECT_CONNECT_PROVIDER) + + if (identity) { + identity.userInfo = { + avatar, + gender, + nickname + } + } + + realData.identities = user.identities + } + + await userCollection.where(query).update(realData) + + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js new file mode 100644 index 0000000000000000000000000000000000000000..3a68e2291ac2ecddaa4c1f6f6d9e4d8693e4bd28 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-auth-result.js @@ -0,0 +1,136 @@ +const { userCollection, REAL_NAME_STATUS, frvLogsCollection } = require('../../common/constants') +const { dataDesensitization, catchAwait } = require('../../common/utils') +const { encryptData, decryptData } = require('../../common/sensitive-aes-cipher') +const { ERROR } = require('../../common/error') + +/** + * 查询认证结果 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-auth-result + * @param {Object} params + * @param {String} params.certifyId 认证ID + * @returns + */ +module.exports = async function (params) { + const schema = { + certifyId: 'string' + } + + this.middleware.validate(params, schema) + + const { uid } = this.authInfo // 从authInfo中取出uid属性 + const { certifyId } = params // 从params中取出certifyId属性 + + const user = await userCollection.doc(uid).get() // 根据uid查询用户信息 + const userInfo = user.data && user.data[0] // 从查询结果中获取userInfo对象 + + // 如果用户不存在,抛出账户不存在的错误 + if (!userInfo) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + + const { realname_auth: realNameAuth = {} } = userInfo + + // 如果用户已经实名认证,抛出已实名认证的错误 + if (realNameAuth.auth_status === REAL_NAME_STATUS.CERTIFIED) { + throw { + errCode: ERROR.REAL_NAME_VERIFIED + } + } + + // 初始化实人认证服务 + const frvManager = uniCloud.getFacialRecognitionVerifyManager({ + requestId: this.getUniCloudRequestId() + }) + + // 调用frvManager的getAuthResult方法,获取认证结果 + const [error, res] = await catchAwait(frvManager.getAuthResult({ + certifyId + })) + + // 如果出现错误,抛出未知错误并打印日志 + if (error) { + console.log(ERROR.UNKNOWN_ERROR, 'error: ', error) + throw error + } + + // 如果认证状态为“PROCESSING”,抛出认证正在处理中的错误 + if (res.authState === 'PROCESSING') { + throw { + errCode: ERROR.FRV_PROCESSING + } + } + + // 如果认证状态为“FAIL”,更新认证日志的状态并抛出认证失败的错误 + if (res.authState === 'FAIL') { + await frvLogsCollection.where({ + certify_id: certifyId + }).update({ + status: REAL_NAME_STATUS.CERTIFY_FAILED + }) + + console.log(ERROR.FRV_FAIL, 'error: ', res) + throw { + errCode: ERROR.FRV_FAIL + } + } + + // 如果认证状态不为“SUCCESS”,抛出未知错误并打印日志 + if (res.authState !== 'SUCCESS') { + console.log(ERROR.UNKNOWN_ERROR, 'source res: ', res) + throw { + errCode: ERROR.UNKNOWN_ERROR + } + } + + // 根据certifyId查询认证记录 + const frvLogs = await frvLogsCollection.where({ + certify_id: certifyId + }).get() + + const log = frvLogs.data && frvLogs.data[0] + + const updateData = { + realname_auth: { + auth_status: REAL_NAME_STATUS.CERTIFIED, + real_name: log.real_name, + identity: log.identity, + auth_date: Date.now(), + type: 0 + } + } + + // 如果获取到了认证照片的地址,则会对其进行下载,并使用uniCloud.uploadFile方法将其上传到云存储,并将上传后的fileID保存起来。 + if (res.pictureUrl) { + const pictureRes = await uniCloud.httpclient.request(res.pictureUrl) + if (pictureRes.status < 400) { + const { + fileID + } = await uniCloud.uploadFile({ + cloudPath: `user/id-card/${uid}.b64`, + cloudPathAsRealPath: true, + fileContent: Buffer.from(encryptData.call(this, pictureRes.data.toString('base64'))) + }) + updateData.realname_auth.in_hand = fileID + } + } + + await Promise.all([ + // 更新用户认证状态 + userCollection.doc(uid).update(updateData), + // 更新实人认证记录状态 + frvLogsCollection.where({ + certify_id: certifyId + }).update({ + status: REAL_NAME_STATUS.CERTIFIED + }) + ]) + + return { + errCode: 0, + authStatus: REAL_NAME_STATUS.CERTIFIED, + realName: dataDesensitization(decryptData.call(this, log.real_name), { onlyLast: true }), // 对姓名进行脱敏处理 + identity: dataDesensitization(decryptData.call(this, log.identity)) // 对身份证号进行脱敏处理 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js new file mode 100644 index 0000000000000000000000000000000000000000..f10d88f4f1f73e4bd382a6a56a21155b6dbdb233 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/get-certify-id.js @@ -0,0 +1,99 @@ +const { userCollection, REAL_NAME_STATUS, frvLogsCollection, dbCmd } = require('../../common/constants') +const { ERROR } = require('../../common/error') +const { encryptData } = require('../../common/sensitive-aes-cipher') +const { getCurrentDateTimestamp } = require('../../common/utils') + +// const CertifyIdExpired = 25 * 60 * 1000 // certifyId 过期时间为30分钟,在25分时置为过期 + +/** + * 获取认证ID + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-frv-certify-id + * @param {Object} params + * @param {String} params.realName 真实姓名 + * @param {String} params.idCard 身份证号码 + * @param {String} params.metaInfo 客户端初始化时返回的metaInfo + * @returns + */ +module.exports = async function (params) { + const schema = { + realName: 'realName', + idCard: 'idCard', + metaInfo: 'string' + } + + this.middleware.validate(params, schema) + + const { realName: originalRealName, idCard: originalIdCard, metaInfo } = params // 解构出传入参数的真实姓名、身份证号码、其他元数据 + const realName = encryptData.call(this, originalRealName) // 对真实姓名进行加密处理 + const idCard = encryptData.call(this, originalIdCard) // 对身份证号码进行加密处理 + + const { uid } = this.authInfo // 获取当前用户的 ID + const idCardCertifyLimit = this.config.idCardCertifyLimit || 1 // 获取身份证认证限制次数,默认为1次 + const realNameCertifyLimit = this.config.realNameCertifyLimit || 5 // 获取实名认证限制次数,默认为5次 + const frvNeedAlivePhoto = this.config.frvNeedAlivePhoto || false // 是否需要拍摄活体照片,默认为 false + + const user = await userCollection.doc(uid).get() // 获取用户信息 + const userInfo = user.data && user.data[0] // 获取用户信息对象中的实名认证信息 + const { realname_auth: realNameAuth = {} } = userInfo // 解构出实名认证信息中的认证状态对象,默认为空对象 + + // 如果用户已经实名认证过,不能再次认证 + if (realNameAuth.auth_status === REAL_NAME_STATUS.CERTIFIED) { + throw { + errCode: ERROR.REAL_NAME_VERIFIED + } + } + + // 查询已经使用同一个身份证认证的账号数量,如果超过限制则不能认证 + const idCardAccount = await userCollection.where({ + realname_auth: { + type: 0, // 用户认证状态是个人 + auth_status: REAL_NAME_STATUS.CERTIFIED, // 认证状态为已认证 + identity: idCard // 身份证号码和传入参数的身份证号码相同 + } + }).get() + if (idCardAccount.data.length >= idCardCertifyLimit) { + throw { + errCode: ERROR.ID_CARD_EXISTS + } + } + + // 查询用户今天已经进行的实名认证次数,如果超过限制则不能认证 + const userFrvLogs = await frvLogsCollection.where({ + user_id: uid, + created_date: dbCmd.gt(getCurrentDateTimestamp()) // 查询今天的认证记录 + }).get() + + // 限制用户每日认证次数 + if (userFrvLogs.data && userFrvLogs.data.length >= realNameCertifyLimit) { + throw { + errCode: ERROR.REAL_NAME_VERIFY_UPPER_LIMIT + } + } + + // 初始化实人认证服务 + const frvManager = uniCloud.getFacialRecognitionVerifyManager({ + requestId: this.getUniCloudRequestId() // 获取当前 + }) + // 调用实人认证服务,获取认证 ID + const res = await frvManager.getCertifyId({ + realName: originalRealName, + idCard: originalIdCard, + needPicture: frvNeedAlivePhoto, + metaInfo + }) + + // 将认证记录插入到实名认证日志中 + await frvLogsCollection.add({ + user_id: uid, + certify_id: res.certifyId, + real_name: realName, + identity: idCard, + status: REAL_NAME_STATUS.WAITING_CERTIFIED, + created_date: Date.now() + }) + + // 返回认证ID + return { + certifyId: res.certifyId + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f80e6ec180d69e864dd17208a9852c0ad6a4c28e --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/facial-recognition-verify/index.js @@ -0,0 +1,4 @@ +module.exports = { + getFrvCertifyId: require('./get-certify-id'), + getFrvAuthResult: require('./get-auth-result') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/accept-invite.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/accept-invite.js new file mode 100644 index 0000000000000000000000000000000000000000..c1d3ba97947924306c7f41a63240ea326c77855d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/accept-invite.js @@ -0,0 +1,25 @@ +const { + acceptInvite +} = require('../../lib/utils/fission') + +/** + * 接受邀请 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#accept-invite + * @param {Object} params + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + inviteCode: 'string' + } + this.middleware.validate(params, schema) + const { + inviteCode + } = params + const uid = this.authInfo.uid + return acceptInvite({ + uid, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/get-invited-user.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/get-invited-user.js new file mode 100644 index 0000000000000000000000000000000000000000..d232977d22eb2bde4b4d621930075069cc5157db --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/get-invited-user.js @@ -0,0 +1,80 @@ +const { + userCollection +} = require('../../common/constants') +const { + coverMobile +} = require('../../common/utils') + +/** + * 获取受邀用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#get-invited-user + * @param {Object} params + * @param {Number} params.level 获取受邀用户的级数,1表示直接邀请的用户 + * @param {Number} params.limit 返回数据大小 + * @param {Number} params.offset 返回数据偏移 + * @param {Boolean} params.needTotal 是否需要返回总数 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + level: 'number', + limit: { + required: false, + type: 'number' + }, + offset: { + required: false, + type: 'number' + }, + needTotal: { + required: false, + type: 'boolean' + } + } + this.middleware.validate(params, schema) + const { + level, + limit = 20, + offset = 0, + needTotal = false + } = params + const uid = this.authInfo.uid + const query = { + [`inviter_uid.${level - 1}`]: uid + } + const getUserRes = await userCollection.where(query) + .field({ + _id: true, + avatar: true, + avatar_file: true, + username: true, + nickname: true, + mobile: true, + invite_time: true + }) + .orderBy('invite_time', 'desc') + .skip(offset) + .limit(limit) + .get() + + const invitedUser = getUserRes.data.map(item => { + return { + uid: item._id, + username: item.username, + nickname: item.nickname, + mobile: coverMobile(item.mobile), + inviteTime: item.invite_time, + avatar: item.avatar, + avatarFile: item.avatar_file + } + }) + const result = { + errCode: 0, + invitedUser + } + if (needTotal) { + const getTotalRes = await userCollection.where(query).count() + result.total = getTotalRes.total + } + return result +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/index.js new file mode 100644 index 0000000000000000000000000000000000000000..dcca9a34e10c8da7685e5330f167d7f88e0e57cd --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/fission/index.js @@ -0,0 +1,4 @@ +module.exports = { + acceptInvite: require('./accept-invite'), + getInvitedUser: require('./get-invited-user') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/index.js new file mode 100644 index 0000000000000000000000000000000000000000..7fbd964477f8e4621045f5f30590e65c69e6672c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/index.js @@ -0,0 +1,20 @@ +module.exports = { + login: require('./login'), + loginBySms: require('./login-by-sms'), + loginByUniverify: require('./login-by-univerify'), + loginByWeixin: require('./login-by-weixin'), + loginByAlipay: require('./login-by-alipay'), + loginByQQ: require('./login-by-qq'), + loginByApple: require('./login-by-apple'), + loginByBaidu: require('./login-by-baidu'), + loginByDingtalk: require('./login-by-dingtalk'), + loginByToutiao: require('./login-by-toutiao'), + loginByDouyin: require('./login-by-douyin'), + loginByWeibo: require('./login-by-weibo'), + loginByTaobao: require('./login-by-taobao'), + loginByEmailLink: require('./login-by-email-link'), + loginByEmailCode: require('./login-by-email-code'), + loginByFacebook: require('./login-by-facebook'), + loginByGoogle: require('./login-by-google'), + loginByWeixinMobile: require('./login-by-weixin-mobile') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-alipay.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-alipay.js new file mode 100644 index 0000000000000000000000000000000000000000..4611b0cb7a9536e30c26060a5157b12d03b7c242 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-alipay.js @@ -0,0 +1,70 @@ +const { + initAlipay +} = require('../../lib/third-party/index') +const { + ERROR +} = require('../../common/error') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + LOG_TYPE +} = require('../../common/constants') + +/** + * 支付宝登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-alipay + * @param {Object} params + * @param {String} params.code 支付宝小程序客户端登录返回的code + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: 'string', + inviteCode: { + type: 'string', + required: false + } + } + this.middleware.validate(params, schema) + const { + code, + inviteCode + } = params + const alipayApi = initAlipay.call(this) + let getAlipayAccountResult + try { + getAlipayAccountResult = await alipayApi.code2Session(code) + } catch (error) { + console.error(error) + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.LOGIN + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { + openid + } = getAlipayAccountResult + + const { + type, + user + } = await preUnifiedLogin.call(this, { + user: { + ali_openid: openid + } + }) + return postUnifiedLogin.call(this, { + user, + extraData: {}, + isThirdParty: true, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-apple.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-apple.js new file mode 100644 index 0000000000000000000000000000000000000000..5a1e2fefa6548b5cc9c074ba77eb935d1fcbefcf --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-apple.js @@ -0,0 +1,77 @@ +const { + initApple +} = require('../../lib/third-party/index') +const { + ERROR +} = require('../../common/error') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + LOG_TYPE +} = require('../../common/constants') + +/** + * 苹果登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-apple + * @param {Object} params + * @param {String} params.identityToken 苹果登录返回的identityToken + * @param {String} params.nickname 用户昵称 + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + identityToken: 'string', + nickname: { + required: false, + type: 'nickname' + }, + inviteCode: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + identityToken, + nickname, + inviteCode + } = params + const appleApi = initApple.call(this) + let verifyResult + try { + verifyResult = await appleApi.verifyIdentityToken(identityToken) + } catch (error) { + console.error(error) + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.LOGIN + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + const { + openid + } = verifyResult + + const { + type, + user + } = await preUnifiedLogin.call(this, { + user: { + apple_openid: openid + } + }) + return postUnifiedLogin.call(this, { + user, + extraData: { + nickname + }, + isThirdParty: true, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-baidu.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-baidu.js new file mode 100644 index 0000000000000000000000000000000000000000..9d4b453993e8f2cf98f45b55d8aafe3be392c308 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-baidu.js @@ -0,0 +1,9 @@ +/** + * 百度登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByBaidu] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-dingtalk.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-dingtalk.js new file mode 100644 index 0000000000000000000000000000000000000000..22c2f8b620a589ac772c290f62b9652d83f7691f --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-dingtalk.js @@ -0,0 +1,9 @@ +/** + * 钉钉登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByDingtalk] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-douyin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-douyin.js new file mode 100644 index 0000000000000000000000000000000000000000..cecf581a652a22e70e1ffc52dfba006c1efb5aa9 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-douyin.js @@ -0,0 +1,9 @@ +/** + * 抖音登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByDouyin] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-email-code.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-email-code.js new file mode 100644 index 0000000000000000000000000000000000000000..1f3c3a663ba073bcd52647ddd3639a014624504e --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-email-code.js @@ -0,0 +1,9 @@ +/** + * 邮箱验证码登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByEmailCode] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-email-link.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-email-link.js new file mode 100644 index 0000000000000000000000000000000000000000..a8f9e6ffd3c77d41d00ee2e4274f67bd8c2d7056 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-email-link.js @@ -0,0 +1,9 @@ +/** + * 邮箱点击链接登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByEmailLink] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-facebook.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-facebook.js new file mode 100644 index 0000000000000000000000000000000000000000..1a5eab894faee54904f8f6b4aa9aee5aeab58d66 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-facebook.js @@ -0,0 +1,9 @@ +/** + * Facebook登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByFacebook] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-google.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-google.js new file mode 100644 index 0000000000000000000000000000000000000000..c12efe3e2385952edf45429ae30c42047f0b40f9 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-google.js @@ -0,0 +1,9 @@ +/** + * Google登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByGoogle] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-qq.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-qq.js new file mode 100644 index 0000000000000000000000000000000000000000..04094019fd01fba4df10fba5e28c1dd4111bb5dc --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-qq.js @@ -0,0 +1,167 @@ +const { + initQQ +} = require('../../lib/third-party/index') +const { + ERROR +} = require('../../common/error') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + LOG_TYPE +} = require('../../common/constants') +const { + getQQPlatform, + generateQQCache, + saveQQUserKey +} = require('../../lib/utils/qq') +const url = require('url') + +/** + * QQ登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-qq + * @param {Object} params + * @param {String} params.code QQ小程序登录返回的code参数 + * @param {String} params.accessToken App端QQ登录返回的accessToken参数 + * @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒 + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: { + type: 'string', + required: false + }, + accessToken: { + type: 'string', + required: false + }, + accessTokenExpired: { + type: 'number', + required: false + }, + inviteCode: { + type: 'string', + required: false + } + } + this.middleware.validate(params, schema) + const { + code, + accessToken, + accessTokenExpired, + inviteCode + } = params + const { + appId + } = this.getUniversalClientInfo() + const qqApi = initQQ.call(this) + const qqPlatform = getQQPlatform.call(this) + let apiName + switch (qqPlatform) { + case 'mp': + apiName = 'code2Session' + break + case 'app': + apiName = 'getOpenidByToken' + break + default: + throw new Error('Unsupported qq platform') + } + let getQQAccountResult + try { + getQQAccountResult = await qqApi[apiName]({ + code, + accessToken + }) + } catch (error) { + console.error(error) + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.LOGIN + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { + openid, + unionid, + // 保存下面的字段 + sessionKey // QQ小程序用户sessionKey + } = getQQAccountResult + + const { + type, + user + } = await preUnifiedLogin.call(this, { + user: { + qq_openid: { + [qqPlatform]: openid + }, + qq_unionid: unionid + } + }) + const extraData = { + qq_openid: { + [`${qqPlatform}_${appId}`]: openid + }, + qq_unionid: unionid + } + if (type === 'register' && qqPlatform !== 'mp') { + const { + nickname, + avatar + } = await qqApi.getUserInfo({ + accessToken, + openid + }) + if (avatar) { + // eslint-disable-next-line n/no-deprecated-api + const extName = url.parse(avatar).pathname.split('.').pop() + const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}` + const getAvatarRes = await uniCloud.httpclient.request(avatar) + if (getAvatarRes.status >= 400) { + throw { + errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED + } + } + const { + fileID + } = await uniCloud.uploadFile({ + cloudPath, + fileContent: getAvatarRes.data + }) + extraData.avatar_file = { + name: cloudPath, + extname: extName, + url: fileID + } + } + extraData.nickname = nickname + } + await saveQQUserKey.call(this, { + openid, + sessionKey, + accessToken, + accessTokenExpired + }) + return postUnifiedLogin.call(this, { + user, + extraData: { + ...extraData, + ...generateQQCache.call(this, { + openid, + sessionKey, // QQ小程序用户sessionKey + accessToken, // App端QQ用户accessToken + accessTokenExpired // App端QQ用户accessToken过期时间 + }) + }, + isThirdParty: true, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-sms.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-sms.js new file mode 100644 index 0000000000000000000000000000000000000000..8f9aeaf997d0be7026033bca8961aeb41062be4c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-sms.js @@ -0,0 +1,99 @@ +const { + getNeedCaptcha, + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + verifyMobileCode +} = require('../../lib/utils/verify-code') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + CAPTCHA_SCENE, + SMS_SCENE, + LOG_TYPE +} = require('../../common/constants') + +/** + * 短信验证码登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-sms + * @param {Object} params + * @param {String} params.mobile 手机号 + * @param {String} params.code 短信验证码 + * @param {String} params.captcha 图形验证码 + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + mobile: 'mobile', + code: 'string', + captcha: { + required: false, + type: 'string' + }, + inviteCode: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + mobile, + code, + captcha, + inviteCode + } = params + + const needCaptcha = await getNeedCaptcha.call(this, { + mobile + }) + + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.LOGIN_BY_SMS + }) + } + + try { + await verifyMobileCode({ + mobile, + code, + scene: SMS_SCENE.LOGIN_BY_SMS + }) + } catch (error) { + console.log(error, { + mobile, + code, + type: SMS_SCENE.LOGIN_BY_SMS + }) + await this.middleware.uniIdLog({ + success: false, + data: { + mobile + }, + type: LOG_TYPE.LOGIN + }) + throw error + } + + const { + type, + user + } = await preUnifiedLogin.call(this, { + user: { + mobile + } + }) + return postUnifiedLogin.call(this, { + user, + extraData: { + mobile_confirmed: 1 + }, + isThirdParty: false, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-taobao.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-taobao.js new file mode 100644 index 0000000000000000000000000000000000000000..8234fa072fcffaacd2d6b32c1517e3f67f828b0d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-taobao.js @@ -0,0 +1,9 @@ +/** + * 淘宝登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByTaobao] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-toutiao.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-toutiao.js new file mode 100644 index 0000000000000000000000000000000000000000..11fda9b404989b7567722f7e98bf0faf38bddbd5 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-toutiao.js @@ -0,0 +1,9 @@ +/** + * 头条登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByToutiao] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-univerify.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-univerify.js new file mode 100644 index 0000000000000000000000000000000000000000..05199b0ae2a364302840bdddf8e0654d0ef2dac3 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-univerify.js @@ -0,0 +1,69 @@ +const { + getPhoneNumber +} = require('../../lib/utils/univerify') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + LOG_TYPE +} = require('../../common/constants') + +/** + * App端一键登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-univerify + * @param {Object} params + * @param {String} params.access_token APP端一键登录返回的access_token + * @param {String} params.openid APP端一键登录返回的openid + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + access_token: 'string', + openid: 'string', + inviteCode: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + // eslint-disable-next-line camelcase + access_token, + openid, + inviteCode + } = params + + let mobile + try { + const phoneInfo = await getPhoneNumber.call(this, { + // eslint-disable-next-line camelcase + access_token, + openid + }) + mobile = phoneInfo.phoneNumber + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.LOGIN + }) + throw error + } + const { + user, + type + } = await preUnifiedLogin.call(this, { + user: { + mobile + } + }) + return postUnifiedLogin.call(this, { + user, + extraData: { + mobile_confirmed: 1 + }, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weibo.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weibo.js new file mode 100644 index 0000000000000000000000000000000000000000..b12a17f2abede705641f7ed01026b5813cc45b44 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weibo.js @@ -0,0 +1,9 @@ +/** + * 微博登录 + * @param {Object} params + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[loginByWeibo] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weixin-mobile.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weixin-mobile.js new file mode 100644 index 0000000000000000000000000000000000000000..1b6e5150dede949621d27742d24b9d8ac609385c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weixin-mobile.js @@ -0,0 +1,106 @@ +const { + initWeixin +} = require('../../lib/third-party/index') +const { + getWeixinAccessToken +} = require('../../lib/utils/weixin') +const { + ERROR +} = require('../../common/error') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + LOG_TYPE +} = require('../../common/constants') +const { + preBind, + postBind +} = require('../../lib/utils/relate') + +/** + * 微信授权手机号登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin-mobile + * @param {Object} params + * @param {String} params.phoneCode 微信手机号返回的code + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + phoneCode: 'string', + inviteCode: { + type: 'string', + required: false + } + } + + this.middleware.validate(params, schema) + + const { phoneCode, inviteCode } = params + + const weixinApi = initWeixin.call(this) + let mobile + + try { + const accessToken = await getWeixinAccessToken.call(this) + const mobileRes = await weixinApi.getPhoneNumber(accessToken, phoneCode) + mobile = mobileRes.purePhoneNumber + } catch (error) { + console.error(error) + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.LOGIN + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { type, user } = await preUnifiedLogin.call(this, { + user: { + mobile + } + }) + + let extraData = { + mobile_confirmed: 1 + } + + if (type === 'login') { + // 绑定手机号 + if (!user.mobile_confirmed) { + const bindAccount = { + mobile + } + await preBind.call(this, { + uid: user._id, + bindAccount, + logType: LOG_TYPE.BIND_MOBILE + }) + await postBind.call(this, { + uid: user._id, + bindAccount, + extraData: { + mobile_confirmed: 1 + }, + logType: LOG_TYPE.BIND_MOBILE + }) + extraData = { + ...extraData, + ...bindAccount + } + } + } + + return postUnifiedLogin.call(this, { + user, + extraData: { + ...extraData + }, + isThirdParty: false, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weixin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weixin.js new file mode 100644 index 0000000000000000000000000000000000000000..210b19b3f481b8d4836e8a3aec5a4e16d909de15 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login-by-weixin.js @@ -0,0 +1,176 @@ +const { + initWeixin +} = require('../../lib/third-party/index') +const { + ERROR +} = require('../../common/error') +const { + preUnifiedLogin, + postUnifiedLogin +} = require('../../lib/utils/unified-login') +const { + generateWeixinCache, + getWeixinPlatform, + saveWeixinUserKey, + saveSecureNetworkCache +} = require('../../lib/utils/weixin') +const { + LOG_TYPE +} = require('../../common/constants') +const url = require('url') + +/** + * 微信登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login-by-weixin + * @param {Object} params + * @param {String} params.code 微信登录返回的code + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: 'string', + inviteCode: { + type: 'string', + required: false + } + } + this.middleware.validate(params, schema) + const { + code, + inviteCode, + // 内部参数,暂不暴露 + secureNetworkCache = false + } = params + const { + appId + } = this.getUniversalClientInfo() + const weixinApi = initWeixin.call(this) + const weixinPlatform = getWeixinPlatform.call(this) + let apiName + switch (weixinPlatform) { + case 'mp': + apiName = 'code2Session' + break + case 'app': + case 'h5': + case 'web': + apiName = 'getOauthAccessToken' + break + default: + throw new Error('Unsupported weixin platform') + } + let getWeixinAccountResult + try { + getWeixinAccountResult = await weixinApi[apiName](code) + } catch (error) { + console.error(error) + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.LOGIN + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { + openid, + unionid, + // 保存下面四个字段 + sessionKey, // 微信小程序用户sessionKey + accessToken, // App端微信用户accessToken + refreshToken, // App端微信用户refreshToken + expired: accessTokenExpired // App端微信用户accessToken过期时间 + } = getWeixinAccountResult + + if (secureNetworkCache) { + if (weixinPlatform !== 'mp') { + throw new Error('Unsupported weixin platform, expect mp-weixin') + } + await saveSecureNetworkCache.call(this, { + code, + openid, + unionid, + sessionKey + }) + } + + const { + type, + user + } = await preUnifiedLogin.call(this, { + user: { + wx_openid: { + [weixinPlatform]: openid + }, + wx_unionid: unionid + } + }) + const extraData = { + wx_openid: { + [`${weixinPlatform}_${appId}`]: openid + }, + wx_unionid: unionid + } + if (type === 'register' && weixinPlatform !== 'mp') { + const { + nickname, + avatar + } = await weixinApi.getUserInfo({ + accessToken, + openid + }) + + if (avatar) { + // eslint-disable-next-line n/no-deprecated-api + const avatarPath = url.parse(avatar).pathname + const extName = avatarPath.indexOf('.') > -1 ? url.parse(avatar).pathname.split('.').pop() : 'jpg' + const cloudPath = `user/avatar/${openid.slice(-8) + Date.now()}-avatar.${extName}` + const getAvatarRes = await uniCloud.httpclient.request(avatar) + if (getAvatarRes.status >= 400) { + throw { + errCode: ERROR.GET_THIRD_PARTY_USER_INFO_FAILED + } + } + + const { + fileID + } = await uniCloud.uploadFile({ + cloudPath, + fileContent: getAvatarRes.data + }) + + extraData.avatar_file = { + name: cloudPath, + extname: extName, + url: fileID + } + } + + extraData.nickname = nickname + } + await saveWeixinUserKey.call(this, { + openid, + sessionKey, + accessToken, + refreshToken, + accessTokenExpired + }) + return postUnifiedLogin.call(this, { + user, + extraData: { + ...extraData, + ...generateWeixinCache.call(this, { + openid, + sessionKey, // 微信小程序用户sessionKey + accessToken, // App端微信用户accessToken + refreshToken, // App端微信用户refreshToken + accessTokenExpired // App端微信用户accessToken过期时间 + }) + }, + isThirdParty: true, + type, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login.js new file mode 100644 index 0000000000000000000000000000000000000000..8419ee8b0e85bc873c94284ceff6e3238c4f1b1b --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/login/login.js @@ -0,0 +1,94 @@ +const { + preLoginWithPassword, + postLogin +} = require('../../lib/utils/login') +const { + getNeedCaptcha, + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + CAPTCHA_SCENE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +/** + * 用户名密码登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.mobile 手机号 + * @param {String} params.email 邮箱 + * @param {String} params.password 密码 + * @param {String} params.captcha 图形验证码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + username: { + required: false, + type: 'username' + }, + mobile: { + required: false, + type: 'mobile' + }, + email: { + required: false, + type: 'email' + }, + password: 'password', + captcha: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + username, + mobile, + email, + password, + captcha + } = params + if (!username && !mobile && !email) { + throw { + errCode: ERROR.INVALID_USERNAME + } + } else if ( + (username && email) || + (username && mobile) || + (email && mobile) + ) { + throw { + errCode: ERROR.INVALID_PARAM + } + } + const needCaptcha = await getNeedCaptcha.call(this, { + username, + mobile, + email + }) + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.LOGIN_BY_PWD + }) + } + const { + user, + extraData + } = await preLoginWithPassword.call(this, { + user: { + username, + mobile, + email + }, + password + }) + return postLogin.call(this, { + user, + extraData + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/logout/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/logout/index.js new file mode 100644 index 0000000000000000000000000000000000000000..897cc1c6e2e37f83faa9fbbe0b06373c0352f4c5 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/logout/index.js @@ -0,0 +1,3 @@ +module.exports = { + logout: require('./logout') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/logout/logout.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/logout/logout.js new file mode 100644 index 0000000000000000000000000000000000000000..f11b4374e3e190ab78e7183748fd60fc61851e65 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/logout/logout.js @@ -0,0 +1,15 @@ +const { + logout +} = require('../../lib/utils/logout') + +/** + * 用户退出登录 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#logout + * @returns + */ +module.exports = async function () { + await logout.call(this) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/authorize-app-login.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/authorize-app-login.js new file mode 100644 index 0000000000000000000000000000000000000000..c6861381fe706b6c5f36f6cf1642da29238e18f0 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/authorize-app-login.js @@ -0,0 +1,37 @@ +const { + isAuthorizeApproved +} = require('./utils') +const { + dbCmd, + userCollection +} = require('../../common/constants') + +/** + * 授权用户登录应用 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#authorize-app-login + * @param {Object} params + * @param {String} params.uid 用户id + * @param {String} params.appId 授权的应用的AppId + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + uid: 'string', + appId: 'string' + } + this.middleware.validate(params, schema) + const { + uid, + appId + } = params + await isAuthorizeApproved({ + uid, + appIdList: [appId] + }) + await userCollection.doc(uid).update({ + dcloud_appid: dbCmd.push(appId) + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ee4dd44bdb84f1b4dae704800b2223dfca77a1c4 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/index.js @@ -0,0 +1,5 @@ +module.exports = { + authorizeAppLogin: require('./authorize-app-login'), + removeAuthorizedApp: require('./remove-authorized-app'), + setAuthorizedApp: require('./set-authorized-app') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/remove-authorized-app.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/remove-authorized-app.js new file mode 100644 index 0000000000000000000000000000000000000000..4fa69dc647ec9ecdaeb7e6e785b9a3df9b658262 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/remove-authorized-app.js @@ -0,0 +1,30 @@ +const { + dbCmd, + userCollection +} = require('../../common/constants') + +/** + * 移除用户登录授权 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#remove-authorized-app + * @param {Object} params + * @param {String} params.uid 用户id + * @param {String} params.appId 取消授权的应用的AppId + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + uid: 'string', + appId: 'string' + } + this.middleware.validate(params, schema) + const { + uid, + appId + } = params + await userCollection.doc(uid).update({ + dcloud_appid: dbCmd.pull(appId) + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/set-authorized-app.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/set-authorized-app.js new file mode 100644 index 0000000000000000000000000000000000000000..774ececf1f0eaf1957e69c6d7372f9f8bc30859b --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/set-authorized-app.js @@ -0,0 +1,36 @@ +const { + isAuthorizeApproved +} = require('./utils') +const { + userCollection +} = require('../../common/constants') + +/** + * 设置用户允许登录的应用列表 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-authorized-app + * @param {Object} params + * @param {String} params.uid 用户id + * @param {Array} params.appIdList 允许登录的应用AppId列表 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + uid: 'string', + appIdList: 'array' + } + this.middleware.validate(params, schema) + const { + uid, + appIdList + } = params + await isAuthorizeApproved({ + uid, + appIdList + }) + await userCollection.doc(uid).update({ + dcloud_appid: appIdList + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/utils.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..bfd1a047560530e1320ba9699b87186c47c43732 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/multi-end/utils.js @@ -0,0 +1,38 @@ +const { + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + findUser +} = require('../../lib/utils/account') + +async function isAuthorizeApproved ({ + uid, + appIdList +} = {}) { + const getUserRes = await userCollection.doc(uid).get() + const userRecord = getUserRes.data[0] + if (!userRecord) { + throw { + errCode: ERROR.ACCOUNT_NOT_EXISTS + } + } + const { + userMatched + } = await findUser({ + userQuery: userRecord, + authorizedApp: appIdList + }) + + if (userMatched.some(item => item._id !== uid)) { + throw { + errCode: ERROR.ACCOUNT_CONFLICT + } + } +} + +module.exports = { + isAuthorizeApproved +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js new file mode 100644 index 0000000000000000000000000000000000000000..319210fa18f94ae614ae1967c516f25d65e2c1f4 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/index.js @@ -0,0 +1,5 @@ +module.exports = { + registerUser: require('./register-user'), + registerAdmin: require('./register-admin'), + registerUserByEmail: require('./register-user-by-email') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js new file mode 100644 index 0000000000000000000000000000000000000000..564ec147a8fc896aaa63b34f5dca4c09733f4f62 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-admin.js @@ -0,0 +1,72 @@ +const { + userCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + preRegisterWithPassword, + postRegister +} = require('../../lib/utils/register') + +/** + * 注册管理员 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-admin + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + username: 'username', + password: 'password', + nickname: { + type: 'nickname', + required: false + } + } + this.middleware.validate(params, schema) + const { + username, + password, + nickname + } = params + const getAdminRes = await userCollection.where({ + role: 'admin' + }).limit(1).get() + if (getAdminRes.data.length > 0) { + const [admin] = getAdminRes.data + const appId = this.getUniversalClientInfo().appId + + if (!admin.dcloud_appid || (admin.dcloud_appid && admin.dcloud_appid.includes(appId))) { + return { + errCode: ERROR.ADMIN_EXISTS, + errMsg: this.t('uni-id-admin-exists') + } + } else { + return { + errCode: ERROR.ADMIN_EXISTS, + errMsg: this.t('uni-id-admin-exist-in-other-apps') + } + } + } + const { + user, + extraData + } = await preRegisterWithPassword.call(this, { + user: { + username + }, + password + }) + return postRegister.call(this, { + user, + extraData: { + ...extraData, + nickname, + role: ['admin'] + } + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js new file mode 100644 index 0000000000000000000000000000000000000000..f5a54c53993c47789444cd35aec3daac9b1991d3 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user-by-email.js @@ -0,0 +1,87 @@ +const { + postRegister, + preRegisterWithPassword +} = require('../../lib/utils/register') +const { + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + CAPTCHA_SCENE, + EMAIL_SCENE, + LOG_TYPE +} = require('../../common/constants') +const { + verifyEmailCode +} = require('../../lib/utils/verify-code') + +/** + * 通过邮箱+验证码注册普通用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user-by-email + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.password 密码 + * @param {String} params.nickname 昵称 + * @param {String} params.code 邮箱验证码 + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + email: 'email', + password: 'password', + nickname: { + required: false, + type: 'nickname' + }, + code: 'string', + inviteCode: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + email, + password, + nickname, + code, + inviteCode + } = params + + try { + // 验证邮箱验证码,验证不通过时写入失败日志 + await verifyEmailCode({ + email, + code, + scene: EMAIL_SCENE.REGISTER + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + email + }, + type: LOG_TYPE.REGISTER, + success: false + }) + throw error + } + + const { + user, + extraData + } = await preRegisterWithPassword.call(this, { + user: { + email + }, + password + }) + return postRegister.call(this, { + user, + extraData: { + ...extraData, + nickname, + email_confirmed: 1 + }, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user.js new file mode 100644 index 0000000000000000000000000000000000000000..510d7683af513dd2c08c670fff799dad734a4d12 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/register/register-user.js @@ -0,0 +1,68 @@ +const { + postRegister, + preRegisterWithPassword +} = require('../../lib/utils/register') +const { + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + CAPTCHA_SCENE +} = require('../../common/constants') + +/** + * 注册普通用户 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user + * @param {Object} params + * @param {String} params.username 用户名 + * @param {String} params.password 密码 + * @param {String} params.captcha 图形验证码 + * @param {String} params.nickname 昵称 + * @param {String} params.inviteCode 邀请码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + username: 'username', + password: 'password', + captcha: 'string', + nickname: { + required: false, + type: 'nickname' + }, + inviteCode: { + required: false, + type: 'string' + } + } + this.middleware.validate(params, schema) + const { + username, + password, + nickname, + captcha, + inviteCode + } = params + + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.REGISTER + }) + + const { + user, + extraData + } = await preRegisterWithPassword.call(this, { + user: { + username + }, + password + }) + return postRegister.call(this, { + user, + extraData: { + ...extraData, + nickname + }, + inviteCode + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-alipay.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-alipay.js new file mode 100644 index 0000000000000000000000000000000000000000..76e12477368e8467510752f4bddb09340f320c00 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-alipay.js @@ -0,0 +1,63 @@ +const { + preBind, + postBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + initAlipay +} = require('../../lib/third-party/index') + +/** + * 绑定支付宝账号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-alipay + * @param {Object} params + * @param {String} params.code 支付宝小程序登录返回的code参数 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: 'string' + } + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + const { + code + } = params + const alipayApi = initAlipay.call(this) + let getAlipayAccountResult + try { + getAlipayAccountResult = await alipayApi().code2Session(code) + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.BIND_ALIPAY + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { + openid + } = getAlipayAccountResult + + const bindAccount = { + ali_openid: openid + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_APPLE + }) + return postBind.call(this, { + uid, + bindAccount, + extraData: {}, + logType: LOG_TYPE.BIND_APPLE + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-apple.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-apple.js new file mode 100644 index 0000000000000000000000000000000000000000..b87ac80428c4fab262fe9750f0970758fb0308fa --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-apple.js @@ -0,0 +1,62 @@ +const { + preBind, + postBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + initApple +} = require('../../lib/third-party/index') + +/** + * 绑定苹果账号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-apple + * @param {Object} params + * @param {String} params.identityToken 苹果登录返回identityToken + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + identityToken: 'string' + } + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + const { + identityToken + } = params + const appleApi = initApple.call(this) + let verifyResult + try { + verifyResult = await appleApi.verifyIdentityToken(identityToken) + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.BIND_APPLE + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + const { + openid + } = verifyResult + + const bindAccount = { + apple_openid: openid + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_APPLE + }) + return postBind.call(this, { + uid, + bindAccount, + extraData: {}, + logType: LOG_TYPE.BIND_APPLE + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-mp-weixin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-mp-weixin.js new file mode 100644 index 0000000000000000000000000000000000000000..2e9a9915e48a3bee964f7831015597189b374210 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-mp-weixin.js @@ -0,0 +1,104 @@ +const { + preBind, + postBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE +} = require('../../common/constants') +const { + decryptWeixinData, + getWeixinCache, getWeixinAccessToken +} = require('../../lib/utils/weixin') +const { initWeixin } = require('../../lib/third-party') +const { ERROR } = require('../../common/error') + +/** + * 通过微信绑定手机号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-mp-weixin + * @param {Object} params + * @param {String} params.encryptedData 微信获取手机号返回的加密信息 + * @param {String} params.iv 微信获取手机号返回的初始向量 + * @param {String} params.code 微信获取手机号返回的code + * @returns + */ +module.exports = async function (params = {}) { + /** + * 微信小程序的规则是客户端应先使用checkSession接口检测上次获取的sessionKey是否仍有效 + * 如果有效则直接使用上次存储的sessionKey即可 + * 如果无效应重新调用login接口再次刷新sessionKey + * 因此此接口不应直接使用客户端login获取的code,只能使用缓存的sessionKey + */ + const schema = { + encryptedData: { + required: false, + type: 'string' + }, + iv: { + required: false, + type: 'string' + }, + code: { + required: false, + type: 'string' + } + } + const { + encryptedData, + iv, + code + } = params + this.middleware.validate(params, schema) + + if ((!encryptedData && !iv) && !code) { + return { + errCode: ERROR.INVALID_PARAM + } + } + + const uid = this.authInfo.uid + + let mobile + if (code) { + // 区分客户端类型 小程序还是App + const accessToken = await getWeixinAccessToken.call(this) + const weixinApi = initWeixin.call(this) + const res = await weixinApi.getPhoneNumber(accessToken, code) + + mobile = res.purePhoneNumber + } else { + const sessionKey = await getWeixinCache.call(this, { + uid, + key: 'session_key' + }) + if (!sessionKey) { + throw new Error('Session key not found') + } + const res = decryptWeixinData.call(this, { + encryptedData, + sessionKey, + iv + }) + + mobile = res.purePhoneNumber + } + + const bindAccount = { + mobile + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_MOBILE + }) + await postBind.call(this, { + uid, + bindAccount, + extraData: { + mobile_confirmed: 1 + }, + logType: LOG_TYPE.BIND_MOBILE + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-sms.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-sms.js new file mode 100644 index 0000000000000000000000000000000000000000..2275b054161365cb150f72bd672e09195da2d72e --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-sms.js @@ -0,0 +1,92 @@ +const { + getNeedCaptcha, + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + LOG_TYPE, + SMS_SCENE, + CAPTCHA_SCENE +} = require('../../common/constants') +const { + verifyMobileCode +} = require('../../lib/utils/verify-code') +const { + preBind, + postBind +} = require('../../lib/utils/relate') + +/** + * 通过短信验证码绑定手机号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-sms + * @param {Object} params + * @param {String} params.mobile 手机号 + * @param {String} params.code 短信验证码 + * @param {String} params.captcha 图形验证码 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + mobile: 'mobile', + code: 'string', + captcha: { + type: 'string', + required: false + } + } + const { + mobile, + code, + captcha + } = params + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + + // 判断是否需要验证码 + const needCaptcha = await getNeedCaptcha.call(this, { + uid, + type: LOG_TYPE.BIND_MOBILE + }) + if (needCaptcha) { + await verifyCaptcha.call(this, { + captcha, + scene: CAPTCHA_SCENE.BIND_MOBILE_BY_SMS + }) + } + + try { + // 验证手机号验证码,验证不通过时写入失败日志 + await verifyMobileCode({ + mobile, + code, + scene: SMS_SCENE.BIND_MOBILE_BY_SMS + }) + } catch (error) { + await this.middleware.uniIdLog({ + data: { + user_id: uid + }, + type: LOG_TYPE.BIND_MOBILE, + success: false + }) + throw error + } + const bindAccount = { + mobile + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_MOBILE + }) + await postBind.call(this, { + uid, + bindAccount, + extraData: { + mobile_confirmed: 1 + }, + logType: LOG_TYPE.BIND_MOBILE + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-univerify.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-univerify.js new file mode 100644 index 0000000000000000000000000000000000000000..b38d1bc818aea79df271ad12e5f02b0bd7cc9c52 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-mobile-by-univerify.js @@ -0,0 +1,70 @@ +const { + getPhoneNumber +} = require('../../lib/utils/univerify') +const { + LOG_TYPE +} = require('../../common/constants') +const { + preBind, + postBind +} = require('../../lib/utils/relate') + +/** + * 通过一键登录绑定手机号 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-mobile-by-univerify + * @param {Object} params + * @param {String} params.openid APP端一键登录返回的openid + * @param {String} params.access_token APP端一键登录返回的access_token + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + openid: 'string', + access_token: 'string' + } + const { + openid, + // eslint-disable-next-line camelcase + access_token + } = params + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + let mobile + try { + const phoneInfo = await getPhoneNumber.call(this, { + // eslint-disable-next-line camelcase + access_token, + openid + }) + mobile = phoneInfo.phoneNumber + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + data: { + user_id: uid + }, + type: LOG_TYPE.BIND_MOBILE + }) + throw error + } + + const bindAccount = { + mobile + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_MOBILE + }) + await postBind.call(this, { + uid, + bindAccount, + extraData: { + mobile_confirmed: 1 + }, + logType: LOG_TYPE.BIND_MOBILE + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-qq.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-qq.js new file mode 100644 index 0000000000000000000000000000000000000000..2ae771ddf7ed89522348cf66746efe5e28f59148 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-qq.js @@ -0,0 +1,110 @@ +const { + preBind, + postBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +const { + initQQ +} = require('../../lib/third-party/index') +const { + generateQQCache, + getQQPlatform, + saveQQUserKey +} = require('../../lib/utils/qq') + +/** + * 绑定QQ + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-qq + * @param {Object} params + * @param {String} params.code 小程序端QQ登录返回的code + * @param {String} params.accessToken APP端QQ登录返回的accessToken + * @param {String} params.accessTokenExpired accessToken过期时间,由App端QQ登录返回的expires_in参数计算而来,单位:毫秒 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: { + type: 'string', + required: false + }, + accessToken: { + type: 'string', + required: false + }, + accessTokenExpired: { + type: 'number', + required: false + } + } + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + const { + code, + accessToken, + accessTokenExpired + } = params + const qqPlatform = getQQPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + const qqApi = initQQ.call(this) + const clientPlatform = this.clientPlatform + const apiName = clientPlatform === 'mp-qq' ? 'code2Session' : 'getOpenidByToken' + let getQQAccountResult + try { + getQQAccountResult = await qqApi[apiName]({ + code, + accessToken + }) + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.BIND_QQ + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { + openid, + unionid, + // 保存下面四个字段 + sessionKey // 微信小程序用户sessionKey + } = getQQAccountResult + + const bindAccount = { + qq_openid: { + [qqPlatform]: openid + }, + qq_unionid: unionid + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_QQ + }) + await saveQQUserKey.call(this, { + openid, + sessionKey, + accessToken, + accessTokenExpired + }) + return postBind.call(this, { + uid, + bindAccount, + extraData: { + qq_openid: { + [`${qqPlatform}_${appId}`]: openid + }, + ...generateQQCache.call(this, { + openid, + sessionKey + }) + }, + logType: LOG_TYPE.BIND_QQ + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-weixin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-weixin.js new file mode 100644 index 0000000000000000000000000000000000000000..def541a8725335558228020127471029b5150b41 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/bind-weixin.js @@ -0,0 +1,100 @@ +const { + preBind, + postBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE +} = require('../../common/constants') +const { + generateWeixinCache, + saveWeixinUserKey, + getWeixinPlatform +} = require('../../lib/utils/weixin') +const { + initWeixin +} = require('../../lib/third-party/index') +const { + ERROR +} = require('../../common/error') + +/** + * 绑定微信 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#bind-weixin + * @param {Object} params + * @param {String} params.code 微信登录返回的code + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: 'string' + } + this.middleware.validate(params, schema) + const uid = this.authInfo.uid + const { + code + } = params + const weixinPlatform = getWeixinPlatform.call(this) + const appId = this.getUniversalClientInfo().appId + + const weixinApi = initWeixin.call(this) + const clientPlatform = this.clientPlatform + const apiName = clientPlatform === 'mp-weixin' ? 'code2Session' : 'getOauthAccessToken' + let getWeixinAccountResult + try { + getWeixinAccountResult = await weixinApi[apiName](code) + } catch (error) { + await this.middleware.uniIdLog({ + success: false, + type: LOG_TYPE.BIND_WEIXIN + }) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + + const { + openid, + unionid, + // 保存下面四个字段 + sessionKey, // 微信小程序用户sessionKey + accessToken, // App端微信用户accessToken + refreshToken, // App端微信用户refreshToken + expired: accessTokenExpired // App端微信用户accessToken过期时间 + } = getWeixinAccountResult + + const bindAccount = { + wx_openid: { + [weixinPlatform]: openid + }, + wx_unionid: unionid + } + await preBind.call(this, { + uid, + bindAccount, + logType: LOG_TYPE.BIND_WEIXIN + }) + await saveWeixinUserKey.call(this, { + openid, + sessionKey, + accessToken, + refreshToken, + accessTokenExpired + }) + return postBind.call(this, { + uid, + bindAccount, + extraData: { + wx_openid: { + [`${weixinPlatform}_${appId}`]: openid + }, + ...generateWeixinCache.call(this, { + openid, + sessionKey, // 微信小程序用户sessionKey + accessToken, // App端微信用户accessToken + refreshToken, // App端微信用户refreshToken + accessTokenExpired // App端微信用户accessToken过期时间 + }) + }, + logType: LOG_TYPE.BIND_WEIXIN + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/index.js new file mode 100644 index 0000000000000000000000000000000000000000..a587e76f2904d99e3068add8cd838837b74e99df --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/index.js @@ -0,0 +1,13 @@ +module.exports = { + bindMobileBySms: require('./bind-mobile-by-sms'), + bindMobileByUniverify: require('./bind-mobile-by-univerify'), + bindMobileByMpWeixin: require('./bind-mobile-by-mp-weixin'), + bindAlipay: require('./bind-alipay'), + bindApple: require('./bind-apple'), + bindQQ: require('./bind-qq'), + bindWeixin: require('./bind-weixin'), + unbindWeixin: require('./unbind-weixin'), + unbindAlipay: require('./unbind-alipay'), + unbindQQ: require('./unbind-qq'), + unbindApple: require('./unbind-apple') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-alipay.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-alipay.js new file mode 100644 index 0000000000000000000000000000000000000000..db7b1a64041d3752bb7fdd754e407b6022423670 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-alipay.js @@ -0,0 +1,32 @@ +const { + preUnBind, + postUnBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE, dbCmd +} = require('../../common/constants') + +/** + * 解绑支付宝 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-alipay + * @returns + */ +module.exports = async function () { + const { uid } = this.authInfo + + await preUnBind.call(this, { + uid, + unBindAccount: { + ali_openid: dbCmd.exists(true) + }, + logType: LOG_TYPE.UNBIND_ALIPAY + }) + + return await postUnBind.call(this, { + uid, + unBindAccount: { + ali_openid: dbCmd.remove() + }, + logType: LOG_TYPE.UNBIND_ALIPAY + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-apple.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-apple.js new file mode 100644 index 0000000000000000000000000000000000000000..c966e3f6862ad295ce83e4aa4d8518267bf7766e --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-apple.js @@ -0,0 +1,32 @@ +const { + preUnBind, + postUnBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE, dbCmd +} = require('../../common/constants') + +/** + * 解绑apple + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-apple + * @returns + */ +module.exports = async function () { + const { uid } = this.authInfo + + await preUnBind.call(this, { + uid, + unBindAccount: { + apple_openid: dbCmd.exists(true) + }, + logType: LOG_TYPE.UNBIND_APPLE + }) + + return await postUnBind.call(this, { + uid, + unBindAccount: { + apple_openid: dbCmd.remove() + }, + logType: LOG_TYPE.UNBIND_APPLE + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-qq.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-qq.js new file mode 100644 index 0000000000000000000000000000000000000000..08794c0216600e08608b34e01848b8d01b7c003f --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-qq.js @@ -0,0 +1,33 @@ +const { + preUnBind, + postUnBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE, dbCmd +} = require('../../common/constants') +/** + * 解绑QQ + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-qq + * @returns + */ +module.exports = async function () { + const { uid } = this.authInfo + + await preUnBind.call(this, { + uid, + unBindAccount: { + qq_openid: dbCmd.exists(true), + qq_unionid: dbCmd.exists(true) + }, + logType: LOG_TYPE.UNBIND_QQ + }) + + return await postUnBind.call(this, { + uid, + unBindAccount: { + qq_openid: dbCmd.remove(), + qq_unionid: dbCmd.remove() + }, + logType: LOG_TYPE.UNBIND_QQ + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-weixin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-weixin.js new file mode 100644 index 0000000000000000000000000000000000000000..647cc77dfce3522e285b96c7803ed114925cf3fa --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/relate/unbind-weixin.js @@ -0,0 +1,38 @@ +const { + preUnBind, + postUnBind +} = require('../../lib/utils/relate') +const { + LOG_TYPE, dbCmd +} = require('../../common/constants') +const { + getWeixinPlatform +} = require('../../lib/utils/weixin') + +/** + * 解绑微信 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-weixin + * @returns + */ +module.exports = async function () { + const { uid } = this.authInfo + // const weixinPlatform = getWeixinPlatform.call(this) + + await preUnBind.call(this, { + uid, + unBindAccount: { + wx_openid: dbCmd.exists(true), + wx_unionid: dbCmd.exists(true) + }, + logType: LOG_TYPE.UNBIND_WEIXIN + }) + + return await postUnBind.call(this, { + uid, + unBindAccount: { + wx_openid: dbCmd.remove(), + wx_unionid: dbCmd.remove() + }, + logType: LOG_TYPE.UNBIND_WEIXIN + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/index.js new file mode 100644 index 0000000000000000000000000000000000000000..314d458415a1f7a3938d5621583de10c5f399b12 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/index.js @@ -0,0 +1,5 @@ +module.exports = { + refreshToken: require('./refresh-token'), + setPushCid: require('./set-push-cid'), + secureNetworkHandshakeByWeixin: require('./secure-network-handshake-by-weixin') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/refresh-token.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/refresh-token.js new file mode 100644 index 0000000000000000000000000000000000000000..995b8a3995bde2758ed33d4e5136987480cfffcc --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/refresh-token.js @@ -0,0 +1,24 @@ +/** + * 刷新token + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-token + */ +module.exports = async function () { + const refreshTokenRes = await this.uniIdCommon.refreshToken({ + token: this.getUniversalUniIdToken() + }) + const { + errCode, + token, + tokenExpired + } = refreshTokenRes + if (errCode) { + throw refreshTokenRes + } + return { + errCode: 0, + newToken: { + token, + tokenExpired + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/secure-network-handshake-by-weixin.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/secure-network-handshake-by-weixin.js new file mode 100644 index 0000000000000000000000000000000000000000..5b9c4afda6964e46f4aa1b6648a570d23c8fef4d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/secure-network-handshake-by-weixin.js @@ -0,0 +1,73 @@ +const { + ERROR +} = require('../../common/error') +const { + initWeixin +} = require('../../lib/third-party/index') +const { + saveWeixinUserKey, + saveSecureNetworkCache +} = require('../../lib/utils/weixin') +const loginByWeixin = require('../login/login-by-weixin') +/** + * 微信安全网络握手 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-push-cid + * @param {object} params + * @param {string} params.code 微信登录返回的code + * @param {boolean} params.callLoginByWeixin 是否同时调用一次微信登录 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + code: 'string', + callLoginByWeixin: { + type: 'boolean', + required: false + } + } + this.middleware.validate(params, schema) + let platform = this.clientPlatform + if (platform !== 'mp-weixin') { + throw new Error(`[secureNetworkHandshake] platform ${platform} is not supported`) + } + const { + code, + callLoginByWeixin = false + } = params + if (callLoginByWeixin) { + return loginByWeixin.call(this, { + code, + secureNetworkCache: true + }) + } + + const weixinApi = initWeixin.call(this) + let getWeixinAccountResult + try { + getWeixinAccountResult = await weixinApi.code2Session(code) + } catch (error) { + console.error(error) + throw { + errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED + } + } + const { + openid, + unionid, + sessionKey // 微信小程序用户sessionKey + } = getWeixinAccountResult + await saveSecureNetworkCache.call(this, { + code, + openid, + unionid, + sessionKey + }) + await saveWeixinUserKey.call(this, { + openid, + sessionKey + }) + + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/set-push-cid.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/set-push-cid.js new file mode 100644 index 0000000000000000000000000000000000000000..54f8ed6836028c5ec272516b40b8e64ba4794b3b --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/utils/set-push-cid.js @@ -0,0 +1,132 @@ +const { + deviceCollection +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +async function setOpendbDevice ({ + pushClientId +} = {}) { + // 仅新增,如果存在进行更新操作 + const { + appId, + deviceId, + deviceBrand, + deviceModel, + osName, + osVersion, + osLanguage, + osTheme, + devicePixelRatio, + windowWidth, + windowHeight, + screenWidth, + screenHeight, + romName, + romVersion + } = this.getUniversalClientInfo() + const platform = this.clientPlatform + const now = Date.now() + + const db = uniCloud.database() + const opendbDeviceCollection = db.collection('opendb-device') + const getDeviceRes = await opendbDeviceCollection.where({ + device_id: deviceId + }).get() + const data = { + appid: appId, + device_id: deviceId, + vendor: deviceBrand, + model: deviceModel, + uni_platform: platform, + os_name: osName, + os_version: osVersion, + os_language: osLanguage, + os_theme: osTheme, + pixel_ratio: devicePixelRatio, + window_width: windowWidth, + window_height: windowHeight, + screen_width: screenWidth, + screen_height: screenHeight, + rom_name: romName, + rom_version: romVersion, + last_update_date: now, + push_clientid: pushClientId + } + if (getDeviceRes.data.length > 0) { + await opendbDeviceCollection.where({ + device_id: deviceId + }).update(data) + return + } + data.create_date = now + await opendbDeviceCollection.add(data) +} + +/** + * 更新device表的push_clien_id + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-push-cid + * @param {object} params + * @param {string} params.pushClientId 客户端pushClientId + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + pushClientId: 'string' + } + this.middleware.validate(params, schema) + const { + deviceId, + appId, + osName + } = this.getUniversalClientInfo() + let platform = this.clientPlatform + if (platform === 'app') { + platform += osName + } + + const { + uid, + exp + } = this.authInfo + const { pushClientId } = params + const tokenExpired = exp * 1000 + const getDeviceRes = await deviceCollection.where({ + device_id: deviceId + }).get() + // console.log(getDeviceRes) + if (getDeviceRes.data.length > 1) { + return { + errCode: ERROR.SYSTEM_ERROR + } + } + const deviceRecord = getDeviceRes.data[0] + await setOpendbDevice.call(this, { + pushClientId + }) + if (!deviceRecord) { + await deviceCollection.add({ + user_id: uid, + device_id: deviceId, + token_expired: tokenExpired, + push_clientid: pushClientId, + appid: appId + }) + return { + errCode: 0 + } + } + + await deviceCollection.where({ + device_id: deviceId + }).update({ + user_id: uid, + token_expired: tokenExpired, + push_clientid: pushClientId, + appid: appId + }) + return { + errCode: 0 + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/create-captcha.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/create-captcha.js new file mode 100644 index 0000000000000000000000000000000000000000..1b79dd8a7d782fcf04337e7ad51c7be6ee2ec03d --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/create-captcha.js @@ -0,0 +1,35 @@ +const { + CAPTCHA_SCENE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +/** + * 创建图形验证码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#create-captcha + * @param {Object} params + * @param {String} params.scene 图形验证码使用场景 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + scene: 'string' + } + this.middleware.validate(params, schema) + + const { deviceId, platform } = this.getUniversalClientInfo() + const { + scene + } = params + if (!(Object.values(CAPTCHA_SCENE).includes(scene))) { + throw { + errCode: ERROR.INVALID_PARAM + } + } + return this.uniCaptcha.create({ + deviceId, + scene, + uniPlatform: platform + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/index.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/index.js new file mode 100644 index 0000000000000000000000000000000000000000..297dc2bb3ec7680ad19bb0dd0a318207be83b0e6 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/index.js @@ -0,0 +1,7 @@ +module.exports = { + createCaptcha: require('./create-captcha'), + refreshCaptcha: require('./refresh-captcha'), + sendSmsCode: require('./send-sms-code'), + sendEmailLink: require('./send-email-link'), + sendEmailCode: require('./send-email-code') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/refresh-captcha.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/refresh-captcha.js new file mode 100644 index 0000000000000000000000000000000000000000..6c0182521e03f6c49fdb2d765766afc1c4c1533c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/refresh-captcha.js @@ -0,0 +1,36 @@ +const { + CAPTCHA_SCENE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +/** + * 刷新图形验证码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-captcha + * @param {Object} params + * @param {String} params.scene 图形验证码使用场景 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + scene: 'string' + } + this.middleware.validate(params, schema) + + const { deviceId, platform } = this.getUniversalClientInfo() + + const { + scene + } = params + if (!(Object.values(CAPTCHA_SCENE).includes(scene))) { + throw { + errCode: ERROR.INVALID_PARAM + } + } + return this.uniCaptcha.refresh({ + deviceId, + scene, + uniPlatform: platform + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js new file mode 100644 index 0000000000000000000000000000000000000000..361b22fd568234e03929b6dbb9978ed296f8c68e --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-code.js @@ -0,0 +1,60 @@ +const { + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + EMAIL_SCENE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') +/** + * 发送邮箱验证码,可用于登录、注册、绑定邮箱、修改密码等操作 + * @tutorial + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.captcha 图形验证码 + * @param {String} params.scene 使用场景 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + email: 'email', + captcha: 'string', + scene: 'string' + } + this.middleware.validate(params, schema) + + const { + email, + captcha, + scene + } = params + + if (!(Object.values(EMAIL_SCENE).includes(scene))) { + throw { + errCode: ERROR.INVALID_PARAM + } + } + + await verifyCaptcha.call(this, { + scene: 'send-email-code', + captcha + }) + + // -- 测试代码 + await require('../../lib/utils/verify-code') + .setEmailVerifyCode.call(this, { + email, + code: '123456', + expiresIn: 180, + scene + }) + return { + errCode: 'uni-id-invalid-mail-template', + errMsg: `已启动测试模式,直接使用:123456作为邮箱验证码即可。\n如果是正式项目,需自行实现发送邮件的相关功能` + } + // -- 测试代码 + + + //发送邮件--需自行实现 +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-link.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-link.js new file mode 100644 index 0000000000000000000000000000000000000000..d48ac068f9eaab7ded3e14f90cfc95c1005e4c21 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-email-link.js @@ -0,0 +1,12 @@ +/** + * 发送邮箱链接,可用于登录、注册、绑定邮箱、修改密码等操作 + * @tutorial + * @param {Object} params + * @param {String} params.email 邮箱 + * @param {String} params.scene 使用场景 + * @returns + */ +module.exports = async function (params = {}) { + // 此接口暂未实现,欢迎向我们提交pr + throw new Error('api[sendEmailLink] is not yet implemented') +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-sms-code.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-sms-code.js new file mode 100644 index 0000000000000000000000000000000000000000..b549a1fd2a7aa65d19c7bac936326107505f41c7 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/module/verify/send-sms-code.js @@ -0,0 +1,71 @@ +const { + sendSmsCode +} = require('../../lib/utils/sms') +const { + verifyCaptcha +} = require('../../lib/utils/captcha') +const { + SMS_SCENE +} = require('../../common/constants') +const { + ERROR +} = require('../../common/error') + +/** + * 发送短信验证码 + * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#send-sms-code + * @param {Object} params + * @param {String} params.mobile 手机号 + * @param {String} params.captcha 图形验证码 + * @param {String} params.scene 短信验证码使用场景 + * @returns + */ +module.exports = async function (params = {}) { + const schema = { + mobile: 'mobile', + captcha: 'string', + scene: 'string' + } + this.middleware.validate(params, schema) + const { + mobile, + captcha, + scene + } = params + if (!(Object.values(SMS_SCENE).includes(scene))) { + throw { + errCode: ERROR.INVALID_PARAM + } + } + await verifyCaptcha.call(this, { + scene: 'send-sms-code', + captcha + }) + + // -- 测试代码 + const { + templateId + } = (this.config.service && + this.config.service.sms && + this.config.service.sms.scene && + this.config.service.sms.scene[scene]) || {} + if (!templateId) { + await require('../../lib/utils/verify-code') + .setMobileVerifyCode.call(this, { + mobile: params.mobile, + code: '123456', + expiresIn: 180, + scene + }) + return { + errCode: 'uni-id-invalid-sms-template-id', + errMsg: `未找到scene=${scene},的短信模版templateId。\n已启动测试模式,直接使用:123456作为短信验证码即可。\n如果是正式项目,请在路径:/common/uni-config-center/uni-id/config.json中service->sms中配置密钥等信息\n更多详情:https://uniapp.dcloud.io/uniCloud/uni-id.html#config` + } + } + // -- 测试代码 + + return sendSmsCode.call(this, { + mobile, + scene + }) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json new file mode 100644 index 0000000000000000000000000000000000000000..c397e50ff24e7a04b93b806713f7cf28080e4180 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/package.json @@ -0,0 +1,25 @@ +{ + "name": "uni-id-co", + "version": "1.1.19", + "description": "", + "main": "index.js", + "keywords": [], + "author": "DCloud", + "dependencies": { + "jsonwebtoken": "8.5.1", + "lodash.merge": "^4.6.2", + "uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha", + "uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center", + "uni-id-common": "file:../../../../uni-id-common/uniCloud/cloudfunctions/common/uni-id-common", + "uni-open-bridge-common": "file:../../../../uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common", + "uni-cloud-s2s": "file:../../../../uni-cloud-s2s/uniCloud/cloudfunctions/common/uni-cloud-s2s" + }, + "extensions": { + "uni-cloud-redis": {}, + "uni-cloud-sms": {}, + "uni-cloud-verify": {} + }, + "cloudfunction-config": { + "keepRunningAfterReturn": false + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json b/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..163e7c1445f8b082143b686279d2c880cd41c434 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/opendb-device.schema.json @@ -0,0 +1,142 @@ +{ + "bsonType": "object", + "required": [], + "permission": { + "read": false, + "create": true, + "update": false, + "delete": false + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "appid": { + "bsonType": "string", + "description": "DCloud appid" + }, + "device_id": { + "bsonType": "string", + "description": "设备唯一标识" + }, + "vendor": { + "bsonType": "string", + "description": "设备厂商" + }, + "push_clientid": { + "bsonType": "string", + "description": "推送设备客户端标识" + }, + "imei": { + "bsonType": "string", + "description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)" + }, + "oaid": { + "bsonType": "string", + "description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)" + }, + "idfa": { + "bsonType": "string", + "description": "iOS平台配置应用使用广告标识(IDFA)" + }, + "imsi": { + "bsonType": "string", + "description": "国际移动用户识别码(International Mobile Subscriber Identification Number)" + }, + "model": { + "bsonType": "string", + "description": "设备型号" + }, + "platform": { + "bsonType": "string", + "description": "平台类型" + }, + "uni_platform": { + "bsonType": "string", + "description": "uni-app 运行平台,与条件编译平台相同。" + }, + "os_name": { + "bsonType": "string", + "description": "ios|android|windows|mac|linux " + }, + "os_version": { + "bsonType": "string", + "description": "操作系统版本号 " + }, + "os_language": { + "bsonType": "string", + "description": "操作系统语言 " + }, + "os_theme": { + "bsonType": "string", + "description": "操作系统主题 light|dark" + }, + "pixel_ratio": { + "bsonType": "string", + "description": "设备像素比 " + }, + "network_model": { + "bsonType": "string", + "description": "设备网络型号wifi\/3G\/4G\/" + }, + "window_width": { + "bsonType": "string", + "description": "设备窗口宽度 " + }, + "window_height": { + "bsonType": "string", + "description": "设备窗口高度" + }, + "screen_width": { + "bsonType": "string", + "description": "设备屏幕宽度" + }, + "screen_height": { + "bsonType": "string", + "description": "设备屏幕高度" + }, + "rom_name": { + "bsonType": "string", + "description": "rom 名称" + }, + "rom_version": { + "bsonType": "string", + "description": "rom 版本" + }, + "location_latitude": { + "bsonType": "double", + "description": "纬度" + }, + "location_longitude": { + "bsonType": "double", + "description": "经度" + }, + "location_country": { + "bsonType": "string", + "description": "国家" + }, + "location_province": { + "bsonType": "string", + "description": "省份" + }, + "location_city": { + "bsonType": "string", + "description": "城市" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "last_update_date": { + "bsonType": "timestamp", + "description": "最后一次修改时间", + "forceDefaultValue": { + "$env": "now" + } + } + }, + "version": "0.0.1" +} \ No newline at end of file diff --git a/uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json b/uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..160b7e9b5b964c9527d64dbf51b6bc194f702a24 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/opendb-frv-logs.schema.json @@ -0,0 +1,44 @@ +{ + "bsonType": "object", + "permission": { + "read": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission", + "create": "'CREATE_UNI_ID_USERS' in auth.permission", + "update": "doc._id == auth.uid || 'UPDATE_UNI_ID_USERS' in auth.permission", + "delete": "'DELETE_UNI_ID_USERS' in auth.permission" + }, + "properties": { + "_id": { + "description": "存储文档 ID(用户 ID),系统自动生成" + }, + "certify_id": { + "bsonType": "string", + "description": "认证id" + }, + "user_id": { + "bsonType": "string", + "description": "用户id" + }, + "real_name": { + "bsonType": "string", + "description": "姓名" + }, + "identity": { + "bsonType": "string", + "description": "身份证号码" + }, + "status": { + "bsonType": "int", + "description": "认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败", + "maximum": 3, + "minimum": 0 + }, + "created_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + }, + "required": [] +} diff --git a/uni_modules/uni-id-pages/uniCloud/database/uni-id-device.schema.json b/uni_modules/uni-id-pages/uniCloud/database/uni-id-device.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..356a6e23957b5b580a1d0a27811250ec7f12ba3c --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/uni-id-device.schema.json @@ -0,0 +1,83 @@ +{ + "bsonType": "object", + "required": [ + "user_id" + ], + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "user_id": { + "bsonType": "string", + "description": "用户id,参考uni-id-users表" + }, + "ua": { + "bsonType": "string", + "description": "userAgent" + }, + "uuid": { + "bsonType": "string", + "description": "设备唯一标识(需要加密存储)" + }, + "os_name": { + "bsonType": "string", + "description": "ios|android|windows|mac|linux " + }, + "os_version": { + "bsonType": "string", + "description": "操作系统版本号 " + }, + "os_language": { + "bsonType": "string", + "description": "操作系统语言 " + }, + "os_theme": { + "bsonType": "string", + "description": "操作系统主题 light|dark" + }, + "vendor": { + "bsonType": "string", + "description": "设备厂商" + }, + "push_clientid": { + "bsonType": "string", + "description": "推送设备客户端标识" + }, + "imei": { + "bsonType": "string", + "description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)" + }, + "oaid": { + "bsonType": "string", + "description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)" + }, + "idfa": { + "bsonType": "string", + "description": "iOS平台配置应用使用广告标识(IDFA)" + }, + "model": { + "bsonType": "string", + "description": "设备型号" + }, + "platform": { + "bsonType": "string", + "description": "平台类型" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "last_active_date": { + "bsonType": "timestamp", + "description": "最后登录时间" + }, + "last_active_ip": { + "bsonType": "string", + "description": "最后登录IP" + } + }, + "version": "0.0.1" +} \ No newline at end of file diff --git a/uni_modules/uni-id-pages/uniCloud/database/uni-id-log.schema.json b/uni_modules/uni-id-pages/uniCloud/database/uni-id-log.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..1b3dc611da9055165b1837c0f68c8c2fe9eac0e7 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/uni-id-log.schema.json @@ -0,0 +1,71 @@ +{ + "bsonType": "object", + "required": ["user_id"], + "permission": { + "read": "'READ_UNI_ID_LOG' in auth.permission" + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "device_uuid": { + "bsonType": "string", + "description": "设备唯一标识" + }, + "ip": { + "bsonType": "string", + "description": "ip地址" + }, + "state": { + "bsonType": "int", + "description": "结果:0 失败、1 成功" + }, + "type": { + "bsonType": "string", + "description": "操作类型", + "enum": [ + "logout", + "login", + "register", + "reset-pwd", + "bind-mobile", + "bind-weixin", + "bind-qq", + "bind-apple", + "bind-alipay" + ] + }, + "ua": { + "bsonType": "string", + "description": "userAgent" + }, + "user_id": { + "bsonType": "string", + "foreignKey": "uni-id-users._id", + "description": "用户id,参考uni-id-users表" + }, + "username": { + "bsonType": "string", + "description": "用户名" + }, + "email": { + "bsonType": "string", + "description": "邮箱" + }, + "mobile": { + "bsonType": "string", + "description": "手机号" + }, + "appid": { + "bsonType": "string", + "description": "客户端DCloud AppId" + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/database/uni-id-permissions.schema.json b/uni_modules/uni-id-pages/uniCloud/database/uni-id-permissions.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..0178d2568d702e5c156fe32992bc45b6c22a9fbb --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/uni-id-permissions.schema.json @@ -0,0 +1,52 @@ +{ + "bsonType": "object", + "required": ["permission_id", "permission_name"], + "permission": { + "read": "'READ_UNI_ID_PERMISSIONS' in auth.permission", + "create": "'CREATE_UNI_ID_PERMISSIONS' in auth.permission", + "update": "'UPDATE_UNI_ID_PERMISSIONS' in auth.permission", + "delete": "'DELETE_UNI_ID_PERMISSIONS' in auth.permission" + }, + "properties": { + "_id": { + "description": "存储文档 ID,系统自动生成" + }, + "comment": { + "bsonType": "string", + "component": { + "name": "textarea" + }, + "description": "备注", + "label": "备注", + "title": "备注", + "trim": "both" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "permission_id": { + "bsonType": "string", + "component": { + "name": "input" + }, + "description": "权限唯一标识,不可修改,不允许重复", + "label": "权限标识", + "title": "权限ID", + "trim": "both" + }, + "permission_name": { + "bsonType": "string", + "component": { + "name": "input" + }, + "description": "权限名称", + "label": "权限名称", + "title": "权限名称", + "trim": "both" + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/database/uni-id-roles.schema.json b/uni_modules/uni-id-pages/uniCloud/database/uni-id-roles.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..b9289c81e103afc465c3f0598bd742ef55b0e9b4 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/uni-id-roles.schema.json @@ -0,0 +1,50 @@ +{ + "bsonType": "object", + "required": ["role_id", "role_name"], + "permission": { + "read": "'READ_UNI_ID_ROLES' in auth.permission", + "create": "'CREATE_UNI_ID_ROLES' in auth.permission", + "update": "'UPDATE_UNI_ID_ROLES' in auth.permission", + "delete": "'DELETE_UNI_ID_ROLES' in auth.permission" + }, + "properties": { + "_id": { + "description": "存储文档 ID,系统自动生成" + }, + "comment": { + "title": "备注", + "bsonType": "string", + "description": "备注", + "trim": "both" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "permission": { + "title": "权限", + "bsonType": "array", + "foreignKey": "uni-id-permissions.permission_id", + "description": "角色拥有的权限列表", + "enum": { + "collection": "uni-id-permissions", + "field": "permission_name as text, permission_id as value" + } + }, + "role_id": { + "title": "唯一ID", + "bsonType": "string", + "description": "角色唯一标识,不可修改,不允许重复", + "trim": "both" + }, + "role_name": { + "title": "名称", + "bsonType": "string", + "description": "角色名称", + "trim": "both" + } + } +} diff --git a/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json b/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..e30b7d297f34f31c121b6ed5ea3899a3f0d76838 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/database/uni-id-users.schema.json @@ -0,0 +1,473 @@ +{ + "bsonType": "object", + "permission": { + "read": true, + "create": "'CREATE_UNI_ID_USERS' in auth.permission", + "update": "doc._id == auth.uid || 'UPDATE_UNI_ID_USERS' in auth.permission", + "delete": "'DELETE_UNI_ID_USERS' in auth.permission" + }, + "properties": { + "_id": { + "description": "存储文档 ID(用户 ID),系统自动生成" + }, + "ali_openid": { + "bsonType": "string", + "description": "支付宝平台openid", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "apple_openid": { + "bsonType": "string", + "description": "苹果登录openid", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "avatar": { + "bsonType": "string", + "description": "头像地址", + "title": "头像地址", + "trim": "both", + "permission": { + "read": true, + "write": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "avatar_file": { + "bsonType": "file", + "description": "用file类型方便使用uni-file-picker组件", + "title": "头像文件", + "permission": { + "read": true, + "write": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "comment": { + "bsonType": "string", + "description": "备注", + "title": "备注", + "trim": "both", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "dcloud_appid": { + "bsonType": "array", + "description": "允许登录的客户端的appid列表", + "foreignKey": "opendb-app-list.appid", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "department_id": { + "bsonType": "array", + "description": "部门ID", + "enum": { + "collection": "opendb-department", + "field": "_id as value, name as text", + "orderby": "name asc" + }, + "enumType": "tree", + "title": "部门", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "email": { + "bsonType": "string", + "description": "邮箱地址", + "format": "email", + "title": "邮箱", + "trim": "both", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "email_confirmed": { + "bsonType": "int", + "defaultValue": 0, + "description": "邮箱验证状态:0 未验证 1 已验证", + "enum": [{ + "text": "未验证", + "value": 0 + }, + { + "text": "已验证", + "value": 1 + } + ], + "title": "邮箱验证状态", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "gender": { + "bsonType": "int", + "defaultValue": 0, + "description": "用户性别:0 未知 1 男性 2 女性", + "enum": [{ + "text": "未知", + "value": 0 + }, + { + "text": "男", + "value": 1 + }, + { + "text": "女", + "value": 2 + } + ], + "title": "性别", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "invite_time": { + "bsonType": "timestamp", + "description": "受邀时间", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "inviter_uid": { + "bsonType": "array", + "description": "用户全部上级邀请者", + "trim": "both", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "last_login_date": { + "bsonType": "timestamp", + "description": "最后登录时间", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "last_login_ip": { + "bsonType": "string", + "description": "最后登录时 IP 地址", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "mobile": { + "bsonType": "string", + "description": "手机号码", + "pattern": "^\\+?[0-9-]{3,20}$", + "title": "手机号码", + "trim": "both", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "mobile_confirmed": { + "bsonType": "int", + "defaultValue": 0, + "description": "手机号验证状态:0 未验证 1 已验证", + "enum": [{ + "text": "未验证", + "value": 0 + }, + { + "text": "已验证", + "value": 1 + } + ], + "title": "手机号验证状态", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "my_invite_code": { + "bsonType": "string", + "description": "用户自身邀请码", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "nickname": { + "bsonType": "string", + "description": "用户昵称", + "title": "昵称", + "trim": "both", + "permission": { + "read": true, + "write": "doc._id == auth.uid || 'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "password": { + "bsonType": "password", + "description": "密码,加密存储", + "title": "密码", + "trim": "both" + }, + "password_secret_version": { + "bsonType": "int", + "description": "密码使用的passwordSecret版本", + "title": "passwordSecret", + "permission": { + "read": false, + "write": false + } + }, + "realname_auth": { + "bsonType": "object", + "description": "实名认证信息", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + }, + "properties": { + "auth_date": { + "bsonType": "timestamp", + "description": "认证通过时间" + }, + "auth_status": { + "bsonType": "int", + "description": "认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败", + "maximum": 3, + "minimum": 0 + }, + "contact_email": { + "bsonType": "string", + "description": "联系人邮箱" + }, + "contact_mobile": { + "bsonType": "string", + "description": "联系人手机号码" + }, + "contact_person": { + "bsonType": "string", + "description": "联系人姓名" + }, + "id_card_back": { + "bsonType": "string", + "description": "身份证反面照 URL" + }, + "id_card_front": { + "bsonType": "string", + "description": "身份证正面照 URL" + }, + "identity": { + "bsonType": "string", + "description": "身份证号码/营业执照号码" + }, + "in_hand": { + "bsonType": "string", + "description": "手持身份证照片 URL" + }, + "license": { + "bsonType": "string", + "description": "营业执照 URL" + }, + "real_name": { + "bsonType": "string", + "description": "真实姓名/企业名称" + }, + "type": { + "bsonType": "int", + "description": "用户类型:0 个人用户 1 企业用户", + "maximum": 1, + "minimum": 0 + } + }, + "required": [ + "type", + "auth_status" + ] + }, + "register_date": { + "bsonType": "timestamp", + "description": "注册时间", + "forceDefaultValue": { + "$env": "now" + }, + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "register_ip": { + "bsonType": "string", + "description": "注册时 IP 地址", + "forceDefaultValue": { + "$env": "clientIP" + }, + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "role": { + "bsonType": "array", + "description": "用户角色", + "enum": { + "collection": "uni-id-roles", + "field": "role_id as value, role_name as text" + }, + "foreignKey": "uni-id-roles.role_id", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + }, + "title": "角色" + }, + "tags":{ + "bsonType": "array", + "description": "用户标签", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + }, + "title": "标签" + }, + "score": { + "bsonType": "int", + "description": "用户积分,积分变更记录可参考:uni-id-scores表定义", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "status": { + "bsonType": "int", + "defaultValue": 0, + "description": "用户状态:0 正常 1 禁用 2 审核中 3 审核拒绝", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + }, + "enum": [{ + "text": "正常", + "value": 0 + }, + { + "text": "禁用", + "value": 1 + }, + { + "text": "审核中", + "value": 2 + }, + { + "text": "审核拒绝", + "value": 3 + } + ], + "title": "用户状态" + }, + "token": { + "bsonType": "array", + "description": "用户token", + "permission": { + "read": false, + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "username": { + "bsonType": "string", + "description": "用户名,不允许重复", + "title": "用户名", + "trim": "both", + "permission": { + "read": "doc._id == auth.uid || 'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "wx_openid": { + "bsonType": "object", + "description": "微信各个平台openid", + "properties": { + "app": { + "bsonType": "string", + "description": "app平台微信openid" + }, + "mp": { + "bsonType": "string", + "description": "微信小程序平台openid" + }, + "h5": { + "bsonType": "string", + "description": "微信公众号登录openid" + }, + "web": { + "bsonType": "string", + "description": "PC页面扫码登录openid" + } + }, + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "wx_unionid": { + "bsonType": "string", + "description": "微信unionid", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "qq_openid": { + "bsonType": "object", + "description": "QQ各个平台openid", + "properties": { + "app": { + "bsonType": "string", + "description": "app平台QQ openid" + }, + "mp": { + "bsonType": "string", + "description": "QQ小程序平台openid" + } + }, + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "qq_unionid": { + "bsonType": "string", + "description": "QQ unionid", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + }, + "third_party": { + "bsonType": "object", + "description": "三方平台凭证", + "permission": { + "read": false, + "write": false + } + }, + "identities": { + "bsonType": "array", + "description": "三方平台身份信息;一个对象代表一个身份,参数支持: provider 身份源, userInfo 三方用户信息, openid 三方openid, unionid 三方unionid, uid 三方uid", + "permission": { + "read": "'READ_UNI_ID_USERS' in auth.permission", + "write": "'CREATE_UNI_ID_USERS' in auth.permission || 'UPDATE_UNI_ID_USERS' in auth.permission" + } + } + }, + "required": [] +} diff --git a/uni_modules/uni-im-msg-reader/changelog.md b/uni_modules/uni-im-msg-reader/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/uni_modules/uni-im-msg-reader/components/uni-im-msg-reader/uni-im-msg-reader.vue b/uni_modules/uni-im-msg-reader/components/uni-im-msg-reader/uni-im-msg-reader.vue new file mode 100644 index 0000000000000000000000000000000000000000..a3437ec683bdbae31dd408fc9870c744c8f43d11 --- /dev/null +++ b/uni_modules/uni-im-msg-reader/components/uni-im-msg-reader/uni-im-msg-reader.vue @@ -0,0 +1,289 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-im-msg-reader/extension.js b/uni_modules/uni-im-msg-reader/extension.js new file mode 100644 index 0000000000000000000000000000000000000000..05bc76dae0664ef7f64d867ddd0e4bf69a38abcc --- /dev/null +++ b/uni_modules/uni-im-msg-reader/extension.js @@ -0,0 +1,74 @@ +import uniIm from '@/uni_modules/uni-im/sdk/index.js' + +function install() { + // 注册 read_msg 消息类型 + uniIm.extensions.installExt('msg-type-register', () => { + return { + type: 'read_msg', + + isReadable(msg) { + return false + }, + + beforeLocalAdd(msgData, conversation) { + let { + body: { + msgId + }, + from_uid, + create_time, + } = msgData + const msg = conversation.msgList.find(msg => msg._id === msgId) + if (msg) { + let reader = { + user_id: from_uid, + create_time + } + msg.reader_list ? msg.reader_list.push(reader) : msg.reader_list = [reader] + conversation.msgManager.localMsg.update(msg.unique_id, msg) + } + }, + } + }) + + // 监听每条消息的显示状态,报送 read_msg 消息 + uniIm.extensions.installExt('msg-appear', (msg, currentUser) => { + // 特殊消息不用处理 + if (msg.type === 'system' || msg.is_revoke) return + + // 如果是我发送的消息则不处理 + if (msg.from_uid == currentUser.user_id) return + + // 如果我已经在已读列表里则不再处理 + if (msg.reader_list?.some(u => u.user_id == currentUser.user_id)) return + + // 如果是群聊消息且没有 @我,则不用处理 + let conversation = uniIm.conversation.getCached(msg.conversation_id) + if (conversation.group_id && !msg.call_uid?.includes(currentUser.user_id)) return + + // 把自己记入已读列表 + msg.reader_list = msg.reader_list || [] + msg.reader_list.push({ + user_id: currentUser.user_id, + create_time: Date.now() + }) + + // 向云端提交 read_msg 消息 + const uniImCo = uniCloud.importObject('uni-im-co', { + customUI: true + }) + uniImCo.sendMsg({ + type: 'read_msg', + body: { + msgId: msg._id + } + }).catch(e => { + // 提交失败,把自己从已读列表里去掉 + msg.reader_list = msg.reader_list.filter(u => u.user_id !== currentUser.user_id) + }) + }) +} + +export default { + install +} \ No newline at end of file diff --git a/uni_modules/uni-im-msg-reader/package.json b/uni_modules/uni-im-msg-reader/package.json new file mode 100644 index 0000000000000000000000000000000000000000..8ea14f562b060099654c60917b6716332ffe4f63 --- /dev/null +++ b/uni_modules/uni-im-msg-reader/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-im-msg-reader", + "displayName": "uni-im-msg-reader", + "version": "1.0.0", + "description": "uni-im-msg-reader", + "keywords": [ + "uni-im-msg-reader" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "", + "data": "", + "permissions": "" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "u", + "aliyun": "u", + "alipay": "u" + }, + "client": { + "Vue": { + "vue2": "u", + "vue3": "u" + }, + "App": { + "app-vue": "u", + "app-nvue": "u", + "app-uvue": "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" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-im-msg-reader/readme.md b/uni_modules/uni-im-msg-reader/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..47722ac7c4302f79f913309e28bb0ba5025d5f17 --- /dev/null +++ b/uni_modules/uni-im-msg-reader/readme.md @@ -0,0 +1,8 @@ +# 已读反馈 + +本模块属于 uni-im 内部项目,是在 uni-im 开源项目基础上实现扩展功能的模块。 + +## 已读反馈模块的功能 + +- 提供一个附加组件,可以显示在消息列表中每条消息的下方(由其它扩展模块选择性开启) +- 监听每条消息的显示状态,报送 read_msg 消息 diff --git a/uni_modules/uni-im-msg-reader/uniCloud/cloudfunctions/common/uni-im-ext-msg-reader/index.js b/uni_modules/uni-im-msg-reader/uniCloud/cloudfunctions/common/uni-im-ext-msg-reader/index.js new file mode 100644 index 0000000000000000000000000000000000000000..27e11e1a934cf2c3b7264c0064e38ee4b9a550e1 --- /dev/null +++ b/uni_modules/uni-im-msg-reader/uniCloud/cloudfunctions/common/uni-im-ext-msg-reader/index.js @@ -0,0 +1,85 @@ +async function onMsgTypeRegister() { + return { + type: 'read_msg', + + // 消息是否可见 + isReadable(msgData) { + return false + }, + + // 消息是否需要保存到数据库 + noPersistent(msgData) { + return true + }, + + // 消息推送时是否需要厂商通道(离线也能推送) + noPushOffline(msgData) { + return true + }, + + /** + * @param {object} params 客户端调用云端 sendMsg() 时提交的参数对象 + * @param {string} current_uid 当前用户 + */ + async beforeSendMsg(params, current_uid) { + const db = uniCloud.database() + const dbCmd = db.command + const dbUniImMsg = db.collection('uni-im-msg') + const dbUniImConversation = db.collection('uni-im-conversation') + + // 我的会话包含此消息 + // 1. 查到此消息 + let { data: [msgData] } = await dbUniImMsg + .doc(params.body.msgId) + .field({ + conversation_id: true, + reader_list: true, + group_id: true, + from_uid: true, + appid: true + }) + .get() + if (!msgData) { + throw new Error('要设为已读的消息不存在') + } else { + // console.log('msgData', msgData); + } + if (msgData.reader_list && msgData.reader_list.some(item => item.user_id == current_uid)) { + throw new Error('你已经在此消息的已读列表中') + } + // 2. 查我是否在这个消息所在的会话中,且此会话必须有效 + let { data: [conversation] } = await dbUniImConversation + .where({ + id: msgData.conversation_id, + user_id: current_uid, + leave: dbCmd.neq(true) + }) + .get() + // console.log('read_msg.beforeSendMsg: conversation:', conversation); + if (!conversation) { + throw new Error('你不在“要设为已读的消息”所在的会话') + } + // 3. 往这个消息的已读字段中,添加当前用户的id + let res = await dbUniImMsg + .doc(params.body.msgId) + .update({ + reader_list: dbCmd.push({ + user_id: current_uid, + create_time: Date.now() + }) + }) + // console.log('res', res); + params.appId = msgData.appid + if (msgData.group_id) { + params.group_id = msgData.group_id + } else { + // 通知消息的发送者,有人读了他的消息 + params.to_uid = msgData.from_uid + } + } + } +} + +module.exports = { + onMsgTypeRegister, +} diff --git a/uni_modules/uni-im-msg-reader/uniCloud/cloudfunctions/common/uni-im-ext-msg-reader/package.json b/uni_modules/uni-im-msg-reader/uniCloud/cloudfunctions/common/uni-im-ext-msg-reader/package.json new file mode 100644 index 0000000000000000000000000000000000000000..8fb18f516c221fab635e98fa397b6aac29ac5d24 --- /dev/null +++ b/uni_modules/uni-im-msg-reader/uniCloud/cloudfunctions/common/uni-im-ext-msg-reader/package.json @@ -0,0 +1,14 @@ +{ + "name": "uni-im-ext-msg-reader", + "version": "1.0.0", + "description": "", + "main": "index.js", + "keywords": [], + "author": "", + "license": "ISC", + "uni-im-ext": { + "extensions": { + "msg-type-register": "onMsgTypeRegister" + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-link/changelog.md b/uni_modules/uni-link/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..7f43317a38f4e4f643eb416baf03638a9043f232 --- /dev/null +++ b/uni_modules/uni-link/changelog.md @@ -0,0 +1,17 @@ +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-link](https://uniapp.dcloud.io/component/uniui/uni-link) +## 1.1.7(2021-11-08) +## 0.0.7(2021-09-03) +- 修复 在 nvue 下不显示的 bug +## 0.0.6(2021-07-30) +- 新增 支持自定义插槽 +## 0.0.5(2021-06-21) +- 新增 download 属性,H5平台下载文件名 +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-03-09) +- 新增 href 属性支持 tel:|mailto: + +## 0.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-link/components/uni-link/uni-link.vue b/uni_modules/uni-link/components/uni-link/uni-link.vue new file mode 100644 index 0000000000000000000000000000000000000000..8d720039fc61e8a16a29d81f4098d623ee67df41 --- /dev/null +++ b/uni_modules/uni-link/components/uni-link/uni-link.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/uni_modules/uni-link/package.json b/uni_modules/uni-link/package.json new file mode 100644 index 0000000000000000000000000000000000000000..3e994711677850872dbb05dbb5892450ae71b1b2 --- /dev/null +++ b/uni_modules/uni-link/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-link", + "displayName": "uni-link 超链接", + "version": "1.0.0", + "description": "uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打", + "keywords": [ + "uni-ui", + "uniui", + "link", + "超链接", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "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" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-link/readme.md b/uni_modules/uni-link/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..d40fdcadcbead3eb1e0d945bb1958a377d15c211 --- /dev/null +++ b/uni_modules/uni-link/readme.md @@ -0,0 +1,11 @@ + + +## Link 链接 +> **组件名:uni-link** +> 代码块: `uLink` + + +uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-link) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uni_modules/uni-list/changelog.md b/uni_modules/uni-list/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..8254a1851abf936feb4623d2ce8ca84ee3b25a69 --- /dev/null +++ b/uni_modules/uni-list/changelog.md @@ -0,0 +1,46 @@ +## 1.2.14(2023-04-14) +- 优化 uni-list-chat 具名插槽`header` 非app端套一层元素,方便使用时通过外层元素定位实现样式修改 +## 1.2.13(2023-03-03) +- uni-list-chat 新增 支持具名插槽`header` +## 1.2.12(2023-02-01) +- 新增 列表图标新增 customPrefix 属性 ,用法 [详见](https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html#icons-props) +## 1.2.11(2023-01-31) +- 修复 无反馈效果呈现的bug +## 1.2.9(2022-11-22) +- 修复 uni-list-chat 在vue3下跳转报错的bug +## 1.2.8(2022-11-21) +- 修复 uni-list-chat avatar属性 值为本地路径时错误的问题 +## 1.2.7(2022-11-21) +- 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题 +## 1.2.6(2022-11-18) +- 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题 +## 1.2.5(2022-11-15) +- 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug +## 1.2.4(2022-11-15) +- 修复 uni-list-item 的 customStyle 属性 padding值在nvue(vue2)下无效的bug +## 1.2.3(2022-11-14) +- uni-list-chat 新增 avatar 支持 fileId +## 1.2.2(2022-11-11) +- uni-list 新增属性 render-reverse 详情参考:[https://uniapp.dcloud.net.cn/component/list.html](https://uniapp.dcloud.net.cn/component/list.html) +- uni-list-chat note属性 支持:“草稿”字样 加红显示 详情参考uni-im:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +- uni-list-item 新增属性 customStyle 支持设置padding、backgroundColor +## 1.2.1(2022-03-30) +- 删除无用文件 +## 1.2.0(2021-11-23) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-list](https://uniapp.dcloud.io/component/uniui/uni-list) +## 1.1.3(2021-08-30) +- 修复 在vue3中to属性在发行应用的时候报错的bug +## 1.1.2(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.1(2021-07-21) +- 修复 与其他组件嵌套使用时,点击失效的Bug +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.17(2021-05-12) +- 新增 组件示例地址 +## 1.0.16(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.15(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 uni-list-chat 角标显示不正常的问题 diff --git a/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue b/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue new file mode 100644 index 0000000000000000000000000000000000000000..d145b5498febefe29d34fee987d7bd3122a0e6d8 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss new file mode 100644 index 0000000000000000000000000000000000000000..7e2708f74c2643208872ef6fa4d2c1f014328639 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss @@ -0,0 +1,58 @@ +/** + * 这里是 uni-list 组件内置的常用样式变量 + * 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码 + * + */ + +// 背景色 +$background-color : #fff; +// 分割线颜色 +$divide-line-color : #e5e5e5; + +// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像 +// nvue 页面不支持修改头像大小 +$avatar-width : 45px ; + +// 头像边框 +$avatar-border-radius: 5px; +$avatar-border-color: #eee; +$avatar-border-width: 1px; + +// 标题文字样式 +$title-size : 16px; +$title-color : #3b4144; +$title-weight : normal; + +// 描述文字样式 +$note-size : 12px; +$note-color : #999; +$note-weight : normal; + +// 右侧额外内容默认样式 +$right-text-size : 12px; +$right-text-color : #999; +$right-text-weight : normal; + +// 角标样式 +// nvue 页面不支持修改圆点位置以及大小 +// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动 +$badge-left: 0px; +$badge-top: 0px; + +// 显示圆点时,圆点大小 +$dot-width: 10px; +$dot-height: 10px; + +// 显示角标时,角标大小和字体大小 +$badge-size : 18px; +$badge-font : 12px; +// 显示角标时,角标前景色 +$badge-color : #fff; +// 显示角标时,角标背景色 +$badge-background-color : #ff5a5f; +// 显示角标时,角标左右间距 +$badge-space : 6px; + +// 状态样式 +// 选中颜色 +$hover : #f5f5f5; diff --git a/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue new file mode 100644 index 0000000000000000000000000000000000000000..27d05ddbc8231b9cb515fba36d13ab51971be5ef --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue @@ -0,0 +1,593 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue b/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue new file mode 100644 index 0000000000000000000000000000000000000000..c25779e4d8a0cc84c4853e5257cf789e8a76d07d --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue @@ -0,0 +1,534 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-list/components/uni-list/uni-list.vue b/uni_modules/uni-list/components/uni-list/uni-list.vue new file mode 100644 index 0000000000000000000000000000000000000000..38470db26753af31632ecc8216acde5e2728c973 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list/uni-list.vue @@ -0,0 +1,123 @@ + + + + diff --git a/uni_modules/uni-list/components/uni-list/uni-refresh.vue b/uni_modules/uni-list/components/uni-list/uni-refresh.vue new file mode 100644 index 0000000000000000000000000000000000000000..3b4c5a230061935d31d8332663b8a511b5143759 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list/uni-refresh.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list/uni-refresh.wxs b/uni_modules/uni-list/components/uni-list/uni-refresh.wxs new file mode 100644 index 0000000000000000000000000000000000000000..818a6b721b1172073d91c1f6a456d2b54d772f77 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list/uni-refresh.wxs @@ -0,0 +1,87 @@ +var pullDown = { + threshold: 95, + maxHeight: 200, + callRefresh: 'onrefresh', + callPullingDown: 'onpullingdown', + refreshSelector: '.uni-refresh' +}; + +function ready(newValue, oldValue, ownerInstance, instance) { + var state = instance.getState() + state.canPullDown = newValue; + // console.log(newValue); +} + +function touchStart(e, instance) { + var state = instance.getState(); + state.refreshInstance = instance.selectComponent(pullDown.refreshSelector); + state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined); + if (!state.canPullDown) { + return + } + + // console.log("touchStart"); + + state.height = 0; + state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY; + state.refreshInstance.setStyle({ + 'height': 0 + }); + state.refreshInstance.callMethod("onchange", true); +} + +function touchMove(e, ownerInstance) { + var instance = e.instance; + var state = instance.getState(); + if (!state.canPullDown) { + return + } + + var oldHeight = state.height; + var endY = e.touches[0].pageY || e.changedTouches[0].pageY; + var height = endY - state.touchStartY; + if (height > pullDown.maxHeight) { + return; + } + + var refreshInstance = state.refreshInstance; + refreshInstance.setStyle({ + 'height': height + 'px' + }); + + height = height < pullDown.maxHeight ? height : pullDown.maxHeight; + state.height = height; + refreshInstance.callMethod(pullDown.callPullingDown, { + height: height + }); +} + +function touchEnd(e, ownerInstance) { + var state = e.instance.getState(); + if (!state.canPullDown) { + return + } + + state.refreshInstance.callMethod("onchange", false); + + var refreshInstance = state.refreshInstance; + if (state.height > pullDown.threshold) { + refreshInstance.callMethod(pullDown.callRefresh); + return; + } + + refreshInstance.setStyle({ + 'height': 0 + }); +} + +function propObserver(newValue, oldValue, instance) { + pullDown = newValue; +} + +module.exports = { + touchmove: touchMove, + touchstart: touchStart, + touchend: touchEnd, + propObserver: propObserver +} diff --git a/uni_modules/uni-list/package.json b/uni_modules/uni-list/package.json new file mode 100644 index 0000000000000000000000000000000000000000..8350efcede660304b2659e81315bee770057503a --- /dev/null +++ b/uni_modules/uni-list/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-list", + "displayName": "uni-list 列表", + "version": "1.2.14", + "description": "List 组件 ,帮助使用者快速构建列表。", + "keywords": [ + "", + "uni-ui", + "uniui", + "列表", + "", + "list" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-badge", + "uni-icons" + ], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-list/readme.md b/uni_modules/uni-list/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..32c28654eb9654bfa833a5de6012f44c4fe854db --- /dev/null +++ b/uni_modules/uni-list/readme.md @@ -0,0 +1,346 @@ +## List 列表 +> **组件名:uni-list** +> 代码块: `uList`、`uListItem` +> 关联组件:`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad` + + +List 列表组件,包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。 + +在vue页面里,它默认使用页面级滚动。在app-nvue页面里,它默认使用原生list组件滚动。这样的长列表,在滚动出屏幕外后,系统会回收不可见区域的渲染内存资源,不会造成滚动越长手机越卡的问题。 + +uni-list组件是父容器,里面的核心是uni-list-item子组件,它代表列表中的一个可重复行,子组件可以无限循环。 + +uni-list-item有很多风格,uni-list-item组件通过内置的属性,满足一些常用的场景。当内置属性不满足需求时,可以通过扩展插槽来自定义列表内容。 + +内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。 + +涉及很多大图或丰富内容的列表,比如类今日头条的新闻列表、类淘宝的电商列表,需要通过扩展插槽实现。 + +下文均有样例给出。 + +uni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件:[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29) + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件 +> - `uni-list` 和 `uni-list-item` 需要配套使用,暂不支持单独使用 `uni-list-item` +> - 只有开启点击反馈后,会有点击选中效果 +> - 使用插槽时,可以完全自定义内容 +> - note 、rightText 属性暂时没做限制,不支持文字溢出隐藏,使用时应该控制长度显示或通过默认插槽自行扩展 +> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: 详情 --> 项目配置 --> 启用 component2 编译 +> - 如果需要修改 `switch`、`badge` 样式,请使用插槽自定义 +> - 在 `HBuilderX` 低版本中,可能会出现组件显示 `undefined` 的问题,请升级最新的 `HBuilderX` 或者 `cli` +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 基本用法 + +- 设置 `title` 属性,可以显示列表标题 +- 设置 `disabled` 属性,可以禁用当前项 + +```html + + + + + +``` + +### 多行内容显示 + +- 设置 `note` 属性 ,可以在第二行显示描述文本信息 + +```html + + + + + +``` + +### 右侧显示角标、switch + +- 设置 `show-badge` 属性 ,可以显示角标内容 +- 设置 `show-switch` 属性,可以显示 switch 开关 + +```html + + + + + +``` + +### 左侧显示略缩图、图标 + +- 设置 `thumb` 属性 ,可以在列表左侧显示略缩图 +- 设置 `show-extra-icon` 属性,并指定 `extra-icon` 可以在左侧显示图标 + +```html + + + + +``` + +### 开启点击反馈和右侧箭头 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,并给列表右侧添加一个箭头 +- 设置 `to` 属性,可以跳转页面,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` + +```html + + + + + + + +``` + + +### 聊天列表示例 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` +- 设置 `to` 属性,可以跳转页面 +- `time` 属性,通常会设置成时间显示,但是这个属性不仅仅可以设置时间,你可以传入任何文本,注意文本长度可能会影响显示 +- `avatar` 和 `avatarList` 属性同时只会有一个生效,同时设置的话,`avatarList` 属性的长度大于1 ,`avatar` 属性将失效 +- 可以通过默认插槽自定义列表右侧内容 + +```html + + + + + + + + + + + + + + + + + 刚刚 + + + + + + + +``` + +```javascript + +export default { + components: {}, + data() { + return { + avatarList: [{ + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }] + } + } +} + +``` + + +```css + +.chat-custom-right { + flex: 1; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: space-between; + align-items: flex-end; +} + +.chat-custom-text { + font-size: 12px; + color: #999; +} + +``` + +## API + +### List Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +border |Boolean |true | 是否显示边框 + + +### ListItem Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +ellipsis |Number |0 | title 是否溢出隐藏,可选值,0:默认; 1:显示一行; 2:显示两行;【nvue 暂不支持】 +thumb |String |- | 左侧缩略图,若thumb有值,则不会显示扩展图标 +thumbSize |String |medium | 略缩图尺寸,可选值,lg:大图; medium:一般; sm:小图; +showBadge |Boolean |false | 是否显示数字角标 +badgeText |String |- | 数字角标内容 +badgeType |String |- | 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21) +badgeStyle |Object |- | 数字角标样式,使用uni-badge的custom-style参数 +rightText |String |- | 右侧文字内容 +disabled |Boolean |false | 是否禁用 +showArrow |Boolean |true | 是否显示箭头图标 +link |String |navigateTo | 新页面跳转方式,可选值见下表 +to |String |- | 新页面跳转地址,如填写此属性,click 会返回页面是否跳转成功 +clickable |Boolean |false | 是否开启点击反馈 +showSwitch |Boolean |false | 是否显示Switch +switchChecked |Boolean |false | Switch是否被选中 +showExtraIcon |Boolean |false | 左侧是否显示扩展图标 +extraIcon |Object |- | 扩展图标参数,格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``,参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28) +direction | String |row | 排版方向,可选值,row:水平排列; column:垂直排列; 3个插槽是水平排还是垂直排,也受此属性控制 + + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItem Events + +事件称名 |说明 |返回参数 +:-: |:-: |:-: +click |点击 uniListItem 触发事件,需开启点击反馈 |- +switchChange |点击切换 Switch 时触发,需显示 switch |e={value:checked} + + + +### ListItem Slots + +名称 | 说明 +:-: | :-: +header | 左/上内容插槽,可完全自定义默认显示 +body | 中间内容插槽,可完全自定义中间内容 +footer | 右/下内容插槽,可完全自定义右侧内容 + + +> **通过插槽扩展** +> 需要注意的是当使用插槽时,内置样式将会失效,只保留排版样式,此时的样式需要开发者自己实现 +> 如果 `uni-list-item` 组件内置属性样式无法满足需求,可以使用插槽来自定义uni-list-item里的内容。 +> uni-list-item提供了3个可扩展的插槽:`header`、`body`、`footer` +> - 当 `direction` 属性为 `row` 时表示水平排列,此时 `header` 表示列表的左边部分,`body` 表示列表的中间部分,`footer` 表示列表的右边部分 +> - 当 `direction` 属性为 `column` 时表示垂直排列,此时 `header` 表示列表的上边部分,`body` 表示列表的中间部分,`footer` 表示列表的下边部分 +> 开发者可以只用1个插槽,也可以3个一起使用。在插槽中可自主编写view标签,实现自己所需的效果。 + + +**示例** + +```html + + + + + + + + + 自定义插槽 + + + + +``` + + + + + +### ListItemChat Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +clickable |Boolean |false | 是否开启点击反馈 +badgeText |String |- | 数字角标内容,设置为 `dot` 将显示圆点 +badgePositon |String |right | 角标位置 +link |String |navigateTo | 是否展示右侧箭头并开启点击反馈,可选值见下表 +clickable |Boolean |false | 是否开启点击反馈 +to |String |- | 跳转页面地址,如填写此属性,click 会返回页面是否跳转成功 +time |String |- | 右侧时间显示 +avatarCircle |Boolean |false | 是否显示圆形头像 +avatar |String |- | 头像地址,avatarCircle 不填时生效 +avatarList |Array |- | 头像组,格式为 [{url:''}] + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItemChat Slots + +名称 | 说明 +:- | :- +default | 自定义列表右侧内容(包括时间和角标显示) + +### ListItemChat Events +事件称名 | 说明 | 返回参数 +:-: | :-: | :-: +@click | 点击 uniListChat 触发事件 | {data:{}} ,如有 to 属性,会返回页面跳转信息 + + + + + + +## 基于uni-list扩展的页面模板 + +通过扩展插槽,可实现多种常见样式的列表 + +**新闻列表类** + +1. 云端一体混合布局:[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546) +2. 云端一体垂直布局,大图模式:[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583) +3. 云端一体垂直布局,多行图文混排:[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584) +4. 云端一体垂直布局,多图模式:[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585) +5. 云端一体水平布局,左图右文:[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586) +6. 云端一体水平布局,左文右图:[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587) +7. 云端一体垂直布局,无图模式,主标题+副标题:[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588) + +**商品列表类** + +1. 云端一体列表/宫格视图互切:[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651) +2. 云端一体列表(宫格模式):[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671) +3. 云端一体列表(列表模式):[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672) + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list) \ No newline at end of file diff --git a/uni_modules/uni-load-more/changelog.md b/uni_modules/uni-load-more/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..8f03f1d57a158042a6c3f0ce254569f37447e845 --- /dev/null +++ b/uni_modules/uni-load-more/changelog.md @@ -0,0 +1,19 @@ +## 1.3.3(2022-01-20) +- 新增 showText属性 ,是否显示文本 +## 1.3.2(2022-01-19) +- 修复 nvue 平台下不显示文本的bug +## 1.3.1(2022-01-19) +- 修复 微信小程序平台样式选择器报警告的问题 +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-load-more](https://uniapp.dcloud.io/component/uniui/uni-load-more) +## 1.2.1(2021-08-24) +- 新增 支持国际化 +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.8(2021-05-12) +- 新增 组件示例地址 +## 1.1.7(2021-03-30) +- 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug +## 1.1.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json b/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json new file mode 100644 index 0000000000000000000000000000000000000000..6f45b0ec86cebdb367de41b7cb9c6ababcf6ed8f --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "Pull up to show more", + "uni-load-more.contentrefresh": "loading...", + "uni-load-more.contentnomore": "No more data" +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js b/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fa8f0f3734de6effb137592fce6c0f722c048432 --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json new file mode 100644 index 0000000000000000000000000000000000000000..3a14ca07bdc3208a3376f0a2fab28dd44d81e1fb --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉显示更多", + "uni-load-more.contentrefresh": "正在加载...", + "uni-load-more.contentnomore": "没有更多数据了" +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json new file mode 100644 index 0000000000000000000000000000000000000000..ee99b0678292e75f9eedb3e4c476424a42288e64 --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉顯示更多", + "uni-load-more.contentrefresh": "正在加載...", + "uni-load-more.contentnomore": "沒有更多數據了" +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue b/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue new file mode 100644 index 0000000000000000000000000000000000000000..ededbc20d39978894c42cf4a62954c5f67ba6951 --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/uni_modules/uni-load-more/package.json b/uni_modules/uni-load-more/package.json new file mode 100644 index 0000000000000000000000000000000000000000..2fa6f040a8952a8246f9dd9a875f6b95e21996f6 --- /dev/null +++ b/uni_modules/uni-load-more/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-load-more", + "displayName": "uni-load-more 加载更多", + "version": "1.3.3", + "description": "LoadMore 组件,常用在列表里面,做滚动加载使用。", + "keywords": [ + "uni-ui", + "uniui", + "加载更多", + "load-more" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-load-more/readme.md b/uni_modules/uni-load-more/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..54dc1fad29ca3906f3498530878791679e63a7bf --- /dev/null +++ b/uni_modules/uni-load-more/readme.md @@ -0,0 +1,14 @@ + + +### LoadMore 加载更多 +> **组件名:uni-load-more** +> 代码块: `uLoadMore` + + +用于列表中,做滚动加载使用,展示 loading 的各种状态。 + + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-load-more) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uni_modules/uni-nav-bar/changelog.md b/uni_modules/uni-nav-bar/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..0f9a2f1b48b6e26f4fe603426a6369b2d2e7f2c6 --- /dev/null +++ b/uni_modules/uni-nav-bar/changelog.md @@ -0,0 +1,51 @@ +## 1.3.11(2023-03-29) +- 修复 自定义状态栏高度闪动BUG +## 1.3.10(2023-03-29) +- 修复 暗黑模式下边线颜色错误的bug +## 1.3.9(2022-10-13) +- 修复 条件编译错误的bug +## 1.3.8(2022-10-12) +- 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug +## 1.3.7(2022-08-11) +- 修复 nvue 环境下 fixed 为 true 的情况下,无法置顶的 bug +## 1.3.6(2022-06-30) +- 修复 组件示例中插槽用法无法显示内容的bug +## 1.3.5(2022-05-24) +- 新增 stat 属性 ,可开启统计title 上报 ,仅使用了title 属性且项目开启了uni统计生效 +## 1.3.4(2022-01-24) +- 更新 组件示例 +## 1.3.3(2022-01-24) +- 新增 left-width/right-width属性 ,可修改左右两侧的宽度 +## 1.3.2(2022-01-18) +- 修复 在vue下,标题不垂直居中的bug +## 1.3.1(2022-01-18) +- 修复 height 属性类型错误 +## 1.3.0(2022-01-18) +- 新增 height 属性,可修改组件高度 +- 新增 dark 属性可可开启暗黑模式 +- 优化 标题字数过多显示省略号 +- 优化 插槽,插入内容可完全覆盖 +## 1.2.1(2022-01-10) +- 修复 color 属性不生效的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-nav-bar](https://uniapp.dcloud.io/component/uniui/uni-nav-bar) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-30) +- 修复 在nvue下fixed为true,宽度不能撑满的Bug +## 1.0.9(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.8(2021-04-14) +- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug + +## 1.0.7(2021-02-25) +- 修复 easycom 下,找不到 uni-status-bar 的bug + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue new file mode 100644 index 0000000000000000000000000000000000000000..92634f1ee8a1c8139dac502aac60f7ba3f9d9c32 --- /dev/null +++ b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue new file mode 100644 index 0000000000000000000000000000000000000000..0a5909d1cd0996ef30f1bd015a545ed75691b450 --- /dev/null +++ b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/uni_modules/uni-nav-bar/package.json b/uni_modules/uni-nav-bar/package.json new file mode 100644 index 0000000000000000000000000000000000000000..240ae959df2df01566cd9f7642b5c0a20ecb5989 --- /dev/null +++ b/uni_modules/uni-nav-bar/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-nav-bar", + "displayName": "uni-nav-bar 自定义导航栏", + "version": "1.3.11", + "description": "自定义导航栏组件,主要用于头部导航。", + "keywords": [ + "uni-ui", + "导航", + "导航栏", + "自定义导航栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-nav-bar/readme.md b/uni_modules/uni-nav-bar/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..3934b3277583a4a8073d2b42a367d237f748157a --- /dev/null +++ b/uni_modules/uni-nav-bar/readme.md @@ -0,0 +1,15 @@ + + +## NavBar 导航栏 +> **组件名:uni-nav-bar** +> 代码块: `uNavBar` + +导航栏组件,主要用于头部导航。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-nav-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/uni_modules/uni-open-bridge-common/changelog.md b/uni_modules/uni-open-bridge-common/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..e97c5353aec5d3e5ce3687b436f6f8bbf0031e5e --- /dev/null +++ b/uni_modules/uni-open-bridge-common/changelog.md @@ -0,0 +1,25 @@ +## 1.2.0(2023-04-27) +- 优化 微信小程序平台 使用微信新增 API getStableAccessToken 获取 access_token, access_token 有效期内重复调用该接口不会更新 access_token, [详情](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getStableAccessToken.html) +## 1.1.5(2023-03-27) +- 修复 微信小程序平台 某些情况下 encrypt_key 插入错误的问题 +## 1.1.4(2023-03-13) +- 修复 平台 weixin-web +## 1.1.3(2023-03-13) +- 新增 支持旧版本 uni-id 配置 +- 新增 支持平台 weixin-app|qq-mp|qq-app +## 1.1.2(2023-02-28) +- 新增 config 配置错误提示语 +## 1.1.1(2023-02-28) +- 新增 支持 provider 参数,和 platform 保持一致 +## 1.1.0(2023-02-27) +- 重要更新 调整数据库key格式,兼容旧版本API,如果开发者通过手动拼接key查询数据库需要修改现有逻辑 + + 原格式: uni-id:[dcloudAppid]:[platform]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket] + + 新格式: uni-id:[provider]:[appid]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket] +## 1.0.4(2022-09-21) +- 新增 支持使用阿里云固定IP获取微信公众号H5凭据 access_token、ticket,开发者需要在微信公众平台配置阿里云固定IP,[固定IP详情](https://uniapp.dcloud.net.cn/uniCloud/cf-functions.html#aliyun-eip) +## 1.0.3(2022-09-06) +- 修复 过期时间问题,容错 AccessToken 默认 fallback 逻辑,当微信服务器没有返回过期时间时设置为2小时后过期 +## 1.0.2(2022-09-02) +- 新增 依赖数据表schema opendb-open-data +## 1.0.0(2022-08-22) +- 首次发布 diff --git a/uni_modules/uni-open-bridge-common/package.json b/uni_modules/uni-open-bridge-common/package.json new file mode 100644 index 0000000000000000000000000000000000000000..30f262015027bfb289df352a768d4da8195fae72 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-open-bridge-common", + "displayName": "uni-open-bridge-common", + "version": "1.2.0", + "description": "统一接管微信等三方平台认证凭据", + "keywords": [ + "uni-open-bridge-common", + "access_token", + "session_key", + "ticket" +], + "repository": "", + "engines": { + "HBuilderX": "^3.5.2" + }, + "dcloudext": { + "type": "unicloud-template-function", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "u", + "vue3": "u" + }, + "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" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-open-bridge-common/readme.md b/uni_modules/uni-open-bridge-common/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..de28c36e067fb692b8f4cd23bc80941ea956e305 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/readme.md @@ -0,0 +1,5 @@ +# uni-open-bridge-common + +`uni-open-bridge-common` 是统一接管微信等三方平台认证凭据(包括但不限于`access_token`、`session_key`、`encrypt_key`、`ticket`)的开源库。 + +文档链接 [https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge#common](https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge#common) diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js new file mode 100644 index 0000000000000000000000000000000000000000..b6cfe66170d1958f57cee2e3715fc4984682c379 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/bridge-error.js @@ -0,0 +1,26 @@ +'use strict'; + +class BridgeError extends Error { + + constructor(code, message) { + super(message) + + this._code = code + } + + get code() { + return this._code + } + + get errCode() { + return this._code + } + + get errMsg() { + return this.message + } +} + +module.exports = { + BridgeError +} diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js new file mode 100644 index 0000000000000000000000000000000000000000..063c7469406bf2f6bd303da408b64f72e02f4495 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/config.js @@ -0,0 +1,124 @@ +'use strict'; + +const { + ProviderType +} = require('./consts.js') + +const configCenter = require('uni-config-center') + +// 多维数据为兼容uni-id以前版本配置 +const OauthConfig = { + 'weixin-app': [ + ['app', 'oauth', 'weixin'], + ['app-plus', 'oauth', 'weixin'] + ], + 'weixin-mp': [ + ['mp-weixin', 'oauth', 'weixin'] + ], + 'weixin-h5': [ + ['web', 'oauth', 'weixin-h5'], + ['h5-weixin', 'oauth', 'weixin'], + ['h5', 'oauth', 'weixin'] + ], + 'weixin-web': [ + ['web', 'oauth', 'weixin-web'] + ], + 'qq-app': [ + ['app', 'oauth', 'qq'], + ['app-plus', 'oauth', 'qq'] + ], + 'qq-mp': [ + ['mp-qq', 'oauth', 'qq'] + ] +} + +const Support_Platforms = [ + ProviderType.WEIXIN_MP, + ProviderType.WEIXIN_H5, + ProviderType.WEIXIN_APP, + ProviderType.WEIXIN_WEB, + ProviderType.QQ_MP, + ProviderType.QQ_APP +] + +class ConfigBase { + + constructor() { + const uniIdConfigCenter = configCenter({ + pluginId: 'uni-id' + }) + + this._uniIdConfig = uniIdConfigCenter.config() + } + + getAppConfig(appid) { + if (Array.isArray(this._uniIdConfig)) { + return this._uniIdConfig.find((item) => { + return (item.dcloudAppid === appid) + }) + } + return this._uniIdConfig + } +} + +class AppConfig extends ConfigBase { + + constructor() { + super() + } + + get(appid, platform) { + if (!this.isSupport(platform)) { + return null + } + + let appConfig = this.getAppConfig(appid) + if (!appConfig) { + return null + } + + return this.getOauthConfig(appConfig, platform) + } + + isSupport(platformName) { + return (Support_Platforms.indexOf(platformName) >= 0) + } + + getOauthConfig(appConfig, platformName) { + let treePath = OauthConfig[platformName] + let node = this.findNode(appConfig, treePath) + if (node && node.appid && node.appsecret) { + return { + appid: node.appid, + secret: node.appsecret + } + } + return null + } + + findNode(treeNode, arrayPath) { + let node = treeNode + for (let treePath of arrayPath) { + for (let name of treePath) { + const currentNode = node[name] + if (currentNode) { + node = currentNode + } else { + node = null + break + } + } + if (node === null) { + node = treeNode + } else { + break + } + } + return node + } +} + + +module.exports = { + AppConfig +}; \ No newline at end of file diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js new file mode 100644 index 0000000000000000000000000000000000000000..d89a5c1274c35bf9beb11037f68e0b5feed94371 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/consts.js @@ -0,0 +1,30 @@ +'use strict'; + +const TAG = "UNI_OPEN_BRIDGE" + +const HTTP_STATUS = { + SUCCESS: 200 +} + +const ProviderType = { + WEIXIN_MP: 'weixin-mp', + WEIXIN_H5: 'weixin-h5', + WEIXIN_APP: 'weixin-app', + WEIXIN_WEB: 'weixin-web', + QQ_MP: 'qq-mp', + QQ_APP: 'qq-app' +} + +// old +const PlatformType = ProviderType + +const ErrorCodeType = { + SYSTEM_ERROR: TAG + "_SYSTEM_ERROR" +} + +module.exports = { + HTTP_STATUS, + ProviderType, + PlatformType, + ErrorCodeType +} diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js new file mode 100644 index 0000000000000000000000000000000000000000..a8dba4b3b2933e8379e8487826f81aec5e2c1fda --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/index.js @@ -0,0 +1,317 @@ +'use strict'; + +const { + PlatformType, + ProviderType, + ErrorCodeType +} = require('./consts.js') + +const { + AppConfig +} = require('./config.js') + +const { + Storage +} = require('./storage.js') + +const { + BridgeError +} = require('./bridge-error.js') + +const { + WeixinServer +} = require('./weixin-server.js') + +const appConfig = new AppConfig() + +class AccessToken extends Storage { + + constructor() { + super('access-token', ['provider', 'appid']) + } + + async update(key) { + super.update(key) + + const result = await this.getByWeixinServer(key) + + return this.set(key, result.value, result.duration) + } + + async fallback(key) { + return this.getByWeixinServer(key) + } + + async getByWeixinServer(key) { + const oauthConfig = appConfig.get(key.dcloudAppid, key.provider) + let methodName + if (key.provider === ProviderType.WEIXIN_MP) { + methodName = 'GetMPAccessTokenData' + } else if (key.provider === ProviderType.WEIXIN_H5) { + methodName = 'GetH5AccessTokenData' + } else { + throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, "provider invalid") + } + + const responseData = await WeixinServer[methodName](oauthConfig) + + const duration = responseData.expires_in || (60 * 60 * 2) + delete responseData.expires_in + + return { + value: responseData, + duration + } + } +} + +class UserAccessToken extends Storage { + + constructor() { + super('user-access-token', ['provider', 'appid', 'openid']) + } +} + +class SessionKey extends Storage { + + constructor() { + super('session-key', ['provider', 'appid', 'openid']) + } +} + +class Encryptkey extends Storage { + + constructor() { + super('encrypt-key', ['provider', 'appid', 'openid']) + } + + async update(key) { + super.update(key) + + const result = await this.getByWeixinServer(key) + + return this.set(key, result.value, result.duration) + } + + getKeyString(key) { + return `${super.getKeyString(key)}-${key.version}` + } + + getExpiresIn(value) { + if (value <= 0) { + return 60 + } + return value + } + + async fallback(key) { + return this.getByWeixinServer(key) + } + + async getByWeixinServer(key) { + const accessToken = await Factory.Get(AccessToken, key) + const userSession = await Factory.Get(SessionKey, key) + + const responseData = await WeixinServer.GetUserEncryptKeyData({ + openid: key.openid, + access_token: accessToken.access_token, + session_key: userSession.session_key + }) + + const keyInfo = responseData.key_info_list.find((item) => { + return item.version === key.version + }) + + if (!keyInfo) { + throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, 'key version invalid') + } + + const value = { + encrypt_key: keyInfo.encrypt_key, + iv: keyInfo.iv + } + + return { + value, + duration: keyInfo.expire_in + } + } +} + +class Ticket extends Storage { + + constructor() { + super('ticket', ['provider', 'appid']) + } + + async update(key) { + super.update(key) + + const result = await this.getByWeixinServer(key) + + return this.set(key, result.value, result.duration) + } + + async fallback(key) { + return this.getByWeixinServer(key) + } + + async getByWeixinServer(key) { + const accessToken = await Factory.Get(AccessToken, { + dcloudAppid: key.dcloudAppid, + provider: ProviderType.WEIXIN_H5 + }) + + const responseData = await WeixinServer.GetH5TicketData(accessToken) + + const duration = responseData.expires_in || (60 * 60 * 2) + delete responseData.expires_in + delete responseData.errcode + delete responseData.errmsg + + return { + value: responseData, + duration + } + } +} + + +const Factory = { + + async Get(T, key, fallback) { + Factory.FixOldKey(key) + return Factory.MakeUnique(T).get(key, fallback) + }, + + async Set(T, key, value, expiresIn) { + Factory.FixOldKey(key) + return Factory.MakeUnique(T).set(key, value, expiresIn) + }, + + async Remove(T, key) { + Factory.FixOldKey(key) + return Factory.MakeUnique(T).remove(key) + }, + + async Update(T, key) { + Factory.FixOldKey(key) + return Factory.MakeUnique(T).update(key) + }, + + FixOldKey(key) { + if (!key.provider) { + key.provider = key.platform + } + + const configData = appConfig.get(key.dcloudAppid, key.provider) + if (!configData) { + throw new BridgeError(ErrorCodeType.SYSTEM_ERROR, 'appid or provider invalid') + } + key.appid = configData.appid + }, + + MakeUnique(T) { + return new T() + } +} + + +// exports + +async function getAccessToken(key, fallback) { + return Factory.Get(AccessToken, key, fallback) +} + +async function setAccessToken(key, value, expiresIn) { + return Factory.Set(AccessToken, key, value, expiresIn) +} + +async function removeAccessToken(key) { + return Factory.Remove(AccessToken, key) +} + +async function updateAccessToken(key) { + return Factory.Update(AccessToken, key) +} + +async function getUserAccessToken(key, fallback) { + return Factory.Get(UserAccessToken, key, fallback) +} + +async function setUserAccessToken(key, value, expiresIn) { + return Factory.Set(UserAccessToken, key, value, expiresIn) +} + +async function removeUserAccessToken(key) { + return Factory.Remove(UserAccessToken, key) +} + +async function getSessionKey(key, fallback) { + return Factory.Get(SessionKey, key, fallback) +} + +async function setSessionKey(key, value, expiresIn) { + return Factory.Set(SessionKey, key, value, expiresIn) +} + +async function removeSessionKey(key) { + return Factory.Remove(SessionKey, key) +} + +async function getEncryptKey(key, fallback) { + return Factory.Get(Encryptkey, key, fallback) +} + +async function setEncryptKey(key, value, expiresIn) { + return Factory.Set(Encryptkey, key, value, expiresIn) +} + +async function removeEncryptKey(key) { + return Factory.Remove(Encryptkey, key) +} + +async function updateEncryptKey(key) { + return Factory.Update(Encryptkey, key) +} + +async function getTicket(key, fallback) { + return Factory.Get(Ticket, key, fallback) +} + +async function setTicket(key, value, expiresIn) { + return Factory.Set(Ticket, key, value, expiresIn) +} + +async function removeTicket(key) { + return Factory.Remove(Ticket, key) +} + +async function updateTicket(key) { + return Factory.Update(Ticket, key) +} + +module.exports = { + getAccessToken, + setAccessToken, + removeAccessToken, + updateAccessToken, + getUserAccessToken, + setUserAccessToken, + removeUserAccessToken, + getSessionKey, + setSessionKey, + removeSessionKey, + getEncryptKey, + setEncryptKey, + removeEncryptKey, + updateEncryptKey, + getTicket, + setTicket, + removeTicket, + updateTicket, + ProviderType, + PlatformType, + WeixinServer, + ErrorCodeType +} diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/package.json b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a017b49a3472401b639652785e3583fa26e3cee8 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/package.json @@ -0,0 +1,15 @@ +{ + "name": "uni-open-bridge-common", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center" + } +} \ No newline at end of file diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js new file mode 100644 index 0000000000000000000000000000000000000000..61e2fb30ef435c7b5ca00d0c285342abf45d79f6 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/storage.js @@ -0,0 +1,111 @@ +'use strict'; + +const { + Validator +} = require('./validator.js') + +const { + CacheKeyCascade +} = require('./uni-cloud-cache.js') + +const { + BridgeError +} = require('./bridge-error.js') + +class Storage { + + constructor(type, keys) { + this._type = type || null + this._keys = keys || [] + } + + async get(key, fallback) { + this.validateKey(key) + const result = await this.create(key, fallback).get() + return result.value + } + + async set(key, value, expiresIn) { + this.validateKey(key) + this.validateValue(value) + const expires_in = this.getExpiresIn(expiresIn) + if (expires_in !== 0) { + await this.create(key).set(this.getValue(value), expires_in) + } + } + + async remove(key) { + this.validateKey(key) + await this.create(key).remove() + } + + // virtual + async update(key) { + this.validateKey(key) + } + + async ttl(key) { + this.validateKey(key) + // 后续考虑支持 + } + + async fallback(key) {} + + getKeyString(key) { + const keyArray = [Storage.Prefix] + this._keys.forEach((name) => { + keyArray.push(key[name]) + }) + keyArray.push(this._type) + return keyArray.join(':') + } + + getValue(value) { + return value + } + + getExpiresIn(value) { + if (value !== undefined) { + return value + } + return -1 + } + + validateKey(key) { + Validator.Key(this._keys, key) + } + + validateValue(value) { + Validator.Value(value) + } + + create(key, fallback) { + const keyString = this.getKeyString(key) + const options = { + layers: [{ + type: 'database', + key: keyString + }, { + type: 'redis', + key: keyString + }] + } + + const _this = this + return new CacheKeyCascade({ + ...options, + fallback: async function() { + if (fallback) { + return fallback(key) + } else if (_this.fallback) { + return _this.fallback(key) + } + } + }) + } +} +Storage.Prefix = "uni-id" + +module.exports = { + Storage +}; diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js new file mode 100644 index 0000000000000000000000000000000000000000..bde572ea87b9ad0c9db9a939ecaeb87cea50d8b8 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/uni-cloud-cache.js @@ -0,0 +1,324 @@ +const db = uniCloud.database() + +function getType(value) { + return Object.prototype.toString.call(value).slice(8, -1).toLowerCase() +} + +const validator = { + key: function(value) { + const err = new Error('Invalid key') + if (typeof value !== 'string') { + throw err + } + const valueTrim = value.trim() + if (!valueTrim || valueTrim !== value) { + throw err + } + }, + value: function(value) { + // 仅作简单校验 + const type = getType(value) + const validValueType = ['null', 'number', 'string', 'array', 'object'] + if (validValueType.indexOf(type) === -1) { + throw new Error('Invalid value type') + } + }, + duration: function(value) { + const err = new Error('Invalid duration') + if (value === undefined) { + return + } + if (typeof value !== 'number' || value === 0) { + throw err + } + } +} + +/** + * 入库时 expired 为过期时间对应的时间戳,永不过期用-1表示 + * 返回结果时 与redis对齐,-1表示永不过期,-2表示已过期或不存在 + */ +class DatabaseCache { + constructor({ + collection = 'opendb-open-data' + } = {}) { + this.type = 'db' + this.collection = db.collection(collection) + } + + _serializeValue(value) { + return value === undefined ? null : JSON.stringify(value) + } + + _deserializeValue(value) { + return value ? JSON.parse(value) : value + } + + async set(key, value, duration) { + validator.key(key) + validator.value(value) + validator.duration(duration) + value = this._serializeValue(value) + await this.collection.doc(key).set({ + value, + expired: duration && duration !== -1 ? Date.now() + (duration * 1000) : -1 + }) + } + + async _getWithDuration(key) { + const getKeyRes = await this.collection.doc(key).get() + const record = getKeyRes.data[0] + if (!record) { + return { + value: null, + duration: -2 + } + } + const value = this._deserializeValue(record.value) + const expired = record.expired + if (expired === -1) { + return { + value, + duration: -1 + } + } + const duration = expired - Date.now() + if (duration <= 0) { + await this.remove(key) + return { + value: null, + duration: -2 + } + } + return { + value, + duration: Math.floor(duration / 1000) + } + } + + async get(key, { + withDuration = true + } = {}) { + const result = await this._getWithDuration(key) + if (!withDuration) { + delete result.duration + } + return result + } + + async remove(key) { + await this.collection.doc(key).remove() + } +} + +class RedisCache { + constructor() { + this.type = 'redis' + this.redis = uniCloud.redis() + } + + _serializeValue(value) { + return value === undefined ? null : JSON.stringify(value) + } + + _deserializeValue(value) { + return value ? JSON.parse(value) : value + } + + async set(key, value, duration) { + validator.key(key) + validator.value(value) + validator.duration(duration) + value = this._serializeValue(value) + if (!duration || duration === -1) { + await this.redis.set(key, value) + } else { + await this.redis.set(key, value, 'EX', duration) + } + } + + async get(key, { + withDuration = false + } = {}) { + let value = await this.redis.get(key) + value = this._deserializeValue(value) + if (!withDuration) { + return { + value + } + } + const durationSecond = await this.redis.ttl(key) + let duration + switch (durationSecond) { + case -1: + duration = -1 + break + case -2: + duration = -2 + break + default: + duration = durationSecond + break + } + return { + value, + duration + } + } + + async remove(key) { + await this.redis.del(key) + } +} + +class Cache { + constructor({ + type, + collection + } = {}) { + if (type === 'database') { + return new DatabaseCache({ + collection + }) + } else if (type === 'redis') { + return new RedisCache() + } else { + throw new Error('Invalid cache type') + } + } +} + +class CacheKey { + constructor({ + type, + collection, + cache, + key, + fallback + } = {}) { + this.cache = cache || new Cache({ + type, + collection + }) + this.key = key + this.fallback = fallback + } + + async set(value, duration) { + await this.cache.set(this.key, value, duration) + } + + async setWithSync(value, duration, syncMethod) { + await Promise.all([ + this.set(this.key, value, duration), + syncMethod(value, duration) + ]) + } + + async get() { + let { + value, + duration + } = await this.cache.get(this.key) + if (value !== null && value !== undefined) { + return { + value, + duration + } + } + if (!this.fallback) { + return { + value: null, + duration: -2 + } + } + const fallbackResult = await this.fallback() + value = fallbackResult.value + duration = fallbackResult.duration + if (value !== null && duration !== undefined) { + await this.cache.set(this.key, value, duration) + } + return { + value, + duration + } + } + + async remove() { + await this.cache.remove(this.key) + } +} + +class CacheKeyCascade { + constructor({ + layers, // [{cache, type, collection, key}] 从低级到高级排序,[DbCacheKey, RedisCacheKey] + fallback + } = {}) { + this.layers = layers + this.cacheLayers = [] + let lastCacheKey + for (let i = 0; i < layers.length; i++) { + const { + type, + cache, + collection, + key + } = layers[i] + const lastCacheKeyTemp = lastCacheKey + try { + const currentCacheKey = new CacheKey({ + type, + collection, + cache, + key, + fallback: i === 0 ? fallback : function() { + return lastCacheKeyTemp.get() + } + }) + this.cacheLayers.push(currentCacheKey) + lastCacheKey = currentCacheKey + } catch (e) {} + } + this.highLevelCache = lastCacheKey + } + + async set(value, duration) { + return Promise.all( + this.cacheLayers.map(item => { + return item.set(value, duration) + }) + ) + } + + async setWithSync(value, duration, syncMethod) { + const setPromise = this.cacheLayers.map(item => { + return item.set(value, duration) + }) + return Promise.all( + [ + ...setPromise, + syncMethod(value, duration) + ] + ) + } + + async get() { + return this.highLevelCache.get() + } + + async remove() { + await Promise.all( + this.cacheLayers.map(cacheKeyItem => { + return cacheKeyItem.remove() + }) + ) + } +} + +module.exports = { + Cache, + DatabaseCache, + RedisCache, + CacheKey, + CacheKeyCascade +} diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js new file mode 100644 index 0000000000000000000000000000000000000000..231dc8b14e84201e4a16a7de2366c4d137eb781f --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/validator.js @@ -0,0 +1,31 @@ +const Validator = { + + Key(keyArray, parameters) { + for (let i = 0; i < keyArray.length; i++) { + const keyName = keyArray[i] + if (typeof parameters[keyName] !== 'string') { + Validator.ThrowNewError(`Invalid ${keyName}`) + } + if (parameters[keyName].length < 1) { + Validator.ThrowNewError(`Invalid ${keyName}`) + } + } + }, + + Value(value) { + if (value === undefined) { + Validator.ThrowNewError('Invalid Value') + } + if (typeof value !== 'object') { + Validator.ThrowNewError('Invalid Value Type') + } + }, + + ThrowNewError(message) { + throw new Error(message) + } +} + +module.exports = { + Validator +} diff --git a/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js new file mode 100644 index 0000000000000000000000000000000000000000..d45e9064e169c80cc32b431cd810e7f82cfd25b7 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common/weixin-server.js @@ -0,0 +1,203 @@ +'use strict'; + +const crypto = require('crypto') + +const { + HTTP_STATUS +} = require('./consts.js') + +const { + BridgeError +} = require('./bridge-error.js') + +class WeixinServer { + + constructor(options = {}) { + this._appid = options.appid + this._secret = options.secret + } + + getAccessToken() { + return uniCloud.httpclient.request(WeixinServer.AccessToken_Url, { + dataType: 'json', + method: 'POST', + contentType: 'json', + data: { + appid: this._appid, + secret: this._secret, + grant_type: "client_credential" + } + }) + } + + // 使用客户端获取的 code 从微信服务器换取 openid,code 仅可使用一次 + codeToSession(code) { + return uniCloud.httpclient.request(WeixinServer.Code2Session_Url, { + dataType: 'json', + data: { + appid: this._appid, + secret: this._secret, + js_code: code, + grant_type: 'authorization_code' + } + }) + } + + getUserEncryptKey({ + access_token, + openid, + session_key + }) { + console.log(access_token, openid, session_key); + const signature = crypto.createHmac('sha256', session_key).update('').digest('hex') + return uniCloud.httpclient.request(WeixinServer.User_Encrypt_Key_Url, { + dataType: 'json', + method: 'POST', + dataAsQueryString: true, + data: { + access_token, + openid: openid, + signature: signature, + sig_method: 'hmac_sha256' + } + }) + } + + getH5AccessToken() { + return uniCloud.httpclient.request(WeixinServer.AccessToken_H5_Url, { + dataType: 'json', + method: 'GET', + data: { + appid: this._appid, + secret: this._secret, + grant_type: "client_credential" + } + }) + } + + getH5Ticket(access_token) { + return uniCloud.httpclient.request(WeixinServer.Ticket_Url, { + dataType: 'json', + dataAsQueryString: true, + method: 'POST', + data: { + access_token + } + }) + } + + getH5AccessTokenForEip() { + return uniCloud.httpProxyForEip.postForm(WeixinServer.AccessToken_H5_Url, { + appid: this._appid, + secret: this._secret, + grant_type: "client_credential" + }, { + dataType: 'json' + }) + } + + getH5TicketForEip(access_token) { + return uniCloud.httpProxyForEip.postForm(WeixinServer.Ticket_Url, { + access_token + }, { + dataType: 'json', + dataAsQueryString: true + }) + } +} + +WeixinServer.AccessToken_Url = 'https://api.weixin.qq.com/cgi-bin/stable_token' +WeixinServer.Code2Session_Url = 'https://api.weixin.qq.com/sns/jscode2session' +WeixinServer.User_Encrypt_Key_Url = 'https://api.weixin.qq.com/wxa/business/getuserencryptkey' +WeixinServer.AccessToken_H5_Url = 'https://api.weixin.qq.com/cgi-bin/token' +WeixinServer.Ticket_Url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi' + +WeixinServer.GetMPAccessToken = function(options) { + return new WeixinServer(options).getAccessToken() +} + +WeixinServer.GetCodeToSession = function(options) { + return new WeixinServer(options).codeToSession(options.code) +} + +WeixinServer.GetUserEncryptKey = function(options) { + return new WeixinServer(options).getUserEncryptKey(options) +} + +WeixinServer.GetH5AccessToken = function(options) { + return new WeixinServer(options).getH5AccessToken() +} + +WeixinServer.GetH5Ticket = function(options) { + return new WeixinServer(options).getH5Ticket(options.access_token) +} + +//////////////////////////////////////////////////////////////// + +function isAliyun() { + return (uniCloud.getCloudInfos()[0].provider === 'aliyun') +} + +WeixinServer.GetResponseData = function(response) { + console.log("WeixinServer::response", response) + + if (!(response.status === HTTP_STATUS.SUCCESS || response.statusCodeValue === HTTP_STATUS.SUCCESS)) { + throw new BridgeError(response.status || response.statusCodeValue, response.status || response.statusCodeValue) + } + + const responseData = response.data || response.body + + if (responseData.errcode !== undefined && responseData.errcode !== 0) { + throw new BridgeError(responseData.errcode, responseData.errmsg) + } + + return responseData +} + +WeixinServer.GetMPAccessTokenData = async function(options) { + const response = await new WeixinServer(options).getAccessToken() + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetCodeToSessionData = async function(options) { + const response = await new WeixinServer(options).codeToSession(options.code) + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetUserEncryptKeyData = async function(options) { + const response = await new WeixinServer(options).getUserEncryptKey(options) + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetH5AccessTokenData = async function(options) { + const ws = new WeixinServer(options) + let response + if (isAliyun()) { + response = await ws.getH5AccessTokenForEip() + if (typeof response === 'string') { + response = JSON.parse(response) + } + } else { + response = await ws.getH5AccessToken() + } + return WeixinServer.GetResponseData(response) +} + +WeixinServer.GetH5TicketData = async function(options) { + const ws = new WeixinServer(options) + let response + if (isAliyun()) { + response = await ws.getH5TicketForEip(options.access_token) + if (typeof response === 'string') { + response = JSON.parse(response) + } + } else { + response = await ws.getH5Ticket(options.access_token) + } + return WeixinServer.GetResponseData(response) +} + + +module.exports = { + WeixinServer +} diff --git a/uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json b/uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..e7774df7d619c6febd408d7c0f5c6284de983835 --- /dev/null +++ b/uni_modules/uni-open-bridge-common/uniCloud/database/opendb-open-data.schema.json @@ -0,0 +1,19 @@ +// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema +{ + "bsonType": "object", + "required": ["_id", "value"], + "properties": { + "_id": { + "bsonType": "string", + "description": "key,格式:uni-id:[provider]:[appid]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]" + }, + "value": { + "bsonType": "object", + "description": "字段_id对应的值" + }, + "expired": { + "bsonType": "date", + "description": "过期时间" + } + } +} diff --git a/uni_modules/uni-popup/changelog.md b/uni_modules/uni-popup/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..decd775ae292efa895da3caf30fb2f3b887904b0 --- /dev/null +++ b/uni_modules/uni-popup/changelog.md @@ -0,0 +1,84 @@ +## 1.9.1(2024-04-02) +- 修复 uni-popup-dialog vue3下使用value无法进行绑定的bug(双向绑定兼容旧写法) +## 1.9.0(2024-03-28) +- 修复 uni-popup-dialog 双向绑定时初始化逻辑修正 +## 1.8.9(2024-03-20) +- 修复 uni-popup-dialog 数据输入时修正为双向绑定 +## 1.8.8(2024-02-20) +- 修复 uni-popup 在微信小程序下出现文字向上闪动的bug +## 1.8.7(2024-02-02) +- 新增 uni-popup-dialog 新增属性focus:input模式下,是否自动自动聚焦 +## 1.8.6(2024-01-30) +- 新增 uni-popup-dialog 新增属性maxLength:限制输入框字数 +## 1.8.5(2024-01-26) +- 新增 uni-popup-dialog 新增属性showClose:控制关闭按钮的显示 +## 1.8.4(2023-11-15) +- 新增 uni-popup 支持uni-app-x 注意暂时仅支持 `maskClick` `@open` `@close` +## 1.8.3(2023-04-17) +- 修复 uni-popup 重复打开时的 bug +## 1.8.2(2023-02-02) +- uni-popup-dialog 组件新增 inputType 属性 +## 1.8.1(2022-12-01) +- 修复 nvue 下 v-show 报错 +## 1.8.0(2022-11-29) +- 优化 主题样式 +## 1.7.9(2022-04-02) +- 修复 弹出层内部无法滚动的bug +## 1.7.8(2022-03-28) +- 修复 小程序中高度错误的bug +## 1.7.7(2022-03-17) +- 修复 快速调用open出现问题的Bug +## 1.7.6(2022-02-14) +- 修复 safeArea 属性不能设置为false的bug +## 1.7.5(2022-01-19) +- 修复 isMaskClick 失效的bug +## 1.7.4(2022-01-19) +- 新增 cancelText \ confirmText 属性 ,可自定义文本 +- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色 +- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题 +## 1.7.3(2022-01-13) +- 修复 设置 safeArea 属性不生效的bug +## 1.7.2(2021-11-26) +- 优化 组件示例 +## 1.7.1(2021-11-26) +- 修复 vuedoc 文字错误 +## 1.7.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup) +## 1.6.2(2021-08-24) +- 新增 支持国际化 +## 1.6.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.6.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.5.0(2021-06-23) +- 新增 mask-click 遮罩层点击事件 +## 1.4.5(2021-06-22) +- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.4(2021-06-18) +- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.3(2021-06-08) +- 修复 错误的 watch 字段 +- 修复 safeArea 属性不生效的问题 +- 修复 点击内容,再点击遮罩无法关闭的Bug +## 1.4.2(2021-05-12) +- 新增 组件示例地址 +## 1.4.1(2021-04-29) +- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题 +## 1.4.0 (2021-04-29) +- 新增 type 属性的 left\right 值,支持左右弹出 +- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗 +- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色 +- 新增 safeArea 属性,是否适配底部安全区 +- 修复 App\h5\微信小程序底部安全区占位不对的Bug +- 修复 App 端弹出等待的Bug +- 优化 提升低配设备性能,优化动画卡顿问题 +- 优化 更简单的组件自定义方式 +## 1.2.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.2.8(2021-02-05) +- 调整为uni_modules目录规范 +## 1.2.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 +- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端 diff --git a/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js b/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js new file mode 100644 index 0000000000000000000000000000000000000000..a747b9fc8c0d3df87adc4e7ffa380beef7f42cbe --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif diff --git a/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue b/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue new file mode 100644 index 0000000000000000000000000000000000000000..e42bfaf08eb17824deb3efb0a3cd123ad88902cc --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue @@ -0,0 +1,316 @@ + + + + + diff --git a/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue b/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue new file mode 100644 index 0000000000000000000000000000000000000000..7f27a1e14bb29fa8a770bc8a1a6870be67f7ad2f --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue @@ -0,0 +1,143 @@ + + + + diff --git a/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue b/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue new file mode 100644 index 0000000000000000000000000000000000000000..7a98a31b6c60fc4b48183b4319e186131fa5acfe --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue @@ -0,0 +1,187 @@ + + + + diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/en.json b/uni_modules/uni-popup/components/uni-popup/i18n/en.json new file mode 100644 index 0000000000000000000000000000000000000000..8c0f5f38e3c7655ff05dbdc4a21cea2ba5d1038c --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/en.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "cancel", + "uni-popup.ok": "ok", + "uni-popup.placeholder": "pleace enter", + "uni-popup.title": "Hint", + "uni-popup.shareTitle": "Share to" +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/index.js b/uni_modules/uni-popup/components/uni-popup/i18n/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fa8f0f3734de6effb137592fce6c0f722c048432 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json new file mode 100644 index 0000000000000000000000000000000000000000..8e5b99f0822dc9c6781f5838a0c6b42c1ca85392 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "确定", + "uni-popup.placeholder": "请输入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json new file mode 100644 index 0000000000000000000000000000000000000000..06ce1629ba3bf090d84720a85e9bdbc9096ba69a --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "確定", + "uni-popup.placeholder": "請輸入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uni_modules/uni-popup/components/uni-popup/keypress.js b/uni_modules/uni-popup/components/uni-popup/keypress.js new file mode 100644 index 0000000000000000000000000000000000000000..16a5818818d5f6f9a6c90b7ae4e9a6f1386f8524 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {} +} +// #endif diff --git a/uni_modules/uni-popup/components/uni-popup/popup.js b/uni_modules/uni-popup/components/uni-popup/popup.js new file mode 100644 index 0000000000000000000000000000000000000000..a37fb9fc19bc1241ccd35d3f8bbae8b01cf7e398 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/popup.js @@ -0,0 +1,26 @@ + +export default { + data() { + return { + + } + }, + created(){ + this.popup = this.getParent() + }, + methods:{ + /** + * 获取父元素实例 + */ + getParent(name = 'uniPopup') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + } +} diff --git a/uni_modules/uni-popup/components/uni-popup/uni-popup.uvue b/uni_modules/uni-popup/components/uni-popup/uni-popup.uvue new file mode 100644 index 0000000000000000000000000000000000000000..5eb8d5be62c49d82317d58f1bc32f1549ad21fb2 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/uni-popup.uvue @@ -0,0 +1,90 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/uni-popup/components/uni-popup/uni-popup.vue b/uni_modules/uni-popup/components/uni-popup/uni-popup.vue new file mode 100644 index 0000000000000000000000000000000000000000..8349e994437c2b5c6d6efbd7d49327de198ab749 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/uni-popup.vue @@ -0,0 +1,503 @@ + + + + diff --git a/uni_modules/uni-popup/package.json b/uni_modules/uni-popup/package.json new file mode 100644 index 0000000000000000000000000000000000000000..3cfa38447890e1d04bcdd66a7fa0575462f703fd --- /dev/null +++ b/uni_modules/uni-popup/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-popup", + "displayName": "uni-popup 弹出层", + "version": "1.9.1", + "description": " Popup 组件,提供常用的弹层", + "keywords": [ + "uni-ui", + "弹出层", + "弹窗", + "popup", + "弹框" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-transition" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y", + "alipay": "n" + }, + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uni_modules/uni-popup/readme.md b/uni_modules/uni-popup/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..fdad4b3d7031e1165d6904d32b9f00e1ad011a51 --- /dev/null +++ b/uni_modules/uni-popup/readme.md @@ -0,0 +1,17 @@ + + +## Popup 弹出层 +> **组件名:uni-popup** +> 代码块: `uPopup` +> 关联组件:`uni-transition` + + +弹出层组件,在应用中弹出一个消息提示窗口、提示框等 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/uni_modules/uni-scss/changelog.md b/uni_modules/uni-scss/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..b863bb0f5b0fb7b6cc18c498471f8cfd55538d07 --- /dev/null +++ b/uni_modules/uni-scss/changelog.md @@ -0,0 +1,8 @@ +## 1.0.3(2022-01-21) +- 优化 组件示例 +## 1.0.2(2021-11-22) +- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题 +## 1.0.1(2021-11-22) +- 修复 vue3中scss语法兼容问题 +## 1.0.0(2021-11-18) +- init diff --git a/uni_modules/uni-scss/index.scss b/uni_modules/uni-scss/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..1744a5f9845f6e3399263d7136e7e195e1e44957 --- /dev/null +++ b/uni_modules/uni-scss/index.scss @@ -0,0 +1 @@ +@import './styles/index.scss'; diff --git a/uni_modules/uni-scss/package.json b/uni_modules/uni-scss/package.json new file mode 100644 index 0000000000000000000000000000000000000000..7cc0ccb73f8f1b87cb9f1719551b94a05896e335 --- /dev/null +++ b/uni_modules/uni-scss/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-scss", + "displayName": "uni-scss 辅助样式", + "version": "1.0.3", + "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。", + "keywords": [ + "uni-scss", + "uni-ui", + "辅助样式" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "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" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uni_modules/uni-scss/readme.md b/uni_modules/uni-scss/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..b7d1c25f3df7789fd3347cd1f8f00e3df05718e4 --- /dev/null +++ b/uni_modules/uni-scss/readme.md @@ -0,0 +1,4 @@ +`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uni_modules/uni-scss/styles/index.scss b/uni_modules/uni-scss/styles/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..ffac4fecdf294152885eb82aac1e9d69e13ace8b --- /dev/null +++ b/uni_modules/uni-scss/styles/index.scss @@ -0,0 +1,7 @@ +@import './setting/_variables.scss'; +@import './setting/_border.scss'; +@import './setting/_color.scss'; +@import './setting/_space.scss'; +@import './setting/_radius.scss'; +@import './setting/_text.scss'; +@import './setting/_styles.scss'; diff --git a/uni_modules/uni-scss/styles/setting/_border.scss b/uni_modules/uni-scss/styles/setting/_border.scss new file mode 100644 index 0000000000000000000000000000000000000000..12a11c3227c180d36c7ae8b19da0395c8a37edf2 --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_border.scss @@ -0,0 +1,3 @@ +.uni-border { + border: 1px $uni-border-1 solid; +} \ No newline at end of file diff --git a/uni_modules/uni-scss/styles/setting/_color.scss b/uni_modules/uni-scss/styles/setting/_color.scss new file mode 100644 index 0000000000000000000000000000000000000000..1ededd94d929eb8043c0f0f50e4b6eaef19d5056 --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_color.scss @@ -0,0 +1,66 @@ + +// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐 +// @mixin get-styles($k,$c) { +// @if $k == size or $k == weight{ +// font-#{$k}:#{$c} +// }@else{ +// #{$k}:#{$c} +// } +// } +$uni-ui-color:( + // 主色 + primary: $uni-primary, + primary-disable: $uni-primary-disable, + primary-light: $uni-primary-light, + // 辅助色 + success: $uni-success, + success-disable: $uni-success-disable, + success-light: $uni-success-light, + warning: $uni-warning, + warning-disable: $uni-warning-disable, + warning-light: $uni-warning-light, + error: $uni-error, + error-disable: $uni-error-disable, + error-light: $uni-error-light, + info: $uni-info, + info-disable: $uni-info-disable, + info-light: $uni-info-light, + // 中性色 + main-color: $uni-main-color, + base-color: $uni-base-color, + secondary-color: $uni-secondary-color, + extra-color: $uni-extra-color, + // 背景色 + bg-color: $uni-bg-color, + // 边框颜色 + border-1: $uni-border-1, + border-2: $uni-border-2, + border-3: $uni-border-3, + border-4: $uni-border-4, + // 黑色 + black:$uni-black, + // 白色 + white:$uni-white, + // 透明 + transparent:$uni-transparent +) !default; +@each $key, $child in $uni-ui-color { + .uni-#{"" + $key} { + color: $child; + } + .uni-#{"" + $key}-bg { + background-color: $child; + } +} +.uni-shadow-sm { + box-shadow: $uni-shadow-sm; +} +.uni-shadow-base { + box-shadow: $uni-shadow-base; +} +.uni-shadow-lg { + box-shadow: $uni-shadow-lg; +} +.uni-mask { + background-color:$uni-mask; +} diff --git a/uni_modules/uni-scss/styles/setting/_radius.scss b/uni_modules/uni-scss/styles/setting/_radius.scss new file mode 100644 index 0000000000000000000000000000000000000000..9a0428bb87c90b48771945ac6dd1e96d0004b159 --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_radius.scss @@ -0,0 +1,55 @@ +@mixin radius($r,$d:null ,$important: false){ + $radius-value:map-get($uni-radius, $r) if($important, !important, null); + // Key exists within the $uni-radius variable + @if (map-has-key($uni-radius, $r) and $d){ + @if $d == t { + border-top-left-radius:$radius-value; + border-top-right-radius:$radius-value; + }@else if $d == r { + border-top-right-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == b { + border-bottom-left-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == l { + border-top-left-radius:$radius-value; + border-bottom-left-radius:$radius-value; + }@else if $d == tl { + border-top-left-radius:$radius-value; + }@else if $d == tr { + border-top-right-radius:$radius-value; + }@else if $d == br { + border-bottom-right-radius:$radius-value; + }@else if $d == bl { + border-bottom-left-radius:$radius-value; + } + }@else{ + border-radius:$radius-value; + } +} + +@each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $key} { + @include radius($key) + } + }@else{ + .uni-radius { + @include radius($key) + } + } +} + +@each $direction in t, r, b, l,tl, tr, br, bl { + @each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $direction}-#{"" + $key} { + @include radius($key,$direction,false) + } + }@else{ + .uni-radius-#{$direction} { + @include radius($key,$direction,false) + } + } + } +} diff --git a/uni_modules/uni-scss/styles/setting/_space.scss b/uni_modules/uni-scss/styles/setting/_space.scss new file mode 100644 index 0000000000000000000000000000000000000000..3c895289782aec33e3b8cc7f7f302bdc86131174 --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_space.scss @@ -0,0 +1,56 @@ + +@mixin fn($space,$direction,$size,$n) { + @if $n { + #{$space}-#{$direction}: #{$size*$uni-space-root}px + } @else { + #{$space}-#{$direction}: #{-$size*$uni-space-root}px + } +} +@mixin get-styles($direction,$i,$space,$n){ + @if $direction == t { + @include fn($space, top,$i,$n); + } + @if $direction == r { + @include fn($space, right,$i,$n); + } + @if $direction == b { + @include fn($space, bottom,$i,$n); + } + @if $direction == l { + @include fn($space, left,$i,$n); + } + @if $direction == x { + @include fn($space, left,$i,$n); + @include fn($space, right,$i,$n); + } + @if $direction == y { + @include fn($space, top,$i,$n); + @include fn($space, bottom,$i,$n); + } + @if $direction == a { + @if $n { + #{$space}:#{$i*$uni-space-root}px; + } @else { + #{$space}:#{-$i*$uni-space-root}px; + } + } +} + +@each $orientation in m,p { + $space: margin; + @if $orientation == m { + $space: margin; + } @else { + $space: padding; + } + @for $i from 0 through 16 { + @each $direction in t, r, b, l, x, y, a { + .uni-#{$orientation}#{$direction}-#{$i} { + @include get-styles($direction,$i,$space,true); + } + .uni-#{$orientation}#{$direction}-n#{$i} { + @include get-styles($direction,$i,$space,false); + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-scss/styles/setting/_styles.scss b/uni_modules/uni-scss/styles/setting/_styles.scss new file mode 100644 index 0000000000000000000000000000000000000000..689afec661c6e7212b41437ad3f1fdf26659606c --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_styles.scss @@ -0,0 +1,167 @@ +/* #ifndef APP-NVUE */ + +$-color-white:#fff; +$-color-black:#000; +@mixin base-style($color) { + color: #fff; + background-color: $color; + border-color: mix($-color-black, $color, 8%); + &:not([hover-class]):active { + background: mix($-color-black, $color, 10%); + border-color: mix($-color-black, $color, 20%); + color: $-color-white; + outline: none; + } +} +@mixin is-color($color) { + @include base-style($color); + &[loading] { + @include base-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &[loading], + &:not([hover-class]):active { + color: $-color-white; + border-color: mix(darken($color,10%), $-color-white); + background-color: mix($color, $-color-white); + } + } + +} +@mixin base-plain-style($color) { + color:$color; + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 70%); + &:not([hover-class]):active { + background: mix($-color-white, $color, 80%); + color: $color; + outline: none; + border-color: mix($-color-white, $color, 50%); + } +} +@mixin is-plain($color){ + &[plain] { + @include base-plain-style($color); + &[loading] { + @include base-plain-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &:active { + color: mix($-color-white, $color, 40%); + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 80%); + } + } + } +} + + +.uni-btn { + margin: 5px; + color: #393939; + border:1px solid #ccc; + font-size: 16px; + font-weight: 200; + background-color: #F9F9F9; + // TODO 暂时处理边框隐藏一边的问题 + overflow: visible; + &::after{ + border: none; + } + + &:not([type]),&[type=default] { + color: #999; + &[loading] { + background: none; + &::before { + margin-right:5px; + } + } + + + + &[disabled]{ + color: mix($-color-white, #999, 60%); + &, + &[loading], + &:active { + color: mix($-color-white, #999, 60%); + background-color: mix($-color-white,$-color-black , 98%); + border-color: mix($-color-white, #999, 85%); + } + } + + &[plain] { + color: #999; + background: none; + border-color: $uni-border-1; + &:not([hover-class]):active { + background: none; + color: mix($-color-white, $-color-black, 80%); + border-color: mix($-color-white, $-color-black, 90%); + outline: none; + } + &[disabled]{ + &, + &[loading], + &:active { + background: none; + color: mix($-color-white, #999, 60%); + border-color: mix($-color-white, #999, 85%); + } + } + } + } + + &:not([hover-class]):active { + color: mix($-color-white, $-color-black, 50%); + } + + &[size=mini] { + font-size: 16px; + font-weight: 200; + border-radius: 8px; + } + + + + &.uni-btn-small { + font-size: 14px; + } + &.uni-btn-mini { + font-size: 12px; + } + + &.uni-btn-radius { + border-radius: 999px; + } + &[type=primary] { + @include is-color($uni-primary); + @include is-plain($uni-primary) + } + &[type=success] { + @include is-color($uni-success); + @include is-plain($uni-success) + } + &[type=error] { + @include is-color($uni-error); + @include is-plain($uni-error) + } + &[type=warning] { + @include is-color($uni-warning); + @include is-plain($uni-warning) + } + &[type=info] { + @include is-color($uni-info); + @include is-plain($uni-info) + } +} +/* #endif */ diff --git a/uni_modules/uni-scss/styles/setting/_text.scss b/uni_modules/uni-scss/styles/setting/_text.scss new file mode 100644 index 0000000000000000000000000000000000000000..a34d08f3f70b83d36f019e6e268f8e62ba9d932d --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_text.scss @@ -0,0 +1,24 @@ +@mixin get-styles($k,$c) { + @if $k == size or $k == weight{ + font-#{$k}:#{$c} + }@else{ + #{$k}:#{$c} + } +} + +@each $key, $child in $uni-headings { + /* #ifndef APP-NVUE */ + .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ + /* #ifdef APP-NVUE */ + .container .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ +} diff --git a/uni_modules/uni-scss/styles/setting/_variables.scss b/uni_modules/uni-scss/styles/setting/_variables.scss new file mode 100644 index 0000000000000000000000000000000000000000..557d3d7c9cfcf8b1d1ce94d23c75333886749841 --- /dev/null +++ b/uni_modules/uni-scss/styles/setting/_variables.scss @@ -0,0 +1,146 @@ +// @use "sass:math"; +@import '../tools/functions.scss'; +// 间距基础倍数 +$uni-space-root: 2 !default; +// 边框半径默认值 +$uni-radius-root:5px !default; +$uni-radius: () !default; +// 边框半径断点 +$uni-radius: map-deep-merge( + ( + 0: 0, + // TODO 当前版本暂时不支持 sm 属性 + // 'sm': math.div($uni-radius-root, 2), + null: $uni-radius-root, + 'lg': $uni-radius-root * 2, + 'xl': $uni-radius-root * 6, + 'pill': 9999px, + 'circle': 50% + ), + $uni-radius +); +// 字体家族 +$body-font-family: 'Roboto', sans-serif !default; +// 文本 +$heading-font-family: $body-font-family !default; +$uni-headings: () !default; +$letterSpacing: -0.01562em; +$uni-headings: map-deep-merge( + ( + 'h1': ( + size: 32px, + weight: 300, + line-height: 50px, + // letter-spacing:-0.01562em + ), + 'h2': ( + size: 28px, + weight: 300, + line-height: 40px, + // letter-spacing: -0.00833em + ), + 'h3': ( + size: 24px, + weight: 400, + line-height: 32px, + // letter-spacing: normal + ), + 'h4': ( + size: 20px, + weight: 400, + line-height: 30px, + // letter-spacing: 0.00735em + ), + 'h5': ( + size: 16px, + weight: 400, + line-height: 24px, + // letter-spacing: normal + ), + 'h6': ( + size: 14px, + weight: 500, + line-height: 18px, + // letter-spacing: 0.0125em + ), + 'subtitle': ( + size: 12px, + weight: 400, + line-height: 20px, + // letter-spacing: 0.00937em + ), + 'body': ( + font-size: 14px, + font-weight: 400, + line-height: 22px, + // letter-spacing: 0.03125em + ), + 'caption': ( + 'size': 12px, + 'weight': 400, + 'line-height': 20px, + // 'letter-spacing': 0.03333em, + // 'text-transform': false + ) + ), + $uni-headings +); + + + +// 主色 +$uni-primary: #2979ff !default; +$uni-primary-disable:lighten($uni-primary,20%) !default; +$uni-primary-light: lighten($uni-primary,25%) !default; + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37 !default; +$uni-success-disable:lighten($uni-success,20%) !default; +$uni-success-light: lighten($uni-success,25%) !default; + +$uni-warning: #f3a73f !default; +$uni-warning-disable:lighten($uni-warning,20%) !default; +$uni-warning-light: lighten($uni-warning,25%) !default; + +$uni-error: #e43d33 !default; +$uni-error-disable:lighten($uni-error,20%) !default; +$uni-error-light: lighten($uni-error,25%) !default; + +$uni-info: #8f939c !default; +$uni-info-disable:lighten($uni-info,20%) !default; +$uni-info-light: lighten($uni-info,25%) !default; + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a !default; // 主要文字 +$uni-base-color: #6a6a6a !default; // 常规文字 +$uni-secondary-color: #909399 !default; // 次要文字 +$uni-extra-color: #c7c7c7 !default; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0 !default; +$uni-border-2: #EDEDED !default; +$uni-border-3: #DCDCDC !default; +$uni-border-4: #B9B9B9 !default; + +// 常规色 +$uni-black: #000000 !default; +$uni-white: #ffffff !default; +$uni-transparent: rgba($color: #000000, $alpha: 0) !default; + +// 背景色 +$uni-bg-color: #f7f7f7 !default; + +/* 水平间距 */ +$uni-spacing-sm: 8px !default; +$uni-spacing-base: 15px !default; +$uni-spacing-lg: 30px !default; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default; +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default; +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default; + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4) !default; diff --git a/uni_modules/uni-scss/styles/tools/functions.scss b/uni_modules/uni-scss/styles/tools/functions.scss new file mode 100644 index 0000000000000000000000000000000000000000..ac6f63e53a9342840367582982b1f6a465928e48 --- /dev/null +++ b/uni_modules/uni-scss/styles/tools/functions.scss @@ -0,0 +1,19 @@ +// 合并 map +@function map-deep-merge($parent-map, $child-map){ + $result: $parent-map; + @each $key, $child in $child-map { + $parent-has-key: map-has-key($result, $key); + $parent-value: map-get($result, $key); + $parent-type: type-of($parent-value); + $child-type: type-of($child); + $parent-is-map: $parent-type == map; + $child-is-map: $child-type == map; + + @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){ + $result: map-merge($result, ( $key: $child )); + }@else { + $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) )); + } + } + @return $result; +}; diff --git a/uni_modules/uni-scss/theme.scss b/uni_modules/uni-scss/theme.scss new file mode 100644 index 0000000000000000000000000000000000000000..80ee62f7d82dd550f11e643f55b66353a48b2b55 --- /dev/null +++ b/uni_modules/uni-scss/theme.scss @@ -0,0 +1,31 @@ +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; +// 主色 +$uni-primary: #2979ff; +// 辅助色 +$uni-success: #4cd964; +// 警告色 +$uni-warning: #f0ad4e; +// 错误色 +$uni-error: #dd524d; +// 描述色 +$uni-info: #909399; +// 中性色 +$uni-main-color: #303133; +$uni-base-color: #606266; +$uni-secondary-color: #909399; +$uni-extra-color: #C0C4CC; +// 背景色 +$uni-bg-color: #f5f5f5; +// 边框颜色 +$uni-border-1: #DCDFE6; +$uni-border-2: #E4E7ED; +$uni-border-3: #EBEEF5; +$uni-border-4: #F2F6FC; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); diff --git a/uni_modules/uni-scss/variables.scss b/uni_modules/uni-scss/variables.scss new file mode 100644 index 0000000000000000000000000000000000000000..1c062d42b29f02edc623834720ef88437b6a3277 --- /dev/null +++ b/uni_modules/uni-scss/variables.scss @@ -0,0 +1,62 @@ +@import './styles/setting/_variables.scss'; +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; + +// 主色 +$uni-primary: #2979ff; +$uni-primary-disable:mix(#fff,$uni-primary,50%); +$uni-primary-light: mix(#fff,$uni-primary,80%); + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37; +$uni-success-disable:mix(#fff,$uni-success,50%); +$uni-success-light: mix(#fff,$uni-success,80%); + +$uni-warning: #f3a73f; +$uni-warning-disable:mix(#fff,$uni-warning,50%); +$uni-warning-light: mix(#fff,$uni-warning,80%); + +$uni-error: #e43d33; +$uni-error-disable:mix(#fff,$uni-error,50%); +$uni-error-light: mix(#fff,$uni-error,80%); + +$uni-info: #8f939c; +$uni-info-disable:mix(#fff,$uni-info,50%); +$uni-info-light: mix(#fff,$uni-info,80%); + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a; // 主要文字 +$uni-base-color: #6a6a6a; // 常规文字 +$uni-secondary-color: #909399; // 次要文字 +$uni-extra-color: #c7c7c7; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0; +$uni-border-2: #EDEDED; +$uni-border-3: #DCDCDC; +$uni-border-4: #B9B9B9; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); + +// 背景色 +$uni-bg-color: #f7f7f7; + +/* 水平间距 */ +$uni-spacing-sm: 8px; +$uni-spacing-base: 15px; +$uni-spacing-lg: 30px; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4); diff --git a/uni_modules/uni-search-bar/changelog.md b/uni_modules/uni-search-bar/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..ddd8819441bd4c05069dd6d3258a3cf3500f4836 --- /dev/null +++ b/uni_modules/uni-search-bar/changelog.md @@ -0,0 +1,43 @@ +## 1.2.8(2024-02-22) +- 修复 清空按钮emit值错误的bug +## 1.2.7(2024-02-21) +- 新增 设置输入框字体颜色:textColor +## 1.2.6(2024-02-20) +- 修复 uni-search-bar在支付宝小程序下样式兼容问题 +## 1.2.5(2024-01-31) +- 修复 uni-search-bar居中问题,现在默认居左,并修复样式偏移问题 +## 1.2.4(2023-05-09) +- 修复 i18n 国际化不正确的 Bug +## 1.2.3(2022-05-24) +- 新增 readonly 属性,组件只读 +## 1.2.2(2022-05-06) +- 修复 vue3 input 事件不生效的bug +## 1.2.1(2022-05-06) +- 修复 多余代码导致的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-search-bar](https://uniapp.dcloud.io/component/uniui/uni-search-bar) +## 1.1.2(2021-08-30) +- 修复 value 属性与 modelValue 属性不兼容的Bug +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 项目示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-04-15) +- uni-ui 新增 uni-search-bar 的 focus 事件 + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持双向绑定 +- 更改 input 事件的返回值,e={value:Number} --> e=value +- 新增 支持图标插槽 +- 新增 支持 clear、blur 事件 +- 新增 支持 focus 属性 +- 去掉组件背景色 diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json new file mode 100644 index 0000000000000000000000000000000000000000..0c72ffb3e02159893395c18151964a9b42b0706e --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "Search enter content" +} \ No newline at end of file diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fa8f0f3734de6effb137592fce6c0f722c048432 --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json new file mode 100644 index 0000000000000000000000000000000000000000..d2a1ced4b5d631c7ad7787bb862b382615aa6f9e --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "取消", + "uni-search-bar.placeholder": "请输入搜索内容" +} diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json new file mode 100644 index 0000000000000000000000000000000000000000..f1c96bc4607c7ca725260393b0e860cfddbc1170 --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "取消", + "uni-search-bar.placeholder": "請輸入搜索內容" +} diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue b/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue new file mode 100644 index 0000000000000000000000000000000000000000..488f285cefb94b433d9f3c260800bcc3824d16f6 --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/uni_modules/uni-search-bar/package.json b/uni_modules/uni-search-bar/package.json new file mode 100644 index 0000000000000000000000000000000000000000..43a3d7fae8fb0d37ad48ef2dc6758822d848159e --- /dev/null +++ b/uni_modules/uni-search-bar/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-search-bar", + "displayName": "uni-search-bar 搜索栏", + "version": "1.2.8", + "description": "搜索栏组件,通常用于搜索商品、文章等", + "keywords": [ + "uni-ui", + "uniui", + "搜索框", + "搜索栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-search-bar/readme.md b/uni_modules/uni-search-bar/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..253092f0bfad35d9d411494ec4d57f6b46c01fab --- /dev/null +++ b/uni_modules/uni-search-bar/readme.md @@ -0,0 +1,14 @@ + + +## SearchBar 搜索栏 + +> **组件名:uni-search-bar** +> 代码块: `uSearchBar` + + +搜索栏组件 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-search-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uni_modules/uni-segmented-control/changelog.md b/uni_modules/uni-segmented-control/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..7d26c78ea0d1f627d5b6922f847c8957fd4345bc --- /dev/null +++ b/uni_modules/uni-segmented-control/changelog.md @@ -0,0 +1,15 @@ +## 1.2.3(2024-04-02) +- 修复 修复在微信小程序下inactiveColor失效bug +## 1.2.2(2024-03-28) +- 修复 在vue2下:style动态绑定导致编译失败的bug +## 1.2.1(2024-03-20) +- 新增 inActiveColor属性,可供配置未激活时的颜色 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-segmented-control](https://uniapp.dcloud.io/component/uniui/uni-segmented-control) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.5(2021-05-12) +- 新增 项目示例地址 +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue b/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue new file mode 100644 index 0000000000000000000000000000000000000000..272cbf505a846ecae6c0bed5fe56ce643568396a --- /dev/null +++ b/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/uni_modules/uni-segmented-control/package.json b/uni_modules/uni-segmented-control/package.json new file mode 100644 index 0000000000000000000000000000000000000000..4d3b7a329c525f81dd36be1952bae0547b2f5576 --- /dev/null +++ b/uni_modules/uni-segmented-control/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-segmented-control", + "displayName": "uni-segmented-control 分段器", + "version": "1.2.3", + "description": "分段器由至少 2 个分段控件组成,用作不同视图的显示", + "keywords": [ + "uni-ui", + "uniui", + "分段器", + "segement", + "顶部选择" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y", + "alipay": "n" + }, + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-segmented-control/readme.md b/uni_modules/uni-segmented-control/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..3527b03f61fbe9f338dad1b6f1af8d2f5d004dc4 --- /dev/null +++ b/uni_modules/uni-segmented-control/readme.md @@ -0,0 +1,13 @@ + + +## SegmentedControl 分段器 +> **组件名:uni-segmented-control** +> 代码块: `uSegmentedControl` + + +用作不同视图的显示 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-segmented-control) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uni_modules/uni-transition/changelog.md b/uni_modules/uni-transition/changelog.md new file mode 100644 index 0000000000000000000000000000000000000000..70c1cd4bf023340623f7e8fea6b0f5c937ddd258 --- /dev/null +++ b/uni_modules/uni-transition/changelog.md @@ -0,0 +1,22 @@ +## 1.3.2(2023-05-04) +- 修复 NVUE 平台报错的问题 +## 1.3.1(2021-11-23) +- 修复 init 方法初始化问题 +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition) +## 1.2.1(2021-09-27) +- 修复 init 方法不生效的 Bug +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.1(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的 Bug +## 1.1.0(2021-04-22) +- 新增 通过方法自定义动画 +- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式 +- 优化 动画触发逻辑,使动画更流畅 +- 优化 支持单独的动画类型 +- 优化 文档示例 +## 1.0.2(2021-02-05) +- 调整为 uni_modules 目录规范 diff --git a/uni_modules/uni-transition/components/uni-transition/createAnimation.js b/uni_modules/uni-transition/components/uni-transition/createAnimation.js new file mode 100644 index 0000000000000000000000000000000000000000..8f89b185c74144560c7047a2535a714518f93f16 --- /dev/null +++ b/uni_modules/uni-transition/components/uni-transition/createAnimation.js @@ -0,0 +1,131 @@ +// const defaultOption = { +// duration: 300, +// timingFunction: 'linear', +// delay: 0, +// transformOrigin: '50% 50% 0' +// } +// #ifdef APP-NVUE +const nvueAnimation = uni.requireNativePlugin('animation') +// #endif +class MPAnimation { + constructor(options, _this) { + this.options = options + // 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误 + this.animation = uni.createAnimation({ + ...options + }) + this.currentStepAnimates = {} + this.next = 0 + this.$ = _this + + } + + _nvuePushAnimates(type, args) { + let aniObj = this.currentStepAnimates[this.next] + let styles = {} + if (!aniObj) { + styles = { + styles: {}, + config: {} + } + } else { + styles = aniObj + } + if (animateTypes1.includes(type)) { + if (!styles.styles.transform) { + styles.styles.transform = '' + } + let unit = '' + if(type === 'rotate'){ + unit = 'deg' + } + styles.styles.transform += `${type}(${args+unit}) ` + } else { + styles.styles[type] = `${args}` + } + this.currentStepAnimates[this.next] = styles + } + _animateRun(styles = {}, config = {}) { + let ref = this.$.$refs['ani'].ref + if (!ref) return + return new Promise((resolve, reject) => { + nvueAnimation.transition(ref, { + styles, + ...config + }, res => { + resolve() + }) + }) + } + + _nvueNextAnimate(animates, step = 0, fn) { + let obj = animates[step] + if (obj) { + let { + styles, + config + } = obj + this._animateRun(styles, config).then(() => { + step += 1 + this._nvueNextAnimate(animates, step, fn) + }) + } else { + this.currentStepAnimates = {} + typeof fn === 'function' && fn() + this.isEnd = true + } + } + + step(config = {}) { + // #ifndef APP-NVUE + this.animation.step(config) + // #endif + // #ifdef APP-NVUE + this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config) + this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin + this.next++ + // #endif + return this + } + + run(fn) { + // #ifndef APP-NVUE + this.$.animationData = this.animation.export() + this.$.timer = setTimeout(() => { + typeof fn === 'function' && fn() + }, this.$.durationTime) + // #endif + // #ifdef APP-NVUE + this.isEnd = false + let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref + if(!ref) return + this._nvueNextAnimate(this.currentStepAnimates, 0, fn) + this.next = 0 + // #endif + } +} + + +const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d', + 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY', + 'translateZ' +] +const animateTypes2 = ['opacity', 'backgroundColor'] +const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom'] +animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => { + MPAnimation.prototype[type] = function(...args) { + // #ifndef APP-NVUE + this.animation[type](...args) + // #endif + // #ifdef APP-NVUE + this._nvuePushAnimates(type, args) + // #endif + return this + } +}) + +export function createAnimation(option, _this) { + if(!_this) return + clearTimeout(_this.timer) + return new MPAnimation(option, _this) +} diff --git a/uni_modules/uni-transition/components/uni-transition/uni-transition.vue b/uni_modules/uni-transition/components/uni-transition/uni-transition.vue new file mode 100644 index 0000000000000000000000000000000000000000..bfbba933c92c6eef34d809918b9f4d85f58dd8ee --- /dev/null +++ b/uni_modules/uni-transition/components/uni-transition/uni-transition.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/uni_modules/uni-transition/package.json b/uni_modules/uni-transition/package.json new file mode 100644 index 0000000000000000000000000000000000000000..ea995a2a156e00176eb4bf3f15b2b17f2e90e938 --- /dev/null +++ b/uni_modules/uni-transition/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-transition", + "displayName": "uni-transition 过渡动画", + "version": "1.3.2", + "description": "元素的简单过渡动画", + "keywords": [ + "uni-ui", + "uniui", + "动画", + "过渡", + "过渡动画" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-transition/readme.md b/uni_modules/uni-transition/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..2f8a77e10b8b2a5f93de2d9347d1be9639e4154d --- /dev/null +++ b/uni_modules/uni-transition/readme.md @@ -0,0 +1,11 @@ + + +## Transition 过渡动画 +> **组件名:uni-transition** +> 代码块: `uTransition` + + +元素过渡动画 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file