mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
fix: delete files & width of pic in editor & timing bug
This commit is contained in:
@@ -110,7 +110,7 @@ export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => {
|
||||
updateDate: '2024-07-01',
|
||||
powered:'Powered by https://apipark.com',
|
||||
mainPage:'/guide/page',
|
||||
language:'en'
|
||||
language:'en-US'
|
||||
});
|
||||
const [accessData,setAccessData] = useState<Map<string,string[]>>(new Map())
|
||||
const [pluginAccessDictionary, setPluginAccessDictionary] = useState<{[k:string]:string}>({})
|
||||
|
||||
@@ -6,8 +6,12 @@ import crc32 from 'crc/crc32';
|
||||
// 引入需要实现国际化的简体、繁体、英文三种数据的json文件
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import jaJP from 'antd/locale/ja_JP';
|
||||
import zhTW from 'antd/locale/zh_TW';
|
||||
import localZh_CN from './scan/zh-CN.json'; // 本地翻译中文文件
|
||||
import localEn_US from './scan/en-US.json'; // 本地翻译英文文件
|
||||
import localZh_TW from './scan/zh-TW.json'; // 本地翻译英文文件
|
||||
import localJa_JP from './scan/ja-JP.json'; // 本地翻译英文文件
|
||||
// import config from '../../../../i18next-scanner.config.js';
|
||||
|
||||
const resources = {
|
||||
@@ -18,6 +22,14 @@ const resources = {
|
||||
'en-US': {
|
||||
translation: localEn_US,
|
||||
...enUS
|
||||
},
|
||||
'zh-TW': {
|
||||
translation: localZh_TW,
|
||||
...zhTW
|
||||
},
|
||||
'ja-JP': {
|
||||
translation: localJa_JP,
|
||||
...jaJP
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,416 +0,0 @@
|
||||
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
|
||||
import { App, Button, Form, Input, Radio, Row, Select, TreeSelect, Upload } from "antd";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx";
|
||||
import { useFetch} from "@common/hooks/http.ts";
|
||||
import { DefaultOptionType } from "antd/es/cascader";
|
||||
import { EntityItem, MemberItem, SimpleTeamItem } from "@common/const/type.ts";
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { validateUrlSlash } from "@common/utils/validate.ts";
|
||||
import { normFile } from "@common/utils/uploadPic.ts";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import { SERVICE_VISUALIZATION_OPTIONS } from "@core/const/system/const.tsx";
|
||||
import { RcFile, UploadChangeParam, UploadFile, UploadProps } from "antd/es/upload/interface";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { getImgBase64 } from "@common/utils/dataTransfer.ts";
|
||||
import { CategorizesType } from "@market/const/serviceHub/type.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { AiServiceConfigHandle, AiServiceConfigFieldType } from "@core/const/ai-service/type";
|
||||
import { useAiServiceContext } from "@core/contexts/AiServiceContext";
|
||||
|
||||
type SimpleAiProviderItem = EntityItem & {
|
||||
configured:boolean
|
||||
logo:string
|
||||
}
|
||||
|
||||
const AiServiceConfig = forwardRef<AiServiceConfigHandle>((_,ref) => {
|
||||
const { message,modal } = App.useApp()
|
||||
const { teamId, serviceId } = useParams<RouterParams>();
|
||||
const [onEdit, setOnEdit] = useState<boolean>(!!teamId)
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const [teamOptionList, setTeamOptionList] = useState<DefaultOptionType[]>()
|
||||
const [providerOptionList, setProviderOptionList] = useState<DefaultOptionType[]>()
|
||||
const navigate = useNavigate();
|
||||
const {setBreadcrumb} = useBreadcrumb()
|
||||
const { setAiServiceInfo} = useAiServiceContext()
|
||||
const [showClassify, setShowClassify] = useState<boolean>()
|
||||
const [imageBase64, setImageBase64] = useState<string | null>(null);
|
||||
const [tagOptionList, setTagOptionList] = useState<DefaultOptionType[]>([])
|
||||
const [serviceClassifyOptionList, setServiceClassifyOptionList] = useState<DefaultOptionType[]>()
|
||||
const [uploadLoading, setUploadLoading] = useState<boolean>(false)
|
||||
const {checkPermission,accessInit, getGlobalAccessData,state, aiConfigFlushed, setAiConfigFlushed} = useGlobalContext()
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save:onFinish
|
||||
}));
|
||||
|
||||
const beforeUpload = async (file: RcFile) => {
|
||||
if (!['image/png', 'image/jpeg', 'image/svg+xml'].includes(file.type)) {
|
||||
alert($t('只允许上传PNG、JPG或SVG格式的图片'));
|
||||
return false;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e: ProgressEvent<FileReader>) => {
|
||||
setImageBase64(e.target?.result as string);
|
||||
form.setFieldValue('logo', e.target?.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
// }
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
const handleChange: UploadProps['onChange'] = (info: UploadChangeParam<UploadFile>) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
setUploadLoading(true);
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
getImgBase64(info.file.originFileObj as RcFile, () => {
|
||||
setUploadLoading(false);
|
||||
});
|
||||
}
|
||||
if (info.fileList.length === 0) {
|
||||
form.setFieldValue( "logo", null );
|
||||
}
|
||||
};
|
||||
|
||||
const uploadButton = (
|
||||
<div>
|
||||
{uploadLoading ? <LoadingOutlined /> : <Icon icon="ic:baseline-add" width="24" height="24"/>}
|
||||
</div>
|
||||
);
|
||||
|
||||
const getTagAndServiceClassifyList = ()=>{
|
||||
setTagOptionList([])
|
||||
setServiceClassifyOptionList([])
|
||||
fetchData<BasicResponse<{ catalogues:CategorizesType[],tags:EntityItem[]}>>('catalogues',{method:'GET'}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setTagOptionList(data.tags?.map((x:EntityItem)=>{return {
|
||||
label:x.name, value:x.name
|
||||
}})||[])
|
||||
setServiceClassifyOptionList(data.catalogues)
|
||||
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 获取表单默认值
|
||||
const getAiServiceInfo = () => {
|
||||
fetchData<BasicResponse<{ service: AiServiceConfigFieldType }>>('ai-service/info',{method:'GET',eoParams:{team:teamId, service:serviceId},eoTransformKeys:['team_id','service_type']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setTimeout(()=>{
|
||||
form.setFieldsValue({
|
||||
...data.service,
|
||||
team:data.service.team.id,
|
||||
catalogue:data.service.catalogue?.id,
|
||||
tags:data.service.tags?.map((x:EntityItem)=>x.name),
|
||||
provider:data.service.provider.id,
|
||||
logoFile:[
|
||||
{
|
||||
uid: '-1', // 文件唯一标识
|
||||
name: 'image.png', // 文件名
|
||||
status: 'done', // 状态有:uploading, done, error, removed
|
||||
url: data.service?.logo || '', // 图片 Base64 数据
|
||||
}
|
||||
]
|
||||
})
|
||||
setImageBase64(data.service.logo)
|
||||
setShowClassify(data.service.serviceType === 'public')
|
||||
},0)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const onFinish:()=>Promise<boolean|string> = () => {
|
||||
return form.validateFields().then((value)=>{
|
||||
return fetchData<BasicResponse<{service:{id:string}}>>(serviceId === undefined? 'team/ai-service':'ai-service/info',{method:serviceId === undefined? 'POST' : 'PUT',eoParams: {...(serviceId === undefined ? {team:value.team} :{service:serviceId,team:teamId})},eoBody:({...value,prefix:value.prefix?.trim()}), eoTransformKeys:['serviceType']},).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
setAiServiceInfo(data.service)
|
||||
return Promise.resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=>{
|
||||
return Promise.reject(errorInfo)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
const getProviderOptionList = ()=>{
|
||||
setProviderOptionList([])
|
||||
fetchData<BasicResponse<{ providers: SimpleAiProviderItem[] }>>('simple/ai/providers',{method:'GET',eoTransformKeys:[]}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setProviderOptionList(data.providers?.filter(x=>x.configured)?.map((x:SimpleAiProviderItem)=>{return {...x,
|
||||
label: <div className="flex items-center" dangerouslySetInnerHTML={{ __html: x.logo }} />, value:x.id
|
||||
}}))
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getTeamOptionList = ()=>{
|
||||
setTeamOptionList([])
|
||||
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(!checkPermission('system.workspace.team.view_all') ?'simple/teams/mine' :'simple/teams',{method:'GET',eoTransformKeys:[]}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setTeamOptionList(data.teams?.map((x:MemberItem)=>{return {...x,
|
||||
label:x.name, value:x.id
|
||||
}}))
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleteAiService = ()=>{
|
||||
fetchData<BasicResponse<null>>('team/ai-service',{method:'DELETE',eoParams:{team:teamId,service:serviceId}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
navigate(`/aiservice/list`)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
aiConfigFlushed && getProviderOptionList()
|
||||
},[aiConfigFlushed])
|
||||
|
||||
useEffect(() => {
|
||||
getProviderOptionList()
|
||||
getTagAndServiceClassifyList()
|
||||
if(accessInit){
|
||||
getTeamOptionList()
|
||||
}else{
|
||||
getGlobalAccessData()?.then(()=>{
|
||||
getTeamOptionList()
|
||||
})
|
||||
}
|
||||
if (serviceId !== undefined) {
|
||||
setOnEdit(true);
|
||||
getAiServiceInfo();
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: <Link to={`/aiservice/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
title: $t('设置')
|
||||
}])
|
||||
|
||||
} else {
|
||||
setOnEdit(false);
|
||||
form.setFieldValue('id',uuidv4());
|
||||
form.setFieldValue('team',teamId);
|
||||
form.setFieldValue('serviceType','inner');
|
||||
}
|
||||
return (form.setFieldsValue({}))
|
||||
}, [serviceId]);
|
||||
|
||||
|
||||
const deleteAiServiceModal = async ()=>{
|
||||
modal.confirm({
|
||||
title:$t('删除'),
|
||||
content:$t(DELETE_TIPS.default),
|
||||
onOk:()=> {
|
||||
return deleteAiService()
|
||||
},
|
||||
width:600,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
danger:true
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>
|
||||
})
|
||||
}
|
||||
|
||||
const visualizationOptions = useMemo(()=>SERVICE_VISUALIZATION_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language])
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithPermission access={onEdit ? 'team.service.service.edit' :''}>
|
||||
<Form
|
||||
layout='vertical'
|
||||
labelAlign='left'
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="w-full pr-PAGE_INSIDE_X "
|
||||
name="systemConfig"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div>
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("服务名称")}
|
||||
name="name"
|
||||
rules={[{ required: true ,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("服务ID")}
|
||||
name="id"
|
||||
rules={[{ required: true ,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("AI 模型供应商")}
|
||||
name="provider"
|
||||
rules={[{ required: true }]}
|
||||
>{
|
||||
(providerOptionList && providerOptionList.length >0 ) ? <Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={providerOptionList} >
|
||||
</Select> : <p>未配置任何 AI 模型供应商,<a href="/aisetting" target="_blank" onClick={()=>setAiConfigFlushed(false)}>立即配置</a></p>
|
||||
}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("API 调用前缀")}
|
||||
name="prefix"
|
||||
extra={$t("选填,作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改")}
|
||||
rules={[
|
||||
{
|
||||
validator: validateUrlSlash,
|
||||
}]}
|
||||
>
|
||||
<Input prefix={onEdit ? '' : '/'} className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("图标")}
|
||||
name="logoFile"
|
||||
extra={$t("仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩")}
|
||||
valuePropName="fileList" getValueFromEvent={normFile}
|
||||
>
|
||||
<Upload
|
||||
listType="picture"
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={handleChange}
|
||||
showUploadList={false}
|
||||
maxCount={1}
|
||||
accept=".png, .jpg, .jpeg, .svg"
|
||||
>
|
||||
<div className="h-[68px] w-[68px] border-[1px] border-dashed border-BORDER flex items-center justify-center rounded bg-bar-theme cursor-pointer" style={{ marginTop: 8 }}>
|
||||
{imageBase64 ? <img src={imageBase64} alt="Logo" style={{ maxWidth: '200px', width:'68px',height:'68px'}} /> : uploadButton}
|
||||
</div>
|
||||
</Upload>
|
||||
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("描述")}
|
||||
name="description"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("Logo")}
|
||||
name="logo"
|
||||
hidden
|
||||
>
|
||||
</Form.Item>
|
||||
|
||||
{!onEdit && <Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("所属团队")}
|
||||
name="team"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" disabled={onEdit} placeholder={$t(PLACEHOLDER.input)} options={teamOptionList} >
|
||||
</Select>
|
||||
</Form.Item>}
|
||||
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("标签")}
|
||||
name="tags"
|
||||
>
|
||||
<Select
|
||||
className="w-INPUT_NORMAL"
|
||||
mode="tags"
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
options={tagOptionList}>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("服务类型")}
|
||||
name="serviceType"
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<Radio.Group className="flex flex-col" options={visualizationOptions} onChange={(e)=>{setShowClassify(e.target.value === 'public')}} />
|
||||
</Form.Item>
|
||||
|
||||
{showClassify &&
|
||||
<Form.Item<AiServiceConfigFieldType>
|
||||
label={$t("所属服务分类")}
|
||||
name="catalogue"
|
||||
extra={$t("设置服务展示在服务市场中的哪个分类下")}
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<TreeSelect
|
||||
className="w-INPUT_NORMAL"
|
||||
fieldNames={{label:'name',value:'id',children:'children'}}
|
||||
showSearch
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
placeholder={$t(PLACEHOLDER.select)}
|
||||
allowClear
|
||||
treeDefaultExpandAll
|
||||
treeData={serviceClassifyOptionList}
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
{onEdit && <>
|
||||
<Row className="mb-[10px]"
|
||||
// wrapperCol={{ offset: 5, span: 19 }}
|
||||
>
|
||||
<WithPermission access={onEdit ? 'team.service.service.edit' :''}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{$t('保存')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
</Row></>}
|
||||
</div>
|
||||
{onEdit && <>
|
||||
<WithPermission access="team.service.service.delete" showDisabled={false}>
|
||||
<div className="bg-[rgb(255_120_117_/_5%)] rounded-[10px] mt-[50px] p-btnrbase pb-0">
|
||||
<p className="text-left"><span className="font-bold">{$t('删除服务')}:</span>{$t('删除操作不可恢复,请谨慎操作!')}</p>
|
||||
<div className="text-left">
|
||||
<WithPermission access="team.service.service.delete">
|
||||
<Button className="m-auto mt-[16px] mb-[20px]" type="default" danger={true} onClick={deleteAiServiceModal}>{$t('删除服务')}</Button>
|
||||
</WithPermission>
|
||||
</div>
|
||||
</div>
|
||||
</WithPermission>
|
||||
</>}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
})
|
||||
export default AiServiceConfig
|
||||
@@ -80,7 +80,7 @@ const ServiceInsideDocument = ()=>{
|
||||
], toolbar: 'undo redo | styles | bold italic | alignleft aligncenter alignright alignjustify | codesample |table|' +
|
||||
'bullist numlist outdent indent | link image | print preview media fullscreen | ' +
|
||||
'forecolor backcolor emoticons | help',
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px; } img { max-width: 100%; }',
|
||||
setup: setupEditor,
|
||||
codesample_languages:[
|
||||
{
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
import PageList from "@common/components/aoplatform/PageList.tsx"
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import { App} from "antd";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { SimpleTeamItem ,SimpleMemberItem} from "@common/const/type.ts";
|
||||
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
|
||||
import AiServiceConfig from "./AiServiceConfig.tsx";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { AiServiceTableListItem, AiServiceConfigHandle } from "@core/const/ai-service/type.ts";
|
||||
import { AI_SERVICE_TABLE_COLUMNS } from "@core/const/ai-service/const.tsx";
|
||||
|
||||
const AiServiceList:FC = ()=>{
|
||||
const navigate = useNavigate();
|
||||
const [tableSearchWord, setTableSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [teamList, setTeamList] = useState<{ [k: string]: { text: string; }; }>()
|
||||
const {fetchData} = useFetch()
|
||||
const [tableListDataSource, setTableListDataSource] = useState<AiServiceTableListItem[]>([]);
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
const { message } = App.useApp()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<{[k:string]:{text:string}}>({})
|
||||
const [open, setOpen] = useState(false);
|
||||
const drawerFormRef = useRef<AiServiceConfigHandle>(null)
|
||||
const {checkPermission,accessInit, getGlobalAccessData,state} = useGlobalContext()
|
||||
|
||||
const getAiServiceList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{
|
||||
getAiServiceList()
|
||||
})
|
||||
return
|
||||
}
|
||||
if(!tableHttpReload){
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
return fetchData<BasicResponse<{services:AiServiceTableListItem[]}>>(!checkPermission('system.workspace.service.view_all') ? 'my_ai_services':'ai-services',{method:'GET',eoParams:{keyword:tableSearchWord},eoTransformKeys:['api_num','can_delete','create_time']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setTableListDataSource(data.services)
|
||||
setTableHttpReload(false)
|
||||
return {data:data.services, success: true}
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return {data:[], success:false}
|
||||
}
|
||||
}).catch(() => {
|
||||
return {data:[], success:false}
|
||||
})
|
||||
}
|
||||
|
||||
const getTeamsList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{
|
||||
getTeamsList()
|
||||
})
|
||||
return
|
||||
}
|
||||
fetchData<BasicResponse<{ teams: SimpleTeamItem[] }>>(!checkPermission('system.workspace.team.view_all') ?'simple/teams/mine' :'simple/teams',{method:'GET',eoTransformKeys:[]}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
setTeamList(data.teams)
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
const tmpValueEnum:{[k:string]:{text:string}} = {}
|
||||
data.teams?.forEach((x:SimpleMemberItem)=>{
|
||||
tmpValueEnum[x.name] = {text:x.name}
|
||||
})
|
||||
setTeamList(tmpValueEnum)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return {data:[], success:false}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true); // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
};
|
||||
|
||||
const getMemberList = async ()=>{
|
||||
setMemberValueEnum({})
|
||||
const {code,data,msg} = await fetchData<BasicResponse<{ members: SimpleMemberItem[] }>>('simple/member',{method:'GET'})
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
const tmpValueEnum:{[k:string]:{text:string}} = {}
|
||||
data.members?.forEach((x:SimpleMemberItem)=>{
|
||||
tmpValueEnum[x.name] = {text:x.name}
|
||||
})
|
||||
setMemberValueEnum(tmpValueEnum)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getTeamsList();
|
||||
getMemberList()
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务')
|
||||
}])
|
||||
}, []);
|
||||
|
||||
const onClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const columns = useMemo(()=>{
|
||||
const res = AI_SERVICE_TABLE_COLUMNS.map(x=>{
|
||||
if(x.filters &&((x.dataIndex as string[])?.indexOf('master') !== -1 ) ){
|
||||
x.valueEnum = memberValueEnum
|
||||
}
|
||||
if(x.filters &&((x.dataIndex as string[])?.indexOf('team') !== -1 ) ){
|
||||
x.valueEnum = teamList
|
||||
}
|
||||
|
||||
return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}})
|
||||
return res
|
||||
},[memberValueEnum,teamList,state.language]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pr-PAGE_INSIDE_X pb-PAGE_INSIDE_B">
|
||||
{/* <Joyride steps={steps} run={true} /> */}
|
||||
<PageList
|
||||
id="global_ai_system"
|
||||
ref={pageListRef}
|
||||
columns={[...columns]}
|
||||
request={()=>getAiServiceList()}
|
||||
addNewBtnTitle={$t("添加服务")}
|
||||
addNewBtnWrapperClass={'my-first-step'}
|
||||
searchPlaceholder={$t("输入名称、ID、所属团队、负责人查找服务")}
|
||||
onAddNewBtnClick={() => {
|
||||
setOpen(true)
|
||||
}}
|
||||
manualReloadTable={manualReloadTable}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
onSearchWordChange={(e) => {
|
||||
setTableSearchWord(e.target.value)
|
||||
}}
|
||||
onRowClick={(row:AiServiceTableListItem)=>navigate(`/aiservice/${row.team.id}/inside/${row.id}`)}
|
||||
/>
|
||||
<DrawerWithFooter title={$t("添加 AI 服务")} open={open} onClose={onClose} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} >
|
||||
<AiServiceConfig ref={drawerFormRef} />
|
||||
</DrawerWithFooter>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
export default AiServiceList
|
||||
@@ -67,7 +67,7 @@ export default function ApiRequestSetting(){
|
||||
>
|
||||
<Form.Item<ApiRequestSettingFieldType>
|
||||
label={$t("API 调用地址")}
|
||||
name="name"
|
||||
name="invokeAddress"
|
||||
rules={[{ required: true,whitespace:true }]}
|
||||
extra={$t("API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。")}
|
||||
>
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
import TreeWithMore from "@common/components/aoplatform/TreeWithMore";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { useFetch } from "@common/hooks/http";
|
||||
import { checkAccess } from "@common/utils/permission";
|
||||
import { CategorizesType, ServiceHubCategoryConfigHandle } from "@market/const/serviceHub/type";
|
||||
import { App, Button, Spin, Tree, TreeDataNode, TreeProps } from "antd";
|
||||
import { DataNode } from "antd/es/tree";
|
||||
import { Key, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ServiceHubCategoryConfig } from "./ServiceHubCategoryConfig";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { Icon } from "@iconify/react/dist/iconify.js";
|
||||
import InsidePage from "@common/components/aoplatform/InsidePage";
|
||||
import { EntityItem } from "@common/const/type";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
export default function ServiceCategory(){
|
||||
const [gData, setGData] = useState<CategorizesType[]>([]);
|
||||
const [cateData, setCateData] = useState<CategorizesType[]>([]);
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const {message,modal} = App.useApp()
|
||||
const {fetchData} = useFetch()
|
||||
const addRef = useRef<ServiceHubCategoryConfigHandle>(null)
|
||||
const addChildRef = useRef<ServiceHubCategoryConfigHandle>(null)
|
||||
const renameRef = useRef<ServiceHubCategoryConfigHandle>(null)
|
||||
const {accessData} = useGlobalContext()
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
|
||||
const onDrop: TreeProps['onDrop'] = (info) => {
|
||||
const dropKey = info.node.key;
|
||||
const dragKey = info.dragNode.key;
|
||||
const dropPos = info.node.pos.split('-');
|
||||
const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1
|
||||
|
||||
const loop = (
|
||||
data: TreeDataNode[],
|
||||
key: React.Key,
|
||||
callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void,
|
||||
) => {
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i].id === key) {
|
||||
return callback(data[i], i, data);
|
||||
}
|
||||
if (data[i].children) {
|
||||
loop(data[i].children!, key, callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
const data = cloneDeep(gData);
|
||||
|
||||
// Find dragObject
|
||||
let dragObj: TreeDataNode;
|
||||
loop(data, dragKey, (item, index, arr) => {
|
||||
arr.splice(index, 1);
|
||||
dragObj = item;
|
||||
});
|
||||
|
||||
if (!info.dropToGap) {
|
||||
// Drop on the content
|
||||
loop(data, dropKey, (item) => {
|
||||
item.children = item.children || [];
|
||||
// where to insert. New item was inserted to the start of the array in this example, but can be anywhere
|
||||
item.children.unshift(dragObj);
|
||||
});
|
||||
} else {
|
||||
let ar: TreeDataNode[] = [];
|
||||
let i: number;
|
||||
loop(data, dropKey, (_item, index, arr) => {
|
||||
ar = arr;
|
||||
i = index;
|
||||
});
|
||||
if (dropPosition === -1) {
|
||||
// Drop on the top of the drop node
|
||||
ar.splice(i!, 0, dragObj!);
|
||||
} else {
|
||||
// Drop on the bottom of the drop node
|
||||
ar.splice(i! + 1, 0, dragObj!);
|
||||
}
|
||||
}
|
||||
|
||||
setGData(data);
|
||||
sortCategories(data)
|
||||
};
|
||||
|
||||
|
||||
const dropdownMenu = (entity:CategorizesType) => [
|
||||
{
|
||||
key: 'addChildCate',
|
||||
label: (
|
||||
<WithPermission access="system.api_market.service_classification.add"><Button className="border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('addChildCate',entity)}>
|
||||
{$t('添加子分类')}
|
||||
</Button></WithPermission>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'renameCate',
|
||||
label: (
|
||||
<WithPermission access="system.api_market.service_classification.edit"><Button className=" border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('renameCate',entity)}>
|
||||
{$t('修改分类名称')}
|
||||
</Button></WithPermission>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: (
|
||||
<WithPermission access="system.api_market.service_classification.delete"><Button className=" border-none p-0 flex items-center bg-transparent " onClick={()=>openModal('delete',entity)}>
|
||||
{$t('删除')}
|
||||
</Button></WithPermission>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const treeData = useMemo(() => {
|
||||
setExpandedKeys([])
|
||||
const loop = (data: CategorizesType[]): DataNode[] =>
|
||||
data?.map((item) => {
|
||||
if (item.children) {
|
||||
setExpandedKeys(prev=>[...prev,item.id])
|
||||
return {
|
||||
title: <TreeWithMore
|
||||
stopClick={false}
|
||||
dropdownMenu={dropdownMenu(item as CategorizesType)}>{item.name}</TreeWithMore> ,
|
||||
key: item.id, children: loop(item.children)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: <TreeWithMore
|
||||
stopClick={false}
|
||||
dropdownMenu={dropdownMenu(item as CategorizesType)}>{item.name}</TreeWithMore>,
|
||||
key: item.id,
|
||||
};
|
||||
});
|
||||
return loop(gData ?? [])
|
||||
}, [gData]);
|
||||
|
||||
const isActionAllowed = (type:'addCate'|'addChildCate'|'renameCate'|'delete') => {
|
||||
const actionToPermissionMap = {
|
||||
'addCate': 'add',
|
||||
'addChildCate': 'add',
|
||||
'renameCate': 'edit',
|
||||
'delete': 'delete'
|
||||
};
|
||||
|
||||
const action = actionToPermissionMap[type];
|
||||
const permission :keyof typeof PERMISSION_DEFINITION[0]= `system.api_market.service_classification.${action}`;
|
||||
|
||||
return !checkAccess(permission, accessData);
|
||||
};
|
||||
|
||||
const openModal = (type:'addCate'|'addChildCate'|'renameCate'|'delete',entity?:CategorizesType)=>{
|
||||
let title:string = ''
|
||||
let content:string|React.ReactNode = ''
|
||||
switch (type){
|
||||
case 'addCate':
|
||||
title=$t('添加分类')
|
||||
content=<ServiceHubCategoryConfig WithPermission={WithPermission} ref={addRef} type={type} />
|
||||
break;
|
||||
case 'addChildCate':
|
||||
title=$t('添加子分类')
|
||||
content=<ServiceHubCategoryConfig WithPermission={WithPermission} ref={addChildRef} type={type} entity={entity} />
|
||||
break;
|
||||
case 'renameCate':
|
||||
title=$t('重命名分类')
|
||||
content=<ServiceHubCategoryConfig WithPermission={WithPermission} ref={renameRef} type={type} entity={entity} />
|
||||
break;
|
||||
case 'delete':
|
||||
title=$t('删除')
|
||||
content=$t(DELETE_TIPS.default)
|
||||
break;
|
||||
}
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk:()=>{
|
||||
switch (type){
|
||||
case 'addCate':
|
||||
return addRef.current?.save().then((res)=>{if(res === true) getCategoryList()})
|
||||
case 'addChildCate':
|
||||
return addChildRef.current?.save().then((res)=>{if(res === true) getCategoryList()})
|
||||
case 'renameCate':
|
||||
return renameRef.current?.save().then((res)=>{if(res === true) getCategoryList()})
|
||||
case 'delete':
|
||||
return deleteCate(entity!).then((res)=>{if(res === true) getCategoryList()})
|
||||
}
|
||||
},
|
||||
width:600,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
disabled : isActionAllowed(type)
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>,
|
||||
})
|
||||
}
|
||||
|
||||
const deleteCate = (entity:CategorizesType)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
fetchData<BasicResponse<null>>('catalogue',{method:'DELETE',eoParams:{catalogue:entity.id},}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const sortCategories = (newData:CategorizesType[])=>{
|
||||
setLoading(true)
|
||||
fetchData<BasicResponse<null>>('catalogue/sort',{method:'PUT',eoBody:newData}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
getCategoryList()
|
||||
}else{
|
||||
setGData(cateData)
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch(()=>{setGData(cateData)}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
|
||||
const getCategoryList = ()=>{
|
||||
setLoading(true)
|
||||
fetchData<BasicResponse<{ catalogues:CategorizesType[],tags:EntityItem[]}>>('catalogues',{method:'GET'}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setGData(data.catalogues)
|
||||
setCateData(data.catalogues)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
setBreadcrumb([
|
||||
{
|
||||
title: $t('服务分类管理')}])
|
||||
getCategoryList()
|
||||
},[])
|
||||
|
||||
return (
|
||||
<InsidePage
|
||||
pageTitle={$t('服务分类管理')}
|
||||
description={$t("设置服务可选择的分类,方便团队成员快速找到API。")}
|
||||
showBorder={false}
|
||||
contentClassName="pr-PAGE_INSIDE_X"
|
||||
scrollPage={false}
|
||||
>
|
||||
<div className="border border-solid border-BORDER p-[20px] rounded-[10px] ">
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
|
||||
<Tree
|
||||
showIcon
|
||||
draggable
|
||||
blockNode
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={(expandedKeys:Key[])=>{setExpandedKeys(expandedKeys as string[])}}
|
||||
onDrop={onDrop}
|
||||
treeData={treeData}
|
||||
/>
|
||||
<WithPermission access="system.api_market.service_classification.add">
|
||||
<Button type="link" className="mt-[12px] pl-[0px]" onClick={()=>openModal('addCate')}><Icon icon="ic:baseline-add" width="18" height="18" className='mr-[2px]'/>{$t('添加分类')}</Button>
|
||||
</WithPermission>
|
||||
</Spin>
|
||||
</div>
|
||||
</InsidePage>
|
||||
)
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import {App, Form, Input} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle} from "react";
|
||||
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { ServiceHubCategoryConfigHandle, ServiceHubCategoryConfigFieldType, ServiceHubCategoryConfigProps } from "@market/const/serviceHub/type.ts"
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
export const ServiceHubCategoryConfig = forwardRef<ServiceHubCategoryConfigHandle,ServiceHubCategoryConfigProps>((props,ref)=>{
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm();
|
||||
const {type,entity} = props
|
||||
const {fetchData} = useFetch()
|
||||
|
||||
const save:()=>Promise<boolean | string> = ()=>{
|
||||
const url:string = 'catalogue'
|
||||
let method:string
|
||||
switch (type){
|
||||
case 'addCate':
|
||||
case 'addChildCate':
|
||||
method = 'POST'
|
||||
break;
|
||||
case 'renameCate':
|
||||
method = 'PUT'
|
||||
break
|
||||
}
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(!url || !method){
|
||||
reject($t(RESPONSE_TIPS.error))
|
||||
return
|
||||
}
|
||||
form.validateFields().then((value)=>{
|
||||
fetchData<BasicResponse<null>>(url,{method,eoBody:(value), eoParams:{ ...(type === 'renameCate' ? {catalogue:value.id} :undefined)}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save
|
||||
})
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
switch(type){
|
||||
case 'addCate':
|
||||
form.setFieldsValue({})
|
||||
break
|
||||
case 'addChildCate':
|
||||
form.setFieldsValue({parent:entity!.id})
|
||||
break
|
||||
case 'renameCate':
|
||||
form.setFieldsValue(entity)
|
||||
break
|
||||
}
|
||||
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<WithPermission access={type === 'addCate'? 'system.api_market.service_classification.add': 'system.api_market.service_classification.edit'}>
|
||||
<Form
|
||||
layout='vertical'
|
||||
scrollToFirstError
|
||||
labelAlign='left'
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="serviceHubCategoryConfig"
|
||||
autoComplete="off"
|
||||
>
|
||||
|
||||
{type === 'renameCate' &&
|
||||
<Form.Item<ServiceHubCategoryConfigFieldType>
|
||||
label={$t("ID")}
|
||||
name="id"
|
||||
hidden
|
||||
rules={[{ required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
}
|
||||
{(type === 'addCate' || type === 'renameCate') &&
|
||||
<Form.Item<ServiceHubCategoryConfigFieldType>
|
||||
label={$t("分类名称")}
|
||||
name="name"
|
||||
rules={[{ required: true ,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>}
|
||||
|
||||
{type === 'addChildCate' &&<>
|
||||
<Form.Item<ServiceHubCategoryConfigFieldType>
|
||||
label={$t("父分类 ID")}
|
||||
name="parent"
|
||||
hidden
|
||||
rules={[{ required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<ServiceHubCategoryConfigFieldType>
|
||||
label={$t("子分类名称")}
|
||||
name="name"
|
||||
rules={[{ required: true ,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
)
|
||||
})
|
||||
@@ -237,7 +237,7 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_,ref) => {
|
||||
if(accessInit){
|
||||
getTeamOptionList()
|
||||
}else{
|
||||
getGlobalAccessData()?.then(()=>{
|
||||
getGlobalAccessData()?.then?.(()=>{
|
||||
getTeamOptionList()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ const ServiceInsideDocument = ()=>{
|
||||
], toolbar: 'undo redo | styles | bold italic | alignleft aligncenter alignright alignjustify | codesample |table|' +
|
||||
'bullist numlist outdent indent | link image | print preview media fullscreen | ' +
|
||||
'forecolor backcolor emoticons | help',
|
||||
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
|
||||
content_style: 'body{ font-family:Helvetica,Arial,sans-serif; font-size:14px } img{ max-width: 100%; }',
|
||||
setup: setupEditor,
|
||||
codesample_languages:[
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ const SystemList:FC = ()=>{
|
||||
|
||||
const getSystemList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{
|
||||
getGlobalAccessData()?.then?.(()=>{
|
||||
getSystemList()
|
||||
})
|
||||
return
|
||||
@@ -63,7 +63,7 @@ const SystemList:FC = ()=>{
|
||||
|
||||
const getTeamsList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{
|
||||
getGlobalAccessData()?.then?.(()=>{
|
||||
getTeamsList()
|
||||
})
|
||||
return
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import {App, Col, Form, Input, Row, Select} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useRef} from "react";
|
||||
import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal.tsx";
|
||||
import styles from "./SystemInsideApi.module.css"
|
||||
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import { HTTP_METHOD, MATCH_CONFIG } from "../../../const/system/const.tsx";
|
||||
import { SystemInsideApiCreateHandle, SystemInsideApiCreateProps, SystemApiProxyFieldType, SystemInsideApiProxyHandle } from "../../../const/system/type.ts";
|
||||
import { MatchItem } from "@common/const/type.ts";
|
||||
import { validateUrlSlash } from "@common/utils/validate.ts";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import SystemInsideApiProxy from "@core/pages/system/api/SystemInsideApiProxy";
|
||||
|
||||
const SystemInsideApiCreate = forwardRef<SystemInsideApiCreateHandle,SystemInsideApiCreateProps>((props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const {type, entity, serviceId,teamId, modalApiPrefix:apiPrefix, modalPrefixForce:prefixForce} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const proxyRef = useRef<SystemInsideApiProxyHandle>(null)
|
||||
|
||||
const onFinish = ()=>{
|
||||
return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([,formValue])=>{
|
||||
const body = {...formValue,path:formValue.path.trim(),proxy:{...formValue.proxy,path:formValue.proxy.path ? (formValue.proxy.path.startsWith('/')? formValue.proxy.path: '/'+ formValue.proxy.path) : undefined}}
|
||||
return fetchData<BasicResponse<{api:SystemApiProxyFieldType}>>('service/api',{method:'POST',eoBody:(body), eoParams: {service:serviceId,team:teamId},eoTransformKeys:['matchType']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch(errInfo=>Promise.reject(errInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const copy: ()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
return form.validateFields().then((value)=>{
|
||||
fetchData<BasicResponse<{api:SystemApiProxyFieldType}>>('service/api/copy',{method:'POST',eoParams:{service:serviceId,team:teamId, api:entity!.id},eoBody:({...value,path:value.path.trim()})}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return resolve(data.api.id)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
copy,
|
||||
save:onFinish
|
||||
})
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if(type === 'copy'){
|
||||
form.setFieldsValue({
|
||||
...entity,
|
||||
name:`${$t('副本')}-${entity!.name}`,
|
||||
...(prefixForce?
|
||||
{prefix:apiPrefix,path: entity!.path.substring(apiPrefix?.length|| 0)}:
|
||||
{}),
|
||||
proxy:{timeout:10000, retry:0, ...entity?.proxy}
|
||||
});
|
||||
}
|
||||
else{
|
||||
form.setFieldValue('prefix',apiPrefix)
|
||||
form.setFieldValue(['proxy','timeout'],10000)
|
||||
form.setFieldValue(['proxy','retry'],0)
|
||||
}
|
||||
return (form.setFieldsValue({}))
|
||||
}, []);
|
||||
|
||||
return (<div className="h-full w-full">
|
||||
<Form
|
||||
layout='vertical'
|
||||
labelAlign='left'
|
||||
scrollToFirstError
|
||||
form={form}
|
||||
className="mx-auto flex flex-col h-full"
|
||||
name="systemInsideApiCreate"
|
||||
onFinish={onFinish}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className="">
|
||||
<Row className="mb-btnybase" > <Col ><span className="font-bold mr-[13px]">{$t('API 基础信息')}</span></Col></Row>
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
label={$t("API 名称")}
|
||||
name="name"
|
||||
rules={[{ required: true ,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
label={$t("描述")}
|
||||
name="description"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
label={$t("请求方式")}
|
||||
name="method"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.select)} options={HTTP_METHOD.map((method:string)=>{
|
||||
return { label:method, value:method}
|
||||
})}>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
label={$t("请求路径")}
|
||||
name="path"
|
||||
rules={[{ required: true,whitespace:true },
|
||||
{
|
||||
validator: validateUrlSlash,
|
||||
}]}
|
||||
className={styles['form-input-group']}
|
||||
>
|
||||
<Input prefix={(prefixForce ? `${apiPrefix}/` :"/")} className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
label={$t("高级匹配")}
|
||||
name="match"
|
||||
>
|
||||
<EditableTableWithModal<MatchItem & {_id:string}>
|
||||
configFields={MATCH_CONFIG}
|
||||
/>
|
||||
</Form.Item>
|
||||
{/* } */}
|
||||
|
||||
{ type !== 'copy' &&<>
|
||||
|
||||
<Row className="mb-btnybase mt-[40px]"><Col ><span className="font-bold mr-[13px]">{$t('转发规则设置')} </span></Col></Row>
|
||||
<Form.Item<SystemApiProxyFieldType>
|
||||
className="mb-0 bg-transparent border-none p-0"
|
||||
name="proxy"
|
||||
>
|
||||
<SystemInsideApiProxy serviceId={serviceId!} teamId={teamId!} ref={proxyRef} />
|
||||
</Form.Item>
|
||||
</>}
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
export default SystemInsideApiCreate
|
||||
@@ -1,99 +0,0 @@
|
||||
|
||||
import {useEffect, useRef, useState} from "react";
|
||||
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {App, Button, Spin} from "antd";
|
||||
import ApiBasicInfoDisplay from "@common/components/postcat/api/ApiPreview/components/ApiBasicInfoDisplay";
|
||||
import ApiPreview from "@common/components/postcat/ApiPreview.tsx";
|
||||
import ApiMatch from "@common/components/postcat/api/ApiPreview/components/ApiMatch";
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import ApiProxy from "@common/components/postcat/api/ApiPreview/components/ApiProxy";
|
||||
import { ProxyHeaderItem, SystemApiDetail, SystemInsideApiDetailProps, SystemInsideApiDocumentHandle } from "../../../const/system/type.ts";
|
||||
import { MatchItem } from "@common/const/type.ts";
|
||||
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
|
||||
import SystemInsideApiDocument from "./SystemInsideApiDocument.tsx";
|
||||
import ScrollableSection from "@common/components/aoplatform/ScrollableSection.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
|
||||
const SystemInsideApiDetail = (props:SystemInsideApiDetailProps)=>{
|
||||
const { message } = App.useApp()
|
||||
const {serviceId, teamId, apiId} = props
|
||||
const {fetchData} = useFetch()
|
||||
const [apiDetail, setApiDetail] = useState<SystemApiDetail>()
|
||||
const [open, setOpen] = useState(false);
|
||||
const drawerFormRef = useRef<SystemInsideApiDocumentHandle>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
|
||||
const getApiDetail = ()=>{
|
||||
setLoading(true)
|
||||
fetchData<BasicResponse<{api:SystemApiDetail}>>('service/api/detail',{method:'GET',eoParams:{service:serviceId,team:teamId, api:apiId},eoTransformKeys:['create_time','update_time','match_type','upstream_id','opt_type']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
const newApiDetail = {
|
||||
...data.api,
|
||||
match:data.api.match?.map((x:MatchItem)=>{x.id = x.id ?? uuidv4();return x}) || [],
|
||||
...data.api.proxy && {proxy:{...data.api.proxy,
|
||||
headers:data.api.proxy?.headers?.map((x:ProxyHeaderItem)=>{x.id = x.id?? uuidv4();return x || []
|
||||
})}
|
||||
}
|
||||
}
|
||||
setApiDetail(newApiDetail)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
|
||||
const onClose = ()=>{
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getApiDetail()
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className="h-full 1" rootClassName="h-full 2" wrapperClassName="h-full 3" >
|
||||
<div className="pb-btnbase h-full overflow-hidden box-border">
|
||||
<ScrollableSection>
|
||||
<div className="content-before pb-[8px] mb-[4px]">
|
||||
{
|
||||
apiDetail !== undefined && <>
|
||||
<div className="flex justify-between">
|
||||
<ApiBasicInfoDisplay apiName={apiDetail?.name} protocol={apiDetail?.protocol || 'HTTP'} method={apiDetail?.method} uri={apiDetail?.path} />
|
||||
<WithPermission access="team.service.api.edit"><Button type="primary" onClick={()=>setOpen(true)}>{$t('编辑文档')}</Button></WithPermission>
|
||||
</div>
|
||||
<p className="text-[14px] leading-[22px] text-[#999999]">
|
||||
<span className="mr-[20px]">{$t('创建者')}:{apiDetail?.creator.name || '-'}</span>
|
||||
<span className="mr-[20px]">{$t('最后编辑人')}:{apiDetail?.updater.name || '-'}</span><span>{$t('更新时间')}:{apiDetail?.updateTime || '-'}</span></p></>
|
||||
}
|
||||
</div>
|
||||
<div className="scroll-area h-[calc(100%-84px)] overflow-auto">
|
||||
{
|
||||
apiDetail?.match && apiDetail.match?.length > 0 &&
|
||||
<ApiMatch title={$t('高级匹配')} rows={apiDetail?.match} />
|
||||
}
|
||||
|
||||
{
|
||||
apiDetail?.proxy && Object.keys(apiDetail?.proxy).length > 0 &&
|
||||
<ApiProxy title={$t('转发规则')} proxyInfo={apiDetail?.proxy} />
|
||||
}
|
||||
|
||||
{apiDetail && <ApiPreview entity={{...apiDetail.doc,name:apiDetail.name, method:apiDetail.method,uri:apiDetail.path, protocol:apiDetail.protocol||'HTTP'}} />}
|
||||
</div>
|
||||
</ScrollableSection>
|
||||
<DrawerWithFooter
|
||||
title={$t("编辑 API")}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res&& getApiDetail();return res})}
|
||||
showLastStep={true}
|
||||
>
|
||||
<SystemInsideApiDocument ref={drawerFormRef} serviceId={serviceId} teamId={teamId} apiId={apiId}/>
|
||||
</DrawerWithFooter>
|
||||
</div>
|
||||
</Spin>)
|
||||
}
|
||||
export default SystemInsideApiDetail
|
||||
@@ -1,251 +0,0 @@
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"
|
||||
import {ActionType} from "@ant-design/pro-components";
|
||||
import {FC, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {Link, useParams} from "react-router-dom";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {App, Divider} from "antd";
|
||||
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import { SimpleMemberItem} from '@common/const/type.ts'
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import SystemInsideApiCreate from "./SystemInsideApiCreate.tsx";
|
||||
import {useSystemContext} from "../../../contexts/SystemContext.tsx";
|
||||
import { SYSTEM_API_TABLE_COLUMNS } from "../../../const/system/const.tsx";
|
||||
import { SystemApiSimpleFieldType, SystemApiTableListItem, SystemInsideApiCreateHandle, SystemInsideApiDocumentHandle } from "../../../const/system/type.ts";
|
||||
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { checkAccess } from "@common/utils/permission.ts";
|
||||
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
|
||||
import SystemInsideApiDetail from "./SystemInsideApiDetail.tsx";
|
||||
import SystemInsideApiDocument from "./SystemInsideApiDocument.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
|
||||
const SystemInsideApiList:FC = ()=>{
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const { modal,message } = App.useApp()
|
||||
// const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [init, setInit] = useState<boolean>(true)
|
||||
const [tableListDataSource, setTableListDataSource] = useState<SystemApiTableListItem[]>([]);
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
const {fetchData} = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const copyRef = useRef<SystemInsideApiCreateHandle>(null)
|
||||
const {apiPrefix, prefixForce} = useSystemContext()
|
||||
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
|
||||
const {accessData,state} = useGlobalContext()
|
||||
const [drawerType,setDrawerType]= useState<'add'|'edit'|'view'|'upstream'|undefined>()
|
||||
const [open, setOpen] = useState(false);
|
||||
const drawerEditFormRef = useRef<SystemInsideApiDocumentHandle>(null)
|
||||
const drawerAddFormRef = useRef<SystemInsideApiCreateHandle>(null)
|
||||
const {serviceId, teamId} = useParams<RouterParams>()
|
||||
|
||||
const [curApi, setCurApi] = useState<SystemApiTableListItem>()
|
||||
|
||||
const getApiList = (): Promise<{ data: SystemApiTableListItem[], success: boolean }>=> {
|
||||
//console.log(sorter, filter)
|
||||
if(!tableHttpReload){
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
|
||||
return fetchData<BasicResponse<{apis:SystemApiTableListItem}>>('service/apis',{method:'GET',eoParams:{service:serviceId,team:teamId, keyword:searchWord},eoTransformKeys:['request_path','create_time','update_time','can_delete']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setTableListDataSource(data.apis)
|
||||
setInit((prev)=>prev ? false : prev)
|
||||
setTableHttpReload(false)
|
||||
return {data:data.apis, success: true}
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return {data:[], success:false}
|
||||
}
|
||||
}).catch(() => {
|
||||
return {data:[], success:false}
|
||||
})
|
||||
}
|
||||
|
||||
const deleteApi = (entity:SystemApiTableListItem)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
fetchData<BasicResponse<null>>('service/api',{method:'DELETE',eoParams:{service:serviceId,team:teamId, api:entity!.id}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const openModal = async (type:'copy' | 'delete',entity:SystemApiTableListItem) =>{
|
||||
let title:string = ''
|
||||
let content:string|React.ReactNode = ''
|
||||
switch (type){
|
||||
case 'copy':{
|
||||
title=$t('复制 API')
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
const {code,data,msg} = await fetchData<BasicResponse<{api:SystemApiSimpleFieldType}>>('service/api/detail/simple',{method:'GET',eoParams:{service:serviceId,team:teamId, api:entity!.id}})
|
||||
message.destroy()
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
content=<SystemInsideApiCreate ref={copyRef} type={type} entity={{...data.api, path:(data.api.path?.startsWith('/')? data.api.path.substring(1): data.api.path),serviceId:serviceId}} serviceId={serviceId!} teamId={teamId!} modalApiPrefix={apiPrefix} modalPrefixForce={prefixForce}/>
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return
|
||||
}
|
||||
break;}
|
||||
case 'delete':
|
||||
title=$t('删除')
|
||||
content=$t(DELETE_TIPS.default)
|
||||
break;
|
||||
}
|
||||
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk:()=> {
|
||||
switch (type){
|
||||
case 'copy':
|
||||
return copyRef.current?.copy().then(()=> {
|
||||
manualReloadTable()
|
||||
})
|
||||
case 'delete':
|
||||
return deleteApi(entity).then((res)=>{if(res === true) manualReloadTable()})
|
||||
}
|
||||
},
|
||||
width:type==='copy'? 900: 600,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
disabled : !checkAccess( `team.service.api.${type}`, accessData )
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>,
|
||||
})
|
||||
}
|
||||
|
||||
const operation:PageProColumns<SystemApiTableListItem>[] =[
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
btnNums:4,
|
||||
fixed:'right',
|
||||
valueType: 'option',
|
||||
render: (_: React.ReactNode, entity: SystemApiTableListItem) => [
|
||||
<TableBtnWithPermission access="team.service.api.view" key="view" btnType="view" onClick={()=>{openDrawer('view',entity)}} btnTitle="详情"/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1" />,
|
||||
<TableBtnWithPermission access="team.service.api.copy" key="copy" btnType="copy" onClick={()=>{openModal('copy',entity)}} btnTitle="复制"/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2"/>,
|
||||
<TableBtnWithPermission access="team.service.api.edit" key="edit" btnType="edit" onClick={()=>{openDrawer('edit',entity)}} btnTitle="编辑"/>,
|
||||
entity.canDelete && <Divider type="vertical" className="mx-0" key="div3"/>,
|
||||
entity.canDelete && <TableBtnWithPermission access="team.service.api.delete" key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle="删除"/>,
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true); // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
};
|
||||
|
||||
const getMemberList = async ()=>{
|
||||
setMemberValueEnum([])
|
||||
const {code,data,msg} = await fetchData<BasicResponse<{ members: SimpleMemberItem[] }>>('simple/member',{method:'GET'})
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setMemberValueEnum(data.members)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}
|
||||
|
||||
const openDrawer = (type:'add'|'edit'|'view',entity?:SystemApiTableListItem)=>{
|
||||
setCurApi(entity)
|
||||
setDrawerType(type)
|
||||
}
|
||||
|
||||
useEffect(()=>{drawerType !== undefined ? setOpen(true):setOpen(false)},[drawerType])
|
||||
|
||||
useEffect(() => {
|
||||
setBreadcrumb([
|
||||
{
|
||||
title:<Link to={`/service/list`}>{$t('服务')}</Link>
|
||||
},
|
||||
{
|
||||
title:$t('API')
|
||||
}
|
||||
])
|
||||
getMemberList()
|
||||
manualReloadTable()
|
||||
}, [serviceId]);
|
||||
|
||||
const onClose = () => {
|
||||
setDrawerType(undefined);
|
||||
setCurApi(undefined)
|
||||
};
|
||||
|
||||
const columns = useMemo(()=>{
|
||||
return [...SYSTEM_API_TABLE_COLUMNS].map(x=>{
|
||||
if(x.filters &&((x.dataIndex as string[])?.indexOf('creator') !== -1) ){
|
||||
const tmpValueEnum:{[k:string]:{text:string}} = {}
|
||||
memberValueEnum?.forEach((x:SimpleMemberItem)=>{
|
||||
tmpValueEnum[x.name] = {text:x.name}
|
||||
})
|
||||
x.valueEnum = tmpValueEnum
|
||||
}
|
||||
return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}})
|
||||
},[memberValueEnum,state.language])
|
||||
|
||||
const handlerSubmit:() => Promise<string | boolean>|undefined= ()=>{
|
||||
switch(drawerType){
|
||||
case 'add':{
|
||||
return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})
|
||||
}
|
||||
case 'edit':{
|
||||
return drawerEditFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})
|
||||
}
|
||||
default:return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageList
|
||||
id="global_system_api"
|
||||
ref={pageListRef}
|
||||
columns = {[...columns,...operation]}
|
||||
request={()=>getApiList()}
|
||||
dataSource={tableListDataSource}
|
||||
addNewBtnTitle={$t('添加 API')}
|
||||
searchPlaceholder={$t('输入名称、URL 查找 API')}
|
||||
onAddNewBtnClick={()=>{openDrawer('add')}}
|
||||
addNewBtnAccess="team.service.api.add"
|
||||
tableClickAccess="team.service.api.view"
|
||||
manualReloadTable={manualReloadTable}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value)}}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
onRowClick={(row:SystemApiTableListItem)=>openDrawer('view',row)}
|
||||
tableClass="mr-PAGE_INSIDE_X "
|
||||
/>
|
||||
<DrawerWithFooter
|
||||
title={drawerType === 'add' ? $t("添加 API"):$t("API 详情")}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onSubmit={()=>handlerSubmit()}
|
||||
showOkBtn={drawerType !== 'view'}
|
||||
>
|
||||
{drawerType === 'add' && <SystemInsideApiCreate ref={drawerAddFormRef} modalApiPrefix={apiPrefix} serviceId={serviceId!} teamId={teamId!} modalPrefixForce={prefixForce}/>}
|
||||
{drawerType === 'edit' && <SystemInsideApiDocument ref={drawerEditFormRef} serviceId={serviceId!} teamId={teamId!} apiId={curApi!.id!}/>}
|
||||
{drawerType === 'view' && <SystemInsideApiDetail serviceId={serviceId!} teamId={teamId!} apiId={curApi!.id!}/>}
|
||||
</DrawerWithFooter>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
export default SystemInsideApiList
|
||||
@@ -35,7 +35,7 @@ const TeamList:FC = ()=>{
|
||||
|
||||
const getTeamList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{getTeamList()})
|
||||
getGlobalAccessData()?.then?.(()=>{getTeamList()})
|
||||
return
|
||||
}
|
||||
return fetchData<BasicResponse<{teams:TeamTableListItem}>>(!checkPermission('system.workspace.team.view_all') ? 'teams':'manager/teams',{method:'GET',eoParams:{keyword:searchWord},eoTransformKeys:['create_time','service_num','can_delete']}).then(response=>{
|
||||
|
||||
@@ -17,6 +17,7 @@ export type ServiceBasicInfoType = {
|
||||
logo?:string
|
||||
invokeAddress:string
|
||||
approvalType:'auto'|'manual'
|
||||
serviceType:'ai'|'rest'
|
||||
}
|
||||
|
||||
export type ServiceDetailType = {
|
||||
|
||||
@@ -35,7 +35,7 @@ const ServiceHubDetail = ()=>{
|
||||
const navigate = useNavigate();
|
||||
|
||||
const getServiceBasicInfo = ()=>{
|
||||
fetchData<BasicResponse<{service:ServiceDetailType}>>('catalogue/service',{method:'GET',eoParams:{service:serviceId}, eoTransformKeys:['app_num','api_num','update_time','api_doc','invoke_address','approval_type']}).then(response=>{
|
||||
fetchData<BasicResponse<{service:ServiceDetailType}>>('catalogue/service',{method:'GET',eoParams:{service:serviceId}, eoTransformKeys:['app_num','api_num','update_time','api_doc','invoke_address','approval_type','service_type']}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setService(data.service)
|
||||
@@ -113,7 +113,7 @@ const ServiceHubDetail = ()=>{
|
||||
{
|
||||
key: 'api-document',
|
||||
label: $t('API 文档'),
|
||||
children: <div className="p-btnbase"><ServiceHubApiDocument service={service!} /></div>,
|
||||
children: <div className={`p-btnbase ${serviceBasicInfo?.serviceType?.toLocaleLowerCase() === 'ai' ? 'ai-service-api-preview' : ''}`}><ServiceHubApiDocument service={service!} /></div>,
|
||||
icon: <ApiFilled />
|
||||
}
|
||||
]
|
||||
|
||||
@@ -78,7 +78,7 @@ export const initialServiceHubListState = {
|
||||
if(!dataSet.selectedTag || dataSet.selectedTag.length === 0) return false
|
||||
if((!x.tags || !x.tags.length )&& dataSet.selectedTag.indexOf('empty') === -1) return false
|
||||
if(x.tags && x.tags.length && !x.tags.some(tag => dataSet.selectedTag.includes(tag.id))) return false;
|
||||
if( dataSet.keyword && !x.name.includes(dataSet.keyword)) return false
|
||||
if( dataSet.keyword && !x.name.toLocaleLowerCase().includes(dataSet.keyword.toLocaleLowerCase())) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function ServiceHubManagement() {
|
||||
|
||||
const getServiceList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{getServiceList()})
|
||||
getGlobalAccessData()?.then?.(()=>{getServiceList()})
|
||||
return
|
||||
}
|
||||
setServiceLoading(true)
|
||||
@@ -56,7 +56,7 @@ const getServiceList = ()=>{
|
||||
|
||||
const getTeamsList = ()=>{
|
||||
if(!accessInit){
|
||||
getGlobalAccessData()?.then(()=>{getTeamsList()})
|
||||
getGlobalAccessData()?.then?.(()=>{getTeamsList()})
|
||||
return
|
||||
}
|
||||
setPageLoading(true)
|
||||
|
||||
Reference in New Issue
Block a user