diff --git a/packages/app/src/app.config.ts b/packages/app/src/app.config.ts index f2e7ea962..b17f8b5fb 100644 --- a/packages/app/src/app.config.ts +++ b/packages/app/src/app.config.ts @@ -32,8 +32,10 @@ export default { 'pages/useAPICheck/index', 'pages/useUpdateManager/index', 'pages/useLaunchOptions/index', + 'pages/useUserInfo/index', 'pages/useAccountInfo/index', 'pages/useAuthorize/index', + 'pages/useLogin/index', // network 'pages/useRequest/index', 'pages/useRequest/defaultRequest/index', diff --git a/packages/app/src/components/DocPage/index.less b/packages/app/src/components/DocPage/index.less index 43e38d6e0..3e5b706d8 100644 --- a/packages/app/src/components/DocPage/index.less +++ b/packages/app/src/components/DocPage/index.less @@ -1,5 +1,6 @@ @import '../../app.less'; @import '../../assets/style/icon.less'; +@import '../../style/mixins.less'; .demo { background-color: #6190e8; @@ -21,3 +22,7 @@ .margin { margin-top: 30px; } + +.ellipsis { + .ellipsis(); +} diff --git a/packages/app/src/constant/index.ts b/packages/app/src/constant/index.ts index acfbfe2b9..9880a824e 100644 --- a/packages/app/src/constant/index.ts +++ b/packages/app/src/constant/index.ts @@ -151,6 +151,10 @@ export const ChildrenList: { [_: string]: APIChildrenItem[] } = { id: 'useLaunchOptions', name: 'useLaunchOptions 启动参数', }, + { + id: 'useUserInfo', + name: 'useUserInfo 用户信息', + }, { id: 'useAccountInfo', name: 'useAccountInfo 账号信息', diff --git a/packages/app/src/pages/useLogin/index.config.ts b/packages/app/src/pages/useLogin/index.config.ts new file mode 100644 index 000000000..88999c7a3 --- /dev/null +++ b/packages/app/src/pages/useLogin/index.config.ts @@ -0,0 +1,3 @@ +export default { + navigationBarTitleText: 'useLogin', +}; diff --git a/packages/app/src/pages/useLogin/index.tsx b/packages/app/src/pages/useLogin/index.tsx new file mode 100644 index 000000000..b5fb04d4f --- /dev/null +++ b/packages/app/src/pages/useLogin/index.tsx @@ -0,0 +1,42 @@ +import React, { useCallback } from 'react'; +import { AtNoticebar, AtButton } from 'taro-ui'; + +import DocPage from '@components/DocPage'; + +import { useLogin, useModal } from 'taro-hooks'; + +export default () => { + const [login, checkSession] = useLogin(); + const [show] = useModal({ mask: true, title: '登录信息', showCancel: false }); + + const handleSession = useCallback(async () => { + let message = '登录状态正常'; + try { + await checkSession(); + } catch (e) { + console.log(e); + message = '登录状态失效'; + } + show({ content: message }); + }, [checkSession, show]); + + const handleLogin = useCallback(() => { + login(true) + .then((code: string) => show({ content: '获取凭证为: ' + code })) + .catch(() => { + show({ content: '获取凭证失败' }); + }); + }, [login, show]); + + return ( + <> + 该hook仅小程序可用 + + 获取凭证 + + 检测当前登录状态 + + + + ); +}; diff --git a/packages/app/src/pages/useUserInfo/index.config.ts b/packages/app/src/pages/useUserInfo/index.config.ts new file mode 100644 index 000000000..d96aa8329 --- /dev/null +++ b/packages/app/src/pages/useUserInfo/index.config.ts @@ -0,0 +1,3 @@ +export default { + navigationBarTitleText: 'useUserInfo', +}; diff --git a/packages/app/src/pages/useUserInfo/index.less b/packages/app/src/pages/useUserInfo/index.less new file mode 100644 index 000000000..eb9c1c193 --- /dev/null +++ b/packages/app/src/pages/useUserInfo/index.less @@ -0,0 +1,3 @@ +.userinfo { + margin-bottom: 10px; +} diff --git a/packages/app/src/pages/useUserInfo/index.tsx b/packages/app/src/pages/useUserInfo/index.tsx new file mode 100644 index 000000000..748656587 --- /dev/null +++ b/packages/app/src/pages/useUserInfo/index.tsx @@ -0,0 +1,70 @@ +import React, { useCallback } from 'react'; +import { AtRadio, AtAvatar, AtButton, AtNoticebar } from 'taro-ui'; +import DocPage from '@components/DocPage'; + +import { useUserInfo, useLogin } from 'taro-hooks'; +import { UserInfo } from '@tarojs/taro'; +import { View, Text } from '@tarojs/components'; + +import 'taro-ui/dist/style/components/flex.scss'; +import './index.less'; + +const MOCK = '1'; + +const transferOptions = (userInfo: UserInfo) => + Object.entries(userInfo).map(([key, value]) => ({ + label: key + ':', + value: key, + desc: JSON.stringify(value), + })); + +export default () => { + const [userInfo, { getUserInfo, getUserProfile }] = useUserInfo(); + const [login] = useLogin(); + + const handleGetUserInfo = useCallback(() => { + login(true).then(() => { + getUserInfo({ lang: 'zh_CN', withCredentials: true }); + }); + }, [login, getUserInfo]); + + return ( + <> + 该hook仅在小程序中使用 + + + + + + + + 昵称: {userInfo.nickName} + + + 性别:{' '} + {!userInfo.gender + ? '未知' + : userInfo.gender === 1 + ? '男性' + : '女性'} + + + + 获取用户信息 + + getUserProfile({ lang: 'zh_CN', desc: '仅作为小程序展示使用' }) + } + > + 获取用户信息(包含敏感) + + + + + ); +}; diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 3feb5dff6..af0808c1f 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -25,7 +25,9 @@ import useAPICheck from './useAPICheck'; import useUpdateManager from './useUpdateManager'; import useLaunchOptions from './useLaunchOptions'; import useAuthorize from './useAuthorize'; +import useUserInfo from './useUserInfo'; import useAccountInfo from './useAccountInfo'; +import useLogin from './useLogin'; // network import useNetworkType from './useNetworkType'; @@ -87,4 +89,6 @@ export { useMap, useAuthorize, useAccountInfo, + useUserInfo, + useLogin, }; diff --git a/packages/hooks/src/useLogin/index.md b/packages/hooks/src/useLogin/index.md new file mode 100644 index 000000000..7f0e3cf08 --- /dev/null +++ b/packages/hooks/src/useLogin/index.md @@ -0,0 +1,46 @@ +--- +title: useLogin +nav: + title: Hooks + path: /hooks + order: 2 +group: + title: 小程序 + path: /wechat +--- + +# useLogin + +获取登录凭证, 检查登录状态 + +## 何时使用 + +当需要获取登录凭证, 检查登录状态 + +## API + +```jsx | pure +const [login, checkSession] = useLogin(); +``` + +## 返回值说明 + +| 返回值 | 说明 | 类型 | +| ------------ | ----------------------------------------------------------------------- | ----------------------------------------------------------- | +| login | 获取登录凭证(若`needCheck`为`true`则自动检测当前登录状态来进行登录操作) | `(needCheck?: boolean) => Promise` | +| checkSession | 检查登录状态 | `() => Promise` | + +## 代码演示 + + + +## Hook 支持度 + +| 微信小程序 | H5 | ReactNative | +| :--------: | :-: | :---------: | +| ✔️ | | | + +## FAQ + +- [login](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html) +- [checkSession](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.checkSession.html) diff --git a/packages/hooks/src/useLogin/index.ts b/packages/hooks/src/useLogin/index.ts new file mode 100644 index 000000000..e7542c53f --- /dev/null +++ b/packages/hooks/src/useLogin/index.ts @@ -0,0 +1,59 @@ +import { login, checkSession, General } from '@tarojs/taro'; +import { useCallback } from 'react'; +import { ENV_TYPE } from '@tarojs/taro'; +import useEnv from '../useEnv'; + +export type ILogin = (needCheck?: boolean) => Promise; +export type IAction = () => Promise; + +function useLogin(): [ILogin, IAction] { + const env = useEnv(); + + const checkSessionSync = useCallback(() => { + return new Promise((resolve, reject) => { + if (env === ENV_TYPE.WEAPP) { + checkSession({ + success: resolve, + fail: reject, + }).catch(reject); + } else { + reject({ errMsg: 'checkSession:fail' }); + } + }); + }, [env]); + + const loginSync = useCallback( + (needCheck) => { + return new Promise((resolve, reject) => { + if (env === ENV_TYPE.WEAPP) { + const loginAction = () => { + login({ + success: (res) => resolve(res.code), + fail: reject, + }).catch(reject); + }; + try { + if (needCheck) { + checkSessionSync() + .then(() => { + loginAction(); + }) + .catch(reject); + } else { + loginAction(); + } + } catch (e) { + reject({ errMsg: 'login:fail', data: e }); + } + } else { + reject({ errMsg: 'login:fail' }); + } + }); + }, + [env, checkSessionSync], + ); + + return [loginSync, checkSessionSync]; +} + +export default useLogin; diff --git a/packages/hooks/src/useUserInfo/index.md b/packages/hooks/src/useUserInfo/index.md new file mode 100644 index 000000000..a4266100e --- /dev/null +++ b/packages/hooks/src/useUserInfo/index.md @@ -0,0 +1,101 @@ +--- +title: useUserInfo +nav: + title: Hooks + path: /hooks + order: 2 +group: + title: 小程序 + path: /wechat +--- + +# useUserInfo + +获取用户信息 + +## 何时使用 + +当需要获取用户信息展示时 + +## API + +```jsx | pure +const [userInfo, { getUserInfo, getUserProfile }] = useUserInfo(); +``` + +## 参数说明 + +无 + +## 返回值说明 + +| 参数 | 类型 | 说明 | +| -------------- | ------------------------------------------------------------------------------ | ---------------------------------- | +| userInfo | `UserInfo` | 用户信息对象 | +| getUserProfile | `(option?: IOption) => Promise` | 获取用户信息(点击生效, 且每次弹窗) | +| getUserInfo | `(option: IProfileOption) => Promise` | 获取用户信息 | + +### IOption + +| 参数 | 类型 | 说明 | +| --------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| lang | `Language` | 显示用户信息的语言 | +| withCredentials | `boolean` | 是否带上登录态信息。当 withCredentials 为 true 时,要求此前有调用过 wx.login 且登录态尚未过期,此时返回的数据会包含 encryptedData, iv 等敏感信息;当 withCredentials 为 false 时,不要求有登录态,返回的数据不包含 encryptedData, iv 等敏感信息 | + +### IProfileOption + +| 参数 | 类型 | 说明 | +| ---- | ---------- | ---------------------------------------------- | +| lang | `Language` | 显示用户信息的语言 | +| desc | `string` | 声明获取用户个人信息后的用途,不超过 30 个字符 | + +### UserInfo + +| 参数 | 类型 | 说明 | +| ------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| avatarUrl | `string` | 用户头像图片的 URL。URL 最后一个数值代表正方形头像大小(有 0、46、64、96、132 数值可选,0 代表 640x640 的正方形头像,46 表示 46x46 的正方形头像,剩余数值以此类推。默认 132),用户没有头像时该项为空。若用户更换头像,原有头像 URL 将失效。 | +| city | `string` | 用户所在城市 | +| country | `string` | 用户所在国家 | +| gender | `0 | 1 | 2` | 用户性别 | +| language | `string` | 显示 country,province,city 所用的语言 | +| nickName | `string` | 用户昵称 | +| province | `string` | 用户所在省份 | +| rawData | `string` | 不包括敏感信息的原始数据字符串,用于计算签名 | +| signature | `string` | 使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息 | +| encryptedData | `string` | 包括敏感数据在内的完整用户信息的加密数据 | +| iv | `string` | 加密算法的初始向量 | +| cloudID | `string` | 敏感数据对应的云 ID | + +### Gender + +| 参数 | 类型 | +| ---- | ---- | +| 0 | 未知 | +| 1 | 男性 | +| 2 | 女性 | + +### Language + +| 参数 | 类型 | +| ----- | -------- | +| en | 英文 | +| zh_CN | 简体中文 | +| zh_TW | 繁体中文 | + +## 代码演示 + + + +## Hook 支持度 + +| 微信小程序 | H5 | ReactNative | +| :--------: | :-: | :---------: | +| ✔️ | | | + +## FAQ + +### 1. 更多说明 + +- [getUserInfo](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html) +- [getUserProfile](https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html) +- [调整说明](https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce88df04cb468bc52801) diff --git a/packages/hooks/src/useUserInfo/index.ts b/packages/hooks/src/useUserInfo/index.ts new file mode 100644 index 000000000..259def8d8 --- /dev/null +++ b/packages/hooks/src/useUserInfo/index.ts @@ -0,0 +1,129 @@ +import { + UserInfo, + getUserInfo, + getUserProfile, + General, + ENV_TYPE, +} from '@tarojs/taro'; +import { useCallback, useState } from 'react'; +import useEnv from '../useEnv'; + +export interface IUserInfo extends Partial { + rawData?: string; + signature?: string; + encryptedData?: string; + iv?: string; + cloudID?: string; +} + +export type TLang = 'en' | 'zh_CN' | 'zh_TW'; + +export interface IOption { + withCredentials?: boolean; + lang?: TLang; +} + +export interface IProfileOption { + lang?: TLang; + desc: string; +} + +export type INormalAction = ( + option?: IOption, +) => Promise; + +export type IProfileAction = ( + option: IProfileOption, +) => Promise; + +const INITOPTION: IOption = { withCredentials: false, lang: 'en' }; + +function useUserInfo(): [ + IUserInfo, + { getUserInfo: INormalAction; getUserProfile: IProfileAction }, +] { + const [userInfo, setUserInfo] = useState({}); + const env = useEnv(); + + const combineUserInfo = useCallback( + ( + info: + | getUserInfo.SuccessCallbackResult + | getUserProfile.SuccessCallbackResult, + ): IUserInfo => { + const { userInfo, rawData, signature, encryptedData, iv, cloudID } = + info || {}; + const painInfo = Object.fromEntries( + Object.entries({ + rawData, + signature, + encryptedData, + iv, + cloudID, + }).filter((v) => v[1]), + ); + const finallyUserInfo = { + ...userInfo, + ...painInfo, + }; + setUserInfo(finallyUserInfo); + return finallyUserInfo; + }, + [], + ); + + const getUserInfoAsync = useCallback( + (option = INITOPTION) => { + return new Promise((resolve, reject) => { + if (env === ENV_TYPE.WEAPP) { + try { + getUserInfo({ + ...option, + success: (res) => { + const info = combineUserInfo(res); + resolve(info); + }, + fail: reject, + }).catch((e) => reject({ errMsg: 'getUserInfo: fail', data: e })); + } catch (e) { + reject({ errMsg: 'getUserInfo: fail', data: e }); + } + } else { + reject({ errMsg: 'getUserInfo: fail' }); + } + }); + }, + [combineUserInfo, env], + ); + + const getUserProfileAsync = useCallback( + (option) => { + return new Promise((resolve, reject) => { + if (env === ENV_TYPE.WEAPP) { + try { + getUserProfile({ + ...option, + success: (res) => { + const info = combineUserInfo(res); + resolve(info); + }, + fail: reject, + }); + } catch (e) { + reject({ errMsg: 'getUserProfile: fail', data: e }); + } + } else { + reject({ errMsg: 'getUserProfile: fail' }); + } + }); + }, + [combineUserInfo, env], + ); + + return [ + userInfo, + { getUserInfo: getUserInfoAsync, getUserProfile: getUserProfileAsync }, + ]; +} + +export default useUserInfo;