feat: 支持了application, system,platform的defaultOrigin配置

This commit is contained in:
Pan Qiancheng 2025-10-24 15:35:48 +08:00
parent 70747be388
commit 4c317906ca
26 changed files with 698 additions and 9 deletions

View File

@ -0,0 +1,11 @@
import { NativeConfig, WebConfig, WechatMpConfig, WechatPublicConfig } from '../../../entities/Application';
import { EntityDict } from '../../../oak-app-domain';
export type AppConfig = WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig;
export type CosConfig = AppConfig['cos'];
declare const _default: (props: import("oak-frontend-base").ReactComponentProps<EntityDict, keyof EntityDict, false, {
config: AppConfig;
entity: string;
entityId: string;
name: string;
}>) => React.ReactElement;
export default _default;

View File

@ -0,0 +1,112 @@
import { cloneDeep, set } from 'oak-domain/lib/utils/lodash';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { isEmptyJsonObject } from '../../../utils/strings';
export default OakComponent({
isList: false,
properties: {
config: {},
entity: 'application',
entityId: '',
name: '',
},
data: {
initialConfig: {},
dirty: false,
currentConfig: {},
selections: [],
},
lifetimes: {
async ready() {
const { config } = this.props;
this.setState({
initialConfig: config,
dirty: false,
currentConfig: cloneDeep(config),
});
const systemId = this.features.application.getApplication().systemId;
const { data: [system] } = await this.features.cache.refresh("system", {
action: 'select',
data: {
config: {
Cos: {
qiniu: 1,
ctyun: 1,
aliyun: 1,
tencent: 1,
local: 1,
s3: 1,
},
},
},
filter: {
id: systemId,
}
});
const cosConfig = system?.config?.Cos;
// 如果key存在并且不为defaultOrigin并且value的keys长度大于0则加入选择项
const selections = [];
if (cosConfig) {
for (const [key, value] of Object.entries(cosConfig)) {
if (key === 'defaultOrigin') {
continue;
}
if (value && !isEmptyJsonObject(value)) {
selections.push({
name: key,
value: key,
});
}
}
}
this.setState({
selections,
});
}
},
methods: {
setValue(path, value) {
const { currentConfig } = this.state;
const newConfig = cloneDeep(currentConfig || {});
set(newConfig, path, value);
this.setState({
currentConfig: newConfig,
dirty: true,
});
},
resetConfig() {
const { initialConfig } = this.state;
this.setState({
dirty: false,
currentConfig: cloneDeep(initialConfig),
});
},
async updateConfig() {
const { currentConfig } = this.state;
const { entity, entityId } = this.props;
if (!entityId) {
this.setMessage({
content: '缺少实体ID无法更新配置',
type: 'error',
});
return;
}
await this.features.cache.operate("application", {
id: generateNewId(),
action: 'update',
data: {
config: currentConfig,
},
filter: {
id: entityId,
}
}, {});
this.setMessage({
content: '操作成功',
type: 'success',
});
this.setState({
dirty: false,
});
},
},
});

View File

@ -0,0 +1,8 @@
{
"qiniu": "七牛云",
"ctyun": "天翼云",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地存储",
"s3": "S3存储"
}

View File

@ -0,0 +1,5 @@
.contains {
margin-top: 20px;
}

View File

@ -0,0 +1,18 @@
import React from 'react';
import { WebComponentProps } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
declare const Cos: (props: WebComponentProps<EntityDict, keyof EntityDict, false, {
currentConfig: EntityDict["application"]["OpSchema"]["config"];
dirty: boolean;
entity: string;
name: string;
selections: {
name: string;
value: string;
}[];
}, {
setValue: (path: string, value: any) => void;
resetConfig: () => void;
updateConfig: () => void;
}>) => React.JSX.Element;
export default Cos;

View File

