feat: 小程序支持账号注册

This commit is contained in:
lxy 2025-11-14 17:19:46 +08:00
parent a1ed3c7ca7
commit 169562994b
38 changed files with 857 additions and 63 deletions

View File

@ -36,11 +36,11 @@
{{t('Login')}}
</l-button>
<view class="methods">
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">一键登录</view>
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">{{t('loginMode.wechatMp')}}</view>
<view wx:else></view>
<view style="color:#835D01; display:flex; align-items:center; justify-content:flex-end; gap:8rpx">
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">短信登录</view>
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">{{t('loginMode.sms')}}</view>
<view wx:if="{{allowSms && allowPassword}}">/</view>
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">密码登录</view>
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">{{t('loginMode.password')}}</view>
</view>
</view>

View File

@ -5,5 +5,10 @@
"Captcha": "输入%{digit}位验证码",
"Email": "请输入邮箱"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"password": "密码登录"
}
}

View File

@ -25,6 +25,8 @@
pwdAllowEmail="{{pwdAllowEmail}}"
pwdAllowLoginName="{{pwdAllowLoginName}}"
setLoginMode="{{setLoginModeMp}}"
allowRegister="{{allowRegister}}"
goRegister="{{goRegister}}"
/>
</block>
<block wx:elif="{{loginMode ==='email'}}">
@ -41,9 +43,9 @@
</block>
<view wx:elif="{{loginMode === 'wechatMp'}}" class="login-body">
<l-button type="default" size="long" disabled="{{loading}}" bind:lintap="loginByWechatMp" style="width:100%">
授权登录
{{t('loginMode.wechatMp')}}
</l-button>
<view wx:if="{{allowSms || allowPassword || allowEmail}}" style="font-size:28rpx; margin-top:28rpx; color:#8F976A" bind:tap="changeLoginMp">其他方式登录</view>
<view wx:if="{{allowSms || allowPassword || allowEmail}}" style="font-size:28rpx; margin-top:28rpx; color:#8F976A" bind:tap="changeLoginMp">{{t('loginMode.other')}}</view>
</view>
</view>
</view>

View File

@ -14,5 +14,9 @@
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
"goRegister": "去注册",
"loginMode": {
"wechatMp": "授权登录",
"other": "其他方式登录"
}
}

View File

@ -12,5 +12,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
allowWechatMp: boolean;
setLoginMode: (value: string) => void;
pwdMode: string;
allowRegister: boolean;
goRegister: () => void;
}>) => React.ReactElement;
export default _default;

View File

@ -44,6 +44,8 @@ export default OakComponent({
allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value) => undefined,
pwdMode: 'all', //密码明文密文存储模式
allowRegister: false,
goRegister: () => undefined,
},
lifetimes: {},
listeners: {
@ -137,6 +139,10 @@ export default OakComponent({
const { setLoginMode } = this.props;
const { value } = e.currentTarget.dataset;
setLoginMode && setLoginMode(value);
},
goRegisterMp() {
const { goRegister } = this.props;
goRegister && goRegister();
}
},
});

View File

@ -4,6 +4,7 @@
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-input": "@oak-frontend-base/miniprogram_npm/lin-ui/input/index",
"oak-icon": "@oak-frontend-base/components/icon/index"
"oak-icon": "@oak-frontend-base/components/icon/index",
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index"
}
}

View File

@ -42,4 +42,14 @@
justify-content: space-between;
align-items: center;
box-sizing: border-box;
}
.registerBox {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 24rpx;
color: @oak-color-primary;
font-size: 28rpx;
}

View File

@ -29,16 +29,22 @@
bind:linclear="inputChangeMp"
/>
</view>
<view wx:if="{{allowRegister}}" class="registerBox">
<l-button catch:lintap="goRegisterMp" special="{{true}}">
<view>{{t('register')}}</view>
<l-icon name="right" size="24" style="transform:translateY(8rpx); margin-left:8rpx"/>
</l-button>
</view>
<l-button size="long" disabled="{{!!disabled || !allowSubmit || loading}}" catch:lintap="loginByAccount" height="{{80}}" style="width:100%">
{{t('Login')}}
</l-button>
<view class="methods">
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">一键登录</view>
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">{{t('loginMode.wechatMp')}}</view>
<view wx:else></view>
<!-- <view wx:if="{{allowSms}}" style="color:#835D01" bindtap="changeLoginMp" data-value="sms">短信登录</view> -->
<view style="color:#835D01; display:flex; align-items:center; justify-content:flex-end; gap:8rpx">
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">短信登录</view>
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">{{t('loginMode.sms')}}</view>
<view wx:if="{{allowSms && allowEmail}}">/</view>
<view wx:if="{{allowEmail}}"bindtap="changeLoginMp" data-value="email">邮箱登录</view>
<view wx:if="{{allowEmail}}"bindtap="changeLoginMp" data-value="email">{{t('loginMode.email')}}</view>
</view>
</view>

View File

@ -6,5 +6,11 @@
"Mobile": "手机号",
"Email": "邮箱",
"Password": "请输入密码"
},
"register": "去注册",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"email": "邮箱登录"
}
}

View File

@ -37,12 +37,12 @@
{{t('Login')}}
</l-button>
<view class="methods">
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">一键登录</view>
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">{{t('loginMode.wechatMp')}}</view>
<view wx:else></view>
<!-- <view wx:if="{{allowPassword}}" style="color:#835D01" bindtap="changeLoginMp" data-value="password">密码登录</view> -->
<view style="color:#835D01; display:flex; align-items:center; justify-content:flex-end; gap:8rpx">
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">密码登录</view>
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">{{t('loginMode.password')}}</view>
<view wx:if="{{allowPassword && allowEmail}}">/</view>
<view wx:if="{{allowEmail}}" bindtap="changeLoginMp" data-value="email">邮箱登录</view>
<view wx:if="{{allowEmail}}" bindtap="changeLoginMp" data-value="email">{{t('loginMode.email')}}</view>
</view>
</view>

View File

@ -5,5 +5,10 @@
"Captcha": "输入%{digit}位短信验证码",
"Mobile": "请输入手机号"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"password": "密码登录",
"email": "邮箱登录"
}
}

View File

@ -15,6 +15,15 @@ export default OakComponent({
pwdNeedVerify: false,
pwdRegexs: [],
pwdTip: '',
loginNameRulesMp: [], //小程序校验账号
passwordRulesMp: [], //小程序校验密码
confirmRulesMp: [], //小程序校验密码确认
loginName: '',
password: '',
confirm: '',
loginNameHasErr: false,
passwordHasErr: false,
confirmHasErr: false,
},
properties: {
goLogin: undefined,
@ -39,6 +48,141 @@ export default OakComponent({
const pwdNeedVerify = !!pwdConfig?.verify;
const pwdRegexs = (pwdConfig?.regexs && pwdConfig?.regexs.length > 0) ? pwdConfig?.regexs : [];
const pwdTip = pwdConfig?.tip ?? '';
const loginNameRulesMp = [
{ required: true, message: this.t('placeholder.loginName'), trigger: 'blur' },
// { min: loginNameMin, message: this.t('validator.loginNameMin', { loginNameMin }), trigger: 'change' },
// { max: loginNameMax, message: this.t('validator.loginNameMax', { loginNameMax }), trigger: 'change' },
{
validator: (rule, value, callback, source) => {
if (!value || value.length < loginNameMin) {
this.setState({
loginNameHasErr: true
});
callback(false);
return;
}
this.setState({
loginNameHasErr: false
});
callback();
}, message: this.t('validator.loginNameMin', { loginNameMin }), trigger: 'change'
},
{
validator: (rule, value, callback, source) => {
if (!value || value.length > loginNameMax) {
this.setState({
loginNameHasErr: true
});
callback(false);
return;
}
this.setState({
loginNameHasErr: false
});
callback();
}, message: this.t('validator.loginNameMax', { loginNameMax }), trigger: 'change'
},
{
validator: (rule, value, callback, source) => {
if (!!loginNameNeedVerify && loginNameRegexs && loginNameRegexs.length > 0) {
for (const regex of loginNameRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
this.setState({
loginNameHasErr: true
});
callback(false);
return;
}
}
}
this.setState({
loginNameHasErr: false
});
callback();
},
message: this.t('validator.loginNameVerify'),
trigger: 'change'
}
];
const passwordRulesMp = [
{ required: true, message: this.t('placeholder.password'), trigger: 'blur' },
// { min: pwdMin, message: this.t('validator.pwdMin', { pwdMin }), trigger: 'change' },
// { max: pwdMax, message: this.t('validator.pwdMax', { pwdMax }), trigger: 'change' },
{
validator: (rule, value, callback, source) => {
if (!value || value.length < pwdMin) {
this.setState({
passwordHasErr: true
});
callback(false);
return;
}
this.setState({
passwordHasErr: false
});
callback();
}, message: this.t('validator.pwdMin', { pwdMin }), trigger: 'change'
},
{
validator: (rule, value, callback, source) => {
if (!value || value.length > pwdMax) {
this.setState({
passwordHasErr: true
});
callback(false);
return;
}
this.setState({
passwordHasErr: false
});
callback();
}, message: this.t('validator.pwdMax', { pwdMax }), trigger: 'change'
},
{
validator: (rule, value, callback, source) => {
if (!!pwdNeedVerify && pwdRegexs && pwdRegexs.length > 0) {
for (const regex of pwdRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
this.setState({
hasErr: true
});
callback(false);
return;
}
}
}
this.setState({
hasErr: false
});
callback();
},
message: this.t('validator.pwdVerify'),
trigger: 'change'
}
];
const confirmRulesMp = [
{ required: true, message: this.t('validator.noRepwd'), trigger: 'blur' },
{
validator: (rule, value, callback, source) => {
const { password } = this.state;
if (password !== value) {
this.setState({
confirmHasErr: true
});
callback(false);
return;
}
this.setState({
confirmHasErr: false
});
callback();
},
message: this.t('validator.pwdDiff'),
trigger: 'change'
}
];
this.setState({
allowRegister,
loginNameMin,
@ -52,6 +196,9 @@ export default OakComponent({
pwdNeedVerify,
pwdRegexs,
pwdTip,
loginNameRulesMp,
passwordRulesMp,
confirmRulesMp,
}, () => this.reRender());
},
},
@ -69,11 +216,11 @@ export default OakComponent({
});
this.setMessage({
type: 'success',
content: '注册成功,请前往登录页登录'
content: this.t('success')
});
}
catch (err) {
if (err instanceof OakPreConditionUnsetException) {
if (err instanceof OakPreConditionUnsetException || err.name === 'OakPreConditionUnsetException') {
this.setMessage({
type: 'error',
content: err.message
@ -83,6 +230,20 @@ export default OakComponent({
throw err;
}
}
},
setValueMp(input) {
const { detail, target: { dataset }, } = input;
const { attr } = dataset;
const { value } = detail;
this.setState({ [attr]: value });
},
async onConfirmMp() {
const { loginName, password, } = this.state;
await this.onConfirm(loginName, password);
},
goLoginMp() {
const { goLogin } = this.props;
goLogin && goLogin();
}
},
});

View File

@ -2,6 +2,9 @@
"navigationBarTitleText": "账号注册",
"enablePullDownRefresh": false,
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index"
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-form": "@oak-frontend-base/miniprogram_npm/lin-ui/form/index",
"l-form-item": "@oak-frontend-base/miniprogram_npm/lin-ui/form-item/index",
"l-input": "@oak-frontend-base/miniprogram_npm/lin-ui/input/index"
}
}

