支持es编译

This commit is contained in:
Wang Kejun 2023-09-05 16:43:42 +08:00
parent 19ae9af698
commit 6247000190
1415 changed files with 53449 additions and 293 deletions

115
es/aspects/AspectDict.d.ts vendored Normal file
View File

@ -0,0 +1,115 @@
import { WebEnv, WechatMpEnv } from 'oak-domain/lib/types/Environment';
import { AppType } from '../oak-app-domain/Application/Schema';
import { EntityDict } from '../oak-app-domain';
import { QiniuUploadInfo } from 'oak-frontend-base';
import { Config, Origin } from '../types/Config';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
export declare type GeneralAspectDict<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>> = {
mergeUser: (params: {
from: string;
to: string;
}, context: Cxt) => Promise<void>;
refreshWechatPublicUserInfo: (params: {}, context: Cxt) => Promise<void>;
getWechatMpUserPhoneNumber: (params: {
code: string;
env: WechatMpEnv;
}, context: Cxt) => Promise<string>;
loginByMobile: (params: {
captcha?: string;
password?: string;
mobile: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt) => Promise<string>;
loginWechat: ({ code, env, wechatLoginId, }: {
code: string;
env: WebEnv;
wechatLoginId?: string;
}, context: Cxt) => Promise<string>;
logout: ({}: {}, context: Cxt) => Promise<void>;
loginWechatMp: ({ code, env, }: {
code: string;
env: WechatMpEnv;
}, context: Cxt) => Promise<string>;
syncUserInfoWechatMp: ({ nickname, avatarUrl, encryptedData, iv, signature, }: {
nickname: string;
avatarUrl: string;
encryptedData: string;
iv: string;
signature: string;
}, context: Cxt) => Promise<void>;
wakeupParasite: (params: {
id: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt) => Promise<string>;
getUploadInfo: (params: {
origin: Origin;
bucket?: string;
key?: string;
}, context: Cxt) => Promise<QiniuUploadInfo>;
sendCaptcha: (params: {
mobile: string;
env: WechatMpEnv | WebEnv;
type: 'login' | 'changePassword' | 'confirm';
}, context: Cxt) => Promise<string>;
getApplication: (params: {
type: AppType;
domain: string;
}, context: Cxt) => Promise<string>;
signatureJsSDK: (params: {
url: string;
env: WebEnv;
}, context: Cxt) => Promise<{
signature: any;
noncestr: string;
timestamp: number;
appId: string;
}>;
updateConfig: (params: {
entity: 'platform' | 'system';
entityId: string;
config: Config;
}, context: Cxt) => Promise<void>;
updateApplicationConfig: (params: {
entity: 'application';
entityId: string;
config: EntityDict['application']['Schema']['config'];
}, context: Cxt) => Promise<void>;
switchTo: (params: {
userId: string;
}, context: Cxt) => Promise<void>;
getMpUnlimitWxaCode: (wechatQrCodeId: string, context: Cxt) => Promise<string>;
createWechatLogin: (params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
}, context: Cxt) => Promise<string>;
unbindingWechat: (params: {
wechatUserId: string;
captcha?: string;
mobile?: string;
}, context: Cxt) => Promise<void>;
loginByWechat: (params: {
wechatLoginId: string;
env: WebEnv;
}, context: Cxt) => Promise<string>;
getInfoByUrl: (params: {
url: string;
}) => Promise<{
title: string;
publishDate: number | undefined;
imageList: string[];
}>;
getChangePasswordChannels: (params: {
userId: string;
}, context: Cxt) => Promise<string[]>;
updateUserPassword: (params: {
userId: string;
prevPassword?: string;
mobile?: string;
captcha?: string;
newPassword: string;
}, context: Cxt) => Promise<{
result: string;
times?: number;
}>;
};
export default GeneralAspectDict;

1
es/aspects/AspectDict.js Normal file
View File

@ -0,0 +1 @@
export {};

17
es/aspects/application.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
import { EntityDict } from "../oak-app-domain";
import { AppType } from "../oak-app-domain/Application/Schema";
import { BackendRuntimeContext } from "../context/BackendRuntimeContext";
import { WebEnv } from 'oak-domain/lib/types/Environment';
export declare function getApplication<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
type: AppType;
domain: string;
}, context: Cxt): Promise<string>;
export declare function signatureJsSDK<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ url, env }: {
url: string;
env: WebEnv;
}, context: Cxt): Promise<{
signature: string;
noncestr: string;
timestamp: number;
appId: string;
}>;

58
es/aspects/application.js Normal file
View File

@ -0,0 +1,58 @@
import { assert } from 'oak-domain/lib/utils/assert';
import { applicationProjection } from '../types/projection';
import { WechatSDK, } from 'oak-external-sdk';
export async function getApplication(params, context) {
const { type, domain } = params;
const url = context.getHeader('host');
console.log('url is', url);
const [application] = await context.select('application', {
data: applicationProjection,
filter: {
type,
system: {
domain$system: {
url: domain,
}
},
},
}, {});
//微信小程序环境下 没有就报错
if (type === 'wechatMp') {
assert(application, '微信小程序环境下 application必须存在小程序相关配置');
}
else {
//web 或 wechatPublic
if (type === 'wechatPublic') {
// 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
if (!application) {
const [application2] = await context.select('application', {
data: applicationProjection,
filter: {
type: 'web',
system: {
domain$system: {
url: domain,
}
},
},
}, {});
assert(application2, '微信公众号环境下 application不存在公众号配置但必须存在web相关配置');
return application2.id;
}
}
else {
assert(application, 'web环境下 application必须存在web相关配置');
}
}
return application.id;
}
export async function signatureJsSDK({ url, env }, context) {
const application = context.getApplication();
const { type, config, systemId } = application;
assert(type === 'wechatPublic' && config.type === 'wechatPublic');
const config2 = config;
const { appId, appSecret } = config2;
const wechatInstance = WechatSDK.getInstance(appId, 'wechatPublic', appSecret);
const result = await wechatInstance.signatureJsSDK({ url });
return result;
}

13
es/aspects/config.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
import { EntityDict } from '../oak-app-domain';
import { Config } from '../types/Config';
export declare function updateConfig<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
entity: 'platform' | 'system';
entityId: string;
config: Config;
}, context: Cxt): Promise<void>;
export declare function updateApplicationConfig<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
entity: 'application';
entityId: string;
config: EntityDict['application']['Schema']['config'];
}, context: Cxt): Promise<void>;

27
es/aspects/config.js Normal file
View File

@ -0,0 +1,27 @@
import { generateNewId } from 'oak-domain/lib/utils/uuid';
export async function updateConfig(params, context) {
const { entity, entityId, config } = params;
await context.operate(entity, {
id: generateNewId(),
action: 'update',
data: {
config,
},
filter: {
id: entityId,
}
}, {});
}
export async function updateApplicationConfig(params, context) {
const { entity, entityId, config } = params;
await context.operate(entity, {
id: generateNewId(),
action: 'update',
data: {
config,
},
filter: {
id: entityId,
},
}, {});
}

16
es/aspects/extraFile.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
import { EntityDict } from '../oak-app-domain';
import { Origin } from '../types/Config';
import { QiniuUploadInfo } from 'oak-frontend-base';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
export declare function getUploadInfo<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
origin: Origin;
bucket?: string;
key?: string;
}, context: Cxt): Promise<QiniuUploadInfo>;
export declare function getInfoByUrl(params: {
url: string;
}): Promise<{
title: string;
publishDate: number | undefined;
imageList: string[];
}>;

15
es/aspects/extraFile.js Normal file
View File

@ -0,0 +1,15 @@
import { getConfig } from '../utils/getContextConfig';
import { assert } from 'oak-domain/lib/utils/assert';
import { WechatSDK } from 'oak-external-sdk';
export async function getUploadInfo(params, context) {
const { origin, key, bucket } = params;
const { instance, config, } = await getConfig(context, 'Cos', origin);
assert(origin === 'qiniu');
const { uploadHost, domain, bucket: bucket2 } = config;
return instance.getUploadInfo(uploadHost, domain, bucket || bucket2, key);
}
// 请求链接获取标题,发布时间,图片等信息
export async function getInfoByUrl(params) {
const { url } = params;
return await WechatSDK.analyzePublicArticle(url);
}

36
es/aspects/index.d.ts vendored Normal file
View File

@ -0,0 +1,36 @@
import { loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptcha, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite } from './token';
import { getUploadInfo, getInfoByUrl } from './extraFile';
import { getApplication, signatureJsSDK } from './application';
import { updateConfig, updateApplicationConfig } from './config';
import { mergeUser, getChangePasswordChannels, updateUserPassword } from './user';
import { createWechatLogin } from './wechatLogin';
import { unbindingWechat } from './wechatUser';
import { getMpUnlimitWxaCode } from './wechatQrCode';
import { confirmUserEntityGrant } from './userEntityGrant';
declare const aspectDict: {
mergeUser: typeof mergeUser;
switchTo: typeof switchTo;
refreshWechatPublicUserInfo: typeof refreshWechatPublicUserInfo;
loginByMobile: typeof loginByMobile;
loginWechat: typeof loginWechat;
loginWechatMp: typeof loginWechatMp;
wakeupParasite: typeof wakeupParasite;
syncUserInfoWechatMp: typeof syncUserInfoWechatMp;
getUploadInfo: typeof getUploadInfo;
sendCaptcha: typeof sendCaptcha;
getApplication: typeof getApplication;
updateConfig: typeof updateConfig;
updateApplicationConfig: typeof updateApplicationConfig;
getWechatMpUserPhoneNumber: typeof getWechatMpUserPhoneNumber;
logout: typeof logout;
signatureJsSDK: typeof signatureJsSDK;
createWechatLogin: typeof createWechatLogin;
unbindingWechat: typeof unbindingWechat;
loginByWechat: typeof loginByWechat;
getInfoByUrl: typeof getInfoByUrl;
getChangePasswordChannels: typeof getChangePasswordChannels;
updateUserPassword: typeof updateUserPassword;
getMpUnlimitWxaCode: typeof getMpUnlimitWxaCode;
confirmUserEntityGrant: typeof confirmUserEntityGrant;
};
export default aspectDict;

36
es/aspects/index.js Normal file
View File

@ -0,0 +1,36 @@
import { loginByMobile, loginWechat, loginWechatMp, syncUserInfoWechatMp, sendCaptcha, switchTo, refreshWechatPublicUserInfo, getWechatMpUserPhoneNumber, logout, loginByWechat, wakeupParasite, } from './token';
import { getUploadInfo, getInfoByUrl } from './extraFile';
import { getApplication, signatureJsSDK } from './application';
import { updateConfig, updateApplicationConfig } from './config';
import { mergeUser, getChangePasswordChannels, updateUserPassword } from './user';
import { createWechatLogin } from './wechatLogin';
import { unbindingWechat } from './wechatUser';
import { getMpUnlimitWxaCode } from './wechatQrCode';
import { confirmUserEntityGrant } from './userEntityGrant';
const aspectDict = {
mergeUser,
switchTo,
refreshWechatPublicUserInfo,
loginByMobile,
loginWechat,
loginWechatMp,
wakeupParasite,
syncUserInfoWechatMp,
getUploadInfo,
sendCaptcha,
getApplication,
updateConfig,
updateApplicationConfig,
getWechatMpUserPhoneNumber,
logout,
signatureJsSDK,
createWechatLogin,
unbindingWechat,
loginByWechat,
getInfoByUrl,
getChangePasswordChannels,
updateUserPassword,
getMpUnlimitWxaCode,
confirmUserEntityGrant,
};
export default aspectDict;

69
es/aspects/token.d.ts vendored Normal file
View File

@ -0,0 +1,69 @@
import { EntityDict } from '../oak-app-domain';
import { WebEnv, WechatMpEnv } from 'oak-domain/lib/types/Environment';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
export declare function loginByMobile<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
captcha?: string;
password?: string;
mobile: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt): Promise<string>;
export declare function refreshWechatPublicUserInfo<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({}: {}, context: Cxt): Promise<void>;
export declare function loginByWechat<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
wechatLoginId: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt): Promise<string>;
/**
*
* @param param0
* @param context
*/
export declare function loginWechat<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ code, env, wechatLoginId, }: {
code: string;
env: WebEnv;
wechatLoginId?: string;
}, context: Cxt): Promise<string>;
/**
*
* @param param0
* @param context
* @returns
*/
export declare function loginWechatMp<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ code, env, }: {
code: string;
env: WechatMpEnv;
}, context: Cxt): Promise<string>;
/**
* wx.getUserProfile拿到的用户信息
* @param param0
* @param context
*/
export declare function syncUserInfoWechatMp<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ nickname, avatarUrl, encryptedData, iv, signature, }: {
nickname: string;
avatarUrl: string;
encryptedData: string;
iv: string;
signature: string;
}, context: Cxt): Promise<void>;
export declare function sendCaptcha<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ mobile, env, type: type2 }: {
mobile: string;
env: WechatMpEnv | WebEnv;
type: 'login' | 'changePassword' | 'confirm';
}, context: Cxt): Promise<string>;
export declare function switchTo<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ userId }: {
userId: string;
}, context: Cxt): Promise<void>;
export declare function getWechatMpUserPhoneNumber<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ code, env }: {
code: string;
env: WechatMpEnv;
}, context: Cxt): Promise<string>;
export declare function logout<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({}: {}, context: Cxt): Promise<void>;
/**
* parasite上的token
* @param params
* @param context
* @returns
*/
export declare function wakeupParasite<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
id: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt): Promise<string>;