@ -0,0 +1,46 @@
import React from 'react';
import Styles from './styles.module.less';
import { Affix, Alert, Button, Select, Space, Typography } from 'antd';
const Cos = (props) => {
const { currentConfig, dirty, entity, name, selections } = props.data;
const { t, setValue, resetConfig, updateConfig } = props.methods;
return (<>
<Affix offsetTop={64}>
<Alert message={<div>
<text>
您正在更新
<Typography.Text keyboard>
{entity}
</Typography.Text>
对象
<Typography.Text keyboard>
{name}
</Typography.Text>
的COS配置请谨慎操作
</text>
</div>} type="info" showIcon action={<Space>
<Button disabled={!dirty} type="primary" danger onClick={() => resetConfig()} style={{
marginRight: 10,
}}>
{t('common::reset')}
</Button>
<Button disabled={!dirty} type="primary" onClick={() => updateConfig()}>
{t('common::action.confirm')}
</Button>
</Space>}/>
</Affix>
<div className={Styles.contains}>
<Space direction="vertical" style={{ width: '100%' }}>
<Typography.Text strong>默认COS源</Typography.Text>
<Select value={currentConfig?.cos?.defaultOrigin} onChange={(v) => {
setValue('cos.defaultOrigin', v);
}} style={{ width: '100%' }} allowClear placeholder="请选择默认COS源">
{selections.map((item) => (<Select.Option key={item.value} value={item.value}>
{t(item.name)}
</Select.Option>))}
</Select>
</Space>
</div>
</>);
};
export default Cos;

View File

@ -6,5 +6,6 @@
"menu": "菜单管理",
"autoReply": "被关注回复管理",
"tag": "标签管理",
"user": "用户管理"
"user": "用户管理",
"cos": "COS配置"
}

View File