View File

@ -14,15 +14,67 @@
.safe-area-inset-bottom();
}
.login-box {
padding: 36rpx;
min-width: 78vw;
.register-box {
padding: 32rpx;
min-width: 80vw;
}
.login-body {
.regitser-title {
padding-bottom: 24rpx;
font-size: 40rpx;
font-weight: 600;
text-align: center;
}
.register-body {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
width: 100%;
}
.my-input {
padding-right: 0rpx !important;
padding-left: 0rpx !important;
height: 80rpx !important;
line-height: 80rpx !important;
border: 2rpx solid #D9d9D9;
border-radius: 16rpx;
}
.input {
padding-left: 12rpx !important;
}
.formItem {
padding: 0rpx !important;
min-width: 80vw;
}
.my-btn {
margin-top: 48rpx;
}
.label {
display: flex;
align-items: flex-start;
justify-content: flex-start;
gap: 8rpx;
color: #000;
}
.help {
font-size: 24rpx;
}
.login {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
color: @oak-color-primary;
font-size: 28rpx;
margin-top: 24rpx;
}

View File

@ -1,4 +1,104 @@
<view class="page-body">
<view class="login-box">
<view class="register-box">
<view class="regitser-title">{{t('registerTitle')}}</view>
<view class="register-body">
<l-form>
<l-form-item
label="{{t('label.loginName')}}"
label-placement="column"
l-form-item-class="formItem"
label-slot="{{true}}"
>
<view slot="label" style="margin:16rpx 0rpx">
<view class="label">
<view style="color:red">*</view>
<view>{{t('label.loginName')}}</view>
</view>
<view class="help">({{loginNameTip}})</view>
</view>
<l-input
value="{{loginName}}"
data-attr="loginName"
rules="{{loginNameRulesMp}}"
bind:lininput="setValueMp"
show-row="{{false}}"
tip-type="text"
hide-label="{{true}}"
show-row="{{false}}"
l-class="my-input"
l-input-class="input"
style="flex:1;"
/>
</l-form-item>
<l-form-item
label="{{t('label.password')}}"
label-placement="column"
l-form-item-class="formItem"
label-slot="{{true}}"
>
<view slot="label" style="margin:16rpx 0rpx">
<view class="label">
<view style="color:red">*</view>
<view>{{t('label.password')}}</view>
</view>
<view class="help">({{pwdTip}})</view>
</view>
<l-input
value="{{password}}"
type="password"
data-attr="password"
rules="{{passwordRulesMp}}"
bind:lininput="setValueMp"
show-row="{{false}}"
tip-type="text"
hide-label="{{true}}"
show-row="{{false}}"
l-class="my-input"
l-input-class="input"
style="flex:1;"
/>
</l-form-item>
<l-form-item
label="{{t('label.rePwd')}}"
label-placement="column"
l-form-item-class="formItem"
label-slot="{{true}}"
>
<view slot="label" style="margin:16rpx 0rpx">
<view class="label">
<view style="color:red">*</view>
<view>{{t('label.rePwd')}}</view>
</view>
</view>
<l-input
value="{{confirm}}"
type="password"
data-attr="confirm"
rules="{{confirmRulesMp}}"
bind:lininput="setValueMp"
show-row="{{false}}"
tip-type="text"
hide-label="{{true}}"
show-row="{{false}}"
l-class="my-input"
l-input-class="input"
style="flex:1;"
/>
</l-form-item>
<l-button
disabled="{{!(loginName && password && confirm) || loginNameHasErr || passwordHasErr || confirmHasErr}}"
catch:lintap="onConfirmMp"
size="long"
l-class="my-btn"
>
{{t('register')}}
</l-button>
</l-form>
</view>
<view class="login">
<l-button catch:lintap="goLoginMp" special="{{true}}">
<view>{{t('goLogin')}}</view>
</l-button>
</view>
</view>
</view>

View File

@ -22,5 +22,6 @@
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
"goLogin": "已有账号?立即登录",
"success": "注册成功,请前往登录页登录"
}

View File

@ -116,7 +116,7 @@ export default function Render(props) {
: 'error');
}
else {
setValidateHelp2(t('noRepwd'));
setValidateHelp2(t('validator.noRepwd'));
setValidateHelp('');
setValidateStatus('error');
}