1265
es/aspects/token.js Normal file

File diff suppressed because it is too large Load Diff

23
es/aspects/user.d.ts vendored Normal file
View File

@ -0,0 +1,23 @@
import { BackendRuntimeContext } from "../context/BackendRuntimeContext";
import { EntityDict } from "../oak-app-domain";
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
export declare function mergeUser<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
from: string;
to: string;
}, context: Cxt, innerLogic?: boolean): Promise<void>;
export declare function getChangePasswordChannels<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
userId: string;
}, context: Cxt, innerLogic?: boolean): Promise<string[]>;
export declare function updateUserPassword<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
userId: string;
prevPassword?: string;
captcha?: string;
newPassword: string;
mobile?: string;
}, context: Cxt, innerLogic?: boolean): Promise<{
result: string;
times: number;
} | {
result: string;
times?: undefined;
}>;

272
es/aspects/user.js Normal file
View File

@ -0,0 +1,272 @@
import { OakUserUnpermittedException } 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';
import dayjs from 'dayjs';
export async function mergeUser(params, context, innerLogic) {
if (!innerLogic && !context.isRoot()) {
throw new OakUserUnpermittedException('不允许执行mergeUser操作');
}
const { from, to } = params;
assert(from);
assert(to);
assert(from !== to, '不能merge到相同user');
const schema = context.getSchema();
/* for (const entity in schema) {
if (['oper', 'modi', 'operEntity', 'modiEntity', 'userEntityGrant', 'wechatQrCode'].includes(entity)) {
continue;
}
const entityDesc = schema[entity];
if (entityDesc.view) {
continue;
}
const { attributes } = entityDesc;
for (const attr in attributes) {
const attrDef = attributes[attr as keyof typeof attributes];
if (attrDef.type === 'ref' && attrDef.ref === 'user') {
await context.operate(entity, {
action: 'update',
data: {
[attr]: to,
},
filter: {
[attr]: from,
}
} as any, { dontCollect: true, dontCreateOper: true, dontCreateModi: true });
}
if (attr === 'entity' && attributes.hasOwnProperty('entityId')) {
await context.operate(entity, {
action: 'update',
data: {
entityId: to,
},
filter: {
entity: 'user',
entityId: from,
}
} as any, { dontCollect: true, dontCreateOper: true, dontCreateModi: true });
}
}
} */
await context.operate('token', {
id: await generateNewIdAsync(),
action: 'disable',
data: {},
filter: {
ableState: 'enabled',
playerId: from, // todo 这里是playerId, root如果正在扮演该用户待处理
},
}, { dontCollect: true });
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'merge',
data: {
refId: to,
userState: 'merged',
},
filter: {
$or: [
{
id: from,
},
{
userState: 'merged',
refId: from,
}
],
},
}, {});
}
export async function getChangePasswordChannels(params, context, innerLogic) {
const { userId } = params;
const mobileList = await context.select('mobile', {
data: {
id: 1,
mobile: 1,
userId: 1,
},
filter: {
userId,
ableState: 'enabled',
},
}, {});
const [user] = await context.select('user', {
data: {
id: 1,
password: 1,
},
filter: {
id: userId,
}
}, {});
const result = [];
if (mobileList.length > 0) {
result.push('mobile');
}
if (user.password) {
result.push('password');
}
return result;
}
export async function updateUserPassword(params, context, innerLogic) {
const { userId, prevPassword, captcha, mobile, newPassword } = params;
const closeRootMode = context.openRootMode();
try {
const [user] = await context.select('user', {
data: {
id: 1,
password: 1,
}
}, {});
if (prevPassword) {
const [lastSuccessfulTemp] = await context.select('changePasswordTemp', {
data: {
id: 1,
$$seq$$: 1,
},
filter: {
userId,
$$createAt$$: {
$gt: dayjs().startOf('day').valueOf(),
},
result: 'success',
},
sorter: [
{
$attr: {
$$seq$$: 1,
},
$direction: 'desc',
},
],
indexFrom: 0,
count: 1,
}, {});
const count1 = await context.count('changePasswordTemp', {
filter: lastSuccessfulTemp ? {
userId,
$$seq$$: {
$gt: lastSuccessfulTemp.$$seq$$,
},
result: 'fail',
} : {
userId,
$$createAt$$: {
$gt: dayjs().startOf('day').valueOf(),
},
result: 'fail',
},
}, {});
if (count1 >= 5) {
closeRootMode();
return {
result: '您今天已尝试过太多次,请稍候再进行操作',
times: count1,
};
}
if (user.password === prevPassword) {
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'update',
data: {
password: newPassword,
passwordSha1: encryptPasswordSha1(newPassword)
},
filter: {
id: userId,
},
}, {});
await context.operate('changePasswordTemp', {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
userId,
prevPassword,
newPassword,
result: 'success',
},
}, {});
closeRootMode();
return {
result: 'success'
};
}
else {
await context.operate('changePasswordTemp', {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
userId,
prevPassword,
newPassword,
result: 'fail',
},
}, {});
closeRootMode();
return {
result: '原密码不正确,请检查后输入',
times: count1,
};
}
}
if (mobile && captcha) {
const [aliveCaptcha] = await context.select('captcha', {
data: {
id: 1,
},
filter: {
mobile,
code: captcha,
expired: false,
},
indexFrom: 0,
count: 1,
}, {});
if (aliveCaptcha) {
await context.operate('user', {
id: await generateNewIdAsync(),
action: 'update',
data: {
password: newPassword,
passwordSha1: encryptPasswordSha1(newPassword)
},
filter: {
id: userId,
},
}, {});
await context.operate('changePasswordTemp', {
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
userId,
prevPassword: user.password,
newPassword,
result: 'success',
},
}, {});
closeRootMode();
return {
result: 'success'
};
}
else {
closeRootMode();
return {
result: '验证码错误',
};
}
}
closeRootMode();
return {
result: '缺少原密码或验证码,请检查后再进行操作'
};
}
catch (err) {
closeRootMode();
throw err;
}
}

7
es/aspects/userEntityGrant.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { BackendRuntimeContext } from "../context/BackendRuntimeContext";
import { EntityDict } from "../oak-app-domain";
import { WebEnv, WechatMpEnv } from 'oak-domain/lib/types/Environment';
export declare function confirmUserEntityGrant<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
id: string;
env: WebEnv | WechatMpEnv;
}, context: Cxt): Promise<number>;

View File

@ -0,0 +1,101 @@
import { generateNewId, generateNewIdAsync } from "oak-domain/lib/utils/uuid";
import { OakRowInconsistencyException, OakExternalException } from 'oak-domain/lib/types';
export async function confirmUserEntityGrant(params, context) {
const { id, env } = params;
const { userId } = context.getToken();
const [userEntityGrant] = await context.select('userEntityGrant', {
data: {
id: 1,
entity: 1,
entityId: 1,
relationId: 1,
number: 1,
confirmed: 1,
},
filter: {
id,
},
indexFrom: 0,
count: 1,
}, {
dontCollect: true,
});
const closeRootMode = context.openRootMode();
const { number, confirmed } = userEntityGrant;
if (confirmed >= number) {
closeRootMode();
throw new OakExternalException(`超出分享上限人数${number}`);
}
Object.assign(userEntityGrant, {
confirmed: confirmed + 1,
});
if (number === 1) {
// 单次分享 附上接收者id
Object.assign(userEntityGrant, {
granteeId: userId,
});
}
const { entity, entityId, relationId, granterId, type } = userEntityGrant;
const result2 = await context.select('userRelation', {
data: {
id: 1,
userId: 1,
relationId: 1,
},
filter: {
userId: userId,
relationId,
entity,
entityId,
},
indexFrom: 0,
count: 1,
}, {
dontCollect: true,
});
if (result2.length) {
const e = new OakRowInconsistencyException(undefined, '已领取该权限');
e.addData('userRelation', result2);
closeRootMode();
throw e;
}
else {
try {
await context.operate('userRelation', {
id: generateNewId(),
action: 'create',
data: {
id: generateNewId(),
userId,
relationId,
entity,
entityId,
},
}, {
dontCollect: true,
});
// todo type是转让的话 需要回收授权者的关系
if (type === 'transfer') {
await context.operate('userRelation', {
id: await generateNewIdAsync(),
action: 'remove',
data: {},
filter: {
relationId,
userId: granterId,
entity,
entityId,
},
}, {
dontCollect: true,
});
}
}
catch (err) {
closeRootMode();
throw err;
}
closeRootMode();
return 1;
}
}

6
es/aspects/wechatLogin.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
import { EntityDict } from "../oak-app-domain";
import { BackendRuntimeContext } from "../context/BackendRuntimeContext";
export declare function createWechatLogin<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
type: EntityDict['wechatLogin']['Schema']['type'];
interval: number;
}, context: Cxt): Promise<string>;

30
es/aspects/wechatLogin.js Normal file
View File

@ -0,0 +1,30 @@
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
export async function createWechatLogin(params, context) {
const { type, interval } = params;
let userId;
if (type === 'bind') {
userId = context.getCurrentUserId();
}
const id = await generateNewIdAsync();
const createData = {
id,
type,
expiresAt: Date.now() + interval,
expired: false,
qrCodeType: 'wechatPublic',
successed: false,
};
if (userId) {
Object.assign(createData, {
userId,
});
}
await context.operate('wechatLogin', {
id: await generateNewIdAsync(),
action: 'create',
data: createData,
}, {
dontCollect: true,
});
return id;
}

22
es/aspects/wechatQrCode.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
import { EntityDict } from "../oak-app-domain";
import { QrCodeType } from '../types/Config';
import { WechatQrCodeProps } from '../oak-app-domain/WechatQrCode/Schema';
import { BackendRuntimeContext } from '../context/BackendRuntimeContext';
/**
*
* 0SystemConfig中指定了qrCodeTypeqrCodeType去生成
* 1
* 2qrCodePrefix
* @param options
* @param context
* @returns
*/
export declare function createWechatQrCode<ED extends EntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(options: {
entity: T;
entityId: string;
tag?: string;
permanent?: boolean;
type?: QrCodeType;
props: WechatQrCodeProps;
}, context: Cxt): Promise<void>;
export declare function getMpUnlimitWxaCode<ED extends EntityDict, T extends keyof ED, Cxt extends BackendRuntimeContext<ED>>(wechatQrCodeId: string, context: Cxt): Promise<string>;

288
es/aspects/wechatQrCode.js Normal file
View File

