Compare commits
122 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
11f978b24c | |
|
|
b8654da5fb | |
|
|
3fae4dd855 | |
|
|
8660c6a1e3 | |
|
|
7312545eda | |
|
|
cf63a53109 | |
|
|
9027a40479 | |
|
|
5c3a02aa95 | |
|
|
b1f8300bbb | |
|
|
10b4e8dbd8 | |
|
|
0ca7f1b020 | |
|
|
0315b11ae2 | |
|
|
98ad339316 | |
|
|
b6a118efc5 | |
|
|
7862282214 | |
|
|
79a98e2d8f | |
|
|
e92f26f50c | |
|
|
9c5fb29259 | |
|
|
9025b7d8b4 | |
|
|
44288172f5 | |
|
|
570f6e8df5 | |
|
|
337782a54b | |
|
|
f585d154ea | |
|
|
fdf948d3ee | |
|
|
14d593cc89 | |
|
|
3fa730cfb1 | |
|
|
ab9789eb4c | |
|
|
2871934bcd | |
|
|
df9855906d | |
|
|
066d834d8d | |
|
|
a52a2874c6 | |
|
|
5543806a63 | |
|
|
f3d782fcbc | |
|
|
95b506b0eb | |
|
|
56b07dc4cb | |
|
|
147dc5eb12 | |
|
|
347e1ab360 | |
|
|
336f0b1aa1 | |
|
|
10077f150e | |
|
|
d5a09546dd | |
|
|
ffcac7f843 | |
|
|
4227430080 | |
|
|
1e5697a28e | |
|
|
a3ec5fc808 | |
|
|
ba434da707 | |
|
|
0e05a3ad4e | |
|
|
7c35f695ab | |
|
|
35f8bfcc0b | |
|
|
40d69bf0f4 | |
|
|
d347e19ede | |
|
|
97fd741a3b | |
|
|
6aff242687 | |
|
|
257f7669fc | |
|
|
edbeb63cc1 | |
|
|
5aa78b89b4 | |
|
|
7c2fba3191 | |
|
|
ab6616526d | |
|
|
67cf576ddc | |
|
|
ae1d1cf70a | |
|
|
21348fff43 | |
|
|
157dbfd88a | |
|
|
660b25a7d8 | |
|
|
fbfeb210bd | |
|
|
a5f7af8ec1 | |
|
|
1b61055ad4 | |
|
|
4e555c90e2 | |
|
|
1b90173e16 | |
|
|
365900b750 | |
|
|
f2ada8fbba | |
|
|
4b30f6c838 | |
|
|
8c8717433c | |
|
|
ef871317a6 | |
|
|
16eb5de960 | |
|
|
24396a8782 | |
|
|
5af3a8f9db | |
|
|
af88199ba7 | |
|
|
78eb039a04 | |
|
|
5ff8f1ed53 | |
|
|
5fbc76c93f | |
|
|
f57dacfe5d | |
|
|
2ed82709b8 | |
|
|
27687c5b2f | |
|
|
799246c21b | |
|
|
3cf3b4da09 | |
|
|
d8d78edde1 | |
|
|
ee57c1e932 | |
|
|
20708057da | |
|
|
696278927a | |
|
|
6a49d6f5af | |
|
|
68b3f93e9f | |
|
|
3995ff52f8 | |
|
|
d34c91bf07 | |
|
|
e8b3a5878e | |
|
|
b9783aa588 | |
|
|
3aec26e06f | |
|
|
7cb2479eab | |
|
|
ca958c60f0 | |
|
|
20030cc9ad | |
|
|
67341a64df | |
|
|
b839318c5c | |
|
|
acf99448a5 | |
|
|
63dca8d683 | |
|
|
126d43a90b | |
|
|
aeb3dee4c2 | |
|
|
e89661326e | |
|
|
818504690b | |
|
|
9751ee5e10 | |
|
|
169562994b | |
|
|
a1ed3c7ca7 | |
|
|
3f690e6725 | |
|
|
d7d302f329 | |
|
|
b93ae46894 | |
|
|
5b702a0d7d | |
|
|
5fb1988b0d | |
|
|
f26eb01807 | |
|
|
62dd001be3 | |
|
|
20cc101f88 | |
|
|
ccdd86923f | |
|
|
cd1bf2ce21 | |
|
|
e2f9a49d76 | |
|
|
5f0c366135 | |
|
|
ca06b24276 |
|
|
@ -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 登录
|
||||
|
|
@ -292,11 +294,14 @@ export type AspectDict<ED extends EntityDict> = {
|
|||
* 创建微信登录会话(用于扫码登录场景)
|
||||
* @param type 登录类型(login-登录,bind-绑定)
|
||||
* @param interval 会话有效期(毫秒)
|
||||
* @param router 扫码目标路由
|
||||
* @returns 返回登录会话 ID
|
||||
*/
|
||||
createWechatLogin: (params: {
|
||||
type: EntityDict['wechatLogin']['Schema']['type'];
|
||||
interval: number;
|
||||
router: EntityDict['wechatLogin']['Schema']['router'];
|
||||
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<string>;
|
||||
/**
|
||||
* 解绑微信用户
|
||||
|
|
@ -538,7 +543,7 @@ export type AspectDict<ED extends EntityDict> = {
|
|||
* @param applicationId 应用 ID
|
||||
* @returns 返回同步结果
|
||||
*/
|
||||
syncMessageTemplate: (params: {
|
||||
syncWechatTemplate: (params: {
|
||||
applicationId: string;
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<any>;
|
||||
/**
|
||||
|
|
@ -653,6 +658,8 @@ export type AspectDict<ED extends EntityDict> = {
|
|||
syncSmsTemplate: (params: {
|
||||
systemId: string;
|
||||
origin: EntityDict['smsTemplate']['Schema']['origin'];
|
||||
pageIndex?: number;
|
||||
pageSize?: number;
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<void>;
|
||||
/**
|
||||
* 获取应用的登录方式配置列表
|
||||
|
|
@ -727,5 +734,56 @@ export type AspectDict<ED extends EntityDict> = {
|
|||
data: EntityDict['oauthApplication']['Schema'] | null;
|
||||
alreadyAuth: boolean;
|
||||
}>;
|
||||
/**
|
||||
* 更新用户头像为微信头像
|
||||
* @param avatar 微信头像临时url
|
||||
* @returns
|
||||
*/
|
||||
setUserAvatarFromWechat: (params: {
|
||||
avatar: string;
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<void>;
|
||||
/**
|
||||
* 合并分片上传的文件
|
||||
* @param extraFileId extraFile的id
|
||||
*/
|
||||
mergeChunkedUpload: (params: {
|
||||
extraFileId: string;
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<void>;
|
||||
/**
|
||||
* 对文件进行预签名得到请求地址等信息
|
||||
* @param params 包含文件信息、请求方式等
|
||||
*/
|
||||
presignFile: (params: {
|
||||
extraDileId: string;
|
||||
method?: 'GET' | 'PUT' | 'POST' | 'DELETE';
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<{
|
||||
url: string;
|
||||
headers?: Record<string, string | string[]>;
|
||||
formdata?: Record<string, any>;
|
||||
}>;
|
||||
/**
|
||||
* 批量预签名需要上传的文件
|
||||
* @param params 包含文件信息, 分片范围等
|
||||
*/
|
||||
presignMultiPartUpload: (params: {
|
||||
extraFileId: string;
|
||||
from: number;
|
||||
to: number;
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<{
|
||||
partNumber: number;
|
||||
uploadUrl: string;
|
||||
formData: Record<string, any>;
|
||||
}[]>;
|
||||
/**
|
||||
* 用户账号注册
|
||||
* @param loginName 账号
|
||||
* @param password 密码
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
registerUserByLoginName: (params: {
|
||||
loginName: string;
|
||||
password: string;
|
||||
}, context: BackendRuntimeContext<ED>) => Promise<void>;
|
||||
};
|
||||
export default AspectDict;
|
||||
|
|
|
|||
|
|
@ -12,9 +12,15 @@ export async function getApplicationPassports(params, context) {
|
|||
config: 1,
|
||||
},
|
||||
isDefault: 1,
|
||||
allowPwd: 1,
|
||||
},
|
||||
filter: {
|
||||
applicationId,
|
||||
passport: {
|
||||
type: {
|
||||
$ne: 'password',
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {});
|
||||
closeRoot();
|
||||
|
|
|
|||
|
|
@ -16,3 +16,26 @@ export declare function uploadExtraFile<ED extends EntityDict>(params: {
|
|||
context: BRC<ED>): Promise<{
|
||||
success: boolean;
|
||||
}>;
|
||||
/**
|
||||
* 合并分片上传的文件
|
||||
*/
|
||||
export declare function mergeChunkedUpload<ED extends EntityDict>(params: {
|
||||
extraFileId: string;
|
||||
}, context: BRC<ED>): Promise<void>;
|
||||
export declare function presignFile<ED extends EntityDict>(params: {
|
||||
extraFileId: string;
|
||||
method?: 'GET' | 'PUT' | 'POST' | 'DELETE';
|
||||
}, context: BRC<ED>): Promise<{
|
||||
url: string;
|
||||
headers?: Record<string, string | string[]>;
|
||||
formdata?: Record<string, any>;
|
||||
}>;
|
||||
export declare function presignMultiPartUpload<ED extends EntityDict>(params: {
|
||||
extraFileId: string;
|
||||
from: number;
|
||||
to: number;
|
||||
}, context: BRC<ED>): Promise<{
|
||||
partNumber: number;
|
||||
uploadUrl: string;
|
||||
formData?: Record<string, any>;
|
||||
}[]>;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
|||
import fs from 'fs';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
|
||||
import { applicationProjection } from '../types/Projection';
|
||||
import { applicationProjection, extraFileProjection } from '../types/Projection';
|
||||
import { getCosBackend } from '../utils/cos/index.backend';
|
||||
// 请求链接获取标题,发布时间,图片等信息
|
||||
export async function getInfoByUrl(params) {
|
||||
const { url } = params;
|
||||
|
|
@ -49,3 +50,99 @@ context) {
|
|||
success: true,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 合并分片上传的文件
|
||||
*/
|
||||
export async function mergeChunkedUpload(params, context) {
|
||||
const { extraFileId } = params;
|
||||
assert(extraFileId, 'extraFileId不能为空');
|
||||
const [extrafile] = await context.select('extraFile', {
|
||||
data: {
|
||||
...extraFileProjection,
|
||||
application: {
|
||||
...applicationProjection,
|
||||
},
|
||||
enableChunkedUpload: 1,
|
||||
chunkInfo: 1,
|
||||
},
|
||||
filter: {
|
||||
id: extraFileId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
assert(extrafile, `找不到id为${extraFileId}的extraFile记录`);
|
||||
assert(extrafile.enableChunkedUpload, `extraFile ${extraFileId} 未启用分片上传功能`);
|
||||
assert(extrafile.chunkInfo, `extraFile ${extraFileId} 的chunkInfo信息缺失`);
|
||||
assert(!extrafile.chunkInfo.merged, `extraFile ${extraFileId} 已经合并过分片,无需重复合并`);
|
||||
// 必须保证所有分片都有上传完成
|
||||
const cos = getCosBackend(extrafile.origin);
|
||||
const { parts } = await cos.listMultipartUploads(extrafile.application, extrafile, context);
|
||||
const allPartsDone = parts.every(part => part.etag && part.size > 0);
|
||||
assert(allPartsDone, `extraFile ${extraFileId} 存在未上传完成的分片,无法合并`);
|
||||
await cos.mergeChunkedUpload(extrafile.application, extrafile, parts.map(part => ({
|
||||
partNumber: part.partNumber,
|
||||
etag: part.etag,
|
||||
})), context);
|
||||
// 更新chunkInfo状态
|
||||
const closeRootMode = context.openRootMode();
|
||||
try {
|
||||
await context.operate('extraFile', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
data: {
|
||||
chunkInfo: {
|
||||
...extrafile.chunkInfo,
|
||||
merged: true,
|
||||
parts: parts.map(part => part.etag),
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: extraFileId,
|
||||
},
|
||||
}, {});
|
||||
closeRootMode();
|
||||
}
|
||||
catch (err) {
|
||||
closeRootMode();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
export async function presignFile(params, context) {
|
||||
const { extraFileId, method = 'GET' } = params;
|
||||
assert(extraFileId, 'extraFileId不能为空');
|
||||
const [extrafile] = await context.select('extraFile', {
|
||||
data: {
|
||||
...extraFileProjection,
|
||||
application: {
|
||||
...applicationProjection,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
id: extraFileId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
assert(extrafile, `找不到id为${extraFileId}的extraFile记录`);
|
||||
const cos = getCosBackend(extrafile.origin);
|
||||
return await cos.presignFile(method, extrafile.application, extrafile, context);
|
||||
}
|
||||
export async function presignMultiPartUpload(params, context) {
|
||||
const { extraFileId, from, to } = params;
|
||||
assert(extraFileId, 'extraFileId不能为空');
|
||||
assert(from >= 1, 'from必须大于等于1');
|
||||
assert(to >= from, 'to必须大于等于from');
|
||||
const [extrafile] = await context.select('extraFile', {
|
||||
data: {
|
||||
...extraFileProjection,
|
||||
application: {
|
||||
...applicationProjection,
|
||||
},
|
||||
chunkInfo: 1,
|
||||
enableChunkedUpload: 1,
|
||||
},
|
||||
filter: {
|
||||
id: extraFileId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
assert(extrafile, `找不到id为${extraFileId}的extraFile记录`);
|
||||
const cos = getCosBackend(extrafile.origin);
|
||||
return cos.presignMultiPartUpload(extrafile.application, extrafile, from, to, context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, loginWechatNative, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken, verifyPassword, loginWebByMpToken } from './token';
|
||||
import { getInfoByUrl } from './extraFile';
|
||||
import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, loginWechatNative, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken, verifyPassword, loginWebByMpToken, setUserAvatarFromWechat } from './token';
|
||||
import { getInfoByUrl, mergeChunkedUpload, presignFile, presignMultiPartUpload } from './extraFile';
|
||||
import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, getArticle, batchGetMaterialList, getMaterial, deleteMaterial } from './application';
|
||||
import { updateConfig, updateApplicationConfig, updateStyle } from './config';
|
||||
import { syncMessageTemplate, getMessageType } from './template';
|
||||
import { syncWechatTemplate, 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';
|
||||
|
|
@ -63,7 +63,7 @@ declare const aspectDict: {
|
|||
getTags: typeof getTags;
|
||||
editTag: typeof editTag;
|
||||
deleteTag: typeof deleteTag;
|
||||
syncMessageTemplate: typeof syncMessageTemplate;
|
||||
syncWechatTemplate: typeof syncWechatTemplate;
|
||||
getMessageType: typeof getMessageType;
|
||||
syncTag: typeof syncTag;
|
||||
oneKeySync: typeof oneKeySync;
|
||||
|
|
@ -85,6 +85,11 @@ declare const aspectDict: {
|
|||
getOAuthClientInfo: typeof getOAuthClientInfo;
|
||||
createOAuthState: typeof createOAuthState;
|
||||
authorize: typeof authorize;
|
||||
setUserAvatarFromWechat: typeof setUserAvatarFromWechat;
|
||||
mergeChunkedUpload: typeof mergeChunkedUpload;
|
||||
presignFile: typeof presignFile;
|
||||
presignMultiPartUpload: typeof presignMultiPartUpload;
|
||||
registerUserByLoginName: typeof registerUserByLoginName;
|
||||
};
|
||||
export default aspectDict;
|
||||
export { AspectDict } from './AspectDict';
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, loginWechatNative, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken, verifyPassword, loginWebByMpToken, } from './token';
|
||||
import { getInfoByUrl } from './extraFile';
|
||||
import { bindByEmail, bindByMobile, loginByAccount, loginByEmail, loginByMobile, loginWechat, loginWechatMp, loginWechatNative, syncUserInfoWechatMp, sendCaptchaByMobile, sendCaptchaByEmail, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, refreshToken, verifyPassword, loginWebByMpToken, setUserAvatarFromWechat, } from './token';
|
||||
import { getInfoByUrl, mergeChunkedUpload, presignFile, presignMultiPartUpload } from './extraFile';
|
||||
import { getApplication, signatureJsSDK, uploadWechatMedia, batchGetArticle, getArticle, batchGetMaterialList, getMaterial, deleteMaterial, } from './application';
|
||||
import { updateConfig, updateApplicationConfig, updateStyle } from './config';
|
||||
import { syncMessageTemplate, getMessageType } from './template';
|
||||
import { syncWechatTemplate, 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';
|
||||
|
|
@ -63,7 +63,7 @@ const aspectDict = {
|
|||
getTags,
|
||||
editTag,
|
||||
deleteTag,
|
||||
syncMessageTemplate,
|
||||
syncWechatTemplate,
|
||||
getMessageType,
|
||||
syncTag,
|
||||
oneKeySync,
|
||||
|
|
@ -86,5 +86,11 @@ const aspectDict = {
|
|||
getOAuthClientInfo,
|
||||
createOAuthState,
|
||||
authorize,
|
||||
setUserAvatarFromWechat,
|
||||
// extraFile新增
|
||||
mergeChunkedUpload,
|
||||
presignFile,
|
||||
presignMultiPartUpload,
|
||||
registerUserByLoginName,
|
||||
};
|
||||
export default aspectDict;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export async function loginByOauth(params, context) {
|
|||
// 验证 state 并获取 OAuth 配置
|
||||
const [state] = await context.select("oauthState", {
|
||||
data: {
|
||||
providerId: 1,
|
||||
provider: {
|
||||
type: 1,
|
||||
clientId: 1,
|
||||
|
|
@ -31,7 +32,32 @@ export async function loginByOauth(params, context) {
|
|||
filter: {
|
||||
state: stateCode,
|
||||
},
|
||||
}, { dontCollect: true, forUpdate: true }); // 这里直接加锁,防止其他人抢了
|
||||
const systemId = context.getSystemId();
|
||||
const [applicationPassport] = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
config: 1,
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
type: 'oauth',
|
||||
},
|
||||
applicationId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
const allowOauth = !!(state.providerId && applicationPassport?.passport?.config?.oauthIds && applicationPassport?.passport?.config)?.oauthIds.includes(state.providerId);
|
||||
if (!allowOauth) {
|
||||
throw new OakUserException('error::user.loginWayDisabled');
|
||||
}
|
||||
assert(state, '无效的 state 参数');
|
||||
assert(state.provider?.ableState && state.provider?.ableState === 'enabled', '该 OAuth 提供商已被禁用');
|
||||
// 如果已经使用
|
||||
|
|
@ -70,7 +96,7 @@ export async function loginByOauth(params, context) {
|
|||
providerUserId: oauthUserInfo.providerUserId,
|
||||
providerConfigId: state.providerId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
}, { dontCollect: true, forUpdate: true }); // 加锁,防止并发绑定
|
||||
// 已登录的情况
|
||||
if (islogginedIn) {
|
||||
// 检查当前用户是否已绑定此提供商
|
||||
|
|
@ -329,7 +355,7 @@ export async function authorize(params, context) {
|
|||
applicationId: context.getApplicationId(),
|
||||
userId: context.getCurrentUserId(),
|
||||
scope: scope === undefined ? [] : [scope],
|
||||
expiresAt: Date.now() + 10 * 60 * 1000,
|
||||
expiresAt: Date.now() + 10 * 60 * 1000, // 10分钟后过期
|
||||
// PKCE 支持
|
||||
codeChallenge: code_challenge,
|
||||
codeChallengeMethod: code_challenge_method || 'plain',
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ export async function createSession(params, context) {
|
|||
aaoe: false,
|
||||
extra: data,
|
||||
userId,
|
||||
id: await generateNewIdAsync(),
|
||||
};
|
||||
if (MsgType === 'text') {
|
||||
Object.assign(sessionMessage, {
|
||||
|
|
@ -103,7 +104,7 @@ export async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'image',
|
||||
tag1: 'image',
|
||||
objectId: await generateNewIdAsync(),
|
||||
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
@ -128,7 +129,7 @@ export async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'video',
|
||||
tag1: 'video',
|
||||
objectId: await generateNewIdAsync(),
|
||||
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
@ -150,7 +151,7 @@ export async function createSession(params, context) {
|
|||
origin: 'wechat',
|
||||
type: 'audio',
|
||||
tag1: 'audio',
|
||||
objectId: await generateNewIdAsync(),
|
||||
objectId: await generateNewIdAsync(), // 这个域用来标识唯一性
|
||||
sort: 1000,
|
||||
uploadState: 'success',
|
||||
extra1: data.MediaId,
|
||||
|
|
|
|||
|
|
@ -3,4 +3,6 @@ import { BRC } from '../types/RuntimeCxt';
|
|||
export declare function syncSmsTemplate<ED extends EntityDict>(params: {
|
||||
origin: EntityDict['smsTemplate']['Schema']['origin'];
|
||||
systemId: string;
|
||||
pageIndex?: number;
|
||||
pageSize?: number;
|
||||
}, context: BRC<ED>): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
import { getSms } from '../utils/sms/index';
|
||||
export async function syncSmsTemplate(params, context) {
|
||||
const { origin, systemId } = params;
|
||||
const { origin, systemId, pageIndex, pageSize } = params;
|
||||
const Sms = getSms(origin);
|
||||
const templateFormalData = await Sms.syncTemplate(systemId, context);
|
||||
const templateFormalData = await Sms.syncTemplate({
|
||||
systemId: systemId,
|
||||
pageIndex,
|
||||
pageSize
|
||||
}, context);
|
||||
const existTemplateList = await context.select('smsTemplate', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { EntityDict } from '../oak-app-domain';
|
|||
import { BRC } from '../types/RuntimeCxt';
|
||||
export declare function registerMessageType(messageType: string[]): void;
|
||||
export declare function getMessageType(): Promise<string[]>;
|
||||
export declare function syncMessageTemplate<ED extends EntityDict>(params: {
|
||||
export declare function syncWechatTemplate<ED extends EntityDict>(params: {
|
||||
applicationId: string;
|
||||
}, context: BRC<ED>): Promise<{
|
||||
wechatId: string;
|
||||
|
|
@ -19,6 +19,6 @@ export declare function syncMessageTemplate<ED extends EntityDict>(params: {
|
|||
example: string;
|
||||
keywordEnumValueList: {
|
||||
keywordCode: string;
|
||||
enumValueList: string[];
|
||||
enumValueList: Array<string>;
|
||||
}[];
|
||||
}[]>;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function analyseContent(content) {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
export async function syncMessageTemplate(params, context) {
|
||||
export async function syncWechatTemplate(params, context) {
|
||||
const applicationId = params?.applicationId;
|
||||
const [application] = await context.select('application', {
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -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拿到的用户信息
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
|||
import WechatSDK from 'oak-external-sdk/lib/WechatSDK';
|
||||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import { OakPreConditionUnsetException, OakRowInconsistencyException, OakUnloggedInException, OakUserException, OakOperationUnpermittedException, } from 'oak-domain/lib/types';
|
||||
import { composeFileUrl } from '../utils/cos/index.backend';
|
||||
import { composeFileUrlBackend } from '../utils/cos/index.backend';
|
||||
import { OakChangeLoginWayException, OakDistinguishUserException, OakIncompleteConfig, OakUserDisabledException, } from '../types/Exception';
|
||||
import { encryptPasswordSha1 } from '../utils/password';
|
||||
import { tokenProjection } from '../types/Projection';
|
||||
|
|
@ -536,23 +536,9 @@ export async function loginByMobile(params, context) {
|
|||
}
|
||||
export async function verifyPassword(params, context) {
|
||||
const { password } = params;
|
||||
const systemId = context.getSystemId();
|
||||
const [pwdPassport] = await context.select('passport', {
|
||||
data: {
|
||||
id: 1,
|
||||
systemId: 1,
|
||||
config: 1,
|
||||
type: 1,
|
||||
enabled: 1,
|
||||
},
|
||||
filter: {
|
||||
systemId,
|
||||
enabled: true,
|
||||
type: 'password',
|
||||
}
|
||||
}, { forUpdate: true });
|
||||
// assert(pwdPassport);
|
||||
const pwdMode = pwdPassport?.config?.mode ?? 'all';
|
||||
const { system } = context.getApplication();
|
||||
const pwdConfig = system?.config.Password;
|
||||
const pwdMode = pwdConfig?.mode ?? 'all';
|
||||
let pwdFilter = {};
|
||||
if (pwdMode === 'all') {
|
||||
pwdFilter = {
|
||||
|
|
@ -606,9 +592,40 @@ export async function loginByAccount(params, context) {
|
|||
const applicationId = context.getApplicationId();
|
||||
assert(password);
|
||||
assert(account);
|
||||
const accountType = isEmail(account) ? 'email' : (isMobile(account) ? 'mobile' : 'loginName');
|
||||
const applicationPassports = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
},
|
||||
allowPwd: 1,
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
},
|
||||
applicationId,
|
||||
allowPwd: true,
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
// 开启邮箱加密码登录
|
||||
const allowEmail = !!applicationPassports.find((ele) => ele.passport?.type === 'email');
|
||||
// 开启手机号加密码登录
|
||||
const allowSms = !!applicationPassports.find((ele) => ele.passport?.type === 'sms');
|
||||
// 默认只支持账密登录
|
||||
const accountType = allowEmail && isEmail(account) ? 'email' : allowSms && (isMobile(account) ? 'mobile' : 'loginName');
|
||||
if (accountType === 'email') {
|
||||
// 检查邮箱格式后缀是否合法
|
||||
const { config, emailConfig } = await getAndCheckPassportByEmail(context, account);
|
||||
if (!allowEmail) {
|
||||
throw new OakUserException('暂不支持邮箱登录');
|
||||
}
|
||||
const existEmail = await context.select('email', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -655,49 +672,23 @@ export async function loginByAccount(params, context) {
|
|||
throw new OakUserException('error::user.passwordUnmath');
|
||||
}
|
||||
case 1: {
|
||||
const applicationPassports = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
},
|
||||
applicationId,
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
const allowEmail = !!applicationPassports.find((ele) => ele.passport?.type === 'email');
|
||||
const [userRow] = result;
|
||||
const { email$user, id: userId, } = userRow;
|
||||
needUpdatePassword = !(userRow.password || userRow.passwordSha1);
|
||||
if (allowEmail) {
|
||||
const email = email$user?.find(ele => ele.email.toLowerCase() === account.toLowerCase());
|
||||
if (email) {
|
||||
const ableState = email.ableState;
|
||||
if (ableState === 'disabled') {
|
||||
// 虽然密码和邮箱匹配,但邮箱已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
const email = email$user?.find(ele => ele.email.toLowerCase() === account.toLowerCase());
|
||||
if (email) {
|
||||
const ableState = email.ableState;
|
||||
if (ableState === 'disabled') {
|
||||
// 虽然密码和邮箱匹配,但邮箱已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
return await setupEmail(account, env, context);
|
||||
}
|
||||
else {
|
||||
throw new OakUserException('error::user.emailUnexists');
|
||||
}
|
||||
return await setupEmail(account, env, context);
|
||||
}
|
||||
else {
|
||||
throw new OakUserException('error::user.loginWayDisabled');
|
||||
throw new OakUserException('error::user.emailUnexists');
|
||||
}
|
||||
}
|
||||
default: {
|
||||
|
|
@ -706,6 +697,9 @@ export async function loginByAccount(params, context) {
|
|||
}
|
||||
}
|
||||
else if (accountType === 'mobile') {
|
||||
if (!allowSms) {
|
||||
throw new OakUserException('暂不支持手机号登录');
|
||||
}
|
||||
const existMobile = await context.select('mobile', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
@ -752,53 +746,27 @@ export async function loginByAccount(params, context) {
|
|||
throw new OakUserException('手机号与密码不匹配');
|
||||
}
|
||||
case 1: {
|
||||
const applicationPassports = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
type: 1,
|
||||
systemId: 1,
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
passport: {
|
||||
systemId,
|
||||
},
|
||||
applicationId,
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
const [userRow] = result;
|
||||
const { mobile$user, id: userId, } = userRow;
|
||||
needUpdatePassword = !(userRow.password || userRow.passwordSha1);
|
||||
const allowSms = !!applicationPassports.find((ele) => ele.passport?.type === 'sms');
|
||||
if (allowSms) {
|
||||
const mobile = mobile$user?.find(ele => ele.mobile === account);
|
||||
if (mobile) {
|
||||
const ableState = mobile.ableState;
|
||||
if (ableState === 'disabled') {
|
||||
// 虽然密码和手机号匹配,但手机号已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
const mobile = mobile$user?.find(ele => ele.mobile === account);
|
||||
if (mobile) {
|
||||
const ableState = mobile.ableState;
|
||||
if (ableState === 'disabled') {
|
||||
// 虽然密码和手机号匹配,但手机号已经禁用了,在可能的情况下提醒用户使用其它方法登录
|
||||
const exception = await tryMakeChangeLoginWay(userId, context);
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
return await setupMobile(account, env, context);
|
||||
}
|
||||
else {
|
||||
throw new OakUserException('手机号未注册');
|
||||
}
|
||||
return await setupMobile(account, env, context);
|
||||
}
|
||||
else {
|
||||
throw new OakUserException('暂不支持手机号登录');
|
||||
throw new OakUserException('手机号未注册');
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new OakUserException('不支持的登录方式');
|
||||
throw new OakUserException('error::user.loginWayDisabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -869,35 +837,15 @@ export async function loginByAccount(params, context) {
|
|||
}
|
||||
}
|
||||
default: {
|
||||
throw new OakUserException('不支持的登录方式');
|
||||
throw new OakUserException('error::user.loginWayDisabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const closeRootMode = context.openRootMode();
|
||||
const application = context.getApplication();
|
||||
const [applicationPassport] = await context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
passportId: 1,
|
||||
passport: {
|
||||
id: 1,
|
||||
config: 1,
|
||||
type: 1,
|
||||
},
|
||||
applicationId: 1,
|
||||
},
|
||||
filter: {
|
||||
applicationId: application?.id,
|
||||
passport: {
|
||||
type: 'password',
|
||||
},
|
||||
}
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
// assert(applicationPassport?.passport);
|
||||
const pwdMode = applicationPassport?.passport?.config?.mode ?? 'all';
|
||||
const { system } = context.getApplication();
|
||||
const pwdConfig = system?.config.Password;
|
||||
const pwdMode = pwdConfig?.mode ?? 'all';
|
||||
let pwdFilter = {}, updateData = {};
|
||||
if (pwdMode === 'all') {
|
||||
pwdFilter = {
|
||||
|
|
@ -1362,7 +1310,7 @@ async function setUserInfoFromWechat(user, userInfo, context) {
|
|||
}
|
||||
if (avatar &&
|
||||
(extraFile$entity?.length === 0 ||
|
||||
composeFileUrl(application, extraFile$entity[0]) !== avatar)) {
|
||||
await composeFileUrlBackend(application, extraFile$entity[0], context) !== avatar)) {
|
||||
// 需要更新新的avatar extra file
|
||||
const extraFileOperations = [
|
||||
{
|
||||
|
|
@ -1437,7 +1385,7 @@ export async function setUserAvatarFromWechat(params, context) {
|
|||
const updateData = {};
|
||||
if (avatar &&
|
||||
(extraFiles?.length === 0 ||
|
||||
composeFileUrl(application, extraFiles[0]) !== avatar)) {
|
||||
await composeFileUrlBackend(application, extraFiles[0], context) !== avatar)) {
|
||||
// 需要更新新的avatar extra file
|
||||
const extraFileOperations = [
|
||||
{
|
||||
|
|
@ -1549,10 +1497,24 @@ async function tryRefreshWechatPublicUserInfo(wechatUserId, context) {
|
|||
atExpiredAt: ate2,
|
||||
scope: s2,
|
||||
},
|
||||
filter: {
|
||||
id: wechatUserId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
accessToken = at2;
|
||||
}
|
||||
const { nickname, gender, avatar } = await wechatInstance.getUserInfo(accessToken, openId);
|
||||
await context.operate('wechatUser', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
data: {
|
||||
nickname,
|
||||
avatar,
|
||||
},
|
||||
filter: {
|
||||
id: wechatUserId,
|
||||
}
|
||||
}, { dontCollect: true });
|
||||
await setUserInfoFromWechat(user, { nickname, gender: gender, avatar }, context);
|
||||
}
|
||||
export async function refreshWechatPublicUserInfo({}, context) {
|
||||
|
|
@ -1638,7 +1600,7 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
wechatMp: 'mp',
|
||||
native: 'native',
|
||||
};
|
||||
const createWechatUserAndReturnTokenId = async (user, wechatLoginId) => {
|
||||
const createWechatUserAndReturnTokenId = async (user) => {
|
||||
const wechatUserCreateData = {
|
||||
id: await generateNewIdAsync(),
|
||||
unionId,
|
||||
|
|
@ -1647,13 +1609,7 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
applicationId: application.id,
|
||||
...wechatUserData,
|
||||
};
|
||||
let tokenValue;
|
||||
if (wechatLoginId) {
|
||||
tokenValue = await setUpTokenAndUser(env, context, 'wechatLogin', wechatLoginId, undefined, user);
|
||||
}
|
||||
else {
|
||||
tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', undefined, wechatUserCreateData, user);
|
||||
}
|
||||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', undefined, wechatUserCreateData, user);
|
||||
return { tokenValue, wechatUserId: wechatUserCreateData.id };
|
||||
};
|
||||
// 扫码者
|
||||
|
|
@ -1711,8 +1667,9 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
assert(wechatLoginData, 'wechatLogin data not found');
|
||||
// 用户已登录,通过扫描二维码绑定
|
||||
if (wechatLoginData && wechatLoginData.type === 'bind') {
|
||||
if (wechatLoginData.type === 'bind') {
|
||||
// 首先通过wechaLogin.userId查询是否存在wechatUser 判断是否绑定
|
||||
// 登录者
|
||||
const [wechatUserLogin] = await context.select('wechatUser', {
|
||||
|
|
@ -1730,12 +1687,16 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
},
|
||||
filter: {
|
||||
userId: wechatLoginData.userId,
|
||||
applicationId: application.id,
|
||||
origin: OriginMap[type],
|
||||
},
|
||||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
// 已绑定
|
||||
assert(!wechatUserLogin, '登录者已经绑定微信公众号');
|
||||
if (wechatUserLogin) {
|
||||
throw new OakUserException('当前登录者已绑定微信');
|
||||
}
|
||||
// 未绑定的情况,就要先看扫码者是否绑定了公众号
|
||||
// 扫码者已绑定, 将扫码者的userId替换成登录者的userId
|
||||
if (wechatUser) {
|
||||
|
|
@ -1744,19 +1705,19 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
action: 'update',
|
||||
data: {
|
||||
userId: wechatLoginData.userId,
|
||||
...wechatUserData
|
||||
},
|
||||
filter: {
|
||||
id: wechatUser.id,
|
||||
},
|
||||
}, { dontCollect: true });
|
||||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, wechatUserLogin
|
||||
.user);
|
||||
await updateWechatLogin({ successed: true });
|
||||
const tokenValue = await setUpTokenAndUser(env, context, 'wechatUser', wechatUser.id, undefined, wechatLoginData.user);
|
||||
await updateWechatLogin({ successed: true, wechatUserId: wechatUser.id });
|
||||
return tokenValue;
|
||||
}
|
||||
else {
|
||||
const { tokenValue } = await createWechatUserAndReturnTokenId(wechatLoginData.user);
|
||||
await updateWechatLogin({ successed: true });
|
||||
const { tokenValue, wechatUserId } = await createWechatUserAndReturnTokenId(wechatLoginData.user);
|
||||
await updateWechatLogin({ successed: true, wechatUserId });
|
||||
return tokenValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -1810,6 +1771,7 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
await updateWechatLogin({
|
||||
userId: wechatUser.userId,
|
||||
successed: true,
|
||||
wechatUserId: wechatUser.id,
|
||||
});
|
||||
return tokenValue;
|
||||
}
|
||||
|
|
@ -1825,7 +1787,7 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
action: 'create',
|
||||
data: userData,
|
||||
}, {});
|
||||
const { tokenValue, wechatUserId } = await createWechatUserAndReturnTokenId(userData, wechatLoginId);
|
||||
const { tokenValue, wechatUserId } = await createWechatUserAndReturnTokenId(userData);
|
||||
await updateWechatLogin({ userId, wechatUserId, successed: true });
|
||||
return tokenValue;
|
||||
}
|
||||
|
|
@ -1928,7 +1890,8 @@ async function loginFromWechatEnv(code, env, context, wechatLoginId) {
|
|||
}
|
||||
}
|
||||
// 到这里都是要同时创建wechatUser和user对象了
|
||||
return (await createWechatUserAndReturnTokenId()).tokenValue;
|
||||
const { tokenValue } = await createWechatUserAndReturnTokenId();
|
||||
return tokenValue;
|
||||
}
|
||||
/**
|
||||
* 微信App授权登录
|
||||
|
|
@ -1957,19 +1920,6 @@ export async function loginWechat({ code, env, wechatLoginId, }, context) {
|
|||
if (tokenInfo.entity === 'wechatUser') {
|
||||
await tryRefreshWechatPublicUserInfo(tokenInfo.entityId, context);
|
||||
}
|
||||
else if (tokenInfo.entity === 'wechatLogin') {
|
||||
const [wechatLogin] = await context.select('wechatLogin', {
|
||||
data: {
|
||||
id: 1,
|
||||
wechatUserId: 1,
|
||||
},
|
||||
filter: {
|
||||
id: tokenInfo.entityId,
|
||||
},
|
||||
}, {});
|
||||
assert(wechatLogin?.wechatUserId);
|
||||
await tryRefreshWechatPublicUserInfo(wechatLogin.wechatUserId, context);
|
||||
}
|
||||
closeRootMode();
|
||||
return tokenValue;
|
||||
}
|
||||
|
|
@ -1979,9 +1929,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;
|
||||
|
|
@ -2158,9 +2108,11 @@ export async function sendCaptchaByMobile({ mobile, env, type: captchaType, }, c
|
|||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
return code;
|
||||
return {
|
||||
code,
|
||||
captchaId: id
|
||||
};
|
||||
};
|
||||
let code = captcha?.code;
|
||||
if (captcha) {
|
||||
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
|
||||
if (now - captcha.$$createAt$$ < captchaDuration) {
|
||||
|
|
@ -2177,11 +2129,8 @@ export async function sendCaptchaByMobile({ mobile, env, type: captchaType, }, c
|
|||
id: captcha.id
|
||||
}
|
||||
}, {});
|
||||
code = await getCode();
|
||||
}
|
||||
else {
|
||||
code = await getCode();
|
||||
}
|
||||
const { code, captchaId } = await getCode();
|
||||
if (mockSend) {
|
||||
closeRootMode();
|
||||
return `验证码[${code}]已创建`;
|
||||
|
|
@ -2199,6 +2148,16 @@ export async function sendCaptchaByMobile({ mobile, env, type: captchaType, }, c
|
|||
if (result.success) {
|
||||
return '验证码已发送';
|
||||
}
|
||||
await context.operate('captcha', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
data: {
|
||||
expired: true
|
||||
},
|
||||
filter: {
|
||||
id: captchaId
|
||||
}
|
||||
}, {});
|
||||
console.error('短信发送失败,原因:\n', result?.res);
|
||||
return '验证码发送失败';
|
||||
}
|
||||
|
|
@ -2279,10 +2238,6 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
|
|||
});
|
||||
const getCode = async () => {
|
||||
let code;
|
||||
// code = Math.floor(Math.random() * Math.random() * Math.pow(10, digit)).toString();
|
||||
// while (code.length < digit) {
|
||||
// code += '0';
|
||||
// }
|
||||
code = Array.from({ length: digit }, () => Math.floor(Math.random() * 10)).join('');
|
||||
const id = await generateNewIdAsync();
|
||||
const applicationId = context.getApplication()?.id;
|
||||
|
|
@ -2304,9 +2259,11 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
|
|||
}, {
|
||||
dontCollect: true,
|
||||
});
|
||||
return code;
|
||||
return {
|
||||
code,
|
||||
captchaId: id
|
||||
};
|
||||
};
|
||||
let code = captcha?.code;
|
||||
if (captcha) {
|
||||
const captchaDuration = process.env.NODE_ENV === 'development' ? 10 * 1000 : 60 * 1000;
|
||||
if (now - captcha.$$createAt$$ < captchaDuration) {
|
||||
|
|
@ -2323,11 +2280,8 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
|
|||
id: captcha.id
|
||||
}
|
||||
}, {});
|
||||
code = await getCode();
|
||||
}
|
||||
else {
|
||||
code = await getCode();
|
||||
}
|
||||
const { code, captchaId } = await getCode();
|
||||
if (mockSend) {
|
||||
closeRootMode();
|
||||
return `验证码[${code}]已创建`;
|
||||
|
|
@ -2346,6 +2300,16 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
|
|||
if (result.success) {
|
||||
return '验证码已发送';
|
||||
}
|
||||
await context.operate('captcha', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
data: {
|
||||
expired: true
|
||||
},
|
||||
filter: {
|
||||
id: captchaId
|
||||
}
|
||||
}, {});
|
||||
console.error('邮件发送失败,原因:\n', result?.error);
|
||||
return '验证码发送失败';
|
||||
}
|
||||
|
|
@ -2353,7 +2317,7 @@ export async function sendCaptchaByEmail({ email, env, type: captchaType, }, con
|
|||
export async function switchTo({ userId }, context) {
|
||||
const reallyRoot = context.isReallyRoot();
|
||||
if (!reallyRoot) {
|
||||
throw new OakOperationUnpermittedException('user', { id: 'switchTo', action: 'switch', data: {}, filter: { id: userId } });
|
||||
throw new OakOperationUnpermittedException('user', { id: 'switchTo', action: 'switch', data: {}, filter: { id: userId } }, context.getCurrentUserId());
|
||||
}
|
||||
const currentUserId = context.getCurrentUserId();
|
||||
if (currentUserId === userId) {
|
||||
|
|
@ -2521,7 +2485,9 @@ export async function refreshToken(params, context) {
|
|||
],
|
||||
},
|
||||
}, {});
|
||||
assert(token, `tokenValue: ${tokenValue}`);
|
||||
if (!token) {
|
||||
throw new OakUnloggedInException("Token令牌已失效,请重新登录");
|
||||
}
|
||||
const now = Date.now();
|
||||
if (!checkTokenEnvConsistency(env, token.env)) {
|
||||
console.log('####### refreshToken 环境改变 start #######\n');
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -6,7 +6,7 @@ import dayjs from 'dayjs';
|
|||
export async function mergeUser(params, context, innerLogic) {
|
||||
const { from, to, mergeMobile, mergeEmail, mergeWechatUser } = params;
|
||||
if (!innerLogic && !context.isRoot()) {
|
||||
throw new OakOperationUnpermittedException('user', { id: 'merge', action: 'merge', data: {}, filter: { id: from } }, '不允许执行mergeUser操作');
|
||||
throw new OakOperationUnpermittedException('user', { id: 'merge', action: 'merge', data: {}, filter: { id: from } }, context.getCurrentUserId(), '不允许执行mergeUser操作');
|
||||
}
|
||||
assert(from);
|
||||
assert(to);
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,4 +3,6 @@ import { BRC } from '../types/RuntimeCxt';
|
|||
export declare function createWechatLogin<ED extends EntityDict>(params: {
|
||||
type: EntityDict['wechatLogin']['Schema']['type'];
|
||||
interval: number;
|
||||
router: EntityDict['wechatLogin']['Schema']['router'];
|
||||
qrCodeType?: EntityDict['wechatLogin']['Schema']['qrCodeType'];
|
||||
}, context: BRC<ED>): Promise<string>;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,37 @@
|
|||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||
export async function createWechatLogin(params, context) {
|
||||
const { type, interval } = params;
|
||||
const { type, interval, qrCodeType = "wechatPublic", router } = params;
|
||||
let userId;
|
||||
if (type === 'bind') {
|
||||
userId = context.getCurrentUserId();
|
||||
}
|
||||
const id = await generateNewIdAsync();
|
||||
let _router = router;
|
||||
// router为空则默认为/wechatLogin/confirm
|
||||
if (!router) {
|
||||
_router = {
|
||||
pathname: '/wechatLogin/confirm',
|
||||
props: {
|
||||
oakId: id,
|
||||
},
|
||||
};
|
||||
}
|
||||
else {
|
||||
_router.props = {
|
||||
oakId: id,
|
||||
};
|
||||
}
|
||||
const createData = {
|
||||
id,
|
||||
type,
|
||||
expiresAt: Date.now() + interval,
|
||||
expired: false,
|
||||
qrCodeType: 'wechatPublic',
|
||||
qrCodeType,
|
||||
successed: false,
|
||||
router: _router,
|
||||
};
|
||||
if (userId) {
|
||||
Object.assign(createData, {
|
||||
userId,
|
||||
});
|
||||
createData.userId = userId;
|
||||
}
|
||||
if (type === 'login') {
|
||||
const closeRoot = context.openRootMode();
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export async function createWechatQrCode(options, context) {
|
|||
permanent,
|
||||
url,
|
||||
expired: false,
|
||||
expiresAt: Date.now() + 2592000 * 1000,
|
||||
expiresAt: Date.now() + 2592000 * 1000, // wecharQrCode里的过期时间都放到最大,由上层关联对象来主动过期(by Xc, 20230131)
|
||||
props,
|
||||
};
|
||||
// 直接创建
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export async function unbindingWechat(params, context) {
|
|||
id: wechatUserId,
|
||||
}
|
||||
}, {});
|
||||
assert(wechatUser.userId === userId, '查询到的wechatUser.userId与当前登录者不相同');
|
||||
assert(wechatUser.userId === userId, '已绑定微信的用户与当前登录者不一致');
|
||||
await context.operate('wechatUser', {
|
||||
id: await generateNewIdAsync(),
|
||||
action: 'update',
|
||||
|
|
@ -39,6 +39,7 @@ export async function unbindingWechat(params, context) {
|
|||
origin: 'mobile',
|
||||
content: mobile,
|
||||
code: captcha,
|
||||
// TODO: 这里的type暂未确定,如果需要发验证码需要再添加一个验证码类型
|
||||
},
|
||||
sorter: [{
|
||||
$attr: {
|
||||
|
|
@ -54,13 +55,13 @@ export async function unbindingWechat(params, context) {
|
|||
if (captchaRow.expired) {
|
||||
throw new OakUserException('验证码已经过期');
|
||||
}
|
||||
fn();
|
||||
await fn();
|
||||
}
|
||||
else {
|
||||
throw new OakUserException('验证码无效');
|
||||
}
|
||||
}
|
||||
else {
|
||||
fn();
|
||||
await fn();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,16 +71,17 @@ const checkers = [
|
|||
checker(operation, context, option) {
|
||||
const { filter } = operation;
|
||||
assert(filter);
|
||||
const remove = context.select('applicationPassport', {
|
||||
return pipeline(() => context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
isDefault: 1,
|
||||
},
|
||||
filter,
|
||||
}, { forUpdate: true });
|
||||
const updateDefaultFn = (id, applicationId) => {
|
||||
return pipeline(() => context.select('applicationPassport', {
|
||||
}, { forUpdate: true }), (removes) => {
|
||||
const removeIds = removes?.map(ele => ele.id);
|
||||
const applicationId = removes?.[0]?.applicationId;
|
||||
return context.select('applicationPassport', {
|
||||
data: {
|
||||
id: 1,
|
||||
applicationId: 1,
|
||||
|
|
@ -88,40 +89,28 @@ const checkers = [
|
|||
},
|
||||
filter: {
|
||||
id: {
|
||||
$ne: id,
|
||||
$nin: removeIds,
|
||||
},
|
||||
isDefault: false,
|
||||
applicationId,
|
||||
},
|
||||
indexFrom: 0,
|
||||
count: 1,
|
||||
}, {}), (other) => {
|
||||
if (other && other.length === 1) {
|
||||
return context.operate('applicationPassport', {
|
||||
id: generateNewId(),
|
||||
action: 'update',
|
||||
data: {
|
||||
isDefault: true,
|
||||
},
|
||||
filter: {
|
||||
id: other[0].id,
|
||||
},
|
||||
}, option);
|
||||
}
|
||||
});
|
||||
};
|
||||
if (remove instanceof Promise) {
|
||||
return remove.then((r) => {
|
||||
if (r[0]?.isDefault) {
|
||||
return updateDefaultFn(r[0].id, r[0].applicationId);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (remove[0]?.isDefault) {
|
||||
return updateDefaultFn(remove[0].id, remove[0].applicationId);
|
||||
}, {});
|
||||
}, (other) => {
|
||||
if (other && other.length === 1) {
|
||||
return context.operate('applicationPassport', {
|
||||
id: generateNewId(),
|
||||
action: 'update',
|
||||
data: {
|
||||
isDefault: true,
|
||||
},
|
||||
filter: {
|
||||
id: other[0].id,
|
||||
},
|
||||
}, option);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "article", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "articleMenu", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "system", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "platform", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
|
||||
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "applicationPassport", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "article", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "articleMenu", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "system", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "platform", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt<import("../oak-app-domain").EntityDict>>)[];
|
||||
export default checkers;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ const checkers = [
|
|||
action: 'select',
|
||||
entity: 'message',
|
||||
checker: (operation, context) => {
|
||||
const systemId = context.getSystemId();
|
||||
const systemId = context.getSystemId(true);
|
||||
if (!systemId) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const checkers = [
|
|||
assert(!(data instanceof Array));
|
||||
checkAttributesNotNull('parasite', data, ['expiresAt', 'tokenLifeLength']);
|
||||
if (data.userId) {
|
||||
// @oak-ignore 这里先不await,下面再具体检查返回类型
|
||||
const users2 = context.select('user', {
|
||||
data: {
|
||||
id: 1,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const checkers = [
|
|||
checker: (operation, context) => {
|
||||
// 只有root才能进行操作
|
||||
if (!context.isRoot()) {
|
||||
throw new OakOperationUnpermittedException('user', { id: 'disable', action: 'disable', data: {} });
|
||||
throw new OakOperationUnpermittedException('user', { id: 'disable', action: 'disable', data: {} }, context.getCurrentUserId());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -179,7 +179,7 @@ export const UserCheckers = [
|
|||
for (const attr in data) {
|
||||
const rel = judgeRelation(context.getSchema(), 'user', attr);
|
||||
if (rel !== 1) {
|
||||
throw new OakOperationUnpermittedException('user', operation, '您不能更新他人信息');
|
||||
throw new OakOperationUnpermittedException('user', operation, context.getCurrentUserId(), '您不能更新他人信息');
|
||||
}
|
||||
}
|
||||
const result = checkFilterContains('user', context, {
|
||||
|
|
@ -188,12 +188,12 @@ export const UserCheckers = [
|
|||
if (result instanceof Promise) {
|
||||
return result.then((r) => {
|
||||
if (!r) {
|
||||
throw new OakOperationUnpermittedException('user', operation, '您不能更新他人信息');
|
||||
throw new OakOperationUnpermittedException('user', operation, context.getCurrentUserId(), '您不能更新他人信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!result) {
|
||||
throw new OakOperationUnpermittedException('user', operation, '您不能更新他人信息');
|
||||
throw new OakOperationUnpermittedException('user', operation, context.getCurrentUserId(), '您不能更新他人信息');
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "address", true, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "address", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="@uiw/react-amap-types" />
|
||||
import React from 'react';
|
||||
export type PositionProps = {
|
||||
loadUI: boolean;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="@uiw/react-amap-types" />
|
||||
import React from 'react';
|
||||
import { ModalProps } from 'antd';
|
||||
import { GeolocationProps } from '@uiw/react-amap';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="@uiw/react-amap-types" />
|
||||
import React from 'react';
|
||||
import { MapProps, APILoaderConfig } from '@uiw/react-amap';
|
||||
import './index.less';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { WebComponentProps } from 'oak-frontend-base';
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
declare const Cos: (props: WebComponentProps<EntityDict, keyof EntityDict, false, {
|
||||
currentConfig: EntityDict['application']['OpSchema']['config'];
|
||||
currentConfig: EntityDict["application"]["OpSchema"]["config"];
|
||||
dirty: boolean;
|
||||
entity: string;
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// @oak-ignore
|
||||
import React from 'react';
|
||||
import Styles from './styles.module.less';
|
||||
import { Affix, Alert, Button, Select, Space, Typography } from 'antd';
|
||||
|
|
@ -7,7 +8,7 @@ const Cos = (props) => {
|
|||
return (<>
|
||||
<Affix offsetTop={64}>
|
||||
<Alert message={<div>
|
||||
<text>
|
||||
<span>
|
||||
您正在更新
|
||||
<Typography.Text keyboard>
|
||||
{entity}
|
||||
|
|
@ -17,7 +18,7 @@ const Cos = (props) => {
|
|||
{name}
|
||||
</Typography.Text>
|
||||
的COS配置,请谨慎操作
|
||||
</text>
|
||||
</span>
|
||||
</div>} type="info" showIcon action={<Space>
|
||||
<Button disabled={!dirty} type="primary" danger onClick={() => resetConfig()} style={{
|
||||
marginRight: 10,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "application", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "application", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "application", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { EntityDict } from "../../oak-app-domain";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "applicationPassport", true, {
|
||||
import { ReactComponentProps } from "oak-frontend-base";
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
|
||||
systemId: string;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export default OakComponent({
|
|||
enabled: 1,
|
||||
},
|
||||
isDefault: 1,
|
||||
allowPwd: 1,
|
||||
},
|
||||
properties: {
|
||||
systemId: '',
|
||||
|
|
@ -34,6 +35,9 @@ export default OakComponent({
|
|||
},
|
||||
passport: {
|
||||
systemId,
|
||||
type: {
|
||||
$ne: 'password'
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -83,8 +87,9 @@ export default OakComponent({
|
|||
}
|
||||
else {
|
||||
const { disabled, disabledTip } = this.checkDisabled(a, r[0]);
|
||||
const { showPwd, pwdDisabled, pwdDisabledTip } = this.checkPwd(r[0]);
|
||||
const apId = await generateNewIdAsync();
|
||||
Object.assign(typeRecords, { [key]: { render, pId: r[0].id, checked: false, disabled, disabledTip, apId, } });
|
||||
Object.assign(typeRecords, { [key]: { render, pId: r[0].id, checked: false, disabled, disabledTip, apId, showPwd, allowPwd: undefined, pwdDisabled, pwdDisabledTip } });
|
||||
}
|
||||
}
|
||||
Object.assign(item, { typeRecords });
|
||||
|
|
@ -106,8 +111,15 @@ export default OakComponent({
|
|||
if (apArray[aIdx].typeRecords[t].pId === p.id) {
|
||||
apArray[aIdx].typeRecords[t].checked = true;
|
||||
apArray[aIdx].typeRecords[t].apId = ap.id;
|
||||
apArray[aIdx].typeRecords[t].allowPwd = ap.allowPwd;
|
||||
}
|
||||
}
|
||||
if (t === 'loginName') {
|
||||
apArray[aIdx].typeRecords[t].pwdDisabledTip = '账号登录必须使用密码方式';
|
||||
}
|
||||
else {
|
||||
apArray[aIdx].typeRecords[t].pwdDisabled = false;
|
||||
}
|
||||
apArray[aIdx].defaultOptions.push({
|
||||
label: this.t(`passport:v.type.${p.type}`),
|
||||
value: ap.id,
|
||||
|
|
@ -157,6 +169,9 @@ export default OakComponent({
|
|||
filter: {
|
||||
systemId,
|
||||
enabled: true,
|
||||
type: {
|
||||
$ne: 'password',
|
||||
}
|
||||
},
|
||||
sorter: [{
|
||||
$attr: {
|
||||
|
|
@ -193,36 +208,36 @@ export default OakComponent({
|
|||
switch (pType) {
|
||||
case 'sms':
|
||||
if (!pConfig.mockSend) {
|
||||
if (!pConfig.templateName || pConfig.templateName === '') {
|
||||
if (!pConfig.templateName) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '短信登录未配置验证码模板名称',
|
||||
disabledTip: '手机号登录未配置验证码模板名称',
|
||||
};
|
||||
}
|
||||
if (!pConfig.defaultOrigin) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '短信登录未配置默认渠道',
|
||||
disabledTip: '手机号登录未配置默认渠道',
|
||||
};
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'email':
|
||||
if (!pConfig.mockSend) {
|
||||
if (!pConfig.account || pConfig.account === '') {
|
||||
if (!pConfig.account) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '邮箱登录未配置账号',
|
||||
};
|
||||
}
|
||||
else if (!pConfig.subject || pConfig.subject === '') {
|
||||
else if (!pConfig.subject) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '邮箱登录未配置邮件主题',
|
||||
};
|
||||
}
|
||||
else if ((!pConfig.text || pConfig.text === '' || !pConfig.text?.includes('${code}')) &&
|
||||
(!pConfig.html || pConfig.html === '' || !pConfig.html?.includes('${code}'))) {
|
||||
else if ((!pConfig.text || !pConfig.text?.includes('${code}')) &&
|
||||
(!pConfig.html || !pConfig.html?.includes('${code}'))) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '邮箱登录未配置邮件内容模板',
|
||||
|
|
@ -231,7 +246,7 @@ export default OakComponent({
|
|||
}
|
||||
break;
|
||||
case 'wechatPublicForWeb':
|
||||
if (!pConfig.appId || pConfig.appId === '') {
|
||||
if (!pConfig.appId) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '公众号授权登录未配置appId',
|
||||
|
|
@ -239,13 +254,27 @@ export default OakComponent({
|
|||
}
|
||||
break;
|
||||
case 'wechatMpForWeb':
|
||||
if (!pConfig.appId || pConfig.appId === '') {
|
||||
if (!pConfig.appId) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '小程序授权登录未配置appId',
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'oauth':
|
||||
if (!(pConfig.oauthIds && pConfig.oauthIds.length > 0)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: 'OAuth授权登录未配置oauth供应商',
|
||||
};
|
||||
}
|
||||
break;
|
||||
case 'password':
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '密码登录已调整',
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -269,7 +298,7 @@ export default OakComponent({
|
|||
}
|
||||
break;
|
||||
case 'wechatMp':
|
||||
if (['wechatPublic', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb'].includes(pType)) {
|
||||
if (['wechatPublic', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb', 'oauth'].includes(pType)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
|
|
@ -277,7 +306,7 @@ export default OakComponent({
|
|||
}
|
||||
break;
|
||||
case 'wechatPublic':
|
||||
if (['wechatMp', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb'].includes(pType)) {
|
||||
if (['wechatMp', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb', 'oauth'].includes(pType)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
|
|
@ -285,7 +314,7 @@ export default OakComponent({
|
|||
}
|
||||
break;
|
||||
case 'native':
|
||||
if (['wechatMp', 'wechatPublic', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb'].includes(pType)) {
|
||||
if (['wechatMp', 'wechatPublic', 'wechatWeb', 'wechatMpForWeb', 'wechatPublicForWeb', 'oauth'].includes(pType)) {
|
||||
return {
|
||||
disabled: true,
|
||||
disabledTip: '当前application不支持该登录方式',
|
||||
|
|
@ -301,13 +330,25 @@ export default OakComponent({
|
|||
};
|
||||
},
|
||||
async onCheckedChange(aId, pId, checked, apId) {
|
||||
const { passports } = this.state;
|
||||
const passportType = passports?.find((ele) => ele.id === pId)?.type;
|
||||
if (checked) {
|
||||
//create applicationPassport
|
||||
this.addItem({
|
||||
applicationId: aId,
|
||||
passportId: pId,
|
||||
isDefault: true,
|
||||
});
|
||||
if (passportType === 'loginName') {
|
||||
this.addItem({
|
||||
applicationId: aId,
|
||||
passportId: pId,
|
||||
isDefault: true,
|
||||
allowPwd: true,
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.addItem({
|
||||
applicationId: aId,
|
||||
passportId: pId,
|
||||
isDefault: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
//remove id为apId的applicationPassport
|
||||
|
|
@ -349,6 +390,20 @@ export default OakComponent({
|
|||
}
|
||||
}
|
||||
return render;
|
||||
}
|
||||
},
|
||||
checkPwd(passport) {
|
||||
const { type } = passport;
|
||||
let showPwd = false, pwdDisabled = undefined, pwdDisabledTip = undefined;
|
||||
if (['sms', 'email', 'loginName'].includes(type)) {
|
||||
showPwd = true;
|
||||
pwdDisabled = true;
|
||||
pwdDisabledTip = '请先启用该登录方式';
|
||||
}
|
||||
return {
|
||||
showPwd,
|
||||
pwdDisabled,
|
||||
pwdDisabledTip,
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ type TypeRecord = Record<string, {
|
|||
checked?: boolean;
|
||||
disabled: boolean;
|
||||
disabledTip: string;
|
||||
showPwd: boolean;
|
||||
allowPwd?: boolean;
|
||||
pwdDisabled?: boolean;
|
||||
pwdDisabledTip?: string;
|
||||
}>;
|
||||
type PassportOption = {
|
||||
label: string;
|
||||
|
|
|
|||
|
|
@ -56,8 +56,11 @@ export default function render(props) {
|
|||
}} disabled={typeRecords[type].disabled} options={typeRecords[type].passportOptions} optionRender={(option) => (<Tooltip title={(option.data.disabled) ? option.data.disabledTip : ''}>
|
||||
<div>{option.data.label}</div>
|
||||
</Tooltip>)} style={{ width: 140 }}/>
|
||||
</Tooltip>) : (<Tooltip title={typeRecords[type].disabled ? typeRecords[type].disabledTip : ''}>
|
||||
<Switch disabled={typeRecords[type].disabled} checkedChildren={<CheckOutlined />} unCheckedChildren={<CloseOutlined />} checked={!!typeRecords[type].checked} onChange={(checked) => {
|
||||
</Tooltip>) : (<>
|
||||
<Tooltip title={typeRecords[type].disabled ? typeRecords[type].disabledTip : ''}>
|
||||
<Space>
|
||||
<div>启用:</div>
|
||||
<Switch disabled={typeRecords[type].disabled} checkedChildren={<CheckOutlined />} unCheckedChildren={<CloseOutlined />} checked={!!typeRecords[type].checked} onChange={(checked) => {
|
||||
if (!checked && checkLastOne(aId, typeRecords[type].pId)) {
|
||||
showConfirm(aId, typeRecords[type].pId, typeRecords[type].apId);
|
||||
}
|
||||
|
|
@ -65,7 +68,20 @@ export default function render(props) {
|
|||
onCheckedChange(aId, typeRecords[type].pId, checked, typeRecords[type].apId);
|
||||
}
|
||||
}}/>
|
||||
</Tooltip>)}
|
||||
</Space>
|
||||
</Tooltip>
|
||||
{typeRecords[type].showPwd &&
|
||||
<Space>
|
||||
<div>允许密码登录:</div>
|
||||
<Tooltip title={typeRecords[type].pwdDisabled ? typeRecords[type].pwdDisabledTip : ''}>
|
||||
<Switch size="small" disabled={typeRecords[type].pwdDisabled} checkedChildren={<CheckOutlined />} unCheckedChildren={<CloseOutlined />} checked={!!typeRecords[type].allowPwd} onChange={(checked) => {
|
||||
methods.updateItem({
|
||||
allowPwd: checked
|
||||
}, typeRecords[type].apId);
|
||||
}}/>
|
||||
</Tooltip>
|
||||
</Space>}
|
||||
</>)}
|
||||
|
||||
</Space>
|
||||
});
|
||||
|
|
@ -94,6 +110,6 @@ export default function render(props) {
|
|||
确定
|
||||
</Button>
|
||||
</div>
|
||||
<Table columns={columns} dataSource={apArray} pagination={false} scroll={{ x: 1200 }}/>
|
||||
<Table columns={columns} dataSource={apArray} pagination={false} scroll={{ x: 1200 }} rowKey="aId"/>
|
||||
</>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ export default OakComponent({
|
|||
tocFixed: true,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
headerTop: 0,
|
||||
headerTop: 0, //页面中吸顶部分高度
|
||||
className: '',
|
||||
scrollId: '',
|
||||
scrollId: '', // 滚动条所在容器id,不传默认body
|
||||
tocWidth: undefined,
|
||||
tocHeight: undefined,
|
||||
showtitle: false,
|
||||
showtitle: false, //大纲顶层显示文章名称
|
||||
activeColor: undefined,
|
||||
},
|
||||
data: {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
tocPosition: "none" | "left" | "right";
|
||||
highlightBgColor: string;
|
||||
onArticlePreview: (content?: string, title?: string) => void;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
origin: null | EntityDict["extraFile"]["Schema"]["origin"];
|
||||
scrollId: string;
|
||||
height: number | "auto";
|
||||
activeColor: string | undefined;
|
||||
|
|
|
|||
|
|
@ -21,16 +21,15 @@ export default OakComponent({
|
|||
data: {
|
||||
editor: null,
|
||||
html: '',
|
||||
contentTip: false,
|
||||
},
|
||||
properties: {
|
||||
articleMenuId: '',
|
||||
changeIsEdit: () => undefined,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
onArticlePreview: (content, title) => undefined,
|
||||
origin: null,
|
||||
scrollId: '',
|
||||
tocPosition: 'none', //目录显示位置,none为不显示目录
|
||||
highlightBgColor: 'none', //点击目录时标题高亮背景色,none为不显示高亮背景色
|
||||
onArticlePreview: (content, title) => undefined, //预览文章
|
||||
origin: null, // 默认为空,由系统决定
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600,
|
||||
activeColor: undefined,
|
||||
},
|
||||
|
|
@ -40,15 +39,14 @@ export default OakComponent({
|
|||
next.editor.setHtml(next.content);
|
||||
}
|
||||
},
|
||||
oakId(prev, next) {
|
||||
if (prev.oakId !== next.oakId) {
|
||||
const { editor } = this.state;
|
||||
if (editor == null)
|
||||
return;
|
||||
editor.destroy();
|
||||
this.setEditor(null);
|
||||
}
|
||||
},
|
||||
// oakId(prev, next) {
|
||||
// if (prev.oakId !== next.oakId) {
|
||||
// const { editor } = this.state;
|
||||
// if (editor == null) return;
|
||||
// editor.destroy();
|
||||
// this.setEditor(null);
|
||||
// }
|
||||
// },
|
||||
name(prev, next) {
|
||||
if (prev.name !== next.name) {
|
||||
window.document.title = next.name ?? '';
|
||||
|
|
@ -58,7 +56,7 @@ export default OakComponent({
|
|||
lifetimes: {
|
||||
async ready() {
|
||||
const { oakId, articleMenuId } = this.props;
|
||||
if (!oakId) {
|
||||
if (this.isCreation()) {
|
||||
if (articleMenuId) {
|
||||
this.update({
|
||||
articleMenuId,
|
||||
|
|
@ -81,7 +79,10 @@ export default OakComponent({
|
|||
},
|
||||
methods: {
|
||||
async uploadFile(extraFile, file) {
|
||||
const result = await this.features.extraFile.autoUpload(extraFile, file);
|
||||
const result = await this.features.extraFile.autoUpload({
|
||||
extraFile: extraFile,
|
||||
file: file
|
||||
});
|
||||
return result;
|
||||
},
|
||||
setEditor(editor) {
|
||||
|
|
@ -89,11 +90,6 @@ export default OakComponent({
|
|||
editor,
|
||||
});
|
||||
},
|
||||
clearContentTip() {
|
||||
this.setState({
|
||||
contentTip: false,
|
||||
});
|
||||
},
|
||||
async check() {
|
||||
if (this.state.name &&
|
||||
this.state.name.length > 0 &&
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"name": "请输入文章标题",
|
||||
"content": "请输入文章内容..."
|
||||
},
|
||||
"chcek": {
|
||||
"check": {
|
||||
"no name": "请填写文章标题!",
|
||||
"no content": "请填写文章内容!"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
editor: any;
|
||||
content?: string;
|
||||
origin?: null | EntityDict['extraFile']['Schema']['origin'];
|
||||
contentTip: boolean;
|
||||
articleMenuId: string;
|
||||
oakId: string;
|
||||
tocPosition: 'none' | 'left' | 'right';
|
||||
|
|
@ -26,6 +25,5 @@ export default function Render(props: WebComponentProps<EntityDict, 'article', f
|
|||
setEditor: (editor: any) => void;
|
||||
check: () => void;
|
||||
uploadFile: (extraFile: EntityDict['extraFile']['CreateOperationData'], file: File) => Promise<string>;
|
||||
clearContentTip: () => void;
|
||||
gotoPreview: (content?: string, title?: string) => void;
|
||||
}>): React.JSX.Element;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Alert, Button, Space, Input, } from "antd";
|
||||
import { Button, Space, Input, } from "antd";
|
||||
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
|
||||
import { SlateNode } from "@wangeditor/editor";
|
||||
|
|
@ -30,7 +30,7 @@ function customCheckImageFn(src, alt, url) {
|
|||
}
|
||||
export default function Render(props) {
|
||||
const { methods, data } = props;
|
||||
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
|
||||
const { t, setMessage, setEditor, check, uploadFile, update, setHtml, gotoPreview, } = methods;
|
||||
const { oakId, oakFullpath, id, name, content, editor, origin, tocPosition = 'none', highlightBgColor, scrollId, tocWidth, tocHeight, height = 600, tocClosed = false, execuable, activeColor, html, oakLoading, oakExecuting, } = data;
|
||||
const [articleId, setArticleId] = useState('');
|
||||
const [toc, setToc] = useState([]);
|
||||
|
|
@ -80,7 +80,6 @@ export default function Render(props) {
|
|||
<div className={classNames(Style.editorContainer, {
|
||||
[Style.editorExternalContainer]: !!scrollId,
|
||||
})}>
|
||||
{data.contentTip && (<Alert type="info" message={t("tips.content")} closable onClose={() => clearContentTip()}/>)}
|
||||
<div className={Style.titleContainer}>
|
||||
<Input onChange={(e) => {
|
||||
if (e.target.value.trim() && e.target.value.trim() !== '') {
|
||||
|
|
@ -89,102 +88,107 @@ export default function Render(props) {
|
|||
else {
|
||||
update({ name: null });
|
||||
}
|
||||
}} value={data.name ?? ''} placeholder={t('placeholder.name')} size="large" maxLength={32}
|
||||
// suffix={`${(data.name || "").length}/32`}
|
||||
showCount className={Style.titleInput}/>
|
||||
}} value={data.name ?? ''} placeholder={t('placeholder.name')} size="large" maxLength={32} showCount className={Style.titleInput}/>
|
||||
</div>
|
||||
<div>
|
||||
<Editor defaultConfig={{
|
||||
autoFocus: true,
|
||||
placeholder: t('placeholder.content'),
|
||||
MENU_CONF: {
|
||||
checkImage: customCheckImageFn,
|
||||
uploadImage: {
|
||||
// 自定义上传
|
||||
async customUpload(file, insertFn) {
|
||||
// TS 语法
|
||||
// file 即选中的文件
|
||||
const { name, size, type } = file;
|
||||
const extension = name.substring(name.lastIndexOf(".") + 1);
|
||||
const filename = name.substring(0, name.lastIndexOf("."));
|
||||
const extraFile = {
|
||||
entity: "article",
|
||||
entityId: oakId || articleId,
|
||||
origin: origin,
|
||||
type: "image",
|
||||
tag1: "source",
|
||||
objectId: generateNewId(),
|
||||
filename,
|
||||
size,
|
||||
extension,
|
||||
bucket: "",
|
||||
id: generateNewId(),
|
||||
fileType: type,
|
||||
};
|
||||
try {
|
||||
// 自己实现上传,并得到图片 url alt href
|
||||
const url = await uploadFile(extraFile, file);
|
||||
// 最后插入图片
|
||||
insertFn(url, extraFile.filename);
|
||||
}
|
||||
catch (err) { }
|
||||
{!!articleId && <Editor defaultConfig={{
|
||||
autoFocus: true,
|
||||
placeholder: t('placeholder.content'),
|
||||
MENU_CONF: {
|
||||
checkImage: customCheckImageFn,
|
||||
uploadImage: {
|
||||
// 自定义上传
|
||||
async customUpload(file, insertFn) {
|
||||
// TS 语法
|
||||
// file 即选中的文件
|
||||
const { name, size, type } = file;
|
||||
const extension = name.substring(name.lastIndexOf(".") + 1);
|
||||
const filename = name.substring(0, name.lastIndexOf("."));
|
||||
const extraFile = {
|
||||
entity: "article",
|
||||
entityId: oakId || articleId,
|
||||
origin: origin,
|
||||
type: "image",
|
||||
tag1: "source",
|
||||
objectId: generateNewId(),
|
||||
filename,
|
||||
size,
|
||||
extension,
|
||||
bucket: "",
|
||||
id: generateNewId(),
|
||||
fileType: type,
|
||||
};
|
||||
try {
|
||||
// 自己实现上传,并得到图片 url alt href
|
||||
const url = await uploadFile(extraFile, file);
|
||||
// 最后插入图片
|
||||
insertFn(url, extraFile.filename);
|
||||
}
|
||||
catch (err) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
content: err.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
uploadVideo: {
|
||||
// 自定义上传
|
||||
async customUpload(file, insertFn) {
|
||||
// TS 语法
|
||||
// file 即选中的文件
|
||||
const { name, size, type } = file;
|
||||
const extension = name.substring(name.lastIndexOf(".") + 1);
|
||||
const filename = name.substring(0, name.lastIndexOf("."));
|
||||
const extraFile = {
|
||||
entity: "article",
|
||||
entityId: oakId || articleId,
|
||||
origin: origin,
|
||||
type: "video",
|
||||
tag1: "source",
|
||||
objectId: generateNewId(),
|
||||
filename,
|
||||
size,
|
||||
extension,
|
||||
bucket: "",
|
||||
id: generateNewId(),
|
||||
fileType: type,
|
||||
};
|
||||
try {
|
||||
// 自己实现上传,并得到图片 url alt href
|
||||
const url = await uploadFile(extraFile, file);
|
||||
// 最后插入图片
|
||||
insertFn(url, url + "?vframe/jpg/offset/0");
|
||||
}
|
||||
catch (err) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
content: err.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
uploadVideo: {
|
||||
// 自定义上传
|
||||
async customUpload(file, insertFn) {
|
||||
// TS 语法
|
||||
// file 即选中的文件
|
||||
const { name, size, type } = file;
|
||||
const extension = name.substring(name.lastIndexOf(".") + 1);
|
||||
const filename = name.substring(0, name.lastIndexOf("."));
|
||||
const extraFile = {
|
||||
entity: "article",
|
||||
entityId: oakId || articleId,
|
||||
origin: origin,
|
||||
type: "video",
|
||||
tag1: "source",
|
||||
objectId: generateNewId(),
|
||||
filename,
|
||||
size,
|
||||
extension,
|
||||
bucket: "",
|
||||
id: generateNewId(),
|
||||
fileType: type,
|
||||
};
|
||||
try {
|
||||
// 自己实现上传,并得到图片 url alt href
|
||||
const url = await uploadFile(extraFile, file);
|
||||
// 最后插入图片
|
||||
insertFn(url, url +
|
||||
"?vframe/jpg/offset/0");
|
||||
}
|
||||
catch (err) { }
|
||||
},
|
||||
},
|
||||
},
|
||||
}} onCreated={setEditor} onChange={(editor) => {
|
||||
setHtml(editor.getHtml());
|
||||
const headers = editor.getElemsByTypePrefix("header");
|
||||
const tocItems = headers.map((header) => {
|
||||
const text = SlateNode.string(header);
|
||||
const { id, type } = header;
|
||||
return {
|
||||
text,
|
||||
level: parseInt(type.substring(6)),
|
||||
id,
|
||||
};
|
||||
});
|
||||
setToc([...tocItems]);
|
||||
}} style={{
|
||||
minHeight: '100%',
|
||||
}} mode="default"/>
|
||||
}} onCreated={setEditor} onChange={(editor) => {
|
||||
setHtml(editor.getHtml());
|
||||
const headers = editor.getElemsByTypePrefix("header");
|
||||
const tocItems = headers.map((header) => {
|
||||
const text = SlateNode.string(header);
|
||||
const { id, type } = header;
|
||||
return {
|
||||
text,
|
||||
level: parseInt(type.substring(6)),
|
||||
id,
|
||||
};
|
||||
});
|
||||
setToc([...tocItems]);
|
||||
}} style={{
|
||||
minHeight: '100%',
|
||||
}} mode="default"/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc}
|
||||
// highlightBgColor={highlightBgColor}
|
||||
activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||||
{tocPosition === "right" ? (<TocView toc={toc} showToc={showToc} tocPosition="right" setShowToc={setShowToc} activeColor={activeColor} scrollId={containerId} tocWidth={tocWidth} tocHeight={tocHeight}/>) : null}
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
/// <reference types="react" />
|
||||
import { GenerateUrlFn } from "../../../types/Article";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", true, {
|
||||
entityId: string;
|
||||
articleMenuId: string | undefined;
|
||||
generateUrl: GenerateUrlFn;
|
||||
empty: import("react").ReactNode;
|
||||
empty: React.ReactNode | undefined;
|
||||
menuCheck: (isArticle: boolean) => void;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ export default OakComponent({
|
|||
tocFixed: true,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
headerTop: 0,
|
||||
headerTop: 0, //页面中吸顶部分高度
|
||||
className: '',
|
||||
scrollId: '',
|
||||
scrollId: '', // 滚动条所在容器id,不传默认body
|
||||
tocWidth: undefined,
|
||||
tocHeight: undefined,
|
||||
showtitle: false,
|
||||
showtitle: false, //大纲顶层显示文章名称
|
||||
activeColor: undefined,
|
||||
},
|
||||
methods: {},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ export default OakComponent({
|
|||
properties: {
|
||||
articleMenuId: '',
|
||||
onChildEditArticleChange: (data) => undefined,
|
||||
show: 'edit',
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
|
||||
breadcrumbItems: [],
|
||||
drawerOpen: false,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
tocPosition: "none" | "left" | "right";
|
||||
highlightBgColor: string;
|
||||
onArticlePreview: (content?: string, title?: string) => void;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
origin: EntityDict["extraFile"]["Schema"]["origin"] | null;
|
||||
scrollId: string;
|
||||
height: number | "auto";
|
||||
activeColor: string | undefined;
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ export default OakComponent({
|
|||
properties: {
|
||||
articleMenuId: '',
|
||||
changeIsEdit: () => undefined,
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
onArticlePreview: (content, title) => undefined,
|
||||
origin: null,
|
||||
scrollId: '',
|
||||
tocPosition: 'none', //目录显示位置,none为不显示目录
|
||||
highlightBgColor: 'none', //点击目录时标题高亮背景色,none为不显示高亮背景色
|
||||
onArticlePreview: (content, title) => undefined, //预览文章
|
||||
origin: null, // 默认为七牛云
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
height: 600,
|
||||
activeColor: undefined,
|
||||
},
|
||||
|
|
@ -75,7 +75,10 @@ export default OakComponent({
|
|||
},
|
||||
methods: {
|
||||
async uploadFile(extraFile, file) {
|
||||
const result = await this.features.extraFile.autoUpload(extraFile, file);
|
||||
const result = await this.features.extraFile.autoUpload({
|
||||
extraFile: extraFile,
|
||||
file: file
|
||||
});
|
||||
return result;
|
||||
},
|
||||
setEditor(editor) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Alert, Button, Space, Input, } from "antd";
|
||||
import { Button, Space, Input, } from "antd";
|
||||
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
|
||||
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
|
||||
import { SlateNode } from "@wangeditor/editor";
|
||||
|
|
@ -30,7 +30,7 @@ function customCheckImageFn(src, alt, url) {
|
|||
}
|
||||
export default function Render(props) {
|
||||
const { methods, data } = props;
|
||||
const { t, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
|
||||
const { t, setMessage, setEditor, check, uploadFile, update, setHtml, gotoPreview, clearContentTip, } = methods;
|
||||
const { oakId, oakFullpath, id, content, editor, origin, tocPosition = 'none', highlightBgColor, activeColor, scrollId, tocWidth, tocHeight, height = 600, html, oakLoading, oakExecuting, } = data;
|
||||
const [articleId, setArticleId] = useState('');
|
||||
const [toc, setToc] = useState([]);
|
||||
|
|
@ -60,7 +60,14 @@ export default function Render(props) {
|
|||
<div id={containerId} className={classNames(Style.editorContainer, {
|
||||
[Style.editorExternalContainer]: !!scrollId,
|
||||
})}>
|
||||
{data.contentTip && (<Alert type="info" message={t("tips.content")} closable onClose={() => clearContentTip()}/>)}
|
||||
{/* {data.contentTip && (
|
||||
<Alert
|
||||
type="info"
|
||||
message={t("tips.content")}
|
||||
closable
|
||||
onClose={() => clearContentTip()}
|
||||
/>
|
||||
)} */}
|
||||
<div className={Style.titleContainer}>
|
||||
<Input onChange={(e) => update({ name: e.target.value })} value={data.name} placeholder={"请输入文章标题"} size="large" maxLength={32} suffix={`${(data.name || "").length}/32`} className={Style.titleInput}/>
|
||||
</div>
|
||||
|
|
@ -98,7 +105,12 @@ export default function Render(props) {
|
|||
// 最后插入图片
|
||||
insertFn(url, extraFile.filename);
|
||||
}
|
||||
catch (err) { }
|
||||
catch (err) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
content: err.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
uploadVideo: {
|
||||
|
|
@ -130,7 +142,12 @@ export default function Render(props) {
|
|||
insertFn(url, url +
|
||||
"?vframe/jpg/offset/0");
|
||||
}
|
||||
catch (err) { }
|
||||
catch (err) {
|
||||
setMessage({
|
||||
type: "error",
|
||||
content: err.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
/// <reference types="react" />
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
import { GenerateUrlFn } from "../../../types/Article";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, false, {
|
||||
entity: string;
|
||||
entityId: string;
|
||||
title: string;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
menuEmpty: import("react").ReactNode;
|
||||
articleEmpty: import("react").ReactNode;
|
||||
origin: null | EntityDict["extraFile"]["Schema"]["origin"];
|
||||
menuEmpty: React.ReactNode | undefined;
|
||||
articleEmpty: React.ReactNode | undefined;
|
||||
generateUrl: GenerateUrlFn;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default OakComponent({
|
|||
entity: '',
|
||||
entityId: '',
|
||||
title: '',
|
||||
origin: null,
|
||||
origin: null, // cos origin默认由系统决定
|
||||
menuEmpty: undefined,
|
||||
articleEmpty: undefined,
|
||||
generateUrl: ((mode, type, id) => { }), //构造文章显示路由
|
||||
|
|
@ -20,7 +20,7 @@ export default OakComponent({
|
|||
showAddArticle: false,
|
||||
showAddMenu: true,
|
||||
parentId: '',
|
||||
articleMenuId: '',
|
||||
articleMenuId: '', //非空时展示atricle表格
|
||||
unsub: undefined,
|
||||
},
|
||||
listeners: {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default OakComponent({
|
|||
};
|
||||
},
|
||||
properties: {
|
||||
highlightBgColor: 'none',
|
||||
highlightBgColor: 'none', //点击文章目录时标题高亮背景色,none为不显示高亮背景色
|
||||
allowHiddenMenu: false,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
/// <reference types="react" />
|
||||
import { GenerateUrlFn } from "../../../types/Article";
|
||||
import { EntityDict } from "../../../oak-app-domain";
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "articleMenu", true, {
|
||||
entity: string;
|
||||
entityId: string;
|
||||
parentId: string | undefined;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
origin: EntityDict["extraFile"]["Schema"]["origin"] | null;
|
||||
onMenuClick: (menuId: string, menuName: string, isArticle: boolean) => void;
|
||||
onArticleClick: (atricleId: string) => void;
|
||||
empty: import("react").ReactNode;
|
||||
empty: React.ReactNode | undefined;
|
||||
changeAddArticle: (show: boolean) => void;
|
||||
generateUrl: GenerateUrlFn;
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default OakComponent({
|
|||
entity: '',
|
||||
entityId: '',
|
||||
parentId: '',
|
||||
origin: null,
|
||||
origin: null, // cos origin默认由系统决定
|
||||
onMenuClick: (menuId, menuName, isArticle) => undefined,
|
||||
onArticleClick: (atricleId) => undefined,
|
||||
empty: undefined,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
setCurrentArticle: (id: string) => void;
|
||||
onMenuViewById: (articleMenuId: string) => void;
|
||||
setCopyArticleUrl: (id: string) => string;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
origin: null | EntityDict["extraFile"]["Schema"]["origin"];
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export default OakComponent({
|
|||
onRemove: () => undefined,
|
||||
onUpdateName: async (name) => undefined,
|
||||
onChildEditArticleChange: (data) => undefined,
|
||||
show: 'edit',
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
|
||||
breadItems: [],
|
||||
drawerOpen: false,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
setCurrentArticle: (id: string) => void;
|
||||
onMenuViewById: (articleMenuId: string) => void;
|
||||
setCopyArticleUrl: (id: string) => string;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
origin: null | EntityDict["extraFile"]["Schema"]["origin"];
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default OakComponent({
|
|||
entityId: '',
|
||||
parentId: '',
|
||||
onGrandChildEditArticleChange: (data) => undefined,
|
||||
show: 'edit',
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
articleMenuId: '',
|
||||
articleId: '',
|
||||
getBreadcrumbItems: (breadcrumbItems) => undefined,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
onArticlePreview: (content?: string, title?: string) => void;
|
||||
onArticleEdit: (articleId: string) => void;
|
||||
setCopyArticleUrl: (articleId: string) => string;
|
||||
origin: import("../../../types/Config").CosOrigin | null;
|
||||
origin: EntityDict["extraFile"]["Schema"]["origin"] | null;
|
||||
scrollId: string;
|
||||
activeColor: string | undefined;
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -14,19 +14,19 @@ export default OakComponent({
|
|||
properties: {
|
||||
entity: '',
|
||||
entityId: '',
|
||||
show: 'edit',
|
||||
articleMenuId: '',
|
||||
articleId: '',
|
||||
tocPosition: 'none',
|
||||
highlightBgColor: 'none',
|
||||
onMenuView: () => undefined,
|
||||
onMenuViewById: (articleMenuId) => undefined,
|
||||
onArticleView: (articleId) => undefined,
|
||||
onArticlePreview: (content, title) => undefined,
|
||||
onArticleEdit: (articleId) => undefined,
|
||||
show: 'edit', // edit为编辑,doc为查看,preview为预览
|
||||
articleMenuId: '', // 菜单id
|
||||
articleId: '', //文章id
|
||||
tocPosition: 'none', //文章目录显示位置,none为不显示目录
|
||||
highlightBgColor: 'none', //点击文章目录时标题高亮背景色,none为不显示高亮背景色
|
||||
onMenuView: () => undefined, //查看全部菜单
|
||||
onMenuViewById: (articleMenuId) => undefined, //查看指定id菜单
|
||||
onArticleView: (articleId) => undefined, //查看文章
|
||||
onArticlePreview: (content, title) => undefined, //预览文章
|
||||
onArticleEdit: (articleId) => undefined, //编辑文章
|
||||
setCopyArticleUrl: (articleId) => '',
|
||||
origin: null,
|
||||
scrollId: '',
|
||||
origin: null, // cos origin默认由系统决定
|
||||
scrollId: '', // 滚动条所在容器id,不传默认页面编辑器容器id
|
||||
activeColor: undefined, //目录高亮颜色
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -54,9 +54,8 @@ export default OakComponent({
|
|||
lifetimes: {
|
||||
async ready() {
|
||||
const lastSendAt = await this.load(SEND_KEY);
|
||||
const application = this.features.application.getApplication();
|
||||
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
|
||||
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
|
||||
const system = this.features.application.getApplication().system;
|
||||
const passwordConfig = system?.config.Password;
|
||||
const mode = passwordConfig?.mode ?? 'all';
|
||||
const pwdMin = passwordConfig?.min ?? 8;
|
||||
const pwdMax = passwordConfig?.max ?? 24;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "user", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -23,9 +23,8 @@ export default OakComponent({
|
|||
},
|
||||
lifetimes: {
|
||||
async ready() {
|
||||
const application = this.features.application.getApplication();
|
||||
const { result: applicationPassports } = await this.features.cache.exec('getApplicationPassports', { applicationId: application.id });
|
||||
const passwordConfig = applicationPassports.find((ele) => ele.passport.type === 'password')?.passport.config;
|
||||
const system = this.features.application.getApplication().system;
|
||||
const passwordConfig = system?.config.Password;
|
||||
const mode = passwordConfig?.mode ?? 'all';
|
||||
const pwdMin = passwordConfig?.min ?? 8;
|
||||
const pwdMax = passwordConfig?.max ?? 24;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="node" />
|
||||
import * as React from 'react';
|
||||
type IDownloadProps = {
|
||||
children?: React.ReactNode;
|
||||
|
|
@ -9,9 +8,9 @@ type IDownloadProps = {
|
|||
};
|
||||
declare function Download(props: IDownloadProps): React.JSX.Element;
|
||||
declare namespace Download {
|
||||
var onDownload: (data: ArrayBuffer | ReadableStream<any>, filename: string) => Promise<void>;
|
||||
var onDownload: (data: ArrayBuffer | ReadableStream, filename: string) => Promise<void>;
|
||||
var base64ToBlob: (base64String: string) => Blob;
|
||||
var arrayBufferToBase64: (buffer: Buffer) => string;
|
||||
var base64ToArrayBuffer: (base64String: string) => ArrayBufferLike;
|
||||
var base64ToArrayBuffer: (base64String: string) => ArrayBuffer;
|
||||
}
|
||||
export default Download;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { ReactComponentProps } from 'oak-frontend-base';
|
|||
import { ECode } from '../../../types/ErrorPage';
|
||||
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
code: ECode;
|
||||
title?: string | undefined;
|
||||
desc?: string | undefined;
|
||||
title?: string;
|
||||
desc?: string;
|
||||
children?: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default OakComponent({
|
|||
code: '',
|
||||
title: '',
|
||||
desc: '',
|
||||
icon: '',
|
||||
icon: '', //web独有
|
||||
imagePath: '', //小程序独有
|
||||
},
|
||||
lifetimes: {
|
||||
|
|
|
|||
|
|
@ -2,21 +2,21 @@ import { EntityDict } from '../../../oak-app-domain';
|
|||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { ReactComponentProps } from 'oak-frontend-base';
|
||||
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
filename?: string | undefined;
|
||||
expiresAt?: number | undefined;
|
||||
filename?: string;
|
||||
expiresAt?: number;
|
||||
tips?: React.ReactNode;
|
||||
onDownload?: ((qrCodeImage: string, filename?: string) => void) | undefined;
|
||||
onRefresh?: (() => void) | undefined;
|
||||
size?: number | undefined;
|
||||
onDownload?: (qrCodeImage: string, filename?: string) => void;
|
||||
onRefresh?: () => void;
|
||||
size?: number;
|
||||
url: string;
|
||||
loading?: boolean | undefined;
|
||||
disableDownload?: boolean | undefined;
|
||||
loading?: boolean;
|
||||
disableDownload?: boolean;
|
||||
disabled: boolean;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
maskColor: string;
|
||||
maskTextColor: string;
|
||||
maskText: string;
|
||||
mode: 'simple' | 'default';
|
||||
mode: "simple" | "default";
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -43,9 +43,9 @@ export default OakComponent({
|
|||
return;
|
||||
}
|
||||
const qrCodeURL = qrcode.drawImg(url, {
|
||||
typeNumber: 4,
|
||||
errorCorrectLevel: 'L',
|
||||
size: size,
|
||||
typeNumber: 4, // 密度
|
||||
errorCorrectLevel: 'L', // 纠错等级
|
||||
size: size, // 白色边框
|
||||
color,
|
||||
bgColor
|
||||
});
|
||||
|
|
@ -77,8 +77,8 @@ export default OakComponent({
|
|||
// 注意:写入前需确保Base64数据已经去掉了数据URL前缀(如"data:image/png;base64,")
|
||||
fs.writeFile({
|
||||
filePath: filePath,
|
||||
data: base64Data,
|
||||
encoding: 'base64',
|
||||
data: base64Data, // 传入纯Base64字符串
|
||||
encoding: 'base64', // 指定编码格式
|
||||
success(res) {
|
||||
// 文件写入成功后,检查并保存到相册
|
||||
that.checkAuthAndSave(filePath);
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
redDot: boolean;
|
||||
text: string;
|
||||
pagePath: string;
|
||||
iconName?: string | undefined;
|
||||
selectedIconName?: string | undefined;
|
||||
iconPath?: string | undefined;
|
||||
selectedIconPath?: string | undefined;
|
||||
iconSize?: string | undefined;
|
||||
iconName?: string;
|
||||
selectedIconName?: string;
|
||||
iconPath?: string;
|
||||
selectedIconPath?: string;
|
||||
iconSize?: string;
|
||||
}[];
|
||||
color: string;
|
||||
selectedColor: string;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default OakComponent({
|
|||
color: '#666',
|
||||
selectedColor: '',
|
||||
border: false,
|
||||
selectedIconPath: '',
|
||||
selectedIconPath: '', //一般在list设置
|
||||
iconPath: '',
|
||||
},
|
||||
lifetimes: {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export default function Native(props) {
|
|||
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
location
|
||||
Location
|
||||
</Divider>
|
||||
<Form colon={true} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
|
||||
<Form.Item label="协议">
|
||||
|
|
@ -66,6 +66,11 @@ export default function Native(props) {
|
|||
<Input placeholder="请输入端口" type="text" value={config?.location?.port} onChange={(e) => setValue(`location.port`, e.target.value || '')}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="扫码页" tooltip="扫码页可选填,未填写的话,使用wechatQrCode/scan">
|
||||
<>
|
||||
<Input placeholder="请输入扫码页" type="text" value={config?.location?.scanPage} onChange={(e) => setValue(`location.scanPage`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
</Space>);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export default function render(props) {
|
|||
return (<>
|
||||
<Affix offsetTop={64}>
|
||||
<Alert message={<div>
|
||||
<text>
|
||||
<span>
|
||||
您正在更新
|
||||
<Typography.Text keyboard className={Style.weight}>
|
||||
{entity}
|
||||
|
|
@ -37,7 +37,7 @@ export default function render(props) {
|
|||
{name}
|
||||
</Typography.Text>
|
||||
的配置,请谨慎操作
|
||||
</text>
|
||||
</span>
|
||||
</div>} type="info" showIcon action={<Space>
|
||||
<Button disabled={!dirty} type="primary" danger onClick={() => resetConfig()} style={{
|
||||
marginRight: 10,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default function Web(props) {
|
|||
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
location
|
||||
Location
|
||||
</Divider>
|
||||
<Form colon={true} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
|
||||
<Form.Item label="协议">
|
||||
|
|
@ -84,6 +84,11 @@ export default function Web(props) {
|
|||
<Input placeholder="请输入端口" type="text" value={config?.location?.port} onChange={(e) => setValue(`location.port`, e.target.value || '')}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="扫码页" tooltip="扫码页可选填,未填写的话,使用wechatQrCode/scan">
|
||||
<>
|
||||
<Input placeholder="请输入扫码页" type="text" value={config?.location?.scanPage} onChange={(e) => setValue(`location.scanPage`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
</Space>);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,44 @@ export default function WechatMp(props) {
|
|||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
Location
|
||||
</Divider>
|
||||
<Form colon={true} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
|
||||
<Form.Item label="协议">
|
||||
<>
|
||||
<Select style={{ width: '100%' }} placeholder="请选择协议" value={config?.location?.protocol} onChange={(value) => {
|
||||
setValue(`location.protocol`, value);
|
||||
}} options={[
|
||||
{
|
||||
label: 'http:',
|
||||
value: 'http:',
|
||||
},
|
||||
{
|
||||
label: 'https:',
|
||||
value: 'https:',
|
||||
},
|
||||
]}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="主机">
|
||||
<>
|
||||
<Input placeholder="请输入主机名" type="text" value={config?.location?.hostname} onChange={(e) => setValue(`location.hostname`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="端口">
|
||||
<>
|
||||
<Input placeholder="请输入端口" type="text" value={config?.location?.port} onChange={(e) => setValue(`location.port`, e.target.value || '')}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="扫码页" tooltip="扫码页可选填,未填写的话,使用wechatQrCode/scan">
|
||||
<>
|
||||
<Input placeholder="请输入扫码页" type="text" value={config?.location?.scanPage} onChange={(e) => setValue(`location.scanPage`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
微信小程序-服务器配置
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default function WechatPublic(props) {
|
|||
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
location
|
||||
Location
|
||||
</Divider>
|
||||
<Form colon={true} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
|
||||
<Form.Item label="协议">
|
||||
|
|
@ -70,65 +70,14 @@ export default function WechatPublic(props) {
|
|||
<Input placeholder="请输入端口" type="text" value={config?.location?.port} onChange={(e) => setValue(`location.port`, e.target.value || '')}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="扫码页" tooltip="扫码页可选填,未填写的话,使用wechatQrCode/scan">
|
||||
<>
|
||||
<Input placeholder="请输入扫码页" type="text" value={config?.location?.scanPage} onChange={(e) => setValue(`location.scanPage`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
|
||||
{/* <Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
网站-授权方式
|
||||
</Divider>
|
||||
<Form
|
||||
colon={true}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<Form.Item label="passport"
|
||||
//name="passport"
|
||||
>
|
||||
<>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{ width: '100%' }}
|
||||
placeholder="请选择授权方式"
|
||||
value={config?.passport as Passport[]}
|
||||
onChange={(value: Passport[]) => {
|
||||
if (value.includes('wechat') && value.includes('wechatPublic')) {
|
||||
// messageApi.warning('微信网站和微信公众号中,只能选择一个');
|
||||
message.warning('微信网站和微信公众号中,只能选择一个')
|
||||
return;
|
||||
}
|
||||
setValue(`passport`, value);
|
||||
}}
|
||||
options={
|
||||
[
|
||||
{
|
||||
label: '邮箱',
|
||||
value: 'email',
|
||||
},
|
||||
{
|
||||
label: '手机号',
|
||||
value: 'mobile',
|
||||
},
|
||||
{
|
||||
label: '微信网站',
|
||||
value: 'wechat',
|
||||
},
|
||||
{
|
||||
label: '微信公众号',
|
||||
value: 'wechatPublic',
|
||||
},
|
||||
] as Array<{
|
||||
label: string;
|
||||
value: Passport;
|
||||
}>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col> */}
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
微信公众号-跳转小程序-小程序配置
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Style } from '../../../../types/Style';
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../../oak-app-domain").EntityDict, keyof import("../../../../oak-app-domain").EntityDict, false, {
|
||||
style: Style;
|
||||
entity: "application" | "platform" | "system";
|
||||
entity: "system" | "platform" | "application";
|
||||
entityId: string;
|
||||
name: string;
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function Render(props) {
|
|||
return (<>
|
||||
<Affix offsetTop={64}>
|
||||
<Alert message={<div>
|
||||
<text>
|
||||
<span>
|
||||
您正在更新
|
||||
<Typography.Text keyboard>
|
||||
{entity}
|
||||
|
|
@ -18,7 +18,7 @@ export default function Render(props) {
|
|||
{name}
|
||||
</Typography.Text>
|
||||
的样式,请谨慎操作
|
||||
</text>
|
||||
</span>
|
||||
</div>} type="info" showIcon action={<Space>
|
||||
<Button disabled={!dirty} type="primary" danger onClick={() => resetStyle()} style={{
|
||||
marginRight: 10,
|
||||
|
|
|
|||
|
|
@ -347,6 +347,25 @@ function AliAccount(props) {
|
|||
<Input placeholder="请输入endpoint" type="text" value={ele.smsEndpoint} onChange={(e) => setValue(`${idx}.smsEndpoint`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="STS endpoint"
|
||||
//name="stsEndpoint"
|
||||
tooltip="STS 访问的域名,如:sts.cn-hangzhou.aliyuncs.com">
|
||||
<>
|
||||
<Input placeholder="请输入endpoint" type="text" value={ele.stsEndpoint} onChange={(e) => setValue(`${idx}.stsEndpoint`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
{/* roleArn */}
|
||||
<Form.Item label="角色ARN">
|
||||
<>
|
||||
<Input placeholder="请输入角色ARN" type="text" value={ele.roleArn} onChange={(e) => setValue(`${idx}.roleArn`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
{/* roleSessionName */}
|
||||
<Form.Item label="角色会话名称">
|
||||
<>
|
||||
<Input placeholder="请输入角色会话名称" type="text" value={ele.roleSessionName} onChange={(e) => setValue(`${idx}.roleSessionName`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>),
|
||||
}))
|
||||
: [
|
||||
|
|
@ -388,6 +407,25 @@ function AliAccount(props) {
|
|||
<Input placeholder="请输入endpoint" type="text" value="" onChange={(e) => setValue(`0.smsEndpoint`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="STS endpoint"
|
||||
//name="stsEndpoint"
|
||||
tooltip="STS 访问的域名,如:sts.cn-hangzhou.aliyuncs.com">
|
||||
<>
|
||||
<Input placeholder="请输入endpoint" type="text" value='' onChange={(e) => setValue(`0.stsEndpoint`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
{/* roleArn */}
|
||||
<Form.Item label="角色ARN">
|
||||
<>
|
||||
<Input placeholder="请输入角色ARN" type="text" value='' onChange={(e) => setValue(`0.roleArn`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
{/* roleSessionName */}
|
||||
<Form.Item label="角色会话名称">
|
||||
<>
|
||||
<Input placeholder="请输入角色会话名称" type="text" value='' onChange={(e) => setValue(`0.roleSessionName`, e.target.value)}/>
|
||||
</>
|
||||
</Form.Item>
|
||||
</Form>),
|
||||
},
|
||||
]}></Tabs>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Config } from '../../../types/Config';
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, keyof import("../../../oak-app-domain").EntityDict, false, {
|
||||
config: Config;
|
||||
entity: "platform" | "system";
|
||||
entity: "system" | "platform";
|
||||
name: string;
|
||||
entityId: string;
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Config } from '../../../../types/Config';
|
||||
export default function Password(props: {
|
||||
password: Required<Config>['Password'];
|
||||
setValue: (path: string, value: any) => void;
|
||||
setValues: (value: Record<string, any>) => void;
|
||||
}): React.JSX.Element;
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { Col, Divider, Input, Form, Space, Radio, InputNumber, Switch, } from 'antd';
|
||||
import Styles from './web.module.less';
|
||||
import EditorRegexs from '../../../passport/password/editorRegexs';
|
||||
export default function Password(props) {
|
||||
const { password, setValue, setValues } = props;
|
||||
const { mode, min, max, verify, regexs, tip } = password || {};
|
||||
const [newTip, setNewTip] = useState('');
|
||||
useEffect(() => {
|
||||
const { password } = props;
|
||||
if (!password.mode) {
|
||||
setValues({
|
||||
mode: 'all',
|
||||
min: 8,
|
||||
max: 24,
|
||||
});
|
||||
}
|
||||
}, [password]);
|
||||
useEffect(() => {
|
||||
if (tip && !newTip) {
|
||||
setNewTip(tip);
|
||||
}
|
||||
}, [tip]);
|
||||
return (<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
|
||||
<Col flex="auto">
|
||||
<Divider orientation="left" className={Styles.title}>
|
||||
密码设置
|
||||
</Divider>
|
||||
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }}>
|
||||
<Form.Item label="密码存储模式" tooltip="密码存储模式">
|
||||
<Radio.Group onChange={({ target }) => {
|
||||
const { value } = target;
|
||||
setValue('mode', value);
|
||||
}} value={mode}>
|
||||
<Radio value="all">明文与SHA1加密</Radio>
|
||||
<Radio value="plain">仅明文</Radio>
|
||||
<Radio value="sha1">仅SHA1加密</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="密码位数范围" tooltip="密码位数范围">
|
||||
<Space>
|
||||
<InputNumber min={1} max={max} value={min} onChange={(value) => {
|
||||
setValue('min', value);
|
||||
}}/>
|
||||
<div>~</div>
|
||||
<InputNumber min={min} value={max} onChange={(value) => {
|
||||
setValue('max', value);
|
||||
}}/>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="开启正则校验" tooltip="开启后将使用下方设置的正则表达式对密码进行校验">
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭" checked={!!verify} onChange={(checked) => {
|
||||
setValue('verigy', checked);
|
||||
}}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="正则" tooltip="可同时设置多组正则,系统将按照【与】逻辑进行校验,每个正则请以^开头以$结尾">
|
||||
<>
|
||||
{!!verify ? (<>
|
||||
<EditorRegexs regexs={regexs || []} updateRegexs={(regexs) => {
|
||||
setValue('regexs', regexs);
|
||||
}}/>
|
||||
</>) : (<div>暂未启用正则校验,无需设置</div>)}
|
||||
</>
|
||||
</Form.Item>
|
||||
<Form.Item label="密码提示语" tooltip="此提示语将显示在用户设置密码的输入框附近,用于清晰告知用户密码的格式要求">
|
||||
<Input placeholder="请输入密码提示语" type="text" value={tip} onChange={(e) => {
|
||||
setNewTip(e.target.value);
|
||||
}} onBlur={() => {
|
||||
if (newTip && newTip !== tip) {
|
||||
setValue('tip', newTip);
|
||||
}
|
||||
}}/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
</Space>);
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
.label {
|
||||
color: var(--oak-text-color-primary);
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
color: var(--oak-text-color-placeholder);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-bottom: 0px;
|
||||
margin-top:36px;
|
||||
}
|
||||
|
|
@ -9,14 +9,15 @@ import Sms from './sms/index';
|
|||
import Email from './email/index';
|
||||
import Basic from './basic/index';
|
||||
import Security from './security/index';
|
||||
import Password from './password/index';
|
||||
export default function Render(props) {
|
||||
const { entity, name, currentConfig, dirty } = props.data;
|
||||
const { resetConfig, updateConfig, setValue, setValues, removeItem, cleanKey, t } = props.methods;
|
||||
const { Account: account, Cos: cos, Map: map, Live: live, Sms: sms, App: app, Emails: emails, Security: security, } = currentConfig || {};
|
||||
const { Account: account, Cos: cos, Map: map, Live: live, Sms: sms, App: app, Emails: emails, Security: security, Password: password, } = currentConfig || {};
|
||||
return (<>
|
||||
<Affix offsetTop={64}>
|
||||
<Alert message={<div>
|
||||
<text>
|
||||
<span>
|
||||
您正在更新
|
||||
<Typography.Text keyboard className={Style.weight}>
|
||||
{entity}
|
||||
|
|
@ -26,7 +27,7 @@ export default function Render(props) {
|
|||
{name}
|
||||
</Typography.Text>
|
||||
的配置,请谨慎操作
|
||||
</text>
|
||||
</span>
|
||||
</div>} type="info" showIcon action={<Space>
|
||||
<Button disabled={!dirty} type="primary" danger onClick={() => resetConfig()} style={{
|
||||
marginRight: 10,
|
||||
|
|
@ -84,6 +85,15 @@ export default function Render(props) {
|
|||
});
|
||||
}}/>),
|
||||
},
|
||||
{
|
||||
key: '密码设置',
|
||||
label: '密码设置',
|
||||
children: (<Password password={password || {}} setValue={(path, value) => setValue(`Password.${path}`, value)} setValues={(value) => {
|
||||
setValues({
|
||||
Password: value
|
||||
});
|
||||
}}/>),
|
||||
},
|
||||
]}></Tabs>
|
||||
</div>
|
||||
</>);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "domain", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,2 @@
|
|||
/// <reference types="wechat-miniprogram" />
|
||||
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "domain", false, WechatMiniprogram.Component.DataOption>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,6 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
|
|||
entityId: string;
|
||||
tag1: string;
|
||||
tag2: string;
|
||||
origin: import("../../types/Config").CosOrigin | null;
|
||||
origin: EntityDict["extraFile"]["Schema"]["origin"] | null;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -85,7 +85,10 @@ export default OakComponent({
|
|||
id: generateNewId(),
|
||||
uploadState: 'uploading'
|
||||
};
|
||||
const url = await this.features.extraFile.autoUpload(extraFile, tempFilePath);
|
||||
const url = await this.features.extraFile.autoUpload({
|
||||
extraFile: extraFile,
|
||||
file: tempFilePath
|
||||
});
|
||||
this.editorCtx.insertImage({
|
||||
src: url,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ export default OakComponent({
|
|||
entity: 1,
|
||||
entityId: 1,
|
||||
sort: 1,
|
||||
fileType: 1,
|
||||
isBridge: 1,
|
||||
uploadState: 1,
|
||||
size: 1,
|
||||
applicationId: 1,
|
||||
},
|
||||
features: ['extraFile'],
|
||||
|
|
@ -124,7 +128,10 @@ export default OakComponent({
|
|||
};
|
||||
// 如果autoUpload
|
||||
if (autoUpload) {
|
||||
await this.features.extraFile.autoUpload(updateData, extra1);
|
||||
await this.features.extraFile.autoUpload({
|
||||
extraFile: updateData,
|
||||
file: extra1
|
||||
});
|
||||
if (avatar) {
|
||||
this.removeItem(avatar.id);
|
||||
this.execute();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="react" />
|
||||
import { EntityDict } from '../../../oak-app-domain';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { ReactComponentProps } from 'oak-frontend-base/lib/types/Page';
|
||||
|
|
@ -9,31 +8,14 @@ type AfterCommit = (() => void) | undefined;
|
|||
type BeforeCommit = (() => boolean | undefined | Promise<boolean | undefined>) | undefined;
|
||||
declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, true, {
|
||||
entity: keyof ED2;
|
||||
action?: string | undefined;
|
||||
size?: ButtonProps['size'] | AmButtonProps['size'];
|
||||
block?: boolean | undefined;
|
||||
type?: ButtonProps['type'] | AmButtonProps['type'];
|
||||
executeText?: string | undefined;
|
||||
buttonProps?: (ButtonProps & {
|
||||
color?: "default" | "success" | "warning" | "primary" | "danger" | undefined;
|
||||
fill?: "none" | "outline" | "solid" | undefined;
|
||||
size?: "small" | "middle" | "large" | "mini" | undefined;
|
||||
block?: boolean | undefined;
|
||||
loading?: boolean | "auto" | undefined;
|
||||
loadingText?: string | undefined;
|
||||
loadingIcon?: import("react").ReactNode;
|
||||
disabled?: boolean | undefined;
|
||||
onClick?: ((event: import("react").MouseEvent<HTMLButtonElement, MouseEvent>) => unknown) | undefined;
|
||||
type?: "button" | "submit" | "reset" | undefined;
|
||||
shape?: "default" | "rounded" | "rectangular" | undefined;
|
||||
children?: import("react").ReactNode;
|
||||
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseDown" | "onMouseUp" | "onTouchEnd" | "onTouchStart"> & {
|
||||
className?: string | undefined;
|
||||
style?: (import("react").CSSProperties & Partial<Record<"--text-color" | "--background-color" | "--border-radius" | "--border-width" | "--border-style" | "--border-color", string>>) | undefined;
|
||||
tabIndex?: number | undefined;
|
||||
} & import("react").AriaAttributes) | undefined;
|
||||
action?: string;
|
||||
size?: ButtonProps["size"] | AmButtonProps["size"];
|
||||
block?: boolean;
|
||||
type?: ButtonProps["type"] | AmButtonProps["type"];
|
||||
executeText?: string;
|
||||
buttonProps?: ButtonProps & AmButtonProps;
|
||||
afterCommit?: AfterCommit;
|
||||
beforeCommit?: BeforeCommit;
|
||||
messageProps?: boolean | MessageProps | undefined;
|
||||
messageProps?: MessageProps | boolean;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
|
|||
extension: string[];
|
||||
selectCount: number;
|
||||
sourceType: SourceType[];
|
||||
mediaType: ('image' | 'video')[];
|
||||
mediaType: ("image" | "video")[];
|
||||
mode: ImageMode;
|
||||
size: number;
|
||||
showUploadList: boolean;
|
||||
|
|
@ -52,7 +52,7 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
|
|||
aspect: number;
|
||||
minZoom: number;
|
||||
maxZoom: number;
|
||||
cropShape: 'rect' | 'round';
|
||||
cropShape: "rect" | "round";
|
||||
cropperProps: object;
|
||||
modalTitle: string;
|
||||
modalWidth: string;
|
||||
|
|
@ -67,7 +67,7 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
|
|||
minHeight: number;
|
||||
compressWidth: number;
|
||||
compressHeight: number;
|
||||
resize: 'contain' | 'cover' | 'none';
|
||||
resize: "contain" | "cover" | "none";
|
||||
compressQuality: number;
|
||||
mimeType: string;
|
||||
convertTypes: string[];
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ export default OakComponent({
|
|||
objectId: 1,
|
||||
filename: 1,
|
||||
extra1: 1,
|
||||
extra2: 1,
|
||||
extension: 1,
|
||||
type: 1,
|
||||
entity: 1,
|
||||
|
|
@ -20,6 +21,8 @@ export default OakComponent({
|
|||
sort: 1,
|
||||
isBridge: 1,
|
||||
uploadState: 1,
|
||||
size: 1,
|
||||
applicationId: 1,
|
||||
},
|
||||
data: {
|
||||
// 根据 size 不同,计算的图片显示大小不同
|
||||
|
|
@ -47,19 +50,19 @@ export default OakComponent({
|
|||
bucket: '',
|
||||
autoUpload: false,
|
||||
maxNumber: 20,
|
||||
extension: [],
|
||||
selectCount: 1,
|
||||
sourceType: ['album', 'camera'],
|
||||
mediaType: ['image'],
|
||||
mode: 'aspectFit',
|
||||
size: 3,
|
||||
showUploadList: true,
|
||||
showUploadProgress: false,
|
||||
accept: 'image/*',
|
||||
disablePreview: false,
|
||||
disableDelete: false,
|
||||
disableAdd: false,
|
||||
disableDownload: false,
|
||||
extension: [], //小程序独有 chooseMessageFile
|
||||
selectCount: 1, // 每次打开图片时,可选中的数量 小程序独有
|
||||
sourceType: ['album', 'camera'], // 小程序独有 chooseMedia
|
||||
mediaType: ['image'], // 小程序独有 chooseMedia
|
||||
mode: 'aspectFit', // 图片显示模式
|
||||
size: 3, // 每行可显示的个数 小程序独有
|
||||
showUploadList: true, //web独有
|
||||
showUploadProgress: false, // web独有
|
||||
accept: 'image/*', // web独有
|
||||
disablePreview: false, // 图片是否可预览
|
||||
disableDelete: false, // 图片是否可删除
|
||||
disableAdd: false, // 上传按钮隐藏
|
||||
disableDownload: false, // 下载按钮隐藏
|
||||
type: 'image',
|
||||
origin: null,
|
||||
tag1: '',
|
||||
|
|
@ -67,40 +70,40 @@ export default OakComponent({
|
|||
entity: '',
|
||||
entityId: '',
|
||||
theme: 'image',
|
||||
enableCrop: false,
|
||||
enableCompross: false,
|
||||
enableCrop: false, //启用裁剪
|
||||
enableCompross: false, //启用压缩
|
||||
//图片裁剪
|
||||
cropQuality: 1,
|
||||
showRest: false,
|
||||
showGrid: false,
|
||||
fillColor: 'white',
|
||||
rotationSlider: false,
|
||||
aspectSlider: false,
|
||||
zoomSlider: true,
|
||||
resetText: '重置',
|
||||
aspect: 1 / 1,
|
||||
minZoom: 1,
|
||||
maxZoom: 3,
|
||||
cropShape: 'rect',
|
||||
cropperProps: {},
|
||||
modalTitle: '编辑图片',
|
||||
modalWidth: '40vw',
|
||||
modalOk: '确定',
|
||||
modalCancel: '取消',
|
||||
cropQuality: 1, //图片裁剪质量,范围:0 ~ 1
|
||||
showRest: false, //显示重置按钮,重置缩放及旋转
|
||||
showGrid: false, //显示裁切区域网格(九宫格)
|
||||
fillColor: 'white', //裁切图像填充色
|
||||
rotationSlider: false, //图片旋转控制
|
||||
aspectSlider: false, //裁切比率控制
|
||||
zoomSlider: true, //图片缩放控制
|
||||
resetText: '重置', //重置按钮文字
|
||||
aspect: 1 / 1, //裁切区域宽高比,width / height
|
||||
minZoom: 1, //最小缩放倍数
|
||||
maxZoom: 3, //最大缩放倍数
|
||||
cropShape: 'rect', //裁切区域形状,'rect' 或 'round'
|
||||
cropperProps: {}, //recat-easy-crop的props
|
||||
modalTitle: '编辑图片', //弹窗标题
|
||||
modalWidth: '40vw', //弹窗宽度
|
||||
modalOk: '确定', //确定按钮文字
|
||||
modalCancel: '取消', //取消按钮的文字
|
||||
//图片压缩
|
||||
strict: true,
|
||||
checkOrientation: true,
|
||||
retainExif: false,
|
||||
maxWidth: Infinity,
|
||||
maxHeight: Infinity,
|
||||
minWidth: 0,
|
||||
minHeight: 0,
|
||||
compressWidth: undefined,
|
||||
compressHeight: undefined,
|
||||
resize: 'none',
|
||||
compressQuality: 0.8,
|
||||
mimeType: 'auto',
|
||||
convertTypes: ['image/png'],
|
||||
strict: true, //当压缩后的图片尺寸大于原图尺寸时输出原图
|
||||
checkOrientation: true, //读取图像的Exif方向值并自动旋转或翻转图像(仅限 JPEG 图像)
|
||||
retainExif: false, //压缩后保留图片的Exif信息
|
||||
maxWidth: Infinity, //输出图片的最大宽度,值需大于0
|
||||
maxHeight: Infinity, //输出图片的最大高度,值需大于0
|
||||
minWidth: 0, //输出图片的最小宽度,值需大于0且不应大于maxWidth
|
||||
minHeight: 0, //输出图片的最小高度。值需大于0且不应大于maxHeight
|
||||
compressWidth: undefined, //输出图像的宽度。如果未指定,则将使用原始图像的宽度,若设置了height,则宽度将根据自然纵横比自动计算。
|
||||
compressHeight: undefined, //输出图像的高度。如果未指定,则将使用原始图像的高度,若设置了width,则高度将根据自然纵横比自动计算。
|
||||
resize: 'none', //仅在同时指定了width和height时生效
|
||||
compressQuality: 0.8, //输出图像的质量。范围:0 ~ 1
|
||||
mimeType: 'auto', //输出图片的 MIME 类型。默认情况下,将使用源图片文件的原始 MIME 类型。
|
||||
convertTypes: ['image/png'], //文件类型包含在其中且文件大小超过该convertSize值的文件将被转换为 JPEG。
|
||||
convertSize: Infinity, //文件类型包含在convertTypes中且文件大小超过此值的文件将转换为 JPEG,Infinity表示禁用该功能
|
||||
},
|
||||
features: ['extraFile'],
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default function render(props) {
|
|||
};
|
||||
return (<>
|
||||
{enableCrop ? (<ImgCrop showReset={showRest} showGrid={showGrid} fillColor={fillColor} rotationSlider={rotationSlider} aspectSlider={aspectSlider} zoomSlider={zoomSlider} resetText={resetText} aspect={aspect} minZoom={minZoom} maxZoom={maxZoom} cropShape={cropShape} quality={cropQuality} cropperProps={{
|
||||
restrictPosition: false,
|
||||
restrictPosition: false, //允许移动图片位置
|
||||
...cropperProps,
|
||||
}} modalTitle={modalTitle} modalWidth={modalWidth} modalOk={modalOk} modalCancel={modalCancel}>
|
||||
<ExtrafileUpload oakPath={oakFullpath} bucket={bucket} autoUpload={autoUpload} maxNumber={maxNumber} mode={mode} showUploadList={showUploadList} showUploadProgress={showUploadProgress} accept={accept} disablePreview={disablePreview} disableDelete={disableDelete} disableAdd={disableAdd} disableDownload={disableDownload} disabled={disabled} type={type} origin={origin} tag1={tag1} tag2={tag2} entity={entity} entityId={entityId} theme={theme} children={children} beforeUpload={async (file) => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export default OakComponent({
|
|||
objectId: 1,
|
||||
filename: 1,
|
||||
extra1: 1,
|
||||
extra2: 1,
|
||||
extension: 1,
|
||||
type: 1,
|
||||
entity: 1,
|
||||
|
|
@ -20,6 +21,9 @@ export default OakComponent({
|
|||
fileType: 1,
|
||||
sort: 1,
|
||||
isBridge: 1,
|
||||
uploadState: 1,
|
||||
size: 1,
|
||||
applicationId: 1,
|
||||
},
|
||||
features: ['extraFile'],
|
||||
filters: [
|
||||
|
|
@ -55,9 +59,9 @@ export default OakComponent({
|
|||
data: {
|
||||
isModalOpen: false,
|
||||
isModalOpen1: false,
|
||||
renderImgs: [],
|
||||
renderImgs: [], // 读取的原文图片,在modal使用
|
||||
methodsType: '',
|
||||
bridgeUrl: '',
|
||||
bridgeUrl: '', // 通过桥接方式获得的url
|
||||
selectedId: -1,
|
||||
},
|
||||
properties: {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default function render(props) {
|
|||
</Space>
|
||||
<img id="previewImg" src={src} alt="previewImg" className={Style.previewImg} style={{ display: src ? 'inline-block' : 'none' }}/>
|
||||
<div className={Style.methodList}>
|
||||
{methods && methods.map((ele) => (<div className={Style.methodListItem} onClick={() => {
|
||||
{methods && methods.map((ele) => (<div key={ele} className={Style.methodListItem} onClick={() => {
|
||||
chooseMethod(ele);
|
||||
}}>
|
||||
{t(ele)}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,6 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
|
|||
tag2: string;
|
||||
entity: keyof ED2;
|
||||
entityId: string;
|
||||
style?: string | undefined;
|
||||
style?: string;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default OakComponent({
|
|||
objectId: 1,
|
||||
filename: 1,
|
||||
extra1: 1,
|
||||
extra2: 1,
|
||||
extension: 1,
|
||||
type: 1,
|
||||
entity: 1,
|
||||
|
|
@ -19,6 +20,8 @@ export default OakComponent({
|
|||
sort: 1,
|
||||
isBridge: 1,
|
||||
uploadState: 1,
|
||||
size: 1,
|
||||
applicationId: 1,
|
||||
},
|
||||
features: ['extraFile'],
|
||||
formData({ data, features }) {
|
||||
|
|
@ -67,10 +70,10 @@ export default OakComponent({
|
|||
},
|
||||
],
|
||||
properties: {
|
||||
mode: 'aspectFit',
|
||||
size: 3,
|
||||
disablePreview: false,
|
||||
disableDownload: false,
|
||||
mode: 'aspectFit', // 图片显示模式
|
||||
size: 3, // 每行可显示的个数 小程序独有
|
||||
disablePreview: false, // 图片是否可预览
|
||||
disableDownload: false, // 下载按钮隐藏
|
||||
tag1: '',
|
||||
tag2: '',
|
||||
entity: '',
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue