支持es编译
This commit is contained in:
parent
19ae9af698
commit
6247000190
|
|
@ -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;
|
||||
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -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;
|
||||
}>;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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,
|
||||
},
|
||||
}, {});
|
||||
}
|
||||
|
|
@ -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[];
|
||||
}>;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
}>;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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';
|
||||
/**
|
||||
* 生成二维码优先级如下:
|
||||
* 0)如果在SystemConfig中指定了qrCodeType,则按照qrCodeType去生成
|
||||
* 1)如果有服务号,优先生成关注服务号的带参二维码
|
||||
* 2)如果有小程序,优先生成小程序的二维码(如果小程序中配置了qrCodePrefix),其次生成小程序码
|
||||
* @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>;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export default {
|
||||
// mobile,
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
const userAuth = {
|
||||
cascadePath: '',
|
||||
};
|
||||
const authDef = {
|
||||
actionAuth: {
|
||||
create: [userAuth],
|
||||
}
|
||||
};
|
||||
export default authDef;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
const checkers = [];
|
||||
export default checkers;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
const checkers = [
|
||||
{
|
||||
type: 'data',
|
||||
action: 'create',
|
||||
entity: 'wechatQrCode',
|
||||
checker: (data) => {
|
||||
},
|
||||
}
|
||||
];
|
||||
export default checkers;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import Map from './map';
|
||||
import Location from './location';
|
||||
export { Map, Location };
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import Map from './map';
|
||||
import Location from './location';
|
||||
export { Map, Location };
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
.oak-map {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
"use strict";
|
||||
// index.ts
|
||||
OakComponent({
|
||||
entity: 'area',
|
||||
isList: false,
|
||||
formData: ({ data: area }) => ({
|
||||
name: area?.name,
|
||||
}),
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
@ -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: {}
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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',
|
||||
} })) }) }) }));
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
.container {
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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: {}
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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%'
|
||||
} }) })] }) }) }));
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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: {},
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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%',
|
||||
} }) })] }) }) }));
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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 })] })] }));
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in New Issue