@ -0,0 +1,288 @@
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { assert } from 'oak-domain/lib/utils/assert';
import { WechatSDK, } from 'oak-external-sdk';
import { shrinkUuidTo32Bytes } from 'oak-domain/lib/utils/uuid';
/**
* 生成二维码优先级如下
* 0如果在SystemConfig中指定了qrCodeType则按照qrCodeType去生成
* 1如果有服务号优先生成关注服务号的带参二维码
* 2如果有小程序优先生成小程序的二维码如果小程序中配置了qrCodePrefix其次生成小程序码
* @param options
* @param context
* @returns
*/
export async function createWechatQrCode(options, context) {
const { entity, entityId, tag, permanent = false, props, type: qrCodeType } = options;
const applicationId = context.getApplicationId();
assert(applicationId);
const [system] = await context.select('system', {
data: {
id: 1,
config: 1,
application$system: {
$entity: 'application',
data: {
id: 1,
type: 1,
config: 1,
systemId: 1,
},
},
},
filter: {
application$system: {
id: applicationId,
}
},
}, {
dontCollect: true,
});
let appId = '', appType = undefined;
let url = undefined;
const { application$system: applications, config: sysConfig } = system;
if (!applications || applications?.length === 0) {
throw new Error('无法生成二维码找不到此system下的应用信息');
}
const id = generateNewId();
if (qrCodeType) {
switch (qrCodeType) {
case 'wechatPublic':
{
const self = applications.find((ele) => ele.type === 'wechatPublic');
if (!(self && self.type === 'wechatPublic' &&
self.config.isService)) {
throw new Error('无法生成公众号二维码,服务号未正确配置');
}
appId = self.id;
appType = 'wechatPublic';
break;
}
case 'wechatMpDomainUrl': {
const self = applications.find((ele) => ele.type === 'wechatMp');
if (!(self.type === 'wechatMp' &&
self.config.qrCodePrefix)) {
throw new Error('无法生成小程序地址码,未配置跳转前缀');
}
url = `${self.config.qrCodePrefix}/${id}`;
appId = self.id;
appType = 'wechatMpDomainUrl';
break;
}
case 'wechatMpWxaCode': {
const self = applications.find((ele) => ele.type === 'wechatMp');
if (self.type !== 'wechatMp') {
throw new Error('无法生成小程序地址码,未配置跳转前缀');
}
appId = self.id;
appType = 'wechatMpWxaCode';
break;
}
case 'wechatPublicForMp': {
const self = applications.find((ele) => ele.type === 'wechatPublic');
if (!(self && self.type === 'wechatPublic' &&
self.config.isService)) {
throw new Error('无法生成公众号-小程序二维码,服务号未正确配置');
}
const selfMp = applications.find((ele) => ele.type === 'wechatMp');
if (!(selfMp && selfMp.config.appId &&
selfMp.config.appSecret)) {
throw new Error('无法生成公众号-小程序二维码,小程序未正确配置');
}
appId = self.id;
appType = 'wechatPublic';
break;
}
default: {
throw new Error('当前类型二维码暂不支持');
}
}
}
else {
if (sysConfig.App.qrCodeApplicationId) {
appId = sysConfig.App.qrCodeApplicationId;
appType = sysConfig.App.qrCodeType;
}
else {
const self = applications.find((ele) => ele.id === applicationId);
// 如果本身是服务号,则优先用自己的
if (self.type === 'wechatPublic' &&
self.config.isService) {
appId = applicationId;
appType = 'wechatPublic';
}
else if (self?.type === 'wechatMp') {
// 如果本身是小程序,则次优先用小程序的地址码,再次优先用二维码
appId = self.id;
if (self.config.qrCodePrefix) {
appType = 'wechatMpDomainUrl';
url = `${self.config.qrCodePrefix}/${id}`;
}
else {
appType = 'wechatMpWxaCode';
}
}
else {
// 查找有没有服务号或者小程序的相关配置,如果有则使用之
const publicApp = applications.find((ele) => ele.type === 'wechatPublic' &&
ele.config.isService);
if (publicApp) {
appId = publicApp.id;
appType = 'wechatPublic';
}
else {
const mpApp = applications.find((ele) => ele.type === 'wechatMp');
if (mpApp) {
appId = mpApp.id;
if (mpApp.config.qrCodePrefix) {
appType = 'wechatMpDomainUrl';
url = `${mpApp.config.qrCodePrefix}/${id}`;
}
else {
appType = 'wechatMpWxaCode';
}
}
}
}
}
}
if (!appId || !appType) {
throw new Error('无法生成二维码找不到此system下的服务号或者小程序信息');
}
const data = {
id,
type: appType,
tag,
entity: entity,
entityId,
applicationId: appId,
allowShare: true,
permanent,
url,
expired: false,
expiresAt: Date.now() + 2592000 * 1000,
props,
};
// 直接创建
const { type } = data;
const application = applications.find((ele) => ele.id === data.applicationId);
assert(application);
const { type: applicationType, config } = application;
// console.log(process.env.OAK_PLATFORM, process.env.NODE_ENV);
switch (type) {
case 'wechatMpWxaCode': {
assert(applicationType === 'wechatMp' && config.type === 'wechatMp');
const config2 = config;
const { appId, appSecret } = config2;
// if (process.env.OAK_PLATFORM === 'web') {
// Object.assign(data, {
// buffer: 'develop环境下无法真实获取二维码数据',
// });
// }
// else {
// // 小程序码去实时获取(暂时不考虑缓存)
// const wechatInstance = WechatSDK.getInstance(
// appId,
// 'wechatMp',
// appSecret
// ) as WechatMpInstance;
// const envVersionVersionDict = {
// development: 'develop',
// staging: 'trial',
// production: 'release',
// };
// const buffer = await wechatInstance.getMpUnlimitWxaCode({
// scene: shrinkUuidTo32Bytes(id),
// envVersion:
// envVersionVersionDict[
// process.env
// .NODE_ENV as keyof typeof envVersionVersionDict
// ] as 'release',
// page: 'pages/wechatQrCode/scan/index', // todo这里用其它的页面微信服务器拒绝因为没发布。应该是 pages/wechatQrCode/scan/index
// });
// // 把arrayBuffer转成字符串返回
// const str = String.fromCharCode(...new Uint8Array(buffer));
// Object.assign(data, {
// buffer: str,
// });
// }
break;
}
case 'wechatPublicForMp':
case 'wechatPublic': {
assert(applicationType === 'wechatPublic' &&
config.type === 'wechatPublic');
if (process.env.OAK_PLATFORM === 'web') {
Object.assign(data, {
ticket: 'develop环境下无法真实获取二维码数据',
url: `http://localhost:3000/wechatQrCode/scan?scene=${shrinkUuidTo32Bytes(id)}`,
});
}
else {
const config2 = config;
const { appId, appSecret } = config2;
const wechatInstance = WechatSDK.getInstance(appId, 'wechatPublic', appSecret);
const result = await wechatInstance.getQrCode({
sceneStr: shrinkUuidTo32Bytes(id),
isPermanent: false,
expireSeconds: 2592000,
});
Object.assign(data, {
ticket: result?.ticket,
url: result?.url,
});
}
break;
}
case 'wechatMpDomainUrl': {
break;
}
default: {
assert(false, `未实现的${type}`);
}
}
await context.operate('wechatQrCode', {
id: generateNewId(),
action: 'create',
data,
}, {
dontCollect: true,
});
}
export async function getMpUnlimitWxaCode(wechatQrCodeId, context) {
const [wechatQrCode] = await context.select('wechatQrCode', {
data: {
id: 1,
application: {
id: 1,
config: 1,
},
},
filter: {
id: wechatQrCodeId,
},
}, {});
const application = wechatQrCode.application;
const { config } = application;
const config2 = config;
const { appId, appSecret } = config2;
if (process.env.OAK_PLATFORM === 'web') {
return 'develop环境下无法真实获取二维码数据';
}
else {
// 小程序码去实时获取(暂时不考虑缓存)
const wechatInstance = WechatSDK.getInstance(appId, 'wechatMp', appSecret);
const envVersionVersionDict = {
development: 'develop',
staging: 'trial',
production: 'release',
};
const buffer = await wechatInstance.getMpUnlimitWxaCode({
scene: shrinkUuidTo32Bytes(wechatQrCodeId),
envVersion: envVersionVersionDict[process.env.NODE_ENV],
page: 'pages/wechatQrCode/scan/index', // todo这里用其它的页面微信服务器拒绝因为没发布。应该是 pages/wechatQrCode/scan/index
});
// 把arrayBuffer转成字符串返回
const str = String.fromCharCode(...new Uint8Array(buffer));
return str;
}
}

7
es/aspects/wechatUser.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { EntityDict } from "../oak-app-domain";
import { BackendRuntimeContext } from "../context/BackendRuntimeContext";
export declare function unbindingWechat<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
wechatUserId: string;
captcha?: string;
mobile?: string;
}, context: Cxt): Promise<void>;

65
es/aspects/wechatUser.js Normal file
View File

@ -0,0 +1,65 @@
import { OakUserException } from 'oak-domain/lib/types';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import assert from "assert";
export async function unbindingWechat(params, context) {
const { wechatUserId, captcha, mobile } = params;
const fn = async () => {
const userId = context.getCurrentUserId();
// 校验传入的wechatUser.userId 是否和当前登录者的id相等
const [wechatUser] = await context.select('wechatUser', {
data: {
id: 1,
userId: 1,
},
filter: {
id: wechatUserId,
}
}, {});
assert(wechatUser.userId === userId, '查询到的wechatUser.userId与当前登录者不相同');
await context.operate('wechatUser', {
id: await generateNewIdAsync(),
action: 'update',
data: {
userId: null,
},
filter: {
id: wechatUserId,
}
}, {
dontCollect: true,
});
};
if (mobile && captcha) {
const result = await context.select('captcha', {
data: {
id: 1,
expired: 1,
},
filter: {
mobile,
code: captcha,
},
sorter: [{
$attr: {
$$createAt$$: 1,
},
$direction: 'desc',
}],
indexFrom: 0,
count: 1,
}, { dontCollect: true });
if (result.length > 0) {
const [captchaRow] = result;
if (captchaRow.expired) {
throw new OakUserException('验证码已经过期');
}
fn();
}
else {
throw new OakUserException('验证码无效');
}
}
else {
fn();
}
}

4
es/auth/index.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
import { AuthDefDict } from 'oak-domain/lib/types/Auth';
import { EntityDict } from '../oak-app-domain';
declare const _default: AuthDefDict<EntityDict>;
export default _default;

3
es/auth/index.js Normal file
View File

@ -0,0 +1,3 @@
export default {
// mobile,
};

4
es/auth/mobile.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
import { AuthDef } from 'oak-domain/lib/types/Auth';
import { EntityDict } from '../oak-app-domain';
declare const authDef: AuthDef<EntityDict, 'mobile'>;
export default authDef;

9
es/auth/mobile.js Normal file
View File

@ -0,0 +1,9 @@
const userAuth = {
cascadePath: '',
};
const authDef = {
actionAuth: {
create: [userAuth],
}
};
export default authDef;

5
es/checkers/address.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from "oak-domain/lib/types";
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'address', RuntimeCxt>[];
export default checkers;

44
es/checkers/address.js Normal file
View File

@ -0,0 +1,44 @@
import { isMobile } from 'oak-domain/lib/utils/validator';
import { OakInputIllegalException } from "oak-domain/lib/types";
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'address',
checker: (data) => {
if (data instanceof Array) {
data.forEach(ele => {
checkAttributesNotNull('address', ele, ['name', 'detail', 'phone' /* , 'areaId' */]);
if (!isMobile(ele.phone)) {
throw new OakInputIllegalException('address', ['phone'], '手机号非法');
}
});
}
else {
checkAttributesNotNull('address', data, ['name', 'detail', 'phone' /* , 'areaId' */]);
if (!isMobile(data.phone)) {
throw new OakInputIllegalException('address', ['phone'], '手机号非法');
}
}
},
},
{
type: 'data',
action: 'update',
entity: 'address',
checker: (data) => {
if (data.name === '') {
throw new OakInputIllegalException('address', ['name'], '姓名不可为空');
}
if (data.detail === '') {
throw new OakInputIllegalException('address', ['name'], '详细地址不可为空');
}
if (data.hasOwnProperty('phone') && !isMobile(data.phone)) {
throw new OakInputIllegalException('address', ['phone'], '手机号非法');
}
return;
},
}
];
export default checkers;

5
es/checkers/application.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'application', RuntimeCxt>[];
export default checkers;

View File

@ -0,0 +1,37 @@
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'application',
checker: (data, context) => {
const setData = (data) => {
if (!data.config) {
Object.assign(data, {
config: {},
});
}
};
if (data instanceof Array) {
data.forEach((ele) => {
checkAttributesNotNull('application', ele, [
'name',
'type',
'systemId',
]);
setData(ele);
});
}
else {
checkAttributesNotNull('application', data, [
'name',
'type',
'systemId',
]);
setData(data);
}
return;
},
},
];
export default checkers;

2
es/checkers/index.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare const checkers: (import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "address", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "token", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "user", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "userEntityGrant", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatQrCode", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "application", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "mobile", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "wechatPublicTag", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "message", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "messageTypeTemplateId", import("..").RuntimeCxt> | import("oak-domain/lib/types").Checker<import("../oak-app-domain").EntityDict, "parasite", import("..").RuntimeCxt>)[];
export default checkers;

25
es/checkers/index.js Normal file
View File

@ -0,0 +1,25 @@
import addressCheckers from './address';
import tokenCheckers from './token';
import userCheckers from './user';
import userEntityGrantCheckers from './userEntityGrant';
import wechatQrCodeCheckers from './wechatQrCode';
import applicationCheckers from './application';
import mobileChecker from './mobile';
import wechatPublicTagChecker from './wechatPublicTag';
import messageChecker from './message';
import messageTypeTemplateId from './messageTypeTemplateId';
import parasite from './parasite';
const checkers = [
...mobileChecker,
...addressCheckers,
...tokenCheckers,
...userCheckers,
...userEntityGrantCheckers,
...wechatQrCodeCheckers,
...applicationCheckers,
...wechatPublicTagChecker,
...messageChecker,
...messageTypeTemplateId,
...parasite,
];
export default checkers;

5
es/checkers/message.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from "oak-domain/lib/types";
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'message', RuntimeCxt>[];
export default checkers;

25
es/checkers/message.js Normal file
View File

@ -0,0 +1,25 @@
import { combineFilters } from 'oak-domain/lib/store/filter';
const checkers = [
{
type: 'logical',
action: 'select',
entity: 'message',
checker: (operation, context) => {
const systemId = context.getSystemId();
if (!systemId) {
return;
}
const isRoot = context.isRoot();
if (isRoot) {
return;
}
const filter = {
messageSystem$message: {
systemId,
}
};
operation.filter = operation.filter ? combineFilters('message', context.getSchema(), [operation.filter, filter]) : filter;
},
}
];
export default checkers;

View File

@ -0,0 +1,5 @@
import { Checker } from "oak-domain/lib/types";
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'messageTypeTemplateId', RuntimeCxt>[];
export default checkers;

View File

@ -0,0 +1,14 @@
import assert from "assert";
import { checkAttributesNotNull } from "oak-domain/lib/utils/validator";
const checkers = [
{
type: 'data',
action: 'create',
entity: 'messageTypeTemplateId',
checker: (data, context) => {
assert(!(data instanceof Array));
checkAttributesNotNull('messageTypeTemplateId', data, ['type', 'templateId', 'applicationId']);
}
}
];
export default checkers;

5
es/checkers/mobile.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'mobile', RuntimeCxt>[];
export default checkers;

33
es/checkers/mobile.js Normal file
View File

@ -0,0 +1,33 @@
import { assert } from 'oak-domain/lib/utils/assert';
import { isMobile } from 'oak-domain/lib/utils/validator';
import { OakInputIllegalException } from 'oak-domain/lib/types';
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'mobile',
checker: (data) => {
assert(!(data instanceof Array));
checkAttributesNotNull('mobile', data, ['mobile']);
if (!isMobile(data.mobile)) {
throw new OakInputIllegalException('mobile', ['mobile'], '手机号非法');
}
},
},
{
type: 'data',
action: 'update',
entity: 'mobile',
checker: (data) => {
assert(!(data instanceof Array));
if (data.hasOwnProperty('mobile')) {
checkAttributesNotNull('mobile', data, ['mobile']);
if (!isMobile(data.mobile)) {
throw new OakInputIllegalException('mobile', ['mobile'], '手机号非法');
}
}
},
},
];
export default checkers;

