登录修改

This commit is contained in:
lxy 2024-08-07 17:50:51 +08:00
parent a39d4e2311
commit 8a258d55d4
20 changed files with 475 additions and 216 deletions

View File

@ -8,9 +8,10 @@
} }
&_dev { &_dev {
height: 280px; // height: 280px;
border: 1px dashed var(--oak-color-primary); border: 1px dashed var(--oak-color-primary);
padding: 10px; padding: 10px;
margin-bottom: 12px;
&_header { &_header {
display: flex; display: flex;
@ -36,7 +37,7 @@
} }
&_btn { &_btn {
margin-top: 16px; // margin-top: 16px;
width: 80px; width: 80px;
height: 80px; height: 80px;
border-radius: 40px; border-radius: 40px;
@ -102,16 +103,16 @@
&_disable { &_disable {
&_border { &_border {
height: 202px; height: 220px;
width: 202px; width: 220px;
margin: 15px; // margin: 15px;
background-color: #f5f7fa; background-color: #f5f7fa;
color: rgba(0, 0, 0, .4); color: rgba(0, 0, 0, .4);
font-size: 14px; font-size: 14px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding: 15px; // padding: 15px;
} }
&_info { &_info {
@ -124,7 +125,7 @@
} }
&_err { &_err {
height: 280px; // height: 280px;
border: 1px dashed var(--oak-color-primary); border: 1px dashed var(--oak-color-primary);
padding: 10px; padding: 10px;
display: flex; display: flex;

View File

@ -10,7 +10,8 @@ export default OakComponent({
isSupportWechatGrant: false, isSupportWechatGrant: false,
domain: undefined, domain: undefined,
passportTypes: [], passportTypes: [],
options: [], inputOptions: [],
scanOptions: [],
allowSms: false, allowSms: false,
allowPassword: false, allowPassword: false,
allowWechatMp: false, allowWechatMp: false,
@ -28,38 +29,44 @@ export default OakComponent({
return {}; return {};
}, },
listeners: { listeners: {
'onlyPassword,onlyCaptcha'(prev, next) { // 'onlyPassword,onlyCaptcha'(prev, next) {
let loginMode = this.state.loginMode, options = this.state.options; // let loginMode = this.state.loginMode, inputOptions = this.state.inputOptions, scanOptions = this.state.scanOptions;
if (next.onlyPassword) { // if (next.onlyPassword) {
loginMode = 'password'; // loginMode = 'password';
options = [{ // inputOptions = [{
label: this.t('passport:v.type.password'), // label: this.t('passport:v.type.password'),
value: 'password', // value: 'password',
}]; // }];
} // } else if (next.onlyCaptcha) {
else if (next.onlyCaptcha) { // loginMode = 'sms';
loginMode = 'sms'; // inputOptions = [{
options = [{ // label: this.t('passport:v.type.sms'),
label: this.t('passport:v.type.sms'), // value: 'sms',
value: 'sms', // }];
}]; // } else {
} // const { passportTypes } = this.state;
else { // if (passportTypes && passportTypes.length > 0) {
const { passportTypes } = this.state; // passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => {
if (passportTypes && passportTypes.length > 0) { // if (ele === 'sms' || ele === 'email' || ele === 'password') {
passportTypes.forEach((ele) => { // inputOptions.push({
options.push({ // label: this.t(`passport:v.type.${ele}`),
label: this.t(`passport:v.type.${ele}`), // value: ele
value: ele // })
}); // } else if (ele === 'wechatMpForWeb' || ele === 'wechatPublicForWeb') {
}); // scanOptions.push({
} // label: this.t(`passport:v.type.${ele}`),
} // value: ele
this.setState({ // })
loginMode, // }
options, // });
}); // }
} // }
// this.setState({
// loginMode,
// inputOptions,
// scanOptions,
// })
// }
}, },
lifetimes: { lifetimes: {
async ready() { async ready() {
@ -67,30 +74,42 @@ export default OakComponent({
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id }); const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const defaultPassport = applicationPassports.find((ele) => ele.isDefault); const defaultPassport = applicationPassports.find((ele) => ele.isDefault);
const passportTypes = applicationPassports.map((ele) => ele.passport.type); const passportTypes = applicationPassports.map((ele) => ele.passport.type);
const { onlyCaptcha, onlyPassword } = this.props;
let loginMode = (await this.load(LOGIN_MODE)) || defaultPassport.passport.type; let loginMode = (await this.load(LOGIN_MODE)) || defaultPassport.passport.type;
let options = []; let inputOptions = [], scanOptions = [];
if (this.props.onlyPassword) { if (onlyPassword) {
loginMode = 'password'; loginMode = 'password';
options = [{ inputOptions = [{
label: this.t('passport:v.type.password'), label: this.t('passport:v.type.password') + this.t('Login'),
value: 'password', value: 'password',
}]; }];
} }
else if (this.props.onlyCaptcha) { else if (onlyCaptcha) {
loginMode = 'sms'; loginMode = 'sms';
options = [{ inputOptions = [{
label: this.t('passport:v.type.sms'), label: this.t('passport:v.type.sms') + this.t('Login'),
value: 'sms', value: 'sms',
}]; }];
} }
else { else {
passportTypes.forEach((ele) => { passportTypes.forEach((ele) => {
options.push({ if (ele === 'sms' || ele === 'email' || ele === 'password') {
label: this.t(`passport:v.type.${ele}`), inputOptions.push({
value: ele label: this.t(`passport:v.type.${ele}`) + this.t('Login'),
}); value: ele
});
}
else if (ele === 'wechatWeb' || ele === 'wechatMpForWeb' || ele === 'wechatPublicForWeb') {
scanOptions.push({
label: this.t(`passport:v.type.${ele}`) + this.t('Login'),
value: ele
});
}
}); });
} }
if (!passportTypes.includes(loginMode)) {
loginMode = defaultPassport.passport.type;
}
const appType = application?.type; const appType = application?.type;
const config = application?.config; const config = application?.config;
let appId; let appId;
@ -109,9 +128,9 @@ export default OakComponent({
domain = config2?.wechat?.domain; domain = config2?.wechat?.domain;
} }
else if (appType === 'wechatMp') { else if (appType === 'wechatMp') {
allowSms = passportTypes.includes('sms'); allowSms = passportTypes.includes('sms') && !onlyPassword;
allowPassword = passportTypes.includes('password'); allowPassword = passportTypes.includes('password') && !onlyCaptcha;
allowWechatMp = passportTypes.includes('wechatMp'); allowWechatMp = passportTypes.includes('wechatMp') && !onlyCaptcha && !onlyPassword;
} }
this.setState({ this.setState({
loginMode, loginMode,
@ -119,7 +138,8 @@ export default OakComponent({
isSupportWechatGrant, isSupportWechatGrant,
domain, domain,
passportTypes, passportTypes,
options, inputOptions,
scanOptions,
allowSms, allowSms,
allowPassword, allowPassword,
allowWechatMp, allowWechatMp,

View File

@ -10,5 +10,8 @@
"Mobile": "请输入手机号", "Mobile": "请输入手机号",
"Password": "请输入密码" "Password": "请输入密码"
}, },
"resendAfter": "秒后可重发" "resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
} }

View File

@ -17,7 +17,8 @@ export default function Render(props: WebComponentProps<EntityDict, 'token', fal
url: string; url: string;
passportTypes: EntityDict['passport']['Schema']['type'][]; passportTypes: EntityDict['passport']['Schema']['type'][];
callback: (() => void) | undefined; callback: (() => void) | undefined;
options: Option[]; inputOptions: Option[];
scanOptions: Option[];
}, { }, {
setLoginMode: (value: number) => void; setLoginMode: (value: number) => void;
}>): React.JSX.Element; }>): React.JSX.Element;

View File

@ -1,7 +1,8 @@
// @ts-nocheck // @ts-nocheck
// Segmented这个对象在antd里的声明是错误的 // Segmented这个对象在antd里的声明是错误的
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Segmented } from 'antd'; import { Segmented, Divider, Space } from 'antd';
import { MobileOutlined, QrcodeOutlined, DesktopOutlined, MailOutlined, ExclamationCircleOutlined, } from '@ant-design/icons';
import classNames from 'classnames'; import classNames from 'classnames';
import Style from './web.module.less'; import Style from './web.module.less';
import WeChatLoginQrCode from '../../common/weChatLoginQrCode'; import WeChatLoginQrCode from '../../common/weChatLoginQrCode';
@ -11,7 +12,7 @@ import SmsLogin from './sms';
import PasswordLogin from './password'; import PasswordLogin from './password';
export default function Render(props) { export default function Render(props) {
const { data, methods } = props; const { data, methods } = props;
const { width, loading, loginMode, appId, domain, isSupportWechatGrant, disabled, redirectUri, url, passportTypes, callback, options, } = data; const { width, loading, loginMode, appId, domain, isSupportWechatGrant, disabled, redirectUri, url, passportTypes, callback, inputOptions, scanOptions, } = data;
const { t, setLoginMode } = methods; const { t, setLoginMode } = methods;
let redirectUri2 = redirectUri; let redirectUri2 = redirectUri;
if (!(redirectUri.startsWith('https') || redirectUri.startsWith('http'))) { if (!(redirectUri.startsWith('https') || redirectUri.startsWith('http'))) {
@ -25,15 +26,65 @@ export default function Render(props) {
if (url) { if (url) {
state = encodeURIComponent(decodeURIComponent(url)); state = encodeURIComponent(decodeURIComponent(url));
} }
const [showInput, setShowInput] = useState(true);
useEffect(() => {
if (loginMode === 'sms' || loginMode === 'email' || loginMode === 'password') {
setShowInput(true);
}
else {
setShowInput(false);
}
}, [loginMode]);
const InputMethods = inputOptions && inputOptions.length > 0 ? (<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 24 }}>
{inputOptions.map((ele) => {
let icon = <></>;
if (ele.value === 'sms') {
icon = <MobileOutlined />;
}
else if (ele.value === 'password') {
icon = <DesktopOutlined />;
}
else if (ele.value === 'email') {
icon = <MailOutlined />;
}
return (<Space size={4} style={{ cursor: 'pointer' }} onClick={() => {
setLoginMode(ele.value);
}}>
{icon}
<div>{ele.label}</div>
</Space>);
})}
</div>
</div>) : <></>;
const ScanMethods = scanOptions && scanOptions.length > 0 ? (<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space style={{ cursor: 'pointer' }} onClick={() => {
setLoginMode(scanOptions[0].value);
}}>
<QrcodeOutlined />
<div>{t('scanLogin')}</div>
</Space>
</div>) : <></>;
const Tip = <div className={Style['loginbox-tip']}>
<Space>
<ExclamationCircleOutlined />
<div>{t('tip')}</div>
</Space>
</div>;
return (<div className={classNames(Style['loginbox-wrap'], { return (<div className={classNames(Style['loginbox-wrap'], {
[Style['loginbox-wrap__mobile']]: width === 'xs', [Style['loginbox-wrap__mobile']]: width === 'xs',
})}> })}>
{options?.length > 1 ? (<div className={Style['loginbox-hd']}> <div className={Style['loginbox-hd']}>
<Segmented className={Style.segmented} value={loginMode} block onChange={setLoginMode} options={options.map((ele) => ({ <Segmented className={Style.segmented} value={loginMode} block onChange={setLoginMode} options={showInput ? inputOptions.map((ele) => ({
label: ele.label, label: ele.label,
value: ele.value, value: ele.value,
}))}></Segmented> })) : scanOptions.map((ele) => ({
</div>) : (<div className={Style['loginbox-hd']}></div>)} label: ele.label,
value: ele.value,
}))}></Segmented>
</div>
<div className={classNames(Style['loginbox-bd'], { <div className={classNames(Style['loginbox-bd'], {
[Style['loginbox-bd__grant']]: isSupportWechatGrant, [Style['loginbox-bd__grant']]: isSupportWechatGrant,
@ -41,22 +92,32 @@ export default function Render(props) {
{isSupportWechatGrant ? (<div className={Style['loginbox-grant']}> {isSupportWechatGrant ? (<div className={Style['loginbox-grant']}>
<WeChatLoginGrant disabled={!!disabled} disableText={disabled} appId={appId} scope="snsapi_userinfo" redirectUri={redirectUri2} state={state}/> <WeChatLoginGrant disabled={!!disabled} disableText={disabled} appId={appId} scope="snsapi_userinfo" redirectUri={redirectUri2} state={state}/>
</div>) : (<> </div>) : (<>
<div className={Style['loginbox-password']} style={{ {showInput ? (<>
display: loginMode === 'password' ? 'block' : 'none', <div className={Style['loginbox-password']} style={{
}}> display: loginMode === 'password' ? 'block' : 'none',
<PasswordLogin disabled={disabled} url={url} callback={callback}/> }}>
</div> <PasswordLogin disabled={disabled} url={url} callback={callback}/>
<div className={Style['loginbox-mobile']} style={{ {Tip}
display: loginMode === 'sms' ? 'block' : 'none', </div>
}}> <div className={Style['loginbox-mobile']} style={{
<SmsLogin disabled={disabled} url={url} callback={callback}/> display: loginMode === 'sms' ? 'block' : 'none',
</div> }}>
{loginMode === 'wechatWeb' && <div className={Style['loginbox-qrcode']}> <SmsLogin disabled={disabled} url={url} callback={callback}/>
<WeChatLoginQrCode disabled={!!disabled} disableText={disabled} appId={appId} scope="snsapi_login" redirectUri={redirectUri2} state={state}/> {Tip}
</div>} </div>
{loginMode === 'wechatPublicForWeb' && <div className={Style['loginbox-qrcode']}> {ScanMethods}
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state}/> </>) : (<>
</div>} {loginMode === 'wechatWeb' &&
<div className={Style['loginbox-qrcode']}>
<WeChatLoginQrCode disabled={!!disabled} disableText={disabled} appId={appId} scope="snsapi_login" redirectUri={redirectUri2} state={state}/>
{Tip}
</div>}
{loginMode === 'wechatPublicForWeb' && <div className={Style['loginbox-qrcode']}>
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state} size={200}/>
{Tip}
</div>}
{InputMethods}
</>)}
</>)} </>)}
</div> </div>
</div>); </div>);

View File

@ -21,14 +21,16 @@
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 4px rgb(0 0 0 / 8%), 0 0 4px rgb(0 0 0 / 8%); box-shadow: 0 2px 4px rgb(0 0 0 / 8%), 0 0 4px rgb(0 0 0 / 8%);
transition: all 0.5s;
} }
&-hd { &-hd {
padding: 32px; padding: 32px;
padding-bottom: 0px;
} }
&-bd { &-bd {
height: 310px; min-height: 320px;
} }
&-only { &-only {
@ -37,17 +39,21 @@
&-mobile { &-mobile {
position: relative; position: relative;
padding: 0 32px; padding: 32px;
height: 220px;
} }
&-password { &-password {
position: relative; position: relative;
padding: 0 32px; padding: 32px;
height: 220px;
} }
&-qrcode { &-qrcode {
padding: 0 32px; padding: 0 32px;
font-size: 14px; font-size: 14px;
height: 268px;
padding-top: 16px;
&__sociallogin { &__sociallogin {
text-align: center; text-align: center;
@ -95,4 +101,23 @@
cursor: default; cursor: default;
background-color: #fff; background-color: #fff;
} }
&-methods {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0px 24px 16px 24px;
font-size: 13px;
color: #6c7d8f;
}
&-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 13px;
color: #808080;
}
} }

View File

@ -1,6 +1,7 @@
import { EntityDict } from '../../../oak-app-domain'; import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, false, { declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, false, {
type: "login" | "bind"; type: "bind" | "login";
url: string; url: string;
size: undefined;
}>) => React.ReactElement; }>) => React.ReactElement;
export default _default; export default _default;

View File

@ -28,6 +28,7 @@ export default OakComponent({
properties: { properties: {
type: 'bind', type: 'bind',
url: '', url: '',
size: undefined,
}, },
methods: { methods: {
async createWechatLogin() { async createWechatLogin() {

View File

@ -7,4 +7,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'wechatLogin
loading: boolean; loading: boolean;
successful: boolean; successful: boolean;
type: EntityDict['wechatLogin']['Schema']['type']; type: EntityDict['wechatLogin']['Schema']['type'];
size: number;
}, {}>): React.JSX.Element; }, {}>): React.JSX.Element;

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import QrCode from '../../../components/common/qrCode'; import QrCode from '../../../components/common/qrCode';
export default function Render(props) { export default function Render(props) {
const { oakFullpath, qrCodeUrl, loading, successful, type } = props.data; const { oakFullpath, qrCodeUrl, loading, successful, type, size } = props.data;
return (<div> return (<div>
<QrCode loading={loading} url={qrCodeUrl} disableDownload={true} tips={<div>微信扫一扫</div>} successful={successful} type={type}/> <QrCode loading={loading} url={qrCodeUrl} disableDownload={true} tips={<div>微信扫一扫</div>} successful={successful} type={type} size={size}/>
</div>); </div>);
} }

View File

@ -317,7 +317,10 @@ const i18ns = [
"Mobile": "请输入手机号", "Mobile": "请输入手机号",
"Password": "请输入密码" "Password": "请输入密码"
}, },
"resendAfter": "秒后可重发" "resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
} }
}, },
{ {

View File

@ -319,7 +319,10 @@ const i18ns = [
"Mobile": "请输入手机号", "Mobile": "请输入手机号",
"Password": "请输入密码" "Password": "请输入密码"
}, },
"resendAfter": "秒后可重发" "resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
} }
}, },
{ {

View File

@ -8,9 +8,10 @@
} }
&_dev { &_dev {
height: 280px; // height: 280px;
border: 1px dashed var(--oak-color-primary); border: 1px dashed var(--oak-color-primary);
padding: 10px; padding: 10px;
margin-bottom: 12px;
&_header { &_header {
display: flex; display: flex;
@ -36,7 +37,7 @@
} }
&_btn { &_btn {
margin-top: 16px; // margin-top: 16px;
width: 80px; width: 80px;
height: 80px; height: 80px;
border-radius: 40px; border-radius: 40px;
@ -102,16 +103,16 @@
&_disable { &_disable {
&_border { &_border {
height: 202px; height: 220px;
width: 202px; width: 220px;
margin: 15px; // margin: 15px;
background-color: #f5f7fa; background-color: #f5f7fa;
color: rgba(0, 0, 0, .4); color: rgba(0, 0, 0, .4);
font-size: 14px; font-size: 14px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
padding: 15px; // padding: 15px;
} }
&_info { &_info {
@ -124,7 +125,7 @@
} }
&_err { &_err {
height: 280px; // height: 280px;
border: 1px dashed var(--oak-color-primary); border: 1px dashed var(--oak-color-primary);
padding: 10px; padding: 10px;
display: flex; display: flex;

View File

@ -19,7 +19,8 @@ export default OakComponent({
isSupportWechatGrant: false, isSupportWechatGrant: false,
domain: undefined as string | undefined, domain: undefined as string | undefined,
passportTypes: [] as EntityDict['passport']['Schema']['type'][], passportTypes: [] as EntityDict['passport']['Schema']['type'][],
options: [] as Option[], inputOptions: [] as Option[],
scanOptions: [] as Option[],
allowSms: false, allowSms: false,
allowPassword: false, allowPassword: false,
allowWechatMp: false, allowWechatMp: false,
@ -37,36 +38,44 @@ export default OakComponent({
return {}; return {};
}, },
listeners: { listeners: {
'onlyPassword,onlyCaptcha'(prev, next) { // 'onlyPassword,onlyCaptcha'(prev, next) {
let loginMode = this.state.loginMode, options = this.state.options; // let loginMode = this.state.loginMode, inputOptions = this.state.inputOptions, scanOptions = this.state.scanOptions;
if (next.onlyPassword) { // if (next.onlyPassword) {
loginMode = 'password'; // loginMode = 'password';
options = [{ // inputOptions = [{
label: this.t('passport:v.type.password'), // label: this.t('passport:v.type.password'),
value: 'password', // value: 'password',
}]; // }];
} else if (next.onlyCaptcha) { // } else if (next.onlyCaptcha) {
loginMode = 'sms'; // loginMode = 'sms';
options = [{ // inputOptions = [{
label: this.t('passport:v.type.sms'), // label: this.t('passport:v.type.sms'),
value: 'sms', // value: 'sms',
}]; // }];
} else { // } else {
const { passportTypes } = this.state; // const { passportTypes } = this.state;
if (passportTypes && passportTypes.length > 0) { // if (passportTypes && passportTypes.length > 0) {
passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => { // passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => {
options.push({ // if (ele === 'sms' || ele === 'email' || ele === 'password') {
label: this.t(`passport:v.type.${ele}`), // inputOptions.push({
value: ele // label: this.t(`passport:v.type.${ele}`),
}) // value: ele
}); // })
} // } else if (ele === 'wechatMpForWeb' || ele === 'wechatPublicForWeb') {
} // scanOptions.push({
this.setState({ // label: this.t(`passport:v.type.${ele}`),
loginMode, // value: ele
options, // })
}) // }
} // });
// }
// }
// this.setState({
// loginMode,
// inputOptions,
// scanOptions,
// })
// }
}, },
lifetimes: { lifetimes: {
async ready() { async ready() {
@ -75,29 +84,42 @@ export default OakComponent({
const defaultPassport = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.isDefault); const defaultPassport = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.isDefault);
const passportTypes = applicationPassports.map((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type); const passportTypes = applicationPassports.map((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type);
const { onlyCaptcha, onlyPassword } = this.props;
let loginMode = (await this.load(LOGIN_MODE)) || defaultPassport.passport.type; let loginMode = (await this.load(LOGIN_MODE)) || defaultPassport.passport.type;
let options: Option[] = []; let inputOptions: Option[] = [], scanOptions: Option[] = [];
if (this.props.onlyPassword) { if (onlyPassword) {
loginMode = 'password'; loginMode = 'password';
options = [{ inputOptions = [{
label: this.t('passport:v.type.password'), label: this.t('passport:v.type.password') + this.t('Login'),
value: 'password', value: 'password',
}]; }];
} else if (this.props.onlyCaptcha) { } else if (onlyCaptcha) {
loginMode = 'sms'; loginMode = 'sms';
options = [{ inputOptions = [{
label: this.t('passport:v.type.sms'), label: this.t('passport:v.type.sms') + this.t('Login'),
value: 'sms', value: 'sms',
}]; }];
} else { } else {
passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => { passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => {
options.push({ if (ele === 'sms' || ele === 'email' || ele === 'password') {
label: this.t(`passport:v.type.${ele}`), inputOptions.push({
value: ele label: this.t(`passport:v.type.${ele}`) + this.t('Login'),
}) value: ele
})
} else if (ele === 'wechatWeb' || ele === 'wechatMpForWeb' || ele === 'wechatPublicForWeb') {
scanOptions.push({
label: this.t(`passport:v.type.${ele}`) + this.t('Login'),
value: ele
})
}
}); });
} }
if (!passportTypes.includes(loginMode)) {
loginMode = defaultPassport.passport.type;
}
const appType = application?.type as AppType; const appType = application?.type as AppType;
const config = application?.config; const config = application?.config;
let appId; let appId;
@ -114,9 +136,9 @@ export default OakComponent({
appId = config2?.wechat?.appId; appId = config2?.wechat?.appId;
domain = config2?.wechat?.domain; domain = config2?.wechat?.domain;
} else if (appType === 'wechatMp') { } else if (appType === 'wechatMp') {
allowSms = passportTypes.includes('sms'); allowSms = passportTypes.includes('sms') && !onlyPassword;
allowPassword = passportTypes.includes('password'); allowPassword = passportTypes.includes('password') && !onlyCaptcha;
allowWechatMp = passportTypes.includes('wechatMp'); allowWechatMp = passportTypes.includes('wechatMp') && !onlyCaptcha && !onlyPassword;
} }
this.setState( this.setState(
@ -126,7 +148,8 @@ export default OakComponent({
isSupportWechatGrant, isSupportWechatGrant,
domain, domain,
passportTypes, passportTypes,
options, inputOptions,
scanOptions,
allowSms, allowSms,
allowPassword, allowPassword,
allowWechatMp, allowWechatMp,

View File

@ -10,5 +10,8 @@
"Mobile": "请输入手机号", "Mobile": "请输入手机号",
"Password": "请输入密码" "Password": "请输入密码"
}, },
"resendAfter": "秒后可重发" "resendAfter": "秒后可重发",
} "otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
}

View File

@ -21,14 +21,16 @@
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 4px rgb(0 0 0 / 8%), 0 0 4px rgb(0 0 0 / 8%); box-shadow: 0 2px 4px rgb(0 0 0 / 8%), 0 0 4px rgb(0 0 0 / 8%);
transition: all 0.5s;
} }
&-hd { &-hd {
padding: 32px; padding: 32px;
padding-bottom: 0px;
} }
&-bd { &-bd {
height: 310px; min-height: 320px;
} }
&-only { &-only {
@ -37,17 +39,21 @@
&-mobile { &-mobile {
position: relative; position: relative;
padding: 0 32px; padding: 32px;
height: 220px;
} }
&-password { &-password {
position: relative; position: relative;
padding: 0 32px; padding: 32px;
height: 220px;
} }
&-qrcode { &-qrcode {
padding: 0 32px; padding: 0 32px;
font-size: 14px; font-size: 14px;
height: 268px;
padding-top: 16px;
&__sociallogin { &__sociallogin {
text-align: center; text-align: center;
@ -95,4 +101,23 @@
cursor: default; cursor: default;
background-color: #fff; background-color: #fff;
} }
&-methods {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0px 24px 16px 24px;
font-size: 13px;
color: #6c7d8f;
}
&-tip {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 13px;
color: #808080;
}
} }

View File

@ -6,10 +6,14 @@ import {
isPassword, isPassword,
isCaptcha, isCaptcha,
} from 'oak-domain/lib/utils/validator'; } from 'oak-domain/lib/utils/validator';
import { Form, Input, Button, Checkbox, Typography, Segmented } from 'antd'; import { Form, Input, Button, Checkbox, Typography, Segmented, Divider, Space } from 'antd';
import { import {
LockOutlined, LockOutlined,
MobileOutlined, MobileOutlined,
QrcodeOutlined,
DesktopOutlined,
MailOutlined,
ExclamationCircleOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { WebComponentProps } from 'oak-frontend-base'; import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain'; import { EntityDict } from '../../../oak-app-domain';
@ -44,7 +48,8 @@ export default function Render(
url: string; url: string;
passportTypes: EntityDict['passport']['Schema']['type'][]; passportTypes: EntityDict['passport']['Schema']['type'][];
callback: (() => void) | undefined; callback: (() => void) | undefined;
options: Option[]; inputOptions: Option[];
scanOptions: Option[];
}, },
{ {
setLoginMode: (value: number) => void; setLoginMode: (value: number) => void;
@ -64,7 +69,8 @@ export default function Render(
url, url,
passportTypes, passportTypes,
callback, callback,
options, inputOptions,
scanOptions,
} = data; } = data;
const { t, setLoginMode } = methods; const { t, setLoginMode } = methods;
@ -84,28 +90,88 @@ export default function Render(
state = encodeURIComponent(decodeURIComponent(url)); state = encodeURIComponent(decodeURIComponent(url));
} }
const [showInput, setShowInput] = useState(true);
useEffect(() => {
if (loginMode === 'sms' || loginMode === 'email' || loginMode === 'password') {
setShowInput(true)
} else {
setShowInput(false)
}
}, [loginMode]);
const InputMethods = inputOptions && inputOptions.length > 0 ? (
<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 24 }}>
{inputOptions.map((ele) => {
let icon = <></>;
if (ele.value === 'sms') {
icon = <MobileOutlined />;
} else if (ele.value === 'password') {
icon = <DesktopOutlined />;
} else if (ele.value === 'email') {
icon = <MailOutlined />;
}
return (
<Space
size={4}
style={{ cursor: 'pointer' }}
onClick={() => {
setLoginMode(ele.value)
}}
>
{icon}
<div>{ele.label}</div>
</Space>
)
})}
</div>
</div>
) : <></>
const ScanMethods = scanOptions && scanOptions.length > 0 ? (
<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space
style={{ cursor: 'pointer' }}
onClick={() => {
setLoginMode(scanOptions[0].value)
}}
>
<QrcodeOutlined />
<div>{t('scanLogin')}</div>
</Space>
</div>
) : <></>
const Tip = <div className={Style['loginbox-tip']}>
<Space>
<ExclamationCircleOutlined />
<div>{t('tip')}</div>
</Space>
</div>
return ( return (
<div <div
className={classNames(Style['loginbox-wrap'], { className={classNames(Style['loginbox-wrap'], {
[Style['loginbox-wrap__mobile']]: width === 'xs', [Style['loginbox-wrap__mobile']]: width === 'xs',
})} })}
> >
{options?.length > 1 ? ( <div className={Style['loginbox-hd']}>
<div className={Style['loginbox-hd']}> <Segmented
<Segmented className={Style.segmented}
className={Style.segmented} value={loginMode}
value={loginMode} block
block onChange={setLoginMode}
onChange={setLoginMode} options={showInput ? inputOptions.map((ele) => ({
options={options.map((ele) => ({ label: ele.label,
label: ele.label, value: ele.value,
value: ele.value, })) : scanOptions.map((ele) => ({
}))} label: ele.label,
></Segmented> value: ele.value,
</div> }))}
) : ( ></Segmented>
<div className={Style['loginbox-hd']}></div> </div>
)}
<div <div
className={classNames(Style['loginbox-bd'], { className={classNames(Style['loginbox-bd'], {
@ -125,55 +191,70 @@ export default function Render(
</div> </div>
) : ( ) : (
<> <>
<div {showInput ? (
className={Style['loginbox-password']} <>
style={{ <div
display: loginMode === 'password' ? 'block' : 'none', className={Style['loginbox-password']}
}} style={{
> display: loginMode === 'password' ? 'block' : 'none',
<PasswordLogin }}
disabled={disabled} >
url={url} <PasswordLogin
callback={callback} disabled={disabled}
/> url={url}
</div> callback={callback}
<div />
className={Style['loginbox-mobile']} {Tip}
style={{ </div>
display: loginMode === 'sms' ? 'block' : 'none', <div
}} className={Style['loginbox-mobile']}
> style={{
<SmsLogin display: loginMode === 'sms' ? 'block' : 'none',
disabled={disabled} }}
url={url} >
callback={callback} <SmsLogin
/> disabled={disabled}
</div> url={url}
{loginMode === 'wechatWeb' && <div callback={callback}
className={Style['loginbox-qrcode']} />
> {Tip}
<WeChatLoginQrCode </div>
disabled={!!disabled} {ScanMethods}
disableText={disabled} </>
appId={appId} ) : (
scope="snsapi_login" <>
redirectUri={redirectUri2} {loginMode === 'wechatWeb' &&
state={state} <div
/> className={Style['loginbox-qrcode']}
</div>} >
{loginMode === 'wechatPublicForWeb' && <div <WeChatLoginQrCode
className={Style['loginbox-qrcode']} disabled={!!disabled}
> disableText={disabled}
<WechatLoginQrCodeForPublic appId={appId}
type="login" scope="snsapi_login"
oakPath="$login-wechatLogin/qrCode" redirectUri={redirectUri2}
oakAutoUnmount={true} state={state}
url={state} />
/> {Tip}
</div>} </div>}
{loginMode === 'wechatPublicForWeb' && <div
className={Style['loginbox-qrcode']}
>
<WechatLoginQrCodeForPublic
type="login"
oakPath="$login-wechatLogin/qrCode"
oakAutoUnmount={true}
url={state}
size={200}
/>
{Tip}
</div>}
{InputMethods}
</>
)}
</> </>
)} )}
</div> </div>
</div> </div >
); );
} }

View File

@ -31,6 +31,7 @@ export default OakComponent({
properties: { properties: {
type: 'bind' as EntityDict['wechatLogin']['Schema']['type'], type: 'bind' as EntityDict['wechatLogin']['Schema']['type'],
url: '', url: '',
size: undefined,
}, },
methods: { methods: {
async createWechatLogin() { async createWechatLogin() {

View File

@ -16,11 +16,12 @@ export default function Render(
loading: boolean; loading: boolean;
successful: boolean; successful: boolean;
type: EntityDict['wechatLogin']['Schema']['type']; type: EntityDict['wechatLogin']['Schema']['type'];
size: number;
}, },
{} {}
> >
) { ) {
const { oakFullpath, qrCodeUrl, loading, successful, type } = props.data; const { oakFullpath, qrCodeUrl, loading, successful, type, size } = props.data;
return ( return (
<div> <div>
@ -31,6 +32,7 @@ export default function Render(
tips={<div></div>} tips={<div></div>}
successful={successful} successful={successful}
type={type} type={type}
size={size}
/> />
</div> </div>
); );

View File

@ -319,7 +319,10 @@ const i18ns: I18n[] = [
"Mobile": "请输入手机号", "Mobile": "请输入手机号",
"Password": "请输入密码" "Password": "请输入密码"
}, },
"resendAfter": "秒后可重发" "resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
} }
}, },
{ {