View File

@ -662,7 +662,12 @@ const i18ns = [
"Captcha": "输入%{digit}位验证码",
"Email": "请输入邮箱"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"password": "密码登录"
}
}
},
{
@ -687,7 +692,11 @@ const i18ns = [
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
"goRegister": "去注册",
"loginMode": {
"wechatMp": "授权登录",
"other": "其他方式登录"
}
}
},
{
@ -704,6 +713,12 @@ const i18ns = [
"Mobile": "手机号",
"Email": "邮箱",
"Password": "请输入密码"
},
"register": "去注册",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"email": "邮箱登录"
}
}
},
@ -720,7 +735,12 @@ const i18ns = [
"Captcha": "输入%{digit}位短信验证码",
"Mobile": "请输入手机号"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"password": "密码登录",
"email": "邮箱登录"
}
}
},
{
@ -812,7 +832,8 @@ const i18ns = [
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
"goLogin": "已有账号?立即登录",
"success": "注册成功,请前往登录页登录"
}
},
{

View File

@ -664,7 +664,12 @@ const i18ns = [
"Captcha": "输入%{digit}位验证码",
"Email": "请输入邮箱"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"password": "密码登录"
}
}
},
{
@ -689,7 +694,11 @@ const i18ns = [
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
"goRegister": "去注册",
"loginMode": {
"wechatMp": "授权登录",
"other": "其他方式登录"
}
}
},
{
@ -706,6 +715,12 @@ const i18ns = [
"Mobile": "手机号",
"Email": "邮箱",
"Password": "请输入密码"
},
"register": "去注册",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"email": "邮箱登录"
}
}
},
@ -722,7 +737,12 @@ const i18ns = [
"Captcha": "输入%{digit}位短信验证码",
"Mobile": "请输入手机号"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"password": "密码登录",
"email": "邮箱登录"
}
}
},
{
@ -814,7 +834,8 @@ const i18ns = [
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
"goLogin": "已有账号?立即登录",
"success": "注册成功,请前往登录页登录"
}
},
{

View File

@ -36,11 +36,11 @@
{{t('Login')}}
</l-button>
<view class="methods">
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">一键登录</view>
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">{{t('loginMode.wechatMp')}}</view>
<view wx:else></view>
<view style="color:#835D01; display:flex; align-items:center; justify-content:flex-end; gap:8rpx">
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">短信登录</view>
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">{{t('loginMode.sms')}}</view>
<view wx:if="{{allowSms && allowPassword}}">/</view>
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">密码登录</view>
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">{{t('loginMode.password')}}</view>
</view>
</view>

View File

@ -5,5 +5,10 @@
"Captcha": "输入%{digit}位验证码",
"Email": "请输入邮箱"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"password": "密码登录"
}
}