5
es/checkers/parasite.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'parasite', RuntimeCxt>[];
export default checkers;

63
es/checkers/parasite.js Normal file
View File

@ -0,0 +1,63 @@
import { OakRowInconsistencyException } from 'oak-domain/lib/types';
import { assert } from 'oak-domain/lib/utils/assert';
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'parasite',
checker: (data, context) => {
// const { data } = operation as EntityDict['parasite']['Create'];
assert(!(data instanceof Array));
checkAttributesNotNull('parasite', data, ['expiresAt', 'tokenLifeLength']);
if (data.userId) {
const users2 = context.select('user', {
data: {
id: 1,
userState: 1,
},
filter: {
id: data.userId,
},
}, { dontCollect: true });
const checkUser = (users) => {
const [user] = users;
if (user.userState !== 'shadow') {
throw new OakRowInconsistencyException({
a: 's',
d: {
user: {
[user.id]: user,
},
},
});
}
};
if (users2 instanceof Promise) {
return users2.then((u) => checkUser(u));
}
return checkUser(users2);
}
assert(data.user && data.user.action === 'create');
},
},
{
type: 'row',
entity: 'parasite',
action: ['cancel'],
errMsg: '您没有设置失效的权限',
filter: {
expired: false,
},
},
{
type: 'row',
entity: 'parasite',
action: ['qrcode'],
errMsg: '您没有查看二维码的权限',
filter: {
expired: false,
},
},
];
export default checkers;

5
es/checkers/platform.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'platform', RuntimeCxt>[];
export default checkers;

29
es/checkers/platform.js Normal file
View File

@ -0,0 +1,29 @@
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'platform',
checker: (data) => {
const setData = (data) => {
if (!data.config) {
Object.assign(data, {
config: {},
});
}
};
if (data instanceof Array) {
data.forEach((ele) => {
checkAttributesNotNull('platform', ele, ['name']);
setData(ele);
});
}
else {
checkAttributesNotNull('platform', data, ['name']);
setData(data);
}
return;
},
},
];
export default checkers;

5
es/checkers/system.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'system', RuntimeCxt>[];
export default checkers;

34
es/checkers/system.js Normal file
View File

@ -0,0 +1,34 @@
import { checkAttributesNotNull } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'system',
checker: (data) => {
const setData = (data) => {
if (!data.config) {
Object.assign(data, {
config: {},
});
}
if (!data.super) {
Object.assign(data, {
super: false,
});
}
};
if (data instanceof Array) {
data.forEach((ele) => {
checkAttributesNotNull('system', ele, ['name', 'platformId']);
setData(ele);
});
}
else {
checkAttributesNotNull('system', data, ['name', 'platformId']);
setData(data);
}
return;
},
},
];
export default checkers;

5
es/checkers/token.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from "oak-domain/lib/types";
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from "../types/RuntimeCxt";
declare const checkers: Checker<EntityDict, 'token', RuntimeCxt>[];
export default checkers;

2
es/checkers/token.js Normal file
View File

@ -0,0 +1,2 @@
const checkers = [];
export default checkers;

5
es/checkers/user.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from "oak-domain/lib/types";
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from "../types/RuntimeCxt";
declare const checkers: Checker<EntityDict, 'user', RuntimeCxt>[];
export default checkers;

85
es/checkers/user.js Normal file
View File

@ -0,0 +1,85 @@
import { judgeRelation } from "oak-domain/lib/store/relation";
import { OakInputIllegalException, OakUserUnpermittedException } from "oak-domain/lib/types";
const checkers = [
{
type: 'row',
action: 'remove',
entity: 'user',
filter: {
userState: 'shadow',
}
},
{
type: 'relation',
action: ['remove', 'disable', 'enable'],
entity: 'user',
relationFilter: () => {
// 只有root才能进行操作
throw new OakUserUnpermittedException();
},
errMsg: '越权操作',
},
{
type: 'data',
action: 'grant',
entity: 'user',
checker: (data) => {
if (Object.keys(data).filter(ele => !ele.includes('$')).length > 0) {
throw new OakInputIllegalException('user', Object.keys(data), '授权不允许传入其它属性');
}
}
},
{
type: 'row',
action: 'disable',
entity: 'user',
filter: {
isRoot: false,
},
errMsg: '不能禁用root用户',
},
// {
// type: 'row',
// action: 'select',
// entity: 'user',
// filter: (operation, context) => {
// const systemId = context.getSystemId();
// // todo 查询用户 先不加systemId
// if (systemId) {
// return {
// id: {
// $in: {
// entity: 'userSystem',
// data: {
// userId: 1,
// },
// filter: {
// systemId,
// },
// },
// },
// };
// }
// },
// },
{
entity: 'user',
action: 'update',
type: 'relation',
relationFilter: (operation, context) => {
const userId = context.getCurrentUserId();
const { data } = operation;
for (const attr in data) {
const rel = judgeRelation(context.getSchema(), 'user', attr);
if (rel === 1) {
return {
id: userId,
};
}
}
return undefined;
},
errMsg: '您不能更新他人信息',
}
];
export default checkers;

5
es/checkers/userEntityGrant.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'userEntityGrant', RuntimeCxt>[];
export default checkers;

View File

@ -0,0 +1,42 @@
import { OakInputIllegalException, } from 'oak-domain/lib/types';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'userEntityGrant',
checker: (data) => {
if (data instanceof Array) {
data.forEach((ele) => {
if (ele.type === 'grant') {
if (ele.number <= 0) {
throw new OakInputIllegalException('userEntityGrant', ['number', '分享的权限数量必须大于0']);
}
}
});
}
else {
if (data.type === 'grant') {
if (data.number <= 0) {
throw new OakInputIllegalException('userEntityGrant', ['number', '分享的权限数量必须大于0']);
}
}
}
},
},
{
type: 'row',
action: 'confirm',
entity: 'userEntityGrant',
filter: {
$expr: {
$gt: [{
"#attr": 'number',
}, {
"#attr": 'confirmed',
}]
}
},
errMsg: '该授权已经被认领完毕',
}
];
export default checkers;

5
es/checkers/wechatPublicTag.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'wechatPublicTag', RuntimeCxt>[];
export default checkers;

View File

@ -0,0 +1,22 @@
import { assert } from 'oak-domain/lib/utils/assert';
import { checkAttributesNotNull, checkAttributesScope, } from 'oak-domain/lib/utils/validator';
const checkers = [
{
type: 'data',
action: 'create',
entity: 'wechatPublicTag',
checker: (data) => {
assert(!(data instanceof Array));
checkAttributesNotNull('wechatPublicTag', data, [
'applicationId',
'text',
]);
checkAttributesScope('wechatPublicTag', data, [
'applicationId',
'id',
'text',
]);
},
},
];
export default checkers;

5
es/checkers/wechatQrCode.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { Checker } from 'oak-domain/lib/types';
import { EntityDict } from '../oak-app-domain';
import { RuntimeCxt } from '../types/RuntimeCxt';
declare const checkers: Checker<EntityDict, 'wechatQrCode', RuntimeCxt>[];
export default checkers;

View File

@ -0,0 +1,10 @@
const checkers = [
{
type: 'data',
action: 'create',
entity: 'wechatQrCode',
checker: (data) => {
},
}
];
export default checkers;

3
es/components/amap/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import Map from './map';
import Location from './location';
export { Map, Location };

View File

@ -0,0 +1,3 @@
import Map from './map';
import Location from './location';
export { Map, Location };

View File

@ -0,0 +1,10 @@
/// <reference types="@uiw/react-amap-types" />
import React from 'react';
export declare type PositionProps = {
loadUI: boolean;
__map__: AMap.Map | undefined;
onSuccess?: (result: AMapUI.PositionPickerResult) => void;
onFail?: (error: any) => void;
};
declare const _default: React.MemoExoticComponent<(props: PositionProps) => null>;
export default _default;

View File

@ -0,0 +1,33 @@
import { useEffect, memo } from 'react';
export default memo((props) => {
const map = props.__map__;
const loadUI = props.loadUI;
const { onSuccess, onFail } = props;
useEffect(() => {
if (map && window.AMapUI) {
dragSiteSelection();
}
}, [map, window.AMapUI]);
const dragSiteSelection = () => {
window.AMapUI.loadUI(['misc/PositionPicker'], (PositionPicker) => {
const positionPicker = new PositionPicker({
mode: 'dragMap',
map,
iconStyle: {
//自定义外观
url: '//webapi.amap.com/ui/1.0/assets/position-picker2.png',
ancher: [24, 40],
size: [48, 48],
},
});
positionPicker.on('success', (result) => {
onSuccess && onSuccess(result);
});
positionPicker.on('fail', (error) => {
onFail && onFail(error);
});
positionPicker.start();
});
};
return null;
});

41
es/components/amap/location/index.d.ts vendored Normal file
View File

@ -0,0 +1,41 @@
/// <reference types="@uiw/react-amap-types" />
import React from 'react';
import { ModalProps } from 'antd';
import { GeolocationProps } from '@uiw/react-amap';
import './index.less';
export declare type LocationProps = {
akey: string;
version?: string;
visible?: boolean;
className?: string;
children?: React.ReactNode;
onClose?: () => void;
onConfirm?: (poi: Poi, result?: AMap.SearchResult | AMapUI.PositionPickerResult) => void;
geolocationProps?: GeolocationProps;
useGeolocation?: boolean;
dialogProps?: ModalProps;
securityJsCode?: string;
serviceHost?: string;
};
export declare type Poi = {
id: string;
name: string;
type: string;
tel: string;
direction?: string;
distance: number;
address: string;
location: AMap.LngLat;
website?: string;
pcode: string;
citycode: string;
adcode: string;
postcode?: string;
pname: string;
cityname: string;
adname: string;
email?: string;
businessArea?: string;
};
declare const Location: (props: LocationProps) => import("react/jsx-runtime").JSX.Element;
export default Location;

View File