@ -9,6 +9,7 @@ import WechatMenu from '../../wechatMenu';
import UserWechatPublicTag from '../../userWechatPublicTag';
import WechatPublicTag from '../..//wechatPublicTag/list';
import WechatPublicAutoReply from '../../wechatPublicAutoReply';
import Cos from '../cos';
export default function Render(props) {
const { id, config, oakFullpath, name, style, type } = props.data;
const { t, update } = props.methods;
@ -29,6 +30,12 @@ export default function Render(props) {
key: 'style',
children: (<StyleUpsert style={style} entity={'platform'} entityId={id} name={name}/>),
},
{
label: <div className={Styles.tabLabel}>{t('cos')}</div>,
key: 'cos',
children: (<Cos oakPath={`#application-panel-cos-${id}`} config={config} entity="application" entityId={id} name={name}>
</Cos>),
},
];
if (type === 'wechatPublic') {
items.push({

View File

@ -1,6 +1,7 @@
import React from 'react';
import { Tabs, Row, Col, Card, Divider, Input, Form, Space, Select, } from 'antd';
import { Tabs, Row, Col, Card, Divider, Input, Form, Space, Select, Typography, } from 'antd';
import Styles from './web.module.less';
import { isEmptyObject } from '../../../../utils/strings';
// https://developer.qiniu.com/kodo/1671/region-endpoint-fq
const QiniuZoneArray = [
{
@ -987,6 +988,24 @@ export default function Cos(props) {
const { cos, setValue, removeItem } = props;
const { qiniu, ctyun, aliyun, tencent, local, s3 } = cos;
return (<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
{/* 默认项选择 */}
<Space direction="vertical" style={{ width: '100%' }}>
<Typography.Text strong>默认COS源</Typography.Text>
<Select value={cos?.defaultOrigin} onChange={(v) => {
setValue('defaultOrigin', v);
}} style={{ width: '100%' }} allowClear placeholder="请选择默认COS源">
{Object.entries(cos).map(([key, value]) => {
if (key === 'defaultOrigin') {
return null;
}
if (value && !isEmptyObject(value)) {
return (<Select.Option key={key} value={key}>
{key}
</Select.Option>);
}
}).filter(Boolean)}
</Select>
</Space>
<Row>
<Card className={Styles.tips}>
每种均可配置一个相应的服务所使用的帐号请准确对应

View File

@ -64,7 +64,7 @@ export default OakComponent({
disableAdd: false, // 上传按钮隐藏
disableDownload: false, // 下载按钮隐藏
type: 'image',
origin: 'qiniu',
origin: null,
tag1: '',
tag2: '',
entity: '',

View File

@ -1,5 +1,20 @@
// 本文件为自动编译产生,请勿直接修改
const i18ns = [
{
id: "ce78f226978ae5cc7f23a6b72d2cb8cb",
namespace: "oak-general-business-c-application-cos",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/application/cos",
data: {
"qiniu": "七牛云",
"ctyun": "天翼云",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地存储",
"s3": "S3存储"
}
},
{
id: "c0ce3f84c8cd6f70457b7e57a8ac8b8f",
namespace: "oak-general-business-c-application-detail",
@ -24,7 +39,8 @@ const i18ns = [
"menu": "菜单管理",
"autoReply": "被关注回复管理",
"tag": "标签管理",
"user": "用户管理"
"user": "用户管理",
"cos": "COS配置"
}
},
{

2
es/utils/strings.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export declare function isEmptyJsonObject(jsonString: string): boolean;
export declare function isEmptyObject(obj: any): boolean;

37
es/utils/strings.js Normal file
View File

@ -0,0 +1,37 @@
export function isEmptyJsonObject(jsonString) {
try {
const obj = JSON.parse(jsonString);
return isEmptyObject(obj);
}
catch (error) {
// 如果解析失败,返回 false
return false;
}
}
export function isEmptyObject(obj) {
// 处理 null 或 undefined
if (obj === null || obj === undefined) {
return true;
}
// 处理空字符串
if (typeof obj === 'string' && obj === '') {
return true;
}
// 如果是数组
if (Array.isArray(obj)) {
// 所有元素都为空才返回 true
return obj.length === 0 || obj.every(item => isEmptyObject(item));
}
// 如果是对象
if (typeof obj === 'object') {
const keys = Object.keys(obj);
// 空对象返回 true
if (keys.length === 0) {
return true;
}
// 递归检查所有值
return keys.every(key => isEmptyObject(obj[key]));
}
// 其他类型(数字、布尔值等)视为非空
return false;
}

View File

@ -2,6 +2,21 @@
// 本文件为自动编译产生,请勿直接修改
Object.defineProperty(exports, "__esModule", { value: true });
const i18ns = [
{
id: "ce78f226978ae5cc7f23a6b72d2cb8cb",
namespace: "oak-general-business-c-application-cos",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/application/cos",
data: {
"qiniu": "七牛云",
"ctyun": "天翼云",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地存储",
"s3": "S3存储"
}
},
{
id: "c0ce3f84c8cd6f70457b7e57a8ac8b8f",
namespace: "oak-general-business-c-application-detail",
@ -26,7 +41,8 @@ const i18ns = [
"menu": "菜单管理",
"autoReply": "被关注回复管理",
"tag": "标签管理",
"user": "用户管理"
"user": "用户管理",
"cos": "COS配置"
}
},
{

2
lib/utils/strings.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
export declare function isEmptyJsonObject(jsonString: string): boolean;
export declare function isEmptyObject(obj: any): boolean;

41
lib/utils/strings.js Normal file
View File

@ -0,0 +1,41 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isEmptyJsonObject = isEmptyJsonObject;
exports.isEmptyObject = isEmptyObject;
function isEmptyJsonObject(jsonString) {
try {
const obj = JSON.parse(jsonString);
return isEmptyObject(obj);
}
catch (error) {
// 如果解析失败,返回 false
return false;
}
}
function isEmptyObject(obj) {
// 处理 null 或 undefined
if (obj === null || obj === undefined) {
return true;
}
// 处理空字符串
if (typeof obj === 'string' && obj === '') {
return true;
}
// 如果是数组
if (Array.isArray(obj)) {
// 所有元素都为空才返回 true
return obj.length === 0 || obj.every(item => isEmptyObject(item));
}
// 如果是对象
if (typeof obj === 'object') {
const keys = Object.keys(obj);
// 空对象返回 true
if (keys.length === 0) {
return true;
}
// 递归检查所有值
return keys.every(key => isEmptyObject(obj[key]));
}
// 其他类型(数字、布尔值等)视为非空
return false;
}

View File

@ -0,0 +1,131 @@
import { cloneDeep, set, get, omit } from 'oak-domain/lib/utils/lodash';
import { Config } from '../../../types/Config';
import { NativeConfig, WebConfig, WechatMpConfig, WechatPublicConfig } from '../../../entities/Application';
import { generateNewId } from 'oak-domain/lib/utils/uuid';
import { MakeOakComponent } from 'oak-frontend-base';
import { EntityDict } from '../../../oak-app-domain';
import { isEmptyJsonObject } from '../../../utils/strings';
export type AppConfig = WebConfig | WechatMpConfig | WechatPublicConfig | NativeConfig
export type CosConfig = AppConfig['cos']
export default OakComponent({
isList: false,
properties: {
config: {} as AppConfig,
entity: 'application',
entityId: '',
name: '',
},
data: {
initialConfig: {} as AppConfig,
dirty: false,
currentConfig: {} as AppConfig,
selections: [] as { name: string; value: string }[],
},
lifetimes: {
async ready() {
const { config } = this.props;
this.setState({
initialConfig: config,
dirty: false,
currentConfig: cloneDeep(config),
})
const systemId = this.features.application.getApplication().systemId!;
const { data: [system] } = await this.features.cache.refresh("system", {
action: 'select',
data: {
config: {
Cos: {
qiniu: 1,
ctyun: 1,
aliyun: 1,
tencent: 1,
local: 1,
s3: 1,
},
},
},
filter: {
id: systemId,
}
});
const cosConfig = system?.config?.Cos;
// 如果key存在并且不为defaultOrigin并且value的keys长度大于0则加入选择项
const selections: { name: string; value: string }[] = [];
if (cosConfig) {
for (const [key, value] of Object.entries(cosConfig)) {
if (key === 'defaultOrigin') {
continue;
}
if (value && !isEmptyJsonObject(value as string)) {
selections.push({
name: key,
value: key,
});
}
}
}
this.setState({
selections,
});
}
},
methods: {
setValue(path: string, value: any) {
const { currentConfig } = this.state;
const newConfig = cloneDeep(currentConfig || {});
set(newConfig, path, value);
this.setState({
currentConfig: newConfig,
dirty: true,
});
},
resetConfig() {
const { initialConfig } = this.state;
this.setState({
dirty: false,
currentConfig: cloneDeep(initialConfig),
});
},
async updateConfig() {
const { currentConfig } = this.state;
const { entity, entityId } = this.props;
if (!entityId) {
this.setMessage({
content: '缺少实体ID无法更新配置',
type: 'error',
});
return;
}
await this.features.cache.operate("application", {
id: generateNewId(),
action: 'update',
data: {
config: currentConfig,
},
filter: {
id: entityId,
}
}, {})
this.setMessage({
content: '操作成功',
type: 'success',
});
this.setState(
{
dirty: false,
}
)
},
},
})

View File

@ -0,0 +1,8 @@
{
"qiniu": "七牛云",
"ctyun": "天翼云",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地存储",
"s3": "S3存储"
}

View File

@ -0,0 +1,5 @@
.contains {
margin-top: 20px;
}

View File

@ -0,0 +1,103 @@
import React from 'react';
import { RowWithActions, WebComponentProps } from 'oak-frontend-base';
import Styles from './styles.module.less';
import { EntityDict } from '../../../oak-app-domain';
import { Affix, Alert, Button, Select, Space, Typography } from 'antd';
const Cos = (
props: WebComponentProps<
EntityDict,
keyof EntityDict,
false,
{
// virtual
currentConfig: EntityDict['application']['OpSchema']['config'];
dirty: boolean;
entity: string;
name: string;
selections: { name: string; value: string }[];
},
{
setValue: (path: string, value: any) => void;
resetConfig: () => void;
updateConfig: () => void;
}
>
) => {
const { currentConfig, dirty, entity, name, selections } = props.data;
const { t, setValue, resetConfig, updateConfig } = props.methods;
return (
<>
<Affix offsetTop={64}>
<Alert
message={
<div>
<text>
<Typography.Text
keyboard
>
{entity}
</Typography.Text>
<Typography.Text
keyboard
>
{name}
</Typography.Text>
COS配置
</text>
</div>
}
type="info"
showIcon
action={
<Space>
<Button
disabled={!dirty}
type="primary"
danger
onClick={() => resetConfig()}
style={{
marginRight: 10,
}}
>
{t('common::reset')}
</Button>
<Button
disabled={!dirty}
type="primary"
onClick={() => updateConfig()}
>
{t('common::action.confirm')}
</Button>
</Space>
}
/>
</Affix>
<div className={Styles.contains}>
<Space direction="vertical" style={{ width: '100%' }}>
<Typography.Text strong>COS源</Typography.Text>
<Select
value={currentConfig?.cos?.defaultOrigin}
onChange={(v) => {
setValue('cos.defaultOrigin', v);
}}
style={{ width: '100%' }}
allowClear
placeholder="请选择默认COS源"
>
{selections.map((item) => (
<Select.Option key={item.value} value={item.value}>
{t(item.name)}
</Select.Option>
))}
</Select>
</Space>
</div >
</>
);
};
export default Cos;

View File

@ -6,5 +6,6 @@
"menu": "菜单管理",
"autoReply": "被关注回复管理",
"tag": "标签管理",
"user": "用户管理"
"user": "用户管理",
"cos": "COS配置"
}

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Tabs, TabsProps } from 'antd';
import { Select, Tabs, TabsProps } from 'antd';
import { WebComponentProps } from 'oak-frontend-base';
import ApplicationDetail from '../detail';
import ConfigUpsert from '../../config/application';
@ -12,6 +12,7 @@ import WechatMenu from '../../wechatMenu';
import UserWechatPublicTag from '../../userWechatPublicTag';
import WechatPublicTag from '../..//wechatPublicTag/list';
import WechatPublicAutoReply from '../../wechatPublicAutoReply';
import Cos from '../cos';
export default function Render(props: WebComponentProps<EntityDict, 'application', false, {
id: string;
@ -59,6 +60,20 @@ export default function Render(props: WebComponentProps<EntityDict, 'application
/>
),
},
{
label: <div className={Styles.tabLabel}>{t('cos')}</div>,
key: 'cos',
children: (
<Cos
oakPath={`#application-panel-cos-${id}`}
config={config}
entity="application"
entityId={id}
name={name}
>
</Cos>
),
},
];
if (type === 'wechatPublic') {
items.push(

View File

@ -9,10 +9,12 @@ import {
Form,
Space,
Select,
Typography,
} from 'antd';
import Styles from './web.module.less';
import { QiniuZone, CTYunZone, ALiYunZone, TencentYunZone } from 'oak-external-sdk';
import { Config, QiniuCosConfig, CTYunCosConfig, ALiYunCosConfig, TencentYunCosConfig, LocalCosConfig, Protocol, S3CosConfig } from '../../../../types/Config';
import { isEmptyObject } from '../../../../utils/strings';
// https://developer.qiniu.com/kodo/1671/region-endpoint-fq
const QiniuZoneArray: Array<{ label: string; value: QiniuZone }> = [
@ -1899,6 +1901,32 @@ export default function Cos(props: {
const { qiniu, ctyun, aliyun, tencent, local, s3 } = cos;
return (
<Space direction="vertical" size="middle" style={{ display: 'flex' }}>
{/* 默认项选择 */}
<Space direction="vertical" style={{ width: '100%' }}>
<Typography.Text strong>COS源</Typography.Text>
<Select
value={cos?.defaultOrigin}
onChange={(v) => {
setValue('defaultOrigin', v);
}}
style={{ width: '100%' }}
allowClear
placeholder="请选择默认COS源"
>
{Object.entries(cos).map(([key, value]) => {
if (key === 'defaultOrigin') {
return null;
}
if (value && !isEmptyObject(value)) {
return (
<Select.Option key={key} value={key}>
{key}
</Select.Option>
);
}
}).filter(Boolean)}
</Select>
</Space>
<Row>
<Card className={Styles.tips}>
使

View File

@ -95,7 +95,7 @@ export default OakComponent({
disableAdd: false, // 上传按钮隐藏
disableDownload: false, // 下载按钮隐藏
type: 'image' as ExtraFile['type'],
origin: 'qiniu' as ExtraFile['origin'],
origin: null as ExtraFile['origin'] | null,
tag1: '',
tag2: '',
entity: '' as keyof EntityDict,

View File

@ -2,6 +2,21 @@
import { CreateOperationData as I18n } from "../oak-app-domain/I18n/Schema";
const i18ns: I18n[] = [
{
id: "ce78f226978ae5cc7f23a6b72d2cb8cb",
namespace: "oak-general-business-c-application-cos",
language: "zh-CN",
module: "oak-general-business",
position: "src/components/application/cos",
data: {
"qiniu": "七牛云",
"ctyun": "天翼云",
"aliyun": "阿里云",
"tencent": "腾讯云",
"local": "本地存储",
"s3": "S3存储"
}
},
{
id: "c0ce3f84c8cd6f70457b7e57a8ac8b8f",
namespace: "oak-general-business-c-application-detail",
@ -26,7 +41,8 @@ const i18ns: I18n[] = [
"menu": "菜单管理",
"autoReply": "被关注回复管理",
"tag": "标签管理",
"user": "用户管理"
"user": "用户管理",
"cos": "COS配置"
}
},
{

41
src/utils/strings.ts Normal file
View File

@ -0,0 +1,41 @@
export function isEmptyJsonObject(jsonString: string): boolean {
try {
const obj = JSON.parse(jsonString);
return isEmptyObject(obj);
} catch (error) {
// 如果解析失败,返回 false
return false;
}
}
export function isEmptyObject(obj: any): boolean {
// 处理 null 或 undefined
if (obj === null || obj === undefined) {
return true;
}
// 处理空字符串
if (typeof obj === 'string' && obj === '') {
return true;
}
// 如果是数组
if (Array.isArray(obj)) {
// 所有元素都为空才返回 true
return obj.length === 0 || obj.every(item => isEmptyObject(item));
}
// 如果是对象
if (typeof obj === 'object') {
const keys = Object.keys(obj);
// 空对象返回 true
if (keys.length === 0) {
return true;
}
// 递归检查所有值
return keys.every(key => isEmptyObject(obj[key]));
}
// 其他类型(数字、布尔值等)视为非空
return false;
}