mirror of https://github.com/msgbyte/tailchat
feat: 增加开放平台的基础框架
parent
6ee8a0a9f2
commit
246733bf75
@ -0,0 +1,22 @@
|
||||
import { DependencyList, useEffect, useReducer } from 'react';
|
||||
import type { FunctionReturningPromise } from '../types';
|
||||
import { useAsyncFn } from './useAsyncFn';
|
||||
|
||||
export function useAsyncRefresh<T extends FunctionReturningPromise>(
|
||||
fn: T,
|
||||
deps: DependencyList = []
|
||||
) {
|
||||
const [state, callback] = useAsyncFn(fn, deps, {
|
||||
loading: true,
|
||||
});
|
||||
const [inc, refresh] = useReducer((i) => i + 1, 0);
|
||||
|
||||
useEffect(() => {
|
||||
callback();
|
||||
}, [callback, inc]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
refresh,
|
||||
};
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"label": "开放平台插件",
|
||||
"name": "com.msgbyte.openapi",
|
||||
"url": "/plugins/com.msgbyte.openapi/index.js",
|
||||
"version": "0.0.0",
|
||||
"author": "msgbyte",
|
||||
"description": "为应用提供开放平台的操作能力",
|
||||
"requireRestart": true
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@plugins/com.msgbyte.openapi",
|
||||
"main": "src/index.ts",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"shepherd.js": "^8.3.1"
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const Bot: React.FC = React.memo(() => {
|
||||
return <div>开发中</div>;
|
||||
});
|
||||
Bot.displayName = 'Bot';
|
||||
|
||||
export default Bot;
|
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const Profile: React.FC = React.memo(() => {
|
||||
return <div>开发中</div>;
|
||||
});
|
||||
Profile.displayName = 'Profile';
|
||||
|
||||
export default Profile;
|
@ -0,0 +1,16 @@
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import React from 'react';
|
||||
|
||||
const Summary: React.FC = React.memo(() => {
|
||||
const { appId, appName } = useOpenAppInfo();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>{appId}</div>
|
||||
<div>{appName}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Summary.displayName = 'Summary';
|
||||
|
||||
export default Summary;
|
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const Webpage: React.FC = React.memo(() => {
|
||||
return <div>开发中</div>;
|
||||
});
|
||||
Webpage.displayName = 'Webpage';
|
||||
|
||||
export default Webpage;
|
@ -0,0 +1,3 @@
|
||||
.plugin-openapi-app-info {
|
||||
display: flex;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Menu } from '@capital/component';
|
||||
import { Loadable } from '@capital/common';
|
||||
import { useOpenAppInfo } from '../context';
|
||||
import './index.less';
|
||||
|
||||
const menuRouteMap: Record<string, React.ComponentType> = {
|
||||
summary: Loadable(() => import('./Summary')),
|
||||
profile: Loadable(() => import('./Profile')),
|
||||
bot: Loadable(() => import('./Bot')),
|
||||
webpage: Loadable(() => import('./Webpage')),
|
||||
};
|
||||
|
||||
const AppInfo: React.FC = React.memo(() => {
|
||||
const [menu, setMenu] = useState('summary');
|
||||
|
||||
return (
|
||||
<div className="plugin-openapi-app-info">
|
||||
<Menu
|
||||
style={{ width: 256 }}
|
||||
selectedKeys={[menu]}
|
||||
onSelect={({ key }) => setMenu(key)}
|
||||
>
|
||||
<Menu.Item key="summary">总览</Menu.Item>
|
||||
<Menu.Item key="profile">基础信息</Menu.Item>
|
||||
<Menu.Item key="bot">机器人</Menu.Item>
|
||||
<Menu.Item key="webpage">网页</Menu.Item>
|
||||
</Menu>
|
||||
|
||||
<div>
|
||||
{menuRouteMap[menu] ? React.createElement(menuRouteMap[menu]) : <div />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
AppInfo.displayName = 'AppInfo';
|
||||
|
||||
export default AppInfo;
|
@ -0,0 +1,17 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { OpenApp } from './types';
|
||||
|
||||
const OpenAppInfoContext = React.createContext<OpenApp>(null);
|
||||
OpenAppInfoContext.displayName = 'OpenAppInfoContext';
|
||||
|
||||
export const OpenAppInfoProvider: React.FC<{ appInfo: OpenApp }> = (props) => {
|
||||
return (
|
||||
<OpenAppInfoContext.Provider value={props.appInfo}>
|
||||
{props.children}
|
||||
</OpenAppInfoContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useOpenAppInfo() {
|
||||
return useContext(OpenAppInfoContext);
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.plugin-openapi-main-panel {
|
||||
// keep
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
postRequest,
|
||||
useAsyncRefresh,
|
||||
openModal,
|
||||
closeModal,
|
||||
} from '@capital/common';
|
||||
import { LoadingSpinner, Space, Table, Button } from '@capital/component';
|
||||
import { OpenApp } from './types';
|
||||
import AppInfo from './AppInfo';
|
||||
import { OpenAppInfoProvider } from './context';
|
||||
import { CreateOpenApp } from '../modals/CreateOpenApp';
|
||||
import './index.less';
|
||||
|
||||
const OpenApiMainPanel: React.FC = React.memo(() => {
|
||||
const [appInfo, setAppInfo] = useState<OpenApp | null>(null);
|
||||
const {
|
||||
loading,
|
||||
value: allApps,
|
||||
refresh,
|
||||
} = useAsyncRefresh(async (): Promise<OpenApp[]> => {
|
||||
const { data } = await postRequest('/openapi/app/all');
|
||||
|
||||
return data ?? [];
|
||||
}, []);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'appName',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_, record: OpenApp) => (
|
||||
<Space>
|
||||
<Button onClick={() => setAppInfo(record)}>进入</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const handleCreateOpenApp = () => {
|
||||
const key = openModal(
|
||||
<CreateOpenApp
|
||||
onSuccess={() => {
|
||||
refresh();
|
||||
closeModal(key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="plugin-openapi-main-panel">
|
||||
{appInfo ? (
|
||||
<OpenAppInfoProvider appInfo={appInfo}>
|
||||
<AppInfo />
|
||||
</OpenAppInfoProvider>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
style={{ marginBottom: 4 }}
|
||||
type="primary"
|
||||
onClick={handleCreateOpenApp}
|
||||
>
|
||||
创建应用
|
||||
</Button>
|
||||
<Table columns={columns} dataSource={allApps} pagination={false} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
OpenApiMainPanel.displayName = 'OpenApiMainPanel';
|
||||
|
||||
export default OpenApiMainPanel;
|
@ -0,0 +1,5 @@
|
||||
export interface OpenApp {
|
||||
appId: string;
|
||||
appSecret: string;
|
||||
appName: string;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import { Loadable, regCustomPanel } from '@capital/common';
|
||||
import { Translate } from './translate';
|
||||
|
||||
regCustomPanel({
|
||||
position: 'setting',
|
||||
icon: '',
|
||||
name: 'com.msgbyte.openapi/mainPanel',
|
||||
label: Translate.openapi,
|
||||
render: Loadable(() => import('./MainPanel')),
|
||||
});
|
@ -0,0 +1,55 @@
|
||||
import {
|
||||
createFastFormSchema,
|
||||
fieldSchema,
|
||||
ModalWrapper,
|
||||
postRequest,
|
||||
showToasts,
|
||||
showErrorToasts,
|
||||
} from '@capital/common';
|
||||
import { WebFastForm } from '@capital/component';
|
||||
import React from 'react';
|
||||
|
||||
const schema = createFastFormSchema({
|
||||
appName: fieldSchema
|
||||
.string()
|
||||
.required('应用名不能为空')
|
||||
.max(20, '应用名过长'),
|
||||
appDesc: fieldSchema.string(),
|
||||
});
|
||||
|
||||
const fields = [
|
||||
{ type: 'text', name: 'appName', label: '应用名' },
|
||||
{
|
||||
type: 'textarea',
|
||||
name: 'appDesc',
|
||||
label: '应用描述',
|
||||
},
|
||||
];
|
||||
|
||||
interface CreateOpenAppProps {
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
export const CreateOpenApp: React.FC<CreateOpenAppProps> = React.memo(
|
||||
(props) => {
|
||||
const handleSubmit = async (values: any) => {
|
||||
try {
|
||||
await postRequest('/openapi/app/create', {
|
||||
...values,
|
||||
appIcon: '',
|
||||
});
|
||||
|
||||
showToasts('应用创建成功', 'success');
|
||||
props.onSuccess?.();
|
||||
} catch (e) {
|
||||
showErrorToasts(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper title="创建应用">
|
||||
<WebFastForm schema={schema} fields={fields} onSubmit={handleSubmit} />
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
);
|
||||
CreateOpenApp.displayName = 'CreateOpenApp';
|
@ -0,0 +1,5 @@
|
||||
import { localTrans } from '@capital/common';
|
||||
|
||||
export const Translate = {
|
||||
openapi: localTrans({ 'zh-CN': '开放平台', 'en-US': 'Open Api' }),
|
||||
};
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"@capital/*": ["../../../src/plugin/*"],
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import { Input } from 'antd';
|
||||
|
||||
export { Button, Checkbox, Input, Divider, Space } from 'antd';
|
||||
export { Button, Checkbox, Input, Divider, Space, Menu, Table } from 'antd';
|
||||
export const TextArea = Input.TextArea;
|
||||
export { Image } from '@/components/Image';
|
||||
export { Icon } from '@iconify/react';
|
||||
export { PillTabs, PillTabPane } from '@/components/PillTabs';
|
||||
export { LoadingSpinner } from '@/components/LoadingSpinner';
|
||||
export { WebFastForm } from '@/components/WebFastForm';
|
||||
|
Loading…
Reference in New Issue