View File

@ -25,6 +25,8 @@
pwdAllowEmail="{{pwdAllowEmail}}"
pwdAllowLoginName="{{pwdAllowLoginName}}"
setLoginMode="{{setLoginModeMp}}"
allowRegister="{{allowRegister}}"
goRegister="{{goRegister}}"
/>
</block>
<block wx:elif="{{loginMode ==='email'}}">
@ -41,9 +43,9 @@
</block>
<view wx:elif="{{loginMode === 'wechatMp'}}" class="login-body">
<l-button type="default" size="long" disabled="{{loading}}" bind:lintap="loginByWechatMp" style="width:100%">
授权登录
{{t('loginMode.wechatMp')}}
</l-button>
<view wx:if="{{allowSms || allowPassword || allowEmail}}" style="font-size:28rpx; margin-top:28rpx; color:#8F976A" bind:tap="changeLoginMp">其他方式登录</view>
<view wx:if="{{allowSms || allowPassword || allowEmail}}" style="font-size:28rpx; margin-top:28rpx; color:#8F976A" bind:tap="changeLoginMp">{{t('loginMode.other')}}</view>
</view>
</view>
</view>

View File

@ -14,5 +14,9 @@
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
"goRegister": "去注册",
"loginMode": {
"wechatMp": "授权登录",
"other": "其他方式登录"
}
}

View File

@ -4,6 +4,7 @@
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-input": "@oak-frontend-base/miniprogram_npm/lin-ui/input/index",
"oak-icon": "@oak-frontend-base/components/icon/index"
"oak-icon": "@oak-frontend-base/components/icon/index",
"l-icon": "@oak-frontend-base/miniprogram_npm/lin-ui/icon/index"
}
}

View File

@ -42,4 +42,14 @@
justify-content: space-between;
align-items: center;
box-sizing: border-box;
}
.registerBox {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 24rpx;
color: @oak-color-primary;
font-size: 28rpx;
}

View File

@ -49,6 +49,8 @@ export default OakComponent({
allowWechatMp: false, //小程序切换授权登录
setLoginMode: (value: string) => undefined as void,
pwdMode: 'all', //密码明文密文存储模式
allowRegister: false,
goRegister: () => undefined as void,
},
lifetimes: {
},
@ -147,6 +149,10 @@ export default OakComponent({
const { setLoginMode } = this.props;
const { value } = e.currentTarget.dataset;
setLoginMode && setLoginMode(value);
},
goRegisterMp() {
const { goRegister } = this.props;
goRegister && goRegister();
}
},
});

View File

@ -29,16 +29,22 @@
bind:linclear="inputChangeMp"
/>
</view>
<view wx:if="{{allowRegister}}" class="registerBox">
<l-button catch:lintap="goRegisterMp" special="{{true}}">
<view>{{t('register')}}</view>
<l-icon name="right" size="24" style="transform:translateY(8rpx); margin-left:8rpx"/>
</l-button>
</view>
<l-button size="long" disabled="{{!!disabled || !allowSubmit || loading}}" catch:lintap="loginByAccount" height="{{80}}" style="width:100%">
{{t('Login')}}
</l-button>
<view class="methods">
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">一键登录</view>
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">{{t('loginMode.wechatMp')}}</view>
<view wx:else></view>
<!-- <view wx:if="{{allowSms}}" style="color:#835D01" bindtap="changeLoginMp" data-value="sms">短信登录</view> -->
<view style="color:#835D01; display:flex; align-items:center; justify-content:flex-end; gap:8rpx">
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">短信登录</view>
<view wx:if="{{allowSms}}" bindtap="changeLoginMp" data-value="sms">{{t('loginMode.sms')}}</view>
<view wx:if="{{allowSms && allowEmail}}">/</view>
<view wx:if="{{allowEmail}}"bindtap="changeLoginMp" data-value="email">邮箱登录</view>
<view wx:if="{{allowEmail}}"bindtap="changeLoginMp" data-value="email">{{t('loginMode.email')}}</view>
</view>
</view>

