feat: 新增用户账号注册组件;登录组件适配账号登录

This commit is contained in:
lxy 2025-11-13 16:12:55 +08:00
parent b93ae46894
commit d7d302f329
55 changed files with 1956 additions and 249 deletions

View File

@ -735,5 +735,16 @@ export type AspectDict<ED extends EntityDict> = {
setUserAvatarFromWechat: (params: {
avatar: string;
}, context: BackendRuntimeContext<ED>) => Promise<void>;
/**
*
* @param loginName
* @param password
* @param context
* @returns
*/
registerUserByLoginName: (params: {
loginName: string;
password: string;
}, context: BackendRuntimeContext<ED>) => Promise<void>;
};
export default AspectDict;

View File

@ -4,7 +4,7 @@ import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, get
import { updateConfig, updateApplicationConfig, updateStyle } from './config';
import { syncMessageTemplate, getMessageType } from './template';
import { syncSmsTemplate } from './sms';
import { mergeUser, getChangePasswordChannels, updateUserPassword } from './user';
import { mergeUser, getChangePasswordChannels, updateUserPassword, registerUserByLoginName } from './user';
import { createWechatLogin } from './wechatLogin';
import { unbindingWechat } from './wechatUser';
import { getMpUnlimitWxaCode } from './wechatQrCode';
@ -86,6 +86,7 @@ declare const aspectDict: {
createOAuthState: typeof createOAuthState;
authorize: typeof authorize;
setUserAvatarFromWechat: typeof setUserAvatarFromWechat;
registerUserByLoginName: typeof registerUserByLoginName;
};
export default aspectDict;
export { AspectDict } from './AspectDict';

View File

@ -4,7 +4,7 @@ import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, get
import { updateConfig, updateApplicationConfig, updateStyle } from './config';
import { syncMessageTemplate, getMessageType } from './template';
import { syncSmsTemplate } from './sms';
import { mergeUser, getChangePasswordChannels, updateUserPassword } from './user';
import { mergeUser, getChangePasswordChannels, updateUserPassword, registerUserByLoginName } from './user';
import { createWechatLogin } from './wechatLogin';
import { unbindingWechat } from './wechatUser';
import { getMpUnlimitWxaCode } from './wechatQrCode';
@ -87,5 +87,6 @@ const aspectDict = {
createOAuthState,
authorize,
setUserAvatarFromWechat,
registerUserByLoginName,
};
export default aspectDict;

View File

