application 多个web配置

This commit is contained in:
wkj 2024-04-12 14:56:43 +08:00
parent 89a7761722
commit ca7f15829f
78 changed files with 1453 additions and 625 deletions

View File

@ -7,7 +7,7 @@ import { File } from 'formidable';
export declare function getApplication<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
type: AppType;
domain: string;
}, context: Cxt): Promise<string>;
}, context: Cxt): Promise<string | undefined>;
export declare function signatureJsSDK<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ url, env }: {
url: string;
env: WebEnv;

View File

@ -8,7 +8,64 @@ 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', {
// const [application] = await context.select(
// 'application',
// {
// data: cloneDeep(applicationProjection),
// filter: {
// type,
// system: {
// domain$system: {
// url: domain,
// },
// },
// },
// },
// {}
// );
// //微信小程序环境下 没有就报错
// if (type === 'wechatMp') {
// assert(
// application,
// '微信小程序环境下 application必须存在小程序相关配置'
// );
// } else if (type === 'native') {
// assert(application, 'APP环境下 application必须存在APP相关配置');
// } else {
// //web 或 wechatPublic
// if (type === 'wechatPublic') {
// // 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
// if (!application) {
// const [application2] = await context.select(
// 'application',
// {
// data: cloneDeep(applicationProjection),
// filter: {
// type: 'web',
// system: {
// domain$system: {
// url: domain,
// },
// },
// },
// },
// {}
// );
// assert(
// application2,
// '微信公众号环境下 application不存在公众号配置但必须存在web相关配置'
// );
// return application2.id as string;
// }
// } else {
// assert(application, 'web环境下 application必须存在web相关配置');
// }
// }
// return application.id as string;
// 先找指定domain的应用如果不存在再找系统下面的domain 但无论怎么样都必须一项
//
let applications = await context.select('application', {
data: cloneDeep(applicationProjection),
filter: {
type,
@ -17,21 +74,47 @@ export async function getApplication(params, context) {
url: domain,
},
},
domain: {
url: domain,
},
},
}, {});
//微信小程序环境下 没有就报错
if (type === 'wechatMp') {
assert(application, '微信小程序环境下 application必须存在小程序相关配置');
assert(applications.length <= 1, `指定域名的应用 只能存在一项或未指定`);
if (applications.length === 0) {
applications = await context.select(
'application',
{
data: cloneDeep(applicationProjection),
filter: {
type,
system: {
domain$system: {
url: domain,
},
},
domainId: {
$exists: false,
},
},
},
{}
);
}
else if (type === 'native') {
assert(application, 'APP环境下 application必须存在APP相关配置');
}
else {
//web 或 wechatPublic
if (type === 'wechatPublic') {
// 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
if (!application) {
const [application2] = await context.select('application', {
switch (type) {
case 'wechatMp': {
assert(applications.length === 1, `微信小程序环境下,同一个系统必须存在唯一的【${type}】应用`);
const application = applications[0];
return application.id;
}
case 'native': {
assert(applications.length === 1, `APP环境下同一个系统必须存在唯一的【${type}】应用`);
const application = applications[0];
return application.id;
}
case 'wechatPublic': {
// 微信公众号环境下未配置公众号可以使用web的application
if (applications.length === 0) {
let applications2 = await context.select('application', {
data: cloneDeep(applicationProjection),
filter: {
type: 'web',
@ -40,17 +123,50 @@ export async function getApplication(params, context) {
url: domain,
},
},
domain: {
url: domain,
},
},
}, {});
assert(application2, '微信公众号环境下 application不存在公众号配置但必须存在web相关配置');
return application2.id;
assert(applications2.length <= 1, `指定域名的应用 只能存在一项或未指定`);
if (applications2.length === 0) {
applications2 = await context.select(
'application',
{
data: cloneDeep(applicationProjection),
filter: {
type,
system: {
domain$system: {
url: domain,
},
},
domainId: {
$exists: false,
},
},
},
{}
);
}
assert(applications2.length === 1, '微信公众号环境下, 可以未配置公众号但必须存在web的application');
const application = applications2[0];
return application.id;
}
assert(applications.length === 1, `微信公众号环境下,同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名`);
const application = applications[0];
return application.id;
}
else {
assert(application, 'web环境下 application必须存在web相关配置');
case 'web': {
assert(applications.length === 1, `web环境下同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名`);
const application = applications[0];
return application.id;
}
default: {
assert(false, `不支持的类型【${type}`);
return;
}
}
return application.id;
}
export async function signatureJsSDK({ url, env }, context) {
const application = context.getApplication();
@ -64,7 +180,7 @@ export async function signatureJsSDK({ url, env }, context) {
}
export async function uploadWechatMedia(params, // FormData表单提交 isPermanent 变成 'true' | 'false'
context) {
const { applicationId, file, type: mediaType, description, extraFileId } = params;
const { applicationId, file, type: mediaType, description, extraFileId, } = params;
assert(applicationId);
const isPermanent = params.isPermanent === 'true';
const filename = file.originalFilename;
@ -98,14 +214,14 @@ context) {
if (isPermanent) {
// 只有公众号才能上传永久素材
assert(type === 'wechatPublic');
const result = await wechatInstance.createMaterial({
const result = (await wechatInstance.createMaterial({
type: mediaType,
media: fileStream,
filename,
filetype,
fileLength,
description: description ? JSON.parse(description) : null,
});
}));
mediaId = result.media_id;
}
else {

View File

@ -1062,135 +1062,118 @@ export async function sendCaptcha({ mobile, env, type: type2, }, context) {
const closeRootMode = context.openRootMode();
if (process.env.NODE_ENV !== 'development' && !mockSend) {
const [count1, count2] = await Promise.all([
context.count(
'captcha',
{
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
context.count('captcha', {
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
},
{
dontCollect: true,
}
),
context.count(
'captcha',
{
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
}, {
dontCollect: true,
}),
context.count('captcha', {
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
},
{
dontCollect: true,
}
),
}, {
dontCollect: true,
}),
]);
if (count1 > 5 || count2 > 5) {
closeRootMode();
throw new OakUserException('您已发送很多次短信,请休息会再发吧');
}
}
const [captcha] = await context.select(
'captcha',
{
data: {
id: 1,
code: 1,
$$createAt$$: 1,
},
filter: {
mobile,
$$createAt$$: {
$gt: now - duration * 60 * 1000,
},
expired: false,
type: type2,
},
const [captcha] = await context.select('captcha', {
data: {
id: 1,
code: 1,
$$createAt$$: 1,
},
{
dontCollect: true,
}
);
filter: {
mobile,
$$createAt$$: {
$gt: now - duration * 60 * 1000,
},
expired: false,
type: type2,
},
}, {
dontCollect: true,
});
if (captcha) {
const code = captcha.code;
if (process.env.NODE_ENV === 'development' || mockSend) {
closeRootMode();
return `验证码[${code}]已创建`;
} else if (captcha.$$createAt$$ - now < 60000) {
}
else if (captcha.$$createAt$$ - now < 60000) {
closeRootMode();
throw new OakUserException('您的操作太迅捷啦,请稍等再点吧');
} else {
}
else {
assert(origin, '必须设置短信渠道');
// todo 再次发送
const result = await sendSms(
{
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
},
context
);
const result = await sendSms({
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
}, context);
closeRootMode();
if (result.success) {
return '验证码已发送';
}
return '验证码发送失败';
}
} else {
}
else {
let code;
if (process.env.NODE_ENV === 'development' || mockSend) {
code = mobile.substring(7);
} else {
}
else {
code = Math.floor(Math.random() * 10000).toString();
while (code.length < 4) {
code += '0';
}
}
const id = await generateNewIdAsync();
await context.operate(
'captcha',
{
id: await generateNewIdAsync(),
action: 'create',
data: {
id,
mobile,
code,
visitorId,
env,
expired: false,
expiresAt: now + duration * 60 * 1000,
type: type2,
},
await context.operate('captcha', {
id: await generateNewIdAsync(),
action: 'create',
data: {
id,
mobile,
code,
visitorId,
env,
expired: false,
expiresAt: now + duration * 60 * 1000,
type: type2,
},
{
dontCollect: true,
}
);
}, {
dontCollect: true,
});
if (process.env.NODE_ENV === 'development' || mockSend) {
closeRootMode();
return `验证码[${code}]已创建`;
} else {
}
else {
assert(origin, '必须设置短信渠道');
//发送短信
const result = await sendSms(
{
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
},
context
);
const result = await sendSms({
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
}, context);
closeRootMode();
if (result.success) {
return '验证码已发送';

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Row, Descriptions, Typography, Button, Modal } from 'antd';
import { Row, Descriptions, Typography, Button, Modal, Space } from 'antd';
import ApplicationUpsert from '../upsert';
export default function Render(props) {
const { id, name, description, type, oakFullpath, oakExecutable, oakExecuting } = props.data;
@ -7,15 +7,23 @@ export default function Render(props) {
const [open, setOpen] = useState(false);
if (id && oakFullpath) {
return (<>
<Modal open={open} width={800} onCancel={() => {
<Modal open={open} width={500} onCancel={() => {
clean();
setOpen(false);
}} footer={<Button type="primary" onClick={async () => {
}} footer={<Space>
<Button onClick={async () => {
clean();
setOpen(false);
}} disabled={oakExecuting}>
{t('common::action.cancel')}
</Button>
<Button type="primary" onClick={async () => {
await execute();
setOpen(false);
}} disabled={oakExecutable !== true || oakExecuting}>
{t('common::action.confirm')}
</Button>}>
{t('common::action.confirm')}
</Button>
</Space>}>
<ApplicationUpsert oakPath={oakFullpath} oakId={id}/>
</Modal>
<Descriptions column={2} bordered>

View File

@ -7,6 +7,8 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
formData({ data }) {
return data || {};

View File

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

View File

@ -1,6 +1,15 @@
export default OakComponent({
isList: false,
entity: 'application',
projection: {
id: 1,
name: 1,
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
formData({ data }) {
return data || {};
},
@ -16,18 +25,29 @@ export default OakComponent({
value: 'wechatPublic',
},
],
domains: [],
},
/* lifetimes: {
ready() {
const { systemId, oakId } = this.props;
if (!oakId) {
if (systemId) {
this.update({
methods: {
async getDomains(systemId) {
const { data: domains } = await this.features.cache.refresh(
'domain',
{
data: {
id: 1,
systemId: 1,
url: 1,
apiPath: 1,
port: 1,
protocol: 1,
},
filter: {
systemId,
});
},
}
}
);
this.setState({
domains,
});
},
}, */
},
});

View File

@ -4,8 +4,6 @@ import { WebComponentProps } from 'oak-frontend-base';
export default function Render(props: WebComponentProps<EntityDict, 'application', false, {
name: string;
description: string;
variant: 'inline' | 'alone' | 'dialog';
showBack: boolean;
type: EntityDict['application']['Schema']['type'];
typeArr: Array<{
label: string;
@ -15,6 +13,9 @@ export default function Render(props: WebComponentProps<EntityDict, 'application
oakId: string;
style: EntityDict['system']['Schema']['style'];
$$createAt$$: number;
domainId: string;
domains: EntityDict['domain']['Schema'][];
}, {
confirm: () => void;
getDomains: (systemId: string) => Promise<void>;
}>): React.JSX.Element;

View File

@ -1,8 +1,8 @@
import React from 'react';
import { Form, Select, Input } from 'antd';
export default function Render(props) {
const { name, description, type, typeArr, $$createAt$$, } = props.data;
const { t, update, navigateBack, confirm } = props.methods;
const { systemId, name, description, type, typeArr, $$createAt$$, domainId, domains, } = props.data;
const { t, update, confirm, getDomains } = props.methods;
return (<Form colon={true} labelCol={{ span: 6 }} wrapperCol={{ span: 16 }}>
<Form.Item label="名称" required>
<>
@ -31,6 +31,26 @@ export default function Render(props) {
update({
type: value,
});
}}/>
</>
</Form.Item>
<Form.Item label="域名">
<>
<Select allowClear value={domainId} style={{ width: 120 }} options={domains?.map((ele) => ({
label: ele.url,
value: ele.id,
}))} onChange={(value) => {
if (!value) {
update({
domainId: undefined,
});
return;
}
update({
domainId: value,
});
}} onClick={() => {
getDomains(systemId);
}}/>
</>
</Form.Item>

View File

@ -6,7 +6,7 @@ const Colors = ['primary', 'success', 'error', 'warning', 'info'];
function Color(props) {
const { value = {}, setValue } = props;
;
return (<Form>
return (<Form labelCol={{ span: 4 }} wrapperCol={{ span: 20 }} style={{ maxWidth: 600 }}>
{Colors.map((ele) => (<Form.Item key={ele} label={ele}
// required
tooltip={`设置系统【${ele}】颜色`}>

View File

@ -0,0 +1,6 @@
import React from 'react';
import { Config } from '../../../../types/Config';
export default function Basic(props: {
app: Required<Config>['App'];
setValue: (path: string, value: any) => void;
}): React.JSX.Element;

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Row, Col, Card, Divider, Input, Form, Space, Switch, } from 'antd';
import Styles from './web.module.less';
// qrCodeType?: QrCodeType; // 生成二维码时,优先生成的类型
// qrCodeApplicationId?: string; // 生成二维码时优先使用的appId
// qrCodePublicForMpId?: string; // 如果qrCodeType是wechatPublicForMp在此指明关联的小程序appId
// mpShareImageUrl?: string; // 小程序分享时的imageUrl使用网络图片54
export default function Basic(props) {
const { app, setValue } = props;
return (<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Row>
<Card className={Styles.tips}>
每种均可配置一个相应的服务所使用的帐号请准确对应
</Card>
</Row>
<Col flex="auto">
<Divider orientation="left" className={Styles.title}>
基础设置
</Divider>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }}>
<Form.Item label="小程序分享时图片地址" tooltip="小程序分享时图片地址,使用网络图片">
<Input placeholder="请输入图片地址" type="text" value={app?.mpShareImageUrl} onChange={(e) => setValue(`mpShareImageUrl`, e.target.value)}/>
</Form.Item>
<Form.Item label="用户合并"
//name="mergeUserDirectly"
tooltip="当发现用户具有相同的特征时是否合并">
<>
<Switch checkedChildren="是" unCheckedChildren="否" checked={app?.mergeUserDirectly} onChange={(checked) => setValue(`mergeUserDirectly`, checked)}/>
</>
</Form.Item>
<Form.Item label="Token刷新时间" tooltip="设置Token刷新时间默认使用系统定义">
<Input placeholder="请输入Token刷新时间" type="number" value={app?.tokenRefreshTime} onChange={(e) => {
const val = e.target.value;
if (val) {
setValue(`tokenRefreshTime`, Number(val));
}
else {
setValue(`tokenRefreshTime`, val);
}
}} suffix="毫秒"/>
</Form.Item>
<Form.Item label="Token过期时间" tooltip="设置Token过期时间默认使用系统定义的">
<Input placeholder="Token过期时间" type="number" value={app?.tokenExpireTime} onChange={(e) => {
const val = e.target.value;
if (val) {
setValue(`tokenExpireTime`, Number(val));
}
else {
setValue(`tokenExpireTime`, val);
}
}} suffix="毫秒"/>
</Form.Item>
</Form>
</Col>
</Space>);
}

View File

@ -0,0 +1,16 @@
.label {
color: var(--oak-text-color-primary);
font-size: 28px;
line-height: 36px;
}
.tips {
color: var(--oak-text-color-placeholder);
font-size: 12px;
}
.title {
margin-bottom: 0px;
margin-top:36px;
}

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Tabs, Row, Col, Card, Divider, Input, Form, Space, Modal, message, Switch, Select } from 'antd';
import { Tabs, Row, Col, Card, Divider, Input, Form, Space, Select, Modal, message, Switch, } from 'antd';
import { get } from 'oak-domain/lib/utils/lodash';
import Styles from './web.module.less';
function Ali(props) {
@ -25,27 +25,27 @@ function Ali(props) {
key: `${idx}`,
label: `短信${idx + 1}`,
children: (<Form colon={false} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
<Form.Item label="accessKeyId" >
<Form.Item label="accessKeyId">
<>
<Input placeholder="请输入accessKeyId" type="text" value={ele.accessKeyId} onChange={(e) => setValue(`${idx}.accessKeyId`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="accessKeySecret" >
<Form.Item label="accessKeySecret">
<>
<Input placeholder="请输入accessKeySecret" type="text" value={ele.accessKeySecret} onChange={(e) => setValue(`${idx}.accessKeySecret`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="endpoint" >
<Form.Item label="endpoint">
<>
<Input placeholder="请输入endpoint" type="text" value={ele.endpoint} onChange={(e) => setValue(`${idx}.endpoint`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="apiVersion" >
<Form.Item label="apiVersion">
<>
<Input placeholder="请输入apiVersion" type="text" value={ele.apiVersion} onChange={(e) => setValue(`${idx}.defaultSignName`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="defaultSignName" >
<Form.Item label="defaultSignName">
<>
<Input placeholder="请输入defaultSignName" type="text" value={ele.defaultSignName} onChange={(e) => setValue(`${idx}.defaultSignName`, e.target.value)}/>
</>
@ -343,22 +343,22 @@ function CTYun(props) {
key: `${idx}`,
label: `短信${idx + 1}`,
children: (<Form colon={false} labelAlign="left" layout="vertical" style={{ marginTop: 10 }}>
<Form.Item label="accessKey" >
<Form.Item label="accessKey">
<>
<Input placeholder="请输入accessKey" type="text" value={ele.accessKey} onChange={(e) => setValue(`${idx}.accessKey`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="securityKey" >
<Form.Item label="securityKey">
<>
<Input placeholder="请输入securityKey" type="text" value={ele.securityKey} onChange={(e) => setValue(`${idx}.securityKey`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="endpoint" >
<Form.Item label="endpoint">
<>
<Input placeholder="请输入endpoint" type="text" value={ele.endpoint} onChange={(e) => setValue(`${idx}.endpoint`, e.target.value)}/>
</>
</Form.Item>
<Form.Item label="defaultSignName" >
<Form.Item label="defaultSignName">
<>
<Input placeholder="请输入defaultSignName" type="text" value={ele.defaultSignName} onChange={(e) => setValue(`${idx}.defaultSignName`, e.target.value)}/>
</>
@ -506,9 +506,8 @@ function CTYun(props) {
}
export default function Sms(props) {
const { sms, setValue, removeItem, cleanKey } = props;
const { ali, tencent, mockSend, ctyun } = sms;
return (
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
const { ali, tencent, ctyun } = sms;
return (<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Row>
<Card className={Styles.tips}>
每种均可配置一个相应的服务所使用的帐号请准确对应
@ -518,97 +517,43 @@ export default function Sms(props) {
<Divider orientation="left" className={Styles.title}>
短信配置
</Divider>
<Form>
<Form.Item
label="模拟发送"
//name="mockSend"
tooltip="开启模拟发送短信发短信不会调用api"
>
<Form labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} style={{ maxWidth: 600 }}>
<Form.Item label="模拟发送"
//name="mockSend"
tooltip="开启模拟发送短信发短信不会调用api">
<>
<Switch
checkedChildren="是"
unCheckedChildren="否"
checked={mockSend}
onChange={(checked) =>
setValue(`mockSend`, checked)
}
/>
<Switch checkedChildren="是" unCheckedChildren="否" checked={sms?.mockSend} onChange={(checked) => setValue(`mockSend`, checked)}/>
</>
</Form.Item>
<Form.Item
label="默认渠道"
tooltip="发送短信渠道,如阿里云、腾讯云、天翼云"
>
<Form.Item label="默认渠道" tooltip="发送短信渠道,如阿里云、腾讯云、天翼云">
<>
<Select
value={sms?.defaultOrigin}
style={{ width: 120 }}
onChange={(value) => {
setValue(`defaultOrigin`, value);
}}
options={[
{ value: 'ali', label: '阿里云' },
{ value: 'tencent', label: '腾讯云' },
{ value: 'ctyun', label: '天翼云' },
]}
/>
<Select placeholder="请选择渠道" value={sms?.defaultOrigin} style={{ width: 120 }} onChange={(value) => {
setValue(`defaultOrigin`, value);
}} options={[
{ value: 'ali', label: '阿里云' },
{ value: 'tencent', label: '腾讯云' },
{ value: 'ctyun', label: '天翼云' },
]}/>
</>
</Form.Item>
<Form.Item label="验证码模版名" tooltip="">
<Input
placeholder="请输入defaultCodeTemplateName"
type="text"
value={sms?.defaultCodeTemplateName}
onChange={(e) =>
setValue(
`defaultCodeTemplateName`,
e.target.value
)
}
/>
<Form.Item label="验证码模版名" tooltip="短信验证码模版名">
<Input placeholder="请输入验证码模版名" type="text" value={sms?.defaultCodeTemplateName} onChange={(e) => setValue(`defaultCodeTemplateName`, e.target.value)}/>
</Form.Item>
<Form.Item label="验证码有效时间" tooltip="">
<Input
placeholder="请输入defaultCodeDuration"
type="number"
value={sms?.defaultCodeDuration}
onChange={(e) => {
const val = e.target.value;
if (val) {
setValue(
`defaultCodeDuration`,
Number(val)
);
} else {
setValue(`defaultCodeDuration`, val);
}
}}
suffix="分钟"
/>
<Form.Item label="验证码有效时间" tooltip="短信验证码发送有效时间">
<Input placeholder="请输入验证码有效时间" type="number" value={sms?.defaultCodeDuration} onChange={(e) => {
const val = e.target.value;
if (val) {
setValue(`defaultCodeDuration`, Number(val));
}
else {
setValue(`defaultCodeDuration`, val);
}
}} suffix="分钟"/>
</Form.Item>
</Form>
</Col>
<Tencent
sms={tencent || []}
setValue={(path, value) => setValue(`tencent.${path}`, value)}
removeItem={(path, index) => removeItem(`tencent`, index)}
addItem={(path, index) => setValue(`tencent.${index}`, {})}
cleanKey={(path, key) => cleanKey(`tencent.${path}`, key)}
/>
<Ali
sms={ali || []}
setValue={(path, value) => setValue(`ali.${path}`, value)}
removeItem={(path, index) => removeItem(`ali`, index)}
addItem={(path, index) => setValue(`ali.${index}`, {})}
cleanKey={(path, key) => cleanKey(`ali.${path}`, key)}
/>
<CTYun
sms={ctyun || []}
setValue={(path, value) => setValue(`ctyun.${path}`, value)}
removeItem={(path, index) => removeItem(`ctyun`, index)}
addItem={(path, index) => setValue(`ctyun.${index}`, {})}
cleanKey={(path, key) => cleanKey(`ctyun.${path}`, key)}
/>
</Space>
);
<Tencent sms={tencent || []} setValue={(path, value) => setValue(`tencent.${path}`, value)} removeItem={(path, index) => removeItem(`tencent`, index)} addItem={(path, index) => setValue(`tencent.${index}`, {})} cleanKey={(path, key) => cleanKey(`tencent.${path}`, key)}/>
<Ali sms={ali || []} setValue={(path, value) => setValue(`ali.${path}`, value)} removeItem={(path, index) => removeItem(`ali`, index)} addItem={(path, index) => setValue(`ali.${index}`, {})} cleanKey={(path, key) => cleanKey(`ali.${path}`, key)}/>
<CTYun sms={ctyun || []} setValue={(path, value) => setValue(`ctyun.${path}`, value)} removeItem={(path, index) => removeItem(`ctyun`, index)} addItem={(path, index) => setValue(`ctyun.${index}`, {})} cleanKey={(path, key) => cleanKey(`ctyun.${path}`, key)}/>
</Space>);
}

View File

@ -6,10 +6,11 @@ import Cos from './cos/index';
import Map from './map/index';
import Live from './live/index';
import Sms from './sms/index';
import Basic from './basic/index';
export default function Render(props) {
const { entity, name, currentConfig, dirty } = props.data;
const { resetConfig, updateConfig, setValue, removeItem, cleanKey, t } = props.methods;
const { Account: account, Cos: cos, Map: map, Live: live, Sms: sms, } = currentConfig || {};
const { Account: account, Cos: cos, Map: map, Live: live, Sms: sms, App: app } = currentConfig || {};
return (<>
<Affix offsetTop={64}>
<Alert message={<div>
@ -62,6 +63,11 @@ export default function Render(props) {
label: '短信设置',
children: (<Sms sms={sms || {}} setValue={(path, value) => setValue(`Sms.${path}`, value)} removeItem={(path, index) => removeItem(`Sms.${path}`, index)} cleanKey={(path, key) => cleanKey(`Sms.${path}`, key)}/>),
},
{
key: '基础设置',
label: '基础设置',
children: (<Basic app={app || {}} setValue={(path, value) => setValue(`App.${path}`, value)}/>),
},
]}></Tabs>
</div>
</>);

View File

@ -13,19 +13,19 @@ declare const _default: <ED2 extends EntityDict & BaseEntityDict, T2 extends key
type?: ButtonProps['type'] | AmButtonProps['type'];
executeText?: string | undefined;
buttonProps?: (ButtonProps & {
color?: "success" | "default" | "primary" | "danger" | "warning" | undefined;
color?: "default" | "success" | "warning" | "primary" | "danger" | undefined;
fill?: "none" | "solid" | "outline" | undefined;
size?: "small" | "large" | "middle" | "mini" | undefined;
size?: "small" | "middle" | "large" | "mini" | undefined;
block?: boolean | undefined;
loading?: boolean | "auto" | undefined;
loadingText?: string | undefined;
loadingIcon?: import("react").ReactNode;
disabled?: boolean | undefined;
onClick?: ((event: import("react").MouseEvent<HTMLButtonElement, MouseEvent>) => unknown) | undefined;
type?: "button" | "reset" | "submit" | undefined;
type?: "button" | "submit" | "reset" | undefined;
shape?: "default" | "rounded" | "rectangular" | undefined;
children?: import("react").ReactNode;
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseDown" | "onMouseUp" | "onTouchEnd" | "onTouchStart"> & {
} & Pick<import("react").ClassAttributes<HTMLButtonElement> & import("react").ButtonHTMLAttributes<HTMLButtonElement>, "id" | "onMouseDown" | "onMouseUp" | "onTouchStart" | "onTouchEnd"> & {
className?: string | undefined;
style?: (import("react").CSSProperties & Partial<Record<"--text-color" | "--background-color" | "--border-radius" | "--border-width" | "--border-style" | "--border-color", string>>) | undefined;
tabIndex?: number | undefined;

View File

@ -7,6 +7,8 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
properties: {
systemId: '',
@ -15,5 +17,5 @@ export default OakComponent({
return {
applications: data || [],
};
}
},
});

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Tabs, Modal, Button } from 'antd';
import { Tabs, Modal, Button, Space } from 'antd';
import ApplicationPanel from '../../application/panel';
import ApplicationUpsert from '../../application/upsert';
export default function render(props) {
@ -9,21 +9,29 @@ export default function render(props) {
const [removeId, setRemoveId] = useState('');
if (oakFullpath && applications?.length > 0) {
return (<>
<Modal open={!!createId} width={800} onCancel={() => {
<Modal open={!!createId} width={500} onCancel={() => {
clean();
setCreateId('');
}} footer={<Button type='primary' onClick={async () => {
}} footer={<Space>
<Button onClick={async () => {
clean();
setCreateId('');
}} disabled={oakExecuting}>
{t('common::action.cancel')}
</Button>
<Button type="primary" onClick={async () => {
await execute();
setCreateId('');
}} disabled={oakExecutable !== true || oakExecuting}>
{t('common::action.confirm')}
</Button>}>
{t('common::action.confirm')}
</Button>
</Space>}>
<ApplicationUpsert oakId={createId} oakPath={`${oakFullpath}.${createId}`}/>
</Modal>
<Modal open={!!removeId} onCancel={() => {
clean();
setRemoveId('');
}} footer={<Button type='primary' onClick={async () => {
}} footer={<Button type="primary" onClick={async () => {
removeItem(removeId);
await execute();
setRemoveId('');
@ -41,12 +49,12 @@ export default function render(props) {
const appId = applications[Number(key)].id;
setRemoveId(appId);
}
}} items={applications?.length > 0 ?
applications.map((item, idx) => {
}} items={applications?.length > 0
? applications.map((item, idx) => {
return {
label: item.name,
key: `${idx}`,
children: (<ApplicationPanel oakPath={`${oakFullpath}.${item.id}`} oakId={item.id}/>)
children: (<ApplicationPanel oakPath={`${oakFullpath}.${item.id}`} oakId={item.id}/>),
};
})
: []}/>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Row, Modal, Descriptions, Typography, Button } from 'antd';
import { Row, Modal, Descriptions, Typography, Button, Space } from 'antd';
import SystemUpsert from '../upsert';
import Styles from './web.pc.module.less';
export default function Render(props) {
@ -11,12 +11,22 @@ export default function Render(props) {
<Modal open={open} onCancel={() => {
clean();
setOpen(false);
}} width={800} footer={<Button type='primary' onClick={async () => {
}} width={500} footer={<Space>
<Button
// type='primary'
onClick={async () => {
clean();
setOpen(false);
}} disabled={oakExecuting}>
{t('common::action.cancel')}
</Button>
<Button type="primary" onClick={async () => {
await execute();
setOpen(false);
}} disabled={oakExecutable !== true || oakExecuting}>
{t('common::action.confirm')}
</Button>}>
{t('common::action.confirm')}
</Button>
</Space>}>
<div className={Styles.upsert}>
<SystemUpsert oakId={oakId} oakPath={oakFullpath}/>
</div>

View File

@ -24,6 +24,8 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
}
}
},

View File

@ -8,8 +8,8 @@ import SmsTemplateList from '../../messageTypeSmsTemplate/tab';
import ApplicationList from '../application';
import Styles from './web.pc.module.less';
export default function Render(props) {
const { id, config, oakFullpath, name, style, application$system: applications } = props.data;
const { t, update, addItem, removeItem } = props.methods;
const { id, config, oakFullpath, name, style } = props.data;
const { t, } = props.methods;
if (id && oakFullpath) {
return (<div className={Styles.container}>
<Tabs tabPosition='left' items={[

View File

@ -1,6 +1,14 @@
export default OakComponent({
isList: false,
entity: 'system',
projection: {
id: 1,
name: 1,
config: 1,
description: 1,
super: 1,
folder: 1,
},
formData({ data }) {
return data || {};
},

View File

@ -15,11 +15,7 @@ export default function Render(props) {
</Form.Item>
<Form.Item label="目录" required
// name="folder"
tooltip="目录属性应和开发目录下的对应目录名匹配,请谨慎修改" rules={[
{
required: true,
},
]}>
tooltip="目录属性应和开发目录下的对应目录名匹配,请谨慎修改">
<>
<Input onChange={(e) => {
update({

View File

@ -5,8 +5,8 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
entity: keyof EntityDict;
entityFilter: any;
relationIds: string[];
rule: "single" | "all" | "free";
ruleOnRow: "single" | "all" | "free";
rule: "all" | "single" | "free";
ruleOnRow: "all" | "single" | "free";
onPickRelations: (ids: string[]) => void;
onPickRows: (ids: string[]) => void;
pickedRowIds: string[] | undefined;

View File

@ -12,7 +12,7 @@ declare const _default: (props: import("oak-frontend-base").ReactComponentProps<
claimUrl: string;
qrCodeType: QrCodeType;
multiple: boolean;
rule: "single" | "all" | "free";
ruleOnRow: "single" | "all" | "free";
rule: "all" | "single" | "free";
ruleOnRow: "all" | "single" | "free";
}>) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
export default _default;

View File

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

View File

@ -2,6 +2,7 @@ import { String, Text } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Schema as System } from './System';
import { Schema as Session } from './Session';
import { Schema as Domain } from './Domain';
import { Style } from '../types/Style';
export type Passport = 'email' | 'mobile' | 'wechat' | 'wechatPublic';
export type AppType = 'web' | 'wechatMp' | 'wechatPublic' | 'native';
@ -63,4 +64,5 @@ export interface Schema extends EntityShape {
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style;
sessions?: Session[];
domain?: Domain;
}

View File

@ -11,6 +11,7 @@ const entityDesc = {
config: '设置',
style: '样式',
sessions: '会话',
domain: '域名',
},
v: {
type: {

View File

@ -19,6 +19,6 @@ export default class Theme<ED extends EntityDict, Cxt extends BackendRuntimeCont
switchThemeMode(finalThemeMode: ETheme): void;
openSystemTheme(): void;
getColor(): string;
switchColor(color: string): void;
insertThemeStylesheet(theme: string, color: string, mode: ETheme): void;
switchColor(color: string, theme?: string): void;
insertThemeStylesheet(color: string, mode: ETheme, theme?: string): void;
}

View File

@ -6,11 +6,7 @@ const initialThemeState = {
setting: false,
themeMode: defaultTheme,
systemTheme: false,
isFullPage: false,
color: '#0052d9',
showHeader: true,
showBreadcrumbs: true,
showFooter: true,
};
export default class Theme extends Feature {
cache;
@ -82,15 +78,17 @@ export default class Theme extends Feature {
const state = this.themeState;
return state.color;
}
switchColor(color) {
switchColor(color, theme) {
const state = this.themeState;
if (color) {
state.color = color; // 某主题 主题色
const themeColor = 'blue'; // 主题颜色类型
// const themeColor = 'blue'; // 主题颜色类型
switch (process.env.OAK_PLATFORM) {
case 'web': {
this.insertThemeStylesheet(themeColor, color, state.themeMode);
document.documentElement.setAttribute('theme-color', themeColor);
this.insertThemeStylesheet(color, state.themeMode, theme);
if (theme) {
document.documentElement.setAttribute('theme-color', theme);
}
break;
}
default: {
@ -100,11 +98,13 @@ export default class Theme extends Feature {
this.set(state);
}
}
insertThemeStylesheet(theme, color, mode) {
insertThemeStylesheet(color, mode, theme) {
const isDarkMode = mode === 'dark';
const root = !isDarkMode
? `:root[theme-color='${theme}']`
: `:root[theme-color='${theme}'][theme-mode='dark']`;
? (theme
? `:root[theme-color='${theme}']`
: `:root`)
: (theme ? `:root[theme-color='${theme}'][theme-mode='dark']` : `:root[theme-mode='dark']`);
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.innerText = `${root}{

View File

@ -6,6 +6,7 @@ import { GenericAction } from "oak-domain/lib/actions/action";
import { String, Text } from "oak-domain/lib/types/DataType";
import { Style } from "../../types/Style";
import * as System from "../System/Schema";
import * as Domain from "../Domain/Schema";
import * as ExtraFile from "../ExtraFile/Schema";
import * as Notification from "../Notification/Schema";
import * as SessionMessage from "../SessionMessage/Schema";
@ -76,6 +77,7 @@ export type OpSchema = EntityShape & {
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
domainId?: ForeignKey<"domain"> | null;
};
export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
@ -85,7 +87,9 @@ export type Schema = EntityShape & {
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
domainId?: ForeignKey<"domain"> | null;
system: System.Schema;
domain?: Domain.Schema | null;
extraFile$application?: Array<ExtraFile.Schema>;
extraFile$application$$aggr?: AggregationResult<ExtraFile.Schema>;
notification$application?: Array<Notification.Schema>;
@ -123,6 +127,8 @@ type AttrFilter = {
system: System.Filter;
config: JsonFilter<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style: JsonFilter<Style>;
domainId: Q_StringValue;
domain: Domain.Filter;
extraFile$application: ExtraFile.Filter & SubQueryPredicateMetadata;
notification$application: Notification.Filter & SubQueryPredicateMetadata;
sessionMessage$application: SessionMessage.Filter & SubQueryPredicateMetadata;
@ -150,6 +156,8 @@ export type Projection = {
system?: System.Projection;
config?: number | JsonProjection<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style?: number | JsonProjection<Style>;
domainId?: number;
domain?: Domain.Projection;
extraFile$application?: ExtraFile.Selection & {
$entity: "extraFile";
};
@ -223,6 +231,9 @@ type ApplicationIdProjection = OneOf<{
type SystemIdProjection = OneOf<{
systemId: number;
}>;
type DomainIdProjection = OneOf<{
domainId: number;
}>;
export type SortAttr = {
id: number;
} | {
@ -243,6 +254,10 @@ export type SortAttr = {
system: System.SortAttr;
} | {
style: number;
} | {
domainId: number;
} | {
domain: Domain.SortAttr;
} | {
[k: string]: any;
} | OneOf<ExprOp<OpAttr | string>>;
@ -254,7 +269,7 @@ export type Sorter = SortNode[];
export type SelectOperation<P extends Object = Projection> = OakSelection<"select", P, Filter, Sorter>;
export type Selection<P extends Object = Projection> = SelectOperation<P>;
export type Aggregation = DeduceAggregation<Projection, Filter, Sorter>;
export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (({
export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId" | "domainId">> & (({
systemId?: never;
system: System.CreateSingleOperation;
} | {
@ -263,6 +278,15 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (
} | {
system?: never;
systemId: ForeignKey<"system">;
}) & ({
domainId?: never;
domain?: Domain.CreateSingleOperation;
} | {
domainId: ForeignKey<"domain">;
domain?: Domain.UpdateOperation;
} | {
domain?: never;
domainId?: ForeignKey<"domain">;
})) & {
extraFile$application?: OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">[]> | Array<OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">> | OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">>>;
notification$application?: OakOperation<Notification.UpdateOperation["action"], Omit<Notification.UpdateOperationData, "application" | "applicationId">, Omit<Notification.Filter, "application" | "applicationId">> | OakOperation<"create", Omit<Notification.CreateOperationData, "application" | "applicationId">[]> | Array<OakOperation<"create", Omit<Notification.CreateOperationData, "application" | "applicationId">> | OakOperation<Notification.UpdateOperation["action"], Omit<Notification.UpdateOperationData, "application" | "applicationId">, Omit<Notification.Filter, "application" | "applicationId">>>;
@ -279,7 +303,7 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (({
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId" | "domainId">> & (({
system?: System.CreateSingleOperation;
systemId?: never;
} | {
@ -291,6 +315,18 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (
} | {
system?: never;
systemId?: ForeignKey<"system">;
}) & ({
domain?: Domain.CreateSingleOperation;
domainId?: never;
} | {
domain?: Domain.UpdateOperation;
domainId?: never;
} | {
domain?: Domain.RemoveOperation;
domainId?: never;
} | {
domain?: never;
domainId?: ForeignKey<"domain"> | null;
})) & {
[k: string]: any;
extraFile$application?: OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<ExtraFile.RemoveOperation["action"], Omit<ExtraFile.RemoveOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">[]> | Array<OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">> | OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<ExtraFile.RemoveOperation["action"], Omit<ExtraFile.RemoveOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">>>;
@ -308,10 +344,13 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({
system?: System.UpdateOperation | System.RemoveOperation;
}) & ({
domain?: Domain.UpdateOperation | Domain.RemoveOperation;
}));
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>;
export type Operation = CreateOperation | UpdateOperation | RemoveOperation;
export type SystemIdSubQuery = Selection<SystemIdProjection>;
export type DomainIdSubQuery = Selection<DomainIdProjection>;
export type ApplicationIdSubQuery = Selection<ApplicationIdProjection>;
export type EntityDef = {
Schema: Schema;

View File

@ -28,6 +28,10 @@ export const desc = {
},
style: {
type: "object"
},
domainId: {
type: "ref",
ref: "domain"
}
},
actionType: "crud",

View File

@ -1 +1 @@
{ "name": "应用", "attr": { "description": "描述", "type": "类型", "system": "系统", "name": "名称", "config": "设置", "style": "样式", "sessions": "会话" }, "v": { "type": { "web": "网站", "wechatPublic": "微信公众号", "wechatMp": "微信小程序", "native": "App" } } }
{ "name": "应用", "attr": { "description": "描述", "type": "类型", "system": "系统", "name": "名称", "config": "设置", "style": "样式", "sessions": "会话", "domain": "域名" }, "v": { "type": { "web": "网站", "wechatPublic": "微信公众号", "wechatMp": "微信小程序", "native": "App" } } }

View File

@ -1,10 +1,11 @@
import { ForeignKey } from "oak-domain/lib/types/DataType";
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, ExprOp, ExpressionKey, SubQueryPredicateMetadata } from "oak-domain/lib/types/Demand";
import { OneOf } from "oak-domain/lib/types/Polyfill";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, EntityShape } from "oak-domain/lib/types/Entity";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, AggregationResult, EntityShape } from "oak-domain/lib/types/Entity";
import { GenericAction } from "oak-domain/lib/actions/action";
import { String, Int } from "oak-domain/lib/types/DataType";
import * as System from "../System/Schema";
import * as Application from "../Application/Schema";
export type OpSchema = EntityShape & {
url: String<64>;
apiPath?: String<32> | null;
@ -20,6 +21,8 @@ export type Schema = EntityShape & {
port: Int<2>;
systemId: ForeignKey<"system">;
system: System.Schema;
application$domain?: Array<Application.Schema>;
application$domain$$aggr?: AggregationResult<Application.Schema>;
} & {
[A in ExpressionKey]?: any;
};
@ -34,6 +37,7 @@ type AttrFilter = {
port: Q_NumberValue;
systemId: Q_StringValue;
system: System.Filter;
application$domain: Application.Filter & SubQueryPredicateMetadata;
};
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr | string>>;
export type Projection = {
@ -49,6 +53,12 @@ export type Projection = {
port?: number;
systemId?: number;
system?: System.Projection;
application$domain?: Application.Selection & {
$entity: "application";
};
application$domain$$aggr?: Application.Aggregation & {
$entity: "application";
};
} & Partial<ExprOp<OpAttr | string>>;
type DomainIdProjection = OneOf<{
id: number;
@ -96,7 +106,9 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (
} | {
system?: never;
systemId: ForeignKey<"system">;
}));
})) & {
application$domain?: OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">[]> | Array<OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">> | OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">>>;
};
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
@ -114,6 +126,7 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (
systemId?: ForeignKey<"system">;
})) & {
[k: string]: any;
application$domain?: OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<Application.RemoveOperation["action"], Omit<Application.RemoveOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">[]> | Array<OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">> | OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<Application.RemoveOperation["action"], Omit<Application.RemoveOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">>>;
};
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({

View File

@ -34,7 +34,6 @@ export const desc = {
type: "object"
}
},
static: true,
actionType: "crud",
actions,
indexes: [

View File

@ -273,7 +273,9 @@ export type ChangePasswordTempIdSubQuery = {
}) | any;
};
export type DomainIdSubQuery = {
[K in "$in" | "$nin"]?: (Domain.DomainIdSubQuery & {
[K in "$in" | "$nin"]?: (Application.DomainIdSubQuery & {
entity: "application";
}) | (Domain.DomainIdSubQuery & {
entity: "domain";
}) | any;
};

View File

@ -14,7 +14,7 @@ export declare function createToDo<ED extends EntityDict & BaseEntityDict, T ext
redirectTo: EntityDict['toDo']['OpSchema']['redirectTo'];
entity: any;
entityId: string;
}, userIds?: string[]): Promise<0 | 1>;
}, userIds?: string[]): Promise<1 | 0>;
/**
* todo例程entity对象上进行action操作时filtertodo完成
* entity的action的后trigger中调用

View File

@ -105,9 +105,9 @@ export type Config = {
};
Sms?: {
mockSend?: boolean;
defaultOrigin?: 'ali' | 'tencent' | 'ctyun'; //默认渠道
defaultCodeTemplateName?: string; //验证码模版名
defaultCodeDuration?: number; //验证码有效时间 单位分钟, 不填1分钟
defaultOrigin?: 'ali' | 'tencent' | 'ctyun';
defaultCodeTemplateName?: string;
defaultCodeDuration?: number;
ali?: AliSmsConfig[];
tencent?: TencentSmsConfig[];
ctyun?: CTYunSmsConfig[];
@ -118,6 +118,8 @@ export type Config = {
qrCodePublicForMpId?: string;
mpShareImageUrl?: string;
mergeUserDirectly?: boolean;
tokenRefreshTime?: number;
tokenExpireTime?: number;
};
};
export type Origin = 'ali' | 'tencent' | 'qiniu' | 'amap' | 'ctyun';

View File

@ -129,8 +129,17 @@ export const applicationProjection = {
apiPath: 1,
protocol: 1,
port: 1,
}
}
},
},
},
domainId: 1,
domain: {
id: 1,
systemId: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
};
export const extraFileProjection = {

4
es/types/Theme.d.ts vendored
View File

@ -13,8 +13,4 @@ export interface IThemeState {
*
*/
systemTheme: boolean;
isFullPage: boolean;
showHeader: boolean;
showBreadcrumbs: boolean;
showFooter: boolean;
}

View File

@ -7,7 +7,7 @@ import { File } from 'formidable';
export declare function getApplication<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>(params: {
type: AppType;
domain: string;
}, context: Cxt): Promise<string>;
}, context: Cxt): Promise<string | undefined>;
export declare function signatureJsSDK<ED extends EntityDict, Cxt extends BackendRuntimeContext<ED>>({ url, env }: {
url: string;
env: WebEnv;

View File

@ -12,7 +12,64 @@ 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', {
// const [application] = await context.select(
// 'application',
// {
// data: cloneDeep(applicationProjection),
// filter: {
// type,
// system: {
// domain$system: {
// url: domain,
// },
// },
// },
// },
// {}
// );
// //微信小程序环境下 没有就报错
// if (type === 'wechatMp') {
// assert(
// application,
// '微信小程序环境下 application必须存在小程序相关配置'
// );
// } else if (type === 'native') {
// assert(application, 'APP环境下 application必须存在APP相关配置');
// } else {
// //web 或 wechatPublic
// if (type === 'wechatPublic') {
// // 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
// if (!application) {
// const [application2] = await context.select(
// 'application',
// {
// data: cloneDeep(applicationProjection),
// filter: {
// type: 'web',
// system: {
// domain$system: {
// url: domain,
// },
// },
// },
// },
// {}
// );
// assert(
// application2,
// '微信公众号环境下 application不存在公众号配置但必须存在web相关配置'
// );
// return application2.id as string;
// }
// } else {
// assert(application, 'web环境下 application必须存在web相关配置');
// }
// }
// return application.id as string;
// 先找指定domain的应用如果不存在再找系统下面的domain 但无论怎么样都必须一项
//
let applications = await context.select('application', {
data: (0, lodash_1.cloneDeep)(Projection_1.applicationProjection),
filter: {
type,
@ -21,21 +78,49 @@ async function getApplication(params, context) {
url: domain,
},
},
domain: {
url: domain,
},
},
}, {});
//微信小程序环境下 没有就报错
if (type === 'wechatMp') {
(0, assert_1.assert)(application, '微信小程序环境下 application必须存在小程序相关配置');
(0, assert_1.assert)(applications.length <= 1, `指定域名的应用 只能存在一项或未指定`);
if (applications.length === 0) {
applications = await context.select(
'application',
{
data: (0, lodash_1.cloneDeep)(
Projection_1.applicationProjection
),
filter: {
type,
system: {
domain$system: {
url: domain,
},
},
domainId: {
$exists: false,
},
},
},
{}
);
}
else if (type === 'native') {
(0, assert_1.assert)(application, 'APP环境下 application必须存在APP相关配置');
}
else {
//web 或 wechatPublic
if (type === 'wechatPublic') {
// 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
if (!application) {
const [application2] = await context.select('application', {
switch (type) {
case 'wechatMp': {
(0, assert_1.assert)(applications.length === 1, `微信小程序环境下,同一个系统必须存在唯一的【${type}】应用`);
const application = applications[0];
return application.id;
}
case 'native': {
(0, assert_1.assert)(applications.length === 1, `APP环境下同一个系统必须存在唯一的【${type}】应用`);
const application = applications[0];
return application.id;
}
case 'wechatPublic': {
// 微信公众号环境下未配置公众号可以使用web的application
if (applications.length === 0) {
let applications2 = await context.select('application', {
data: (0, lodash_1.cloneDeep)(Projection_1.applicationProjection),
filter: {
type: 'web',
@ -44,17 +129,52 @@ async function getApplication(params, context) {
url: domain,
},
},
domain: {
url: domain,
},
},
}, {});
(0, assert_1.assert)(application2, '微信公众号环境下 application不存在公众号配置但必须存在web相关配置');
return application2.id;
(0, assert_1.assert)(applications2.length <= 1, `指定域名的应用 只能存在一项或未指定`);
if (applications2.length === 0) {
applications2 = await context.select(
'application',
{
data: (0, lodash_1.cloneDeep)(
Projection_1.applicationProjection
),
filter: {
type,
system: {
domain$system: {
url: domain,
},
},
domainId: {
$exists: false,
},
},
},
{}
);
}
(0, assert_1.assert)(applications2.length === 1, '微信公众号环境下, 可以未配置公众号但必须存在web的application');
const application = applications2[0];
return application.id;
}
(0, assert_1.assert)(applications.length === 1, `微信公众号环境下,同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名`);
const application = applications[0];
return application.id;
}
else {
(0, assert_1.assert)(application, 'web环境下 application必须存在web相关配置');
case 'web': {
(0, assert_1.assert)(applications.length === 1, `web环境下同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名`);
const application = applications[0];
return application.id;
}
default: {
(0, assert_1.assert)(false, `不支持的类型【${type}`);
return;
}
}
return application.id;
}
exports.getApplication = getApplication;
async function signatureJsSDK({ url, env }, context) {
@ -70,7 +190,7 @@ async function signatureJsSDK({ url, env }, context) {
exports.signatureJsSDK = signatureJsSDK;
async function uploadWechatMedia(params, // FormData表单提交 isPermanent 变成 'true' | 'false'
context) {
const { applicationId, file, type: mediaType, description, extraFileId } = params;
const { applicationId, file, type: mediaType, description, extraFileId, } = params;
(0, assert_1.assert)(applicationId);
const isPermanent = params.isPermanent === 'true';
const filename = file.originalFilename;
@ -104,14 +224,14 @@ context) {
if (isPermanent) {
// 只有公众号才能上传永久素材
(0, assert_1.assert)(type === 'wechatPublic');
const result = await wechatInstance.createMaterial({
const result = (await wechatInstance.createMaterial({
type: mediaType,
media: fileStream,
filename,
filetype,
fileLength,
description: description ? JSON.parse(description) : null,
});
}));
mediaId = result.media_id;
}
else {

View File

@ -1072,139 +1072,118 @@ async function sendCaptcha({ mobile, env, type: type2, }, context) {
const closeRootMode = context.openRootMode();
if (process.env.NODE_ENV !== 'development' && !mockSend) {
const [count1, count2] = await Promise.all([
context.count(
'captcha',
{
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
context.count('captcha', {
filter: {
visitorId,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
},
{
dontCollect: true,
}
),
context.count(
'captcha',
{
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
}, {
dontCollect: true,
}),
context.count('captcha', {
filter: {
mobile,
$$createAt$$: {
$gt: now - 3600 * 1000,
},
type: type2,
},
{
dontCollect: true,
}
),
}, {
dontCollect: true,
}),
]);
if (count1 > 5 || count2 > 5) {
closeRootMode();
throw new types_1.OakUserException(
'您已发送很多次短信,请休息会再发吧'
);
throw new types_1.OakUserException('您已发送很多次短信,请休息会再发吧');
}
}
const [captcha] = await context.select(
'captcha',
{
data: {
id: 1,
code: 1,
$$createAt$$: 1,
},
filter: {
mobile,
$$createAt$$: {
$gt: now - duration * 60 * 1000,
},
expired: false,
type: type2,
},
const [captcha] = await context.select('captcha', {
data: {
id: 1,
code: 1,
$$createAt$$: 1,
},
{
dontCollect: true,
}
);
filter: {
mobile,
$$createAt$$: {
$gt: now - duration * 60 * 1000,
},
expired: false,
type: type2,
},
}, {
dontCollect: true,
});
if (captcha) {
const code = captcha.code;
if (process.env.NODE_ENV === 'development' || mockSend) {
closeRootMode();
return `验证码[${code}]已创建`;
} else if (captcha.$$createAt$$ - now < 60000) {
}
else if (captcha.$$createAt$$ - now < 60000) {
closeRootMode();
throw new types_1.OakUserException(
'您的操作太迅捷啦,请稍等再点吧'
);
} else {
throw new types_1.OakUserException('您的操作太迅捷啦,请稍等再点吧');
}
else {
(0, assert_1.assert)(origin, '必须设置短信渠道');
// todo 再次发送
const result = await (0, sms_1.sendSms)(
{
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
},
context
);
const result = await (0, sms_1.sendSms)({
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
}, context);
closeRootMode();
if (result.success) {
return '验证码已发送';
}
return '验证码发送失败';
}
} else {
}
else {
let code;
if (process.env.NODE_ENV === 'development' || mockSend) {
code = mobile.substring(7);
} else {
}
else {
code = Math.floor(Math.random() * 10000).toString();
while (code.length < 4) {
code += '0';
}
}
const id = await (0, uuid_1.generateNewIdAsync)();
await context.operate(
'captcha',
{
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id,
mobile,
code,
visitorId,
env,
expired: false,
expiresAt: now + duration * 60 * 1000,
type: type2,
},
await context.operate('captcha', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: {
id,
mobile,
code,
visitorId,
env,
expired: false,
expiresAt: now + duration * 60 * 1000,
type: type2,
},
{
dontCollect: true,
}
);
}, {
dontCollect: true,
});
if (process.env.NODE_ENV === 'development' || mockSend) {
closeRootMode();
return `验证码[${code}]已创建`;
} else {
}
else {
(0, assert_1.assert)(origin, '必须设置短信渠道');
//发送短信
const result = await (0, sms_1.sendSms)(
{
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
},
context
);
const result = await (0, sms_1.sendSms)({
origin: origin,
templateName: codeTemplateName,
mobile,
templateParam: { code, duration: duration.toString() },
}, context);
closeRootMode();
if (result.success) {
return '验证码已发送';

View File

@ -2,6 +2,7 @@ import { String, Text } from 'oak-domain/lib/types/DataType';
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Schema as System } from './System';
import { Schema as Session } from './Session';
import { Schema as Domain } from './Domain';
import { Style } from '../types/Style';
export type Passport = 'email' | 'mobile' | 'wechat' | 'wechatPublic';
export type AppType = 'web' | 'wechatMp' | 'wechatPublic' | 'native';
@ -63,4 +64,5 @@ export interface Schema extends EntityShape {
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style;
sessions?: Session[];
domain?: Domain;
}

View File

@ -13,6 +13,7 @@ const entityDesc = {
config: '设置',
style: '样式',
sessions: '会话',
domain: '域名',
},
v: {
type: {

View File

@ -19,6 +19,6 @@ export default class Theme<ED extends EntityDict, Cxt extends BackendRuntimeCont
switchThemeMode(finalThemeMode: ETheme): void;
openSystemTheme(): void;
getColor(): string;
switchColor(color: string): void;
insertThemeStylesheet(theme: string, color: string, mode: ETheme): void;
switchColor(color: string, theme?: string): void;
insertThemeStylesheet(color: string, mode: ETheme, theme?: string): void;
}

View File

@ -8,11 +8,7 @@ const initialThemeState = {
setting: false,
themeMode: defaultTheme,
systemTheme: false,
isFullPage: false,
color: '#0052d9',
showHeader: true,
showBreadcrumbs: true,
showFooter: true,
};
class Theme extends Feature_1.Feature {
cache;
@ -84,15 +80,17 @@ class Theme extends Feature_1.Feature {
const state = this.themeState;
return state.color;
}
switchColor(color) {
switchColor(color, theme) {
const state = this.themeState;
if (color) {
state.color = color; // 某主题 主题色
const themeColor = 'blue'; // 主题颜色类型
// const themeColor = 'blue'; // 主题颜色类型
switch (process.env.OAK_PLATFORM) {
case 'web': {
this.insertThemeStylesheet(themeColor, color, state.themeMode);
document.documentElement.setAttribute('theme-color', themeColor);
this.insertThemeStylesheet(color, state.themeMode, theme);
if (theme) {
document.documentElement.setAttribute('theme-color', theme);
}
break;
}
default: {
@ -102,11 +100,13 @@ class Theme extends Feature_1.Feature {
this.set(state);
}
}
insertThemeStylesheet(theme, color, mode) {
insertThemeStylesheet(color, mode, theme) {
const isDarkMode = mode === 'dark';
const root = !isDarkMode
? `:root[theme-color='${theme}']`
: `:root[theme-color='${theme}'][theme-mode='dark']`;
? (theme
? `:root[theme-color='${theme}']`
: `:root`)
: (theme ? `:root[theme-color='${theme}'][theme-mode='dark']` : `:root[theme-mode='dark']`);
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.innerText = `${root}{

View File

@ -6,6 +6,7 @@ import { GenericAction } from "oak-domain/lib/actions/action";
import { String, Text } from "oak-domain/lib/types/DataType";
import { Style } from "../../types/Style";
import * as System from "../System/Schema";
import * as Domain from "../Domain/Schema";
import * as ExtraFile from "../ExtraFile/Schema";
import * as Notification from "../Notification/Schema";
import * as SessionMessage from "../SessionMessage/Schema";
@ -76,6 +77,7 @@ export type OpSchema = EntityShape & {
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
domainId?: ForeignKey<"domain"> | null;
};
export type OpAttr = keyof OpSchema;
export type Schema = EntityShape & {
@ -85,7 +87,9 @@ export type Schema = EntityShape & {
systemId: ForeignKey<"system">;
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style | null;
domainId?: ForeignKey<"domain"> | null;
system: System.Schema;
domain?: Domain.Schema | null;
extraFile$application?: Array<ExtraFile.Schema>;
extraFile$application$$aggr?: AggregationResult<ExtraFile.Schema>;
notification$application?: Array<Notification.Schema>;
@ -123,6 +127,8 @@ type AttrFilter = {
system: System.Filter;
config: JsonFilter<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style: JsonFilter<Style>;
domainId: Q_StringValue;
domain: Domain.Filter;
extraFile$application: ExtraFile.Filter & SubQueryPredicateMetadata;
notification$application: Notification.Filter & SubQueryPredicateMetadata;
sessionMessage$application: SessionMessage.Filter & SubQueryPredicateMetadata;
@ -150,6 +156,8 @@ export type Projection = {
system?: System.Projection;
config?: number | JsonProjection<WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig>;
style?: number | JsonProjection<Style>;
domainId?: number;
domain?: Domain.Projection;
extraFile$application?: ExtraFile.Selection & {
$entity: "extraFile";
};
@ -223,6 +231,9 @@ type ApplicationIdProjection = OneOf<{
type SystemIdProjection = OneOf<{
systemId: number;
}>;
type DomainIdProjection = OneOf<{
domainId: number;
}>;
export type SortAttr = {
id: number;
} | {
@ -243,6 +254,10 @@ export type SortAttr = {
system: System.SortAttr;
} | {
style: number;
} | {
domainId: number;
} | {
domain: Domain.SortAttr;
} | {
[k: string]: any;
} | OneOf<ExprOp<OpAttr | string>>;
@ -254,7 +269,7 @@ export type Sorter = SortNode[];
export type SelectOperation<P extends Object = Projection> = OakSelection<"select", P, Filter, Sorter>;
export type Selection<P extends Object = Projection> = SelectOperation<P>;
export type Aggregation = DeduceAggregation<Projection, Filter, Sorter>;
export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (({
export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId" | "domainId">> & (({
systemId?: never;
system: System.CreateSingleOperation;
} | {
@ -263,6 +278,15 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (
} | {
system?: never;
systemId: ForeignKey<"system">;
}) & ({
domainId?: never;
domain?: Domain.CreateSingleOperation;
} | {
domainId: ForeignKey<"domain">;
domain?: Domain.UpdateOperation;
} | {
domain?: never;
domainId?: ForeignKey<"domain">;
})) & {
extraFile$application?: OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">[]> | Array<OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">> | OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">>>;
notification$application?: OakOperation<Notification.UpdateOperation["action"], Omit<Notification.UpdateOperationData, "application" | "applicationId">, Omit<Notification.Filter, "application" | "applicationId">> | OakOperation<"create", Omit<Notification.CreateOperationData, "application" | "applicationId">[]> | Array<OakOperation<"create", Omit<Notification.CreateOperationData, "application" | "applicationId">> | OakOperation<Notification.UpdateOperation["action"], Omit<Notification.UpdateOperationData, "application" | "applicationId">, Omit<Notification.Filter, "application" | "applicationId">>>;
@ -279,7 +303,7 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (({
export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId" | "domainId">> & (({
system?: System.CreateSingleOperation;
systemId?: never;
} | {
@ -291,6 +315,18 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (
} | {
system?: never;
systemId?: ForeignKey<"system">;
}) & ({
domain?: Domain.CreateSingleOperation;
domainId?: never;
} | {
domain?: Domain.UpdateOperation;
domainId?: never;
} | {
domain?: Domain.RemoveOperation;
domainId?: never;
} | {
domain?: never;
domainId?: ForeignKey<"domain"> | null;
})) & {
[k: string]: any;
extraFile$application?: OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<ExtraFile.RemoveOperation["action"], Omit<ExtraFile.RemoveOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">[]> | Array<OakOperation<"create", Omit<ExtraFile.CreateOperationData, "application" | "applicationId">> | OakOperation<ExtraFile.UpdateOperation["action"], Omit<ExtraFile.UpdateOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">> | OakOperation<ExtraFile.RemoveOperation["action"], Omit<ExtraFile.RemoveOperationData, "application" | "applicationId">, Omit<ExtraFile.Filter, "application" | "applicationId">>>;
@ -308,10 +344,13 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({
system?: System.UpdateOperation | System.RemoveOperation;
}) & ({
domain?: Domain.UpdateOperation | Domain.RemoveOperation;
}));
export type RemoveOperation = OakOperation<"remove", RemoveOperationData, Filter, Sorter>;
export type Operation = CreateOperation | UpdateOperation | RemoveOperation;
export type SystemIdSubQuery = Selection<SystemIdProjection>;
export type DomainIdSubQuery = Selection<DomainIdProjection>;
export type ApplicationIdSubQuery = Selection<ApplicationIdProjection>;
export type EntityDef = {
Schema: Schema;

View File

@ -31,6 +31,10 @@ exports.desc = {
},
style: {
type: "object"
},
domainId: {
type: "ref",
ref: "domain"
}
},
actionType: "crud",

View File

@ -1 +1 @@
{ "name": "应用", "attr": { "description": "描述", "type": "类型", "system": "系统", "name": "名称", "config": "设置", "style": "样式", "sessions": "会话" }, "v": { "type": { "web": "网站", "wechatPublic": "微信公众号", "wechatMp": "微信小程序", "native": "App" } } }
{ "name": "应用", "attr": { "description": "描述", "type": "类型", "system": "系统", "name": "名称", "config": "设置", "style": "样式", "sessions": "会话", "domain": "域名" }, "v": { "type": { "web": "网站", "wechatPublic": "微信公众号", "wechatMp": "微信小程序", "native": "App" } } }

View File

@ -1,10 +1,11 @@
import { ForeignKey } from "oak-domain/lib/types/DataType";
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, ExprOp, ExpressionKey } from "oak-domain/lib/types/Demand";
import { Q_DateValue, Q_NumberValue, Q_StringValue, Q_EnumValue, NodeId, MakeFilter, ExprOp, ExpressionKey, SubQueryPredicateMetadata } from "oak-domain/lib/types/Demand";
import { OneOf } from "oak-domain/lib/types/Polyfill";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, EntityShape } from "oak-domain/lib/types/Entity";
import { FormCreateData, FormUpdateData, DeduceAggregation, Operation as OakOperation, Selection as OakSelection, MakeAction as OakMakeAction, AggregationResult, EntityShape } from "oak-domain/lib/types/Entity";
import { GenericAction } from "oak-domain/lib/actions/action";
import { String, Int } from "oak-domain/lib/types/DataType";
import * as System from "../System/Schema";
import * as Application from "../Application/Schema";
export type OpSchema = EntityShape & {
url: String<64>;
apiPath?: String<32> | null;
@ -20,6 +21,8 @@ export type Schema = EntityShape & {
port: Int<2>;
systemId: ForeignKey<"system">;
system: System.Schema;
application$domain?: Array<Application.Schema>;
application$domain$$aggr?: AggregationResult<Application.Schema>;
} & {
[A in ExpressionKey]?: any;
};
@ -34,6 +37,7 @@ type AttrFilter = {
port: Q_NumberValue;
systemId: Q_StringValue;
system: System.Filter;
application$domain: Application.Filter & SubQueryPredicateMetadata;
};
export type Filter = MakeFilter<AttrFilter & ExprOp<OpAttr | string>>;
export type Projection = {
@ -49,6 +53,12 @@ export type Projection = {
port?: number;
systemId?: number;
system?: System.Projection;
application$domain?: Application.Selection & {
$entity: "application";
};
application$domain$$aggr?: Application.Aggregation & {
$entity: "application";
};
} & Partial<ExprOp<OpAttr | string>>;
type DomainIdProjection = OneOf<{
id: number;
@ -96,7 +106,9 @@ export type CreateOperationData = FormCreateData<Omit<OpSchema, "systemId">> & (
} | {
system?: never;
systemId: ForeignKey<"system">;
}));
})) & {
application$domain?: OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">[]> | Array<OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">> | OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">>>;
};
export type CreateSingleOperation = OakOperation<"create", CreateOperationData>;
export type CreateMultipleOperation = OakOperation<"create", Array<CreateOperationData>>;
export type CreateOperation = CreateSingleOperation | CreateMultipleOperation;
@ -114,6 +126,7 @@ export type UpdateOperationData = FormUpdateData<Omit<OpSchema, "systemId">> & (
systemId?: ForeignKey<"system">;
})) & {
[k: string]: any;
application$domain?: OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<Application.RemoveOperation["action"], Omit<Application.RemoveOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">[]> | Array<OakOperation<"create", Omit<Application.CreateOperationData, "domain" | "domainId">> | OakOperation<Application.UpdateOperation["action"], Omit<Application.UpdateOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">> | OakOperation<Application.RemoveOperation["action"], Omit<Application.RemoveOperationData, "domain" | "domainId">, Omit<Application.Filter, "domain" | "domainId">>>;
};
export type UpdateOperation = OakOperation<"update" | string, UpdateOperationData, Filter, Sorter>;
export type RemoveOperationData = {} & (({

View File

@ -37,7 +37,6 @@ exports.desc = {
type: "object"
}
},
static: true,
actionType: "crud",
actions: action_1.genericActions,
indexes: [

View File

@ -273,7 +273,9 @@ export type ChangePasswordTempIdSubQuery = {
}) | any;
};
export type DomainIdSubQuery = {
[K in "$in" | "$nin"]?: (Domain.DomainIdSubQuery & {
[K in "$in" | "$nin"]?: (Application.DomainIdSubQuery & {
entity: "application";
}) | (Domain.DomainIdSubQuery & {
entity: "domain";
}) | any;
};

View File

@ -105,9 +105,9 @@ export type Config = {
};
Sms?: {
mockSend?: boolean;
defaultOrigin?: 'ali' | 'tencent' | 'ctyun'; //默认渠道
defaultCodeTemplateName?: string; //验证码模版名
defaultCodeDuration?: number; //验证码有效时间 单位分钟, 不填1分钟
defaultOrigin?: 'ali' | 'tencent' | 'ctyun';
defaultCodeTemplateName?: string;
defaultCodeDuration?: number;
ali?: AliSmsConfig[];
tencent?: TencentSmsConfig[];
ctyun?: CTYunSmsConfig[];
@ -118,6 +118,8 @@ export type Config = {
qrCodePublicForMpId?: string;
mpShareImageUrl?: string;
mergeUserDirectly?: boolean;
tokenRefreshTime?: number;
tokenExpireTime?: number;
};
};
export type Origin = 'ali' | 'tencent' | 'qiniu' | 'amap' | 'ctyun';

View File

@ -132,8 +132,17 @@ exports.applicationProjection = {
apiPath: 1,
protocol: 1,
port: 1,
}
}
},
},
},
domainId: 1,
domain: {
id: 1,
systemId: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
};
exports.extraFileProjection = {

View File

@ -13,8 +13,4 @@ export interface IThemeState {
*
*/
systemTheme: boolean;
isFullPage: boolean;
showHeader: boolean;
showBreadcrumbs: boolean;
showFooter: boolean;
}

View File

@ -11,13 +11,14 @@ import { MediaType, MaterialType } from '../types/WeChat';
import { WebEnv } from 'oak-domain/lib/types/Environment';
import WechatSDK, {
WechatPublicInstance,
WechatMpInstance
WechatMpInstance,
} from 'oak-external-sdk/lib/WechatSDK';
import fs from 'fs';
import { File } from 'formidable';
import { cloneDeep } from 'oak-domain/lib/utils/lodash';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
export async function getApplication<
ED extends EntityDict,
Cxt extends BackendRuntimeContext<ED>
@ -32,7 +33,64 @@ export async function getApplication<
const url = context.getHeader('host');
console.log('url is', url);
const [application] = await context.select(
// const [application] = await context.select(
// 'application',
// {
// data: cloneDeep(applicationProjection),
// filter: {
// type,
// system: {
// domain$system: {
// url: domain,
// },
// },
// },
// },
// {}
// );
// //微信小程序环境下 没有就报错
// if (type === 'wechatMp') {
// assert(
// application,
// '微信小程序环境下 application必须存在小程序相关配置'
// );
// } else if (type === 'native') {
// assert(application, 'APP环境下 application必须存在APP相关配置');
// } else {
// //web 或 wechatPublic
// if (type === 'wechatPublic') {
// // 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
// if (!application) {
// const [application2] = await context.select(
// 'application',
// {
// data: cloneDeep(applicationProjection),
// filter: {
// type: 'web',
// system: {
// domain$system: {
// url: domain,
// },
// },
// },
// },
// {}
// );
// assert(
// application2,
// '微信公众号环境下 application不存在公众号配置但必须存在web相关配置'
// );
// return application2.id as string;
// }
// } else {
// assert(application, 'web环境下 application必须存在web相关配置');
// }
// }
// return application.id as string;
// 先找指定domain的应用如果不存在再找系统下面的domain 但无论怎么样都必须一项
//
let applications = await context.select(
'application',
{
data: cloneDeep(applicationProjection),
@ -43,26 +101,56 @@ export async function getApplication<
url: domain,
},
},
domain: {
url: domain,
},
},
},
{}
);
//微信小程序环境下 没有就报错
if (type === 'wechatMp') {
assert(
application,
'微信小程序环境下 application必须存在小程序相关配置'
assert(applications.length <= 1, `指定域名的应用 只能存在一项或未指定`);
if (applications.length === 0) {
applications = await context.select(
'application',
{
data: cloneDeep(applicationProjection),
filter: {
type,
system: {
domain$system: {
url: domain,
},
},
domainId: {
$exists: false,
},
},
},
{}
);
}
else if (type === 'native') {
assert(application, 'APP环境下 application必须存在APP相关配置');
} else {
//web 或 wechatPublic
if (type === 'wechatPublic') {
// 如果微信公众号环境下 application不存在公众号配置但又在公众号访问这时可以使用web的application
if (!application) {
const [application2] = await context.select(
switch (type) {
case 'wechatMp': {
assert(
applications.length === 1,
`微信小程序环境下,同一个系统必须存在唯一的【${type}】应用`
);
const application = applications[0];
return application.id as string;
}
case 'native': {
assert(
applications.length === 1,
`APP环境下同一个系统必须存在唯一的【${type}】应用`
);
const application = applications[0];
return application.id as string;
}
case 'wechatPublic': {
// 微信公众号环境下未配置公众号可以使用web的application
if (applications.length === 0) {
let applications2 = await context.select(
'application',
{
data: cloneDeep(applicationProjection),
@ -73,24 +161,66 @@ export async function getApplication<
url: domain,
},
},
domain: {
url: domain,
},
},
},
{}
);
assert(
applications2.length <= 1,
`指定域名的应用 只能存在一项或未指定`
);
if (applications2.length === 0) {
applications2 = await context.select(
'application',
{
data: cloneDeep(applicationProjection),
filter: {
type,
system: {
domain$system: {
url: domain,
},
},
domainId: {
$exists: false,
},
},
},
{}
);
}
assert(
application2,
'微信公众号环境下 application不存在公众号配置但必须存在web相关配置'
applications2.length === 1,
'微信公众号环境下, 可以未配置公众号但必须存在web的application'
);
return application2.id as string;
const application = applications2[0];
return application.id as string;
}
} else {
assert(application, 'web环境下 application必须存在web相关配置');
assert(
applications.length === 1,
`微信公众号环境下,同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名`
);
const application = applications[0];
return application.id as string;
}
case 'web': {
assert(
applications.length === 1,
`web环境下同一个系统必须存在唯一的【${type}】应用 或 多个${type}应用必须配置域名`
);
const application = applications[0];
return application.id as string;
}
default: {
assert(false, `不支持的类型【${type}`);
return;
}
}
return application.id as string;
}
export async function signatureJsSDK<
@ -127,7 +257,13 @@ export async function uploadWechatMedia<
}, // FormData表单提交 isPermanent 变成 'true' | 'false'
context: Cxt
): Promise<{ mediaId: string }> {
const { applicationId, file, type: mediaType, description, extraFileId } = params;
const {
applicationId,
file,
type: mediaType,
description,
extraFileId,
} = params;
assert(applicationId);
const isPermanent = params.isPermanent === 'true';
const filename = file.originalFilename!;
@ -175,7 +311,7 @@ export async function uploadWechatMedia<
if (isPermanent) {
// 只有公众号才能上传永久素材
assert(type === 'wechatPublic');
const result = await(
const result = (await (
wechatInstance as WechatPublicInstance
).createMaterial({
type: mediaType,
@ -184,7 +320,7 @@ export async function uploadWechatMedia<
filetype,
fileLength,
description: description ? JSON.parse(description) : null,
}) as {
})) as {
media_id: string;
url: string;
};
@ -231,7 +367,6 @@ export async function uploadWechatMedia<
}
}
return {
mediaId,
};
@ -292,7 +427,7 @@ export async function getMaterial<
if (isPermanent) {
// 只有公众号才能获取永久素材
assert(type === 'wechatPublic');
result = await(wechatInstance as WechatPublicInstance).getMaterial({
result = await (wechatInstance as WechatPublicInstance).getMaterial({
mediaId,
});
} else {

View File

@ -1,5 +1,5 @@
import React, { ReactNode, useState } from 'react';
import { Tabs, Row, Descriptions, Typography, Button, Modal } from 'antd';
import { Tabs, Row, Descriptions, Typography, Button, Modal, Space } from 'antd';
import {
AppType,
@ -37,22 +37,35 @@ export default function Render(
<>
<Modal
open={open}
width={800}
width={500}
onCancel={() => {
clean();
setOpen(false);
}}
footer={
<Button
type="primary"
onClick={async () => {
await execute();
setOpen(false);
}}
disabled={oakExecutable !== true || oakExecuting}
>
{t('common::action.confirm')}
</Button>
<Space>
<Button
onClick={async () => {
clean();
setOpen(false);
}}
disabled={oakExecuting}
>
{t('common::action.cancel')}
</Button>
<Button
type="primary"
onClick={async () => {
await execute();
setOpen(false);
}}
disabled={
oakExecutable !== true || oakExecuting
}
>
{t('common::action.confirm')}
</Button>
</Space>
}
>
<ApplicationUpsert oakPath={oakFullpath} oakId={id} />

View File

@ -7,6 +7,8 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
formData({ data }) {
return data || {};

View File

@ -1,4 +1,5 @@
import { AppType } from '../../../oak-app-domain/Application/Schema';
import { EntityDict } from '../../../oak-app-domain';
type typeOption = {
value: AppType
@ -7,6 +8,15 @@ type typeOption = {
export default OakComponent({
isList: false,
entity: 'application',
projection: {
id: 1,
name: 1,
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
formData({ data }) {
return data || {};
},
@ -22,18 +32,29 @@ export default OakComponent({
value: 'wechatPublic',
},
] as typeOption[],
domains: [] as Partial<EntityDict['domain']['Schema']>[],
},
/* lifetimes: {
ready() {
const { systemId, oakId } = this.props;
if (!oakId) {
if (systemId) {
this.update({
methods: {
async getDomains(systemId: string) {
const { data: domains } = await this.features.cache.refresh(
'domain',
{
data: {
id: 1,
systemId: 1,
url: 1,
apiPath: 1,
port: 1,
protocol: 1,
},
filter: {
systemId,
});
},
}
}
);
this.setState({
domains,
});
},
}, */
},
});

View File

@ -12,8 +12,6 @@ export default function Render(
{
name: string;
description: string;
variant: 'inline' | 'alone' | 'dialog';
showBack: boolean;
type: EntityDict['application']['Schema']['type'];
typeArr: Array<{
label: string;
@ -23,35 +21,32 @@ export default function Render(
oakId: string;
style: EntityDict['system']['Schema']['style'];
$$createAt$$: number;
domainId: string;
domains: EntityDict['domain']['Schema'][];
},
{
confirm: () => void;
getDomains: (systemId: string) => Promise<void>;
}
>
) {
const {
systemId,
name,
description,
type,
typeArr,
$$createAt$$,
domainId,
domains,
} = props.data;
const { t, update, navigateBack, confirm } = props.methods;
const { t, update, confirm, getDomains } = props.methods;
return (
<Form
colon={true}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
>
<Form colon={true} labelCol={{ span: 6 }} wrapperCol={{ span: 16 }}>
<Form.Item
label="名称"
required
// name="name"
// rules={[
// {
// required: true,
// },
// ]}
// name="name"
>
<>
<Input
@ -66,7 +61,7 @@ export default function Render(
</Form.Item>
<Form.Item
label="描述"
//name="description"
//name="description"
>
<>
<Input.TextArea
@ -82,26 +77,17 @@ export default function Render(
<Form.Item
label="应用类型"
required
// name="type"
// rules={[
// {
// required: true,
// },
// ]}
// name="type"
>
<>
<Select
value={type}
style={{ width: 120 }}
disabled={$$createAt$$ > 1}
options={typeArr.map(
(ele: { value: string }) => ({
label: t(
`application:v.type.${ele.value}`
),
value: ele.value,
})
)}
options={typeArr.map((ele: { value: string }) => ({
label: t(`application:v.type.${ele.value}`),
value: ele.value,
}))}
onChange={(value) => {
update({
type: value,
@ -110,6 +96,36 @@ export default function Render(
/>
</>
</Form.Item>
<Form.Item
label="域名"
// name="domain"
>
<>
<Select
allowClear
value={domainId}
style={{ width: 120 }}
options={domains?.map((ele) => ({
label: ele.url,
value: ele.id,
}))}
onChange={(value) => {
if (!value) {
update({
domainId: undefined,
});
return;
}
update({
domainId: value,
});
}}
onClick={() => {
getDomains(systemId!);
}}
/>
</>
</Form.Item>
</Form>
);
}

View File

@ -14,7 +14,11 @@ const Colors: ColorType[] = ['primary', 'success', 'error', 'warning', 'info'];
function Color(props: { value: StyleType['color'], setValue: (path: string, value: string) => void }) {
const { value = {}, setValue } = props;;
return (
<Form>
<Form
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
style={{ maxWidth: 600 }}
>
{Colors.map((ele) => (
<Form.Item
key={ele}

View File

@ -0,0 +1,114 @@
import React, { useState } from 'react';
import {
Tabs,
Row,
Col,
Card,
Divider,
Input,
Form,
Space,
Select,
Switch,
} from 'antd';
import Styles from './web.module.less';
import { Config } from '../../../../types/Config';
// qrCodeType?: QrCodeType; // 生成二维码时,优先生成的类型
// qrCodeApplicationId?: string; // 生成二维码时优先使用的appId
// qrCodePublicForMpId?: string; // 如果qrCodeType是wechatPublicForMp在此指明关联的小程序appId
// mpShareImageUrl?: string; // 小程序分享时的imageUrl使用网络图片54
export default function Basic(props: {
app: Required<Config>['App'];
setValue: (path: string, value: any) => void;
}) {
const { app, setValue } = props;
return (
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
<Row>
<Card className={Styles.tips}>
使
</Card>
</Row>
<Col flex="auto">
<Divider orientation="left" className={Styles.title}>
</Divider>
<Form
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
>
<Form.Item
label="小程序分享时图片地址"
tooltip="小程序分享时图片地址,使用网络图片"
>
<Input
placeholder="请输入图片地址"
type="text"
value={app?.mpShareImageUrl}
onChange={(e) =>
setValue(`mpShareImageUrl`, e.target.value)
}
/>
</Form.Item>
<Form.Item
label="用户合并"
//name="mergeUserDirectly"
tooltip="当发现用户具有相同的特征时是否合并"
>
<>
<Switch
checkedChildren="是"
unCheckedChildren="否"
checked={app?.mergeUserDirectly}
onChange={(checked) =>
setValue(`mergeUserDirectly`, checked)
}
/>
</>
</Form.Item>
<Form.Item
label="Token刷新时间"
tooltip="设置Token刷新时间默认使用系统定义"
>
<Input
placeholder="请输入Token刷新时间"
type="number"
value={app?.tokenRefreshTime}
onChange={(e) => {
const val = e.target.value;
if (val) {
setValue(`tokenRefreshTime`, Number(val));
} else {
setValue(`tokenRefreshTime`, val);
}
}}
suffix="毫秒"
/>
</Form.Item>
<Form.Item
label="Token过期时间"
tooltip="设置Token过期时间默认使用系统定义的"
>
<Input
placeholder="Token过期时间"
type="number"
value={app?.tokenExpireTime}
onChange={(e) => {
const val = e.target.value;
if (val) {
setValue(`tokenExpireTime`, Number(val));
} else {
setValue(`tokenExpireTime`, val);
}
}}
suffix="毫秒"
/>
</Form.Item>
</Form>
</Col>
</Space>
);
}

View File

@ -0,0 +1,16 @@
.label {
color: var(--oak-text-color-primary);
font-size: 28px;
line-height: 36px;
}
.tips {
color: var(--oak-text-color-placeholder);
font-size: 12px;
}
.title {
margin-bottom: 0px;
margin-top:36px;
}

View File

@ -940,7 +940,11 @@ export default function Sms(props: {
<Divider orientation="left" className={Styles.title}>
</Divider>
<Form>
<Form
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
>
<Form.Item
label="模拟发送"
//name="mockSend"
@ -963,6 +967,7 @@ export default function Sms(props: {
>
<>
<Select
placeholder="请选择渠道"
value={sms?.defaultOrigin}
style={{ width: 120 }}
onChange={(value) => {
@ -978,7 +983,7 @@ export default function Sms(props: {
</Form.Item>
<Form.Item label="验证码模版名" tooltip="短信验证码模版名">
<Input
placeholder="请输入defaultCodeTemplateName"
placeholder="请输入验证码模版名"
type="text"
value={sms?.defaultCodeTemplateName}
onChange={(e) =>
@ -994,7 +999,7 @@ export default function Sms(props: {
tooltip="短信验证码发送有效时间"
>
<Input
placeholder="请输入defaultCodeDuration"
placeholder="请输入验证码有效时间"
type="number"
value={sms?.defaultCodeDuration}
onChange={(e) => {

View File

@ -6,7 +6,7 @@ import Cos from './cos/index';
import Map from './map/index';
import Live from './live/index';
import Sms from './sms/index';
import Basic from './basic/index';
import { Config } from '../../../types/Config';
import { EntityDict } from '../../../oak-app-domain';
@ -41,6 +41,7 @@ export default function Render(
Map: map,
Live: live,
Sms: sms,
App: app
} = currentConfig || {};
return (
<>
@ -169,6 +170,18 @@ export default function Render(
/>
),
},
{
key: '基础设置',
label: '基础设置',
children: (
<Basic
app={app || {}}
setValue={(path, value) =>
setValue(`App.${path}`, value)
}
/>
),
},
]}
></Tabs>
</div>

View File

@ -7,7 +7,8 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
},
properties: {
systemId: '',
@ -16,5 +17,5 @@ export default OakComponent({
return {
applications: data || [],
};
}
},
});

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Tabs, Modal, Button } from 'antd';
import { Tabs, Modal, Button, Space } from 'antd';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import ApplicationPanel from '../../application/panel';
@ -19,22 +19,35 @@ export default function render(props: WebComponentProps<EntityDict, 'application
<>
<Modal
open={!!createId}
width={800}
width={500}
onCancel={() => {
clean();
setCreateId('');
}}
footer={
<Button
type='primary'
onClick={async () => {
await execute();
setCreateId('');
}}
disabled={oakExecutable !== true || oakExecuting}
>
{t('common::action.confirm')}
</Button>
<Space>
<Button
onClick={async () => {
clean();
setCreateId('');
}}
disabled={oakExecuting}
>
{t('common::action.cancel')}
</Button>
<Button
type="primary"
onClick={async () => {
await execute();
setCreateId('');
}}
disabled={
oakExecutable !== true || oakExecuting
}
>
{t('common::action.confirm')}
</Button>
</Space>
}
>
<ApplicationUpsert
@ -50,7 +63,7 @@ export default function render(props: WebComponentProps<EntityDict, 'application
}}
footer={
<Button
type='primary'
type="primary"
onClick={async () => {
removeItem(removeId);
await execute();
@ -69,32 +82,29 @@ export default function render(props: WebComponentProps<EntityDict, 'application
if (action === 'add') {
const id = addItem({ systemId });
setCreateId(id);
}
else if (action === 'remove') {
} else if (action === 'remove') {
const appId = applications![Number(key)].id;
setRemoveId(appId);
}
}}
items={
applications?.length > 0 ?
applications.map(
(item, idx) => {
return {
label: item.name,
key: `${idx}`,
children: (
<ApplicationPanel
oakPath={`${oakFullpath}.${item.id}`}
oakId={item.id}
/>
)
}
}
)
applications?.length > 0
? applications.map((item, idx) => {
return {
label: item.name,
key: `${idx}`,
children: (
<ApplicationPanel
oakPath={`${oakFullpath}.${item.id}`}
oakId={item.id}
/>
),
};
})
: []
}
/>
</>
)
);
}
}

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Row, Modal, Descriptions, Typography, Button } from 'antd';
import { Row, Modal, Descriptions, Typography, Button, Space } from 'antd';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
@ -33,25 +33,36 @@ export default function Render(
clean();
setOpen(false);
}}
width={800}
width={500}
footer={
<Button
type='primary'
onClick={async () => {
await execute();
setOpen(false);
}}
disabled={oakExecutable !== true || oakExecuting}
>
{t('common::action.confirm')}
</Button>
<Space>
<Button
// type='primary'
onClick={async () => {
clean();
setOpen(false);
}}
disabled={oakExecuting}
>
{t('common::action.cancel')}
</Button>
<Button
type="primary"
onClick={async () => {
await execute();
setOpen(false);
}}
disabled={
oakExecutable !== true || oakExecuting
}
>
{t('common::action.confirm')}
</Button>
</Space>
}
>
<div className={Styles.upsert}>
<SystemUpsert
oakId={oakId}
oakPath={oakFullpath}
/>
<SystemUpsert oakId={oakId} oakPath={oakFullpath} />
</div>
</Modal>
<Descriptions column={2} bordered>
@ -60,30 +71,20 @@ export default function Render(
{oakId}
</Typography.Paragraph>
</Descriptions.Item>
<Descriptions.Item
label={t('system:attr.name')}
>
<Descriptions.Item label={t('system:attr.name')}>
{name}
</Descriptions.Item>
<Descriptions.Item
label={t('system:attr.description')}
>
<Descriptions.Item label={t('system:attr.description')}>
{description}
</Descriptions.Item>
<Descriptions.Item
label={t('system:attr.super')}
>
<Descriptions.Item label={t('system:attr.super')}>
{isSuper ? '是' : '否'}
</Descriptions.Item>
<Descriptions.Item
label={t('system:attr.folder')}
>
<Descriptions.Item label={t('system:attr.folder')}>
{folder}
</Descriptions.Item>
<Descriptions.Item>
<Row
justify="end"
>
<Row justify="end">
<Button
type="primary"
onClick={() => setOpen(true)}

View File

@ -24,6 +24,8 @@ export default OakComponent({
config: 1,
description: 1,
type: 1,
systemId: 1,
domainId: 1,
}
}
},

View File

@ -19,17 +19,21 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
style: Style;
application$system: EntityDict['application']['OpSchema'][];
}>) {
const { id, config, oakFullpath, name, style, application$system: applications } = props.data;
const { t, update, addItem, removeItem } = props.methods;
const { id, config, oakFullpath, name, style } = props.data;
const { t, } = props.methods;
if (id && oakFullpath) {
return (
<div className={Styles.container}>
<Tabs
tabPosition='left'
tabPosition="left"
items={[
{
label: <div className={Styles.tabLabel}>{t('detail')}</div>,
label: (
<div className={Styles.tabLabel}>
{t('detail')}
</div>
),
key: 'detail',
children: (
<SystemDetail
@ -39,7 +43,11 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
),
},
{
label: <div className={Styles.tabLabel}>{t('config')}</div>,
label: (
<div className={Styles.tabLabel}>
{t('config')}
</div>
),
key: 'config',
children: (
<ConfigUpsert
@ -51,7 +59,11 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
),
},
{
label: <div className={Styles.tabLabel}>{t('style')}</div>,
label: (
<div className={Styles.tabLabel}>
{t('style')}
</div>
),
key: 'style',
children: (
<StyleUpsert
@ -63,7 +75,11 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
),
},
{
label: <div className={Styles.tabLabel}>{t('application-list')}</div>,
label: (
<div className={Styles.tabLabel}>
{t('application-list')}
</div>
),
key: 'application',
children: (
<ApplicationList
@ -73,7 +89,11 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
),
},
{
label: <div className={Styles.tabLabel}>{t('domain-list')}</div>,
label: (
<div className={Styles.tabLabel}>
{t('domain-list')}
</div>
),
key: 'domain_list',
children: (
<DomainList
@ -83,7 +103,11 @@ export default function Render(props: WebComponentProps<EntityDict, 'system', fa
),
},
{
label: <div className={Styles.tabLabel}>{t('smsTemplate-list')}</div>,
label: (
<div className={Styles.tabLabel}>
{t('smsTemplate-list')}
</div>
),
key: 'smsTemplate-list',
children: (
<SmsTemplateList

View File

@ -1,6 +1,14 @@
export default OakComponent({
isList: false,
entity: 'system',
projection: {
id: 1,
name: 1,
config: 1,
description: 1,
super: 1,
folder: 1,
},
formData({ data }) {
return data || {};
},

View File

@ -54,11 +54,6 @@ export default function Render(
required
// name="folder"
tooltip="目录属性应和开发目录下的对应目录名匹配,请谨慎修改"
rules={[
{
required: true,
},
]}
>
<>
<Input

View File

@ -2,6 +2,7 @@ import { String, Int, Datetime, Image, Boolean, Text } from 'oak-domain/lib/type
import { EntityShape } from 'oak-domain/lib/types/Entity';
import { Schema as System } from './System';
import { Schema as Session } from './Session';
import { Schema as Domain } from './Domain';
import { Style } from '../types/Style';
import { EntityDesc } from 'oak-domain/lib/types/EntityDesc';
@ -72,6 +73,7 @@ export interface Schema extends EntityShape {
config: WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
style?: Style;
sessions?: Session[];
domain?: Domain;
};
const entityDesc: EntityDesc<
@ -93,6 +95,7 @@ const entityDesc: EntityDesc<
config: '设置',
style: '样式',
sessions: '会话',
domain: '域名',
},
v: {
type: {

View File

@ -15,11 +15,7 @@ const initialThemeState: IThemeState = {
setting: false,
themeMode: defaultTheme,
systemTheme: false,
isFullPage: false,
color: '#0052d9',
showHeader: true,
showBreadcrumbs: true,
showFooter: true,
};
export default class Theme<
@ -32,8 +28,10 @@ export default class Theme<
private themeState: IThemeState;
private storage: LocalStorage;
private async loadSavedState() {
const themeState = await this.storage.load(LOCAL_STORAGE_KEYS.themeState);
private async loadSavedState() {
const themeState = await this.storage.load(
LOCAL_STORAGE_KEYS.themeState
);
this.themeState = themeState;
}
@ -115,22 +113,21 @@ export default class Theme<
return state.color;
}
switchColor(color: string) {
switchColor(color: string, theme?: string) {
const state = this.themeState;
if (color) {
state.color = color; // 某主题 主题色
const themeColor = 'blue'; // 主题颜色类型
// const themeColor = 'blue'; // 主题颜色类型
switch (process.env.OAK_PLATFORM) {
case 'web': {
this.insertThemeStylesheet(
themeColor,
color,
state.themeMode
);
document.documentElement.setAttribute(
'theme-color',
themeColor
);
this.insertThemeStylesheet(color, state.themeMode, theme);
if (theme) {
document.documentElement.setAttribute(
'theme-color',
theme
);
}
break;
}
default: {
@ -141,11 +138,13 @@ export default class Theme<
}
}
insertThemeStylesheet(theme: string, color: string, mode: ETheme) {
insertThemeStylesheet(color: string, mode: ETheme, theme?: string) {
const isDarkMode = mode === 'dark';
const root = !isDarkMode
? `:root[theme-color='${theme}']`
: `:root[theme-color='${theme}'][theme-mode='dark']`;
? (theme
? `:root[theme-color='${theme}']`
: `:root`)
: (theme ? `:root[theme-color='${theme}'][theme-mode='dark']` : `:root[theme-mode='dark']`);
const styleSheet = document.createElement('style');
styleSheet.type = 'text/css';

View File

@ -126,7 +126,7 @@ export type Config = {
mockSend?: boolean;
defaultOrigin?: 'ali' | 'tencent' | 'ctyun'; //默认渠道
defaultCodeTemplateName?: string; //验证码模版名
defaultCodeDuration?: number; //验证码有效时间 单位分钟, 不填1分钟
defaultCodeDuration?: number; //验证码有效时间 单位分钟, 不填1分钟
ali?: AliSmsConfig[];
tencent?: TencentSmsConfig[];
ctyun?: CTYunSmsConfig[];
@ -137,6 +137,8 @@ export type Config = {
qrCodePublicForMpId?: string; // 如果qrCodeType是wechatPublicForMp在此指明关联的小程序appId
mpShareImageUrl?: string; // 小程序分享时的imageUrl使用网络图片54
mergeUserDirectly?: boolean; // 当发现用户具有相同的特征时直接合并
tokenRefreshTime?: number; //毫秒
tokenExpireTime?: number; //毫秒
};
};

View File

@ -135,8 +135,17 @@ export const applicationProjection: EntityDict['application']['Selection']['data
apiPath: 1,
protocol: 1,
port: 1,
}
}
},
},
},
domainId: 1,
domain: {
id: 1,
systemId: 1,
url: 1,
apiPath: 1,
protocol: 1,
port: 1,
},
};

View File

@ -15,9 +15,5 @@ export interface IThemeState {
*
*/
systemTheme: boolean;
isFullPage: boolean;
showHeader: boolean;
showBreadcrumbs: boolean;
showFooter: boolean;
}