@ -0,0 +1,242 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import React, { useState, useEffect, useRef } from 'react';
import { Modal, Row, Col, Button, Input, List, Empty, Spin, } from 'antd';
import { SearchOutlined, CheckCircleFilled } from '@ant-design/icons';
import { Geolocation } from '@uiw/react-amap';
import Map from './../map';
import PositionPicker from './PositionPicker';
import './index.less';
const Location = (props) => {
const { visible, akey, securityJsCode, serviceHost, version = '2.0', onClose, onConfirm, geolocationProps = {}, useGeolocation = true, dialogProps = {}, } = props;
const prefixCls = 'oak';
const searchRef = useRef();
const [searchValue, setSearchValue] = useState('');
const [refresh, setRefresh] = useState(true); // 点击poi不触发setPositionPickerResult
const [mode, setMode] = useState('dragMap');
const [city, setCity] = useState('全国');
const [map, setMap] = useState();
const [positionPickerResult, setPositionPickerResult] = useState();
const [searchResult, setSearchResult] = useState();
const [pois, setPois] = useState();
const [currentPoi, setCurrentPoi] = useState();
const [oldPois, setOldPois] = useState();
const [oldPoi, setOldPoi] = useState();
const [loadUI, setLoadUI] = useState(false);
const [focus, setFocus] = useState(false);
const [searchLoading, setSearchLoading] = useState(false);
const [show, setShow] = useState(false);
const setCenter = (center) => {
if (map) {
map.setCenter(center);
}
};
const citySearch = () => {
return new Promise((resolve, reject) => {
window.AMap?.plugin(['AMap.CitySearch'], () => {
const citySearchFn = new window.AMap.CitySearch();
citySearchFn.getLocalCity((status, result) => {
if (status === 'complete') {
resolve(result);
}
else {
reject(result);
}
});
});
});
};
const placeSearch = (value) => {
// window.AMap存在再搜素
return new Promise(async (resolve, reject) => {
window.AMap?.plugin(['AMap.PlaceSearch'], () => {
const placeSearchFn = new window.AMap.PlaceSearch({
pageSize: 20,
pageIndex: 1,
extensions: 'all',
city: city, //城市
});
placeSearchFn.search(value, (status, result) => {
if (status === 'complete') {
resolve(result);
}
else {
reject(result);
}
});
});
});
};
useEffect(() => {
// 对安全密钥的支持
if (serviceHost || securityJsCode) {
if (serviceHost) {
window._AMapSecurityConfig = {
serviceHost: `${serviceHost}/_AMapService`,
};
}
else {
window._AMapSecurityConfig = {
securityJsCode,
};
}
}
}, []);
useEffect(() => {
if (window.AMap && !window.AMapUI) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `${window.location.protocol}//webapi.amap.com/ui/1.1/main.js`;
document.getElementsByTagName('head')[0].appendChild(script);
script.onload = () => {
setLoadUI(true);
};
script.onerror = (error) => {
setLoadUI(false);
};
}
else if (window.AMap && window.AMapUI) {
setLoadUI(true);
}
}, [window.AMap]);
useEffect(() => {
if (currentPoi && !refresh) {
const lngLat = new window.AMap.LngLat(currentPoi.location.lng, currentPoi.location.lat);
setCenter(lngLat);
}
}, [refresh, currentPoi]);
useEffect(() => {
// 拖动地图才触发
if (mode === 'dragMap' && positionPickerResult && refresh) {
const { regeocode } = positionPickerResult;
const { pois, addressComponent } = regeocode;
const pois2 = pois?.map((poi, index) => {
return {
...poi,
pcode: '',
citycode: addressComponent?.citycode,
adcode: addressComponent.adcode,
postcode: '',
pname: addressComponent.province,
cityname: addressComponent.city,
adname: addressComponent?.district,
};
});
setPois(pois2);
setCurrentPoi(pois2[0]);
}
}, [refresh, positionPickerResult]);
useEffect(() => {
if (searchValue) {
setSearchLoading(true);
placeSearch(searchValue).then((result) => {
const { pois } = result?.poiList;
setSearchResult(result);
setShow(true);
setSearchLoading(false);
setPois(pois);
// setCurrentPoi(pois[0]);
}, (error) => {
setSearchResult(undefined);
setShow(true);
setSearchLoading(false);
});
}
}, [searchValue]);
useEffect(() => {
if (mode === 'searchPoi') {
setOldPoi(currentPoi);
setOldPois(pois);
setPois([]);
setCurrentPoi(undefined);
}
else {
setPois(oldPois);
setCurrentPoi(oldPoi);
}
}, [mode]);
useEffect(() => {
if (visible && map && loadUI) {
setCenter(map.getCenter());
citySearch().then((result) => {
if (result?.info === 'OK') {
setCity(result.city);
}
});
}
}, [visible, map, loadUI]);
const clearData = () => {
setMode('dragMap');
setFocus(false);
setShow(false);
setSearchValue('');
setRefresh(true);
};
return (_jsx(Modal, { width: "80%", okText: "\u786E\u5B9A", cancelText: "\u53D6\u6D88", title: "\u9009\u62E9\u4F4D\u7F6E", ...dialogProps, open: visible, destroyOnClose: false, onCancel: () => {
onClose && onClose();
clearData();
}, onOk: () => {
if (!currentPoi) {
return;
}
onConfirm &&
onConfirm(currentPoi, mode === 'dragMap' ? positionPickerResult : searchResult);
clearData();
}, children: _jsx("div", { className: `${prefixCls}-location`, children: _jsxs(Row, { gutter: [16, 16], children: [_jsx(Col, { xs: 24, sm: 14, children: _jsxs(Map, { className: `${prefixCls}-location-map`, akey: akey, version: version, useAMapUI: true, mapRef: (instance) => {
if (instance && instance.map && !map) {
setMap(instance.map);
}
}, mapProps: {
onDragStart: () => {
setRefresh(true);
setMode('dragMap');
setSearchValue('');
setShow(false);
},
}, children: [_jsx(PositionPicker, { loadUI: loadUI, __map__: map, onSuccess: (result) => {
setPositionPickerResult(result);
} }), useGeolocation && (_jsx(Geolocation, { maximumAge: 100000, borderRadius: "5px", position: "RB", offset: [10, 10], zoomToAccuracy: true, showCircle: true, ...geolocationProps, onComplete: (data) => { }, onError: (err) => {
console.error(err);
} }))] }) }), _jsx(Col, { xs: 24, sm: 10, children: _jsx("div", { children: _jsxs(List, { className: `${prefixCls}-location-list`, header: _jsxs("div", { className: `${prefixCls}-location-list-header`, children: [_jsx(Input, { ref: searchRef, placeholder: "\u641C\u7D22\u5730\u70B9", value: searchValue, allowClear: true, onChange: (e) => {
setSearchValue(e.target.value);
}, prefix: _jsx(SearchOutlined, {}), onFocus: () => {
setMode('searchPoi');
setFocus(true);
}, onBlur: () => {
setFocus(false);
} }), mode === 'searchPoi' && (_jsx(Button, { style: { marginLeft: 5 }, type: "link", onClick: () => {
setMode('dragMap');
setSearchValue('');
setShow(false);
//@ts-ignore
searchRef?.current?.blur();
}, children: "\u53D6\u6D88" }))] }), children: [mode === 'dragMap' &&
pois?.map((poi, index) => {
return (_jsx("div", { onClick: () => {
setRefresh(false);
setCurrentPoi(poi);
}, children: _jsx(List.Item, { actions: [
_jsx("div", { style: {
width: 24,
}, children: currentPoi?.id ===
poi.id && (_jsx(CheckCircleFilled, { className: `${prefixCls}-location-list-checked` })) }),
], children: _jsx(List.Item.Meta, { title: poi.name, description: `${poi.distance
? `${poi.distance}m内 | `
: ''}${poi.address}` }) }) }, poi.id));
}), mode === 'searchPoi' && (_jsxs(React.Fragment, { children: [searchLoading && (_jsx("div", { className: `${prefixCls}-location-list-loadingBox`, children: _jsx(Spin, { delay: 0, spinning: true, size: "default" }) })), pois?.length
? pois.map((poi, index) => {
return (_jsx("div", { onClick: () => {
setRefresh(false);
setCurrentPoi(poi);
}, children: _jsx(List.Item, { actions: [
_jsx("div", { style: {
width: 24,
}, children: currentPoi?.id ===
poi.id && (_jsx(CheckCircleFilled, { className: `${prefixCls}-location-list-checked` })) }),
], children: _jsx(List.Item.Meta, { title: poi.name, description: `${poi.distance
? `${poi.distance}m内 | `
: ''}${poi.address}` }) }) }, poi.id));
})
: show &&
!searchLoading && (_jsx(Empty, { description: "\u65E0\u641C\u7D22\u7ED3\u679C", image: Empty.PRESENTED_IMAGE_SIMPLE }))] }))] }) }) })] }) }) }));
};
export default Location;

View File

@ -0,0 +1,30 @@
.oak-location {
&-map {
width: 100%;
height: 500px;
}
&-list {
height: 500px;
overflow: auto;
&-header {
display: flex;
flex-direction: row;
align-items: center;
}
&-checked {
color: var(--oak-color-primary);
font-size: 24px;
}
&-loadingBox {
display: flex;
flex-direction: row;
justify-content: center;
min-height: 100px;
}
}
}

33
es/components/amap/map/index.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
/// <reference types="@uiw/react-amap-types" />
import React from 'react';
import { MapProps, APILoaderConfig } from '@uiw/react-amap';
import './index.less';
export declare type APILoaderProps = {
akey: APILoaderConfig['akey'];
version?: APILoaderConfig['version'];
};
declare type RenderProps = {
children?: (data: {
AMap: typeof AMap;
map: AMap.Map;
container?: HTMLDivElement | null;
}) => undefined;
} | {
children?: React.ReactNode;
};
export interface AMapProps extends APILoaderProps {
style?: React.CSSProperties;
className?: string;
children?: RenderProps['children'];
mapProps?: MapProps;
mapRef?: React.Ref<MapProps & {
map?: AMap.Map | undefined;
}>;
useAMapUI?: boolean;
uiVersion?: string;
uiCallback?: (status: 'success' | 'fail', result?: any) => void;
securityJsCode?: string;
serviceHost?: string;
}
declare const memo: (props: AMapProps) => import("react/jsx-runtime").JSX.Element;
export default memo;

View File

@ -0,0 +1,44 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { useEffect } from 'react';
import { Map, APILoader } from '@uiw/react-amap';
import classNames from 'classnames';
import './index.less';
;
const memo = (props) => {
const { akey, securityJsCode, serviceHost, version, className, style, children, mapProps = {}, mapRef, useAMapUI, uiVersion = '1.1', uiCallback, } = props;
const prefixCls = 'oak';
useEffect(() => {
// 对安全密钥的支持
if (serviceHost || securityJsCode) {
if (serviceHost) {
window._AMapSecurityConfig = {
serviceHost: `${serviceHost}/_AMapService`,
};
}
else {
window._AMapSecurityConfig = {
securityJsCode,
};
}
}
}, []);
useEffect(() => {
if (!useAMapUI) {
return;
}
if (window.AMap && !window.AMapUI) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `${window.location.protocol}//webapi.amap.com/ui/${uiVersion}/main.js`;
document.getElementsByTagName('head')[0].appendChild(script);
script.onload = () => {
uiCallback && uiCallback('success');
};
script.onerror = (error) => {
uiCallback && uiCallback('fail', error);
};
}
}, [window.AMap, useAMapUI]);
return (_jsx("div", { style: style, className: classNames(`${prefixCls}-map`, className), children: _jsx(APILoader, { akey: akey, version: version, children: _jsx(Map, { ref: mapRef, ...mapProps, children: children }) }) }));
};
export default memo;

View File

@ -0,0 +1,5 @@
.oak-map {
width: 500px;
height: 500px;
}

0
es/components/area/upsert/index.d.ts vendored Normal file
View File

View File

