feat: 支持小程序扫码授权登录网页

This commit is contained in:
lxy 2025-11-17 17:44:23 +08:00
parent 169562994b
commit 9751ee5e10
45 changed files with 495 additions and 130 deletions

View File

@ -134,11 +134,13 @@ export type AspectDict<ED extends EntityDict> = {
*
* @param code code
* @param env
* @param wechatLoginId ID
* @returns token
*/
loginWechatMp: ({ code, env, }: {
loginWechatMp: ({ code, env, wechatLoginId, }: {
code: string;
env: WechatMpEnv;
wechatLoginId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<string>;
/**
* APP
@ -297,6 +299,7 @@ export type AspectDict<ED extends EntityDict> = {
createWechatLogin: (params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
}, context: BackendRuntimeContext<ED>) => Promise<string>;
/**
*

View File

@ -77,9 +77,10 @@ export declare function loginWechat<ED extends EntityDict>({ code, env, wechatLo
* @param context
* @returns
*/
export declare function loginWechatMp<ED extends EntityDict>({ code, env, }: {
export declare function loginWechatMp<ED extends EntityDict>({ code, env, wechatLoginId, }: {
code: string;
env: WechatMpEnv;
wechatLoginId?: string;
}, context: BRC<ED>): Promise<string>;
/**
* wx.getUserProfile拿到的用户信息

View File

@ -1949,9 +1949,9 @@ export async function loginWechat({ code, env, wechatLoginId, }, context) {
* @param context
* @returns
*/
export async function loginWechatMp({ code, env, }, context) {
export async function loginWechatMp({ code, env, wechatLoginId, }, context) {
const closeRootMode = context.openRootMode();
const tokenValue = await loginFromWechatEnv(code, env, context);
const tokenValue = await loginFromWechatEnv(code, env, context, wechatLoginId);
await loadTokenInfo(tokenValue, context);
closeRootMode();
return tokenValue;

View File

@ -3,4 +3,5 @@ import { BRC } from '../types/RuntimeCxt';
export declare function createWechatLogin<ED extends EntityDict>(params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
}, context: BRC<ED>): Promise<string>;

View File

@ -1,6 +1,6 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
export async function createWechatLogin(params, context) {
const { type, interval } = params;
const { type, interval, qrCodeType = "wechatPublic" } = params;
let userId;
if (type === 'bind') {
userId = context.getCurrentUserId();
@ -11,7 +11,7 @@ export async function createWechatLogin(params, context) {
type,
expiresAt: Date.now() + interval,
expired: false,
qrCodeType: 'wechatPublic',
qrCodeType,
successed: false,
};
if (userId) {

View File

@ -36,9 +36,10 @@ export default OakComponent({
formData({ data }) {
const passports = data.map((ele) => {
const stateColor = ele.type ? this.features.style.getColor('passport', 'type', ele.type) : '#00BFFF';
let appIdStr;
let appIdStr, hasQrCodePrefix = false;
if (ele.type === 'wechatMpForWeb') {
appIdStr = this.getAppIdStr('wechatMp', ele.config?.appId);
hasQrCodePrefix = this.checkMpQrCodePrefix(ele.config?.appId);
}
else if (ele.type === 'wechatPublicForWeb') {
appIdStr = this.getAppIdStr('wechatPublic', ele.config?.appId);
@ -47,6 +48,7 @@ export default OakComponent({
...ele,
appIdStr,
stateColor,
hasQrCodePrefix,
};
});
return {
@ -277,6 +279,24 @@ export default OakComponent({
}
});
return application?.name ? appId + ' applicationName' + application.name + '' : appId;
},
checkMpQrCodePrefix(appId) {
const systemId = this.features.application.getApplication().systemId;
const [application] = this.features.cache.get('application', {
data: {
id: 1,
config: 1,
},
filter: {
systemId,
config: {
appId,
},
type: 'wechatMp',
}
});
const config = application?.config;
return !!config?.qrCodePrefix;
}
},
});

View File

@ -6,6 +6,7 @@ export default function render(props: WebComponentProps<EntityDict, 'passport',
passports: (EntityDict['passport']['OpSchema'] & {
appIdStr: string;
stateColor: string;
hasQrCodePrefix: boolean;
})[];
systemId: string;
systemName: string;

View File

@ -125,7 +125,7 @@ export default function render(props) {
}, passport.id);
}} updateConfig={updateConfig}/>);
case 'wechatMpForWeb':
return (<WechatMpForWeb key={passport.id} passport={passport} appIdStr={passport?.appIdStr} t={t} changeEnabled={(enabled) => {
return (<WechatMpForWeb key={passport.id} passport={passport} appIdStr={passport?.appIdStr} hasQrCodePrefix={passport?.hasQrCodePrefix} t={t} changeEnabled={(enabled) => {
updateItem({
enabled,
}, passport.id);

View File

@ -6,6 +6,7 @@ export default function wechatMpForWeb(props: {
stateColor: string;
};
appIdStr: string;
hasQrCodePrefix: boolean;
t: (k: string, params?: any) => string;
changeEnabled: (enabled: boolean) => void;
updateConfig: (id: string, config: SmsConfig | EmailConfig | PfwConfig | MfwConfig | PwdConfig | NameConfig | OAuthConfig, path: string, value: any, type?: string) => void;

View File

@ -1,20 +1,20 @@
import React from "react";
import { Switch, Form, Tag, Input } from 'antd';
import { Switch, Form, Tooltip, Tag, Input } from 'antd';
import Styles from './web.module.less';
export default function wechatMpForWeb(props) {
const { passport, appIdStr, t, changeEnabled, updateConfig } = props;
const { passport, appIdStr, hasQrCodePrefix, t, changeEnabled, updateConfig } = props;
const { id, type, enabled, stateColor } = passport;
const config = passport.config || {};
return (<div className={Styles.item}>
<div className={Styles.title}>
<Tag color={stateColor}>{t(`passport:v.type.${type}`)}</Tag>
{/* <Tooltip title={(mpAppIds && mpAppIds.length > 0) ? '' : '如需启用小程序授权登录请先前往应用管理创建小程序application,并完成基础配置'}> */}
<Switch
<Tooltip title={hasQrCodePrefix ? '' : '请先配置小程序普通链接二维码规则'}>
<Switch
// disabled={!(mpAppIds && mpAppIds.length > 0)}
checkedChildren="开启" unCheckedChildren="关闭" checked={enabled} onChange={(checked) => {
changeEnabled(checked);
}}/>
{/* </Tooltip> */}
}} disabled={!hasQrCodePrefix}/>
</Tooltip>
</div>
<Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} style={{ maxWidth: 900, marginTop: 16 }}>
<Form.Item label="appId">

View File

@ -150,6 +150,10 @@ export default function Render(props) {
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state} size={180}/>
{Tip}
</div>}
{loginMode === 'wechatMpForWeb' && <div className={Style['loginbox-qrcode']}>
<WechatLoginQrCodeForPublic type="login" oakPath="$login-wechatLogin/qrCode" oakAutoUnmount={true} url={state} size={180} qrCodeType={'wechatMpDomainUrl'}/>
{Tip}
</div>}
{(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"/>}>

View File

@ -61,7 +61,7 @@
&-qrcode {
padding: 16px 32px;
font-size: 14px;
height: 268px;
min-height: 268px;
&__sociallogin {
text-align: center;

View File

@ -1,3 +1,2 @@
/// <reference types="wechat-miniprogram" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "wechatLogin", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
export default _default;

View File

@ -44,6 +44,15 @@ export default OakComponent({
const redirectUrl = `https%3A%2F%2F${window.location.host}%2FwechatUser%2Flogin%3FwechatLoginId%3D${this.props.oakId}`; //网址转义 `https://${window.location.host}/wechatUser/login?wechatLoginId=${this.props.oakId}`
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
}
},
async loginByWechatMp() {
const { loginUserId } = this.state;
if (!loginUserId) {
// 先小程序登录
await this.features.token.loginWechatMp();
}
await this.features.token.loginWechatMp({ wechatLoginId: this.props.oakId });
this.refresh();
}
},
});

View File

@ -9,6 +9,8 @@
flex-direction: column;
background-color: @oak-bg-color-container;
box-sizing: border-box;
align-items: center;
justify-content: center;
.safe-area-inset-bottom();
}
@ -19,19 +21,6 @@
margin-bottom: 60rpx;
}
.circle-view {
margin-top: 30rpx;
padding: 10rpx;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
}
.title {
font-size: 32rpx;
color: @oak-text-color-primary;
@ -41,4 +30,23 @@
margin-top: 16rpx;
font-size: 28rpx;
color: @oak-text-color-secondary;
}
.circle-view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.text {
font-size: 36rpx;
color: @oak-text-color-primary;
margin-top: 16rpx;
}
.desc {
font-size: 24rpx;
color: @oak-text-color-secondary;
margin-top: 16rpx;
}

View File

@ -1,4 +1,30 @@
<!-- index.wxml -->
<view class="page-body">
绑定小程序尚未实现
<block wx:if="{{expired}}">
<view class="circle-view">
<l-icon name="warning" size="120" />
<text class="text">二维码已过期,请重新扫码</text>
<text class="desc">抱歉,该码已过期</text>
</view>
</block>
<block wx:if="{{type==='login'}}">
<view wx:if="{{successed}}" class="circle-view">
<l-icon name="success" size="120" />
<text class="text">登录成功</text>
</view>
<block wx:else>
<l-button
type="default"
size="long"
disabled="{{oakExecuting || oakLoading}}"
bind:lintap="loginByWechatMp"
style="width:100%"
>
一键登录
</l-button>
</block>
</block>
<view wx:else>
绑定小程序尚未实现
</view>
</view>

View File

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

View File

@ -29,13 +29,15 @@ export default OakComponent({
type: 'bind',
url: '',
size: undefined,
qrCodeType: 'wechatPublic',
},
methods: {
async createWechatLogin() {
const { type = 'bind' } = this.props;
const { type = 'bind', qrCodeType = 'wechatPublic' } = this.props;
const { result: wechatLoginId } = await this.features.cache.exec('createWechatLogin', {
type,
interval: Interval,
qrCodeType,
});
this.setState({
wechatLoginId,
@ -64,7 +66,7 @@ export default OakComponent({
id: 1,
entity: 1,
entityId: 1,
type: 1,
type: 1, //类型
ticket: 1,
url: 1,
buffer: 1,

View File

@ -1,3 +1,2 @@
/// <reference types="wechat-miniprogram" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
export default _default;

View File

@ -6,10 +6,10 @@ export default OakComponent({
},
lifetimes: {
attached() {
if (process.env.OAK_PLATFORM === 'web') {
//处理微信授权登录
this.login();
}
// if (process.env.OAK_PLATFORM === 'web') {
//处理微信授权登录
this.login();
// }
},
},
methods: {

View File

@ -25,7 +25,9 @@ export declare class Token<ED extends EntityDict> extends Feature {
loginWechat(code: string, params?: {
wechatLoginId?: string;
}): Promise<void>;
loginWechatMp(): Promise<void>;
loginWechatMp(params?: {
wechatLoginId?: string;
}): Promise<void>;
loginWechatNative(code: string): Promise<void>;
syncUserInfoWechatMp(): Promise<void>;
logout(dontPublish?: boolean): Promise<void>;

View File

@ -188,12 +188,13 @@ export class Token extends Feature {
this.publish();
this.checkNeedSetPassword();
}
async loginWechatMp() {
async loginWechatMp(params) {
const { code } = await wx.login();
const env = await this.environment.getEnv();
const { result } = await this.cache.exec('loginWechatMp', {
code,
env: env,
wechatLoginId: params?.wechatLoginId,
});
this.tokenValue = result;
await this.storage.save(LOCAL_STORAGE_KEYS.token, result);

View File

@ -21,7 +21,7 @@ const triggers = [
}
},
{
name: 'web applicaiton清空微信网站配置时将相应的applicationPassport删除',
name: 'web applicaiton清空微信网站配置时将相应的passport禁用',
entity: 'application',
action: 'update',
when: 'before',
@ -30,7 +30,7 @@ const triggers = [
return !!data.config;
},
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const { filter, data } = operation;
const applications = await context.select('application', {
data: {
id: 1,
@ -43,9 +43,10 @@ const triggers = [
let count = 0;
for (const application of applications) {
if (application.type === 'web') {
const { wechat } = application.config || {};
const { appId, appSecret } = wechat || {};
if (!(appId && appId !== '' && appSecret && appSecret !== '')) {
// const { wechat } = application.config as WebConfig || {};
// const { appId, appSecret } = wechat || {};
const { appId: newAppId, appSecret: newAppSecret } = data?.config?.wechat || {};
if (!newAppId || !newAppSecret) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -59,14 +60,16 @@ const triggers = [
indexFrom: 0,
}, { forUpdate: true });
if (passport) {
await context.operate('applicationPassport', {
await context.operate('passport', {
id: await generateNewIdAsync(),
action: 'remove',
data: {},
filter: {
passportId: passport.id,
applicationId: application.id,
action: 'update',
data: {
enabled: false,
config: {},
},
filter: {
id: passport.id,
}
}, option);
count++;
}
@ -100,7 +103,7 @@ const triggers = [
for (const application of applications) {
if (application.type === 'wechatPublic') {
const { appId, appSecret, isService } = application.config || {};
if (appId && appId !== '') {
if (appId) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -116,7 +119,7 @@ const triggers = [
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (appSecret && appSecret !== '' && isService) {
if (appSecret && isService) {
if (!passport) {
await context.operate('passport', {
id: await generateNewIdAsync(),
@ -154,7 +157,7 @@ const triggers = [
}
else if (application.type === 'wechatMp') {
const { appId, appSecret, } = application.config || {};
if (appId && appId !== '') {
if (appId) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -169,7 +172,7 @@ const triggers = [
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (appSecret && appSecret !== '') {
if (appSecret) {
if (!passport) {
await context.operate('passport', {
id: await generateNewIdAsync(),
@ -265,6 +268,66 @@ const triggers = [
return count;
}
},
{
name: 'wechatMp applicaiton清空普通链接二维码规则配置时将相应的passport禁用',
entity: 'application',
action: 'update',
when: 'after',
check: (operation) => {
const { data } = operation;
return !!data.config;
},
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const applications = await context.select('application', {
data: {
id: 1,
config: 1,
type: 1,
systemId: 1,
},
filter,
}, {});
let count = 0;
for (const application of applications) {
if (application.type === 'wechatMp') {
const { qrCodePrefix, appId } = application.config || {};
if (appId && !qrCodePrefix) {
const [passport] = await context.select('passport', {
data: {
id: 1,
},
filter: {
enabled: true,
systemId: application.systemId,
type: 'wechatMpForWeb',
config: {
appId,
}
},
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (passport) {
await context.operate('passport', {
id: await generateNewIdAsync(),
action: 'update',
data: {
enabled: false,
config: {},
},
filter: {
id: passport.id,
}
}, option);
count++;
}
}
}
}
return count;
}
},
{
name: '删除application前将相关的passport删除',
entity: 'application',

View File

@ -134,11 +134,13 @@ export type AspectDict<ED extends EntityDict> = {
*
* @param code code
* @param env
* @param wechatLoginId ID
* @returns token
*/
loginWechatMp: ({ code, env, }: {
loginWechatMp: ({ code, env, wechatLoginId, }: {
code: string;
env: WechatMpEnv;
wechatLoginId?: string;
}, context: BackendRuntimeContext<ED>) => Promise<string>;
/**
* APP
@ -297,6 +299,7 @@ export type AspectDict<ED extends EntityDict> = {
createWechatLogin: (params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
}, context: BackendRuntimeContext<ED>) => Promise<string>;
/**
*

View File

@ -77,9 +77,10 @@ export declare function loginWechat<ED extends EntityDict>({ code, env, wechatLo
* @param context
* @returns
*/
export declare function loginWechatMp<ED extends EntityDict>({ code, env, }: {
export declare function loginWechatMp<ED extends EntityDict>({ code, env, wechatLoginId, }: {
code: string;
env: WechatMpEnv;
wechatLoginId?: string;
}, context: BRC<ED>): Promise<string>;
/**
* wx.getUserProfile拿到的用户信息

View File

@ -1975,9 +1975,9 @@ async function loginWechat({ code, env, wechatLoginId, }, context) {
* @param context
* @returns
*/
async function loginWechatMp({ code, env, }, context) {
async function loginWechatMp({ code, env, wechatLoginId, }, context) {
const closeRootMode = context.openRootMode();
const tokenValue = await loginFromWechatEnv(code, env, context);
const tokenValue = await loginFromWechatEnv(code, env, context, wechatLoginId);
await loadTokenInfo(tokenValue, context);
closeRootMode();
return tokenValue;

View File

@ -3,4 +3,5 @@ import { BRC } from '../types/RuntimeCxt';
export declare function createWechatLogin<ED extends EntityDict>(params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
}, context: BRC<ED>): Promise<string>;

View File

@ -1,9 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWechatLogin = void 0;
exports.createWechatLogin = createWechatLogin;
const uuid_1 = require("oak-domain/lib/utils/uuid");
async function createWechatLogin(params, context) {
const { type, interval } = params;
const { type, interval, qrCodeType = "wechatPublic" } = params;
let userId;
if (type === 'bind') {
userId = context.getCurrentUserId();
@ -14,7 +14,7 @@ async function createWechatLogin(params, context) {
type,
expiresAt: Date.now() + interval,
expired: false,
qrCodeType: 'wechatPublic',
qrCodeType,
successed: false,
};
if (userId) {
@ -44,4 +44,3 @@ async function createWechatLogin(params, context) {
}
return id;
}
exports.createWechatLogin = createWechatLogin;

View File

@ -191,12 +191,13 @@ class Token extends Feature_1.Feature {
this.publish();
this.checkNeedSetPassword();
}
async loginWechatMp() {
async loginWechatMp(params) {
const { code } = await wx.login();
const env = await this.environment.getEnv();
const { result } = await this.cache.exec('loginWechatMp', {
code,
env: env,
wechatLoginId: params?.wechatLoginId,
});
this.tokenValue = result;
await this.storage.save(constants_1.LOCAL_STORAGE_KEYS.token, result);

View File

@ -23,7 +23,7 @@ const triggers = [
}
},
{
name: 'web applicaiton清空微信网站配置时将相应的applicationPassport删除',
name: 'web applicaiton清空微信网站配置时将相应的passport禁用',
entity: 'application',
action: 'update',
when: 'before',
@ -32,7 +32,7 @@ const triggers = [
return !!data.config;
},
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const { filter, data } = operation;
const applications = await context.select('application', {
data: {
id: 1,
@ -45,9 +45,10 @@ const triggers = [
let count = 0;
for (const application of applications) {
if (application.type === 'web') {
const { wechat } = application.config || {};
const { appId, appSecret } = wechat || {};
if (!(appId && appId !== '' && appSecret && appSecret !== '')) {
// const { wechat } = application.config as WebConfig || {};
// const { appId, appSecret } = wechat || {};
const { appId: newAppId, appSecret: newAppSecret } = data?.config?.wechat || {};
if (!newAppId || !newAppSecret) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -61,14 +62,16 @@ const triggers = [
indexFrom: 0,
}, { forUpdate: true });
if (passport) {
await context.operate('applicationPassport', {
await context.operate('passport', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'remove',
data: {},
filter: {
passportId: passport.id,
applicationId: application.id,
action: 'update',
data: {
enabled: false,
config: {},
},
filter: {
id: passport.id,
}
}, option);
count++;
}
@ -102,7 +105,7 @@ const triggers = [
for (const application of applications) {
if (application.type === 'wechatPublic') {
const { appId, appSecret, isService } = application.config || {};
if (appId && appId !== '') {
if (appId) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -118,7 +121,7 @@ const triggers = [
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (appSecret && appSecret !== '' && isService) {
if (appSecret && isService) {
if (!passport) {
await context.operate('passport', {
id: await (0, uuid_1.generateNewIdAsync)(),
@ -156,7 +159,7 @@ const triggers = [
}
else if (application.type === 'wechatMp') {
const { appId, appSecret, } = application.config || {};
if (appId && appId !== '') {
if (appId) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -171,7 +174,7 @@ const triggers = [
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (appSecret && appSecret !== '') {
if (appSecret) {
if (!passport) {
await context.operate('passport', {
id: await (0, uuid_1.generateNewIdAsync)(),
@ -267,6 +270,66 @@ const triggers = [
return count;
}
},
{
name: 'wechatMp applicaiton清空普通链接二维码规则配置时将相应的passport禁用',
entity: 'application',
action: 'update',
when: 'after',
check: (operation) => {
const { data } = operation;
return !!data.config;
},
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const applications = await context.select('application', {
data: {
id: 1,
config: 1,
type: 1,
systemId: 1,
},
filter,
}, {});
let count = 0;
for (const application of applications) {
if (application.type === 'wechatMp') {
const { qrCodePrefix, appId } = application.config || {};
if (appId && !qrCodePrefix) {
const [passport] = await context.select('passport', {
data: {
id: 1,
},
filter: {
enabled: true,
systemId: application.systemId,
type: 'wechatMpForWeb',
config: {
appId,
}
},
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (passport) {
await context.operate('passport', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: {
enabled: false,
config: {},
},
filter: {
id: passport.id,
}
}, option);
count++;
}
}
}
}
return count;
}
},
{
name: '删除application前将相关的passport删除',
entity: 'application',

View File

@ -179,15 +179,18 @@ export type AspectDict<ED extends EntityDict> = {
*
* @param code code
* @param env
* @param wechatLoginId ID
* @returns token
*/
loginWechatMp: (
{
code,
env,
wechatLoginId,
}: {
code: string;
env: WechatMpEnv;
wechatLoginId?: string;
},
context: BackendRuntimeContext<ED>
) => Promise<string>;
@ -411,6 +414,7 @@ export type AspectDict<ED extends EntityDict> = {
params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
},
context: BackendRuntimeContext<ED>
) => Promise<string>;

View File

@ -2573,14 +2573,16 @@ export async function loginWechatMp<ED extends EntityDict>(
{
code,
env,
wechatLoginId,
}: {
code: string;
env: WechatMpEnv;
wechatLoginId?: string;
},
context: BRC<ED>
): Promise<string> {
const closeRootMode = context.openRootMode();
const tokenValue = await loginFromWechatEnv<ED>(code, env, context);
const tokenValue = await loginFromWechatEnv<ED>(code, env, context, wechatLoginId);
await loadTokenInfo<ED>(tokenValue, context);
closeRootMode();
return tokenValue;

View File

@ -8,10 +8,11 @@ export async function createWechatLogin<ED extends EntityDict>(
params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
qrCodeType?:EntityDict['wechatLogin']['Schema']['qrCodeType'];
},
context: BRC<ED>
) {
const { type, interval } = params;
const { type, interval, qrCodeType="wechatPublic" } = params;
let userId;
if (type === 'bind') {
userId = context.getCurrentUserId();
@ -22,7 +23,7 @@ export async function createWechatLogin<ED extends EntityDict>(
type,
expiresAt: Date.now() + interval,
expired: false,
qrCodeType: 'wechatPublic',
qrCodeType,
successed: false,
};
if (userId) {

View File

@ -1,6 +1,7 @@
import { cloneDeep, isEqual, set, } from "oak-domain/lib/utils/lodash";
import { EmailConfig, MfwConfig, PfwConfig, SmsConfig, NameConfig, OAuthConfig } from "../../entities/Passport";
import { EntityDict } from "../../oak-app-domain";
import { WechatMpConfig } from "../../entities/Application";
export default OakComponent({
entity: 'passport',
@ -39,9 +40,10 @@ export default OakComponent({
formData({ data }) {
const passports = data.map((ele) => {
const stateColor = ele.type ? this.features.style.getColor('passport', 'type', ele.type) : '#00BFFF';
let appIdStr;
let appIdStr, hasQrCodePrefix = false;
if (ele.type === 'wechatMpForWeb') {
appIdStr = this.getAppIdStr('wechatMp', (ele.config as MfwConfig)?.appId);
hasQrCodePrefix = this.checkMpQrCodePrefix((ele.config as MfwConfig)?.appId);
} else if (ele.type === 'wechatPublicForWeb') {
appIdStr = this.getAppIdStr('wechatPublic', (ele.config as PfwConfig)?.appId);
}
@ -49,6 +51,7 @@ export default OakComponent({
...ele,
appIdStr,
stateColor,
hasQrCodePrefix,
}
})
return {
@ -282,6 +285,26 @@ export default OakComponent({
}
)
return application?.name ? appId + ' applicationName' + application.name + '' : appId
},
checkMpQrCodePrefix(appId: string) {
const systemId = this.features.application.getApplication().systemId;
const [application] = this.features.cache.get('application',
{
data: {
id: 1,
config: 1,
},
filter: {
systemId,
config: {
appId,
},
type: 'wechatMp',
}
}
);
const config = application?.config as WechatMpConfig;
return !!config?.qrCodePrefix;
}
},

View File

@ -47,7 +47,7 @@ export default function render(props: WebComponentProps<
'passport',
true,
{
passports: (EntityDict['passport']['OpSchema'] & { appIdStr: string; stateColor: string })[];
passports: (EntityDict['passport']['OpSchema'] & { appIdStr: string; stateColor: string; hasQrCodePrefix: boolean })[];
systemId: string;
systemName: string;
oauthOptions: {
@ -227,6 +227,7 @@ export default function render(props: WebComponentProps<
key={passport.id}
passport={passport}
appIdStr={passport?.appIdStr}
hasQrCodePrefix={passport?.hasQrCodePrefix}
t={t}
changeEnabled={(enabled) => {
updateItem({

View File

@ -7,11 +7,12 @@ import Styles from './web.module.less';
export default function wechatMpForWeb(props: {
passport: EntityDict['passport']['OpSchema'] & { stateColor: string };
appIdStr: string;
hasQrCodePrefix: boolean;
t: (k: string, params?: any) => string;
changeEnabled: (enabled: boolean) => void;
updateConfig: (id: string, config: SmsConfig | EmailConfig | PfwConfig | MfwConfig | PwdConfig | NameConfig | OAuthConfig, path: string, value: any, type?: string) => void;
}) {
const { passport, appIdStr, t, changeEnabled, updateConfig } = props;
const { passport, appIdStr, hasQrCodePrefix, t, changeEnabled, updateConfig } = props;
const { id, type, enabled, stateColor } = passport;
const config = passport.config as MfwConfig || {};
@ -19,17 +20,18 @@ export default function wechatMpForWeb(props: {
<div className={Styles.item}>
<div className={Styles.title}>
<Tag color={stateColor}>{t(`passport:v.type.${type}`)}</Tag>
{/* <Tooltip title={(mpAppIds && mpAppIds.length > 0) ? '' : '如需启用小程序授权登录请先前往应用管理创建小程序application,并完成基础配置'}> */}
<Switch
// disabled={!(mpAppIds && mpAppIds.length > 0)}
checkedChildren="开启"
unCheckedChildren="关闭"
checked={enabled}
onChange={(checked) => {
changeEnabled(checked);
}}
/>
{/* </Tooltip> */}
<Tooltip title={hasQrCodePrefix ? '' : '请先配置小程序普通链接二维码规则'}>
<Switch
// disabled={!(mpAppIds && mpAppIds.length > 0)}
checkedChildren="开启"
unCheckedChildren="关闭"
checked={enabled}
onChange={(checked) => {
changeEnabled(checked);
}}
disabled={!hasQrCodePrefix}
/>
</Tooltip>
</div>
<Form
labelCol={{ span: 4 }}

View File

@ -61,7 +61,7 @@
&-qrcode {
padding: 16px 32px;
font-size: 14px;
height: 268px;
min-height: 268px;
&__sociallogin {
text-align: center;

View File

@ -335,6 +335,19 @@ export default function Render(
/>
{Tip}
</div>}
{loginMode === 'wechatMpForWeb' && <div
className={Style['loginbox-qrcode']}
>
<WechatLoginQrCodeForPublic
type="login"
oakPath="$login-wechatLogin/qrCode"
oakAutoUnmount={true}
url={state}
size={180}
qrCodeType={'wechatMpDomainUrl'}
/>
{Tip}
</div>}
{(inputOptions?.length > 0 || oauthOptions?.length > 0) ? (
<div className={Style['loginbox-methods']}>
<Divider plain style={{ fontSize: 13, color: '#808080', }}>{t('otherMethods')}</Divider>

View File

@ -9,6 +9,8 @@
flex-direction: column;
background-color: @oak-bg-color-container;
box-sizing: border-box;
align-items: center;
justify-content: center;
.safe-area-inset-bottom();
}
@ -19,19 +21,6 @@
margin-bottom: 60rpx;
}
.circle-view {
margin-top: 30rpx;
padding: 10rpx;
width: 200rpx;
height: 200rpx;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
}
.title {
font-size: 32rpx;
color: @oak-text-color-primary;
@ -41,4 +30,23 @@
margin-top: 16rpx;
font-size: 28rpx;
color: @oak-text-color-secondary;
}
.circle-view {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.text {
font-size: 36rpx;
color: @oak-text-color-primary;
margin-top: 16rpx;
}
.desc {
font-size: 24rpx;
color: @oak-text-color-secondary;
margin-top: 16rpx;
}

View File

@ -49,6 +49,16 @@ export default OakComponent({
const redirectUrl = `https%3A%2F%2F${window.location.host}%2FwechatUser%2Flogin%3FwechatLoginId%3D${this.props.oakId}`; //网址转义 `https://${window.location.host}/wechatUser/login?wechatLoginId=${this.props.oakId}`
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirectUrl}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`
}
},
async loginByWechatMp() {
const {loginUserId} = this.state;
if(!loginUserId){
// 先小程序登录
await this.features.token.loginWechatMp();
}
await this.features.token.loginWechatMp({ wechatLoginId: this.props.oakId });
this.refresh();
}
},
});

View File

@ -1,4 +1,30 @@
<!-- index.wxml -->
<view class="page-body">
绑定小程序尚未实现
<block wx:if="{{expired}}">
<view class="circle-view">
<l-icon name="warning" size="120" />
<text class="text">二维码已过期,请重新扫码</text>
<text class="desc">抱歉,该码已过期</text>
</view>
</block>
<block wx:if="{{type==='login'}}">
<view wx:if="{{successed}}" class="circle-view">
<l-icon name="success" size="120" />
<text class="text">登录成功</text>
</view>
<block wx:else>
<l-button
type="default"
size="long"
disabled="{{oakExecuting || oakLoading}}"
bind:lintap="loginByWechatMp"
style="width:100%"
>
一键登录
</l-button>
</block>
</block>
<view wx:else>
绑定小程序尚未实现
</view>
</view>

View File

@ -32,15 +32,17 @@ export default OakComponent({
type: 'bind' as EntityDict['wechatLogin']['Schema']['type'],
url: '',
size: undefined,
qrCodeType: 'wechatPublic' as EntityDict['wechatLogin']['Schema']['qrCodeType'],
},
methods: {
async createWechatLogin() {
const { type = 'bind' } = this.props;
const { type = 'bind', qrCodeType = 'wechatPublic' } = this.props;
const { result: wechatLoginId } = await this.features.cache.exec(
'createWechatLogin',
{
type,
interval: Interval,
qrCodeType,
}
);
this.setState(

View File

@ -7,10 +7,10 @@ export default OakComponent({
},
lifetimes: {
attached() {
if (process.env.OAK_PLATFORM === 'web') {
// if (process.env.OAK_PLATFORM === 'web') {
//处理微信授权登录
this.login();
}
// }
},
},
methods: {

View File

@ -280,13 +280,14 @@ export class Token<ED extends EntityDict> extends Feature {
this.checkNeedSetPassword();
}
async loginWechatMp() {
async loginWechatMp(params?: { wechatLoginId?: string }) {
const { code } = await wx.login();
const env = await this.environment.getEnv();
const { result } = await this.cache.exec('loginWechatMp', {
code,
env: env as WechatMpEnv,
wechatLoginId: params?.wechatLoginId,
});
this.tokenValue = result;
await this.storage.save(LOCAL_STORAGE_KEYS.token, result);

View File

@ -30,7 +30,7 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
}
} as RemoveTrigger<EntityDict, 'application', BRC<EntityDict>>,
{
name: 'web applicaiton清空微信网站配置时将相应的applicationPassport删除',
name: 'web applicaiton清空微信网站配置时将相应的passport禁用',
entity: 'application',
action: 'update',
when: 'before',
@ -39,7 +39,7 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
return !!data.config;
},
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const { filter, data } = operation;
const applications = await context.select('application', {
data: {
id: 1,
@ -52,9 +52,10 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
let count = 0;
for (const application of applications) {
if (application.type === 'web') {
const { wechat } = application.config as WebConfig || {};
const { appId, appSecret } = wechat || {};
if (!(appId && appId !== '' && appSecret && appSecret !== '')) {
// const { wechat } = application.config as WebConfig || {};
// const { appId, appSecret } = wechat || {};
const { appId: newAppId, appSecret: newAppSecret } = (data?.config as WebConfig)?.wechat || {};
if (!newAppId || !newAppSecret) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -68,14 +69,16 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
indexFrom: 0,
}, { forUpdate: true });
if (passport) {
await context.operate('applicationPassport', {
await context.operate('passport', {
id: await generateNewIdAsync(),
action: 'remove',
data: {},
filter: {
passportId: passport.id,
applicationId: application.id,
action: 'update',
data: {
enabled: false,
config: {},
},
filter: {
id: passport.id,
}
}, option);
count++;
}
@ -109,7 +112,7 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
for (const application of applications) {
if (application.type === 'wechatPublic') {
const { appId, appSecret, isService } = application.config as WechatPublicConfig || {};
if (appId && appId !== '') {
if (appId) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -125,7 +128,7 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (appSecret && appSecret !== '' && isService) {
if (appSecret && isService) {
if (!passport) {
await context.operate('passport', {
id: await generateNewIdAsync(),
@ -161,7 +164,7 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
}
} else if (application.type === 'wechatMp') {
const { appId, appSecret, } = application.config as WechatMpConfig || {};
if (appId && appId !== '') {
if (appId) {
const [passport] = await context.select('passport', {
data: {
id: 1,
@ -176,7 +179,7 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (appSecret && appSecret !== '') {
if (appSecret) {
if (!passport) {
await context.operate('passport', {
id: await generateNewIdAsync(),
@ -273,6 +276,66 @@ const triggers: Trigger<EntityDict, 'application', BRC<EntityDict>>[] = [
return count;
}
},
{
name: 'wechatMp applicaiton清空普通链接二维码规则配置时将相应的passport禁用',
entity: 'application',
action: 'update',
when: 'after',
check: (operation) => {
const { data } = operation as EntityDict['application']['Update'];
return !!data.config;
},
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const applications = await context.select('application', {
data: {
id: 1,
config: 1,
type: 1,
systemId: 1,
},
filter,
}, {});
let count = 0;
for (const application of applications) {
if (application.type === 'wechatMp') {
const { qrCodePrefix, appId } = application.config as WechatMpConfig || {};
if (appId && !qrCodePrefix) {
const [passport] = await context.select('passport', {
data: {
id: 1,
},
filter: {
enabled: true,
systemId: application.systemId!,
type: 'wechatMpForWeb',
config: {
appId,
}
},
count: 1,
indexFrom: 0,
}, { forUpdate: true });
if (passport) {
await context.operate('passport', {
id: await generateNewIdAsync(),
action: 'update',
data: {
enabled: false,
config: {},
},
filter: {
id: passport.id,
}
}, option);
count++;
}
}
}
}
return count;
}
},
{
name: '删除application前将相关的passport删除',
entity: 'application',