View File

@ -6,5 +6,11 @@
"Mobile": "手机号",
"Email": "邮箱",
"Password": "请输入密码"
},
"register": "去注册",
"loginMode":{
"wechatMp":"一键登录",
"sms":"短信登录",
"email":"邮箱登录"
}
}

View File

@ -37,12 +37,12 @@
{{t('Login')}}
</l-button>
<view class="methods">
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">一键登录</view>
<view wx:if="{{allowWechatMp}}" style="color:#8F976A" bindtap="changeLoginMp" data-value="wechatMp">{{t('loginMode.wechatMp')}}</view>
<view wx:else></view>
<!-- <view wx:if="{{allowPassword}}" style="color:#835D01" bindtap="changeLoginMp" data-value="password">密码登录</view> -->
<view style="color:#835D01; display:flex; align-items:center; justify-content:flex-end; gap:8rpx">
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">密码登录</view>
<view wx:if="{{allowPassword}}" bindtap="changeLoginMp" data-value="password">{{t('loginMode.password')}}</view>
<view wx:if="{{allowPassword && allowEmail}}">/</view>
<view wx:if="{{allowEmail}}" bindtap="changeLoginMp" data-value="email">邮箱登录</view>
<view wx:if="{{allowEmail}}" bindtap="changeLoginMp" data-value="email">{{t('loginMode.email')}}</view>
</view>
</view>

View File

@ -5,5 +5,10 @@
"Captcha": "输入%{digit}位短信验证码",
"Mobile": "请输入手机号"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"password": "密码登录",
"email": "邮箱登录"
}
}

View File

@ -2,6 +2,9 @@
"navigationBarTitleText": "账号注册",
"enablePullDownRefresh": false,
"usingComponents": {
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index"
"l-button": "@oak-frontend-base/miniprogram_npm/lin-ui/button/index",
"l-form":"@oak-frontend-base/miniprogram_npm/lin-ui/form/index",
"l-form-item":"@oak-frontend-base/miniprogram_npm/lin-ui/form-item/index",
"l-input":"@oak-frontend-base/miniprogram_npm/lin-ui/input/index"
}
}

View File