@ -0,0 +1,9 @@
"use strict";
// index.ts
OakComponent({
entity: 'area',
isList: false,
formData: ({ data: area }) => ({
name: area?.name,
}),
});

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,28 @@
/** index.wxss **/
.page-body {
display: flex;
flex-direction: column;
align-items: stretch;
color: #aaa;
}
.label-bar {
padding: 20rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.label-bar text {
margin-right: 20rpx;
width: 200rpx;
}
.label-bar input {
flex: 1;
}
.usermotto {
margin-top: 200px;
}

View File

@ -0,0 +1,7 @@
<!-- index.wxml -->
<view class="page-body">
<view class="label-bar">
<text>地区名称</text>
<input placeholder="请输入名称" value="{{name}}" type="string" data-path="name" bindinput="setUpdateData" />
</view>
</view>

4
es/components/article/cell/index.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", false, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,27 @@
export default OakComponent({
entity: 'article',
isList: false,
projection: {
id: 1,
name: 1,
content: 1,
articleMenu: {
id: 1,
name: 1,
isArticle: 1,
isLeaf: 1,
},
},
data: {
content: '',
name: '',
},
formData({ data: article }) {
return {
content: article?.content,
name: article?.name,
};
},
lifetimes: {},
methods: {}
});

10
es/components/article/cell/web.d.ts vendored Normal file
View File

@ -0,0 +1,10 @@
import '@wangeditor/editor/dist/css/style.css';
import { EntityDict } from '../../../oak-app-domain';
import { WebComponentProps } from 'oak-frontend-base';
export default function Render(props: WebComponentProps<EntityDict, 'article', false, {
oakId: string;
content: string;
name: string;
width: string;
editor: any;
}, {}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,26 @@
import { jsx as _jsx } from "react/jsx-runtime";
import { useState, useEffect } from 'react';
import { Row, Col, Modal } from 'antd';
const { confirm } = Modal;
import '@wangeditor/editor/dist/css/style.css'; // 引入 css
import { Editor } from "@wangeditor/editor-for-react";
import Style from './web.module.less';
export default function Render(props) {
const { methods: method, data } = props;
const { content, oakId, width } = props.data;
const { t } = method;
const editorConfig = {
readOnly: true,
autoFocus: true,
scroll: false,
};
const [value, setValue] = useState('');
useEffect(() => {
if (content) {
setValue(content);
}
}, [content]);
return (_jsx("div", { className: Style.container, children: _jsx(Row, { children: _jsx(Col, { xs: 24, sm: 16, children: content && (_jsx(Editor, { defaultConfig: editorConfig, value: content ? content : value, mode: "default", style: {
width: width === 'xs' ? '100vw' : '900px',
} })) }) }) }));
}

View File

@ -0,0 +1,6 @@
.container {
// display: flex;
// flex-direction: column;
// flex: 1;
width: 100%;
}

View File

@ -0,0 +1,4 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", false, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,27 @@
export default OakComponent({
entity: 'article',
isList: false,
projection: {
id: 1,
name: 1,
content: 1,
articleMenu: {
id: 1,
name: 1,
isArticle: 1,
isLeaf: 1,
},
},
data: {
content: '',
name: '',
},
formData: function ({ data: article }) {
return {
content: article?.content,
name: article?.name,
};
},
lifetimes: {},
methods: {}
});

11
es/components/article/detail/web.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'article', false, {
id: string;
name: string;
editor: any;
title?: string;
content?: string;
html?: string;
origin?: string;
}, {}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,21 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import Style from './web.module.less';
import { Editor } from "@wangeditor/editor-for-react";
import { useState, useEffect } from 'react';
export default function Render(props) {
const { id, name, editor, title, content } = props.data;
const editorConfig = {
readOnly: true,
autoFocus: true,
scroll: false,
};
const [value, setValue] = useState('');
useEffect(() => {
if (content) {
setValue(content);
}
}, [content]);
return (_jsx("div", { className: Style.container, children: _jsx("div", { className: Style.content, children: _jsxs("div", { className: Style.editorContainer, children: [_jsx("div", { className: Style.titleContainer, children: _jsx("span", { className: Style.title, children: name }) }), _jsx("div", { id: "article-content", style: { width: "100%" }, children: _jsx(Editor, { defaultConfig: editorConfig, value: value, mode: "default", style: {
width: '100%'
} }) })] }) }) }));
}

View File

@ -0,0 +1,49 @@
.container {
background-color: #f3f5f7;
padding: 30px 300px;
min-height: 100vh;
}
.content {
display: flex;
background-color: #f3f5f7;
width: 100%;
justify-content: center;
}
.editorContainer {
// width: 100%;
// margin: 30px auto 50px auto;
width: 100%;
display: flex;
flex-direction: column;
background: var(--oak-bg-color-container);
padding: 20px 50px 50px 50px;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
}
.titleContainer {
display: flex;
justify-content: center;
padding: 5px 0 10px 0;
}
.authorContainer {
padding: 5px 0 10px 0;
}
.title {
font-size: 32px !important;
font-weight: bold !important;
}
.author {
color: var(--oak-text-color-brand);
font-size: 14px;
}
.abstract {
width: 100%;
border-top: 1px solid #e8e8e8;
}

View File

@ -0,0 +1,4 @@
/// <reference types="wechat-miniprogram" />
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", false, WechatMiniprogram.Component.DataOption>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,39 @@
export default OakComponent({
isList: false,
entity: 'article',
projection: {
id: 1,
name: 1,
content: 1,
articleMenu: {
id: 1,
}
},
formData: function ({ data: article, features }) {
return {
id: article?.id,
content: article?.content,
name: article?.name,
};
},
data: {
content: '',
title: '',
author: '',
},
lifetimes: {
attached() {
const data = this.load('article_html') || '{}';
const data2 = typeof data === 'string' ? JSON.parse(data) : data;
this.setState({
content: data2?.content,
title: data2?.title,
author: data2?.author,
});
},
detached() {
this.save('article_html', '{}');
},
},
methods: {},
});

11
es/components/article/preview/web.d.ts vendored Normal file
View File

@ -0,0 +1,11 @@
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
export default function Render(props: WebComponentProps<EntityDict, 'article', false, {
id: string;
name: string;
editor: any;
title?: string;
content?: string;
html?: string;
origin?: string;
}, {}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,21 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import Style from './web.module.less';
import { Editor } from '@wangeditor/editor-for-react';
import { useState, useEffect } from 'react';
export default function Render(props) {
const { id, name, editor, title, content } = props.data;
const editorConfig = {
readOnly: true,
autoFocus: true,
scroll: false,
};
const [value, setValue] = useState('');
useEffect(() => {
if (content) {
setValue(content);
}
}, [content]);
return (_jsx("div", { className: Style.container, children: _jsx("div", { className: Style.content, children: _jsxs("div", { className: Style.editorContainer, children: [_jsx("div", { className: Style.titleContainer, children: _jsx("span", { className: Style.title, children: title }) }), _jsx("div", { id: "article-content", style: { width: '100%' }, children: _jsx(Editor, { defaultConfig: editorConfig, value: value, mode: "default", style: {
width: '100%',
} }) })] }) }) }));
}

View File

@ -0,0 +1,49 @@
.container {
background-color: #f3f5f7;
padding: 30px 200px;
min-height: 100vh;
}
.content {
display: flex;
background-color: #f3f5f7;
width: 100%;
justify-content: center;
}
.editorContainer {
// width: 100%;
// margin: 30px auto 50px auto;
width: 100%;
display: flex;
flex-direction: column;
background: var(--oak-bg-color-container);
padding: 20px 50px 50px 50px;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
}
.titleContainer {
display: flex;
justify-content: center;
padding: 5px 0 10px 0;
}
.authorContainer {
padding: 5px 0 10px 0;
}
.title {
font-size: 32px !important;
font-weight: bold !important;
}
.author {
color: var(--oak-text-color-brand);
font-size: 14px;
}
.abstract {
width: 100%;
border-top: 1px solid #e8e8e8;
}

View File

@ -0,0 +1,20 @@
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "article", true, {
articleMenuId: string | undefined;
onChildEditArticleChange: (data: string) => void;
show: string;
getBreadcrumbItemsByParent: (breadcrumbItems: string[]) => void;
breadcrumbItems: string[];
drawerOpen: boolean;
changeDrawerOpen: (open: boolean) => void;
selectedArticleId: string;
openArray: string[];
getTopInfo: (data: {
name: string;
date: number;
}) => void;
articleId: string;
currentArticle: string;
setCurrentArticle: (id: string) => void;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,50 @@
export default OakComponent({
entity: 'article',
isList: true,
properties: {
articleMenuId: '',
onChildEditArticleChange: (data) => undefined,
show: '',
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
breadcrumbItems: [],
drawerOpen: false,
changeDrawerOpen: (open) => undefined,
selectedArticleId: '',
openArray: [],
getTopInfo: (data) => undefined,
articleId: '',
currentArticle: '',
setCurrentArticle: (id) => undefined,
},
projection: {
id: 1,
name: 1,
articleMenuId: 1,
},
filters: [
{
filter() {
const { articleMenuId } = this.props;
return {
articleMenuId,
};
}
}
],
formData({ data: rows }) {
return {
rows,
};
},
methods: {
async createOne() {
const { articleMenuId } = this.props;
this.addItem({
name: '文章标题',
content: '',
articleMenuId,
});
await this.execute();
},
}
});

View File

@ -0,0 +1,22 @@
import { WebComponentProps } from "oak-frontend-base";
import { EntityDict } from "../../../oak-app-domain";
export default function Render(props: WebComponentProps<EntityDict, 'articleMenu', true, {
rows: EntityDict['article']['OpSchema'][];
onChildEditArticleChange: (data: string) => void;
show: string;
getBreadcrumbItemsByParent: (breadcrumbItems: string[]) => void;
breadcrumbItems: string[];
drawerOpen: boolean;
changeDrawerOpen: (open: boolean) => void;
selectedArticleId: string;
openArray: string[];
getTopInfo: (data: {
name: string;
date: number;
}) => void;
articleId: string;
currentArticle: string;
setCurrentArticle: (id: string) => void;
}, {
createOne: () => Promise<void>;
}>): import("react/jsx-runtime").JSX.Element | null;

View File

@ -0,0 +1,98 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from 'react';
import Styles from './web.pc.module.less';
import { Input, Button, Divider, Modal } from 'antd';
import { EditOutlined, MinusOutlined, CopyOutlined } from '@ant-design/icons';
import copy from 'copy-to-clipboard';
export default function Render(props) {
const { rows, oakFullpath, onChildEditArticleChange, show, getBreadcrumbItemsByParent, breadcrumbItems, drawerOpen, changeDrawerOpen, selectedArticleId, openArray, getTopInfo, articleId, currentArticle, setCurrentArticle, } = props.data;
const { t, setMessage, addItem, removeItem, updateItem, execute } = props.methods;
const [modal, contextHolder] = Modal.useModal();
const [nameEditing, setNameEditing] = useState('');
const [name, setName] = useState(undefined);
const [onlyOne, setOnlyOne] = useState(false);
useEffect(() => {
if (openArray && openArray.length > 0 && rows && rows.length > 0 && !onlyOne) {
rows.map((row) => {
if (openArray.includes(row.id) && !articleId) {
onChildEditArticleChange(row.id);
getBreadcrumbItemsByParent([...breadcrumbItems, row.name]);
setOnlyOne(true);
}
});
}
}, [openArray, rows]);
useEffect(() => {
if (rows && rows.length > 0 && selectedArticleId) {
const data = rows.find((ele) => ele.id === selectedArticleId);
if (data) {
getTopInfo({ name: data.name, date: data.$$createAt$$ });
}
}
}, [selectedArticleId, rows]);
if (articleId && rows && rows.length > 0) {
rows.map((row) => {
if (openArray.includes(row.id) && currentArticle !== row.id) {
setCurrentArticle(row.id);
getBreadcrumbItemsByParent([...breadcrumbItems, row.name]);
}
});
}
;
if (oakFullpath) {
if (!show) {
if (rows?.length > 0) {
return (_jsxs("div", { children: [rows.map((ele, idx) => (_jsxs(_Fragment, { children: [_jsxs("div", { className: Styles.container, onClick: () => {
onChildEditArticleChange(ele.id);
}, children: [_jsx("div", { className: Styles.ne, children: nameEditing === ele.id ? _jsx("div", { className: Styles.name, children: _jsx(Input, { autoFocus: true, value: name !== undefined ? name : ele?.name, onChange: (evt) => setName(evt.target.value), onPressEnter: async () => {
if (name && name !== ele?.name) {
updateItem({ name }, ele.id);
await execute();
}
setNameEditing('');
}, onBlur: async () => {
if (name !== ele?.name) {
updateItem({ name }, ele.id);
await execute();
}
setNameEditing('');
} }) }) : _jsxs(_Fragment, { children: [_jsx(Button, { type: "text", icon: _jsx(EditOutlined, {}), size: "small", onClick: (e) => {
e.stopPropagation();
setNameEditing(ele.id);
}, style: { marginRight: 4 } }), _jsx("div", { className: Styles.name, children: _jsx("div", { style: { marginLeft: 4, overflow: 'hidden', width: '150px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: ele?.name }) })] }) }), _jsx(Divider, { type: "vertical", style: { height: '100%', marginTop: 4, marginBottom: 4 } }), _jsxs("div", { className: Styles.control, children: [_jsx(Button, { type: "text", icon: _jsx(CopyOutlined, {}), size: "small", onClick: (e) => {
e.stopPropagation();
const url = `${window.location.host}/article/detail?oakId=${ele.id}`;
copy(url);
setMessage({
content: '复制链接成功',
type: 'success',
});
} }), _jsx(Button, { type: "text", icon: _jsx(MinusOutlined, {}), size: "small", onClick: (e) => {
e.stopPropagation();
modal.confirm({
title: '请确认',
content: '确认删除吗?',
okText: '确定',
cancelText: '取消',
onOk: async () => {
onChildEditArticleChange('');
removeItem(ele.id);
await execute();
}
});
} })] })] }), _jsx(Divider, { style: { margin: 1 } })] }))), contextHolder] }));
}
}
else {
if (rows?.length > 0) {
return (_jsxs("div", { children: [rows.map((ele, idx) => (_jsx(_Fragment, { children: _jsx("div", { className: Styles.test, onClick: () => {
onChildEditArticleChange(ele.id);
getBreadcrumbItemsByParent([...breadcrumbItems, ele.name]);
changeDrawerOpen(!drawerOpen);
getTopInfo({ name: ele.name, date: ele.$$createAt$$ });
}, children: _jsx("div", { className: Styles.ne, children: selectedArticleId === ele.id ? (_jsxs("div", { className: Styles.name, children: [_jsx("div", { className: Styles.dot }), _jsx("div", { className: Styles.title, children: ele?.name })] })) : (_jsxs("div", { className: Styles.name, children: [_jsx("div", { className: Styles.dot2 }), _jsx("div", { children: ele?.name })] })) }) }) }))), contextHolder] }));
}
}
}
return null;
}

View File

@ -0,0 +1,160 @@
.container {
width: 320px;
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
justify-content: space-between;
cursor: pointer;
.ne {
min-width: 140px;
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
align-items: center;
justify-content: space-between;
.name {
flex: 1;
}
}
.control {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
padding: 10px;
.ph {
width: 24px;
height: 24px;
}
}
}
.container2 {
width: 260px;
display: flex;
flex-direction: row;
align-items: center;
height: 45px;
justify-content: space-between;
cursor: pointer;
.ne {
min-width: 140px;
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
align-items: center;
justify-content: space-between;
.name {
flex: 1;
display: flex;
flex-direction: row;
font-size: 14px;
font-weight: 600;
.dot {
margin-top: 6px;
width: 4px;
height: 4px;
border-radius: 2px;
background: var(--oak-color-primary);
margin-right: 5px;
display: flex;
}
.placeholder {
width: 9px;
}
.title {
color: var(--oak-color-primary);
}
}
}
.control {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
padding: 10px;
.ph {
width: 24px;
height: 24px;
}
}
}
.container2:hover {
color: #999;
}
.test {
display: flex;
flex-direction: row;
align-items: center;
height: 45px;
justify-content: space-between;
cursor: pointer;
background: #f5f5f5;
.ne {
min-width: 140px;
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
align-items: center;
justify-content: space-between;
.name {
flex: 1;
display: flex;
flex-direction: row;
font-size: 14px;
font-weight: 600;
align-items: center;
color: #b2b2b2;
.dot {
width: 14px;
height: 14px;
border-radius: 50%;
margin-right: 10px;
display: flex;
border: 2px solid #000;
}
.title {
color: var(--oak-color-primary);
color: #000;
}
.dot2 {
width: 14px;
height: 14px;
border-radius: 50%;
margin-right: 10px;
display: flex;
border: 2px solid #b2b2b2;
}
}
}
.control {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
padding: 10px;
.ph {
width: 24px;
height: 24px;
}
}
}
.test:hover {
color: #999;
}

View File

@ -0,0 +1,7 @@
/// <reference types="react" />
import { EntityDict } from '../../../oak-app-domain';
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, "article", false, {
articleMenuId: string;
changeIsEdit: () => void;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,149 @@
import { generateNewId } from 'oak-domain/lib/utils/uuid';
export default OakComponent({
entity: 'article',
projection: {
id: 1,
name: 1,
content: 1,
articleMenu: {
id: 1,
},
},
isList: false,
formData: function ({ data: article, features }) {
return {
id: article?.id,
content: article?.content,
name: article?.name,
articleMenuId: article?.articleMenuId,
};
},
data: {
editor: null,
html: '',
origin1: 'qiniu',
contentTip: false,
},
properties: {
articleMenuId: '',
changeIsEdit: () => undefined,
},
listeners: {
'editor,content'(prev, next) {
if (next.editor && next.content) {
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);
}
},
},
lifetimes: {
async ready() {
const { oakId, articleMenuId } = this.props;
if (!oakId) {
if (articleMenuId) {
this.update({
articleMenuId,
});
const { editor } = this.state;
editor?.setHtml('');
this.update({
content: '',
});
}
}
else {
}
},
detached() {
const { editor } = this.state;
if (editor == null)
return;
editor.destroy();
this.setEditor(null);
},
},
methods: {
async addExtraFile(extraFile) {
const result = await this.features.cache.operate('extraFile', {
action: 'create',
data: extraFile,
id: generateNewId(),
});
return result;
},
async uploadFile(extraFile) {
const result = await this.features.extraFile.upload(extraFile);
return result;
},
setEditor(editor) {
this.setState({
editor,
});
},
clearContentTip() {
this.setState({
contentTip: false,
});
},
async check() {
if (this.state.name &&
this.state.name.length > 0 &&
this.state.content &&
this.state.content.length > 0 &&
this.state.html !== '<p><br></p>') {
await this.execute();
if (this.props.changeIsEdit) {
this.props.changeIsEdit();
}
}
else if (this.state.name && this.state.name.length > 0) {
this.setMessage({
content: '请填写文章内容!',
type: 'warning',
});
}
else if (this.state.content &&
this.state.content.length > 0 &&
this.state.html !== '<p><br></p>') {
this.setMessage({
content: '请填写文章标题!',
type: 'warning',
});
}
},
async reset() {
// 重置
this.clean();
},
setHtml(html) {
this.setState({
html,
});
if (html && html !== '<p><br></p>' && this.state.oakFullpath) {
this.update({ content: html });
}
},
preview() {
const { html } = this.state;
this.save('article_html', JSON.stringify({
content: html,
}));
window.open('/article/preview');
},
gotoPreview(content, title) {
this.save('article_html', JSON.stringify({
content,
title,
}));
window.open('/article/preview');
},
},
});

29
es/components/article/upsert/web.d.ts vendored Normal file
View File

@ -0,0 +1,29 @@
import "@wangeditor/editor/dist/css/style.css";
import { EntityDict } from "./../../../oak-app-domain";
import { WebComponentProps } from "oak-frontend-base";
export default function Render(props: WebComponentProps<EntityDict, "article", false, {
id: string;
name: string;
editor: any;
abstract?: string;
content?: string;
html?: string;
origin?: string;
contentTip: boolean;
origin1: string;
articleMenuId: string;
oakId: string;
changeIsEdit: () => void;
}, {
setHtml: (content: string) => void;
setEditor: (editor: any) => void;
check: () => void;
preview: () => void;
addExtraFile: (file: EntityDict["extraFile"]["CreateSingle"]["data"]) => Promise<void>;
uploadFile: (file: EntityDict["extraFile"]["CreateSingle"]["data"]) => Promise<{
bucket: string;
url: string;
}>;
clearContentTip: () => void;
gotoPreview: (content?: string, title?: string) => void;
}>): import("react/jsx-runtime").JSX.Element;

View File

@ -0,0 +1,122 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { generateNewId } from "oak-domain/lib/utils/uuid";
import { useState, useEffect } from "react";
import { Alert, Button, Row, Col, Space, Input, } from "antd";
import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import Style from "./web.module.less";
import { EyeOutlined, } from "@ant-design/icons";
// 工具栏配置
const toolbarConfig = {
excludeKeys: ["fullScreen"],
}; // TS 语法
// 自定义校验图片
function customCheckImageFn(src, alt, url) {
// TS 语法
if (!src) {
return;
}
if (src.indexOf("http") !== 0) {
return "图片网址必须以 http/https 开头";
}
return true;
// 返回值有三种选择:
// 1. 返回 true ,说明检查通过,编辑器将正常插入图片
// 2. 返回一个字符串,说明检查未通过,编辑器会阻止插入。会 alert 出错误信息(即返回的字符串)
// 3. 返回 undefined即没有任何返回说明检查未通过编辑器会阻止插入。但不会提示任何信息
}
export default function Render(props) {
const { methods: method, data } = props;
const { t, setEditor, check, preview, addExtraFile, uploadFile, update, setHtml, gotoPreview, } = method;
const { id, content, editor, origin1, oakFullpath, html, oakId, articleMenuId, changeIsEdit } = data;
const [articleId, setArticleId] = useState("");
useEffect(() => {
if (id) {
setArticleId(id);
}
}, [id]);
return (_jsxs("div", { className: Style.container, children: [_jsx("div", { style: { width: 'calc(100% - 20px)' }, children: _jsx(Toolbar, { editor: editor, defaultConfig: toolbarConfig, mode: "default" }) }), _jsxs(Row, { children: [_jsx(Col, { flex: 4 }), _jsx(Col, { flex: 16, children: _jsx("div", { className: Style.content, children: _jsxs("div", { className: Style.editorContainer, children: [data.contentTip && (_jsx(Alert, { type: "info", message: t("tips.content"), closable: true, onClose: () => method.clearContentTip() })), _jsx("div", { className: Style.titleContainer, children: _jsx(Input, { onChange: (e) => update({ name: e.target.value }), value: data.name, placeholder: "请输入文章标题", size: "large", maxLength: 32, suffix: `${[...(data.name || "")].length}/32`, className: Style.titleInput }) }), _jsx("div", { className: Style.editorContent, children: _jsx(Editor, { defaultConfig: {
autoFocus: true,
placeholder: "请输入文章内容...",
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: articleId,
extra1: file,
origin: origin1,
type: "image",
tag1: "source",
objectId: generateNewId(),
filename,
size,
extension,
bucket: "",
id: generateNewId(),
};
try {
// 自己实现上传,并得到图片 url alt href
const { url, bucket } = await uploadFile(extraFile);
extraFile.bucket = bucket;
extraFile.extra1 = null;
await addExtraFile(extraFile);
// 最后插入图片
insertFn("http://" + url, extraFile.filename);
}
catch (err) {
}
},
},
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: articleId,
extra1: file,
origin: origin1,
type: "video",
tag1: "source",
objectId: generateNewId(),
filename,
size,
extension,
bucket: "",
id: generateNewId(),
};
try {
// 自己实现上传,并得到图片 url alt href
const { url, bucket } = await uploadFile(extraFile);
extraFile.bucket = bucket;
extraFile.extra1 = null;
await addExtraFile(extraFile);
// 最后插入图片
insertFn("http://" + url, "http://" + url + "?vframe/jpg/offset/0");
}
catch (err) { }
},
},
},
}, onCreated: setEditor, onChange: (editorDom) => {
setHtml(editorDom.getHtml());
}, style: {
minHeight: 440,
}, mode: "default" }) }), _jsx("div", { className: Style.footer, children: _jsx(Row, { align: "middle", children: _jsx(Col, { flex: "none", children: _jsxs(Space, { children: [_jsx(Button, { disabled: !data.oakDirty || data.oakExecuting, type: "primary", onClick: () => {
check();
}, children: "\u4FDD\u5B58" }), _jsxs(Button, { onClick: () => {
gotoPreview(content, data.name);
}, children: [_jsx(EyeOutlined, {}), "\u9884\u89C8"] })] }) }) }) })] }) }) }), _jsx(Col, { flex: 4 })] })] }));
}

View File

@ -0,0 +1,78 @@
.container {
// background-color: var(--oak-bg-color-page);
// padding: 30px 32px;
margin: 0 14px;
width: 100%;
}
.content {
// background-color: var(--oak-bg-color-page);
// height: calc(100% - 40px);
//overflow-y: auto;
position: relative;
}
.editorContainer {
min-width: 600px;
width: 100%;
margin: 30px auto 50px auto;
background: var(--oak-bg-color-container);
padding: 20px 40px 40px 40px;
box-shadow: 0 2px 10px rgb(0 0 0 / 12%);
overflow-y: scroll;
max-height: 600px;
}
.titleContainer {
padding: 5px 0;
border-bottom: 1px solid #e8e8e8;
}
.authorContainer {
padding: 5px 0;
//border-bottom: 1px solid #e8e8e8;
}
.input {
border: unset !important;
box-shadow: none !important;
}
.titleInput {
border: unset !important;
box-shadow: none !important;
font-size: 18px !important;
font-weight: bold !important;
}
.input:hover {
border: unset !important;
}
.abstract {
width: 100%;
border-top: 1px solid #e8e8e8;
}
.card {
:global {
.ant-card-head {
border: none;
}
}
}
.footer {
// position: absolute;
bottom: 0;
height: 50px;
margin-top: 10px;
}
.contentNumber {
display: flex;
min-height: 32px;
flex-direction: column;
justify-content: center;
color: var(--oak-text-color-secondary);
}

View File

@ -0,0 +1,27 @@
/// <reference types="react" />
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<import("../../../oak-app-domain").EntityDict, "articleMenu", false, {
onRemove: () => void;
onUpdateName: (name: string) => Promise<void>;
onChildEditArticleChange: (data: string) => void;
show: string;
getBreadcrumbItemsByParent: (breadcrumbItems: string[]) => void;
breadItems: string[];
drawerOpen: boolean;
changeDrawerOpen: (open: boolean) => void;
selectedArticleId: string;
openArray: string[];
getTopInfo: (data: {
name: string;
date: number;
}) => void;
articleId: string;
articleMenuId: string;
getSideInfo: (data: {
id: string;
name: string;
coverUrl: string;
}) => void;
currentArticle: string;
setCurrentArticle: (id: string) => void;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

@ -0,0 +1,132 @@
import { generateNewIdAsync } from "oak-domain/lib/utils/uuid";
export default OakComponent({
entity: 'articleMenu',
isList: false,
projection: {
id: 1,
name: 1,
entity: 1,
entityId: 1,
parentId: 1,
isArticle: 1,
extraFile$entity: {
$entity: 'extraFile',
data: {
id: 1,
tag1: 1,
origin: 1,
bucket: 1,
objectId: 1,
filename: 1,
extra1: 1,
extension: 1,
type: 1,
entity: 1,
},
filter: {
tag1: {
$in: ['logo'],
},
},
},
articleMenu$parent: {
$entity: 'articleMenu',
data: {
id: 1,
},
indexFrom: 0,
count: 1,
},
article$articleMenu: {
$entity: 'article',
data: {
id: 1,
name: 1,
},
indexFrom: 0,
count: 1,
},
},
properties: {
onRemove: () => undefined,
onUpdateName: async (name) => undefined,
onChildEditArticleChange: (data) => undefined,
show: '',
getBreadcrumbItemsByParent: (breadcrumbItems) => undefined,
breadItems: [],
drawerOpen: false,
changeDrawerOpen: (open) => undefined,
selectedArticleId: '',
openArray: [],
getTopInfo: (data) => undefined,
articleId: '',
articleMenuId: '',
getSideInfo: (data) => undefined,
currentArticle: '',
setCurrentArticle: (id) => undefined,
},
formData({ data: row }) {
const { articleMenu$parent, article$articleMenu } = row || {};
const allowCreateSubMenu = article$articleMenu && article$articleMenu.length === 0;
const allowCreateSubArticle = articleMenu$parent && articleMenu$parent.length === 0;
const allowRemove = allowCreateSubMenu && allowCreateSubArticle;
const logo = this.features.extraFile.getUrl(row?.extraFile$entity?.find((ele) => ele.tag1 === 'logo'));
return {
row,
allowCreateSubMenu,
allowCreateSubArticle,
allowRemove,
logo,
article$articleMenu,
};
},
data: {
editArticle: '',
},
methods: {
async createSubArticle(name) {
const id = await generateNewIdAsync();
this.setState({
editArticle: '',
});
this.update({
article$articleMenu: [{
id,
action: 'create',
data: {
id,
name,
content: '',
}
}]
});
await this.execute();
this.setState({
editArticle: id
});
},
async createSubArticleMenu(name) {
const { row } = this.state;
this.update({
articleMenu$parent: [
{
id: await generateNewIdAsync(),
action: 'create',
data: {
id: await generateNewIdAsync(),
name,
entity: row.entity,
entityId: row.entityId,
isArticle: false,
isLeaf: false,
},
}
]
});
await this.execute();
},
gotoDoc(articleMenuId) {
window.open(`/article/doc?articleMenuId=${articleMenuId}`);
}
}
});

View File

@ -0,0 +1,37 @@
import { WebComponentProps } from "oak-frontend-base";
import { EntityDict } from "../../../oak-app-domain";
export default function Render(props: WebComponentProps<EntityDict, 'articleMenu', false, {
row: EntityDict['articleMenu']['OpSchema'];
allowCreateSubMenu: boolean;
allowCreateSubArticle: boolean;
allowRemove: boolean;
logo: string;
onRemove: () => void;
onUpdateName: (name: string) => Promise<void>;
onChildEditArticleChange: (data: string) => void;
editArticle: string;
show: string;
getBreadcrumbItemsByParent: (breadcrumbItems: string[]) => void;
breadItems: string[];
drawerOpen: boolean;
changeDrawerOpen: (open: boolean) => void;
selectedArticleId: string;
openArray: string[];
getTopInfo: (data: {
name: string;
date: number;
}) => void;
articleId: string;
articleMenuId: string;
getSideInfo: (data: {
id: string;
name: string;
coverUrl: string;
}) => void;
currentArticle: string;
setCurrentArticle: (id: string) => void;
}, {
createSubArticle: (name: string) => Promise<void>;
createSubArticleMenu: (name: string) => Promise<void>;
gotoDoc: (articleMenuId: string) => void;
}>): import("react/jsx-runtime").JSX.Element | null;

View File

@ -0,0 +1,175 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useRef, useEffect } from 'react';
import { Input, Button, Dropdown, Divider, Modal, Form, Image } from 'antd';
import { EditOutlined, DownOutlined, UpOutlined, MinusOutlined, PlusOutlined, EyeOutlined } from '@ant-design/icons';
import ArticleMenuTreeList from '../treeList';
import ArticleTreeList from '../../article/treeList';
import Styles from './web.pc.module.less';
import OakGallery from "../../../components/extraFile/gallery";
export default function Render(props) {
const { row, allowCreateSubArticle, allowCreateSubMenu, allowRemove, onRemove, onUpdateName, oakFullpath, logo, onChildEditArticleChange, editArticle, show, getBreadcrumbItemsByParent, breadItems, drawerOpen, changeDrawerOpen, selectedArticleId, openArray, getTopInfo, articleId, articleMenuId, getSideInfo, currentArticle, setCurrentArticle } = props.data;
const { update, execute, createSubArticle, createSubArticleMenu, setMessage, gotoDoc } = props.methods;
const [nameEditing, setNameEditing] = useState(false);
useEffect(() => {
if (editArticle.length > 0) {
onChildEditArticleChange(editArticle);
}
}, [editArticle]);
const [modal, contextHolder] = Modal.useModal();
const [name, setName] = useState('');
const [showSub, setShowSub] = useState(false);
const [newBreadcrumbItems, setNewBreadcrumbItems] = useState([]);
const menuNameRef = useRef(null);
const subMenuNameRef = useRef(null);
const subArticleNameRef = useRef(null);
const hasSubArticles = !allowCreateSubMenu;
const hasSubMenus = !allowCreateSubArticle;
const [onlyOne, setOnlyOne] = useState(false);
useEffect(() => {
if (openArray && openArray.length > 0 && row && !onlyOne) {
if (openArray.includes(row.id)) {
setShowSub(true);
setNewBreadcrumbItems([...breadItems, row?.name]);
setOnlyOne(true);
}
}
}, [openArray, row]);
useEffect(() => {
if (row && !row.parentId && articleMenuId) {
getSideInfo({ id: row.id, name: row.name, coverUrl: logo });
}
else {
getSideInfo({ id: '', name: '帮助文档', coverUrl: '' });
}
}, [row]);
if (oakFullpath && row) {
if (!show) {
const Sub = showSub && hasSubArticles ? (_jsx(ArticleTreeList, { onChildEditArticleChange: onChildEditArticleChange, articleMenuId: row.id, oakPath: `${oakFullpath}.article$articleMenu` })) : (_jsx(ArticleMenuTreeList, { parentId: row.id, oakPath: `${oakFullpath}.articleMenu$parent`, entity: row.entity, entityId: row.entityId, onGrandChildEditArticleChange: onChildEditArticleChange }));
const items = [];
if (allowCreateSubArticle) {
items.push({
key: 'allowCreateSubArticle',
label: (_jsx("div", { className: Styles.addAction, onClick: () => {
modal.confirm({
title: '输入文章标题',
cancelText: '取消',
okText: '提交',
content: (_jsx(Input, { ref: subArticleNameRef })),
onOk: async () => {
const { value } = subArticleNameRef.current.input;
if (!value) {
setMessage({
type: 'warning',
content: '请输入文章标题',
});
}
else {
await createSubArticle(value);
setShowSub(true);
}
}
});
}, children: "\u6DFB\u52A0\u6587\u7AE0" }))
});
}
if (allowCreateSubMenu) {
items.push({
key: 'allowCreateSubMenu',
label: (_jsx("div", { className: Styles.addAction, onClick: () => {
modal.confirm({
title: '输入子分类标题',
cancelText: '取消',
okText: '提交',
content: (_jsx(Input, { ref: subMenuNameRef })),
onOk: async () => {
const { value } = subMenuNameRef.current.input;
if (!value) {
setMessage({
type: 'warning',
content: '请输入分类标题',
});
}
else {
await createSubArticleMenu(value);
setShowSub(true);
}
}
});
}, children: "\u6DFB\u52A0\u5B50\u5206\u7C7B" }))
});
}
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: Styles.container, children: [_jsx("div", { className: Styles.ne, children:
// nameEditing ? <div className={Styles.name}>
// <Input
// autoFocus
// value={ name || row?.name}
// onChange={(evt) => setName(evt.target.value)}
// onPressEnter={async () => {
// if (name && name !== row?.name) {
// await onUpdateName(name);
// }
// setNameEditing(false);
// }}
// onBlur={async () => {
// if (name && name !== row?.name) {
// await onUpdateName(name);
// }
// setNameEditing(false);
// }}
// />
// </div> :
_jsxs(_Fragment, { children: [_jsx(Button, { type: "text", icon: _jsx(EditOutlined, {}), size: "small", onClick: () => {
setNameEditing(true);
modal.confirm({
title: '编辑分类',
cancelText: '取消',
okText: '提交',
content: (_jsxs("div", { children: [_jsx(Form.Item, { label: "\u5206\u7C7B\u540D\u79F0", children: _jsx(Input, { ref: menuNameRef, defaultValue: row.name }) }), _jsx(Form.Item, { label: "LOGO", help: _jsxs("div", { children: [_jsx("span", { children: "\u8BF7\u4E0A\u4F20LOGO\u9AD8\u6E05\u56FE\u7247\uFF0C" }), _jsx("span", { children: "108*108\u50CF\u7D20\uFF0C\u4EC5\u652F\u6301PNG\u3001JPG\u683C\u5F0F\uFF0C\u5927\u5C0F\u4E0D\u8D85\u8FC7300KB\u3002" })] }), children: _jsx(_Fragment, { children: _jsx(OakGallery, { oakPath: oakFullpath
? `${oakFullpath}.extraFile$entity$1`
: undefined, type: "image", origin: "qiniu", tag1: "logo", entity: "articleMenu", accept: ".PNG, .JPG", maxNumber: 1 }) }) })] })),
onOk: async () => {
if (menuNameRef.current.input.value) {
await onUpdateName(menuNameRef.current.input.value);
}
else {
setMessage({
type: 'warning',
content: '请输入分类标题',
});
}
}
});
}, style: { marginRight: 4 } }), _jsxs("div", { className: Styles.name, children: [logo ? (_jsx(Image, { height: 26, width: 26, src: logo, preview: false })) : null, _jsx("div", { style: { marginLeft: 4, overflow: 'hidden', width: '100px', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: row?.name })] })] }) }), _jsx(Divider, { type: "vertical", style: { height: '100%', marginTop: 4, marginBottom: 4 } }), _jsxs("div", { className: Styles.control, children: [!row.parentId && _jsx(Button, { type: "text", onClick: () => {
gotoDoc(row?.id);
}, icon: _jsx(EyeOutlined, {}) }), _jsx(Dropdown, { menu: { items }, placement: "bottomRight", arrow: true, children: _jsx(Button, { type: "text", icon: _jsx(PlusOutlined, {}), size: "small" }) }), _jsx(Button, { type: "text", icon: _jsx(MinusOutlined, {}), size: "small", onClick: () => {
if (!allowRemove) {
modal.error({
title: '无法删除',
content: hasSubArticles ? '请先删除目录下的文章' : '请先删除目录下的子目录',
okText: '确认'
});
}
else {
onRemove();
}
} }), (hasSubArticles || hasSubMenus) ? (showSub ?
_jsx(Button, { type: "text", icon: _jsx(UpOutlined, {}), size: "small", onClick: () => setShowSub(false) }) :
_jsx(Button, { type: "text", icon: _jsx(DownOutlined, {}), size: "small", onClick: () => setShowSub(true) })) : _jsx("div", { className: Styles.ph })] })] }), showSub && (_jsx("div", { className: Styles.sub, children: Sub })), contextHolder] }));
}
else {
const Sub = showSub && hasSubArticles ? (_jsx(ArticleTreeList, { onChildEditArticleChange: onChildEditArticleChange, articleMenuId: row.id, oakPath: `${oakFullpath}.article$articleMenu`, show: show, getBreadcrumbItemsByParent: getBreadcrumbItemsByParent, breadcrumbItems: newBreadcrumbItems, drawerOpen: drawerOpen, changeDrawerOpen: changeDrawerOpen, selectedArticleId: selectedArticleId, openArray: openArray ? openArray : undefined, getTopInfo: getTopInfo, articleId: articleId, currentArticle: currentArticle, setCurrentArticle: setCurrentArticle })) : (_jsx(ArticleMenuTreeList, { parentId: row.id, oakPath: `${oakFullpath}.articleMenu$parent`, onGrandChildEditArticleChange: onChildEditArticleChange, show: show, getBreadcrumbItems: getBreadcrumbItemsByParent, breadcrumbItems: newBreadcrumbItems, drawerOpen: drawerOpen, changeDrawerOpen: changeDrawerOpen, selectedArticleId: selectedArticleId, openArray: openArray ? openArray : undefined, getTopInfo: getTopInfo, articleId: articleId, currentArticle: currentArticle, setCurrentArticle: setCurrentArticle }));
if (!row.parentId && articleMenuId) {
return (_jsxs(_Fragment, { children: [_jsx("div", { children: Sub }), contextHolder] }));
}
else {
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: Styles.test, onClick: () => {
setShowSub(!showSub);
setNewBreadcrumbItems([...breadItems, row?.name]);
}, style: showSub ? { background: '#f5f5f5' } : { background: 'transparent' }, children: [_jsx("div", { className: Styles.ne, children: _jsxs("div", { className: Styles.name, children: [logo ? (_jsx(Image, { height: 26, width: 26, src: logo, preview: false, style: { marginRight: 4 } })) : null, _jsx("div", { children: row?.name })] }) }), _jsx("div", { className: Styles.control, children: (hasSubArticles || hasSubMenus) ? (showSub ?
_jsx("div", { className: Styles.downArrow }) :
_jsx("div", { className: Styles.leftArrow })) : _jsx("div", { className: Styles.ph }) })] }), showSub && (_jsx("div", { className: Styles.sub3, style: { background: '#f5f5f5' }, children: Sub })), contextHolder] }));
}
}
}
return null;
}