@ -23,3 +23,12 @@ export declare function updateUserPassword<ED extends EntityDict>(params: {
result: string;
times?: undefined;
}>;
/**
*
* @param params
* @param context
*/
export declare function registerUserByLoginName<ED extends EntityDict>(params: {
loginName: string;
password: string;
}, context: BRC<ED>): Promise<void>;

View File

@ -1,4 +1,4 @@
import { OakOperationUnpermittedException } from "oak-domain/lib/types";
import { OakOperationUnpermittedException, OakPreConditionUnsetException } from "oak-domain/lib/types";
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
import { encryptPasswordSha1 } from '../utils/password';
import { assert } from 'oak-domain/lib/utils/assert';
@ -177,20 +177,17 @@ export async function updateUserPassword(params, context, innerLogic) {
const systemId = context.getSystemId();
const closeRootMode = context.openRootMode();
try {
const [passport] = await context.select('passport', {
const [system] = await context.select('system', {
data: {
id: 1,
type: 1,
config: 1,
systemId: 1,
},
filter: {
systemId,
type: 'password',
id: systemId,
}
}, { forUpdate: true });
assert(passport);
const config = passport.config;
assert(system);
const config = system.config?.Password;
const mode = config?.mode ?? 'all';
const [user] = await context.select('user', {
data: {
@ -257,13 +254,13 @@ export async function updateUserPassword(params, context, innerLogic) {
};
}
const allowUpdate = mode === 'sha1' ? user.passwordSha1 === prevPassword : user.password === prevPassword; //sha1密文模式判断密文是否相等
let userDate = {}, changeCreateDate = {};
let userData = {}, changeCreateData = {};
if (mode === 'all') {
userDate = {
userData = {
password: newPassword,
passwordSha1: encryptPasswordSha1(newPassword),
};
changeCreateDate = {
changeCreateData = {
prevPassword,
newPassword,
prevPasswordSha1: encryptPasswordSha1(prevPassword),
@ -271,19 +268,19 @@ export async function updateUserPassword(params, context, innerLogic) {
};
}
else if (mode === 'plain') {
userDate = {
userData = {
password: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPassword,
newPassword,
};
}
else if (mode === 'sha1') {
userDate = {
userData = {
passwordSha1: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPasswordSha1: prevPassword,
newPasswordSha1: newPassword,
};
@ -292,7 +289,7 @@ export async function updateUserPassword(params, context, innerLogic) {
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'update',
data: userDate,
data: userData,
filter: {
id: userId,
},
@ -306,7 +303,7 @@ export async function updateUserPassword(params, context, innerLogic) {
id: await generateNewIdAsync(),
userId,
result: 'success',
...changeCreateDate,
...changeCreateData,
},
}, {
dontCollect: true,
@ -324,7 +321,7 @@ export async function updateUserPassword(params, context, innerLogic) {
id: await generateNewIdAsync(),
userId,
result: 'fail',
...changeCreateDate,
...changeCreateData,
},
}, {
dontCollect: true,
@ -353,13 +350,13 @@ export async function updateUserPassword(params, context, innerLogic) {
dontCollect: true,
});
if (aliveCaptcha) {
let userDate = {}, changeCreateDate = {};
let userData = {}, changeCreateData = {};
if (mode === 'all') {
userDate = {
userData = {
password: newPassword,
passwordSha1: encryptPasswordSha1(newPassword),
};
changeCreateDate = {
changeCreateData = {
prevPassword: user.password,
newPassword,
prevPasswordSha1: user.passwordSha1,
@ -367,19 +364,19 @@ export async function updateUserPassword(params, context, innerLogic) {
};
}
else if (mode === 'plain') {
userDate = {
userData = {
password: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPassword: user.password,
newPassword,
};
}
else if (mode === 'sha1') {
userDate = {
userData = {
passwordSha1: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPasswordSha1: user.passwordSha1,
newPasswordSha1: newPassword,
};
@ -387,7 +384,7 @@ export async function updateUserPassword(params, context, innerLogic) {
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'update',
data: userDate,
data: userData,
filter: {
id: userId,
},
@ -401,7 +398,7 @@ export async function updateUserPassword(params, context, innerLogic) {
id: await generateNewIdAsync(),
userId,
result: 'success',
...changeCreateDate,
...changeCreateData,
},
}, {
dontCollect: true,
@ -428,3 +425,83 @@ export async function updateUserPassword(params, context, innerLogic) {
throw err;
}
}
/**
* 用户账号注册
* @param params
* @param context
*/
export async function registerUserByLoginName(params, context) {
const { loginName, password } = params;
const systemId = context.getSystemId();
const closeRootMode = context.openRootMode();
try {
// 检查loginName是否重复
const [existLoginName] = await context.select('loginName', {
data: {
id: 1,
name: 1,
},
filter: {
name: loginName,
ableState: 'enabled',
},
}, { dontCollect: true, forUpdate: true });
if (existLoginName) {
closeRootMode();
throw new OakPreConditionUnsetException('账号已存在,请重新设置');
}
// 创建user并附上密码级联创建loginName
const [system] = await context.select('system', {
data: {
id: 1,
config: 1,
},
filter: {
id: systemId,
}
}, { forUpdate: true });
assert(system);
const config = system.config?.Password;
const mode = config?.mode ?? 'all';
let passwordData = {};
if (mode === 'all') {
passwordData = {
password: password,
passwordSha1: encryptPasswordSha1(password),
};
}
else if (mode === 'plain') {
passwordData = {
password: password,
};
}
else if (mode === 'sha1') {
passwordData = {
passwordSha1: password,
};
}
const userData = {
id: await generateNewIdAsync(),
loginName$user: [
{
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
name: loginName,
}
}
]
};
Object.assign(userData, passwordData);
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'create',
data: userData,
}, {});
}
catch (err) {
closeRootMode();
throw err;
}
}

View File

@ -21,8 +21,8 @@ export default OakComponent({
},
properties: {
disabled: '',
url: '',
callback: undefined,
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
setLoginMode: (value) => undefined,
digit: 4, //验证码位数
},

View File

@ -6,5 +6,8 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
redirectUri: string;
url: string;
callback: (() => void) | undefined;
goRegister: (() => void) | undefined;
isRegisterBack: boolean;
goOauthLogin: ((oauthProviderId: string) => void) | undefined;
}>) => React.ReactElement;
export default _default;

View File

@ -12,79 +12,104 @@ export default OakComponent({
passportTypes: [],
inputOptions: [],
scanOptions: [],
allowSms: false,
allowEmail: false,
oauthOptions: [],
allowMobile: false, //密码登录允许使用手机号
allowEmail: false, //密码登录允许使用邮箱
allowLoginName: false, //密码登录允许使用账号
allowPassword: false,
allowSms: false, //小程序使用
allowWechatMp: false,
setLoginModeMp(value) { this.setLoginMode(value); },
smsDigit: 4,
emailDigit: 4,
smsDigit: 4, //短信验证码位数
emailDigit: 4, //邮箱验证码位数
pwdMode: 'all', //密码明文密文存储模式
allowRegister: false, //开启账号登录且允许注册
},
properties: {
onlyCaptcha: false,
onlyPassword: false,
disabled: '',
redirectUri: '',
url: '',
redirectUri: '', // 微信登录后的redirectUri要指向wechatUser/login去处理
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
goRegister: undefined, //跳转注册
isRegisterBack: false, //从注册页跳回登录时将优先选中账号登录方式
goOauthLogin: undefined //跳转指定第三方授权
},
formData({ features, props }) {
return {};
},
listeners: {
// 'onlyPassword,onlyCaptcha'(prev, next) {
// let loginMode = this.state.loginMode, inputOptions = this.state.inputOptions, scanOptions = this.state.scanOptions;
// if (next.onlyPassword) {
// loginMode = 'password';
// inputOptions = [{
// label: this.t('passport:v.type.password'),
// value: 'password',
// }];
// } else if (next.onlyCaptcha) {
// loginMode = 'sms';
// inputOptions = [{
// label: this.t('passport:v.type.sms'),
// value: 'sms',
// }];
// } else {
// const { passportTypes } = this.state;
// if (passportTypes && passportTypes.length > 0) {
// passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => {
// if (ele === 'sms' || ele === 'email' || ele === 'password') {
// inputOptions.push({
// label: this.t(`passport:v.type.${ele}`),
// value: ele
// })
// } else if (ele === 'wechatMpForWeb' || ele === 'wechatPublicForWeb') {
// scanOptions.push({
// label: this.t(`passport:v.type.${ele}`),
// value: ele
// })
// }
// });
// }
// }
// this.setState({
// loginMode,
// inputOptions,
// scanOptions,
// })
// }
// 'onlyPassword,onlyCaptcha'(prev, next) {
// let loginMode = this.state.loginMode, inputOptions = this.state.inputOptions, scanOptions = this.state.scanOptions;
// if (next.onlyPassword) {
// loginMode = 'password';
// inputOptions = [{
// label: this.t('passport:v.type.password'),
// value: 'password',
// }];
// } else if (next.onlyCaptcha) {
// loginMode = 'sms';
// inputOptions = [{
// label: this.t('passport:v.type.sms'),
// value: 'sms',
// }];
// } else {
// const { passportTypes } = this.state;
// if (passportTypes && passportTypes.length > 0) {
// passportTypes.forEach((ele: EntityDict['passport']['Schema']['type']) => {
// if (ele === 'sms' || ele === 'email' || ele === 'password') {
// inputOptions.push({
// label: this.t(`passport:v.type.${ele}`),
// value: ele
// })
// } else if (ele === 'wechatMpForWeb' || ele === 'wechatPublicForWeb') {
// scanOptions.push({
// label: this.t(`passport:v.type.${ele}`),
// value: ele
// })
// }
// });
// }
// }
// this.setState({
// loginMode,
// inputOptions,
// scanOptions,
// })
// }
isRegisterBack(prev, next) {
if (prev.isRegisterBack !== next.isRegisterBack && next.isRegisterBack) {
const { passportTypes } = this.state;
const { onlyCaptcha } = this.props;
if (passportTypes.includes('loginName') && !onlyCaptcha) {
this.setState({
loginMode: 'password'
});
}
}
}
},
lifetimes: {
async ready() {
const { isRegisterBack } = this.props;
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const defaultPassport = applicationPassports.find((ele) => ele.isDefault);
const passportTypes = applicationPassports.map((ele) => ele.passport.type);
const smsDigit = applicationPassports.find((ele) => ele.passport.type === 'sms')?.passport.config.digit || 4;
const emailDigit = applicationPassports.find((ele) => ele.passport.type === 'email')?.passport.config.digit || 4;
const pwdMode = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config.mode || 'all';
const pwdConfig = application?.system?.config?.Password;
const pwdMode = pwdConfig?.mode || 'all';
const { onlyCaptcha, onlyPassword } = this.props;
const smsAP = applicationPassports.find((ele) => ele.passport.type === 'sms');
const loginNameAP = applicationPassports.find((ele) => ele.passport.type === 'loginName');
const emailAP = applicationPassports.find((ele) => ele.passport.type === 'email');
const showPassword = (smsAP && smsAP?.allowPwd) || (loginNameAP && loginNameAP?.allowPwd) || (emailAP && emailAP?.allowPwd); //(手机号、账号、邮箱登录中)存在至少一种开启密码登录的登录方式且非仅手机验证码登录
let loginMode = (await this.load(LOGIN_MODE)) || defaultPassport?.passport?.type || 'sms';
let inputOptions = [], scanOptions = [];
if (onlyPassword) {
let oauthOptions = [];
if (onlyPassword && showPassword) {
loginMode = 'password';
inputOptions = [{
label: this.t('passport:v.type.password') + this.t('Login'),
@ -99,8 +124,14 @@ export default OakComponent({
}];
}
else {
if (showPassword) {
inputOptions.push({
label: this.t(`passport:v.type.password`) + this.t('Login'),
value: 'password'
});
}
passportTypes.forEach((ele) => {
if (ele === 'sms' || ele === 'email' || ele === 'password') {
if (ele === 'sms' || ele === 'email') {
inputOptions.push({
label: this.t(`passport:v.type.${ele}`) + this.t('Login'),
value: ele
@ -113,8 +144,36 @@ export default OakComponent({
});
}
});
const oauthAp = applicationPassports.find((ele) => ele.passport.type === 'oauth');
const { oauthIds } = oauthAp?.passport?.config || {};
if (oauthIds && oauthIds.length > 0) {
const { data: oauthProviders } = await this.features.cache.refresh('oauthProvider', {
data: {
id: 1,
name: 1,
logo: 1,
},
filter: {
id: {
$in: oauthIds,
}
}
});
if (oauthProviders && oauthProviders?.length > 0) {
oauthOptions = oauthProviders?.map((ele) => {
return {
name: ele.name,
value: ele.id,
logo: ele.logo ?? undefined,
};
});
}
}
}
if (!passportTypes.includes(loginMode)) {
if (isRegisterBack && !onlyCaptcha) {
loginMode = 'password';
}
if ((loginMode !== 'password' && !passportTypes.includes(loginMode)) || (loginMode === 'password' && !showPassword)) {
loginMode = defaultPassport.passport.type;
}
const appType = application?.type;
@ -133,10 +192,13 @@ export default OakComponent({
appId = config2?.wechat?.appId;
domain = config2?.wechat?.domain;
}
const allowSms = passportTypes.includes('sms') && !onlyPassword;
const allowEmail = passportTypes.includes('email') && !onlyCaptcha && !onlyPassword;
const allowPassword = passportTypes.includes('password') && !onlyCaptcha;
const allowMobile = smsAP && smsAP?.allowPwd;
const allowEmail = emailAP && emailAP?.allowPwd;
const allowLoginName = loginNameAP && loginNameAP?.allowPwd;
const allowWechatMp = passportTypes.includes('wechatMp') && !onlyCaptcha && !onlyPassword;
const allowPassword = !onlyCaptcha && showPassword;
const allowSms = passportTypes.includes('sms') && !onlyPassword;
const allowRegister = loginNameAP && loginNameAP?.passport?.config?.register;
this.setState({
loginMode,
appId,
@ -145,10 +207,14 @@ export default OakComponent({
passportTypes,
inputOptions,
scanOptions,
allowSms,
oauthOptions,
allowMobile,
allowEmail,
allowLoginName,
allowPassword,
allowSms,
allowWechatMp,
allowRegister,
smsDigit,
emailDigit,
pwdMode,

View File

@ -13,5 +13,6 @@
"resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
}

View File

@ -4,8 +4,10 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
redirectUri: string;
url: string;
callback: (() => void) | undefined;
allowSms: boolean;
allowMobile: boolean;
allowEmail: boolean;
allowLoginName: boolean;
allowSms: boolean;
allowWechatMp: boolean;
setLoginMode: (value: string) => void;
pwdMode: string;

View File

@ -3,7 +3,21 @@ import { encryptPasswordSha1 } from "../../../../utils/password";
export default OakComponent({
isList: false,
formData({ features, props }) {
return {};
const { allowMobile, allowEmail, allowLoginName } = this.props;
let tips = [];
if (allowLoginName) {
tips.push(this.t('placeholder.LoginName'));
}
if (allowMobile) {
tips.push(this.t('placeholder.Mobile'));
}
if (allowEmail) {
tips.push(this.t('placeholder.Email'));
}
const accountPlaceholder = tips.length > 0 ? this.t('placeholder.Account') + tips.join('/') : this.t('placeholder.Account');
return {
accountPlaceholder,
};
},
data: {
counter: 0,
@ -19,12 +33,14 @@ export default OakComponent({
},
properties: {
disabled: '',
redirectUri: '',
url: '',
callback: undefined,
allowSms: false,
redirectUri: '', // 微信登录后的redirectUri要指向wechatUser/login去处理
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
allowMobile: false,
allowEmail: false,
allowWechatMp: false,
allowLoginName: false,
allowSms: false, //小程序切换手机号验证码登录
allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
pwdMode: 'all', //密码明文密文存储模式
},
@ -86,10 +102,10 @@ export default OakComponent({
}
},
inputChange(type, value) {
const { allowSms, allowEmail } = this.props;
const { allowMobile, allowEmail } = this.props;
switch (type) {
case 'account':
// const validMobile = allowSms && !!isMobile(value);
// const validMobile = allowMobile && !!isMobile(value);
// const vaildEmail = allowEmail && !!isEmail(value);
const vaildAccount = !!(value && value.trim() && value.trim() !== '');
this.setState({

View File

@ -2,7 +2,7 @@
<oak-icon name="mine" size="28" color="#808080" />
<l-input
hide-label="{{true}}"
placeholder="{{t('placeholder.Account')}}"
placeholder="{{accountPlaceholder}}"
clear="{{true}}"
showRow="{{false}}"
l-class="my-input"

View File

@ -1,9 +1,10 @@
{
"Login": "登录",
"placeholder": {
"Account": "请输入账号",
"Mobile": "/手机号",
"Email": "/邮箱",
"Account": "请输入",
"LoginName": "账号",
"Mobile": "手机号",
"Email": "邮箱",
"Password": "请输入密码"
}
}

View File

@ -9,8 +9,7 @@ export default function Render(props: WebComponentProps<EntityDict, 'token', fal
validMobile: boolean;
validPassword: boolean;
allowSubmit: boolean;
allowSms: boolean;
allowEmail: boolean;
accountPlaceholder: string;
}, {
loginByAccount: () => Promise<void>;
inputChange: (type: 'account' | 'password', value: string) => void;

View File

@ -6,13 +6,13 @@ import { LockOutlined, UserOutlined, } from '@ant-design/icons';
import Style from './web.module.less';
export default function Render(props) {
const { data, methods } = props;
const { loading, disabled, account, password, validMobile, validPassword, allowSubmit, allowSms, allowEmail, } = data;
const { loading, disabled, account, password, validMobile, validPassword, allowSubmit, accountPlaceholder } = data;
const { loginByAccount, t, inputChange } = methods;
return (<Form colon={true}>
<Form.Item name="mobile">
<Input allowClear value={account} size="large"
// maxLength={11}
prefix={<UserOutlined />} placeholder={t('placeholder.Account') + (allowSms ? t('placeholder.Mobile') : '') + (allowEmail ? t('placeholder.Email') : '')} onChange={(e) => {
prefix={<UserOutlined />} placeholder={accountPlaceholder} onChange={(e) => {
inputChange('account', e.target.value);
}} className={Style['loginbox-input']}/>
</Form.Item>

View File

@ -21,10 +21,10 @@ export default OakComponent({
},
properties: {
disabled: '',
url: '',
callback: undefined,
allowPassword: false,
allowWechatMp: false,
url: '', // 登录系统之后要返回的页面
callback: undefined, // 登录成功回调,排除微信登录方式
allowPassword: false, //小程序切换密码登录
allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
digit: 4 //验证码位数,
},

View File

@ -21,9 +21,18 @@ export default function Render(props: WebComponentProps<EntityDict, 'token', fal
scanOptions: Option[];
smsDigit: number;
emailDigit: number;
allowSms: boolean;
allowMobile: boolean;
allowEmail: boolean;
allowLoginName: boolean;
pwdMode: 'all' | 'plain' | 'sha1';
allowRegister: boolean;
oauthOptions: {
name: string;
value: string;
logo?: string;
}[];
goRegister: () => void;
goOauthLogin: (oauthProviderId: string) => void;
}, {
setLoginMode: (value: number) => void;
}>): React.JSX.Element;

View File

@ -1,8 +1,8 @@
// @ts-nocheck
// Segmented这个对象在antd里的声明是错误的
import React, { useEffect, useState } from 'react';
import { Segmented, Divider, Space } from 'antd';
import { MobileOutlined, QrcodeOutlined, DesktopOutlined, MailOutlined, ExclamationCircleOutlined, } from '@ant-design/icons';
import { Button, Segmented, Divider, Space, Tooltip, Image } from 'antd';
import { MobileOutlined, QrcodeOutlined, DesktopOutlined, MailOutlined, ExclamationCircleOutlined, LinkOutlined, } from '@ant-design/icons';
import classNames from 'classnames';
import Style from './web.module.less';
import WeChatLoginQrCode from '../../common/weChatLoginQrCode';
@ -13,8 +13,8 @@ import PasswordLogin from './password';
import EmailLogin from './email';
export default function Render(props) {
const { data, methods } = props;
const { width, loading, loginMode, appId, domain, isSupportWechatGrant, disabled, redirectUri, url, passportTypes, callback, inputOptions, scanOptions, smsDigit, emailDigit, allowSms, allowEmail, pwdMode, } = data;
const { t, setLoginMode } = methods;
const { width, loading, loginMode, appId, domain, isSupportWechatGrant, disabled, redirectUri, url, passportTypes, callback, inputOptions, scanOptions, smsDigit, emailDigit, allowMobile, allowEmail, allowLoginName, pwdMode, allowRegister, oauthOptions, goRegister, goOauthLogin, } = data;
const { t, setLoginMode, } = methods;
let redirectUri2 = redirectUri;
if (!(redirectUri.startsWith('https') || redirectUri.startsWith('http'))) {
const hostname = domain || window.location.hostname;
@ -36,10 +36,11 @@ export default function Render(props) {
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) => {
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 />;
@ -53,21 +54,36 @@ export default function Render(props) {
return (<Space key={ele.value} size={4} style={{ cursor: 'pointer' }} onClick={() => {
setLoginMode(ele.value);
}}>
{icon}
<div>{ele.label}</div>
</Space>);
{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={() => {
</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>) : <></>;
<QrcodeOutlined />
<div>{t('scanLogin')}</div>
</Space>
// </div>
) : <></>;
const OauthMethods = oauthOptions && oauthOptions.length > 0 ? (
// <div className={Style['loginbox-methods']}>
// <Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space style={{ cursor: 'pointer' }}>
{oauthOptions?.map((ele) => (<Tooltip title={ele.name}>
<Image preview={false} width={20} height={20} src={ele.logo} onClick={() => {
goOauthLogin(ele.value);
}} placeholder={<LinkOutlined />}/>
</Tooltip>))}
</Space>
// </div>
) : <></>;
const Tip = <div className={Style['loginbox-tip']}>
<Space>
<ExclamationCircleOutlined />
@ -97,8 +113,13 @@ export default function Render(props) {
<div className={Style['loginbox-password']} style={{
display: loginMode === 'password' ? 'block' : 'none',
}}>
<PasswordLogin disabled={disabled} url={url} callback={callback} allowSms={allowSms} allowEmail={allowEmail} pwdMode={pwdMode}/>
{Tip}
<PasswordLogin disabled={disabled} url={url} callback={callback} allowMobile={allowMobile} allowEmail={allowEmail} allowLoginName={allowLoginName} pwdMode={pwdMode}/>
{allowRegister && (<div className={Style['loginbox-register']}>
{/* <Button type='link' iconPosition='end' icon={<RightOutlined />}>去注册</Button> */}
<Button type='link' onClick={() => {
goRegister && goRegister();
}}>{`${t('goRegister')} >`}</Button>
</div>)}
</div>
<div className={Style['loginbox-mobile']} style={{
display: loginMode === 'sms' ? 'block' : 'none',
@ -112,7 +133,13 @@ export default function Render(props) {
<EmailLogin disabled={disabled} url={url} callback={callback} digit={emailDigit}/>
{Tip}
</div>
{ScanMethods}
{(scanOptions?.length > 0 || oauthOptions?.length > 0) ? (<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space split={<Divider type="vertical"/>}>
{ScanMethods}
{OauthMethods}
</Space>
</div>) : (<></>)}
</>) : (<>
{loginMode === 'wechatWeb' &&
<div className={Style['loginbox-qrcode']}>
@ -123,7 +150,13 @@ export default function Render(props) {
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state} size={180}/>
{Tip}
</div>}
{InputMethods}
{(inputOptions?.length > 0 || oauthOptions?.length > 0) ? (<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space split={<Divider type="vertical"/>}>
{InputMethods}
{OauthMethods}
</Space>
</div>) : (<></>)}
</>)}
</>)}
</div>

View File

@ -41,25 +41,27 @@
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-password {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-email {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-qrcode {
padding: 0 32px;
padding: 16px 32px;
font-size: 14px;
height: 268px;
padding-top: 16px;
&__sociallogin {
text-align: center;
@ -126,4 +128,11 @@
font-size: 13px;
color: #808080;
}
&-register {
display: flex;
align-items: center;
justify-content: flex-end;
}
}

View File

@ -0,0 +1,6 @@
import { EntityDict } from "../../../oak-app-domain";
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, false, {
goLogin: (() => void) | undefined;
goBack: (() => void) | undefined;
}>) => React.ReactElement;
export default _default;

View File

@ -0,0 +1,88 @@
import { OakPreConditionUnsetException } from "oak-domain/lib/types";
import { encryptPasswordSha1 } from "../../../utils/password";
export default OakComponent({
isList: false,
data: {
allowRegister: false,
loginNameMin: 2,
loginNameMax: 8,
loginNameNeedVerify: false,
loginNameRegexs: [],
loginNameTip: '',
mode: 'all',
pwdMin: 8,
pwdMax: 24,
pwdNeedVerify: false,
pwdRegexs: [],
pwdTip: '',
},
properties: {
goLogin: undefined,
goBack: undefined,
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const loginNameAP = applicationPassports.find((ele) => ele.passport.type === 'loginName');
const loginNameConfig = loginNameAP?.passport?.config;
const loginNameMin = loginNameConfig?.min ?? 2;
const loginNameMax = loginNameConfig?.max ?? 8;
const loginNameNeedVerify = loginNameConfig?.verify;
const loginNameRegexs = (loginNameConfig?.regexs && loginNameConfig?.regexs.length > 0) ? loginNameConfig?.regexs : [];
const loginNameTip = loginNameConfig?.tip;
const allowRegister = !!loginNameConfig?.register;
const pwdConfig = application?.system?.config?.Password;
const mode = pwdConfig?.mode ?? 'all';
const pwdMin = pwdConfig?.min ?? 8;
const pwdMax = pwdConfig?.max ?? 24;
const pwdNeedVerify = !!pwdConfig?.verify;
const pwdRegexs = (pwdConfig?.regexs && pwdConfig?.regexs.length > 0) ? pwdConfig?.regexs : [];
const pwdTip = pwdConfig?.tip ?? '';
this.setState({
allowRegister,
loginNameMin,
loginNameMax,
loginNameNeedVerify,
loginNameRegexs,
loginNameTip,
mode,
pwdMin,
pwdMax,
pwdNeedVerify,
pwdRegexs,
pwdTip,
}, () => this.reRender());
},
},
methods: {
async onConfirm(loginName, password) {
const { mode } = this.state;
let pwd = password;
if (mode === 'sha1') {
pwd = encryptPasswordSha1(password);
}
try {
await this.features.cache.exec('registerUserByLoginName', {
loginName,
password: pwd
});
this.setMessage({
type: 'success',
content: '注册成功,请前往登录页登录'
});
}
catch (err) {
if (err instanceof OakPreConditionUnsetException) {
this.setMessage({
type: 'error',
content: err.message
});
}
else {
throw err;
}
}
}
},
});

View File

@ -0,0 +1,7 @@
{
"navigationBarTitleText": "账号注册",
"enablePullDownRefresh": false,
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index"
}
}

View File

@ -0,0 +1,28 @@
/** index.wxss **/
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.page-body {
height: 100%;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
background-color: @oak-bg-color-container;
.safe-area-inset-bottom();
}
.login-box {
padding: 36rpx;
min-width: 78vw;
}
.login-body {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}

View File

@ -0,0 +1,4 @@
<view class="page-body">
<view class="login-box">
</view>
</view>

View File

@ -0,0 +1,26 @@
{
"not allow register": "暂未支持自行注册,请联系管理员为您分配账号!",
"registerTitle": "账号注册",
"label": {
"loginName": "账号",
"password": "密码",
"rePwd": "密码确认"
},
"placeholder": {
"loginName": "请输入账号",
"password": "请输入密码",
"rePwd": "请再次输入密码"
},
"validator": {
"loginNameMin": "账号最短长度为%{loginNameMin}位",
"loginNameMax": "账号最大长度为%{loginNameMax}位",
"loginNameVerify": "当前账号未符合规范",
"pwdMin": "密码最短长度为%{pwdMin}位",
"pwdMax": "密码最短长度为%{pwdMax}位",
"pwdVerify": "当前密码较弱",
"pwdDiff": "两次输入的密码不一致,请检查",
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
}

21
es/components/user/register/web.d.ts vendored Normal file
View File

@ -0,0 +1,21 @@
import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'token', false, {
width: string;
allowRegister: boolean;
loginNameMin: number;
loginNameMax: number;
loginNameNeedVerify: boolean;
loginNameRegexs: string[];
loginNameTip: string;
pwdMin: number;
pwdMax: number;
pwdNeedVerify: boolean;
pwdRegexs: string[];
pwdTip: string;
goLogin: () => void;
goBack: () => void;
}, {
onConfirm: (loginName: string, password: string) => Promise<void>;
}>): React.JSX.Element;

View File

@ -0,0 +1,173 @@
import React, { useState } from 'react';
import { Form, Input, Button, } from 'antd';
import { EyeTwoTone, EyeInvisibleOutlined, LeftOutlined, } from '@ant-design/icons';
import classNames from 'classnames';
import Style from './web.module.less';
export default function Render(props) {
const { data, methods } = props;
const { width, allowRegister, loginNameMin, loginNameMax, loginNameNeedVerify, loginNameRegexs, loginNameTip, pwdMin, pwdMax, pwdNeedVerify, pwdRegexs, pwdTip, goLogin, goBack, oakExecuting, oakLoading, } = data;
const { t, onConfirm } = methods;
const [loginName, setLoginName] = useState('');
const [loginNameHelp, setLoginNameHelp] = useState('');
const [loginNameStatus, setLoginNameStatus] = useState('');
const [password, setPassword] = useState('');
const [password2, setPassword2] = useState('');
const [validateHelp, setValidateHelp] = useState('');
const [validateHelp2, setValidateHelp2] = useState('');
const [validateStatus, setValidateStatus] = useState('');
if (!allowRegister) {
return (<div className={classNames(Style['registerbox-wrap'], {
[Style['registerbox-wrap__mobile']]: width === 'xs',
})}>
<div style={{ minHeight: 200, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{t('not allow register')}
</div>
</div>);
}
return (<div className={classNames(Style['registerbox-wrap'], {
[Style['registerbox-wrap__mobile']]: width === 'xs',
})}>
{!!goBack && <LeftOutlined style={{ position: 'absolute', top: 40, left: 32, color: '#555555' }} onClick={() => goBack()}/>}
<div className={Style['registerbox-hd']}>
<div>{t('registerTitle')}</div>
</div>
<div className={Style['registerbox-bd']}>
<Form style={{ maxWidth: 400 }} layout="vertical">
<Form.Item label={t('label.loginName')} name="loginName" tooltip={loginNameTip} help={loginNameHelp} hasFeedback validateStatus={loginNameStatus} rules={[
{
required: true,
message: t('placeholder.loginName'),
validator: (_, value) => {
if (value.length < loginNameMin) {
setLoginNameHelp(t('validator.loginNameMin', { loginNameMin }));
setLoginNameStatus('error');
return;
}
else if (value.length > loginNameMax) {
setLoginNameHelp(t('validator.loginNameMax', { loginNameMax }));
setLoginNameStatus('error');
return;
}
else if (!!loginNameNeedVerify && loginNameRegexs && loginNameRegexs.length > 0) {
for (const regex of loginNameRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
setLoginNameHelp(t('validator.loginNameVerify'));
setLoginNameStatus('error');
return;
}
}
}
setLoginNameHelp('');
setLoginNameStatus('success');
}
},
]}>
<Input autoFocus onChange={({ currentTarget }) => setLoginName(currentTarget.value)} placeholder={t('placeholder.loginName')} value={loginName}/>
</Form.Item>
<Form.Item label={t('label.password')} name="password" help={validateHelp} tooltip={pwdTip} rules={[
{
required: true,
message: t('placeholder.password'),
validator: (_, value) => {
if (value.length < pwdMin) {
setValidateHelp(t('validator.pwdMin', { pwdMin }));
setValidateStatus('error');
return;
}
else if (value.length > pwdMax) {
setValidateHelp(t('validator.pwdMax', { pwdMax }));
setValidateStatus('error');
return;
}
else if (!!pwdNeedVerify && pwdRegexs && pwdRegexs.length > 0) {
for (const regex of pwdRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
setValidateHelp(t('validator.pwdVerify'));
setValidateHelp2('');
setValidateStatus('error');
return;
}
}
if (password2) {
setValidateHelp('');
setValidateHelp2(value === password2
? ''
: t('validator.pwdDiff'));
setValidateStatus(value === password2
? 'success'
: 'error');
}
else {
setValidateHelp2(t('noRepwd'));
setValidateHelp('');
setValidateStatus('error');
}
}
else {
if (password2) {
setValidateHelp('');
setValidateHelp2(value === password2
? ''
: t('validator.pwdDiff'));
setValidateStatus(value === password2
? 'success'
: 'error');
}
else {
setValidateHelp2(t('noRepwd'));
setValidateHelp('');
setValidateStatus('error');
}
}
}
},
]} hasFeedback validateStatus={validateStatus}>
<Input.Password value={password} onChange={(e) => {
const strValue = e.target.value;
setPassword(strValue);
}} iconRender={(visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} placeholder={t('placeholder.password')}/>
</Form.Item>
<Form.Item label={t('label.rePwd')} name="passwordConfirm" rules={[
{
required: true,
validator: (_, value) => {
if (password.length < pwdMin || password.length > pwdMax) {
return;
}
else if (!!pwdNeedVerify && pwdRegexs && pwdRegexs.length > 0) {
for (const regex of pwdRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(password)) {
return;
}
}
}
setValidateHelp2(value === password ? '' : t('validator.pwdDiff'));
setValidateStatus(value === password ? 'success' : 'error');
}
},
]} validateTrigger="onChange" help={validateHelp2} validateStatus={validateStatus} hasFeedback>
<Input.Password value={password2} onChange={(e) => {
const strValue = e.target.value;
setPassword2(strValue);
}} iconRender={(visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} placeholder={t('placeholder.rePwd')}/>
</Form.Item>
<Button block size="large" type="primary" onClick={async () => {
await onConfirm(loginName, password);
}} disabled={!(loginName && loginNameStatus !== 'error' && password && validateStatus !== 'error') || oakExecuting || oakLoading}>
{t('register')}
</Button>
</Form>
{!!goLogin && (<div className={Style['registerbox-login']}>
<Button type="link" onClick={() => {
goLogin && goLogin();
}}>
{t('goLogin')}
</Button>
</div>)}
</div>
</div>);
}

View File

@ -0,0 +1,145 @@
.registerbox {
&-main {
height: 100%;
display: flex;
flex: 1;
align-items: center;
flex-direction: column;
justify-content: center;
background: var(--oak-bg-color-container);
}
&-logo {
width: 194px;
margin-bottom: 20px;
}
&-wrap {
width: 400px;
display: block;
background: var(--oak-bg-color-container);
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 4px rgb(0 0 0 / 8%), 0 0 4px rgb(0 0 0 / 8%);
transition: all 0.5s;
position: relative;
}
&-hd {
padding: 32px;
padding-bottom: 0px;
font-size: 20px;
font-weight: 600;
text-align: center;
}
&-bd {
padding: 32px;
padding-top: 24px;
}
&-only {
padding-top: 32px !important;
}
&-mobile {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-password {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-email {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-qrcode {
padding: 16px 32px;
font-size: 14px;
height: 268px;
&__sociallogin {
text-align: center;
color: #999;
}
&__refresh {
color: var(--oak-text-color-brand);
margin-left: 10px;
cursor: pointer;
&-icon {
color: var(--oak-text-color-brand);
font-size: 14px;
margin-left: 4px;
}
}
&__iframe {
position: relative;
width: 300px;
margin: 0 auto;
}
}
&-input {
// background-color: rgba(0, 0, 0, .04) !important;
}
&-ft {
height: 54px;
border-top: 1px solid #f2f3f5;
font-size: 14px;
&__btn {}
}
&-protocal {
padding: 20px 32px;
}
&-current {
color: var(--oak-text-color-brand) !important;
cursor: default;
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;
}
&-login {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding-top: 18px;
}
}

View File

@ -685,7 +685,8 @@ const i18ns = [
"resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
}
},
{
@ -780,6 +781,39 @@ const i18ns = [
}
}
},
{
id: "36c643dbcc19c3258f6077a6684236ff",
namespace: "oak-general-business-c-user-register",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/user/register",
data: {
"not allow register": "暂未支持自行注册,请联系管理员为您分配账号!",
"registerTitle": "账号注册",
"label": {
"loginName": "账号",
"password": "密码",
"rePwd": "密码确认"
},
"placeholder": {
"loginName": "请输入账号",
"password": "请输入密码",
"rePwd": "请再次输入密码"
},
"validator": {
"loginNameMin": "账号最短长度为%{loginNameMin}位",
"loginNameMax": "账号最大长度为%{loginNameMax}位",
"loginNameVerify": "当前账号未符合规范",
"pwdMin": "密码最短长度为%{pwdMin}位",
"pwdMax": "密码最短长度为%{pwdMax}位",
"pwdVerify": "当前密码较弱",
"pwdDiff": "两次输入的密码不一致,请检查",
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
}
},
{
id: "5bf96a3e054b8d73c76d7bb45ea90a80",
namespace: "oak-general-business-c-userEntityGrant-claim",

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -21,6 +21,7 @@ import oauthProviderTriggers from './oauthProvider';
import oauthUserTriggers from './oauthUser';
import oauthUserAuthTriggers from './oauthUserAuth';
import mobileTriggers from './mobile';
import applicationPassportTriggers from './applicationPassport';
// import accountTriggers from './account';
export default [
// ...accountTriggers,
@ -47,4 +48,5 @@ export default [
...oauthUserTriggers,
...oauthUserAuthTriggers,
...mobileTriggers,
...applicationPassportTriggers,
];

View File

@ -735,5 +735,16 @@ export type AspectDict<ED extends EntityDict> = {
setUserAvatarFromWechat: (params: {
avatar: string;
}, context: BackendRuntimeContext<ED>) => Promise<void>;
/**
*
* @param loginName
* @param password
* @param context
* @returns
*/
registerUserByLoginName: (params: {
loginName: string;
password: string;
}, context: BackendRuntimeContext<ED>) => Promise<void>;
};
export default AspectDict;

View File

@ -4,7 +4,7 @@ import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, get
import { updateConfig, updateApplicationConfig, updateStyle } from './config';
import { syncMessageTemplate, getMessageType } from './template';
import { syncSmsTemplate } from './sms';
import { mergeUser, getChangePasswordChannels, updateUserPassword } from './user';
import { mergeUser, getChangePasswordChannels, updateUserPassword, registerUserByLoginName } from './user';
import { createWechatLogin } from './wechatLogin';
import { unbindingWechat } from './wechatUser';
import { getMpUnlimitWxaCode } from './wechatQrCode';
@ -86,6 +86,7 @@ declare const aspectDict: {
createOAuthState: typeof createOAuthState;
authorize: typeof authorize;
setUserAvatarFromWechat: typeof setUserAvatarFromWechat;
registerUserByLoginName: typeof registerUserByLoginName;
};
export default aspectDict;
export { AspectDict } from './AspectDict';

View File

@ -89,5 +89,6 @@ const aspectDict = {
createOAuthState: oauth_1.createOAuthState,
authorize: oauth_1.authorize,
setUserAvatarFromWechat: token_1.setUserAvatarFromWechat,
registerUserByLoginName: user_1.registerUserByLoginName,
};
exports.default = aspectDict;

View File

@ -23,3 +23,12 @@ export declare function updateUserPassword<ED extends EntityDict>(params: {
result: string;
times?: undefined;
}>;
/**
*
* @param params
* @param context
*/
export declare function registerUserByLoginName<ED extends EntityDict>(params: {
loginName: string;
password: string;
}, context: BRC<ED>): Promise<void>;

View File

@ -1,6 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateUserPassword = exports.getChangePasswordChannels = exports.mergeUser = void 0;
exports.mergeUser = mergeUser;
exports.getChangePasswordChannels = getChangePasswordChannels;
exports.updateUserPassword = updateUserPassword;
exports.registerUserByLoginName = registerUserByLoginName;
const tslib_1 = require("tslib");
const types_1 = require("oak-domain/lib/types");
const uuid_1 = require("oak-domain/lib/utils/uuid");
@ -140,7 +143,6 @@ async function mergeUser(params, context, innerLogic) {
}, { dontCollect: true });
}
}
exports.mergeUser = mergeUser;
async function getChangePasswordChannels(params, context, innerLogic) {
const { userId } = params;
const mobileList = await context.select('mobile', {
@ -177,26 +179,22 @@ async function getChangePasswordChannels(params, context, innerLogic) {
}
return result;
}
exports.getChangePasswordChannels = getChangePasswordChannels;
async function updateUserPassword(params, context, innerLogic) {
const { userId, prevPassword, captcha, mobile, newPassword } = params;
const systemId = context.getSystemId();
const closeRootMode = context.openRootMode();
try {
const [passport] = await context.select('passport', {
const [system] = await context.select('system', {
data: {
id: 1,
type: 1,
config: 1,
systemId: 1,
},
filter: {
systemId,
type: 'password',
id: systemId,
}
}, { forUpdate: true });
(0, assert_1.assert)(passport);
const config = passport.config;
(0, assert_1.assert)(system);
const config = system.config?.Password;
const mode = config?.mode ?? 'all';
const [user] = await context.select('user', {
data: {
@ -263,13 +261,13 @@ async function updateUserPassword(params, context, innerLogic) {
};
}
const allowUpdate = mode === 'sha1' ? user.passwordSha1 === prevPassword : user.password === prevPassword; //sha1密文模式判断密文是否相等
let userDate = {}, changeCreateDate = {};
let userData = {}, changeCreateData = {};
if (mode === 'all') {
userDate = {
userData = {
password: newPassword,
passwordSha1: (0, password_1.encryptPasswordSha1)(newPassword),
};
changeCreateDate = {
changeCreateData = {
prevPassword,
newPassword,
prevPasswordSha1: (0, password_1.encryptPasswordSha1)(prevPassword),
@ -277,19 +275,19 @@ async function updateUserPassword(params, context, innerLogic) {
};
}
else if (mode === 'plain') {
userDate = {
userData = {
password: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPassword,
newPassword,
};
}
else if (mode === 'sha1') {
userDate = {
userData = {
passwordSha1: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPasswordSha1: prevPassword,
newPasswordSha1: newPassword,
};
@ -298,7 +296,7 @@ async function updateUserPassword(params, context, innerLogic) {
await context.operate('user', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: userDate,
data: userData,
filter: {
id: userId,
},
@ -312,7 +310,7 @@ async function updateUserPassword(params, context, innerLogic) {
id: await (0, uuid_1.generateNewIdAsync)(),
userId,
result: 'success',
...changeCreateDate,
...changeCreateData,
},
}, {
dontCollect: true,
@ -330,7 +328,7 @@ async function updateUserPassword(params, context, innerLogic) {
id: await (0, uuid_1.generateNewIdAsync)(),
userId,
result: 'fail',
...changeCreateDate,
...changeCreateData,
},
}, {
dontCollect: true,
@ -359,13 +357,13 @@ async function updateUserPassword(params, context, innerLogic) {
dontCollect: true,
});
if (aliveCaptcha) {
let userDate = {}, changeCreateDate = {};
let userData = {}, changeCreateData = {};
if (mode === 'all') {
userDate = {
userData = {
password: newPassword,
passwordSha1: (0, password_1.encryptPasswordSha1)(newPassword),
};
changeCreateDate = {
changeCreateData = {
prevPassword: user.password,
newPassword,
prevPasswordSha1: user.passwordSha1,
@ -373,19 +371,19 @@ async function updateUserPassword(params, context, innerLogic) {
};
}
else if (mode === 'plain') {
userDate = {
userData = {
password: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPassword: user.password,
newPassword,
};
}
else if (mode === 'sha1') {
userDate = {
userData = {
passwordSha1: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPasswordSha1: user.passwordSha1,
newPasswordSha1: newPassword,
};
@ -393,7 +391,7 @@ async function updateUserPassword(params, context, innerLogic) {
await context.operate('user', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: userDate,
data: userData,
filter: {
id: userId,
},
@ -407,7 +405,7 @@ async function updateUserPassword(params, context, innerLogic) {
id: await (0, uuid_1.generateNewIdAsync)(),
userId,
result: 'success',
...changeCreateDate,
...changeCreateData,
},
}, {
dontCollect: true,
@ -434,4 +432,83 @@ async function updateUserPassword(params, context, innerLogic) {
throw err;
}
}
exports.updateUserPassword = updateUserPassword;
/**
* 用户账号注册
* @param params
* @param context
*/
async function registerUserByLoginName(params, context) {
const { loginName, password } = params;
const systemId = context.getSystemId();
const closeRootMode = context.openRootMode();
try {
// 检查loginName是否重复
const [existLoginName] = await context.select('loginName', {
data: {
id: 1,
name: 1,
},
filter: {
name: loginName,
ableState: 'enabled',
},
}, { dontCollect: true, forUpdate: true });
if (existLoginName) {
closeRootMode();
throw new types_1.OakPreConditionUnsetException('账号已存在,请重新设置');
}
// 创建user并附上密码级联创建loginName
const [system] = await context.select('system', {
data: {
id: 1,
config: 1,
},
filter: {
id: systemId,
}
}, { forUpdate: true });
(0, assert_1.assert)(system);
const config = system.config?.Password;
const mode = config?.mode ?? 'all';
let passwordData = {};
if (mode === 'all') {
passwordData = {
password: password,
passwordSha1: (0, password_1.encryptPasswordSha1)(password),
};
}
else if (mode === 'plain') {
passwordData = {
password: password,
};
}
else if (mode === 'sha1') {
passwordData = {
passwordSha1: password,
};
}
const userData = {
id: await (0, uuid_1.generateNewIdAsync)(),
loginName$user: [
{
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id: await (0, uuid_1.generateNewIdAsync)(),
name: loginName,
}
}
]
};
Object.assign(userData, passwordData);
await context.operate('user', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: userData,
}, {});
}
catch (err) {
closeRootMode();
throw err;
}
}

View File

@ -687,7 +687,8 @@ const i18ns = [
"resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
}
},
{
@ -782,6 +783,39 @@ const i18ns = [
}
}
},
{
id: "36c643dbcc19c3258f6077a6684236ff",
namespace: "oak-general-business-c-user-register",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/user/register",
data: {
"not allow register": "暂未支持自行注册,请联系管理员为您分配账号!",
"registerTitle": "账号注册",
"label": {
"loginName": "账号",
"password": "密码",
"rePwd": "密码确认"
},
"placeholder": {
"loginName": "请输入账号",
"password": "请输入密码",
"rePwd": "请再次输入密码"
},
"validator": {
"loginNameMin": "账号最短长度为%{loginNameMin}位",
"loginNameMax": "账号最大长度为%{loginNameMax}位",
"loginNameVerify": "当前账号未符合规范",
"pwdMin": "密码最短长度为%{pwdMin}位",
"pwdMax": "密码最短长度为%{pwdMax}位",
"pwdVerify": "当前密码较弱",
"pwdDiff": "两次输入的密码不一致,请检查",
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
}
},
{
id: "5bf96a3e054b8d73c76d7bb45ea90a80",
namespace: "oak-general-business-c-userEntityGrant-claim",

View File

@ -1,2 +1,2 @@
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
declare const _default: (import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "message", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "address", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "application", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "article", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "articleMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "extraFile", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "user", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "notification", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatLogin", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "parasite", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "sessionMessage", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMenu", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "wechatMpJump", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "system", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "passport", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthApplication", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthProvider", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUser", import("..").BRC<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "oauthUserAuthorization", import("../context/BackendRuntimeContext").BackendRuntimeContext<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Trigger<import("../oak-app-domain").EntityDict, "mobile", import("..").BRC<import("../oak-app-domain").EntityDict>>)[];
export default _default;

View File

@ -24,6 +24,7 @@ const oauthProvider_1 = tslib_1.__importDefault(require("./oauthProvider"));
const oauthUser_1 = tslib_1.__importDefault(require("./oauthUser"));
const oauthUserAuth_1 = tslib_1.__importDefault(require("./oauthUserAuth"));
const mobile_1 = tslib_1.__importDefault(require("./mobile"));
const applicationPassport_1 = tslib_1.__importDefault(require("./applicationPassport"));
// import accountTriggers from './account';
exports.default = [
// ...accountTriggers,
@ -50,4 +51,5 @@ exports.default = [
...oauthUser_1.default,
...oauthUserAuth_1.default,
...mobile_1.default,
...applicationPassport_1.default,
];

View File

@ -1008,6 +1008,20 @@ export type AspectDict<ED extends EntityDict> = {
},
context: BackendRuntimeContext<ED>
) => Promise<void>;
/**
*
* @param loginName
* @param password
* @param context
* @returns
*/
registerUserByLoginName: (
params: {
loginName: string,
password: string,
},
context: BackendRuntimeContext<ED>
) => Promise<void>;
};
export default AspectDict;

View File

@ -35,7 +35,7 @@ import {
import { updateConfig, updateApplicationConfig, updateStyle } from './config';
import { syncMessageTemplate, getMessageType } from './template';
import { syncSmsTemplate } from './sms';
import { mergeUser, getChangePasswordChannels, updateUserPassword } from './user';
import { mergeUser, getChangePasswordChannels, updateUserPassword, registerUserByLoginName } from './user';
import { createWechatLogin } from './wechatLogin';
import { unbindingWechat } from './wechatUser';
import { getMpUnlimitWxaCode } from './wechatQrCode';
@ -144,6 +144,7 @@ const aspectDict = {
createOAuthState,
authorize,
setUserAvatarFromWechat,
registerUserByLoginName,
};
export default aspectDict;

View File

@ -1,4 +1,4 @@
import { OakOperationUnpermittedException } from "oak-domain/lib/types";
import { OakOperationUnpermittedException, OakPreConditionUnsetException } from "oak-domain/lib/types";
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
import { BackendRuntimeContext } from "../context/BackendRuntimeContext";
import { EntityDict } from "../oak-app-domain";
@ -6,7 +6,6 @@ import { encryptPasswordSha1 } from '../utils/password';
import { assert } from 'oak-domain/lib/utils/assert';
import dayjs from 'dayjs';
import { BRC } from "../types/RuntimeCxt";
import { PwdConfig } from "../entities/Passport";
export async function mergeUser<ED extends EntityDict>(
params: {
@ -204,22 +203,19 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
const systemId = context.getSystemId();
const closeRootMode = context.openRootMode();
try {
const [passport] = await context.select('passport',
const [system] = await context.select('system',
{
data: {
id: 1,
type: 1,
config: 1,
systemId: 1,
},
filter: {
systemId,
type: 'password',
id: systemId,
}
}, { forUpdate: true }
);
assert(passport);
const config = passport.config as PwdConfig;
assert(system);
const config = system.config?.Password;
const mode = config?.mode ?? 'all';
const [user] = await context.select(
'user',
@ -299,31 +295,31 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
}
const allowUpdate = mode === 'sha1' ? user.passwordSha1 === prevPassword : user.password === prevPassword; //sha1密文模式判断密文是否相等
let userDate = {}, changeCreateDate = {};
let userData = {}, changeCreateData = {};
if (mode === 'all') {
userDate = {
userData = {
password: newPassword,
passwordSha1: encryptPasswordSha1(newPassword),
};
changeCreateDate = {
changeCreateData = {
prevPassword,
newPassword,
prevPasswordSha1: encryptPasswordSha1(prevPassword),
newPasswordSha1: encryptPasswordSha1(newPassword),
};
} else if (mode === 'plain') {
userDate = {
userData = {
password: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPassword,
newPassword,
};
} else if (mode === 'sha1') {
userDate = {
userData = {
passwordSha1: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPasswordSha1: prevPassword,
newPasswordSha1: newPassword,
};
@ -335,7 +331,7 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
{
id: await generateNewIdAsync(),
action: 'update',
data: userDate,
data: userData,
filter: {
id: userId,
},
@ -353,7 +349,7 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
id: await generateNewIdAsync(),
userId,
result: 'success',
...changeCreateDate,
...changeCreateData,
},
},
{
@ -374,7 +370,7 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
id: await generateNewIdAsync(),
userId,
result: 'fail',
...changeCreateDate,
...changeCreateData,
},
},
{
@ -409,31 +405,31 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
}
);
if (aliveCaptcha) {
let userDate = {}, changeCreateDate = {};
let userData = {}, changeCreateData = {};
if (mode === 'all') {
userDate = {
userData = {
password: newPassword,
passwordSha1: encryptPasswordSha1(newPassword),
};
changeCreateDate = {
changeCreateData = {
prevPassword: user.password,
newPassword,
prevPasswordSha1: user.passwordSha1,
newPasswordSha1: encryptPasswordSha1(newPassword),
};
} else if (mode === 'plain') {
userDate = {
userData = {
password: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPassword: user.password,
newPassword,
};
} else if (mode === 'sha1') {
userDate = {
userData = {
passwordSha1: newPassword,
};
changeCreateDate = {
changeCreateData = {
prevPasswordSha1: user.passwordSha1,
newPasswordSha1: newPassword,
};
@ -443,7 +439,7 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
{
id: await generateNewIdAsync(),
action: 'update',
data: userDate,
data: userData,
filter: {
id: userId,
},
@ -461,7 +457,7 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
id: await generateNewIdAsync(),
userId,
result: 'success',
...changeCreateDate,
...changeCreateData,
},
},
{
@ -488,3 +484,92 @@ export async function updateUserPassword<ED extends EntityDict>(params: { userId
throw err;
}
}
/**
*
* @param params
* @param context
*/
export async function registerUserByLoginName<ED extends EntityDict>(
params: {
loginName: string,
password: string,
}, context: BRC<ED>,
) {
const { loginName, password } = params;
const systemId = context.getSystemId();
const closeRootMode = context.openRootMode();
try {
// 检查loginName是否重复
const [existLoginName] = await context.select(
'loginName',
{
data: {
id: 1,
name: 1,
},
filter: {
name: loginName!,
ableState: 'enabled',
},
},
{ dontCollect: true, forUpdate: true }
);
if (existLoginName) {
closeRootMode();
throw new OakPreConditionUnsetException('账号已存在,请重新设置');
}
// 创建user并附上密码级联创建loginName
const [system] = await context.select('system',
{
data: {
id: 1,
config: 1,
},
filter: {
id: systemId,
}
}, { forUpdate: true }
);
assert(system);
const config = system.config?.Password;
const mode = config?.mode ?? 'all';
let passwordData = {};
if (mode === 'all') {
passwordData = {
password: password,
passwordSha1: encryptPasswordSha1(password),
};
} else if (mode === 'plain') {
passwordData = {
password: password,
};
} else if (mode === 'sha1') {
passwordData = {
passwordSha1: password,
};
}
const userData: EntityDict['user']['CreateSingle']['data'] = {
id: await generateNewIdAsync(),
loginName$user: [
{
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
name: loginName,
}
}
]
}
Object.assign(userData, passwordData)
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'create',
data: userData,
}, {});
} catch (err) {
closeRootMode();
throw err;
}
}

View File

@ -1,7 +1,7 @@
import { WebConfig, WechatPublicConfig, AppType } from "../../../entities/Application";
import { LOCAL_STORAGE_KEYS } from '../../../config/constants';
import { EntityDict } from "../../../oak-app-domain";
import { EntityDict, } from "../../../oak-app-domain";
const LOGIN_MODE = LOCAL_STORAGE_KEYS.loginMode;
type Option = {
@ -46,6 +46,8 @@ export default OakComponent({
url: '', // 登录系统之后要返回的页面
callback: undefined as (() => void) | undefined, // 登录成功回调,排除微信登录方式
goRegister: undefined as (() => void) | undefined, //跳转注册
isRegisterBack: false, //从注册页跳回登录时将优先选中账号登录方式
goOauthLogin: undefined as ((oauthProviderId: string) => void) | undefined //跳转指定第三方授权
},
formData({ features, props }) {
return {};
@ -89,9 +91,21 @@ export default OakComponent({
// scanOptions,
// })
// }
isRegisterBack(prev, next) {
if (prev.isRegisterBack !== next.isRegisterBack && next.isRegisterBack) {
const { passportTypes } = this.state;
const { onlyCaptcha } = this.props;
if (passportTypes.includes('loginName') && !onlyCaptcha) {
this.setState({
loginMode: 'password'
})
}
}
}
},
lifetimes: {
async ready() {
const { isRegisterBack } = this.props;
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const defaultPassport = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.isDefault);
@ -148,7 +162,7 @@ export default OakComponent({
}
});
const oauthAp = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'oauth');
const { oauthIds } = oauthAp?.config || {};
const { oauthIds } = oauthAp?.passport?.config || {};
if (oauthIds && oauthIds.length > 0) {
const { data: oauthProviders } = await this.features.cache.refresh('oauthProvider', {
data: {
@ -173,8 +187,10 @@ export default OakComponent({
}
}
}
if (!passportTypes.includes(loginMode)) {
if (isRegisterBack && !onlyCaptcha) {
loginMode = 'password'
}
if ((loginMode !== 'password' && !passportTypes.includes(loginMode)) || (loginMode === 'password' && !showPassword)) {
loginMode = defaultPassport.passport.type;
}

View File

@ -13,5 +13,6 @@
"resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
}

View File

@ -41,25 +41,27 @@
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-password {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-email {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-qrcode {
padding: 0 32px;
padding: 16px 32px;
font-size: 14px;
height: 268px;
padding-top: 16px;
&__sociallogin {
text-align: center;

View File

@ -6,7 +6,7 @@ import {
isPassword,
isCaptcha,
} from 'oak-domain/lib/utils/validator';
import { Form, Input, Button, Checkbox, Typography, Segmented, Divider, Space } from 'antd';
import { Form, Input, Button, Checkbox, Typography, Segmented, Divider, Space, Tooltip, Image } from 'antd';
import {
LockOutlined,
MobileOutlined,
@ -15,6 +15,7 @@ import {
MailOutlined,
ExclamationCircleOutlined,
RightOutlined,
LinkOutlined,
} from '@ant-design/icons';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
@ -59,10 +60,16 @@ export default function Render(
allowLoginName: boolean;
pwdMode: 'all' | 'plain' | 'sha1';
allowRegister: boolean;
oauthOptions: {
name: string,
value: string,
logo?: string,
}[];
goRegister: () => void;
goOauthLogin: (oauthProviderId: string) => void;
},
{
setLoginMode: (value: number) => void;
goRegister: () => void;
}
>
) {
@ -88,8 +95,11 @@ export default function Render(
allowLoginName,
pwdMode,
allowRegister,
oauthOptions,
goRegister,
goOauthLogin,
} = data;
const { t, setLoginMode, goRegister } = methods;
const { t, setLoginMode, } = methods;
let redirectUri2 = redirectUri;
if (!(redirectUri.startsWith('https') || redirectUri.startsWith('http'))) {
@ -117,49 +127,73 @@ export default function Render(
}, [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
key={ele.value}
size={4}
style={{ cursor: 'pointer' }}
onClick={() => {
setLoginMode(ele.value)
}}
>
{icon}
<div>{ele.label}</div>
</Space>
)
})}
</div>
// <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
key={ele.value}
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>
// <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 OauthMethods = oauthOptions && oauthOptions.length > 0 ? (
// <div className={Style['loginbox-methods']}>
// <Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space
style={{ cursor: 'pointer' }}
>
{oauthOptions?.map((ele) => (
<Tooltip title={ele.name}>
<Image
preview={false}
width={20}
height={20}
src={ele.logo}
onClick={() => {
goOauthLogin(ele.value)
}}
placeholder={<LinkOutlined />}
/>
</Tooltip>
))}
</Space>
// </div>
) : <></>
const Tip = <div className={Style['loginbox-tip']}>
@ -231,7 +265,7 @@ export default function Render(
{/* <Button type='link' iconPosition='end' icon={<RightOutlined />}>去注册</Button> */}
<Button type='link' onClick={() => {
goRegister && goRegister()
}}>{`去注册 >`}</Button>
}}>{`${t('goRegister')} >`}</Button>
</div>
)}
</div>
@ -263,7 +297,15 @@ export default function Render(
/>
{Tip}
</div>
{ScanMethods}
{(scanOptions?.length > 0 || oauthOptions?.length > 0) ? (
<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space split={<Divider type="vertical" />}>
{ScanMethods}
{OauthMethods}
</Space>
</div>
) : (<></>)}
</>
) : (
<>
@ -293,7 +335,15 @@ export default function Render(
/>
{Tip}
</div>}
{InputMethods}
{(inputOptions?.length > 0 || oauthOptions?.length > 0) ? (
<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>
<Space split={<Divider type="vertical" />}>
{InputMethods}
{OauthMethods}
</Space>
</div>
) : (<></>)}
</>
)}
</>

View File

@ -0,0 +1,7 @@
{
"navigationBarTitleText": "账号注册",
"enablePullDownRefresh": false,
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index"
}
}

View File

@ -0,0 +1,28 @@
/** index.wxss **/
@import "../../../config/styles/mp/index.less";
@import "../../../config/styles/mp/mixins.less";
.page-body {
height: 100%;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
box-sizing: border-box;
background-color: @oak-bg-color-container;
.safe-area-inset-bottom();
}
.login-box {
padding: 36rpx;
min-width: 78vw;
}
.login-body {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
}

View File

@ -0,0 +1,98 @@
import { OakPreConditionUnsetException } from "oak-domain/lib/types";
import { NameConfig } from "../../../entities/Passport";
import { EntityDict } from "../../../oak-app-domain";
import { encryptPasswordSha1 } from "../../../utils/password";
export default OakComponent({
isList: false,
data: {
allowRegister: false,
loginNameMin: 2,
loginNameMax: 8,
loginNameNeedVerify: false,
loginNameRegexs: [] as string[],
loginNameTip: '',
mode: 'all',
pwdMin: 8,
pwdMax: 24,
pwdNeedVerify: false,
pwdRegexs: [] as string[],
pwdTip: '',
},
properties: {
goLogin: undefined as (() => void) | undefined,
goBack: undefined as (() => void) | undefined,
},
lifetimes: {
async ready() {
const application = this.features.application.getApplication();
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
const loginNameAP = applicationPassports.find((ele: EntityDict['applicationPassport']['Schema']) => ele.passport.type === 'loginName');
const loginNameConfig = loginNameAP?.passport?.config as NameConfig;
const loginNameMin = loginNameConfig?.min ?? 2;
const loginNameMax = loginNameConfig?.max ?? 8;
const loginNameNeedVerify = loginNameConfig?.verify;
const loginNameRegexs = (loginNameConfig?.regexs && loginNameConfig?.regexs.length > 0) ? loginNameConfig?.regexs : [];
const loginNameTip = loginNameConfig?.tip;
const allowRegister = !!loginNameConfig?.register;
const pwdConfig = application?.system?.config?.Password;
const mode = pwdConfig?.mode ?? 'all';
const pwdMin = pwdConfig?.min ?? 8;
const pwdMax = pwdConfig?.max ?? 24;
const pwdNeedVerify = !!pwdConfig?.verify;
const pwdRegexs = (pwdConfig?.regexs && pwdConfig?.regexs.length > 0) ? pwdConfig?.regexs : [];
const pwdTip = pwdConfig?.tip ?? '';
this.setState(
{
allowRegister,
loginNameMin,
loginNameMax,
loginNameNeedVerify,
loginNameRegexs,
loginNameTip,
mode,
pwdMin,
pwdMax,
pwdNeedVerify,
pwdRegexs,
pwdTip,
},
() => this.reRender()
);
},
},
methods: {
async onConfirm(loginName: string, password: string) {
const { mode } = this.state;
let pwd = password;
if (mode === 'sha1') {
pwd = encryptPasswordSha1(password);
}
try {
await this.features.cache.exec('registerUserByLoginName', {
loginName,
password: pwd
});
this.setMessage(
{
type: 'success',
content: '注册成功,请前往登录页登录'
}
)
} catch (err) {
if (err instanceof OakPreConditionUnsetException) {
this.setMessage(
{
type: 'error',
content: err.message
}
)
} else {
throw err;
}
}
}
},
});

View File

@ -0,0 +1,4 @@
<view class="page-body">
<view class="login-box">
</view>
</view>

View File

@ -0,0 +1,26 @@
{
"not allow register": "暂未支持自行注册,请联系管理员为您分配账号!",
"registerTitle": "账号注册",
"label": {
"loginName": "账号",
"password": "密码",
"rePwd": "密码确认"
},
"placeholder": {
"loginName": "请输入账号",
"password": "请输入密码",
"rePwd": "请再次输入密码"
},
"validator": {
"loginNameMin": "账号最短长度为%{loginNameMin}位",
"loginNameMax": "账号最大长度为%{loginNameMax}位",
"loginNameVerify": "当前账号未符合规范",
"pwdMin": "密码最短长度为%{pwdMin}位",
"pwdMax": "密码最短长度为%{pwdMax}位",
"pwdVerify": "当前密码较弱",
"pwdDiff": "两次输入的密码不一致,请检查",
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
}

View File

@ -0,0 +1,145 @@
.registerbox {
&-main {
height: 100%;
display: flex;
flex: 1;
align-items: center;
flex-direction: column;
justify-content: center;
background: var(--oak-bg-color-container);
}
&-logo {
width: 194px;
margin-bottom: 20px;
}
&-wrap {
width: 400px;
display: block;
background: var(--oak-bg-color-container);
border-radius: 4px;
overflow: hidden;
box-shadow: 0 2px 4px rgb(0 0 0 / 8%), 0 0 4px rgb(0 0 0 / 8%);
transition: all 0.5s;
position: relative;
}
&-hd {
padding: 32px;
padding-bottom: 0px;
font-size: 20px;
font-weight: 600;
text-align: center;
}
&-bd {
padding: 32px;
padding-top: 24px;
}
&-only {
padding-top: 32px !important;
}
&-mobile {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-password {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-email {
position: relative;
padding: 32px;
height: 220px;
padding-bottom: 0px;
}
&-qrcode {
padding: 16px 32px;
font-size: 14px;
height: 268px;
&__sociallogin {
text-align: center;
color: #999;
}
&__refresh {
color: var(--oak-text-color-brand);
margin-left: 10px;
cursor: pointer;
&-icon {
color: var(--oak-text-color-brand);
font-size: 14px;
margin-left: 4px;
}
}
&__iframe {
position: relative;
width: 300px;
margin: 0 auto;
}
}
&-input {
// background-color: rgba(0, 0, 0, .04) !important;
}
&-ft {
height: 54px;
border-top: 1px solid #f2f3f5;
font-size: 14px;
&__btn {}
}
&-protocal {
padding: 20px 32px;
}
&-current {
color: var(--oak-text-color-brand) !important;
cursor: default;
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;
}
&-login {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
padding-top: 18px;
}
}

View File

@ -0,0 +1,287 @@
import React, { useEffect, useState } from 'react';
import { Form, Input, Button, } from 'antd';
import { EyeTwoTone, EyeInvisibleOutlined, LeftOutlined, } from '@ant-design/icons';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import classNames from 'classnames';
import Style from './web.module.less';
export default function Render(
props: WebComponentProps<
EntityDict,
'token',
false,
{
width: string;
allowRegister: boolean;
loginNameMin: number;
loginNameMax: number;
loginNameNeedVerify: boolean;
loginNameRegexs: string[];
loginNameTip: string;
pwdMin: number;
pwdMax: number;
pwdNeedVerify: boolean;
pwdRegexs: string[];
pwdTip: string;
goLogin: () => void;
goBack: () => void;
},
{
onConfirm: (loginName: string, password: string) => Promise<void>;
}
>
) {
const { data, methods } = props;
const {
width,
allowRegister,
loginNameMin,
loginNameMax,
loginNameNeedVerify,
loginNameRegexs,
loginNameTip,
pwdMin,
pwdMax,
pwdNeedVerify,
pwdRegexs,
pwdTip,
goLogin,
goBack,
oakExecuting,
oakLoading,
} = data;
const { t, onConfirm } = methods;
const [loginName, setLoginName] = useState('');
const [loginNameHelp, setLoginNameHelp] = useState('');
const [loginNameStatus, setLoginNameStatus] = useState('' as '' | "success" | "warning" | "error" | "validating" | undefined);
const [password, setPassword] = useState('');
const [password2, setPassword2] = useState('');
const [validateHelp, setValidateHelp] = useState('');
const [validateHelp2, setValidateHelp2] = useState('');
const [validateStatus, setValidateStatus] = useState('' as '' | "success" | "warning" | "error" | "validating" | undefined);
if (!allowRegister) {
return (
<div
className={classNames(Style['registerbox-wrap'], {
[Style['registerbox-wrap__mobile']]: width === 'xs',
})}
>
<div
style={{ minHeight: 200, boxSizing: 'border-box', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
{t('not allow register')}
</div>
</div>
)
}
return (
<div
className={classNames(Style['registerbox-wrap'], {
[Style['registerbox-wrap__mobile']]: width === 'xs',
})}
>
{!!goBack && <LeftOutlined style={{ position: 'absolute', top: 40, left: 32, color:'#555555' }} onClick={() => goBack()} />}
<div
className={Style['registerbox-hd']}
>
<div>{t('registerTitle')}</div>
</div>
<div
className={Style['registerbox-bd']}
>
<Form
style={{ maxWidth: 400 }}
layout="vertical"
>
<Form.Item
label={t('label.loginName')}
name="loginName"
tooltip={loginNameTip}
help={loginNameHelp}
hasFeedback
validateStatus={loginNameStatus}
rules={[
{
required: true,
message: t('placeholder.loginName'),
validator: (_, value) => {
if (value.length < loginNameMin) {
setLoginNameHelp(t('validator.loginNameMin', { loginNameMin }));
setLoginNameStatus('error');
return;
} else if (value.length > loginNameMax) {
setLoginNameHelp(t('validator.loginNameMax', { loginNameMax }));
setLoginNameStatus('error');
return;
} else if (!!loginNameNeedVerify && loginNameRegexs && loginNameRegexs.length > 0) {
for (const regex of loginNameRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
setLoginNameHelp(t('validator.loginNameVerify'));
setLoginNameStatus('error');
return;
}
}
}
setLoginNameHelp('')
setLoginNameStatus('success');
}
},
]}
>
<Input
autoFocus
onChange={({ currentTarget }) => setLoginName(currentTarget.value)}
placeholder={t('placeholder.loginName')}
value={loginName}
/>
</Form.Item>
<Form.Item
label={t('label.password')}
name="password"
help={validateHelp}
tooltip={pwdTip}
rules={[
{
required: true,
message: t('placeholder.password'),
validator: (_, value) => {
if (value.length < pwdMin) {
setValidateHelp(t('validator.pwdMin', { pwdMin }));
setValidateStatus('error');
return;
} else if (value.length > pwdMax) {
setValidateHelp(t('validator.pwdMax', { pwdMax }));
setValidateStatus('error');
return;
} else if (!!pwdNeedVerify && pwdRegexs && pwdRegexs.length > 0) {
for (const regex of pwdRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
setValidateHelp(t('validator.pwdVerify'));
setValidateHelp2('')
setValidateStatus('error');
return;
}
}
if (password2) {
setValidateHelp('')
setValidateHelp2(
value === password2
? ''
: t('validator.pwdDiff')
);
setValidateStatus(
value === password2
? 'success'
: 'error'
);
} else {
setValidateHelp2(t('noRepwd'));
setValidateHelp('');
setValidateStatus('error');
}
}
else {
if (password2) {
setValidateHelp('')
setValidateHelp2(
value === password2
? ''
: t('validator.pwdDiff')
);
setValidateStatus(
value === password2
? 'success'
: 'error'
);
} else {
setValidateHelp2(t('noRepwd'));
setValidateHelp('');
setValidateStatus('error');
}
}
}
},
]}
hasFeedback
validateStatus={validateStatus}
>
<Input.Password
value={password}
onChange={(e) => {
const strValue = e.target.value;
setPassword(strValue);
}}
iconRender={(visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)}
placeholder={t('placeholder.password')}
/>
</Form.Item>
<Form.Item
label={t('label.rePwd')}
name="passwordConfirm"
rules={[
{
required: true,
validator: (_, value) => {
if (password.length < pwdMin || password.length > pwdMax) {
return;
}
else if (!!pwdNeedVerify && pwdRegexs && pwdRegexs.length > 0) {
for (const regex of pwdRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(password)) {
return;
}
}
}
setValidateHelp2(value === password ? '' : t('validator.pwdDiff'))
setValidateStatus(value === password ? 'success' : 'error')
}
},
]}
validateTrigger="onChange"
help={validateHelp2}
validateStatus={validateStatus}
hasFeedback
>
<Input.Password
value={password2}
onChange={(e) => {
const strValue = e.target.value;
setPassword2(strValue)
}}
iconRender={(visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)}
placeholder={t('placeholder.rePwd')}
/>
</Form.Item>
<Button
block
size="large"
type="primary"
onClick={async () => {
await onConfirm(loginName, password);
}}
disabled={!(loginName && loginNameStatus !== 'error' && password && validateStatus !== 'error') || oakExecuting || oakLoading}
>
{t('register')}
</Button>
</Form>
{!!goLogin && (
<div className={Style['registerbox-login']}>
<Button type="link" onClick={() => {
goLogin && goLogin()
}}>
{t('goLogin')}
</Button>
</div>
)}
</div>
</div >
);
}

View File

@ -687,7 +687,8 @@ const i18ns: I18n[] = [
"resendAfter": "秒后可重发",
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册"
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
}
},
{
@ -782,6 +783,39 @@ const i18ns: I18n[] = [
}
}
},
{
id: "36c643dbcc19c3258f6077a6684236ff",
namespace: "oak-general-business-c-user-register",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/user/register",
data: {
"not allow register": "暂未支持自行注册,请联系管理员为您分配账号!",
"registerTitle": "账号注册",
"label": {
"loginName": "账号",
"password": "密码",
"rePwd": "密码确认"
},
"placeholder": {
"loginName": "请输入账号",
"password": "请输入密码",
"rePwd": "请再次输入密码"
},
"validator": {
"loginNameMin": "账号最短长度为%{loginNameMin}位",
"loginNameMax": "账号最大长度为%{loginNameMax}位",
"loginNameVerify": "当前账号未符合规范",
"pwdMin": "密码最短长度为%{pwdMin}位",
"pwdMax": "密码最短长度为%{pwdMax}位",
"pwdVerify": "当前密码较弱",
"pwdDiff": "两次输入的密码不一致,请检查",
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
}
},
{
id: "5bf96a3e054b8d73c76d7bb45ea90a80",
namespace: "oak-general-business-c-userEntityGrant-claim",

View File

@ -21,6 +21,7 @@ import oauthProviderTriggers from './oauthProvider';
import oauthUserTriggers from './oauthUser';
import oauthUserAuthTriggers from './oauthUserAuth';
import mobileTriggers from './mobile';
import applicationPassportTriggers from './applicationPassport';
// import accountTriggers from './account';
@ -50,4 +51,5 @@ export default [
...oauthUserTriggers,
...oauthUserAuthTriggers,
...mobileTriggers,
...applicationPassportTriggers,
];