@ -14,15 +14,67 @@
.safe-area-inset-bottom();
}
.login-box {
padding: 36rpx;
min-width: 78vw;
.register-box {
padding: 32rpx;
min-width: 80vw;
}
.login-body {
.regitser-title {
padding-bottom: 24rpx;
font-size: 40rpx;
font-weight: 600;
text-align: center;
}
.register-body {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
width: 100%;
}
.my-input {
padding-right: 0rpx !important;
padding-left: 0rpx !important;
height: 80rpx !important;
line-height: 80rpx !important;
border: 2rpx solid #D9d9D9;
border-radius: 16rpx;
}
.input {
padding-left: 12rpx !important;
}
.formItem {
padding: 0rpx !important;
min-width: 80vw;
}
.my-btn {
margin-top: 48rpx;
}
.label {
display: flex;
align-items: flex-start;
justify-content: flex-start;
gap: 8rpx;
color: #000;
}
.help {
font-size: 24rpx;
}
.login {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
color: @oak-color-primary;
font-size: 28rpx;
margin-top: 24rpx;
}

View File

@ -18,6 +18,15 @@ export default OakComponent({
pwdNeedVerify: false,
pwdRegexs: [] as string[],
pwdTip: '',
loginNameRulesMp: [] as any[], //小程序校验账号
passwordRulesMp: [] as any[], //小程序校验密码
confirmRulesMp: [] as any[], //小程序校验密码确认
loginName: '',
password: '',
confirm: '',
loginNameHasErr: false,
passwordHasErr: false,
confirmHasErr: false,
},
properties: {
goLogin: undefined as (() => void) | undefined,
@ -44,6 +53,143 @@ export default OakComponent({
const pwdNeedVerify = !!pwdConfig?.verify;
const pwdRegexs = (pwdConfig?.regexs && pwdConfig?.regexs.length > 0) ? pwdConfig?.regexs : [];
const pwdTip = pwdConfig?.tip ?? '';
const loginNameRulesMp = [
{ required: true, message: this.t('placeholder.loginName'), trigger: 'blur' },
// { min: loginNameMin, message: this.t('validator.loginNameMin', { loginNameMin }), trigger: 'change' },
// { max: loginNameMax, message: this.t('validator.loginNameMax', { loginNameMax }), trigger: 'change' },
{
validator: (rule: any, value: any, callback: any, source: any) => {
if (!value || value.length < loginNameMin) {
this.setState({
loginNameHasErr: true
})
callback(false);
return;
}
this.setState({
loginNameHasErr: false
})
callback()
}, message: this.t('validator.loginNameMin', { loginNameMin }), trigger: 'change'
},
{
validator: (rule: any, value: any, callback: any, source: any) => {
if (!value || value.length > loginNameMax) {
this.setState({
loginNameHasErr: true
})
callback(false);
return;
}
this.setState({
loginNameHasErr: false
})
callback()
}, message: this.t('validator.loginNameMax', { loginNameMax }), trigger: 'change'
},
{
validator: (rule: any, value: any, callback: any, source: any) => {
if (!!loginNameNeedVerify && loginNameRegexs && loginNameRegexs.length > 0) {
for (const regex of loginNameRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
this.setState({
loginNameHasErr: true
})
callback(false);
return;
}
}
}
this.setState({
loginNameHasErr: false
})
callback()
},
message: this.t('validator.loginNameVerify'),
trigger: 'change'
}
];
const passwordRulesMp = [
{ required: true, message: this.t('placeholder.password'), trigger: 'blur' },
// { min: pwdMin, message: this.t('validator.pwdMin', { pwdMin }), trigger: 'change' },
// { max: pwdMax, message: this.t('validator.pwdMax', { pwdMax }), trigger: 'change' },
{
validator: (rule: any, value: any, callback: any, source: any) => {
if (!value || value.length < pwdMin) {
this.setState({
passwordHasErr: true
})
callback(false);
return;
}
this.setState({
passwordHasErr: false
})
callback()
}, message: this.t('validator.pwdMin', { pwdMin }), trigger: 'change'
},
{
validator: (rule: any, value: any, callback: any, source: any) => {
if (!value || value.length > pwdMax) {
this.setState({
passwordHasErr: true
})
callback(false);
return;
}
this.setState({
passwordHasErr: false
})
callback()
}, message: this.t('validator.pwdMax', { pwdMax }), trigger: 'change'
},
{
validator: (rule: any, value: any, callback: any, source: any) => {
if (!!pwdNeedVerify && pwdRegexs && pwdRegexs.length > 0) {
for (const regex of pwdRegexs) {
const pattern = new RegExp(regex);
if (!pattern.test(value)) {
this.setState({
hasErr: true
})
callback(false);
return;
}
}
}
this.setState({
hasErr: false
})
callback()
},
message: this.t('validator.pwdVerify'),
trigger: 'change'
}
]
const confirmRulesMp = [
{ required: true, message: this.t('validator.noRepwd'), trigger: 'blur' },
{
validator: (rule: any, value: any, callback: any, source: any) => {
const { password } = this.state;
if (password !== value) {
this.setState({
confirmHasErr: true
})
callback(false);
return;
}
this.setState({
confirmHasErr: false
})
callback()
},
message: this.t('validator.pwdDiff'),
trigger: 'change'
}
]
this.setState(
{
allowRegister,
@ -58,6 +204,9 @@ export default OakComponent({
pwdNeedVerify,
pwdRegexs,
pwdTip,
loginNameRulesMp,
passwordRulesMp,
confirmRulesMp,
},
() => this.reRender()
);
@ -78,11 +227,11 @@ export default OakComponent({
this.setMessage(
{
type: 'success',
content: '注册成功,请前往登录页登录'
content: this.t('success')
}
)
} catch (err) {
if (err instanceof OakPreConditionUnsetException) {
} catch (err: any) {
if (err instanceof OakPreConditionUnsetException || err.name === 'OakPreConditionUnsetException') {
this.setMessage(
{
type: 'error',
@ -93,6 +242,23 @@ export default OakComponent({
throw err;
}
}
},
setValueMp(input: WechatMiniprogram.Input) {
const {
detail,
target: { dataset },
} = input;
const { attr } = dataset!;
const { value } = detail;
this.setState({ [attr]: value });
},
async onConfirmMp() {
const { loginName, password, } = this.state
await this.onConfirm(loginName, password);
},
goLoginMp() {
const { goLogin } = this.props;
goLogin && goLogin();
}
},
});

View File

@ -1,4 +1,104 @@
<view class="page-body">
<view class="login-box">
<view class="register-box">
<view class="regitser-title">{{t('registerTitle')}}</view>
<view class="register-body">
<l-form>
<l-form-item
label="{{t('label.loginName')}}"
label-placement="column"
l-form-item-class="formItem"
label-slot="{{true}}"
>
<view slot="label" style="margin:16rpx 0rpx">
<view class="label">
<view style="color:red">*</view>
<view>{{t('label.loginName')}}</view>
</view>
<view class="help">({{loginNameTip}})</view>
</view>
<l-input
value="{{loginName}}"
data-attr="loginName"
rules="{{loginNameRulesMp}}"
bind:lininput="setValueMp"
show-row="{{false}}"
tip-type="text"
hide-label="{{true}}"
show-row="{{false}}"
l-class="my-input"
l-input-class="input"
style="flex:1;"
/>
</l-form-item>
<l-form-item
label="{{t('label.password')}}"
label-placement="column"
l-form-item-class="formItem"
label-slot="{{true}}"
>
<view slot="label" style="margin:16rpx 0rpx">
<view class="label">
<view style="color:red">*</view>
<view>{{t('label.password')}}</view>
</view>
<view class="help">({{pwdTip}})</view>
</view>
<l-input
value="{{password}}"
type="password"
data-attr="password"
rules="{{passwordRulesMp}}"
bind:lininput="setValueMp"
show-row="{{false}}"
tip-type="text"
hide-label="{{true}}"
show-row="{{false}}"
l-class="my-input"
l-input-class="input"
style="flex:1;"
/>
</l-form-item>
<l-form-item
label="{{t('label.rePwd')}}"
label-placement="column"
l-form-item-class="formItem"
label-slot="{{true}}"
>
<view slot="label" style="margin:16rpx 0rpx">
<view class="label">
<view style="color:red">*</view>
<view>{{t('label.rePwd')}}</view>
</view>
</view>
<l-input
value="{{confirm}}"
type="password"
data-attr="confirm"
rules="{{confirmRulesMp}}"
bind:lininput="setValueMp"
show-row="{{false}}"
tip-type="text"
hide-label="{{true}}"
show-row="{{false}}"
l-class="my-input"
l-input-class="input"
style="flex:1;"
/>
</l-form-item>
<l-button
disabled="{{!(loginName && password && confirm) || loginNameHasErr || passwordHasErr || confirmHasErr}}"
catch:lintap="onConfirmMp"
size="long"
l-class="my-btn"
>
{{t('register')}}
</l-button>
</l-form>
</view>
<view class="login">
<l-button catch:lintap="goLoginMp" special="{{true}}">
<view>{{t('goLogin')}}</view>
</l-button>
</view>
</view>
</view>

View File

@ -22,5 +22,6 @@
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
"goLogin": "已有账号?立即登录",
"success": "注册成功,请前往登录页登录"
}

View File

@ -200,7 +200,7 @@ export default function Render(
: 'error'
);
} else {
setValidateHelp2(t('noRepwd'));
setValidateHelp2(t('validator.noRepwd'));
setValidateHelp('');
setValidateStatus('error');
}

View File

@ -664,7 +664,12 @@ const i18ns: I18n[] = [
"Captcha": "输入%{digit}位验证码",
"Email": "请输入邮箱"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"password": "密码登录"
}
}
},
{
@ -689,7 +694,11 @@ const i18ns: I18n[] = [
"otherMethods": "其他登录方式",
"scanLogin": "扫码登录",
"tip": "未注册用户首次登录将自动注册",
"goRegister": "去注册"
"goRegister": "去注册",
"loginMode": {
"wechatMp": "授权登录",
"other": "其他方式登录"
}
}
},
{
@ -706,6 +715,12 @@ const i18ns: I18n[] = [
"Mobile": "手机号",
"Email": "邮箱",
"Password": "请输入密码"
},
"register": "去注册",
"loginMode": {
"wechatMp": "一键登录",
"sms": "短信登录",
"email": "邮箱登录"
}
}
},
@ -722,7 +737,12 @@ const i18ns: I18n[] = [
"Captcha": "输入%{digit}位短信验证码",
"Mobile": "请输入手机号"
},
"resendAfter": "秒后可重发"
"resendAfter": "秒后可重发",
"loginMode": {
"wechatMp": "一键登录",
"password": "密码登录",
"email": "邮箱登录"
}
}
},
{
@ -814,7 +834,8 @@ const i18ns: I18n[] = [
"noRepwd": "请再次确认密码"
},
"register": "立即注册",
"goLogin": "已有账号?立即登录"
"goLogin": "已有账号?立即登录",
"success": "注册成功,请前往登录页登录"
}
},
{