View File

@ -0,0 +1,210 @@
.container {
width: 320px;
display: flex;
flex-direction: row;
align-items: center;
height: 60px;
justify-content: space-between;
.ne {
min-width: 140px;
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
align-items: center;
justify-content: space-between;
.name {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
}
}
.control {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
padding: 10px;
.ph {
width: 24px;
height: 24px;
}
.addAction {
display: flex;
justify-content: center;
align-items: center;
}
}
}
.sub {
background: rgba(0, 0, 0, 0.02);
padding-left: 18px;
}
.container2 {
width: 260px;
display: flex;
flex-direction: row;
align-items: center;
height: 45px;
justify-content: space-between;
cursor: pointer;
.ne {
min-width: 140px;
flex: 1;
display: flex;
flex-direction: row;
margin-left: 20px;
align-items: center;
justify-content: space-between;
.name {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
font-size: 16px;
font-weight: 700;
}
}
.control {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
padding: 10px;
.ph {
width: 24px;
height: 24px;
}
.addAction {
display: flex;
justify-content: center;
align-items: center;
}
}
}
.sub2 {
padding-left: 18px;
}
.container2:hover {
color: #999;
}
.test {
display: flex;
flex-direction: row;
align-items: center;
height: 45px;
justify-content: space-between;
cursor: pointer;
.ne {
margin-left: 10px;
min-width: 140px;
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.name {
display: flex;
flex-direction: row;
align-items: center;
flex: 1;
font-size: 14px;
font-weight: bold;
}
}
.control {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
// padding: 10px;
.ph {
width: 24px;
height: 24px;
}
.addAction {
display: flex;
justify-content: center;
align-items: center;
}
.leftArrow {
width: 0;
height: 0;
position: relative;
margin: 20px;
}
.leftArrow::before,
.leftArrow::after {
content: '';
position: absolute;
width: 0;
height: 1px; /* 调整箭头线的粗细 */
background-color: #000000E0; /* 调整箭头线的颜色 */
top: 50%; /* 垂直居中 */
}
.leftArrow::before {
left: 0;
transform: rotate(45deg); /* 调整箭头线的角度 */
transform-origin: 0 0; /* 调整旋转的中心点 */
width: 6px; /* 调整箭头线的长度 */
}
.leftArrow::after {
left: 0;
width: 5px; /* 调整箭头线的长度 */
transform: rotate(-45deg); /* 调整箭头线的角度 */
transform-origin: 0 0; /* 调整旋转的中心点 */
}
.downArrow {
width: 0;
height: 0;
position: relative;
margin: 20px;
}
.downArrow::before,
.downArrow::after {
content: '';
position: absolute;
width: 0;
height: 1px; /* 调整箭头线的粗细 */
background-color: #000000E0; /* 调整箭头线的颜色 */
top: 50%; /* 垂直居中 */
}
.downArrow::before {
left: 0;
transform: rotate(-45deg); /* 调整箭头线的角度 */
transform-origin: 0 0; /* 调整旋转的中心点 */
width: 6px; /* 调整箭头线的长度 */
}
.downArrow::after {
left: 0;
width: 5px; /* 调整箭头线的长度 */
transform: rotate(-135deg); /* 调整箭头线的角度 */
transform-origin: 0 0; /* 调整旋转的中心点 */
}
}
}
.test:hover {
color: #999
}
.sub3 {
padding-left: 10px;
}

View File

@ -0,0 +1,34 @@
/// <reference types="react" />
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;
onGrandChildEditArticleChange: (data: string) => void;
show: string;
articleMenuId: string;
articleId: string;
getBreadcrumbItems: (breadcrumbItems: string[]) => void;
breadcrumbItems: string[];
drawerOpen: boolean;
changeDrawerOpen: (open: boolean) => void;
addOpen: boolean;
changeAddOpen: (addOpen: boolean) => void;
selectedArticleId: string;
defaultOpen: boolean;
changeDefaultOpen: (defaultOpen: boolean, openArray: string[]) => void;
openArray: string[];
getTopInfo: (data: {
name: string;
date: number;
}) => void;
getSearchOpen: (searchOpenArray: string[]) => void;
getSideInfo: (data: {
id: string;
name: string;
coverUrl: string;
}) => void;
currentArticle: string;
setCurrentArticle: (id: string) => void;
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

Some files were not shown because too many files have changed in this diff Show More