mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-04 10:13:53 +08:00
Feature/1.4 (#154)
- Load balancing (can connect to multiple accounts, automatically switch accounts when there is no quota) - AI call log - Model rate configuration
This commit is contained in:
+1
-1
@@ -3,4 +3,4 @@
|
||||
/config.yml
|
||||
/build/
|
||||
/apipark
|
||||
.gitlab-ci.yml
|
||||
.gitlab-ci.yml
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Antd",
|
||||
"apinto",
|
||||
"Apipark",
|
||||
"logsettings",
|
||||
"resourcesettings"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
.next
|
||||
*.d.ts
|
||||
*.js
|
||||
@@ -18,13 +18,18 @@
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint", "prettier"],
|
||||
"plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["warn"]
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100,
|
||||
"semi": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"jsxSingleQuote": false
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"bracketLine": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
Create detailed components with these requirements:
|
||||
1. Use 'use client' directive for client-side components
|
||||
2. Style with Tailwind CSS utility classes for responsive design
|
||||
3. Use React Router for navigation
|
||||
4. Use Ant Design for UI components
|
||||
5. Use iconify React for icons (from @iconify/react package). Do NOT use other UI libraries unless requested
|
||||
6. Use local photos from public folder where appropriate, only valid URLs you know exist
|
||||
7. Create root layout.tsx page that wraps necessary navigation items to all pages
|
||||
8. MUST implement the navigation elements items in their rightful place i.e. Left sidebar, Top header
|
||||
9. Accurately implement necessary grid layouts
|
||||
10. Follow proper import practices:
|
||||
- Use @/ path aliases
|
||||
- Keep component imports organized
|
||||
- Update current packages/core/src/pages/Root.tsx with new comprehensive code
|
||||
- Don't forget root route (page.tsx) handling
|
||||
- You MUST complete the entire prompt before stopping
|
||||
11. Table component should use `import PageList from "@common/components/aoplatform/PageList.tsx"`
|
||||
12. PageList component MUST use addNewBtnTitle for add button, NOT toolBarRender. Example:
|
||||
<PageList
|
||||
id="global_team"
|
||||
className="pl-btnbase"
|
||||
ref={pageListRef}
|
||||
columns = {[...columns]}
|
||||
request = {()=>getTeamList()}
|
||||
showPagination={false}
|
||||
addNewBtnTitle={$t('添加团队')}
|
||||
addNewBtnAccess = "system.organization.team.add"
|
||||
searchPlaceholder={$t("输入名称、ID、负责人查找团队")}
|
||||
onAddNewBtnClick={()=>{openModal('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value)}}
|
||||
onRowClick={(row:TeamTableListItem)=>(navigate(`../inside/${row.id}/setting`))}
|
||||
/>
|
||||
13. use `const { fetchData } = useFetch()` to fetch http data,such as
|
||||
```tsx
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
```
|
||||
14. can't not import new package!
|
||||
@@ -6,7 +6,7 @@ const systemLanguage = {
|
||||
en_US: 'en-US',
|
||||
zh_CN: 'zh-CN',
|
||||
ja_JP: 'ja-JP',
|
||||
zh_TW: 'zh-TW'
|
||||
zh_TW: 'zh-TW',
|
||||
};
|
||||
const localesDir = 'packages/common/src/locales/scan';
|
||||
const newJsonDir = 'packages/common/src/locales/scan/newJson';
|
||||
@@ -19,7 +19,7 @@ fs.readdirSync(localesDir).forEach(file => {
|
||||
const lang = path.basename(file, '.json');
|
||||
const filePath = path.join(localesDir, file);
|
||||
try {
|
||||
console.log('Current working directory:', process.cwd(),filePath);
|
||||
console.log('Current working directory:', process.cwd(), filePath);
|
||||
const existJsonData = fs.readFileSync(filePath);
|
||||
existData[lang] = JSON.parse(existJsonData);
|
||||
} catch (error) {
|
||||
@@ -36,20 +36,18 @@ fs.readdirSync(localesDir).forEach(file => {
|
||||
|
||||
const keyList = Object.keys(existData);
|
||||
|
||||
|
||||
// 清空 newJson 目录下的所有语言文件
|
||||
Object.values(systemLanguage).forEach(lng => {
|
||||
const newJsonPath = path.join(newJsonDir, `${lng}.json`);
|
||||
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
|
||||
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
input: [
|
||||
'packages/*/src/**/*.{js,jsx,tsx,ts}',
|
||||
// 不需要扫描的文件加!
|
||||
'!packages/*/src/locales/**',
|
||||
'!**/node_modules/**'
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
output: 'packages/common/src/locales/scan', // 输出目录
|
||||
options: {
|
||||
@@ -62,15 +60,15 @@ module.exports = {
|
||||
loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
|
||||
savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key)
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n'
|
||||
lineEnding: '\n',
|
||||
},
|
||||
removeUnusedKeys: true,
|
||||
nsSeparator: false, // namespace separator
|
||||
keySeparator: false, // key separator
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}'
|
||||
}
|
||||
suffix: '}}',
|
||||
},
|
||||
},
|
||||
// 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换.
|
||||
transform: function (file, enc, done) {
|
||||
@@ -80,11 +78,10 @@ module.exports = {
|
||||
parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
|
||||
options.defaultValue = key;
|
||||
const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
|
||||
keyHashMap[key] = hashKey;
|
||||
|
||||
keyHashMap[key] = hashKey;
|
||||
|
||||
// 遍历每种语言,逐个语言检查翻译是否存在
|
||||
keyList.forEach((lng) => {
|
||||
keyList.forEach(lng => {
|
||||
const langData = existData[lng] || {};
|
||||
|
||||
// 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中
|
||||
@@ -116,13 +113,12 @@ module.exports = {
|
||||
});
|
||||
done();
|
||||
},
|
||||
flush: function(done) {
|
||||
flush: function (done) {
|
||||
// 将 keyHashMap 写入文件
|
||||
fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2));
|
||||
|
||||
|
||||
// 遍历每种语言,处理旧字段
|
||||
keyList.forEach((lng) => {
|
||||
keyList.forEach(lng => {
|
||||
const localeFilePath = path.join(localesDir, `${lng}.json`);
|
||||
const oldJsonPath = path.join(oldJsonDir, `${lng}.json`);
|
||||
const langData = existData[lng] || {};
|
||||
@@ -132,7 +128,7 @@ module.exports = {
|
||||
// 将不存在于 keyHashMap 中的键移动到 oldJson 文件中
|
||||
Object.keys(langData).forEach(hashKey => {
|
||||
if (!Object.values(keyHashMap).includes(hashKey)) {
|
||||
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
|
||||
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,5 +138,5 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
done();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
"serve:remotes": "lerna run serve --scope=remote --parallel",
|
||||
"dev": "lerna run dev --scope=core --stream",
|
||||
"stop": "kill-port --port 5000",
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js"
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -65,8 +67,12 @@
|
||||
"antd": "^5.19.4",
|
||||
"babel-jest": "^29.7.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"eslint-plugin-react": "7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next-scanner": "^4.5.0",
|
||||
"jest": "^29.7.0",
|
||||
@@ -78,6 +84,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.2.2",
|
||||
|
||||
@@ -1,39 +1,37 @@
|
||||
import React from "react"
|
||||
import SwaggerUI from 'swagger-ui-react';
|
||||
import 'swagger-ui-react/swagger-ui.css';
|
||||
import React from 'react'
|
||||
import SwaggerUI from 'swagger-ui-react'
|
||||
import 'swagger-ui-react/swagger-ui.css'
|
||||
|
||||
export default function ApiDocument({spec}:{spec?:string|object}) {
|
||||
|
||||
class OperationsLayout extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
getComponent
|
||||
} = this.props
|
||||
const Operations = getComponent("operations", true)
|
||||
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
}
|
||||
}
|
||||
export default function ApiDocument({ spec }: { spec?: string | object }) {
|
||||
class OperationsLayout extends React.Component {
|
||||
render() {
|
||||
const { getComponent } = this.props
|
||||
const Operations = getComponent('operations', true)
|
||||
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return(
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
supportedSubmitMethods={[]}
|
||||
customComponents={{Header:()=>null}}
|
||||
layout="OperationsLayout"
|
||||
plugins={[OperationsLayoutPlugin ]} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
supportedSubmitMethods={[]}
|
||||
customComponents={{ Header: () => null }}
|
||||
layout="OperationsLayout"
|
||||
plugins={[OperationsLayoutPlugin]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
import {
|
||||
MenuProps,
|
||||
App,
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Dropdown
|
||||
} from 'antd';
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import Logo from '@common/assets/layout-logo.png';
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components'
|
||||
import AvatarPic from '@common/assets/default-avatar.png'
|
||||
import { useCallback, useEffect, useMemo, useState} from "react";
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts';
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx';
|
||||
import { UserInfoType } from '@common/const/type.ts';
|
||||
import { useFetch } from '@common/hooks/http.ts';
|
||||
import { ProjectFilled } from '@ant-design/icons';
|
||||
import { getNavItem, transformMenuData } from '@common/utils/navigation';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { $t } from '@common/locales';
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components';
|
||||
import LanguageSetting from './LanguageSetting';
|
||||
import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext';
|
||||
import Logo from '@common/assets/layout-logo.png'
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'
|
||||
import { UserInfoType } from '@common/const/type.ts'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales'
|
||||
import { transformMenuData } from '@common/utils/navigation'
|
||||
import { Icon } from '@iconify/react'
|
||||
import { App, Button, ConfigProvider, Dropdown, MenuProps } from 'antd'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import LanguageSetting from './LanguageSetting'
|
||||
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE;
|
||||
export type MenuItem = Required<MenuProps>['items'][number];
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE
|
||||
export type MenuItem = Required<MenuProps>['items'][number]
|
||||
|
||||
const themeToken = {
|
||||
bgLayout: '#17163E;',
|
||||
@@ -32,92 +25,99 @@ const themeToken = {
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0
|
||||
}
|
||||
}
|
||||
|
||||
function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
const navigator = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
|
||||
useGlobalContext()
|
||||
const [pathname, setPathname] = useState(currentUrl)
|
||||
const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
|
||||
function BasicLayout({project = 'core'}:{project:string}){
|
||||
const navigator = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { state,accessData,checkPermission,accessInit,dispatch,resetAccess,getGlobalAccessData, menuList} = useGlobalContext()
|
||||
const [pathname, setPathname] = useState(currentUrl);
|
||||
const mainPage = project === 'core' ?'/service/list':'/serviceHub/list'
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>();
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
const newMenu = transformMenuData(menuList)
|
||||
setMenuItems(newMenu);
|
||||
},[menuList, state.language,accessInit])
|
||||
setMenuItems(newMenu)
|
||||
}, [menuList, state.language, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUrl === '/') {
|
||||
navigator(mainPage)
|
||||
}
|
||||
|
||||
}, [currentUrl]);
|
||||
}, [currentUrl])
|
||||
|
||||
const headerMenuData = useMemo(() => {
|
||||
// 判断权限
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]);
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
|
||||
// 过滤菜单项
|
||||
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
|
||||
return [...menu]
|
||||
.filter(x => x) // 过滤掉空数据
|
||||
.filter((x) => x) // 过滤掉空数据
|
||||
.map((item: any) => {
|
||||
if (item.routes && item.routes.length > 0) {
|
||||
// 递归处理子菜单
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes);
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes)
|
||||
|
||||
if (filteredRoutes.length === 0) {
|
||||
return false
|
||||
}
|
||||
return { ...item,routes: filteredRoutes,name:$t(item.name) };
|
||||
return { ...item, routes: filteredRoutes, name: $t(item.name) }
|
||||
}
|
||||
// 处理没有 routes 的菜单项
|
||||
if (item.access) {
|
||||
return (item.access === 'all' || hasAccess(item.access)) ? {...item,name:$t(item.name)} : null;
|
||||
return item.access === 'all' || hasAccess(item.access) ? { ...item, name: $t(item.name) } : null
|
||||
}
|
||||
// 如果没有 access 和 routes,则保留
|
||||
return {...item,name:$t(item.name) };
|
||||
})
|
||||
.filter(x => x); // 过滤掉处理后为 null 的项
|
||||
};
|
||||
|
||||
// 初始过滤操作
|
||||
const res = [...(menuItems || [])]!.filter(x => x).map((x: any) => (x.routes ? { ...x,name:$t(x.name), routes: filterMenu(x.routes) } : {...x,name:$t(x.name)}));
|
||||
// 返回处理后的数据
|
||||
return { path: '/', routes: res.map(x=> ({...x, routes: x.routes?.filter(x=> (x.access || x.routes?.length > 0))})).filter(x=> (x.access || x.routes?.length > 0)) };
|
||||
}, [accessData, state.language,menuItems]);
|
||||
|
||||
const { message } = App.useApp()
|
||||
const [userInfo,setUserInfo] = useState<UserInfoType>()
|
||||
const {fetchData} = useFetch()
|
||||
const navigate = useNavigate();
|
||||
// 如果没有 access 和 routes,则保留
|
||||
return { ...item, name: $t(item.name) }
|
||||
})
|
||||
.filter((x) => x) // 过滤掉处理后为 null 的项
|
||||
}
|
||||
|
||||
// 初始过滤操作
|
||||
const res = [...(menuItems || [])]!
|
||||
.filter((x) => x)
|
||||
.map((x: any) =>
|
||||
x.routes ? { ...x, name: $t(x.name), routes: filterMenu(x.routes) } : { ...x, name: $t(x.name) }
|
||||
)
|
||||
// 返回处理后的数据
|
||||
return {
|
||||
path: '/',
|
||||
routes: res
|
||||
.map((x) => ({ ...x, routes: x.routes?.filter((x) => x.access || x.routes?.length > 0) }))
|
||||
.filter((x) => x.access || x.routes?.length > 0)
|
||||
}
|
||||
}, [accessData, state.language, menuItems])
|
||||
|
||||
const { message } = App.useApp()
|
||||
const [userInfo, setUserInfo] = useState<UserInfoType>()
|
||||
const { fetchData } = useFetch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const getUserInfo = () => {
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' })
|
||||
.then(response => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUserInfo()
|
||||
getGlobalAccessData()
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
const logOut = () => {
|
||||
fetchData<BasicResponse<null>>('account/logout', { method: 'GET' }).then(response => {
|
||||
fetchData<BasicResponse<null>>('account/logout', { method: 'GET' }).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
dispatch({ type: 'LOGOUT' })
|
||||
@@ -130,133 +130,162 @@ const themeToken = {
|
||||
})
|
||||
}
|
||||
|
||||
const items: MenuProps['items'] = useMemo(() => [
|
||||
userInfo?.type !== 'guest' && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button key="changePsw" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={() => navigator('/userProfile/changepsw')}>
|
||||
{$t('账号设置')}
|
||||
</Button>)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: (
|
||||
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={logOut}>
|
||||
{$t('退出登录')}
|
||||
</Button>)
|
||||
},
|
||||
].filter(Boolean), [userInfo]);
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
[
|
||||
userInfo?.type !== 'guest' && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button
|
||||
key="changePsw"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={() => navigator('/userProfile/changepsw')}
|
||||
>
|
||||
{$t('账号设置')}
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: (
|
||||
<Button
|
||||
key="logout"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={logOut}
|
||||
>
|
||||
{$t('退出登录')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
].filter(Boolean),
|
||||
[userInfo]
|
||||
)
|
||||
|
||||
const actionRender = useMemo(() => {
|
||||
return [
|
||||
<LanguageSetting />,
|
||||
<Button
|
||||
className=" text-[#ffffffb3] hover:text-[#fff] border-none"
|
||||
type="default"
|
||||
ghost
|
||||
onClick={() => {
|
||||
window.open('https://docs.apipark.com', '_blank')
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-[8px]">
|
||||
{' '}
|
||||
<Icon icon="ic:baseline-help" width="14" height="14" />
|
||||
{$t('文档')}
|
||||
</span>
|
||||
</Button>,
|
||||
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || [])
|
||||
]
|
||||
}, [pluginSlotHub.getSlot('basicLayoutAfterBtns')])
|
||||
|
||||
const actionRender =useMemo( ()=>{
|
||||
return [
|
||||
<LanguageSetting />,
|
||||
<Button className=" text-[#ffffffb3] hover:text-[#fff] border-none" type="default" ghost onClick={()=>{window.open('https://docs.apipark.com','_blank')}}>
|
||||
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-help" width="14" height="14"/>{$t('文档')}</span>
|
||||
</Button> ,
|
||||
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[] )||[] )
|
||||
]
|
||||
},[pluginSlotHub.getSlot('basicLayoutAfterBtns') ])
|
||||
|
||||
|
||||
return(
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
return (
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body;
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{
|
||||
pathname
|
||||
}}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{
|
||||
type: 'group',
|
||||
collapsedShowGroupTitle: true
|
||||
}}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: AvatarPic || userInfo?.avatar,
|
||||
size: 'small',
|
||||
title: userInfo?.username || 'unknown',
|
||||
render: (props, dom) => {
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items
|
||||
}}
|
||||
>
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{
|
||||
pathname,
|
||||
}}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{
|
||||
type: 'group',
|
||||
collapsedShowGroupTitle: true,
|
||||
}}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: AvatarPic || userInfo?.avatar,
|
||||
size: 'small',
|
||||
title: userInfo?.username||'unknown',
|
||||
render: (props, dom) => {
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items
|
||||
}}
|
||||
>
|
||||
<div className='avatar-dom'>{dom}
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
},
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return [];
|
||||
if (typeof window === 'undefined') return [];
|
||||
return actionRender;
|
||||
}}
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img
|
||||
className="h-[20px] cursor-pointer "
|
||||
src={Logo}
|
||||
onClick={()=> navigator(mainPage)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
logo={Logo}
|
||||
pageTitleRender={()=>$t('APIPark')}
|
||||
menuFooterRender={(props) => {
|
||||
if (props?.collapsed) return undefined;
|
||||
}}
|
||||
menuItemRender={(item, dom) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
// 同级目录点击无效
|
||||
if(item.key && routerKeyMap.get(item.key) && routerKeyMap.get(item.key).length > 0 && routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1){
|
||||
return
|
||||
}
|
||||
if(item.key === pathname.split('/')[1]){
|
||||
return
|
||||
}
|
||||
|
||||
if(item.path){
|
||||
navigator(item.path)
|
||||
}
|
||||
setPathname(item.path || '');
|
||||
}}
|
||||
>
|
||||
{dom}
|
||||
</div>
|
||||
)}
|
||||
fixSiderbar={true}
|
||||
layout='mix'
|
||||
splitMenus={true}
|
||||
collapsed={false}
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden' }`}>
|
||||
<Outlet />
|
||||
</div>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
</ProConfigProvider>
|
||||
</div>
|
||||
)
|
||||
>
|
||||
<div className="avatar-dom">{dom}</div>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return []
|
||||
if (typeof window === 'undefined') return []
|
||||
return actionRender
|
||||
}}
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img className="h-[20px] cursor-pointer " src={Logo} onClick={() => navigator(mainPage)} />
|
||||
</div>
|
||||
)}
|
||||
logo={Logo}
|
||||
pageTitleRender={() => $t('APIPark')}
|
||||
menuFooterRender={(props) => {
|
||||
if (props?.collapsed) return undefined
|
||||
}}
|
||||
menuItemRender={(item, dom) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
// 同级目录点击无效
|
||||
if (
|
||||
item.key &&
|
||||
routerKeyMap.get(item.key) &&
|
||||
routerKeyMap.get(item.key).length > 0 &&
|
||||
routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (item.key === pathname.split('/')[1]) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.path) {
|
||||
navigator(item.path)
|
||||
}
|
||||
setPathname(item.path || '')
|
||||
}}
|
||||
>
|
||||
{dom}
|
||||
</div>
|
||||
)}
|
||||
fixSiderbar={true}
|
||||
layout="mix"
|
||||
splitMenus={true}
|
||||
collapsed={false}
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${
|
||||
currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'
|
||||
}`}
|
||||
>
|
||||
<Outlet />
|
||||
</div>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
</ProConfigProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default BasicLayout
|
||||
export default BasicLayout
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Breadcrumb } from "antd"
|
||||
import { useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {FC,useEffect} from "react";
|
||||
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { FC, useEffect } from 'react'
|
||||
|
||||
const TopBreadcrumb: FC = () => {
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {
|
||||
}, [breadcrumb]);
|
||||
return (
|
||||
<Breadcrumb items={breadcrumb} />
|
||||
)
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {}, [breadcrumb])
|
||||
return <Breadcrumb items={breadcrumb} />
|
||||
}
|
||||
|
||||
export default TopBreadcrumb
|
||||
export default TopBreadcrumb
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
import { FC } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { $t } from '@common/locales';
|
||||
import { FC } from 'react'
|
||||
import { Table } from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
interface DataType {
|
||||
httpStatusCode: string;
|
||||
systemStatusCode: string;
|
||||
description: string;
|
||||
|
||||
httpStatusCode: string
|
||||
systemStatusCode: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title:$t('HTTP 状态码'),
|
||||
title: $t('HTTP 状态码'),
|
||||
dataIndex: 'httpStatusCode',
|
||||
key: 'httpStatusCode',
|
||||
key: 'httpStatusCode'
|
||||
},
|
||||
{
|
||||
title:$t('系统状态码'),
|
||||
title: $t('系统状态码'),
|
||||
dataIndex: 'systemStatusCode',
|
||||
key: 'systemStatusCode',
|
||||
key: 'systemStatusCode'
|
||||
},
|
||||
{
|
||||
title: $t('描述'),
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis:true
|
||||
},
|
||||
|
||||
];
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
const data: DataType[] = [
|
||||
// {
|
||||
@@ -44,12 +42,12 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '413',
|
||||
systemStatusCode: '10003',
|
||||
description: '请求频率过高',
|
||||
description: '请求频率过高'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '403',
|
||||
systemStatusCode: '10004',
|
||||
description: '请求来源非法,不在白名单中',
|
||||
description: '请求来源非法,不在白名单中'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '416',
|
||||
@@ -59,7 +57,7 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '504',
|
||||
systemStatusCode: '10006',
|
||||
description: '网关超时',
|
||||
description: '网关超时'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '504',
|
||||
@@ -69,7 +67,7 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '404',
|
||||
systemStatusCode: '10007',
|
||||
description: '接口不存在',
|
||||
description: '接口不存在'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '416',
|
||||
@@ -84,42 +82,43 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10010',
|
||||
description: '无法识别请求内容,请检查请求体是否正确',
|
||||
description: '无法识别请求内容,请检查请求体是否正确'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10011',
|
||||
description: '请求头部缺少 Content-Type 字段',
|
||||
description: '请求头部缺少 Content-Type 字段'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10011',
|
||||
description: '请求头部 Content-Type 字段错误',
|
||||
description: '请求头部 Content-Type 字段错误'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10014',
|
||||
description: '批量参数超出单次批量数量的最大限制',
|
||||
description: '批量参数超出单次批量数量的最大限制'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10016',
|
||||
description: '参数缺少内容',
|
||||
description: '参数缺少内容'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '500',
|
||||
systemStatusCode: '10017',
|
||||
description: '参数类型错误',
|
||||
},
|
||||
];
|
||||
description: '参数类型错误'
|
||||
}
|
||||
]
|
||||
|
||||
const CodePage: FC = () =>
|
||||
<Table
|
||||
const CodePage: FC = () => (
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
className='table-border border-b-0 rounded'
|
||||
dataSource={data?.map((item, index) => ({...item, key: index})) || []}
|
||||
columns={columns}
|
||||
className="table-border border-b-0 rounded"
|
||||
dataSource={data?.map((item, index) => ({ ...item, key: index })) || []}
|
||||
pagination={false}
|
||||
/>;
|
||||
/>
|
||||
)
|
||||
|
||||
export default CodePage;
|
||||
export default CodePage
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
|
||||
import { useState,FC } from 'react';
|
||||
import { Tooltip, Button } from 'antd';
|
||||
import useCopyToClipboard from '@common/hooks/copy';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { useState, FC } from 'react'
|
||||
import { Tooltip, Button } from 'antd'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
type AddressItem = {
|
||||
expand?: boolean;
|
||||
[key: string]: unknown;
|
||||
expand?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type CopyAddrListProps = {
|
||||
addrItem: AddressItem;
|
||||
onAddrItemChange?: (addrItem: AddressItem) => void;
|
||||
keyName: string;
|
||||
type CopyAddrListProps = {
|
||||
addrItem: AddressItem
|
||||
onAddrItemChange?: (addrItem: AddressItem) => void
|
||||
keyName: string
|
||||
}
|
||||
|
||||
const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyName }) => {
|
||||
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem);
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem)
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
|
||||
const toggleExpand = () => {
|
||||
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand };
|
||||
setLocalAddrItem(updatedAddrItem);
|
||||
onAddrItemChange?.(updatedAddrItem);
|
||||
};
|
||||
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand }
|
||||
setLocalAddrItem(updatedAddrItem)
|
||||
onAddrItemChange?.(updatedAddrItem)
|
||||
}
|
||||
|
||||
const renderTooltipTitle = () => {
|
||||
// 假设keyName对应的值是一个字符串数组
|
||||
const addresses:string[] = localAddrItem[keyName] as string[]
|
||||
const addresses: string[] = localAddrItem[keyName] as string[]
|
||||
return (
|
||||
<div>
|
||||
{addresses?.map((addr, index) => (
|
||||
@@ -36,29 +35,41 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const renderAddresses = () => {
|
||||
if (!localAddrItem.expand) {
|
||||
return (
|
||||
<span className="overflow-ellipsis w-full inline-block overflow-hidden align-middle">
|
||||
<Tooltip title={renderTooltipTitle}>
|
||||
<span className='flex items-center'>
|
||||
<span className={`overflow-ellipsis inline-block overflow-hidden align-middle ${((localAddrItem[keyName] as string[]).length > 1) ? 'w-5/6' : 'w-full'}`}>
|
||||
<span className="flex items-center">
|
||||
<span
|
||||
className={`overflow-ellipsis inline-block overflow-hidden align-middle ${(localAddrItem[keyName] as string[]).length > 1 ? 'w-5/6' : 'w-full'}`}
|
||||
>
|
||||
{(localAddrItem[keyName] as string[]).join(',')}
|
||||
</span>
|
||||
{(localAddrItem[keyName] as string[]).length === 1 && (
|
||||
<Button type="primary" className="border-none ant-typography-copy text-theme hover:text-A_HOVER " ghost onClick={() => copyToClipboard((localAddrItem[keyName] as string))} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
|
||||
<Button
|
||||
type="primary"
|
||||
className="border-none ant-typography-copy text-theme hover:text-A_HOVER "
|
||||
ghost
|
||||
onClick={() => copyToClipboard(localAddrItem[keyName] as string)}
|
||||
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
{(localAddrItem[keyName] as string[]).length !== 1 && (
|
||||
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="zhankai" style={{marginTop:'4px'}}></iconpark-icon>} onClick={toggleExpand} />
|
||||
<Button
|
||||
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
|
||||
icon={<iconpark-icon name="zhankai" style={{ marginTop: '4px' }}></iconpark-icon>}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center justify-between">
|
||||
@@ -66,21 +77,28 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
|
||||
{(localAddrItem[keyName] as string[])?.map((addr: string, index: number) => (
|
||||
<div key={index} className="block w-full">
|
||||
<span className="leading-6">{addr}</span>
|
||||
<Button type="primary" className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER" ghost onClick={() => copyToClipboard(addr)} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
|
||||
<Button
|
||||
type="primary"
|
||||
className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER"
|
||||
ghost
|
||||
onClick={() => copyToClipboard(addr)}
|
||||
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="shouqi-2"></iconpark-icon>} onClick={toggleExpand} />
|
||||
<Button
|
||||
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
|
||||
icon={<iconpark-icon name="shouqi-2"></iconpark-icon>}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderAddresses()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <div>{renderAddresses()}</div>
|
||||
}
|
||||
|
||||
export default CopyAddrList;
|
||||
export default CopyAddrList
|
||||
|
||||
@@ -1,59 +1,84 @@
|
||||
|
||||
import { Button, Drawer, DrawerProps, Space } from "antd";
|
||||
import WithPermission from "./WithPermission";
|
||||
import { useEffect, useState } from "react";
|
||||
import { $t } from '@common/locales';
|
||||
import { Button, Drawer, DrawerProps, Space } from 'antd'
|
||||
import WithPermission from './WithPermission'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export type DrawerWithFooterProps = DrawerProps & {
|
||||
onSubmit?: () => Promise<boolean|string>|undefined
|
||||
submitAccess?: string
|
||||
submitDisabled?:boolean
|
||||
onClose?:()=>void
|
||||
showLastStep?:boolean
|
||||
onLastStep?:()=>void
|
||||
notAutoClose?:boolean
|
||||
showOkBtn?:boolean
|
||||
extraBtn?:React.ReactNode
|
||||
okBtnTitle?:string
|
||||
cancelBtnTitle?:string
|
||||
onSubmit?: () => Promise<boolean | string> | undefined
|
||||
submitAccess?: string
|
||||
submitDisabled?: boolean
|
||||
onClose?: () => void
|
||||
showLastStep?: boolean
|
||||
onLastStep?: () => void
|
||||
notAutoClose?: boolean
|
||||
showOkBtn?: boolean
|
||||
extraBtn?: React.ReactNode
|
||||
okBtnTitle?: string
|
||||
cancelBtnTitle?: string
|
||||
}
|
||||
export function DrawerWithFooter(props:DrawerWithFooterProps){
|
||||
const {children,title,placement='right',onClose,onSubmit,submitDisabled = false,okBtnTitle= $t('提交'),cancelBtnTitle,open,submitAccess,showLastStep,onLastStep,notAutoClose,showOkBtn=true,extraBtn} = props
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||
const handlerSubmit = ()=>{
|
||||
setSubmitLoading(true)
|
||||
onSubmit?.()?.then(()=>{!notAutoClose && onClose?.()}).finally(()=>{setSubmitLoading(false)})
|
||||
}
|
||||
export function DrawerWithFooter(props: DrawerWithFooterProps) {
|
||||
const {
|
||||
children,
|
||||
title,
|
||||
placement = 'right',
|
||||
onClose,
|
||||
onSubmit,
|
||||
submitDisabled = false,
|
||||
okBtnTitle = $t('提交'),
|
||||
cancelBtnTitle,
|
||||
open,
|
||||
submitAccess,
|
||||
showLastStep,
|
||||
onLastStep,
|
||||
notAutoClose,
|
||||
showOkBtn = true,
|
||||
extraBtn
|
||||
} = props
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||
const handlerSubmit = () => {
|
||||
setSubmitLoading(true)
|
||||
onSubmit?.()
|
||||
?.then(() => {
|
||||
!notAutoClose && onClose?.()
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{!open && setSubmitLoading(false)},[open])
|
||||
return (<>
|
||||
<Drawer
|
||||
{...props}
|
||||
push={false}
|
||||
title={title}
|
||||
placement={placement}
|
||||
width="60%"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
classNames={
|
||||
{footer:'text-right'}
|
||||
}
|
||||
footer={
|
||||
<Space className="flex flex-row-reverse" style={{}}>
|
||||
{showOkBtn && <WithPermission access={submitAccess}>
|
||||
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
|
||||
{ okBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>}
|
||||
{ showLastStep && <Button onClick={onLastStep ?? onClose}> { $t('上一步')}</Button>}
|
||||
{ extraBtn }
|
||||
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消'):$t('关闭'))}</Button>
|
||||
</Space>
|
||||
}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
</>)
|
||||
}
|
||||
useEffect(() => {
|
||||
!open && setSubmitLoading(false)
|
||||
}, [open])
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
{...props}
|
||||
push={false}
|
||||
title={title}
|
||||
placement={placement}
|
||||
width="60%"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
classNames={{ footer: 'text-right' }}
|
||||
footer={
|
||||
<Space className="flex flex-row-reverse" style={{}}>
|
||||
{showOkBtn && (
|
||||
<WithPermission access={submitAccess}>
|
||||
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
|
||||
{okBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
)}
|
||||
{showLastStep && <Button onClick={onLastStep ?? onClose}> {$t('上一步')}</Button>}
|
||||
{extraBtn}
|
||||
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消') : $t('关闭'))}</Button>
|
||||
</Space>
|
||||
}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,91 +1,93 @@
|
||||
|
||||
import {FC } from 'react';
|
||||
import { Input, Space } from 'antd';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { FC } from 'react'
|
||||
import { Input, Space } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
type KeyValueInput = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type DynamicKeyValueInputProps = {
|
||||
value?: KeyValueInput[];
|
||||
onChange?: (newValue: KeyValueInput[]) => void;
|
||||
};
|
||||
value?: KeyValueInput[]
|
||||
onChange?: (newValue: KeyValueInput[]) => void
|
||||
}
|
||||
|
||||
|
||||
export function transferToList (rawData:unknown):Array<{key:string, value:string}> {
|
||||
const res:Array<{key:string, value:string}> = []
|
||||
if(!rawData)
|
||||
return res
|
||||
const keys:Array<string> = Object.keys(rawData)
|
||||
export function transferToList(rawData: unknown): Array<{ key: string; value: string }> {
|
||||
const res: Array<{ key: string; value: string }> = []
|
||||
if (!rawData) return res
|
||||
const keys: Array<string> = Object.keys(rawData)
|
||||
if (keys?.length > 0) {
|
||||
for (const key of keys) {
|
||||
res.push({ key: key, value: rawData[key] })
|
||||
for (const key of keys) {
|
||||
res.push({ key: key, value: rawData[key] })
|
||||
}
|
||||
return [...res, { key: '', value: '' }]
|
||||
}
|
||||
return [...res, { key: '', value: '' }]
|
||||
}
|
||||
return [{ key: '', value: '' }]
|
||||
return [{ key: '', value: '' }]
|
||||
}
|
||||
|
||||
export function transferToMap (rawData:Array<{key:string, value:string}>):{[key:string]:string} {
|
||||
const res:{[key:string]:string} = {}
|
||||
export function transferToMap(rawData: Array<{ key: string; value: string }>): { [key: string]: string } {
|
||||
const res: { [key: string]: string } = {}
|
||||
for (const kv of rawData) {
|
||||
if (kv.key && kv.value) { res[kv.key] = kv.value }
|
||||
if (kv.key && kv.value) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({value = [{key:'',value:''}],onChange}) => {
|
||||
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({ value = [{ key: '', value: '' }], onChange }) => {
|
||||
// const [keyValuePairs, setKeyValuePairs] = useState<KeyValueInput[]>([{ key: '', value: '' }]);
|
||||
|
||||
// Define a handler for when the inputs change
|
||||
// Define a handler for when the inputs change
|
||||
const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => {
|
||||
// Create a new array with the updated value
|
||||
const newKeyValuePairs = value ? [...value] : [];
|
||||
const newKeyValuePairs = value ? [...value] : []
|
||||
if (newKeyValuePairs[index]) {
|
||||
newKeyValuePairs[index][type] = newValue;
|
||||
newKeyValuePairs[index][type] = newValue
|
||||
// If we're changing the last input and it's not empty, add a new pair
|
||||
if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) {
|
||||
newKeyValuePairs.push({ key: '', value: '' });
|
||||
newKeyValuePairs.push({ key: '', value: '' })
|
||||
}
|
||||
// Call the onChange handler if it exists
|
||||
onChange?.(newKeyValuePairs);
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const addNewPair = () => {
|
||||
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }];
|
||||
onChange?.(newKeyValuePairs);
|
||||
};
|
||||
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }]
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
|
||||
const removePair = (index: number) => {
|
||||
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || [];
|
||||
onChange?.(newKeyValuePairs);
|
||||
};
|
||||
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || []
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{value && value?.map((pair, index) => (
|
||||
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Input
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
|
||||
style={{ width: 162 }} />
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
style={{ width: 162 }} />
|
||||
{index !== value.length - 1 && (
|
||||
<>
|
||||
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14"/>
|
||||
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14"/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
<>
|
||||
{value &&
|
||||
value?.map((pair, index) => (
|
||||
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Input
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
|
||||
style={{ width: 162 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
style={{ width: 162 }}
|
||||
/>
|
||||
{index !== value.length - 1 && (
|
||||
<>
|
||||
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14" />
|
||||
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14" />
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,116 +1,130 @@
|
||||
import { EditableProTable } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { PageProColumns } from "./PageList";
|
||||
import TableBtnWithPermission from "./TableBtnWithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
import { EditableProTable } from '@ant-design/pro-components'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PageProColumns } from './PageList'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
interface EditableTableProps<T> {
|
||||
configFields: PageProColumns<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
extendsId?:string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
|
||||
configFields: PageProColumns<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?: boolean
|
||||
extendsId?: string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
|
||||
}
|
||||
|
||||
const EditableTable = <T extends { _id: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
extendsId,
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
|
||||
const {state} = useGlobalContext()
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
extendsId
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
|
||||
value?.map((item) => item._id) || ['1234']
|
||||
);
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
|
||||
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]);
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }])
|
||||
}, [value])
|
||||
|
||||
const getNotEmptyValue = (value:unknown)=>{
|
||||
return value
|
||||
}
|
||||
const getNotEmptyValue = (value: unknown) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const translatedColumns = useMemo(()=>configFields.map((x)=>({...x, title:$t(x.title as string)})),[state.language,configFields])
|
||||
const translatedColumns = useMemo(
|
||||
() => configFields.map((x) => ({ ...x, title: $t(x.title as string) })),
|
||||
[state.language, configFields]
|
||||
)
|
||||
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={ {
|
||||
type: 'multiple',
|
||||
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission key="add" btnType="add" onClick={() => {
|
||||
const newId = uuidv4();
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
const newId = uuidv4()
|
||||
const lastRecord:{[k:string]:unknown} = tmpPreData[tmpPreData.length - 1];
|
||||
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if(extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach(field => {
|
||||
newRecord[field] = lastRecord[field];
|
||||
});
|
||||
}
|
||||
tmpPreData.splice(Number(config.index) + 1, 0,newRecord);
|
||||
onChange?.(getNotEmptyValue(tmpPreData));
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>([...prev,newId]))
|
||||
}}
|
||||
btnTitle="增加"/>,
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission
|
||||
key="add"
|
||||
btnType="add"
|
||||
onClick={() => {
|
||||
const newId = uuidv4()
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
const newId = uuidv4()
|
||||
const lastRecord: { [k: string]: unknown } = tmpPreData[tmpPreData.length - 1]
|
||||
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
|
||||
|
||||
(config.index !== configurations.length - 1 )&& <TableBtnWithPermission key="remove" btnType="remove" btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
tmpPreData.splice(Number(config.index), 1);
|
||||
onChange?.(tmpPreData);
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
|
||||
}}/>,,
|
||||
];
|
||||
},
|
||||
onValuesChange: (record, recordList) => {
|
||||
if(record._id === recordList[recordList.length - 1]._id){
|
||||
const newId = uuidv4()
|
||||
const lastRecord:{[k:string]:unknown} = recordList[recordList.length - 1];
|
||||
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if(extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach(field => {
|
||||
newRecord[field] = lastRecord[field];
|
||||
});
|
||||
}
|
||||
|
||||
recordList = ([...recordList, newRecord as T]);
|
||||
setEditableRowKeys((prev)=>[...prev, newId])
|
||||
}
|
||||
setConfigurations(recordList);
|
||||
onChange?.(recordList);
|
||||
},
|
||||
onChange: setEditableRowKeys,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if (extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach((field) => {
|
||||
newRecord[field] = lastRecord[field]
|
||||
})
|
||||
}
|
||||
tmpPreData.splice(Number(config.index) + 1, 0, newRecord)
|
||||
onChange?.(getNotEmptyValue(tmpPreData))
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => [...prev, newId])
|
||||
}}
|
||||
btnTitle="增加"
|
||||
/>,
|
||||
|
||||
export default EditableTable;
|
||||
config.index !== configurations.length - 1 && (
|
||||
<TableBtnWithPermission
|
||||
key="remove"
|
||||
btnType="remove"
|
||||
btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
tmpPreData.splice(Number(config.index), 1)
|
||||
onChange?.(tmpPreData)
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
|
||||
}}
|
||||
/>
|
||||
),
|
||||
,
|
||||
]
|
||||
},
|
||||
onValuesChange: (record, recordList) => {
|
||||
if (record._id === recordList[recordList.length - 1]._id) {
|
||||
const newId = uuidv4()
|
||||
const lastRecord: { [k: string]: unknown } = recordList[recordList.length - 1]
|
||||
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if (extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach((field) => {
|
||||
newRecord[field] = lastRecord[field]
|
||||
})
|
||||
}
|
||||
|
||||
recordList = [...recordList, newRecord as T]
|
||||
setEditableRowKeys((prev) => [...prev, newId])
|
||||
}
|
||||
setConfigurations(recordList)
|
||||
onChange?.(recordList)
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTable
|
||||
|
||||
@@ -1,105 +1,119 @@
|
||||
import { EditableFormInstance, EditableProTable } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useMemo, useRef, MutableRefObject } from "react";
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { PageProColumns } from "./PageList";
|
||||
import TableBtnWithPermission from "./TableBtnWithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { Form } from "antd";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
import { EditableFormInstance, EditableProTable } from '@ant-design/pro-components'
|
||||
import { useState, useEffect, useMemo, useRef, MutableRefObject } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PageProColumns } from './PageList'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { Form } from 'antd'
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
interface EditableTableProps<T> {
|
||||
configFields: PageProColumns<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
getFromRef?:(form:MutableRefObject<EditableFormInstance<T> | undefined>)=>void
|
||||
configFields: PageProColumns<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?: boolean
|
||||
getFromRef?: (form: MutableRefObject<EditableFormInstance<T> | undefined>) => void
|
||||
}
|
||||
|
||||
const EditableTableNotAutoGen = <T extends { _id: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
getFromRef
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
|
||||
const {state} = useGlobalContext()
|
||||
const form =useRef<EditableFormInstance<T>>();
|
||||
const [tableForm] = Form.useForm();
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
|
||||
value?.map((item) => item._id) || ['1234']
|
||||
);
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
getFromRef
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
|
||||
const { state } = useGlobalContext()
|
||||
const form = useRef<EditableFormInstance<T>>()
|
||||
const [tableForm] = Form.useForm()
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
|
||||
|
||||
useEffect(()=>{
|
||||
getFromRef?.(form)
|
||||
},[form])
|
||||
useEffect(() => {
|
||||
getFromRef?.(form)
|
||||
}, [form])
|
||||
|
||||
useEffect(() => {
|
||||
const newValue = value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]
|
||||
setConfigurations(newValue);
|
||||
setTimeout(()=>validateForm(),1000)
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
const newValue = value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }]
|
||||
setConfigurations(newValue)
|
||||
setTimeout(() => validateForm(), 1000)
|
||||
}, [value])
|
||||
|
||||
const validateForm = async ()=>{
|
||||
await tableForm.validateFields();
|
||||
}
|
||||
const validateForm = async () => {
|
||||
await tableForm.validateFields()
|
||||
}
|
||||
|
||||
const translatedColumns = useMemo(()=>configFields.map((x)=>(
|
||||
{...x,
|
||||
title:$t(x.title as string),
|
||||
formItemProps:{
|
||||
...(x. formItemProps || {}),
|
||||
rules:[...(x.formItemProps?.rules || []).map((r:Record<string, string>)=>{
|
||||
if(r.message){
|
||||
r.message = $t(r.message)
|
||||
}
|
||||
return r
|
||||
})],
|
||||
}})),[state.language,configFields])
|
||||
|
||||
const debouncedOnChange = useMemo(() => debounce((value) => {
|
||||
onChange?.(value);
|
||||
}, 500), [onChange]);
|
||||
const translatedColumns = useMemo(
|
||||
() =>
|
||||
configFields.map((x) => ({
|
||||
...x,
|
||||
title: $t(x.title as string),
|
||||
formItemProps: {
|
||||
...(x.formItemProps || {}),
|
||||
rules: [
|
||||
...(x.formItemProps?.rules || []).map((r: Record<string, string>) => {
|
||||
if (r.message) {
|
||||
r.message = $t(r.message)
|
||||
}
|
||||
return r
|
||||
})
|
||||
]
|
||||
}
|
||||
})),
|
||||
[state.language, configFields]
|
||||
)
|
||||
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
onChange={debouncedOnChange}
|
||||
controlled={true}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
editableFormRef={form}
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={ {
|
||||
type: 'multiple',
|
||||
form: tableForm,
|
||||
// errorType:'default',
|
||||
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission key="delete" btnType="delete" btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
tmpPreData.splice(Number(config.index), 1);
|
||||
onChange?.(tmpPreData);
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
|
||||
}}/>,
|
||||
];
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const debouncedOnChange = useMemo(
|
||||
() =>
|
||||
debounce((value) => {
|
||||
onChange?.(value)
|
||||
}, 500),
|
||||
[onChange]
|
||||
)
|
||||
|
||||
export default EditableTableNotAutoGen;
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
onChange={debouncedOnChange}
|
||||
controlled={true}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
editableFormRef={form}
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
form: tableForm,
|
||||
// errorType:'default',
|
||||
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
tmpPreData.splice(Number(config.index), 1)
|
||||
onChange?.(tmpPreData)
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
|
||||
}}
|
||||
/>
|
||||
]
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTableNotAutoGen
|
||||
|
||||
@@ -1,165 +1,195 @@
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import WithPermission from './WithPermission';
|
||||
import { $t } from '@common/locales';
|
||||
import { COLUMNS_TITLE, VALIDATE_MESSAGE } from '@common/const/const';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import TableBtnWithPermission from './TableBtnWithPermission';
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import WithPermission from './WithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { COLUMNS_TITLE } from '@common/const/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
|
||||
export interface ConfigField<T> {
|
||||
title: string;
|
||||
key: keyof T;
|
||||
component: React.ReactNode;
|
||||
renderText?: (value: unknown, record: T) => string;
|
||||
required?: boolean;
|
||||
ellipsis?:boolean
|
||||
unRender?:(form:FormInstance)=>boolean
|
||||
title: string
|
||||
key: keyof T
|
||||
component: React.ReactNode
|
||||
renderText?: (value: unknown, record: T) => string
|
||||
required?: boolean
|
||||
ellipsis?: boolean
|
||||
unRender?: (form: FormInstance) => boolean
|
||||
}
|
||||
|
||||
interface EditableTableWithModalProps<T> {
|
||||
configFields: ConfigField<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
configFields: ConfigField<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
tableProps?: TableProps<T>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const EditableTableWithModal = <T extends { _id?: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
tableProps,
|
||||
disabled,
|
||||
className
|
||||
}: EditableTableWithModalProps<T>) => {
|
||||
const [form] = Form.useForm<FormInstance>();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [configurations, setConfigurations] = useState<T[]>(value ||[]);
|
||||
const [editingConfig, setEditingConfig] = useState<T | null>(null);
|
||||
const {state} = useGlobalContext()
|
||||
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
tableProps,
|
||||
disabled,
|
||||
className
|
||||
}: EditableTableWithModalProps<T>) => {
|
||||
const [form] = Form.useForm<FormInstance>()
|
||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||
const [configurations, setConfigurations] = useState<T[]>(value || [])
|
||||
const [editingConfig, setEditingConfig] = useState<T | null>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
|
||||
|
||||
const showModal = (config?: T) => {
|
||||
if (config) {
|
||||
form.setFieldsValue(config as Record<string, unknown>);
|
||||
setEditingConfig(config);
|
||||
const showModal = (config?: T) => {
|
||||
if (config) {
|
||||
form.setFieldsValue(config as Record<string, unknown>)
|
||||
setEditingConfig(config)
|
||||
} else {
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
}
|
||||
setIsModalVisible(true)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false)
|
||||
}
|
||||
|
||||
const handleDelete = (_id: string) => {
|
||||
const newConfigurations = configurations.filter((config) => config._id !== _id)
|
||||
setConfigurations(newConfigurations)
|
||||
onChange?.(newConfigurations)
|
||||
}
|
||||
|
||||
const handleOk = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
let newConfigurations = [...configurations]
|
||||
if (editingConfig && editingConfig._id) {
|
||||
newConfigurations = newConfigurations?.map((config) =>
|
||||
config._id === editingConfig._id ? { ...config, ...values } : config
|
||||
)
|
||||
} else {
|
||||
form.resetFields();
|
||||
setEditingConfig(null);
|
||||
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>
|
||||
newConfigurations.push(newConfig as T)
|
||||
}
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
setConfigurations(newConfigurations)
|
||||
onChange?.(newConfigurations)
|
||||
setIsModalVisible(false)
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log('Validate Failed:', info)
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [])
|
||||
}, [value])
|
||||
|
||||
const handleDelete = (_id: string) => {
|
||||
const newConfigurations = configurations.filter(config => config._id !== _id);
|
||||
setConfigurations(newConfigurations);
|
||||
onChange?.(newConfigurations);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
let newConfigurations = [...configurations];
|
||||
if (editingConfig && editingConfig._id) {
|
||||
newConfigurations = newConfigurations?.map(config =>
|
||||
config._id === editingConfig._id ? { ...config, ...values } : config
|
||||
);
|
||||
} else {
|
||||
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>;
|
||||
newConfigurations.push(newConfig as T);
|
||||
}
|
||||
setConfigurations(newConfigurations);
|
||||
onChange?.(newConfigurations);
|
||||
setIsModalVisible(false);
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []);
|
||||
}, [value]);
|
||||
|
||||
const columns = useMemo(()=>[
|
||||
...configFields.map(({ title, key, renderText }) => ({
|
||||
title:$t(title),
|
||||
dataIndex: key as string,
|
||||
key: key as string,
|
||||
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
|
||||
ellipsis:true
|
||||
})),
|
||||
...(disabled ? []:[{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'action',
|
||||
btnNums:2,
|
||||
render: (_: unknown, record: T) => (
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
...configFields.map(({ title, key, renderText }) => ({
|
||||
title: $t(title),
|
||||
dataIndex: key as string,
|
||||
key: key as string,
|
||||
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
|
||||
ellipsis: true
|
||||
})),
|
||||
...(disabled
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'action',
|
||||
btnNums: 2,
|
||||
render: (_: unknown, record: T) => (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<TableBtnWithPermission key="add" disabled={disabled} btnType="edit" onClick={()=>{showModal(record)}} btnTitle='编辑'/>
|
||||
<div className="flex items-center">
|
||||
<TableBtnWithPermission
|
||||
key="add"
|
||||
disabled={disabled}
|
||||
btnType="edit"
|
||||
onClick={() => {
|
||||
showModal(record)
|
||||
}}
|
||||
btnTitle="编辑"
|
||||
/>
|
||||
<Divider key="div1" type="vertical" />
|
||||
<TableBtnWithPermission key="delete" disabled={disabled} btnType="delete" onClick={()=>{handleDelete(record._id || '')}} btnTitle='删除'/>
|
||||
</div>
|
||||
<TableBtnWithPermission
|
||||
key="delete"
|
||||
disabled={disabled}
|
||||
btnType="delete"
|
||||
onClick={() => {
|
||||
handleDelete(record._id || '')
|
||||
}}
|
||||
btnTitle="删除"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
}] )
|
||||
],[state.language, disabled, configFields])
|
||||
)
|
||||
}
|
||||
])
|
||||
],
|
||||
[state.language, disabled, configFields]
|
||||
)
|
||||
|
||||
|
||||
const formItems = useMemo(()=>{
|
||||
return configFields.map(({ title,key, component, required,unRender }) => {
|
||||
return (
|
||||
unRender && unRender(formsValue) ? null :
|
||||
<Form.Item
|
||||
label={$t(title as string)}
|
||||
name={key as string}
|
||||
rules={[{ required}]}
|
||||
>
|
||||
{component}
|
||||
</Form.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
,[formsValue])
|
||||
const formItems = useMemo(() => {
|
||||
return configFields.map(({ title, key, component, required, unRender }) => {
|
||||
return unRender && unRender(formsValue) ? null : (
|
||||
<Form.Item label={$t(title as string)} name={key as string} rules={[{ required }]}>
|
||||
{component}
|
||||
</Form.Item>
|
||||
)
|
||||
})
|
||||
}, [formsValue])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<Button className="" disabled={disabled} onClick={() => showModal()}>
|
||||
{$t('添加配置')}
|
||||
</Button>
|
||||
)}
|
||||
{configurations.length > 0 && (
|
||||
<Table
|
||||
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`}
|
||||
{...tableProps}
|
||||
dataSource={configurations}
|
||||
size="small"
|
||||
columns={columns}
|
||||
rowKey="_id"
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
|
||||
open={isModalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
maskClosable={false}
|
||||
>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
form={form}
|
||||
name="editableTableWithModal"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
onFieldsChange={() => {
|
||||
setFormsValue(form.getFieldsValue())
|
||||
}}
|
||||
// labelCol={{ span: 7 }}
|
||||
// wrapperCol={{ span: 17}}
|
||||
autoComplete="off"
|
||||
>
|
||||
{formItems}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && <Button className="" disabled={disabled} onClick={() => showModal()}>{$t('添加配置')}</Button>}
|
||||
{configurations.length > 0 &&
|
||||
<Table
|
||||
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`} {...tableProps} dataSource={configurations} size="small" columns={columns} rowKey="_id" pagination={false}/>}
|
||||
<Modal
|
||||
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
|
||||
open={isModalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
maskClosable={false}
|
||||
|
||||
>
|
||||
<WithPermission access=""><Form form={form} name="editableTableWithModal"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
onFieldsChange={(()=>{
|
||||
setFormsValue(form.getFieldsValue())
|
||||
})}
|
||||
// labelCol={{ span: 7 }}
|
||||
// wrapperCol={{ span: 17}}
|
||||
autoComplete="off">
|
||||
{formItems}
|
||||
</Form></WithPermission>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditableTableWithModal;
|
||||
export default EditableTableWithModal
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from 'react'
|
||||
function ErrorBoundary({ children }) {
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("error", (event) => {
|
||||
setError(event.error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h1>An error occurred</h1>
|
||||
<pre>{error.message}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('error', (event) => {
|
||||
setError(event.error)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h1>An error occurred</h1>
|
||||
<pre>{error.message}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
@@ -1,66 +1,108 @@
|
||||
|
||||
import { Button, Tag } from "antd"
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { ArrowLeftOutlined, LeftOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { Button, Tag } from 'antd'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
class InsidePageProps {
|
||||
showBanner?:boolean = true
|
||||
pageTitle:string| React.ReactNode = ''
|
||||
tagList?:Array<{label:string|ReactNode}> = []
|
||||
children:React.ReactNode
|
||||
showBtn?:boolean = false
|
||||
btnTitle?:string = ''
|
||||
description?:string | React.ReactNode= ''
|
||||
onBtnClick?:()=>void
|
||||
backUrl?:string = '/'
|
||||
btnAccess?:string
|
||||
showBorder?:boolean = true
|
||||
className?:string = ''
|
||||
contentClassName?:string=''
|
||||
headerClassName?:string=''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?:boolean = true
|
||||
customBtn?:ReactNode
|
||||
showBanner?: boolean = true
|
||||
pageTitle: string | React.ReactNode = ''
|
||||
tagList?: Array<{ label: string | ReactNode }> = []
|
||||
children: React.ReactNode
|
||||
showBtn?: boolean = false
|
||||
btnTitle?: string = ''
|
||||
description?: string | React.ReactNode = ''
|
||||
onBtnClick?: () => void
|
||||
backUrl?: string = '/'
|
||||
btnAccess?: string
|
||||
showBorder?: boolean = true
|
||||
className?: string = ''
|
||||
contentClassName?: string = ''
|
||||
headerClassName?: string = ''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?: boolean = true
|
||||
customBtn?: ReactNode
|
||||
}
|
||||
|
||||
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',headerClassName='',scrollPage=true,customBtn})=>{
|
||||
const navigate = useNavigate();
|
||||
const InsidePage: FC<InsidePageProps> = ({
|
||||
showBanner = true,
|
||||
pageTitle,
|
||||
tagList,
|
||||
showBtn,
|
||||
btnTitle,
|
||||
btnAccess,
|
||||
description,
|
||||
children,
|
||||
onBtnClick,
|
||||
backUrl,
|
||||
showBorder = true,
|
||||
className = '',
|
||||
contentClassName = '',
|
||||
headerClassName = '',
|
||||
scrollPage = true,
|
||||
customBtn
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const goBack = () => {
|
||||
navigate(backUrl || '/');
|
||||
};
|
||||
return (
|
||||
// <div className="h-full flex flex-col flex-1 overflow-hidden bg-[#f7f8fa]">
|
||||
<div className={`h-full flex flex-col flex-1 overflow-hidden ${className}`}>
|
||||
{ showBanner && <div className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-b-[1px] border-solid border-BORDER' : ''} ${headerClassName}`}>
|
||||
{!pageTitle && !description && !backUrl &&!customBtn ? <></>: <div className="mb-[30px]">
|
||||
{backUrl &&<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button>
|
||||
</div>}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT ">
|
||||
<div className="text-theme text-[26px] ">{pageTitle}</div>
|
||||
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{
|
||||
return ( <Tag key={tag.label as string} bordered={false} >{tag.label}</Tag>)
|
||||
})}
|
||||
</div>
|
||||
{showBtn && <WithPermission access={btnAccess}><Button type="primary" onClick={()=> {
|
||||
onBtnClick&&onBtnClick()
|
||||
}}>{btnTitle}</Button></WithPermission>}
|
||||
{customBtn}
|
||||
</div>
|
||||
<div >
|
||||
{description}
|
||||
</div>
|
||||
</div>}
|
||||
</div>}
|
||||
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>{children}</div>
|
||||
const goBack = () => {
|
||||
navigate(backUrl || '/')
|
||||
}
|
||||
return (
|
||||
<div className={`flex overflow-hidden flex-col flex-1 h-full ${className}`}>
|
||||
{showBanner && (
|
||||
<div
|
||||
className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
|
||||
>
|
||||
{!pageTitle && !description && !backUrl && !customBtn ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="mb-[30px]">
|
||||
{backUrl && (
|
||||
<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}>
|
||||
<ArrowLeftOutlined className="max-h-[14px]" />
|
||||
{$t('返回')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT">
|
||||
<div className="text-theme text-[26px] ">{pageTitle}</div>
|
||||
{tagList &&
|
||||
tagList?.length > 0 &&
|
||||
tagList?.map((tag) => {
|
||||
return (
|
||||
<Tag key={tag.label as string} bordered={false}>
|
||||
{tag.label}
|
||||
</Tag>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{showBtn && (
|
||||
<WithPermission access={btnAccess}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
onBtnClick && onBtnClick()
|
||||
}}
|
||||
>
|
||||
{btnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
)}
|
||||
{customBtn}
|
||||
</div>
|
||||
<div>{description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InsidePage
|
||||
export default InsidePage
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Dropdown, Row, Col, Button } from 'antd';
|
||||
import i18n from '@common/locales';
|
||||
import { memo, useEffect, useMemo } from 'react';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import i18n from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Button, Dropdown } from 'antd'
|
||||
import { memo, useEffect, useMemo } from 'react'
|
||||
|
||||
const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
const { dispatch, state } = useGlobalContext();
|
||||
const { dispatch, state } = useGlobalContext()
|
||||
const items = [
|
||||
{
|
||||
key: 'en-US',
|
||||
@@ -14,7 +14,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
English
|
||||
</Button>
|
||||
),
|
||||
title: 'English',
|
||||
title: 'English'
|
||||
},
|
||||
{
|
||||
key: 'ja-JP',
|
||||
@@ -23,7 +23,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
日本語
|
||||
</Button>
|
||||
),
|
||||
title: '日本語',
|
||||
title: '日本語'
|
||||
},
|
||||
{
|
||||
key: 'zh-TW',
|
||||
@@ -32,7 +32,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
繁體中文
|
||||
</Button>
|
||||
),
|
||||
title: '繁體中文',
|
||||
title: '繁體中文'
|
||||
},
|
||||
{
|
||||
key: 'zh-CN',
|
||||
@@ -41,40 +41,41 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
简体中文
|
||||
</Button>
|
||||
),
|
||||
title: '简体中文',
|
||||
},
|
||||
];
|
||||
title: '简体中文'
|
||||
}
|
||||
]
|
||||
|
||||
const langLabel = useMemo(
|
||||
() => items.find(item => item?.key === state.language)?.title,
|
||||
[state.language]
|
||||
);
|
||||
const langLabel = useMemo(() => items.find((item) => item?.key === state.language)?.title, [state.language])
|
||||
|
||||
useEffect(() => {
|
||||
const savedLang = sessionStorage.getItem('i18nextLng');
|
||||
const browserLang = navigator.language || navigator.userLanguage;
|
||||
if (savedLang) return;
|
||||
const savedLang = i18n.language || sessionStorage.getItem('i18nextLng')
|
||||
if (savedLang && state.language !== savedLang) {
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang })
|
||||
} else if (!savedLang) {
|
||||
const browserLang = navigator.language
|
||||
const supportedLang = items.find((item) => item.key === browserLang) ? browserLang : 'zh-CN'
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: supportedLang })
|
||||
i18n.changeLanguage(supportedLang)
|
||||
}
|
||||
}, [])
|
||||
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang });
|
||||
}, []);
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items,
|
||||
style: { minWidth: '80px' },
|
||||
onClick: e => {
|
||||
const { key } = e;
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: key });
|
||||
i18n.changeLanguage(key);
|
||||
},
|
||||
onClick: (e) => {
|
||||
const { key } = e
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: key })
|
||||
i18n.changeLanguage(key)
|
||||
sessionStorage.setItem('i18nextLng', key)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className={`border-none ${
|
||||
mode === 'dark'
|
||||
? 'text-[#333] hover:text-[#333333b3]'
|
||||
: 'text-[#ffffffb3] hover:text-[#fff] '
|
||||
mode === 'dark' ? 'text-[#333] hover:text-[#333333b3]' : 'text-[#ffffffb3] hover:text-[#fff] '
|
||||
}`}
|
||||
type="default"
|
||||
ghost
|
||||
@@ -86,6 +87,6 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
</span>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
export default memo(LanguageSetting);
|
||||
)
|
||||
}
|
||||
export default memo(LanguageSetting)
|
||||
|
||||
@@ -1,171 +1,199 @@
|
||||
|
||||
import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from "antd";
|
||||
import { DataNode } from "antd/es/tree";
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
||||
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { ColumnsType } from "antd/es/table";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from 'antd'
|
||||
import { DataNode } from 'antd/es/tree'
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import { ColumnsType } from 'antd/es/table'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
export type TransferTableProps<T> = {
|
||||
request?:(k?:string)=>Promise<{data:T[],success:boolean}>
|
||||
request?: (k?: string) => Promise<{ data: T[]; success: boolean }>
|
||||
columns: ColumnsType<T>
|
||||
primaryKey:string
|
||||
onSelect:(selectedData:string[])=>void
|
||||
tableType?:'member'|'api'
|
||||
disabledData:string[]
|
||||
searchPlaceholder?:string
|
||||
primaryKey: string
|
||||
onSelect: (selectedData: string[]) => void
|
||||
tableType?: 'member' | 'api'
|
||||
disabledData: string[]
|
||||
searchPlaceholder?: string
|
||||
}
|
||||
|
||||
export type TransferTableHandle<T> = {
|
||||
selectedRowKeys: () => React.Key[];
|
||||
selectedRowKeys: () => React.Key[]
|
||||
}
|
||||
|
||||
interface TreeTransferProps {
|
||||
dataSource: TreeDataNode[];
|
||||
targetKeys: TransferProps['targetKeys'];
|
||||
onChange: TransferProps['onChange'];
|
||||
dataSource: TreeDataNode[]
|
||||
targetKeys: TransferProps['targetKeys']
|
||||
onChange: TransferProps['onChange']
|
||||
}
|
||||
|
||||
|
||||
const generateTree = (
|
||||
treeNodes: TreeDataNode[] = [],
|
||||
checkedKeys: TreeTransferProps['targetKeys'] = [],
|
||||
checkedKeys: TreeTransferProps['targetKeys'] = [],
|
||||
filterUnchecked: boolean = false,
|
||||
disabledData:string[],
|
||||
filteredItems?:Set<string>
|
||||
disabledData: string[],
|
||||
filteredItems?: Set<string>
|
||||
): TreeDataNode[] => {
|
||||
const checkedKeysSet = new Set(checkedKeys);
|
||||
const checkedKeysSet = new Set(checkedKeys)
|
||||
return treeNodes
|
||||
.map(({ children, ...props }) => {
|
||||
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems);
|
||||
const isDisabled = (!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1)
|
||||
? true
|
||||
: (filterUnchecked ? false : checkedKeysSet.has(props.id as string));
|
||||
const hasEnabledChild = childNodes.some(node => !node.disabled);
|
||||
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems)
|
||||
const isDisabled =
|
||||
!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1
|
||||
? true
|
||||
: filterUnchecked
|
||||
? false
|
||||
: checkedKeysSet.has(props.id as string)
|
||||
const hasEnabledChild = childNodes.some((node) => !node.disabled)
|
||||
|
||||
return {
|
||||
...props,
|
||||
title: <span className="w-full truncate ml-[4px] block">{props.name}</span>,
|
||||
key: props.id,
|
||||
disabled: isDisabled && !hasEnabledChild,
|
||||
children: childNodes,
|
||||
};
|
||||
})
|
||||
.filter(node => {
|
||||
let res:boolean= true
|
||||
if(filterUnchecked){
|
||||
res =(!disabledData || disabledData.indexOf(node.key as string) === -1) && (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0) )
|
||||
children: childNodes
|
||||
}
|
||||
|
||||
if(filterUnchecked && filteredItems &&((filteredItems.size && !filteredItems.has(node.key as string))&& !(node.children && node.children.length > 0) )){
|
||||
})
|
||||
.filter((node) => {
|
||||
let res: boolean = true
|
||||
if (filterUnchecked) {
|
||||
res =
|
||||
(!disabledData || disabledData.indexOf(node.key as string) === -1) &&
|
||||
(checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0))
|
||||
}
|
||||
|
||||
if (
|
||||
filterUnchecked &&
|
||||
filteredItems &&
|
||||
filteredItems.size &&
|
||||
!filteredItems.has(node.key as string) &&
|
||||
!(node.children && node.children.length > 0)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return res
|
||||
}
|
||||
)
|
||||
};
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
const MemberTransfer = forwardRef<
|
||||
TransferTableHandle<{ [k: string]: unknown }>,
|
||||
TransferTableProps<{ [k: string]: unknown }>
|
||||
>(<T extends { [k: string]: unknown }>(props: TransferTableProps<T>, ref: Ref<TransferTableHandle<T>>) => {
|
||||
const { request, columns, primaryKey, onSelect, tableType, disabledData = [], searchPlaceholder } = props
|
||||
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([])
|
||||
const [dataSource, setDataSource] = useState<DataNode[]>([])
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const { state } = useGlobalContext()
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([])
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
useEffect(() => {
|
||||
setTargetKeys(disabledData)
|
||||
}, [disabledData])
|
||||
|
||||
const MemberTransfer= forwardRef<TransferTableHandle<{[k:string]:unknown}>, TransferTableProps<{[k:string]:unknown}>>(
|
||||
<T extends {[k:string]:unknown}>(props: TransferTableProps<T>, ref:Ref<TransferTableHandle<T>>) => {
|
||||
const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props
|
||||
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([]);
|
||||
const [dataSource, setDataSource] = useState<DataNode[] >([])
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const {state} = useGlobalContext()
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
useEffect(()=>{
|
||||
setTargetKeys(disabledData)
|
||||
},[disabledData])
|
||||
useImperativeHandle(ref, () => ({
|
||||
selectedRowKeys: () => targetKeys
|
||||
}))
|
||||
|
||||
useImperativeHandle(ref, () =>({
|
||||
selectedRowKeys: () => targetKeys,}))
|
||||
|
||||
const translatedDataSource = useMemo(()=>{
|
||||
|
||||
const translatedDataSource = useMemo(() => {
|
||||
const loop = (data: DataNode[]): DataNode[] =>
|
||||
data?.map((item) => {
|
||||
const strTitle:string = item.name === '所有成员' ? $t(item.name) as string : item.name as string;
|
||||
const index = strTitle.indexOf(searchWord);
|
||||
const beforeStr = strTitle.substring(0, index);
|
||||
const afterStr = strTitle.slice(index + searchWord.length);
|
||||
const title =
|
||||
index > -1 ? (
|
||||
<span className='w-[calc(100%-16px)] truncate' title={strTitle}>
|
||||
{beforeStr}
|
||||
<span className="text-theme">{searchWord}</span>
|
||||
{afterStr}
|
||||
</span>
|
||||
) : (
|
||||
<span className='w-[calc(100%-16px)] truncate' title={`${strTitle}`}>{strTitle}</span>
|
||||
)
|
||||
if (item.children) {
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
disableCheckbox:disabledData.indexOf(item.key as string) !== -1,
|
||||
icon:<ApartmentOutlined />,
|
||||
children: loop(item.children as T[]) };
|
||||
}
|
||||
|
||||
const strTitle: string = item.name === '所有成员' ? ($t(item.name) as string) : (item.name as string)
|
||||
const index = strTitle.indexOf(searchWord)
|
||||
const beforeStr = strTitle.substring(0, index)
|
||||
const afterStr = strTitle.slice(index + searchWord.length)
|
||||
const title =
|
||||
index > -1 ? (
|
||||
<span className="w-[calc(100%-16px)] truncate" title={strTitle}>
|
||||
{beforeStr}
|
||||
<span className="text-theme">{searchWord}</span>
|
||||
{afterStr}
|
||||
</span>
|
||||
) : (
|
||||
<span className="w-[calc(100%-16px)] truncate" title={`${strTitle}`}>
|
||||
{strTitle}
|
||||
</span>
|
||||
)
|
||||
if (item.children) {
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
icon:<UserOutlined />,
|
||||
isLeaf:true,
|
||||
disableCheckbox:disabledData.indexOf(item.key as string) !== -1
|
||||
};
|
||||
});
|
||||
return loop(dataSource);
|
||||
},[dataSource, state.language, searchWord])
|
||||
...item,
|
||||
title,
|
||||
disableCheckbox: disabledData.indexOf(item.key as string) !== -1,
|
||||
icon: <ApartmentOutlined />,
|
||||
children: loop(item.children as T[])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
icon: <UserOutlined />,
|
||||
isLeaf: true,
|
||||
disableCheckbox: disabledData.indexOf(item.key as string) !== -1
|
||||
}
|
||||
})
|
||||
return loop(dataSource)
|
||||
}, [dataSource, state.language, searchWord])
|
||||
|
||||
const getInitExpandKeys = (data:T[], expandKeys:string[] = [])=>{
|
||||
data.forEach((item)=>{
|
||||
if(item.children?.length){
|
||||
const getInitExpandKeys = (data: T[], expandKeys: string[] = []) => {
|
||||
data.forEach((item) => {
|
||||
if (item.children?.length) {
|
||||
expandKeys.push(item.key as string)
|
||||
getInitExpandKeys(item.children,expandKeys)
|
||||
getInitExpandKeys(item.children, expandKeys)
|
||||
}
|
||||
})
|
||||
return expandKeys
|
||||
}
|
||||
|
||||
const getDataSource = ()=>{
|
||||
setLoading(true)
|
||||
request && request().then((res)=>{
|
||||
const {data,success} = res
|
||||
setDataSource(success? data : [])
|
||||
setExpandedKeys(getInitExpandKeys(success? data:[]))
|
||||
}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
|
||||
const getDataSource = () => {
|
||||
setLoading(true)
|
||||
request &&
|
||||
request()
|
||||
.then((res) => {
|
||||
const { data, success } = res
|
||||
setDataSource(success ? data : [])
|
||||
setExpandedKeys(getInitExpandKeys(success ? data : []))
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getDataSource()
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={parentRef}>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
|
||||
<Input className="mb-[10px]" placeholder={searchPlaceholder} onChange={(e)=>setSearchWord(e.target.value)} value={searchWord} />
|
||||
<>{ translatedDataSource && translatedDataSource.length > 0 ? <Tree
|
||||
checkable
|
||||
expandedKeys={expandedKeys}
|
||||
checkedKeys={targetKeys}
|
||||
selectable={false}
|
||||
onCheck={(e)=>{setTargetKeys(e);
|
||||
onSelect(((e as string[])?.filter(x=>disabledData.indexOf(x as string) === -1))||[])}}
|
||||
onExpand={setExpandedKeys}
|
||||
treeData={translatedDataSource}
|
||||
blockNode
|
||||
showIcon
|
||||
/>
|
||||
: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/> }</>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div ref={parentRef}>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className="">
|
||||
<Input
|
||||
className="mb-[10px]"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setSearchWord(e.target.value)}
|
||||
value={searchWord}
|
||||
/>
|
||||
<>
|
||||
{translatedDataSource && translatedDataSource.length > 0 ? (
|
||||
<Tree
|
||||
checkable
|
||||
expandedKeys={expandedKeys}
|
||||
checkedKeys={targetKeys}
|
||||
selectable={false}
|
||||
onCheck={(e) => {
|
||||
setTargetKeys(e)
|
||||
onSelect((e as string[])?.filter((x) => disabledData.indexOf(x as string) === -1) || [])
|
||||
}}
|
||||
onExpand={setExpandedKeys}
|
||||
treeData={translatedDataSource}
|
||||
blockNode
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</>
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default MemberTransfer;
|
||||
export default MemberTransfer
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Result, Skeleton } from 'antd';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Result, Skeleton } from 'antd'
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
const [showPage, setShowPage] = useState<boolean>(false)
|
||||
const [showPage, setShowPage] = useState<boolean>(false)
|
||||
|
||||
useEffect(()=>{
|
||||
setTimeout(()=>setShowPage(true), 1000)
|
||||
},[])
|
||||
useEffect(() => {
|
||||
setTimeout(() => setShowPage(true), 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
|
||||
{ showPage ? <Result
|
||||
className='w-full'
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
/> : <Skeleton active /> }
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
|
||||
{showPage ? (
|
||||
<Result className="w-full" status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
|
||||
) : (
|
||||
<Skeleton active />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
export default NotFound
|
||||
|
||||
@@ -1,243 +1,370 @@
|
||||
|
||||
import {Button, Dropdown, Input, MenuProps, TablePaginationConfig} from 'antd';
|
||||
import {ChangeEvent, RefAttributes, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
|
||||
import type {ActionType, ParamsType, ProColumns, ProTableProps} from '@ant-design/pro-components';
|
||||
import {
|
||||
DragSortTable,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import './PageList.module.css'
|
||||
import {SearchOutlined} from "@ant-design/icons";
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components'
|
||||
import { DragSortTable, ProTable } from '@ant-design/pro-components'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { withMinimumDelay } from '@common/utils/ux'
|
||||
import { Button, Dropdown, Input, MenuProps, TablePaginationConfig } from 'antd'
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
|
||||
import { debounce } from 'lodash-es'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission';
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions';
|
||||
import { withMinimumDelay } from '@common/utils/ux';
|
||||
import {
|
||||
ChangeEvent,
|
||||
RefAttributes,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
import './PageList.module.css'
|
||||
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T , ValueType> & {btnNums? : number}
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T, ValueType> & { btnNums?: number }
|
||||
|
||||
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
|
||||
id?:string
|
||||
columns: PageProColumns<T,'text'>[]
|
||||
request?:(params: (ParamsType & {pageSize?: number | undefined, current?: number | undefined, keyword?: string | undefined}), sorter: unknown, filter: unknown)=>Promise<{data:T[], success:boolean}>
|
||||
dropMenu?:MenuProps
|
||||
searchPlaceholder?:string
|
||||
showPagination?:boolean
|
||||
primaryKey?:string
|
||||
addNewBtnTitle?:string
|
||||
addNewBtnAccess?:string
|
||||
tableClickAccess?:string
|
||||
onAddNewBtnClick?:()=>void
|
||||
beforeSearchNode?:React.ReactNode[]
|
||||
onSearchWordChange?:(e:ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?:React.ReactNode[]
|
||||
dragSortKey?:string
|
||||
onDragSortEnd?:(beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?:string
|
||||
dataSource?:T[]
|
||||
onRowClick?:(record:T)=>void
|
||||
showColSetting?:boolean
|
||||
minVirtualHeight?:number
|
||||
besidesTableHeight?:number
|
||||
noTop?:boolean
|
||||
tableClass?:string
|
||||
tableTitleClass?:string
|
||||
addNewBtnWrapperClass?:string
|
||||
delayLoading?:boolean
|
||||
noScroll?:boolean
|
||||
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
|
||||
id?: string
|
||||
columns: PageProColumns<T, 'text'>[]
|
||||
request?: (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sorter: unknown,
|
||||
filter: unknown
|
||||
) => Promise<{ data: T[]; success: boolean }>
|
||||
dropMenu?: MenuProps
|
||||
searchPlaceholder?: string
|
||||
showPagination?: boolean
|
||||
primaryKey?: string
|
||||
addNewBtnTitle?: string
|
||||
addNewBtnAccess?: string
|
||||
tableClickAccess?: string
|
||||
onAddNewBtnClick?: () => void
|
||||
beforeSearchNode?: React.ReactNode[]
|
||||
onSearchWordChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?: React.ReactNode[]
|
||||
dragSortKey?: string
|
||||
onDragSortEnd?: (beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?: string
|
||||
dataSource?: T[]
|
||||
onRowClick?: (record: T) => void
|
||||
showColSetting?: boolean
|
||||
minVirtualHeight?: number
|
||||
besidesTableHeight?: number
|
||||
noTop?: boolean
|
||||
tableClass?: string
|
||||
tableTitleClass?: string
|
||||
addNewBtnWrapperClass?: string
|
||||
delayLoading?: boolean
|
||||
noScroll?: boolean
|
||||
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
|
||||
manualReloadTable?:()=>void
|
||||
manualReloadTable?: () => void
|
||||
}
|
||||
|
||||
|
||||
|
||||
const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChildren<PageListProps<T>>,ref: React.Ref<ActionType>) => {
|
||||
const {id,columns,request,dropMenu,searchPlaceholder,showPagination=true,primaryKey='id',addNewBtnTitle,addNewBtnAccess,tableClickAccess,tableClass,onAddNewBtnClick,beforeSearchNode,onSearchWordChange,manualReloadTable,afterNewBtn,dragSortKey,onDragSortEnd,tableTitle,rowSelection,onChange,dataSource,onRowClick,showColSetting=false,minVirtualHeight,noTop,addNewBtnWrapperClass = '',tableTitleClass,delayLoading = true,besidesTableHeight, noScroll} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight);
|
||||
const [tableWidth, setTableWidth] = useState<number|undefined>(undefined);
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [allowTableClick,setAllowTableClick] = useState<boolean>(false)
|
||||
const {accessData,checkPermission,accessInit,state} = useGlobalContext()
|
||||
const PageList = <T extends Record<string, unknown>>(
|
||||
props: React.PropsWithChildren<PageListProps<T>>,
|
||||
ref: React.Ref<ActionType>
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
columns,
|
||||
request,
|
||||
dropMenu,
|
||||
searchPlaceholder,
|
||||
showPagination = true,
|
||||
primaryKey = 'id',
|
||||
addNewBtnTitle,
|
||||
addNewBtnAccess,
|
||||
tableClickAccess,
|
||||
tableClass,
|
||||
onAddNewBtnClick,
|
||||
beforeSearchNode,
|
||||
onSearchWordChange,
|
||||
manualReloadTable,
|
||||
afterNewBtn,
|
||||
dragSortKey,
|
||||
onDragSortEnd,
|
||||
tableTitle,
|
||||
rowSelection,
|
||||
onChange,
|
||||
dataSource,
|
||||
onRowClick,
|
||||
showColSetting = false,
|
||||
minVirtualHeight,
|
||||
noTop,
|
||||
addNewBtnWrapperClass = '',
|
||||
tableTitleClass,
|
||||
delayLoading = true,
|
||||
besidesTableHeight,
|
||||
noScroll
|
||||
} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight)
|
||||
const [tableWidth, setTableWidth] = useState<number | undefined>(undefined)
|
||||
const actionRef = useRef<ActionType>()
|
||||
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
|
||||
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
|
||||
const [minTableWidth, setMinTableWidth] = useState<number>(0)
|
||||
|
||||
// 使用useImperativeHandle来自定义暴露给父组件的实例值
|
||||
useImperativeHandle(ref, () => actionRef.current!);
|
||||
useImperativeHandle(ref, () => actionRef.current!)
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
actionRef?.current?.reload?.()
|
||||
},[state.language])
|
||||
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!tableClickAccess) return true
|
||||
return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[allowTableClick, accessData,accessInit])
|
||||
}, [state.language])
|
||||
|
||||
useEffect(()=>{
|
||||
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
|
||||
},[accessData])
|
||||
|
||||
const resizeObserverRef = useRef<ResizeObserver |null >(null);
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!tableClickAccess) return true
|
||||
return checkPermission(tableClickAccess as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [allowTableClick, accessData, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
|
||||
}, [accessData])
|
||||
|
||||
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (parentRef.current && !noScroll) {
|
||||
const res = parentRef.current.getBoundingClientRect();
|
||||
const height = res.height - ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) +( besidesTableHeight ?? 0) + 1); // 减去顶部按钮、底部分页、表头高度
|
||||
setTableWidth(minTableWidth - 5> res.width ? minTableWidth : undefined);
|
||||
height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight));
|
||||
const res = parentRef.current.getBoundingClientRect()
|
||||
const height =
|
||||
res.height -
|
||||
((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) + (besidesTableHeight ?? 0) + 1) // 减去顶部按钮、底部分页、表头高度
|
||||
setTableWidth(minTableWidth - 5 > res.width ? minTableWidth : undefined)
|
||||
height &&
|
||||
setTableHeight(
|
||||
minVirtualHeight === undefined ? height : height > minVirtualHeight ? height : minVirtualHeight
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const debouncedHandleResize = debounce(handleResize, 200);
|
||||
const debouncedHandleResize = debounce(handleResize, 200)
|
||||
|
||||
if (!resizeObserverRef.current && !noScroll) {
|
||||
// 创建一个 ResizeObserver 来监听高度变化,只创建一次
|
||||
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize);
|
||||
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize)
|
||||
// 开始监听
|
||||
if (parentRef.current && !minVirtualHeight) {
|
||||
resizeObserverRef.current.observe(parentRef.current);
|
||||
resizeObserverRef.current.observe(parentRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
// 在 minTableWidth 变化时手动触发 handleResize
|
||||
handleResize();
|
||||
handleResize()
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (resizeObserverRef.current) {
|
||||
resizeObserverRef.current.disconnect();
|
||||
resizeObserverRef.current = null;
|
||||
resizeObserverRef.current.disconnect()
|
||||
resizeObserverRef.current = null
|
||||
}
|
||||
};
|
||||
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]); // 将相关依赖项作为 useEffect 的依赖项
|
||||
}
|
||||
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]) // 将相关依赖项作为 useEffect 的依赖项
|
||||
|
||||
|
||||
|
||||
|
||||
const newColumns = useMemo(()=>{
|
||||
let width:number = 0
|
||||
const res = columns?.map(
|
||||
(x, index)=>{
|
||||
const sorter = localStorage.getItem(`${id}_sorter`)
|
||||
const filters = localStorage.getItem(`${id}_filters`)
|
||||
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
|
||||
if(sorter && x.sorter){
|
||||
const sorterObj = JSON.parse(sorter)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
|
||||
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
|
||||
// x.showSorterTooltip = {target:'sorter-icon'}
|
||||
}
|
||||
if(filters && x.filters){
|
||||
const filtersObj = JSON.parse(filters)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
|
||||
x.defaultFilteredValue = filtersObj?.[xName as string]
|
||||
}
|
||||
if((index === columns.length -1 || x.key === 'option') && x.btnNums){
|
||||
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
|
||||
x.width = Math.max(optionWidth, 54)
|
||||
}
|
||||
width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100))
|
||||
return x})
|
||||
setMinTableWidth(width)
|
||||
const newColumns = useMemo(() => {
|
||||
let width: number = 0
|
||||
const res = columns?.map((x, index) => {
|
||||
const sorter = localStorage.getItem(`${id}_sorter`)
|
||||
const filters = localStorage.getItem(`${id}_filters`)
|
||||
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
|
||||
if (sorter && x.sorter) {
|
||||
const sorterObj = JSON.parse(sorter)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
|
||||
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
|
||||
// x.showSorterTooltip = {target:'sorter-icon'}
|
||||
}
|
||||
if (filters && x.filters) {
|
||||
const filtersObj = JSON.parse(filters)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
|
||||
x.defaultFilteredValue = filtersObj?.[xName as string]
|
||||
}
|
||||
if ((index === columns.length - 1 || x.key === 'option') && x.btnNums) {
|
||||
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
|
||||
x.width = Math.max(optionWidth, 54)
|
||||
}
|
||||
width += Number(x.width ?? (x.filters || x.sorter ? 120 : 100))
|
||||
return x
|
||||
})
|
||||
setMinTableWidth(width)
|
||||
return res
|
||||
},[columns])
|
||||
}, [columns])
|
||||
|
||||
const headerTitle = ()=>{
|
||||
const headerTitle = () => {
|
||||
return (
|
||||
<>{
|
||||
tableTitle ? <span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span> : (
|
||||
addNewBtnTitle ? <WithPermission access={addNewBtnAccess} ><Button type="primary" className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`} onClick={onAddNewBtnClick}>{addNewBtnTitle}</Button></WithPermission> : undefined
|
||||
)
|
||||
|
||||
}
|
||||
{afterNewBtn ? afterNewBtn as React.ReactNode[] :undefined}
|
||||
</>
|
||||
)
|
||||
<>
|
||||
{tableTitle ? (
|
||||
<span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span>
|
||||
) : addNewBtnTitle ? (
|
||||
<WithPermission access={addNewBtnAccess}>
|
||||
<Button
|
||||
type="primary"
|
||||
className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`}
|
||||
onClick={onAddNewBtnClick}
|
||||
>
|
||||
{addNewBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
) : undefined}
|
||||
{afterNewBtn ? (afterNewBtn as React.ReactNode[]) : undefined}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const requestWithDelay = (params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined;}, sort: unknown, filter: unknown) => {
|
||||
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false? 0 : undefined);
|
||||
};
|
||||
const getTableActions = () => {
|
||||
return [
|
||||
...(beforeSearchNode ? [beforeSearchNode] : []),
|
||||
...(searchPlaceholder
|
||||
? [
|
||||
<Input
|
||||
key="search-input"
|
||||
className="my-btnbase ml-btnbase"
|
||||
onChange={onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined}
|
||||
onPressEnter={() => {
|
||||
if (manualReloadTable) {
|
||||
manualReloadTable()
|
||||
return
|
||||
}
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reset?.()
|
||||
actionRef.current.reload?.()
|
||||
}
|
||||
}}
|
||||
allowClear
|
||||
placeholder={searchPlaceholder}
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reset?.()
|
||||
actionRef.current.reload?.()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
|
||||
const requestWithDelay = (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sort: unknown,
|
||||
filter: unknown
|
||||
) => {
|
||||
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false ? 0 : undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={parentRef} className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag':''} ${tableClass ?? ''}`}style={{ height: '100%' }}>
|
||||
{dragSortKey? <DragSortTable<T>
|
||||
<div
|
||||
ref={parentRef}
|
||||
className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag' : ''} ${tableClass ?? ''}`}
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
{dragSortKey ? (
|
||||
<DragSortTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
rowKey={primaryKey}
|
||||
search={false}
|
||||
pagination={false}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
request={request}
|
||||
dragSortKey={dragSortKey}
|
||||
onDragSortEnd={onDragSortEnd}
|
||||
scroll={noScroll ? undefined :{ y: tableHeight }}
|
||||
scroll={noScroll ? undefined : { y: tableHeight }}
|
||||
options={{
|
||||
reload: false,
|
||||
density: false,
|
||||
setting: false,
|
||||
setting: false
|
||||
}}
|
||||
headerTitle={
|
||||
headerTitle()
|
||||
}
|
||||
/> : <ProTable<T>
|
||||
toolbar={{
|
||||
actions: getTableActions()
|
||||
}}
|
||||
headerTitle={headerTitle()}
|
||||
/>
|
||||
) : (
|
||||
<ProTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
virtual
|
||||
scroll={noScroll ? undefined : {x:tableWidth,y: tableHeight }}
|
||||
scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
|
||||
size="middle"
|
||||
rowSelection={rowSelection}
|
||||
tableAlertRender={false}
|
||||
tableAlertOptionRender={false}
|
||||
request={request ? requestWithDelay : undefined}
|
||||
toolBarRender={() => [
|
||||
dropMenu ? (<Dropdown
|
||||
key="menu"
|
||||
menu={dropMenu}
|
||||
>
|
||||
<Button>
|
||||
筛选
|
||||
</Button>
|
||||
</Dropdown>):null,
|
||||
dropMenu ? (
|
||||
<Dropdown key="menu" menu={dropMenu}>
|
||||
<Button>筛选</Button>
|
||||
</Dropdown>
|
||||
) : null
|
||||
]}
|
||||
toolbar={{
|
||||
actions:[...[beforeSearchNode],...[searchPlaceholder?<Input className="my-btnbase ml-btnbase" onChange={ onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>manualReloadTable ? manualReloadTable():actionRef.current?.reload?.()} allowClear placeholder={searchPlaceholder} prefix={<SearchOutlined className="cursor-pointer" onClick={()=>{actionRef.current?.reload?.()}}/>}/>:null]],
|
||||
actions: getTableActions()
|
||||
}}
|
||||
options={{
|
||||
reload: false,
|
||||
density: false,
|
||||
setting: showColSetting ? {
|
||||
draggable:false,
|
||||
showListItemOption:false
|
||||
} :false,
|
||||
setting: showColSetting
|
||||
? {
|
||||
draggable: false,
|
||||
showListItemOption: false
|
||||
}
|
||||
: false
|
||||
}}
|
||||
showSorterTooltip={false}
|
||||
columnsState={{persistenceType:'localStorage',persistenceKey:id}}
|
||||
pagination={showPagination ? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size:'default'
|
||||
}:false}
|
||||
columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
onChange={(
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue | null>,
|
||||
sorter: SorterResult<T> | SorterResult<T>[],
|
||||
extra: TableCurrentDataSource<T>
|
||||
) => {
|
||||
localStorage.setItem(`${id}_filters`, JSON.stringify(filters))
|
||||
!Array.isArray(sorter) &&
|
||||
localStorage.setItem(
|
||||
`${id}_sorter`,
|
||||
JSON.stringify({ columnKey: sorter?.columnKey, order: sorter?.order })
|
||||
)
|
||||
onChange?.(pagination, filters, sorter, extra)
|
||||
}}
|
||||
rowKey={primaryKey}
|
||||
onChange={(pagination: TablePaginationConfig, filters: Record<string, FilterValue | null>, sorter: SorterResult<T> | SorterResult<T>[],extra:TableCurrentDataSource<T>) =>{
|
||||
localStorage.setItem(`${id}_filters`,JSON.stringify(filters))
|
||||
!Array.isArray(sorter) && localStorage.setItem(`${id}_sorter`,JSON.stringify({columnKey:sorter?.columnKey, order: sorter?.order}))
|
||||
onChange?.(pagination,filters,sorter,extra)}}
|
||||
dataSource={dataSource}
|
||||
search={false}
|
||||
headerTitle={
|
||||
headerTitle()
|
||||
headerTitle={headerTitle()}
|
||||
onRow={
|
||||
onRowClick && allowTableClick
|
||||
? (record) => ({
|
||||
onClick: () => {
|
||||
onRowClick(record)
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
onRow={onRowClick && allowTableClick ? (record) => ({
|
||||
onClick: () => {
|
||||
onRowClick(record);
|
||||
}
|
||||
}):undefined}
|
||||
rowClassName={()=>onRowClick && allowTableClick ?"cursor-pointer":''}
|
||||
/>}
|
||||
rowClassName={() => (onRowClick && allowTableClick ? 'cursor-pointer' : '')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(PageList) as <T extends Record<string,unknown>>(props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }) => ReturnType<typeof PageList>;
|
||||
export default forwardRef(PageList) as <T extends Record<string, unknown>>(
|
||||
props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }
|
||||
) => ReturnType<typeof PageList>
|
||||
|
||||
@@ -1,95 +1,100 @@
|
||||
import {App, Form, Input, Row, Table} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { PolicyPublishModalHandle, PolicyPublishModalProps } from "@common/const/type";
|
||||
import { App, Form, Input, Row, Table } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { PolicyPublishModalHandle, PolicyPublishModalProps } from '@common/const/type'
|
||||
|
||||
export const PolicyPublishModalContent = forwardRef<PolicyPublishModalHandle, PolicyPublishModalProps>((props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { data } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
export const PolicyPublishModalContent = forwardRef<PolicyPublishModalHandle,PolicyPublishModalProps>((props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { data} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
const publish:()=>Promise<boolean | string | Record<string, unknown>> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then((value)=>{
|
||||
const body = {...value, source:data.source}
|
||||
fetchData<BasicResponse<null>>('strategy/global/data-masking/publish',{method: 'POST',eoBody:body,eoTransformKeys:['versionName']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
publish,
|
||||
const publish: () => Promise<boolean | string | Record<string, unknown>> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
const body = { ...value, source: data.source }
|
||||
fetchData<BasicResponse<null>>('strategy/global/data-masking/publish', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['versionName']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
)
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue(data)
|
||||
},[data])
|
||||
useImperativeHandle(ref, () => ({
|
||||
publish
|
||||
}))
|
||||
|
||||
const translatedPolicyColumns = useMemo(()=>PolicyPublishColumns.map((x)=>({
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(data)
|
||||
}, [data])
|
||||
|
||||
const translatedPolicyColumns = useMemo(
|
||||
() =>
|
||||
PolicyPublishColumns.map((x) => ({
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithPermission access=""><Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
>
|
||||
return (
|
||||
<>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label={$t('发布名称')} name="versionName" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("发布名称")}
|
||||
name='versionName'
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("描述")}
|
||||
name="desc"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('策略列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedPolicyColumns}
|
||||
bordered={true}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
dataSource={data.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
{!data?.isPublish&& data?.unpublishMsg&& <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
|
||||
</Row>
|
||||
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>)
|
||||
})
|
||||
<Form.Item label={$t('描述')} name="desc">
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('策略列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedPolicyColumns}
|
||||
bordered={true}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
dataSource={data.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
{!data?.isPublish && data?.unpublishMsg && <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
|
||||
</Row>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
+394
-240
@@ -1,257 +1,402 @@
|
||||
import {App, Col, Form, Input, Row, Table, Tooltip} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react";
|
||||
import {PublishApprovalInfoType, PublishApprovalModalHandle, PublishApprovalModalProps, PublishVersionTableListItem} from "@common/const/approval/type.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR} from "@common/const/const.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
import { ApprovalPolicyColumns, ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline";
|
||||
import { App, Col, Form, Input, Row, Table, Tooltip } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import {
|
||||
PublishApprovalInfoType,
|
||||
PublishApprovalModalHandle,
|
||||
PublishApprovalModalProps,
|
||||
PublishVersionTableListItem
|
||||
} from '@common/const/approval/type.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import {
|
||||
BasicResponse,
|
||||
FORM_ERROR_TIPS,
|
||||
PLACEHOLDER,
|
||||
RESPONSE_TIPS,
|
||||
STATUS_CODE,
|
||||
STATUS_COLOR
|
||||
} from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from '@core/const/system/const.tsx'
|
||||
import { $t } from '@common/locales'
|
||||
import {
|
||||
ApprovalPolicyColumns,
|
||||
ApprovalRouteColumns,
|
||||
ApprovalStatusColorClass,
|
||||
ApprovalUpstreamColumns,
|
||||
ChangeTypeEnum
|
||||
} from '@common/const/approval/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { SystemInsidePublishOnlineItems } from '@core/pages/system/publish/SystemInsidePublishOnline'
|
||||
|
||||
|
||||
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle,PublishApprovalModalProps>((props, ref) => {
|
||||
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle, PublishApprovalModalProps>(
|
||||
(props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { type,data,insidePage = false, serviceType = 'rest', serviceId, teamId} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const {state} = useGlobalContext()
|
||||
const { type, data, insidePage = false, serviceType = 'rest', serviceId, teamId } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
|
||||
if(type === 'view'){
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
if (type === 'view') {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}
|
||||
])
|
||||
form.scrollToField('opinion')
|
||||
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
}
|
||||
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`, {
|
||||
method: 'PUT',
|
||||
eoBody: { comments: value.opinion },
|
||||
eoParams: { id: data!.id, project: serviceId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return form.validateFields().then((value)=>{
|
||||
if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){
|
||||
form.setFields([{
|
||||
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}])
|
||||
form.scrollToField('opinion')
|
||||
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
}
|
||||
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`,{method: 'PUT',eoBody:({comments:value.opinion}), eoParams:{id:data!.id, project:serviceId},eoTransformKeys:['versionRemark']}).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((errorInfo)=> Promise.reject(errorInfo))
|
||||
}).catch((err)=> {form.scrollToField(err.errorFields[0].name[0]); return Promise.reject(err)})
|
||||
}
|
||||
|
||||
const publish:(notSave?:boolean)=>Promise<boolean | string | Record<string, unknown>> = (notSave)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then((value)=>{
|
||||
const body = {...value, ...(type === 'publish'&&{release:data.id})}
|
||||
fetchData<BasicResponse<null>>(
|
||||
notSave ? 'service/publish/apply' : 'service/publish/release/do',{method: 'POST',eoBody:body, eoParams:{service:serviceId, team:teamId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const online:()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then(()=>{
|
||||
fetchData<BasicResponse<null>>('service/publish/execute',{method: 'PUT', eoParams:{project:serviceId,id:(data as PublishVersionTableListItem).flowId},eoTransformKeys:['versionRemark']}).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,
|
||||
publish,
|
||||
online
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => Promise.reject(errorInfo))
|
||||
})
|
||||
.catch((err) => {
|
||||
form.scrollToField(err.errorFields[0].name[0])
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
const publish: (notSave?: boolean) => Promise<boolean | string | Record<string, unknown>> = (notSave) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
const body = { ...value, ...(type === 'publish' && { release: data.id }) }
|
||||
fetchData<BasicResponse<null>>(notSave ? 'service/publish/apply' : 'service/publish/release/do', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const online: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
fetchData<BasicResponse<null>>('service/publish/execute', {
|
||||
method: 'PUT',
|
||||
eoParams: { project: serviceId, id: (data as PublishVersionTableListItem).flowId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.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,
|
||||
publish,
|
||||
online
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
}, [])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(
|
||||
() =>
|
||||
ApprovalUpstreamColumns.map((x) => ({
|
||||
...x,
|
||||
...(x.dataIndex === 'type'
|
||||
? {
|
||||
valueEnum: {
|
||||
static: {
|
||||
text: $t('静态上游')
|
||||
}
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(x.dataIndex === 'change'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
{entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({ opinion:'',...data})
|
||||
},[])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(()=>ApprovalUpstreamColumns.map((x)=>({
|
||||
...x,
|
||||
...(x.dataIndex === 'type' ? {valueEnum:{
|
||||
'static':{
|
||||
text:$t('静态上游')
|
||||
}
|
||||
}}:{}),
|
||||
...(x.dataIndex === 'change' ? {
|
||||
render:(_,entity)=>(
|
||||
<Tooltip placement="top" title={entity.change === 'error' ? $t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}</span>
|
||||
</Tooltip>)
|
||||
}:{}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language])
|
||||
|
||||
|
||||
const translatedRouteColumns = useMemo(()=>ApprovalRouteColumns.filter(x=> serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods').map((x)=>({
|
||||
...x,
|
||||
...(x.dataIndex === 'change' ? {
|
||||
render:(_,entity)=>(
|
||||
<Tooltip placement="top" title={entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
|
||||
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}
|
||||
</span>
|
||||
</Tooltip>)
|
||||
}:{}
|
||||
),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language, serviceType])
|
||||
|
||||
const translatedPublishColumns = useMemo(()=>SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{
|
||||
if(x.dataIndex === 'status'){
|
||||
return {...x,title:$t(x.title),
|
||||
render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{
|
||||
switch(entity.status){
|
||||
case 'done':
|
||||
return <span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
|
||||
case 'error':
|
||||
return <Tooltip title={entity.error || $t('上线失败')}><span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>{$t('失败')} {entity.error}</span></Tooltip>
|
||||
default:
|
||||
return <LoadingOutlined className="text-theme" spin />
|
||||
const translatedRouteColumns = useMemo(
|
||||
() =>
|
||||
ApprovalRouteColumns.filter((x) =>
|
||||
serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods'
|
||||
).map((x) => ({
|
||||
...x,
|
||||
...(x.dataIndex === 'change'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''
|
||||
}
|
||||
}}
|
||||
}
|
||||
}),[state.language])
|
||||
>
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
{entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language, serviceType]
|
||||
)
|
||||
|
||||
|
||||
const translatedPolicyColumns = useMemo(()=>ApprovalPolicyColumns.map((x)=>{
|
||||
return {
|
||||
const translatedPublishColumns = useMemo(
|
||||
() =>
|
||||
SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x) => {
|
||||
if (x.dataIndex === 'status') {
|
||||
return {
|
||||
...x,
|
||||
title: $t(x.title),
|
||||
render: (_: unknown, entity: SystemInsidePublishOnlineItems) => {
|
||||
switch (entity.status) {
|
||||
case 'done':
|
||||
return (
|
||||
<span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
|
||||
)
|
||||
case 'error':
|
||||
return (
|
||||
<Tooltip title={entity.error || $t('上线失败')}>
|
||||
<span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>
|
||||
{$t('失败')} {entity.error}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
default:
|
||||
return <LoadingOutlined className="text-theme" spin />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
const translatedPolicyColumns = useMemo(
|
||||
() =>
|
||||
ApprovalPolicyColumns.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
...(x.dataIndex === 'status' ? {
|
||||
render:(_,entity)=> (
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
|
||||
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
...(x.dataIndex === 'status'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
</span>
|
||||
)
|
||||
}:{})
|
||||
}
|
||||
}),[state.language])
|
||||
|
||||
)
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{!insidePage && <>
|
||||
<>
|
||||
{!insidePage && (
|
||||
<>
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请系统')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请系统')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('所属团队')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('所属团队')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请人')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请人')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请时间')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请时间')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
|
||||
</Row>
|
||||
</> }
|
||||
<WithPermission access=""><Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
</>
|
||||
)}
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
{insidePage && (
|
||||
<>
|
||||
<Form.Item label={$t('版本号')} name="version" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
{
|
||||
insidePage &&
|
||||
<>
|
||||
<Form.Item
|
||||
label={$t("版本号")}
|
||||
name="version"
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("版本说明")}
|
||||
name="versionRemark"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('路由列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedRouteColumns}
|
||||
bordered={true}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={data.diffs?.routers || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
{
|
||||
serviceType === 'rest' && <>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('上游列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedUpstreamColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.upstreams || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
</>
|
||||
}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('策略列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedPolicyColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.strategies || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
{/* <Form.Item
|
||||
<Form.Item label={$t('版本说明')} name="versionRemark">
|
||||
<Input.TextArea
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={type !== 'add' && type !== 'publish'}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('路由列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedRouteColumns}
|
||||
bordered={true}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={data.diffs?.routers || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
{serviceType === 'rest' && (
|
||||
<>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('上游列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedUpstreamColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.upstreams || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('策略列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedPolicyColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
{/* <Form.Item
|
||||
label={$t("备注")}
|
||||
name="remark"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item> */}
|
||||
{/*
|
||||
{/*
|
||||
{type !== 'add' && type !== 'publish' && <Form.Item
|
||||
label={$t("审核意见"
|
||||
name="opinion"
|
||||
@@ -264,20 +409,29 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
|
||||
},
|
||||
]);}}/>
|
||||
</Form.Item>} */}
|
||||
|
||||
{['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <>
|
||||
<Row className="text-left h-[32px] mb-8px]" span={3}><span>{$t('上线情况')}:</span></Row>
|
||||
<Row span={24} className="mb-mbase">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={[...translatedPublishColumns]}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.clusterPublishStatus || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row></>}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>)
|
||||
})
|
||||
|
||||
{['error', 'done'].indexOf(data.status) !== -1 &&
|
||||
data.clusterPublishStatus &&
|
||||
data.clusterPublishStatus.length > 0 && (
|
||||
<>
|
||||
<Row className="text-left h-[32px] mb-8px]" span={3}>
|
||||
<span>{$t('上线情况')}:</span>
|
||||
</Row>
|
||||
<Row span={24} className="mb-mbase">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={[...translatedPublishColumns]}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.clusterPublishStatus || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
|
||||
import {FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react';
|
||||
import { FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react'
|
||||
|
||||
interface ScrollableSectionProps {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ScrollableSection: FC<ScrollableSectionProps> = ({ children }) => {
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (scrollAreaRef.current) {
|
||||
const scrollTop = scrollAreaRef.current.scrollTop;
|
||||
const scrollHeight = scrollAreaRef.current.scrollHeight;
|
||||
const clientHeight = scrollAreaRef.current.clientHeight;
|
||||
const scrollTop = scrollAreaRef.current.scrollTop
|
||||
const scrollHeight = scrollAreaRef.current.scrollHeight
|
||||
const clientHeight = scrollAreaRef.current.clientHeight
|
||||
|
||||
// 如果滚动到顶部,.content-before 应该显示阴影
|
||||
const showTopShadow = scrollTop > 0;
|
||||
// 如果滚动到底部,.content-after 应该显示阴影
|
||||
const showBottomShadow = scrollHeight - scrollTop < clientHeight;
|
||||
// 这里我们不直接更新状态,而是通过ref来设置样式
|
||||
if (showTopShadow && !showBottomShadow) {
|
||||
setElementShadow('.content-before', true);
|
||||
setElementShadow('.content-after', false);
|
||||
} else if (!showTopShadow && showBottomShadow) {
|
||||
setElementShadow('.content-before', false);
|
||||
setElementShadow('.content-after', true);
|
||||
} else {
|
||||
setElementShadow('.content-before', false);
|
||||
setElementShadow('.content-after', false);
|
||||
}
|
||||
// 如果滚动到顶部,.content-before 应该显示阴影
|
||||
const showTopShadow = scrollTop > 0
|
||||
// 如果滚动到底部,.content-after 应该显示阴影
|
||||
const showBottomShadow = scrollHeight - scrollTop < clientHeight
|
||||
// 这里我们不直接更新状态,而是通过ref来设置样式
|
||||
if (showTopShadow && !showBottomShadow) {
|
||||
setElementShadow('.content-before', true)
|
||||
setElementShadow('.content-after', false)
|
||||
} else if (!showTopShadow && showBottomShadow) {
|
||||
setElementShadow('.content-before', false)
|
||||
setElementShadow('.content-after', true)
|
||||
} else {
|
||||
setElementShadow('.content-before', false)
|
||||
setElementShadow('.content-after', false)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll);
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
||||
|
||||
return () => {
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setElementShadow = (elementSelector: string, showShadow: boolean) => {
|
||||
const element = document.querySelector(elementSelector);
|
||||
const element = document.querySelector(elementSelector)
|
||||
if (element) {
|
||||
element.style.boxShadow = showShadow ? ( elementSelector === '.content-before' ? '0 2px 2px #0000000d':'0 -2px 2px -2px var(--border-color)') : 'none';
|
||||
element.style.boxShadow = showShadow
|
||||
? elementSelector === '.content-before'
|
||||
? '0 2px 2px #0000000d'
|
||||
: '0 -2px 2px -2px var(--border-color)'
|
||||
: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
const childrenWithRef = Children.toArray(children).map((child) => {
|
||||
if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) {
|
||||
// 将 ref 附加到具有 'scroll-area' 类名的子元素
|
||||
return cloneElement(child, { ref: scrollAreaRef });
|
||||
return cloneElement(child, { ref: scrollAreaRef })
|
||||
}
|
||||
return child;
|
||||
});
|
||||
return child
|
||||
})
|
||||
|
||||
return (
|
||||
<> {childrenWithRef}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return <> {childrenWithRef}</>
|
||||
}
|
||||
|
||||
export default ScrollableSection;
|
||||
export default ScrollableSection
|
||||
|
||||
+110
-96
@@ -1,115 +1,129 @@
|
||||
import {App, Col, Form, Input, Row} from "antd";
|
||||
import { forwardRef, useEffect, useImperativeHandle} from "react";
|
||||
import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx";
|
||||
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { SubscribeApprovalList } from "@common/const/approval/const";
|
||||
import { $t } from "@common/locales";
|
||||
import { App, Col, Form, Input, Row } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react'
|
||||
import { SubscribeApprovalInfoType } from '@common/const/approval/type.tsx'
|
||||
import { BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { SubscribeApprovalList } from '@common/const/approval/const'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
type SubscribeApprovalModalProps = {
|
||||
type:'approval'|'view'
|
||||
data?:SubscribeApprovalInfoType
|
||||
inSystem?:boolean
|
||||
serviceId:string
|
||||
teamId:string
|
||||
type: 'approval' | 'view'
|
||||
data?: SubscribeApprovalInfoType
|
||||
inSystem?: boolean
|
||||
serviceId: string
|
||||
teamId: string
|
||||
}
|
||||
|
||||
export type SubscribeApprovalModalHandle = {
|
||||
save:(operate:'pass'|'refuse') =>Promise<boolean|string>
|
||||
save: (operate: 'pass' | 'refuse') => Promise<boolean | string>
|
||||
}
|
||||
|
||||
type FieldType = {
|
||||
reason?:string;
|
||||
opinion?:string;
|
||||
};
|
||||
reason?: string
|
||||
opinion?: string
|
||||
}
|
||||
|
||||
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle,SubscribeApprovalModalProps>((props, ref) => {
|
||||
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle, SubscribeApprovalModalProps>(
|
||||
(props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const {data, type,inSystem=false, teamId, serviceId} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const { data, type, inSystem = false, teamId, serviceId } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(type === 'view'){
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
form.validateFields().then((value)=>{
|
||||
if(operate === 'refuse' && form.getFieldValue('opinion') === ''){
|
||||
form.setFields([{
|
||||
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}])
|
||||
form.scrollToField('opinion')
|
||||
reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
return
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (type === 'view') {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}
|
||||
fetchData<BasicResponse<null>>(`${inSystem?'service/':''}approval/subscribe`,{method: 'POST',eoBody:({opinion:value.opinion,operate}), eoParams:(inSystem ? {apply:data!.id, team:teamId} : {id:data!.id,team:teamId})}).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))
|
||||
})
|
||||
])
|
||||
form.scrollToField('opinion')
|
||||
reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
return
|
||||
}
|
||||
fetchData<BasicResponse<null>>(`${inSystem ? 'service/' : ''}approval/subscribe`, {
|
||||
method: 'POST',
|
||||
eoBody: { opinion: value.opinion, operate },
|
||||
eoParams: inSystem ? { apply: data!.id, team: teamId } : { id: data!.id, team: teamId }
|
||||
})
|
||||
.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
|
||||
})
|
||||
)
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({opinion:'',...data})
|
||||
},[])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="my-btnybase">{
|
||||
SubscribeApprovalList?.map((x)=>(
|
||||
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
|
||||
<Col className="text-left" span={6}>{$t(x.title)}:</Col>
|
||||
<Col >{(data as {[k:string]:unknown})?.[x.key]?.name || (data as {[k:string]:unknown})?.[x.key] || '-'}</Col>
|
||||
</Row>
|
||||
))
|
||||
}
|
||||
<div className="my-btnybase">
|
||||
{SubscribeApprovalList?.map((x) => (
|
||||
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
|
||||
<Col className="text-left" span={6}>
|
||||
{$t(x.title)}:
|
||||
</Col>
|
||||
<Col>
|
||||
{(data as { [k: string]: unknown })?.[x.key]?.name || (data as { [k: string]: unknown })?.[x.key] || '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="subscribeApprovalModalContent"
|
||||
// labelCol={{ span: 6}}
|
||||
// wrapperCol={{ span: 18}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
label={$t("申请原因")}
|
||||
name="reason"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label={$t("审核意见")}
|
||||
name="opinion"
|
||||
extra={$t(FORM_ERROR_TIPS.refuseOpinion)}
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} onChange={()=>{ form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [], // 设置为空数组来移除错误信息
|
||||
},
|
||||
])}} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
<Form
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="subscribeApprovalModalContent"
|
||||
// labelCol={{ span: 6}}
|
||||
// wrapperCol={{ span: 18}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
<Form.Item<FieldType> label={$t('申请原因')} name="reason">
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={$t('审核意见')} name="opinion" extra={$t(FORM_ERROR_TIPS.refuseOpinion)}>
|
||||
<Input.TextArea
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
onChange={() => {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [] // 设置为空数组来移除错误信息
|
||||
}
|
||||
])
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,41 +1,51 @@
|
||||
|
||||
import { Button, Tooltip } from "antd"
|
||||
import { useState, useMemo, useEffect, useCallback } from "react"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions"
|
||||
import { Icon } from "@iconify/react/dist/iconify.js"
|
||||
import { $t } from "@common/locales"
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
type TableBtnWithPermissionProps = {
|
||||
btnTitle: string
|
||||
access?: keyof typeof PERMISSION_DEFINITION[0],
|
||||
tooltip?: string,
|
||||
disabled?: boolean,
|
||||
navigateTo?: string,
|
||||
access?: keyof (typeof PERMISSION_DEFINITION)[0]
|
||||
tooltip?: string
|
||||
disabled?: boolean
|
||||
navigateTo?: string
|
||||
onClick?: (args?: unknown) => void
|
||||
className?: string
|
||||
btnType: string
|
||||
}
|
||||
|
||||
const TableIconName = {
|
||||
'add': 'ic:baseline-add',
|
||||
'edit': 'ic:baseline-edit',
|
||||
'delete': 'ic:baseline-delete',
|
||||
'remove': 'ic:baseline-minus',
|
||||
'copy': 'ic:baseline-file-copy',
|
||||
'view': 'ic:baseline-remove-red-eye',
|
||||
'publish': 'ic:baseline-publish',
|
||||
'approval': 'ic:baseline-approval',
|
||||
'stop': 'ic:baseline-stop-circle',
|
||||
'online': 'ic:baseline-check-circle',
|
||||
'cancel': 'ic:baseline-cancel-schedule-send',
|
||||
'refresh': 'ic:baseline-refresh',
|
||||
'logs': 'hugeicons:google-doc'
|
||||
add: 'ic:baseline-add',
|
||||
edit: 'ic:baseline-edit',
|
||||
delete: 'ic:baseline-delete',
|
||||
remove: 'ic:baseline-minus',
|
||||
copy: 'ic:baseline-file-copy',
|
||||
view: 'ic:baseline-remove-red-eye',
|
||||
publish: 'ic:baseline-publish',
|
||||
offline: 'ic:baseline-file-download-off',
|
||||
approval: 'ic:baseline-approval',
|
||||
stop: 'ic:baseline-stop-circle',
|
||||
online: 'ic:baseline-check-circle',
|
||||
cancel: 'ic:baseline-cancel-schedule-send',
|
||||
refresh: 'ic:baseline-refresh',
|
||||
logs: 'hugeicons:google-doc',
|
||||
disable: 'ic:baseline-pause-circle',
|
||||
enable: 'ic:baseline-play-circle'
|
||||
}
|
||||
// 表格操作栏按钮,受权限控制
|
||||
const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateTo, onClick, className, btnType }: TableBtnWithPermissionProps) => {
|
||||
|
||||
const TableBtnWithPermission = ({
|
||||
btnTitle,
|
||||
access,
|
||||
tooltip,
|
||||
disabled,
|
||||
navigateTo,
|
||||
onClick,
|
||||
className,
|
||||
btnType
|
||||
}: TableBtnWithPermissionProps) => {
|
||||
const [btnAccess, setBtnAccess] = useState<boolean>(false)
|
||||
const [btnStatus, setBtnStatus] = useState<boolean>(false)
|
||||
const [closeToolTip, setCloseToolTip] = useState<boolean>(false)
|
||||
@@ -51,16 +61,18 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT
|
||||
access ? setBtnAccess(lastAccess) : setBtnAccess(true)
|
||||
}, [access, lastAccess])
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
setTimeout(() => {
|
||||
setBtnStatus(false)
|
||||
setCloseToolTip(true)
|
||||
})
|
||||
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
setTimeout(() => {
|
||||
setBtnStatus(false)
|
||||
setCloseToolTip(true)
|
||||
})
|
||||
|
||||
navigateTo ? navigate(navigateTo) : onClick?.()
|
||||
}, [navigateTo, navigate, onClick])
|
||||
navigateTo ? navigate(navigateTo) : onClick?.()
|
||||
},
|
||||
[navigateTo, navigate, onClick]
|
||||
)
|
||||
const changeTooltipStatus = (open: boolean) => {
|
||||
setBtnStatus(open)
|
||||
if (closeToolTip) {
|
||||
@@ -68,18 +80,42 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT
|
||||
setCloseToolTip(false)
|
||||
}
|
||||
}
|
||||
return (<>{
|
||||
!btnAccess || (disabled && tooltip) ?
|
||||
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
|
||||
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} >{ }</Button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip placement="top" title={$t(btnTitle)} trigger='hover' open={btnStatus} onOpenChange={changeTooltipStatus}>
|
||||
<Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />} onClick={handleClick}>{ }</Button>
|
||||
</Tooltip>
|
||||
|
||||
}</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{!btnAccess || (disabled && tooltip) ? (
|
||||
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={true}
|
||||
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
|
||||
key={btnType}
|
||||
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
|
||||
>
|
||||
{}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={$t(btnTitle)}
|
||||
trigger="hover"
|
||||
open={btnStatus}
|
||||
onOpenChange={changeTooltipStatus}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={disabled}
|
||||
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
|
||||
key={btnType}
|
||||
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableBtnWithPermission
|
||||
export default TableBtnWithPermission
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import { Tag, TagProps } from 'antd'
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
import { Tag, TagProps } from "antd";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
export interface TagWithPermission extends TagProps{
|
||||
access?:string
|
||||
export interface TagWithPermission extends TagProps {
|
||||
access?: string
|
||||
}
|
||||
export default function TagWithPermission(props:TagWithPermission){
|
||||
const {access,onClose} = props
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!access) return true
|
||||
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
export default function TagWithPermission(props: TagWithPermission) {
|
||||
const { access, onClose } = props
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!access) return true
|
||||
return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
access ? setEditAccess(lastAccess) : setEditAccess(true)
|
||||
},[lastAccess])
|
||||
|
||||
const handleTagClose = (e: React.MouseEvent<HTMLElement>)=>{
|
||||
e.preventDefault();
|
||||
if(!editAccess) return
|
||||
onClose?.(e)
|
||||
}
|
||||
useEffect(() => {
|
||||
access ? setEditAccess(lastAccess) : setEditAccess(true)
|
||||
}, [lastAccess])
|
||||
|
||||
return <Tag
|
||||
closeIcon
|
||||
{...props}
|
||||
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
|
||||
onClose={handleTagClose}>
|
||||
{props.children}
|
||||
</Tag>
|
||||
const handleTagClose = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
if (!editAccess) return
|
||||
onClose?.(e)
|
||||
}
|
||||
|
||||
}
|
||||
return (
|
||||
<Tag
|
||||
closeIcon
|
||||
{...props}
|
||||
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
|
||||
onClose={handleTagClose}
|
||||
>
|
||||
{props.children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
const ThemeSwitcher = () => {
|
||||
const [darkMode, setDarkMode] = useState(true);
|
||||
const [darkMode, setDarkMode] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
let isDarkMode = localStorage.getItem('dark-mode');
|
||||
if(isDarkMode !== undefined && isDarkMode !== null){
|
||||
setDarkMode(isDarkMode === 'true')
|
||||
}else{
|
||||
localStorage.setItem('dark-mode', (darkMode).toString());
|
||||
const isDarkMode = localStorage.getItem('dark-mode')
|
||||
if (isDarkMode !== undefined && isDarkMode !== null) {
|
||||
setDarkMode(isDarkMode === 'true')
|
||||
} else {
|
||||
localStorage.setItem('dark-mode', darkMode.toString())
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
useEffect(()=>{
|
||||
document.documentElement.classList.toggle('dark', darkMode);
|
||||
},[darkMode])
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle('dark', darkMode)
|
||||
}, [darkMode])
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setDarkMode(!darkMode);
|
||||
localStorage.setItem('dark-mode', (!darkMode).toString());
|
||||
document.documentElement.classList.toggle('dark', !darkMode);
|
||||
};
|
||||
setDarkMode(!darkMode)
|
||||
localStorage.setItem('dark-mode', (!darkMode).toString())
|
||||
document.documentElement.classList.toggle('dark', !darkMode)
|
||||
}
|
||||
|
||||
return (
|
||||
// <button onClick={toggleDarkMode}>
|
||||
// {darkMode ? '切换到白天模式' : '切换到黑夜模式'}
|
||||
// </button>
|
||||
<></>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSwitcher;
|
||||
export default ThemeSwitcher
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import '../../index.css'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import "../../index.css"
|
||||
import { $t } from '@common/locales';
|
||||
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>
|
||||
export type RangeValue = [Dayjs | null, Dayjs | null] | null
|
||||
|
||||
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>;
|
||||
export type RangeValue = [Dayjs | null, Dayjs | null] | null;
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export type TimeRange = {
|
||||
start: number | null
|
||||
end: number | null
|
||||
}
|
||||
|
||||
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays';
|
||||
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
|
||||
|
||||
type TimeRangeSelectorProps = {
|
||||
initialTimeButton?: TimeRangeButton,
|
||||
initialTimeButton?: TimeRangeButton
|
||||
initialDatePickerValue?: RangeValue
|
||||
onTimeRangeChange?: (timeRange: TimeRange) => void
|
||||
hideTitle?: boolean
|
||||
@@ -30,17 +29,27 @@ type TimeRangeSelectorProps = {
|
||||
defaultTimeButton?: TimeRangeButton
|
||||
}
|
||||
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const { initialTimeButton, initialDatePickerValue, onTimeRangeChange, hideTitle, onTimeButtonChange, labelSize = 'default', bindRef, hideBtns = [], defaultTimeButton = 'hour' } = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '');
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null]);
|
||||
const {
|
||||
initialTimeButton,
|
||||
initialDatePickerValue,
|
||||
onTimeRangeChange,
|
||||
hideTitle,
|
||||
onTimeButtonChange,
|
||||
labelSize = 'default',
|
||||
bindRef,
|
||||
hideBtns = [],
|
||||
defaultTimeButton = 'hour'
|
||||
} = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
|
||||
useEffect(() => {
|
||||
if (bindRef) {
|
||||
bindRef({ reset });
|
||||
bindRef({ reset })
|
||||
}
|
||||
}, [bindRef])
|
||||
// 根据选择的时间范围计算开始和结束时间
|
||||
const calculateTimeRange = (curBtn: TimeRangeButton) => {
|
||||
const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳
|
||||
const currentSecond = Math.floor(Date.now() / 1000) // 当前秒级时间戳
|
||||
let startMin = currentSecond - 60 * 60
|
||||
switch (curBtn) {
|
||||
case 'hour': {
|
||||
@@ -52,30 +61,26 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
break
|
||||
}
|
||||
case 'threeDays': {
|
||||
startMin =
|
||||
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
|
||||
2 * 24 * 60 * 60
|
||||
startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 2 * 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
case 'sevenDays': {
|
||||
startMin =
|
||||
Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) -
|
||||
6 * 24 * 60 * 60
|
||||
startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 6 * 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
}
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start: startMin, end: currentSecond });
|
||||
onTimeRangeChange({ start: startMin, end: currentSecond })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 处理单选按钮的变化
|
||||
const handleRadioChange = (e: RadioChangeEvent) => {
|
||||
setTimeButton(e.target.value);
|
||||
setTimeButton(e.target.value)
|
||||
onTimeButtonChange?.(e.target.value)
|
||||
setDatePickerValue(null)
|
||||
calculateTimeRange(e.target.value);
|
||||
};
|
||||
calculateTimeRange(e.target.value)
|
||||
}
|
||||
const reset = () => {
|
||||
setTimeButton(defaultTimeButton)
|
||||
calculateTimeRange(defaultTimeButton)
|
||||
@@ -86,35 +91,43 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const handleDatePickerChange = (dates: RangeValue) => {
|
||||
setTimeButton(dates ? '' : defaultTimeButton)
|
||||
onTimeButtonChange?.(dates ? '' : defaultTimeButton)
|
||||
setDatePickerValue(dates);
|
||||
setDatePickerValue(dates)
|
||||
if (dates && Array.isArray(dates) && dates.length === 2) {
|
||||
const [startDate, endDate] = dates;
|
||||
const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00
|
||||
const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59
|
||||
const [startDate, endDate] = dates
|
||||
const start = startDate!.startOf('day').unix() // 开始日期的00:00:00
|
||||
const end = endDate!.endOf('day').unix() // 结束日期的23:59:59
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start, end });
|
||||
onTimeRangeChange({ start, end })
|
||||
}
|
||||
}
|
||||
if (!dates) {
|
||||
calculateTimeRange(defaultTimeButton)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
|
||||
// Can not select days before today and today
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf();
|
||||
};
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}:</label>}
|
||||
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : <Radio.Button value="hour">{$t('近1小时')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('day') ? null : <Radio.Button value="day">{$t('近24小时')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('threeDays') ? null : <Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('sevenDays') ? null : <Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>}
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : (
|
||||
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('day') ? null : (
|
||||
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('threeDays') ? null : (
|
||||
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('sevenDays') ? null : (
|
||||
<Radio.Button className="rounded-e-none" value="sevenDays">
|
||||
{$t('近7天')}
|
||||
</Radio.Button>
|
||||
)}
|
||||
</Radio.Group>
|
||||
<DatePicker.RangePicker
|
||||
value={datePickerValue}
|
||||
@@ -129,7 +142,7 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default TimeRangeSelector;
|
||||
export default TimeRangeSelector
|
||||
|
||||
@@ -1,45 +1,94 @@
|
||||
|
||||
import {CheckOutlined, LoadingOutlined, MoreOutlined} from "@ant-design/icons";
|
||||
import {Dropdown, Input, InputRef, MenuProps} from "antd";
|
||||
import { ReactNode, useEffect, useRef, useState} from "react";
|
||||
import { CheckOutlined, LoadingOutlined, MoreOutlined } from '@ant-design/icons'
|
||||
import { Dropdown, Input, InputRef, MenuProps } from 'antd'
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
|
||||
export type TreeWithMoreProp = {
|
||||
children:ReactNode,
|
||||
dropdownMenu:MenuProps['items']
|
||||
editable?:boolean
|
||||
editingId?:string
|
||||
afterEdit?:(val:string)=>Promise<string|boolean>
|
||||
editKey?:string
|
||||
entity?:{id:string,[k:string]:unknown | string}
|
||||
onBlur?:()=>void
|
||||
stopClick?:boolean
|
||||
children: ReactNode
|
||||
dropdownMenu: MenuProps['items']
|
||||
editable?: boolean
|
||||
editingId?: string
|
||||
afterEdit?: (val: string) => Promise<string | boolean>
|
||||
editKey?: string
|
||||
entity?: { id: string; [k: string]: unknown | string }
|
||||
onBlur?: () => void
|
||||
stopClick?: boolean
|
||||
}
|
||||
|
||||
const TreeWithMore = ({children,dropdownMenu,editable,editingId,entity,editKey='name',afterEdit,onBlur,stopClick=true}:TreeWithMoreProp)=>{
|
||||
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
const TreeWithMore = ({
|
||||
children,
|
||||
dropdownMenu,
|
||||
editable,
|
||||
editingId,
|
||||
entity,
|
||||
editKey = 'name',
|
||||
afterEdit,
|
||||
onBlur,
|
||||
stopClick = true
|
||||
}: TreeWithMoreProp) => {
|
||||
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
|
||||
const handleSubmit = (val:string)=>{
|
||||
if(submitting) return
|
||||
setSubmitting(true)
|
||||
afterEdit && afterEdit(val).finally(()=>setSubmitting(false))
|
||||
}
|
||||
const handleSubmit = (val: string) => {
|
||||
if (submitting) return
|
||||
setSubmitting(true)
|
||||
afterEdit && afterEdit(val).finally(() => setSubmitting(false))
|
||||
}
|
||||
|
||||
useEffect(()=>{inputRef.current?.focus()},[inputRef])
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus()
|
||||
}, [inputRef])
|
||||
|
||||
return (<>
|
||||
{
|
||||
editable && editingId && entity?.id && editingId === entity.id ? <Input ref={inputRef} value={editValue} onChange={(e)=>{setEditValue(e.target.value)}} onBlur={()=>{onBlur?.()}} onClick={(e)=>stopClick&&e?.stopPropagation()} onPressEnter={()=>{handleSubmit(editValue)}} suffix={submitting ? <LoadingOutlined />:<CheckOutlined onClick={()=>{handleSubmit(editValue)}}/>} />:
|
||||
<Dropdown menu={{items:dropdownMenu}} trigger={['contextMenu']} >
|
||||
<div className='tree-title-hover' >{children}
|
||||
<span onClick={(e)=>{ stopClick && e.stopPropagation();}}>
|
||||
<Dropdown menu={{items:dropdownMenu}} trigger={['click']} >
|
||||
<MoreOutlined className="tree-title-more" onClick={(e)=>{ stopClick && e.stopPropagation(); }} />
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
{editable && editingId && entity?.id && editingId === entity.id ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => {
|
||||
setEditValue(e.target.value)
|
||||
}}
|
||||
onBlur={() => {
|
||||
onBlur?.()
|
||||
}}
|
||||
onClick={(e) => stopClick && e?.stopPropagation()}
|
||||
onPressEnter={() => {
|
||||
handleSubmit(editValue)
|
||||
}}
|
||||
suffix={
|
||||
submitting ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
<CheckOutlined
|
||||
onClick={() => {
|
||||
handleSubmit(editValue)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Dropdown menu={{ items: dropdownMenu }} trigger={['contextMenu']}>
|
||||
<div className="tree-title-hover">
|
||||
{children}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
stopClick && e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<Dropdown menu={{ items: dropdownMenu }} trigger={['click']}>
|
||||
<MoreOutlined
|
||||
className="tree-title-more"
|
||||
onClick={(e) => {
|
||||
stopClick && e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
}</>)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default TreeWithMore
|
||||
export default TreeWithMore
|
||||
|
||||
@@ -1,190 +1,190 @@
|
||||
import { $t } from "@common/locales"
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/
|
||||
export const TranslateWord = ()=>{
|
||||
return (
|
||||
<>
|
||||
{$t('文件日志')}
|
||||
{$t('HTTP日志')}
|
||||
{$t('Kafka日志')}
|
||||
{$t('NSQ日志')}
|
||||
{$t('Syslog日志')}
|
||||
{$t('未分配')}
|
||||
{$t('超级管理员')}
|
||||
{$t('团队管理员')}
|
||||
{$t('运维管理员')}
|
||||
{$t('普通成员')}
|
||||
{$t('只读成员')}
|
||||
{$t('服务管理员')}
|
||||
{$t('服务开发者')}
|
||||
{$t('消费者开发者')}
|
||||
{$t('消费者管理员')}
|
||||
{$t('驱动名称')}
|
||||
{$t('请求失败数')}
|
||||
{$t('转发失败数')}
|
||||
{$t('作用范围')}
|
||||
{$t('添加条目')}
|
||||
{$t('添加地址')}
|
||||
{$t('文件名称')}
|
||||
{$t('存放目录')}
|
||||
{$t('日志分割周期')}
|
||||
{$t('过期时间')}
|
||||
{$t('单位:天')}
|
||||
{$t('输出格式')}
|
||||
{$t('格式化配置')}
|
||||
{$t('服务器地址')}
|
||||
{$t('Access日志')}
|
||||
{$t('NSQD地址列表')}
|
||||
{$t('鉴权Secret')}
|
||||
{$t('网络协议')}
|
||||
{$t('日志等级')}
|
||||
{$t('单行')}
|
||||
{$t('小时')}
|
||||
{$t('天')}
|
||||
{$t('未发布')}
|
||||
{$t('待发布')}
|
||||
{$t('单位:s,最小值:1')}
|
||||
{$t('上传文件')}
|
||||
{$t('替换文件')}
|
||||
{$t('是否放行')}
|
||||
{$t('监控')}
|
||||
{$t('必填')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
{$t('无需审核')}
|
||||
{$t('需要审核')}
|
||||
{$t('创建时间')}
|
||||
{$t('协议')}
|
||||
{$t('方法')}
|
||||
{$t('地址(IP 端口或域名)')}
|
||||
{$t('权重(0-999)')}
|
||||
{$t('带权轮询')}
|
||||
{$t('发布版本')}
|
||||
{$t('发布申请记录')}
|
||||
{$t('创建版本时间')}
|
||||
{$t('版本状态')}
|
||||
{$t('创建人')}
|
||||
{$t('审核时间')}
|
||||
{$t('申请方-消费者')}
|
||||
{$t('审核状态')}
|
||||
{$t('申请人')}
|
||||
{$t('审核人')}
|
||||
{$t('审核时间')}
|
||||
{$t('来源')}
|
||||
{$t('订阅时间')}
|
||||
{$t('请输入')}
|
||||
{$t('请选择')}
|
||||
{$t('创建者')}
|
||||
{$t('服务数量')}
|
||||
{$t('负责人')}
|
||||
{$t('姓名')}
|
||||
{$t('团队角色')}
|
||||
{$t('添加日期')}
|
||||
{$t('请求成功数')}
|
||||
{$t('转发成功数')}
|
||||
{$t('API 名称')}
|
||||
{$t('失败状态码数')}
|
||||
{$t('所属服务')}
|
||||
{$t('平均响应时间(ms)')}
|
||||
{$t('最大响应时间(ms)')}
|
||||
{$t('最小响应时间(ms)')}
|
||||
{$t('平均请求流量(KB)')}
|
||||
{$t('最大请求流量(KB)')}
|
||||
{$t('最小请求流量(KB)')}
|
||||
{$t('所有成员')}
|
||||
{$t('状态')}
|
||||
{$t('角色名称')}
|
||||
{$t('绑定域名')}
|
||||
{$t('过期日期')}
|
||||
{$t('支持字母开头、英文数字中横线下划线组合')}
|
||||
{$t('英文数字下划线任意一种,首字母必须为英文')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
|
||||
{$t('选择拒绝时,审核意见为必填')}
|
||||
{$t('操作成功')}
|
||||
{$t('操作失败')}
|
||||
{$t('正在操作')}
|
||||
{$t('正在加载数据')}
|
||||
{$t('获取数据失败')}
|
||||
{$t('登录成功')}
|
||||
{$t('退出成功,将跳转至登录页')}
|
||||
{$t('未填写审核意见')}
|
||||
{$t('复制成功')}
|
||||
{$t('复制失败,请手动复制')}
|
||||
{$t('服务所属团队')}
|
||||
{$t('在线')}
|
||||
{$t('已拒绝')}
|
||||
{$t('中止')}
|
||||
{$t('发布异常')}
|
||||
{$t('发布中')}
|
||||
{$t('申请方所属团队')}
|
||||
{$t('发布状态')}
|
||||
{$t(' 次')}
|
||||
{$t('每分钟')}
|
||||
{$t('每5分钟')}
|
||||
{$t('每小时')}
|
||||
{$t('每天')}
|
||||
{$t('每周')}
|
||||
{$t('上线结果')}
|
||||
{$t('订阅服务数量')}
|
||||
{$t('鉴权数量')}
|
||||
{$t('列表')}
|
||||
{$t('块')}
|
||||
{$t('HTTP 请求头')}
|
||||
{$t('全等匹配')}
|
||||
{$t('前缀匹配')}
|
||||
{$t('后缀匹配')}
|
||||
{$t('子串匹配')}
|
||||
{$t('非等匹配')}
|
||||
{$t('空值匹配')}
|
||||
{$t('存在匹配')}
|
||||
{$t('不存在匹配')}
|
||||
{$t('区分大小写的正则匹配')}
|
||||
{$t('不区分大小写的正则匹配')}
|
||||
{$t('任意匹配')}
|
||||
{$t('驳回')}
|
||||
{$t('已订阅')}
|
||||
{$t('取消申请')}
|
||||
{$t('透传客户端请求 Host')}
|
||||
{$t('使用上游服务 Host')}
|
||||
{$t('重写 Host')}
|
||||
{$t('动态服务发现')}
|
||||
{$t('地址')}
|
||||
{$t('新增')}
|
||||
{$t('申请方消费者')}
|
||||
{$t('策略名称')}
|
||||
{$t('优先级')}
|
||||
{$t('筛选条件')}
|
||||
{$t('处理数')}
|
||||
{$t('数据格式')}
|
||||
{$t('关键字')}
|
||||
{$t('正则表达式')}
|
||||
{$t('手机号')}
|
||||
{$t('身份证号')}
|
||||
{$t('银行卡号')}
|
||||
{$t('金额')}
|
||||
{$t('日期')}
|
||||
{$t('局部显示')}
|
||||
{$t('局部遮蔽')}
|
||||
{$t('截取')}
|
||||
{$t('替换')}
|
||||
{$t('乱序')}
|
||||
{$t('随机字符串')}
|
||||
{$t('自定义字符串')}
|
||||
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
|
||||
{$t('待更新')}
|
||||
{$t('待删除')}
|
||||
{$t('内容')}
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const TranslateWord = () => {
|
||||
return (
|
||||
<>
|
||||
{$t('文件日志')}
|
||||
{$t('HTTP日志')}
|
||||
{$t('Kafka日志')}
|
||||
{$t('NSQ日志')}
|
||||
{$t('Syslog日志')}
|
||||
{$t('未分配')}
|
||||
{$t('超级管理员')}
|
||||
{$t('团队管理员')}
|
||||
{$t('运维管理员')}
|
||||
{$t('普通成员')}
|
||||
{$t('只读成员')}
|
||||
{$t('服务管理员')}
|
||||
{$t('服务开发者')}
|
||||
{$t('消费者开发者')}
|
||||
{$t('消费者管理员')}
|
||||
{$t('驱动名称')}
|
||||
{$t('请求失败数')}
|
||||
{$t('转发失败数')}
|
||||
{$t('作用范围')}
|
||||
{$t('添加条目')}
|
||||
{$t('添加地址')}
|
||||
{$t('文件名称')}
|
||||
{$t('存放目录')}
|
||||
{$t('日志分割周期')}
|
||||
{$t('过期时间')}
|
||||
{$t('单位:天')}
|
||||
{$t('输出格式')}
|
||||
{$t('格式化配置')}
|
||||
{$t('服务器地址')}
|
||||
{$t('Access日志')}
|
||||
{$t('NSQD地址列表')}
|
||||
{$t('鉴权Secret')}
|
||||
{$t('网络协议')}
|
||||
{$t('日志等级')}
|
||||
{$t('单行')}
|
||||
{$t('小时')}
|
||||
{$t('天')}
|
||||
{$t('未发布')}
|
||||
{$t('待发布')}
|
||||
{$t('单位:s,最小值:1')}
|
||||
{$t('上传文件')}
|
||||
{$t('替换文件')}
|
||||
{$t('是否放行')}
|
||||
{$t('监控')}
|
||||
{$t('必填')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
{$t('无需审核')}
|
||||
{$t('需要审核')}
|
||||
{$t('创建时间')}
|
||||
{$t('协议')}
|
||||
{$t('方法')}
|
||||
{$t('地址(IP 端口或域名)')}
|
||||
{$t('权重(0-999)')}
|
||||
{$t('带权轮询')}
|
||||
{$t('发布版本')}
|
||||
{$t('发布申请记录')}
|
||||
{$t('创建版本时间')}
|
||||
{$t('版本状态')}
|
||||
{$t('创建人')}
|
||||
{$t('审核时间')}
|
||||
{$t('申请方-消费者')}
|
||||
{$t('审核状态')}
|
||||
{$t('申请人')}
|
||||
{$t('审核人')}
|
||||
{$t('审核时间')}
|
||||
{$t('来源')}
|
||||
{$t('订阅时间')}
|
||||
{$t('请输入')}
|
||||
{$t('请选择')}
|
||||
{$t('创建者')}
|
||||
{$t('服务数量')}
|
||||
{$t('负责人')}
|
||||
{$t('姓名')}
|
||||
{$t('团队角色')}
|
||||
{$t('添加日期')}
|
||||
{$t('请求成功数')}
|
||||
{$t('转发成功数')}
|
||||
{$t('API 名称')}
|
||||
{$t('失败状态码数')}
|
||||
{$t('所属服务')}
|
||||
{$t('平均响应时间(ms)')}
|
||||
{$t('最大响应时间(ms)')}
|
||||
{$t('最小响应时间(ms)')}
|
||||
{$t('平均请求流量(KB)')}
|
||||
{$t('最大请求流量(KB)')}
|
||||
{$t('最小请求流量(KB)')}
|
||||
{$t('所有成员')}
|
||||
{$t('状态')}
|
||||
{$t('角色名称')}
|
||||
{$t('绑定域名')}
|
||||
{$t('过期日期')}
|
||||
{$t('支持字母开头、英文数字中横线下划线组合')}
|
||||
{$t('英文数字下划线任意一种,首字母必须为英文')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
|
||||
{$t('选择拒绝时,审核意见为必填')}
|
||||
{$t('操作成功')}
|
||||
{$t('操作失败')}
|
||||
{$t('正在操作')}
|
||||
{$t('正在加载数据')}
|
||||
{$t('获取数据失败')}
|
||||
{$t('登录成功')}
|
||||
{$t('退出成功,将跳转至登录页')}
|
||||
{$t('未填写审核意见')}
|
||||
{$t('复制成功')}
|
||||
{$t('复制失败,请手动复制')}
|
||||
{$t('服务所属团队')}
|
||||
{$t('在线')}
|
||||
{$t('已拒绝')}
|
||||
{$t('中止')}
|
||||
{$t('发布异常')}
|
||||
{$t('发布中')}
|
||||
{$t('申请方所属团队')}
|
||||
{$t('发布状态')}
|
||||
{$t(' 次')}
|
||||
{$t('每分钟')}
|
||||
{$t('每5分钟')}
|
||||
{$t('每小时')}
|
||||
{$t('每天')}
|
||||
{$t('每周')}
|
||||
{$t('上线结果')}
|
||||
{$t('订阅服务数量')}
|
||||
{$t('鉴权数量')}
|
||||
{$t('列表')}
|
||||
{$t('块')}
|
||||
{$t('HTTP 请求头')}
|
||||
{$t('全等匹配')}
|
||||
{$t('前缀匹配')}
|
||||
{$t('后缀匹配')}
|
||||
{$t('子串匹配')}
|
||||
{$t('非等匹配')}
|
||||
{$t('空值匹配')}
|
||||
{$t('存在匹配')}
|
||||
{$t('不存在匹配')}
|
||||
{$t('区分大小写的正则匹配')}
|
||||
{$t('不区分大小写的正则匹配')}
|
||||
{$t('任意匹配')}
|
||||
{$t('驳回')}
|
||||
{$t('已订阅')}
|
||||
{$t('取消申请')}
|
||||
{$t('透传客户端请求 Host')}
|
||||
{$t('使用上游服务 Host')}
|
||||
{$t('重写 Host')}
|
||||
{$t('动态服务发现')}
|
||||
{$t('地址')}
|
||||
{$t('新增')}
|
||||
{$t('申请方消费者')}
|
||||
{$t('策略名称')}
|
||||
{$t('优先级')}
|
||||
{$t('筛选条件')}
|
||||
{$t('处理数')}
|
||||
{$t('数据格式')}
|
||||
{$t('关键字')}
|
||||
{$t('正则表达式')}
|
||||
{$t('手机号')}
|
||||
{$t('身份证号')}
|
||||
{$t('银行卡号')}
|
||||
{$t('金额')}
|
||||
{$t('日期')}
|
||||
{$t('局部显示')}
|
||||
{$t('局部遮蔽')}
|
||||
{$t('截取')}
|
||||
{$t('替换')}
|
||||
{$t('乱序')}
|
||||
{$t('随机字符串')}
|
||||
{$t('自定义字符串')}
|
||||
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
|
||||
{$t('待更新')}
|
||||
{$t('待删除')}
|
||||
{$t('内容')}
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
|
||||
import { Button, Tooltip, Upload } from "antd";
|
||||
import { ReactElement, cloneElement, useEffect, useMemo, useState } from "react";
|
||||
import { useGlobalContext } from "../../contexts/GlobalStateContext";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { $t } from "@common/locales";
|
||||
import { last } from "lodash-es";
|
||||
import { Button, Tooltip, Upload } from 'antd'
|
||||
import { ReactElement, cloneElement, useEffect, useMemo, useState } from 'react'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
type WithPermissionProps = {
|
||||
access?:string | string[]
|
||||
tooltip?:string
|
||||
children:ReactElement
|
||||
disabled?:boolean
|
||||
showDisabled?:boolean
|
||||
access?: string | string[]
|
||||
tooltip?: string
|
||||
children: ReactElement
|
||||
disabled?: boolean
|
||||
showDisabled?: boolean
|
||||
}
|
||||
// 权限控制的高阶组件
|
||||
const WithPermission = ({access, tooltip, children,disabled, showDisabled = true}:WithPermissionProps) => {
|
||||
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const WithPermission = ({ access, tooltip, children, disabled, showDisabled = true }: WithPermissionProps) => {
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!access) return true
|
||||
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!access) return true
|
||||
return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
|
||||
access && setEditAccess(lastAccess)
|
||||
},[lastAccess,disabled])
|
||||
useEffect(() => {
|
||||
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
|
||||
access && setEditAccess(lastAccess)
|
||||
}, [lastAccess, disabled])
|
||||
|
||||
return (
|
||||
<>
|
||||
{}
|
||||
{editAccess && !disabled && cloneElement(children)}
|
||||
{editAccess && disabled && <Tooltip title={tooltip}>{cloneElement(children, { disabled: true })}</Tooltip>}
|
||||
{!editAccess && children?.type !== Button && children?.type !== Upload && showDisabled && (
|
||||
<Tooltip title={tooltip ?? $t('暂无操作权限,请联系管理员分配。')}>
|
||||
{cloneElement(children, {
|
||||
disabled: true,
|
||||
onClick: (e) => e.preventDefault(),
|
||||
okButtonProps: { disabled: true }
|
||||
})}
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>{
|
||||
}
|
||||
{editAccess && !disabled && cloneElement(children)}
|
||||
{editAccess && disabled && <Tooltip title={tooltip}>
|
||||
{ cloneElement(children, {disabled:true})}
|
||||
</Tooltip>}
|
||||
{!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled) && <Tooltip title={tooltip ?? $t("暂无操作权限,请联系管理员分配。")}>
|
||||
{ cloneElement(children, {disabled:true, onClick:(e)=>e.preventDefault(),okButtonProps:{disabled:true}})}
|
||||
</Tooltip>}
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default WithPermission
|
||||
export default WithPermission
|
||||
|
||||
@@ -1,79 +1,85 @@
|
||||
import { set } from 'lodash-es';
|
||||
import { ExoticComponent, JSXElementConstructor, ReactElement, useEffect, useState } from 'react';
|
||||
import { useBlocker, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { JSX } from 'react/jsx-runtime';
|
||||
import { ExoticComponent, JSXElementConstructor, useEffect, useState } from 'react'
|
||||
import { useBlocker, useLocation } from 'react-router-dom'
|
||||
import { JSX } from 'react/jsx-runtime'
|
||||
|
||||
const withRouteGuard = (WrappedComponent: ExoticComponent<any> | JSXElementConstructor<any>, {
|
||||
canActivate,
|
||||
canLoad ,
|
||||
canDeactivate,
|
||||
deactivated,
|
||||
pathPrefix
|
||||
}: { pathPrefix?:string, canActivate?: () => Promise<boolean>; canLoad?: () => Promise<boolean>; canDeactivate?: () => Promise<boolean>; deactivated?: () => Promise<void>; } = {}) => {
|
||||
const withRouteGuard = (
|
||||
WrappedComponent: ExoticComponent<any> | JSXElementConstructor<any>,
|
||||
{
|
||||
canActivate,
|
||||
canLoad,
|
||||
canDeactivate,
|
||||
deactivated,
|
||||
pathPrefix
|
||||
}: {
|
||||
pathPrefix?: string
|
||||
canActivate?: () => Promise<boolean>
|
||||
canLoad?: () => Promise<boolean>
|
||||
canDeactivate?: () => Promise<boolean>
|
||||
deactivated?: () => Promise<void>
|
||||
} = {}
|
||||
) => {
|
||||
return function RouteGuard(props: JSX.IntrinsicAttributes) {
|
||||
const [isActivated, setIsActivated] = useState<boolean>(false);
|
||||
const location = useLocation();
|
||||
const [isActivated, setIsActivated] = useState<boolean>(false)
|
||||
const location = useLocation()
|
||||
// check canActivate
|
||||
const startLifecycle = async ()=>{
|
||||
if(canActivate){
|
||||
const activateRes = await canActivate();
|
||||
setIsActivated(activateRes);
|
||||
}else{
|
||||
setIsActivated(true);
|
||||
const startLifecycle = async () => {
|
||||
if (canActivate) {
|
||||
const activateRes = await canActivate()
|
||||
setIsActivated(activateRes)
|
||||
} else {
|
||||
setIsActivated(true)
|
||||
}
|
||||
}
|
||||
|
||||
// check canDeactivate
|
||||
const handleBeforeUnload = async (event: { preventDefault: () => void; returnValue: string }) => {
|
||||
const deactivateRes = canDeactivate ? await canDeactivate() : true
|
||||
if (!deactivateRes) {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
}
|
||||
|
||||
// check canDeactivate
|
||||
const handleBeforeUnload =async (event: { preventDefault: () => void; returnValue: string; }) => {
|
||||
const deactivateRes = canDeactivate? await canDeactivate():true;
|
||||
if (!deactivateRes) {
|
||||
event.preventDefault();
|
||||
event.returnValue = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 激活组件时的检查
|
||||
useEffect(() => {
|
||||
startLifecycle();
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
startLifecycle()
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
deactivated?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
deactivated?.()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const blocker = useBlocker((tx) => {
|
||||
const currentPath = location.pathname;
|
||||
const targetPath = tx.nextLocation.pathname;
|
||||
const currentPath = location.pathname
|
||||
const targetPath = tx.nextLocation.pathname
|
||||
|
||||
if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) {
|
||||
if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) {
|
||||
canDeactivate().then((res) => {
|
||||
if(res){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
if (res) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
const checkCanLoad = async()=>{
|
||||
const loadRes = await canLoad!();
|
||||
!loadRes && setIsActivated(false);
|
||||
const checkCanLoad = async () => {
|
||||
const loadRes = await canLoad!()
|
||||
!loadRes && setIsActivated(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (isActivated && canLoad) {
|
||||
checkCanLoad()
|
||||
}
|
||||
}, [isActivated]);
|
||||
}, [isActivated])
|
||||
|
||||
return isActivated ? <WrappedComponent {...props}/> : null;
|
||||
};
|
||||
return isActivated ? <WrappedComponent {...props} /> : null
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouteGuard;
|
||||
export default withRouteGuard
|
||||
|
||||
+124
-131
@@ -1,150 +1,143 @@
|
||||
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
import { Input } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
export const ArrayItemBlankComponent = forwardRef(
|
||||
(props: { [k: string]: any }, ref) => {
|
||||
const { onChange, value, dataFormat } = props
|
||||
export const ArrayItemBlankComponent = forwardRef((props: { [k: string]: any }, ref) => {
|
||||
const { onChange, value, dataFormat } = props
|
||||
|
||||
const getDefaultListItem = () => {
|
||||
const defaultData: { [k: string]: unknown } = {}
|
||||
const getDefaultListItem = () => {
|
||||
const defaultData: { [k: string]: unknown } = {}
|
||||
|
||||
for (const data of dataFormat) {
|
||||
defaultData[data.key] = ''
|
||||
}
|
||||
|
||||
return [defaultData]
|
||||
for (const data of dataFormat) {
|
||||
defaultData[data.key] = ''
|
||||
}
|
||||
|
||||
const [resList, setResList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...value
|
||||
?.filter((v: string) => {
|
||||
return v
|
||||
})
|
||||
?.map((v: string) => {
|
||||
const vTmp = v
|
||||
const newValue: { [k: string]: unknown } = {}
|
||||
for (let index = 0; index < dataFormat.length; index++) {
|
||||
if (dataFormat[index]?.hideName) {
|
||||
newValue[dataFormat[index].key] = vTmp.split(' ')[index]
|
||||
} else {
|
||||
const vTmp2: string | string[] | undefined =
|
||||
vTmp.indexOf(' ') === -1
|
||||
? vTmp
|
||||
: vTmp.split(' ')[index]
|
||||
return [defaultData]
|
||||
}
|
||||
|
||||
const [resList, setResList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...value
|
||||
?.filter((v: string) => {
|
||||
return v
|
||||
})
|
||||
?.map((v: string) => {
|
||||
const vTmp = v
|
||||
const newValue: { [k: string]: unknown } = {}
|
||||
for (let index = 0; index < dataFormat.length; index++) {
|
||||
if (dataFormat[index]?.hideName) {
|
||||
newValue[dataFormat[index].key] = vTmp.split(' ')[index]
|
||||
} else {
|
||||
const vTmp2: string | string[] | undefined =
|
||||
vTmp.indexOf(' ') === -1
|
||||
? vTmp
|
||||
: vTmp.split(' ')[index]
|
||||
? vTmp.split(' ')[index].indexOf('=') === -1
|
||||
? ''
|
||||
: vTmp.split(' ')[index].split('=')
|
||||
: ''
|
||||
|
||||
if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) {
|
||||
vTmp2.shift()
|
||||
}
|
||||
newValue[dataFormat[index].key] =
|
||||
vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2
|
||||
if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) {
|
||||
vTmp2.shift()
|
||||
}
|
||||
newValue[dataFormat[index].key] = vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2
|
||||
}
|
||||
return newValue
|
||||
}),
|
||||
...getDefaultListItem()
|
||||
]
|
||||
: [...getDefaultListItem()]
|
||||
)
|
||||
}
|
||||
return newValue
|
||||
}),
|
||||
...getDefaultListItem()
|
||||
]
|
||||
: [...getDefaultListItem()]
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
const emitNewArr = () => {
|
||||
const newArr: Array<string> = []
|
||||
for (const r of resList) {
|
||||
if (r[dataFormat[0].key]) {
|
||||
newArr.push(
|
||||
dataFormat?.map((format: { key: string; hideName: boolean }) => {
|
||||
return format?.hideName
|
||||
? r[format.key]
|
||||
: `${format.key}=${r[format.key]}`
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
onChange(newArr)
|
||||
}
|
||||
|
||||
const changeInputValue = (
|
||||
newValue: string,
|
||||
index: number,
|
||||
keyName: string,
|
||||
dataFormat: unknown
|
||||
) => {
|
||||
const newArr = [...resList]
|
||||
newArr[index][keyName] = newValue
|
||||
newArr[index].status =
|
||||
(dataFormat.required && !newValue) ||
|
||||
(dataFormat.pattern && !dataFormat.pattern.test(newValue))
|
||||
? 'error'
|
||||
: ''
|
||||
setResList(newArr)
|
||||
emitNewArr()
|
||||
if (index === resList.length - 1) {
|
||||
setResList([...newArr, ...getDefaultListItem()])
|
||||
const emitNewArr = () => {
|
||||
const newArr: Array<string> = []
|
||||
for (const r of resList) {
|
||||
if (r[dataFormat[0].key]) {
|
||||
newArr.push(
|
||||
dataFormat
|
||||
?.map((format: { key: string; hideName: boolean }) => {
|
||||
return format?.hideName ? r[format.key] : `${format.key}=${r[format.key]}`
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
resList.splice(index + 1, 0, ...getDefaultListItem())
|
||||
const newKvList = [...resList]
|
||||
setResList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
resList.splice(index, 1)
|
||||
const newKvList = [...resList]
|
||||
setResList([...newKvList])
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div
|
||||
key={n + index}
|
||||
className="flex"
|
||||
style={{ marginTop: index === 0 ? '0px' : '16px' }}
|
||||
>
|
||||
{dataFormat?.map((data: unknown, index2: unknown) => {
|
||||
return (
|
||||
<Input
|
||||
key={data.key + index2}
|
||||
className="mr-[8px]"
|
||||
style={{ width: data.width }}
|
||||
value={n[data.key]}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, data.key, data)
|
||||
}}
|
||||
placeholder={data.placeholder || `请输入${data.key}`}
|
||||
status={n.status}
|
||||
type={data.type || 'text'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{index !== resList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n[dataFormat[0].key] && (
|
||||
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
|
||||
)}
|
||||
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
onChange(newArr)
|
||||
}
|
||||
)
|
||||
|
||||
const changeInputValue = (newValue: string, index: number, keyName: string, dataFormat: unknown) => {
|
||||
const newArr = [...resList]
|
||||
newArr[index][keyName] = newValue
|
||||
newArr[index].status =
|
||||
(dataFormat.required && !newValue) || (dataFormat.pattern && !dataFormat.pattern.test(newValue)) ? 'error' : ''
|
||||
setResList(newArr)
|
||||
emitNewArr()
|
||||
if (index === resList.length - 1) {
|
||||
setResList([...newArr, ...getDefaultListItem()])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
resList.splice(index + 1, 0, ...getDefaultListItem())
|
||||
const newKvList = [...resList]
|
||||
setResList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
resList.splice(index, 1)
|
||||
const newKvList = [...resList]
|
||||
setResList([...newKvList])
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div key={n + index} className="flex" style={{ marginTop: index === 0 ? '0px' : '16px' }}>
|
||||
{dataFormat?.map((data: unknown, index2: unknown) => {
|
||||
return (
|
||||
<Input
|
||||
key={data.key + index2}
|
||||
className="mr-[8px]"
|
||||
style={{ width: data.width }}
|
||||
value={n[data.key]}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, data.key, data)
|
||||
}}
|
||||
placeholder={data.placeholder || `请输入${data.key}`}
|
||||
status={n.status}
|
||||
type={data.type || 'text'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{index !== resList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n[dataFormat[0].key] && (
|
||||
<Icon
|
||||
icon="ic:baseline-add"
|
||||
onClick={() => addLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="ic:baseline-minus"
|
||||
onClick={() => removeLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+30
-42
@@ -1,47 +1,35 @@
|
||||
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
import { Codebox } from '@common/components/postcat/api/Codebox'
|
||||
|
||||
export const CustomCodeboxComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const {
|
||||
mode = 'yaml',
|
||||
theme = 'xcode',
|
||||
fontSize,
|
||||
height,
|
||||
width = '100%',
|
||||
onChange,
|
||||
value
|
||||
} = props
|
||||
const [code, setCode] = useState(
|
||||
mode === 'json' ? JSON.stringify(value) : value
|
||||
)
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
const handleChange = (value: string) => {
|
||||
setCode(value)
|
||||
let res = value
|
||||
if (mode === 'json') {
|
||||
try {
|
||||
res = JSON.parse(value)
|
||||
} catch {
|
||||
console.warn(' 输入的json语句格式有误')
|
||||
}
|
||||
export const CustomCodeboxComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { mode = 'yaml', theme = 'xcode', fontSize, height, width = '100%', onChange, value } = props
|
||||
const [code, setCode] = useState(mode === 'json' ? JSON.stringify(value) : value)
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
const handleChange = (value: string) => {
|
||||
setCode(value)
|
||||
let res = value
|
||||
if (mode === 'json') {
|
||||
try {
|
||||
res = JSON.parse(value)
|
||||
} catch {
|
||||
console.warn(' 输入的json语句格式有误')
|
||||
}
|
||||
onChange(res)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" mt-[4px] border-[1px] border-solid border-BORDER">
|
||||
<Codebox
|
||||
value={code}
|
||||
language={mode}
|
||||
enableToolbar={false}
|
||||
theme={theme}
|
||||
fontSize={fontSize}
|
||||
height={height ?? 500}
|
||||
width={width}
|
||||
onChange={handleChange} />
|
||||
</div>
|
||||
)
|
||||
onChange(res)
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className=" mt-[4px] border-[1px] border-solid border-BORDER">
|
||||
<Codebox
|
||||
value={code}
|
||||
language={mode}
|
||||
enableToolbar={false}
|
||||
theme={theme}
|
||||
fontSize={fontSize}
|
||||
height={height ?? 500}
|
||||
width={width}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+48
-53
@@ -1,5 +1,4 @@
|
||||
|
||||
import {forwardRef,useImperativeHandle} from 'react'
|
||||
import { forwardRef, useImperativeHandle } from 'react'
|
||||
import { createSchemaField } from '@formily/react'
|
||||
import {
|
||||
FormItem,
|
||||
@@ -82,57 +81,53 @@ const SchemaField = createSchemaField({
|
||||
}
|
||||
})
|
||||
|
||||
export const CustomDialogComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, title, value, render } = props
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
let editPage: boolean = false
|
||||
try {
|
||||
editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0
|
||||
} catch {}
|
||||
export const CustomDialogComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, title, value, render } = props
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
let editPage: boolean = false
|
||||
try {
|
||||
editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0
|
||||
} catch {}
|
||||
|
||||
return (
|
||||
<FormDialog.Portal>
|
||||
<span
|
||||
className="ant-formily-array-base-config"
|
||||
onClick={() => {
|
||||
const dialog = FormDialog(
|
||||
editPage ? $t('编辑(0)',[title||'']) : $t('添加(0)',[title||'']),
|
||||
() => {
|
||||
return (
|
||||
<FormLayout
|
||||
// labelCol={6}
|
||||
layout={'vertical'}
|
||||
scrollToFirstError
|
||||
name="CustomDialogComponent"
|
||||
// wrapperCol={10}
|
||||
form={value}>
|
||||
<SchemaField schema={JSON.parse(render)} />
|
||||
</FormLayout>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<FormDialog.Portal>
|
||||
<span
|
||||
className="ant-formily-array-base-config"
|
||||
onClick={() => {
|
||||
const dialog = FormDialog(editPage ? $t('编辑(0)', [title || '']) : $t('添加(0)', [title || '']), () => {
|
||||
return (
|
||||
<FormLayout
|
||||
// labelCol={6}
|
||||
layout={'vertical'}
|
||||
scrollToFirstError
|
||||
name="CustomDialogComponent"
|
||||
// wrapperCol={10}
|
||||
form={value}
|
||||
>
|
||||
<SchemaField schema={JSON.parse(render)} />
|
||||
</FormLayout>
|
||||
)
|
||||
dialog
|
||||
.forOpen((payload, next) => {
|
||||
next({
|
||||
initialValues: value
|
||||
})
|
||||
})
|
||||
dialog
|
||||
.forOpen((payload, next) => {
|
||||
next({
|
||||
initialValues: value
|
||||
})
|
||||
.forConfirm((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.forCancel((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.open()
|
||||
.then(onChange)
|
||||
}}
|
||||
>
|
||||
<svg style={{ width: '16px', height: '16px' }}>
|
||||
<use href="#tool"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</FormDialog.Portal>
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
.forConfirm((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.forCancel((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.open()
|
||||
.then(onChange)
|
||||
}}
|
||||
>
|
||||
<svg style={{ width: '16px', height: '16px' }}>
|
||||
<use href="#tool"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</FormDialog.Portal>
|
||||
)
|
||||
})
|
||||
|
||||
+90
-95
@@ -1,104 +1,99 @@
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
import { Input } from '@formily/antd-v5'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
export const SimpleMapComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
value,
|
||||
placeholderKey = $t('请输入Key'),
|
||||
placeholderValue = $t('请输入Value')
|
||||
} = props
|
||||
export const SimpleMapComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, value, placeholderKey = $t('请输入Key'), placeholderValue = $t('请输入Value') } = props
|
||||
|
||||
const [kvList, setKvList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...Object.keys(value)?.map((k: string) => {
|
||||
return { key: k, value: value[k] }
|
||||
}),
|
||||
{ key: '', value: '' }
|
||||
]
|
||||
: [{ key: '', value: '' }]
|
||||
)
|
||||
const [kvList, setKvList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...Object.keys(value)?.map((k: string) => {
|
||||
return { key: k, value: value[k] }
|
||||
}),
|
||||
{ key: '', value: '' }
|
||||
]
|
||||
: [{ key: '', value: '' }]
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
const emitNewArr = () => {
|
||||
const res: { [k: string]: unknown } = {}
|
||||
for (const kv of kvList) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
onChange(res)
|
||||
const emitNewArr = () => {
|
||||
const res: { [k: string]: unknown } = {}
|
||||
for (const kv of kvList) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
|
||||
const changeInputValue = (
|
||||
newValue: string,
|
||||
index: number,
|
||||
type: 'key' | 'value'
|
||||
) => {
|
||||
const newArr = [...kvList]
|
||||
newArr[index][type] = newValue
|
||||
setKvList(newArr)
|
||||
emitNewArr()
|
||||
if (index === kvList.length - 1) {
|
||||
setKvList([...newArr, { key: '', value: '' }])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
kvList.splice(index + 1, 0, { key: '', value: '' })
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
kvList.splice(index, 1)
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kvList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div
|
||||
key={n + index}
|
||||
className="flex"
|
||||
style={{ marginTop: index === 0 ? '0px' : '16px' }}
|
||||
>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL mr-[8px]"
|
||||
style={{ width: '174px' }}
|
||||
value={n.key}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'key')
|
||||
}}
|
||||
placeholder={placeholderKey}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '164px', marginRight: '10px' }}
|
||||
value={n.value}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'value')
|
||||
}}
|
||||
placeholder={placeholderValue}
|
||||
/>
|
||||
{index !== kvList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n.key && (
|
||||
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
|
||||
)}
|
||||
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
onChange(res)
|
||||
}
|
||||
)
|
||||
|
||||
const changeInputValue = (newValue: string, index: number, type: 'key' | 'value') => {
|
||||
const newArr = [...kvList]
|
||||
newArr[index][type] = newValue
|
||||
setKvList(newArr)
|
||||
emitNewArr()
|
||||
if (index === kvList.length - 1) {
|
||||
setKvList([...newArr, { key: '', value: '' }])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
kvList.splice(index + 1, 0, { key: '', value: '' })
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
kvList.splice(index, 1)
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kvList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div key={n + index} className="flex" style={{ marginTop: index === 0 ? '0px' : '16px' }}>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL mr-[8px]"
|
||||
style={{ width: '174px' }}
|
||||
value={n.key}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'key')
|
||||
}}
|
||||
placeholder={placeholderKey}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '164px', marginRight: '10px' }}
|
||||
value={n.value}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'value')
|
||||
}}
|
||||
placeholder={placeholderValue}
|
||||
/>
|
||||
{index !== kvList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n.key && (
|
||||
<Icon
|
||||
icon="ic:baseline-add"
|
||||
onClick={() => addLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="ic:baseline-minus"
|
||||
onClick={() => removeLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+292
-286
@@ -1,327 +1,333 @@
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'
|
||||
import { action } from '@formily/reactive'
|
||||
import {
|
||||
FormItem,
|
||||
Space,
|
||||
ArrayItems,
|
||||
DatePicker,
|
||||
Editable,
|
||||
FormButtonGroup,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Submit,
|
||||
Cascader,
|
||||
Form,
|
||||
FormGrid,
|
||||
FormLayout,
|
||||
Upload,
|
||||
FormItem,
|
||||
Space,
|
||||
ArrayItems,
|
||||
DatePicker,
|
||||
Editable,
|
||||
FormButtonGroup,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Submit,
|
||||
Cascader,
|
||||
Form,
|
||||
FormGrid,
|
||||
FormLayout,
|
||||
Upload,
|
||||
ArrayCollapse,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Checkbox,
|
||||
FormCollapse,
|
||||
FormDialog,
|
||||
FormDrawer,
|
||||
FormStep,
|
||||
FormTab,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Reset,
|
||||
SelectTable,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
ArrayCards
|
||||
} from '@formily/antd-v5'
|
||||
import { createForm } from '@formily/core'
|
||||
import { CustomCodeboxComponent } from '@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx'
|
||||
import { SimpleMapComponent } from '@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx'
|
||||
import { CustomDialogComponent } from '@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx'
|
||||
import { ArrayItemBlankComponent } from '@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { createSchemaField, FormProvider, RecursionField, useField, useForm } from '@formily/react'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { App } from 'antd'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { setValidateLanguage } from '@formily/core'
|
||||
|
||||
export const DynamicRender = (props) => {
|
||||
const { schema } = props
|
||||
const field = useField()
|
||||
const form = useForm()
|
||||
const [renderSchema, setRenderSchema] = useState({})
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
useEffect(() => {
|
||||
form.clearFormGraph(`${field.address}.*`)
|
||||
try {
|
||||
const parsedSchema = JSON.parse(schema)
|
||||
setRenderSchema(parsedSchema[form?.values?.driver])
|
||||
} catch (e) {
|
||||
console.error('渲染出错', e?.message)
|
||||
}
|
||||
}, [form.values.driver])
|
||||
|
||||
const translateSchema = (render) => {
|
||||
const res1 = {
|
||||
...render,
|
||||
...(render.title ? { title: $t(render.title) } : {}),
|
||||
...(render.description ? { description: $t(render.description) } : {}),
|
||||
...(render.label ? { label: $t(render.label) } : {}),
|
||||
...(render.properties
|
||||
? {
|
||||
properties: Object.keys(render.properties).reduce((total, cur) => {
|
||||
try {
|
||||
total[cur] = translateSchema(render.properties[cur])
|
||||
} catch (error) {
|
||||
console.error(`Error translating schema for property ${cur}:`, error)
|
||||
}
|
||||
return total
|
||||
}, {})
|
||||
}
|
||||
: {}),
|
||||
...(render.items && Array.isArray(render.items) ? { items: render.items.map((x) => translateSchema(x)) } : {}),
|
||||
...(render.items && !Array.isArray(render.items) ? { items: translateSchema(render.items) } : {}),
|
||||
...(render.additionalProperties ? { additionalProperties: translateSchema(render.additionalProperties) } : {}),
|
||||
...(render.enum ? { enum: render.enum.map((x) => ({ ...x, label: $t(x.label) })) } : {})
|
||||
}
|
||||
return res1
|
||||
}
|
||||
|
||||
const translatedRenderSchema = useMemo(() => {
|
||||
const res = renderSchema && translateSchema(renderSchema)
|
||||
return res
|
||||
}, [state.language, renderSchema])
|
||||
|
||||
return <RecursionField basePath={field.address} schema={translatedRenderSchema} onlyRenderProperties />
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigProps = {
|
||||
type: 'add' | 'edit'
|
||||
renderSchema: unknown
|
||||
tabData: DefaultOptionType[]
|
||||
moduleId: string
|
||||
driverSelectionOptions: DefaultOptionType[]
|
||||
entityId?: string
|
||||
initFormValue: { [k: string]: unknown }
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigHandle = {
|
||||
save: () => Promise<boolean | string>
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
ArrayCards,
|
||||
ArrayCollapse,
|
||||
ArrayItems,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Editable,
|
||||
Form,
|
||||
FormButtonGroup,
|
||||
FormCollapse,
|
||||
// @ts-ignore
|
||||
FormDialog,
|
||||
// @ts-ignore
|
||||
FormDrawer,
|
||||
FormGrid,
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormStep,
|
||||
FormTab,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Radio,
|
||||
Reset,
|
||||
Select,
|
||||
SelectTable,
|
||||
Space,
|
||||
Submit,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
ArrayCards
|
||||
} from '@formily/antd-v5'
|
||||
import { createForm } from '@formily/core'
|
||||
import {CustomCodeboxComponent} from "@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx";
|
||||
import {SimpleMapComponent} from "@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx";
|
||||
import {CustomDialogComponent} from "@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx";
|
||||
import {ArrayItemBlankComponent} from "@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx";
|
||||
import {DefaultOptionType} from "antd/es/cascader";
|
||||
import {createSchemaField, FormProvider, RecursionField, useField, useForm} from "@formily/react";
|
||||
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {App, Descriptions} from "antd";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { setValidateLanguage } from '@formily/core'
|
||||
|
||||
export const DynamicRender = (props) => {
|
||||
const {schema} = props
|
||||
const field = useField()
|
||||
const form = useForm()
|
||||
const [renderSchema, setRenderSchema] = useState({})
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
useEffect(() => {
|
||||
form.clearFormGraph(`${field.address}.*`)
|
||||
try{
|
||||
const parsedSchema = JSON.parse(schema)
|
||||
setRenderSchema(parsedSchema[form?.values?.driver])
|
||||
}catch(e){
|
||||
console.error('渲染出错',e?.message)
|
||||
}
|
||||
}, [form.values.driver])
|
||||
|
||||
|
||||
const translateSchema = (render) =>{
|
||||
const res1 = {
|
||||
...render,
|
||||
...(render.title ? {title:$t(render.title)} : {}),
|
||||
...(render.description) ? {description:$t(render.description)} : {},
|
||||
...(render.label ? {label:$t(render.label)} : {}),
|
||||
...(render.properties ? {properties: Object.keys(render.properties).reduce((total, cur) => {
|
||||
try {
|
||||
total[cur] = translateSchema(render.properties[cur]);
|
||||
} catch (error) {
|
||||
console.error(`Error translating schema for property ${cur}:`, error);
|
||||
}
|
||||
return total;
|
||||
}, {})} : {}),
|
||||
...(render.items && Array.isArray(render.items) ? {items:render.items.map(x=>translateSchema(x))} : {}),
|
||||
...(render.items && !Array.isArray(render.items) ? {items:translateSchema(render.items)} : {}),
|
||||
...(render.additionalProperties ? {additionalProperties: translateSchema(render.additionalProperties)} : {}),
|
||||
...(render.enum ? {enum: render.enum.map(x=>({...x, label:$t(x.label)}))} : {}),
|
||||
|
||||
}
|
||||
return res1
|
||||
}
|
||||
|
||||
const translatedRenderSchema = useMemo(()=>{
|
||||
const res = renderSchema && translateSchema(renderSchema)
|
||||
return res
|
||||
},[state.language,renderSchema])
|
||||
|
||||
return (
|
||||
<RecursionField
|
||||
basePath={field.address}
|
||||
schema={translatedRenderSchema}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type IntelligentPluginConfigProps = {
|
||||
type:'add'|'edit'
|
||||
renderSchema:unknown
|
||||
tabData:DefaultOptionType[]
|
||||
moduleId:string
|
||||
driverSelectionOptions:DefaultOptionType[]
|
||||
entityId?:string
|
||||
initFormValue:{[k:string]:unknown}
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigHandle = {
|
||||
save:()=>Promise<boolean | string>
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
ArrayCards,
|
||||
ArrayCollapse,
|
||||
ArrayItems,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Editable,
|
||||
Form,
|
||||
FormButtonGroup,
|
||||
FormCollapse,
|
||||
// @ts-ignore
|
||||
FormDialog,
|
||||
// @ts-ignore
|
||||
FormDrawer,
|
||||
FormGrid,
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormStep,
|
||||
FormTab,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Radio,
|
||||
Reset,
|
||||
Select,
|
||||
SelectTable,
|
||||
Space,
|
||||
Submit,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
CustomCodeboxComponent,
|
||||
SimpleMapComponent,
|
||||
CustomDialogComponent,
|
||||
ArrayItemBlankComponent,
|
||||
DynamicRender
|
||||
}
|
||||
Upload,
|
||||
CustomCodeboxComponent,
|
||||
SimpleMapComponent,
|
||||
CustomDialogComponent,
|
||||
ArrayItemBlankComponent,
|
||||
DynamicRender
|
||||
}
|
||||
})
|
||||
|
||||
export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle,IntelligentPluginConfigProps>((props,ref)=>{
|
||||
const { type,renderSchema,moduleId,driverSelectionOptions,initFormValue} = props
|
||||
export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle, IntelligentPluginConfigProps>(
|
||||
(props, ref) => {
|
||||
const { type, renderSchema, moduleId, driverSelectionOptions, initFormValue } = props
|
||||
const { message } = App.useApp()
|
||||
const {fetchData} = useFetch()
|
||||
const { fetchData } = useFetch()
|
||||
const form = createForm({ validateFirst: type === 'edit' })
|
||||
form.setInitialValues(initFormValue || {})
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
useEffect(()=>{
|
||||
setValidateLanguage(state.language)
|
||||
},[state.language])
|
||||
useEffect(() => {
|
||||
setValidateLanguage(state.language)
|
||||
}, [state.language])
|
||||
|
||||
const pluginEditSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
layout: 'vertical',
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
layout: 'vertical'
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: $t('ID'),
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: $t('ID'),
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet),
|
||||
},
|
||||
'x-disabled': type === 'edit'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: $t('名称'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input),
|
||||
}
|
||||
},
|
||||
driver: {
|
||||
type: 'string',
|
||||
title: $t('Driver'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
disabled: type === 'edit'
|
||||
},
|
||||
'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden',
|
||||
enum: [...driverSelectionOptions]
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
title: $t('描述'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input),
|
||||
}
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
'x-component': 'DynamicRender',
|
||||
'x-component-props': {
|
||||
schema: JSON.stringify(renderSchema),
|
||||
}
|
||||
}
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet)
|
||||
},
|
||||
'x-disabled': type === 'edit'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: $t('名称'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input)
|
||||
}
|
||||
},
|
||||
driver: {
|
||||
type: 'string',
|
||||
title: $t('Driver'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
disabled: type === 'edit'
|
||||
},
|
||||
'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden',
|
||||
enum: [...driverSelectionOptions]
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
title: $t('描述'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input)
|
||||
}
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
'x-component': 'DynamicRender',
|
||||
'x-component-props': {
|
||||
schema: JSON.stringify(renderSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const save :()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validate().then(()=>{
|
||||
fetchData<BasicResponse<null>>(type === 'add'?`dynamic/${moduleId}`:`dynamic/${moduleId}/config`,{method:type === 'add'? 'POST' : 'PUT',eoBody:form.values, eoParams:{...(type !== 'add' && {id:initFormValue.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))
|
||||
}).catch((errorInfo:unknown)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save
|
||||
})
|
||||
)
|
||||
const save: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validate()
|
||||
.then(() => {
|
||||
fetchData<BasicResponse<null>>(type === 'add' ? `dynamic/${moduleId}` : `dynamic/${moduleId}/config`, {
|
||||
method: type === 'add' ? 'POST' : 'PUT',
|
||||
eoBody: form.values,
|
||||
eoParams: { ...(type !== 'add' && { id: initFormValue.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))
|
||||
})
|
||||
.catch((errorInfo: unknown) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
const getSkillData = async (skill: string) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
fetchData<BasicResponse<{[k:string]:Array<{name:string,title:string}>}>>(`api/common/provider/${skill}`,{method:'GET'}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
resolve(data[skill]?.map((x:{name:string,title:string})=>{return{label:x.title, value:x.name}}) || [])
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<{ [k: string]: Array<{ name: string; title: string }> }>>(
|
||||
`api/common/provider/${skill}`,
|
||||
{ method: 'GET' }
|
||||
).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
resolve(
|
||||
data[skill]?.map((x: { name: string; title: string }) => {
|
||||
return { label: x.title, value: x.name }
|
||||
}) || []
|
||||
)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const useAsyncDataSource =
|
||||
(service: unknown, skill: string) => (field: unknown) => {
|
||||
field.loading = true
|
||||
service(skill).then(
|
||||
action.bound &&
|
||||
action.bound((data: unknown) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
const useAsyncDataSource = (service: unknown, skill: string) => (field: unknown) => {
|
||||
field.loading = true
|
||||
service(skill).then(
|
||||
action.bound &&
|
||||
action.bound((data: unknown) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="pl-[12px]">
|
||||
<FormProvider form={form} >
|
||||
<SchemaField
|
||||
schema={pluginEditSchema}
|
||||
scope={{ useAsyncDataSource, getSkillData, form }}
|
||||
/>
|
||||
</FormProvider>
|
||||
</div>)
|
||||
})
|
||||
<div className="pl-[12px]">
|
||||
<FormProvider form={form}>
|
||||
<SchemaField schema={pluginEditSchema} scope={{ useAsyncDataSource, getSkillData, form }} />
|
||||
</FormProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
+400
-315
@@ -1,349 +1,434 @@
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {App, Divider, Spin} from "antd";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import { useLocation, useOutletContext, useParams} from "react-router-dom";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {ActionType, ParamsType} from "@ant-design/pro-components";
|
||||
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import {DefaultOptionType} from "antd/es/cascader";
|
||||
import {IntelligentPluginConfig, IntelligentPluginConfigHandle} from "./IntelligentPluginConfig.tsx";
|
||||
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {EntityItem} from "@common/const/type.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx";
|
||||
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { ActionType, ParamsType } from '@ant-design/pro-components'
|
||||
import { DrawerWithFooter } from '@common/components/aoplatform/DrawerWithFooter.tsx'
|
||||
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx'
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Divider, Spin } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useLocation, useOutletContext, useParams } from 'react-router-dom'
|
||||
import { IntelligentPluginConfig, IntelligentPluginConfigHandle } from './IntelligentPluginConfig.tsx'
|
||||
|
||||
type DynamicTableField = {
|
||||
name: string,
|
||||
title: string,
|
||||
attr: string,
|
||||
enum: Array<string>
|
||||
type DynamicTableField = {
|
||||
name: string
|
||||
title: string
|
||||
attr: string
|
||||
enum: Array<string>
|
||||
}
|
||||
|
||||
type DynamicDriverData = {
|
||||
name:string, title:string
|
||||
type DynamicDriverData = {
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type DynamicTableConfig = {
|
||||
basic:{
|
||||
id:string,
|
||||
name: string,
|
||||
title: string,
|
||||
drivers: Array<DynamicDriverData>,
|
||||
fields: Array<DynamicTableField>,
|
||||
}
|
||||
list: Array<DynamicTableItem>,
|
||||
total:number
|
||||
basic: {
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
drivers: Array<DynamicDriverData>
|
||||
fields: Array<DynamicTableField>
|
||||
}
|
||||
list: Array<DynamicTableItem>
|
||||
total: number
|
||||
}
|
||||
|
||||
export type DynamicRender = {
|
||||
render:unknown,
|
||||
basic:{
|
||||
id:string,
|
||||
name:string,
|
||||
title:string
|
||||
}
|
||||
render: unknown
|
||||
basic: {
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
export type DynamicPublishCluster = {
|
||||
name:string,
|
||||
title:string,
|
||||
status:string,
|
||||
updater:EntityItem,
|
||||
update_time:string,
|
||||
checked?:boolean
|
||||
name: string
|
||||
title: string
|
||||
status: string
|
||||
updater: EntityItem
|
||||
update_time: string
|
||||
checked?: boolean
|
||||
}
|
||||
|
||||
export type DynamicPublishData = {
|
||||
id:string,
|
||||
name:string,
|
||||
title:string,
|
||||
description:string
|
||||
clusters:DynamicPublishCluster[]
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
description: string
|
||||
clusters: DynamicPublishCluster[]
|
||||
}
|
||||
|
||||
export type DynamicTableItem = {[k:string]:unknown}
|
||||
export type DynamicTableItem = { [k: string]: unknown }
|
||||
|
||||
export const StatusColorClass = {
|
||||
"已发布":'text-[#03a9f4]',
|
||||
"待发布":'text-[#46BE11]',
|
||||
"未发布":'text-[#03a9f4]'
|
||||
已发布: 'text-[#03a9f4]',
|
||||
待发布: 'text-[#46BE11]',
|
||||
未发布: 'text-[#03a9f4]'
|
||||
}
|
||||
|
||||
|
||||
export type DynamicPublish = {
|
||||
code:number,
|
||||
msg:string,
|
||||
data:{
|
||||
success:Array<string>,
|
||||
fail:Array<string>
|
||||
}
|
||||
code: number
|
||||
msg: string
|
||||
data: {
|
||||
success: Array<string>
|
||||
fail: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
export default function IntelligentPluginList(){
|
||||
const { modal,message } = App.useApp()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { moduleId } = useParams<RouterParams>();
|
||||
const [pluginName,setPluginName] = useState<string>('-')
|
||||
const [partitionOptions] = useState<DefaultOptionType[]>([{label:'default', value:'default'}])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [renderSchema ,setRenderSchema] = useState<{[k:string]:unknown}>({})
|
||||
const drawerFormRef = useRef<IntelligentPluginConfigHandle>(null);
|
||||
const [driverOptions, setDriverOptions] = useState<DefaultOptionType[]>([])
|
||||
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([]);
|
||||
export default function IntelligentPluginList() {
|
||||
const { modal, message } = App.useApp()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { moduleId } = useParams<RouterParams>()
|
||||
const [pluginName, setPluginName] = useState<string>('-')
|
||||
const [partitionOptions] = useState<DefaultOptionType[]>([{ label: 'default', value: 'default' }])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [renderSchema, setRenderSchema] = useState<{ [k: string]: unknown }>({})
|
||||
const drawerFormRef = useRef<IntelligentPluginConfigHandle>(null)
|
||||
const [driverOptions, setDriverOptions] = useState<DefaultOptionType[]>([])
|
||||
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([])
|
||||
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
const [columns,setColumns] = useState<DynamicTableField[] >([])
|
||||
const {fetchData} = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
|
||||
const [curDetail,setCurDetail] = useState<{[k: string]: unknown;}|undefined>()
|
||||
const [drawerType, setDrawerType] = useState<'add'|'edit'>('add')
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
|
||||
const location = useLocation().pathname
|
||||
const {accessPrefix} = useOutletContext<{accessPrefix:string}>()
|
||||
const {state} = useGlobalContext()
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
const [columns, setColumns] = useState<DynamicTableField[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
|
||||
const [curDetail, setCurDetail] = useState<{ [k: string]: unknown } | undefined>()
|
||||
const [drawerType, setDrawerType] = useState<'add' | 'edit'>('add')
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
|
||||
const location = useLocation().pathname
|
||||
const { accessPrefix } = useOutletContext<{ accessPrefix: string }>()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
|
||||
const getIntelligentPluginTableList=(params:ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
keyword?: string | undefined;
|
||||
}): Promise<{ data: DynamicTableItem[], success: boolean }>=> {
|
||||
if(!tableHttpReload){
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true,
|
||||
});
|
||||
const getIntelligentPluginTableList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
): Promise<{ data: DynamicTableItem[]; success: boolean }> => {
|
||||
if (!tableHttpReload) {
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true
|
||||
})
|
||||
}
|
||||
const query = {
|
||||
page: params.current,
|
||||
pageSize: params.pageSize,
|
||||
keyword: searchWord
|
||||
}
|
||||
return fetchData<BasicResponse<DynamicTableConfig>>(`dynamic/${moduleId}/list`, {
|
||||
method: 'GET',
|
||||
eoParams: query,
|
||||
eoTransformKeys: ['pageSize']
|
||||
})
|
||||
.then((res) => {
|
||||
message.destroy()
|
||||
if (res.code === STATUS_CODE.SUCCESS) {
|
||||
getConfig(res.data)
|
||||
setColumns(res.data.basic.fields)
|
||||
setTableListDataSource(res.data.list)
|
||||
return { data: res.data.list, success: true, total: res.data.total }
|
||||
} else {
|
||||
setTableListDataSource([])
|
||||
return { data: [], success: false }
|
||||
}
|
||||
const query = {
|
||||
page:params.current,
|
||||
pageSize:params.pageSize,
|
||||
keyword:searchWord,
|
||||
}
|
||||
return fetchData<BasicResponse<DynamicTableConfig>>(
|
||||
`dynamic/${moduleId}/list`,
|
||||
{method:'GET',eoParams:query,eoTransformKeys:['pageSize']}).then((res)=>{
|
||||
message.destroy();
|
||||
if(res.code === STATUS_CODE.SUCCESS){
|
||||
getConfig(res.data)
|
||||
setColumns(res.data.basic.fields)
|
||||
setTableListDataSource(res.data.list);
|
||||
return ({ data: res.data.list, success: true,total:res.data.total });
|
||||
}else{
|
||||
setTableListDataSource([]);
|
||||
return ({ data: [], success: false });
|
||||
}
|
||||
}).catch((e)=>{console.warn(e);
|
||||
return ({ data: [], success: false });})
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn(e)
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
|
||||
const translatedCol = useMemo(()=>columns.map((field:DynamicTableField, index:number)=>({
|
||||
title: typeof field.title === 'string' ? $t(field.title as string): field.title,
|
||||
dataIndex:field.name,
|
||||
fixed:field.name === 'title' ? 'left' : undefined,
|
||||
ellipsis:true,
|
||||
width:field.name === 'title' ? 150 : undefined,
|
||||
...(field.enum?.length > 0 ?{
|
||||
onFilter: (value: string, record: { [x: string]: string | string[]; }) => record[field.name].indexOf(value) === 0,
|
||||
filters:field.enum?.map((x:string)=>{return {text:$t(x), value:x}}),
|
||||
render:(_: unknown, entity: { [x: string]: string; })=> {
|
||||
return <span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>{$t(entity[field.name] as string)}</span>
|
||||
},
|
||||
}:{}),
|
||||
})),[state.language,columns])
|
||||
|
||||
|
||||
|
||||
const getConfig = (data:DynamicTableConfig)=>{
|
||||
const {basic,list } = data
|
||||
const {title,drivers} = basic
|
||||
|
||||
setBreadcrumb([
|
||||
{title:location.includes('resourcesettings') ? $t('资源'): $t('日志')},
|
||||
{
|
||||
title
|
||||
}
|
||||
])
|
||||
|
||||
setPluginName(title)
|
||||
setDriverOptions(drivers?.map((driver:DynamicDriverData) => {
|
||||
return { label: driver.title, value: driver.name }
|
||||
}) || [])
|
||||
|
||||
}
|
||||
|
||||
const getRender = ()=>{
|
||||
return fetchData<BasicResponse<DynamicRender>>(`dynamic/${moduleId}/render`,{method:'GET'}).then((resp) => {
|
||||
if (resp.code === STATUS_CODE.SUCCESS) {
|
||||
setRenderSchema(resp.data.render)
|
||||
return Promise.resolve(resp.data.render)
|
||||
}
|
||||
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
const operation:PageProColumns<DynamicTableItem>[] =[
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
fixed:'right',
|
||||
valueType: 'option',
|
||||
btnNums:3,
|
||||
render: (_: React.ReactNode, entity: DynamicTableItem) => [
|
||||
<TableBtnWithPermission access={`${accessPrefix}.publish`} key="publish" btnType="publish" onClick={()=>{openModal('publish',entity)}} btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1"/>,
|
||||
<TableBtnWithPermission access={`${accessPrefix}.view`} key="edit" btnType="edit" onClick={()=>{openDrawer('edit',entity)}} btnTitle={$t("查看")}/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2"/>,
|
||||
<TableBtnWithPermission access={`${accessPrefix}.delete`} key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle={$t("删除")}/>,
|
||||
],
|
||||
}
|
||||
]
|
||||
const handleClusterChange = (e:string[])=>{
|
||||
setTableHttpReload(true)
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true); // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
};
|
||||
|
||||
const deleteInstance = (entity:DynamicTableItem)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
fetchData<BasicResponse<null>>(`dynamic/${moduleId}/batch`,{method:'DELETE',eoParams:{ids:JSON.stringify([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))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = async (type:'add'|'edit', entity?:DynamicTableItem)=>{
|
||||
switch (type){
|
||||
case 'add':
|
||||
setCurDetail({driver:driverOptions[0].value || '',config:{}})
|
||||
break;
|
||||
case 'edit':{
|
||||
setDrawerLoading(true)
|
||||
fetchData<BasicResponse<{info:DynamicTableItem}>>(
|
||||
`dynamic/${moduleId}/info`,
|
||||
{method:'GET',eoParams:{id:entity!.id}}).then((res)=>{
|
||||
const {code, data, msg } = res
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
if(data.info.config){
|
||||
}
|
||||
setCurDetail(data.info)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>setDrawerLoading(false))
|
||||
break;
|
||||
}
|
||||
}
|
||||
setDrawerType(type)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
const openModal = async (type:'publish'|'delete', entity?:DynamicTableItem)=>{
|
||||
let title:string = ''
|
||||
let content:string|React.ReactNode = ''
|
||||
switch (type){
|
||||
case 'publish':{
|
||||
message.loading($t(RESPONSE_TIPS.operating))
|
||||
await fetchData<BasicResponse<DynamicPublish>>(`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline':'online'}`, {
|
||||
method: 'PUT',
|
||||
eoParams:{id:entity!.id},
|
||||
}).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((errorInfo)=> Promise.reject(errorInfo))
|
||||
message.destroy()
|
||||
return;}
|
||||
case 'delete':
|
||||
title='删除'
|
||||
content=<span>{$t(DELETE_TIPS.default)}</span>
|
||||
break;
|
||||
}
|
||||
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk:()=>{
|
||||
switch (type){ // case 'publish':
|
||||
// return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()})
|
||||
case 'delete':
|
||||
return deleteInstance(entity!).then((res)=>{if(res === true) manualReloadTable()})
|
||||
}
|
||||
},
|
||||
width: type === 'delete'? 600 : 900,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
disabled:false
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>,
|
||||
footer:(_, { OkBtn, CancelBtn }) =>{
|
||||
const translatedCol = useMemo(
|
||||
() =>
|
||||
columns.map((field: DynamicTableField, index: number) => ({
|
||||
title: typeof field.title === 'string' ? $t(field.title as string) : field.title,
|
||||
dataIndex: field.name,
|
||||
fixed: field.name === 'title' ? 'left' : undefined,
|
||||
ellipsis: true,
|
||||
width: field.name === 'title' ? 150 : undefined,
|
||||
...(field.enum?.length > 0
|
||||
? {
|
||||
onFilter: (value: string, record: { [x: string]: string | string[] }) =>
|
||||
record[field.name].indexOf(value) === 0,
|
||||
filters: field.enum?.map((x: string) => {
|
||||
return { text: $t(x), value: x }
|
||||
}),
|
||||
render: (_: unknown, entity: { [x: string]: string }) => {
|
||||
return (
|
||||
<>
|
||||
<WithPermission access=""><CancelBtn/></WithPermission>
|
||||
<WithPermission access=""><OkBtn/></WithPermission>
|
||||
</>
|
||||
);
|
||||
},
|
||||
<span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>
|
||||
{$t(entity[field.name] as string)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})),
|
||||
[state.language, columns]
|
||||
)
|
||||
|
||||
const getConfig = (data: DynamicTableConfig) => {
|
||||
const { basic, list } = data
|
||||
const { title, drivers } = basic
|
||||
|
||||
setBreadcrumb([
|
||||
{ title: location.includes('resourcesettings') ? $t('资源') : $t('日志') },
|
||||
{
|
||||
title
|
||||
}
|
||||
])
|
||||
|
||||
setPluginName(title)
|
||||
setDriverOptions(
|
||||
drivers?.map((driver: DynamicDriverData) => {
|
||||
return { label: driver.title, value: driver.name }
|
||||
}) || []
|
||||
)
|
||||
}
|
||||
|
||||
const getRender = () => {
|
||||
return fetchData<BasicResponse<DynamicRender>>(`dynamic/${moduleId}/render`, { method: 'GET' }).then((resp) => {
|
||||
if (resp.code === STATUS_CODE.SUCCESS) {
|
||||
setRenderSchema(resp.data.render)
|
||||
return Promise.resolve(resp.data.render)
|
||||
}
|
||||
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
const operation: PageProColumns<DynamicTableItem>[] = [
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
fixed: 'right',
|
||||
valueType: 'option',
|
||||
btnNums: 3,
|
||||
render: (_: React.ReactNode, entity: DynamicTableItem) => [
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.publish`}
|
||||
key={entity.status === $t('已发布') ? 'offline' : 'publish'}
|
||||
btnType={entity.status === $t('已发布') ? 'offline' : 'publish'}
|
||||
onClick={() => {
|
||||
openModal('publish', entity)
|
||||
}}
|
||||
btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}
|
||||
/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1" />,
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.view`}
|
||||
key="edit"
|
||||
btnType="edit"
|
||||
onClick={() => {
|
||||
openDrawer('edit', entity)
|
||||
}}
|
||||
btnTitle={$t('查看 ')}
|
||||
/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2" />,
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.delete`}
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
onClick={() => {
|
||||
openModal('delete', entity)
|
||||
}}
|
||||
btnTitle={$t('删除')}
|
||||
/>
|
||||
]
|
||||
}
|
||||
]
|
||||
const handleClusterChange = (e: string[]) => {
|
||||
setTableHttpReload(true)
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true) // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const deleteInstance = (entity: DynamicTableItem) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<null>>(`dynamic/${moduleId}/batch`, {
|
||||
method: 'DELETE',
|
||||
eoParams: { ids: JSON.stringify([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))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = async (type: 'add' | 'edit', entity?: DynamicTableItem) => {
|
||||
switch (type) {
|
||||
case 'add':
|
||||
setCurDetail({ driver: driverOptions[0].value || '', config: {} })
|
||||
break
|
||||
case 'edit': {
|
||||
setDrawerLoading(true)
|
||||
fetchData<BasicResponse<{ info: DynamicTableItem }>>(`dynamic/${moduleId}/info`, {
|
||||
method: 'GET',
|
||||
eoParams: { id: entity!.id }
|
||||
})
|
||||
.then((res) => {
|
||||
const { code, data, msg } = res
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.info.config) {
|
||||
}
|
||||
setCurDetail(data.info)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.finally(() => setDrawerLoading(false))
|
||||
break
|
||||
}
|
||||
}
|
||||
setDrawerType(type)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
const openModal = async (type: 'publish' | 'delete', entity?: DynamicTableItem) => {
|
||||
let title: string = ''
|
||||
let content: string | React.ReactNode = ''
|
||||
switch (type) {
|
||||
case 'publish': {
|
||||
message.loading($t(RESPONSE_TIPS.operating))
|
||||
await fetchData<BasicResponse<DynamicPublish>>(
|
||||
`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline' : 'online'}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
eoParams: { id: entity!.id }
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
manualReloadTable()
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => Promise.reject(errorInfo))
|
||||
return
|
||||
}
|
||||
case 'delete':
|
||||
title = '删除'
|
||||
content = <span>{$t(DELETE_TIPS.default)}</span>
|
||||
break
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getRender()
|
||||
pageListRef.current?.reload()
|
||||
}, [moduleId]);
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk: () => {
|
||||
switch (
|
||||
type // case 'publish':
|
||||
) {
|
||||
// return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()})
|
||||
case 'delete':
|
||||
return deleteInstance(entity!).then((res) => {
|
||||
if (res === true) manualReloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
width: type === 'delete' ? 600 : 900,
|
||||
okText: $t('确认'),
|
||||
okButtonProps: {
|
||||
disabled: false
|
||||
},
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>,
|
||||
footer: (_, { OkBtn, CancelBtn }) => {
|
||||
return (
|
||||
<>
|
||||
<WithPermission access="">
|
||||
<CancelBtn />
|
||||
</WithPermission>
|
||||
<WithPermission access="">
|
||||
<OkBtn />
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getRender()
|
||||
pageListRef.current?.reload()
|
||||
}, [moduleId])
|
||||
|
||||
return (<>
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
columns = {[...translatedCol,...operation]}
|
||||
request={(params)=>getIntelligentPluginTableList(params)}
|
||||
addNewBtnTitle={$t('添加(0)',[$t(pluginName)])}
|
||||
searchPlaceholder={$t('搜索(0)名称',[$t(pluginName)])}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
addNewBtnAccess={`${accessPrefix}.add`}
|
||||
onAddNewBtnClick={()=>{openDrawer('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value);setTableHttpReload(true);setTableHttpReload(true)}}
|
||||
/>
|
||||
|
||||
<DrawerWithFooter title={`${drawerType === 'add' ? $t('添加(0)',[$t(pluginName)]) : $t('编辑(0)',[$t(pluginName)])}`} open={drawerOpen} onClose={()=>{setCurDetail(undefined);setDrawerOpen(false)}} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} submitAccess=''>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin/>} spinning={drawerLoading}>
|
||||
<IntelligentPluginConfig
|
||||
ref={drawerFormRef!}
|
||||
type={drawerType}
|
||||
renderSchema={renderSchema}
|
||||
tabData={partitionOptions}
|
||||
moduleId={moduleId!}
|
||||
driverSelectionOptions={driverOptions}
|
||||
initFormValue={curDetail as { [k: string]: unknown; }} />
|
||||
</Spin>
|
||||
</DrawerWithFooter>
|
||||
</>)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
columns={[...translatedCol, ...operation]}
|
||||
request={(params) => getIntelligentPluginTableList(params)}
|
||||
addNewBtnTitle={$t('添加(0)', [$t(pluginName)])}
|
||||
searchPlaceholder={$t('搜索(0)名称', [$t(pluginName)])}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
addNewBtnAccess={`${accessPrefix}.add`}
|
||||
onAddNewBtnClick={() => {
|
||||
openDrawer('add')
|
||||
}}
|
||||
onSearchWordChange={(e) => {
|
||||
setSearchWord(e.target.value)
|
||||
setTableHttpReload(true)
|
||||
setTableHttpReload(true)
|
||||
}}
|
||||
/>
|
||||
|
||||
<DrawerWithFooter
|
||||
title={`${drawerType === 'add' ? $t('添加(0)', [$t(pluginName)]) : $t('编辑(0)', [$t(pluginName)])}`}
|
||||
open={drawerOpen}
|
||||
onClose={() => {
|
||||
setCurDetail(undefined)
|
||||
setDrawerOpen(false)
|
||||
}}
|
||||
onSubmit={() =>
|
||||
drawerFormRef.current?.save()?.then((res) => {
|
||||
res && manualReloadTable()
|
||||
return res
|
||||
})
|
||||
}
|
||||
submitAccess=""
|
||||
>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={drawerLoading}>
|
||||
<IntelligentPluginConfig
|
||||
ref={drawerFormRef!}
|
||||
type={drawerType}
|
||||
renderSchema={renderSchema}
|
||||
tabData={partitionOptions}
|
||||
moduleId={moduleId!}
|
||||
driverSelectionOptions={driverOptions}
|
||||
initFormValue={curDetail as { [k: string]: unknown }}
|
||||
/>
|
||||
</Spin>
|
||||
</DrawerWithFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import type {
|
||||
EditorState,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$getRoot,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import { useEffect } from 'react'
|
||||
import type { EditorState } from 'lexical'
|
||||
import { $getRoot, TextNode } from 'lexical'
|
||||
import { CodeNode } from '@lexical/code'
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
|
||||
@@ -19,25 +14,13 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
|
||||
// import TreeView from './plugins/tree-view'
|
||||
import Placeholder from './plugins/placeholder'
|
||||
import ComponentPickerBlock from './plugins/component-picker-block/index'
|
||||
import {
|
||||
ContextBlock,
|
||||
ContextBlockNode,
|
||||
ContextBlockReplacementBlock,
|
||||
} from './plugins/context-block/index'
|
||||
import {
|
||||
QueryBlock,
|
||||
QueryBlockNode,
|
||||
QueryBlockReplacementBlock,
|
||||
} from './plugins/query-block/index'
|
||||
import {
|
||||
HistoryBlock,
|
||||
HistoryBlockNode,
|
||||
HistoryBlockReplacementBlock,
|
||||
} from './plugins/history-block/index'
|
||||
import { ContextBlock, ContextBlockNode, ContextBlockReplacementBlock } from './plugins/context-block/index'
|
||||
import { QueryBlock, QueryBlockNode, QueryBlockReplacementBlock } from './plugins/query-block/index'
|
||||
import { HistoryBlock, HistoryBlockNode, HistoryBlockReplacementBlock } from './plugins/history-block/index'
|
||||
import {
|
||||
WorkflowVariableBlock,
|
||||
WorkflowVariableBlockNode,
|
||||
WorkflowVariableBlockReplacementBlock,
|
||||
WorkflowVariableBlockReplacementBlock
|
||||
} from './plugins/workflow-variable-block/index'
|
||||
import VariableBlock from './plugins/variable-block/index'
|
||||
import VariableValueBlock from './plugins/variable-value-block/index'
|
||||
@@ -52,12 +35,9 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from './types'
|
||||
import {
|
||||
UPDATE_DATASETS_EVENT_EMITTER,
|
||||
UPDATE_HISTORY_EVENT_EMITTER,
|
||||
} from './constants'
|
||||
import { UPDATE_DATASETS_EVENT_EMITTER, UPDATE_HISTORY_EVENT_EMITTER } from './constants'
|
||||
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
|
||||
|
||||
export type PromptEditorProps = {
|
||||
@@ -97,7 +77,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
}) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const initialConfig = {
|
||||
@@ -107,51 +87,58 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
CustomTextNode,
|
||||
{
|
||||
replace: TextNode,
|
||||
with: (node: TextNode) => new CustomTextNode(node.__text),
|
||||
with: (node: TextNode) => new CustomTextNode(node.__text)
|
||||
},
|
||||
ContextBlockNode,
|
||||
HistoryBlockNode,
|
||||
QueryBlockNode,
|
||||
WorkflowVariableBlockNode,
|
||||
VariableValueBlockNode,
|
||||
VariableValueBlockNode
|
||||
],
|
||||
editorState: textToEditorState(value || ''),
|
||||
onError: (error: Error) => {
|
||||
throw error
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleEditorChange = (editorState: EditorState) => {
|
||||
const text = editorState.read(() => {
|
||||
return $getRoot().getChildren().map(p => p.getTextContent()).join('\n')
|
||||
return $getRoot()
|
||||
.getChildren()
|
||||
.map((p) => p.getTextContent())
|
||||
.join('\n')
|
||||
})
|
||||
if (onChange)
|
||||
onChange(text)
|
||||
if (onChange) onChange(text)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_DATASETS_EVENT_EMITTER,
|
||||
payload: contextBlock?.datasets,
|
||||
payload: contextBlock?.datasets
|
||||
} as any)
|
||||
}, [eventEmitter, contextBlock?.datasets])
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_HISTORY_EVENT_EMITTER,
|
||||
payload: historyBlock?.history,
|
||||
payload: historyBlock?.history
|
||||
} as any)
|
||||
}, [eventEmitter, historyBlock?.history])
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<div className='relative min-h-5'>
|
||||
<div className="relative min-h-5">
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`} style={style || {}} />}
|
||||
contentEditable={
|
||||
<ContentEditable
|
||||
className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`}
|
||||
style={style || {}}
|
||||
/>
|
||||
}
|
||||
placeholder={<Placeholder value={placeholder} className={placeholderClassName} compact={compact} />}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString='/'
|
||||
triggerString="/"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@@ -160,7 +147,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString='{'
|
||||
triggerString="{"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@@ -168,46 +155,36 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
/>
|
||||
{
|
||||
contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)}
|
||||
{queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)}
|
||||
{historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)}
|
||||
{(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)}
|
||||
{workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)}
|
||||
<OnChangePlugin onChange={handleEditorChange} />
|
||||
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
|
||||
<UpdateBlock instanceId={instanceId} />
|
||||
|
||||
+88
-76
@@ -1,82 +1,94 @@
|
||||
import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx'
|
||||
import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx'
|
||||
import { useState } from 'react'
|
||||
import { getVars } from './utils'
|
||||
import { VariableItems } from '@core/const/ai-service/type'
|
||||
|
||||
import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx';
|
||||
import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getVars } from './utils';
|
||||
import { VariableItems } from '@core/const/ai-service/type';
|
||||
const PromptEditorResizable = (props: {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
variablesChange?: (keys: string[]) => void
|
||||
promptVariables: VariableItems[]
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
const { value, onChange, variablesChange, promptVariables, disabled } = props
|
||||
const minHeight = 68
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
const [previousKeys, setPreviousKeys] = useState<string[]>([])
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
onChange?.(newTemplates)
|
||||
}
|
||||
|
||||
const PromptEditorResizable = (props:{value?:string, onChange?:(value:string)=>void, variablesChange?:(keys:string[])=>void,promptVariables:VariableItems[]}) =>{
|
||||
const {value , onChange,variablesChange,promptVariables} = props
|
||||
const minHeight = 68
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
const [previousKeys, setPreviousKeys] = useState<string[]>([])
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
onChange?.(newTemplates)
|
||||
return (
|
||||
<PromptEditorHeightResizeWrap
|
||||
className="px-4 pt-2 min-h-[94px] bg-white rounded-t-xl text-sm text-gray-700"
|
||||
height={editorHeight}
|
||||
minHeight={minHeight}
|
||||
onHeightChange={setEditorHeight}
|
||||
hideResize={false}
|
||||
footer={
|
||||
<div className="pl-4 pb-2 flex bg-white rounded-b-xl">
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">
|
||||
{value?.length || 0}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return ( <PromptEditorHeightResizeWrap
|
||||
className='px-4 pt-2 min-h-[94px] bg-white rounded-t-xl text-sm text-gray-700'
|
||||
height={editorHeight}
|
||||
minHeight={minHeight}
|
||||
onHeightChange={setEditorHeight}
|
||||
hideResize={false}
|
||||
footer={(
|
||||
<div className='pl-4 pb-2 flex bg-white rounded-b-xl'>
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value?.length || 0}</div>
|
||||
</div>
|
||||
>
|
||||
<>
|
||||
{value !== undefined && (
|
||||
<PromptEditor
|
||||
className="min-h-[68px]"
|
||||
compact
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: false,
|
||||
selectable: true
|
||||
// datasets: dataSets.map(item => ({
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.data_source_type,
|
||||
// })),
|
||||
// onAddContext: ()=>{console.log('?onAddContext')},
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables: promptVariables?.map((x) => ({ name: x.key, value: x.key })) || []
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: false,
|
||||
externalTools: []
|
||||
// onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: ''
|
||||
},
|
||||
onEditRole: () => {}
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: true
|
||||
}}
|
||||
onChange={(value) => {
|
||||
handleChange?.(value, [])
|
||||
}}
|
||||
onBlur={() => {
|
||||
const keys = getVars(value)
|
||||
handleChange(value, keys)
|
||||
if (keys.filter((key) => !previousKeys.includes(key)).length > 0) {
|
||||
variablesChange?.(keys)
|
||||
setPreviousKeys(keys)
|
||||
}
|
||||
}}
|
||||
editable={disabled ? false : true}
|
||||
/>
|
||||
)}
|
||||
><>
|
||||
{value !== undefined && <PromptEditor
|
||||
className='min-h-[68px]'
|
||||
compact
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: false,
|
||||
selectable: true,
|
||||
// datasets: dataSets.map(item => ({
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.data_source_type,
|
||||
// })),
|
||||
// onAddContext: ()=>{console.log('?onAddContext')},
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables:promptVariables?.map(x=>({name:x.key, value:x.key})) || [],
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: false,
|
||||
externalTools: [],
|
||||
// onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: '',
|
||||
},
|
||||
onEditRole: () => { },
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: true,
|
||||
}}
|
||||
onChange={(value) => {
|
||||
handleChange?.(value, [])
|
||||
}}
|
||||
onBlur={() => {
|
||||
const keys = getVars(value)
|
||||
handleChange(value, keys)
|
||||
if(keys.filter(key => !previousKeys.includes(key)).length > 0){
|
||||
variablesChange?.(keys)
|
||||
setPreviousKeys(keys)
|
||||
}
|
||||
}}
|
||||
editable={true}
|
||||
/>
|
||||
}</>
|
||||
</PromptEditorHeightResizeWrap>)
|
||||
</>
|
||||
</PromptEditorHeightResizeWrap>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptEditorResizable
|
||||
export default PromptEditorResizable
|
||||
|
||||
@@ -9,40 +9,35 @@ export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-
|
||||
export const MAX_VAR_KEY_LENGTH = 30
|
||||
|
||||
export const checkHasContextBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(CONTEXT_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
export const checkHasHistoryBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(HISTORY_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
export const checkHasQueryBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(QUERY_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
/*
|
||||
* {{#1711617514996.name#}} => [1711617514996, name]
|
||||
* {{#1711617514996.sys.query#}} => [sys, query]
|
||||
*/
|
||||
* {{#1711617514996.name#}} => [1711617514996, name]
|
||||
* {{#1711617514996.sys.query#}} => [sys, query]
|
||||
*/
|
||||
export const getInputVars = (text: string) => {
|
||||
if (!text)
|
||||
return []
|
||||
if (!text) return []
|
||||
|
||||
const allVars = text.match(/{{#([^#]*)#}}/g)
|
||||
if (allVars && allVars?.length > 0) {
|
||||
// {{#context#}}, {{#query#}} is not input vars
|
||||
const inputVars = allVars
|
||||
.filter(item => item.includes('.'))
|
||||
.filter((item) => item.includes('.'))
|
||||
.map((item) => {
|
||||
const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.')
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0]))
|
||||
return valueSelector.slice(1)
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0])) return valueSelector.slice(1)
|
||||
|
||||
return valueSelector
|
||||
})
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { Dispatch, RefObject, SetStateAction } from 'react'
|
||||
import type {
|
||||
Klass,
|
||||
LexicalCommand,
|
||||
LexicalEditor,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import type { Klass, LexicalCommand, LexicalEditor, TextNode } from 'lexical'
|
||||
import {
|
||||
$getNodeByKey,
|
||||
$getSelection,
|
||||
@@ -18,12 +8,10 @@ import {
|
||||
$isNodeSelection,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
KEY_BACKSPACE_COMMAND,
|
||||
KEY_DELETE_COMMAND,
|
||||
KEY_DELETE_COMMAND
|
||||
} from 'lexical'
|
||||
import type { EntityMatch } from '@lexical/text'
|
||||
import {
|
||||
mergeRegister,
|
||||
} from '@lexical/utils'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { $isContextBlockNode } from './plugins/context-block/node'
|
||||
@@ -35,7 +23,10 @@ import { DELETE_QUERY_BLOCK_COMMAND } from './plugins/query-block'
|
||||
import type { CustomTextNode } from './plugins/custom-text/node'
|
||||
import { registerLexicalTextEntity } from './utils'
|
||||
|
||||
export type UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand<undefined>) => [RefObject<HTMLDivElement>, boolean]
|
||||
export type UseSelectOrDeleteHandler = (
|
||||
nodeKey: string,
|
||||
command?: LexicalCommand<undefined>
|
||||
) => [RefObject<HTMLDivElement>, boolean]
|
||||
export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand<undefined>) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -46,13 +37,11 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
const selection = $getSelection()
|
||||
const nodes = selection?.getNodes()
|
||||
if (
|
||||
!isSelected
|
||||
&& nodes?.length === 1
|
||||
&& (
|
||||
($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND)
|
||||
|| ($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND)
|
||||
|| ($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND)
|
||||
)
|
||||
!isSelected &&
|
||||
nodes?.length === 1 &&
|
||||
(($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND) ||
|
||||
($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND) ||
|
||||
($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND))
|
||||
)
|
||||
editor.dispatchCommand(command, undefined)
|
||||
|
||||
@@ -60,8 +49,7 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
event.preventDefault()
|
||||
const node = $getNodeByKey(nodeKey)
|
||||
if ($isDecoratorNode(node)) {
|
||||
if (command)
|
||||
editor.dispatchCommand(command, undefined)
|
||||
if (command) editor.dispatchCommand(command, undefined)
|
||||
|
||||
node.remove()
|
||||
return true
|
||||
@@ -70,38 +58,31 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
|
||||
return false
|
||||
},
|
||||
[isSelected, nodeKey, command, editor],
|
||||
[isSelected, nodeKey, command, editor]
|
||||
)
|
||||
|
||||
const handleSelect = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
}, [setSelected, clearSelection])
|
||||
const handleSelect = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
},
|
||||
[setSelected, clearSelection]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const ele = ref.current
|
||||
|
||||
if (ele)
|
||||
ele.addEventListener('click', handleSelect)
|
||||
if (ele) ele.addEventListener('click', handleSelect)
|
||||
|
||||
return () => {
|
||||
if (ele)
|
||||
ele.removeEventListener('click', handleSelect)
|
||||
if (ele) ele.removeEventListener('click', handleSelect)
|
||||
}
|
||||
}, [handleSelect])
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
KEY_DELETE_COMMAND,
|
||||
handleDelete,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(
|
||||
KEY_BACKSPACE_COMMAND,
|
||||
handleDelete,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(KEY_DELETE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW),
|
||||
editor.registerCommand(KEY_BACKSPACE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW)
|
||||
)
|
||||
}, [editor, clearSelection, handleDelete])
|
||||
|
||||
@@ -114,17 +95,15 @@ export const useTrigger: UseTriggerHandler = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const handleOpen = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setOpen(v => !v)
|
||||
setOpen((v) => !v)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const trigger = triggerRef.current
|
||||
if (trigger)
|
||||
trigger.addEventListener('click', handleOpen)
|
||||
if (trigger) trigger.addEventListener('click', handleOpen)
|
||||
|
||||
return () => {
|
||||
if (trigger)
|
||||
trigger.removeEventListener('click', handleOpen)
|
||||
if (trigger) trigger.removeEventListener('click', handleOpen)
|
||||
}
|
||||
}, [handleOpen])
|
||||
|
||||
@@ -134,7 +113,7 @@ export const useTrigger: UseTriggerHandler = () => {
|
||||
export function useLexicalTextEntity<T extends TextNode>(
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
targetNode: Klass<T>,
|
||||
createNode: (textNode: CustomTextNode) => T,
|
||||
createNode: (textNode: CustomTextNode) => T
|
||||
) {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -148,24 +127,16 @@ export type MenuTextMatch = {
|
||||
matchingString: string
|
||||
replaceableString: string
|
||||
}
|
||||
export type TriggerFn = (
|
||||
text: string,
|
||||
editor: LexicalEditor,
|
||||
) => MenuTextMatch | null
|
||||
export type TriggerFn = (text: string, editor: LexicalEditor) => MenuTextMatch | null
|
||||
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'
|
||||
export function useBasicTypeaheadTriggerMatch(
|
||||
trigger: string,
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number },
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number }
|
||||
): TriggerFn {
|
||||
return useCallback(
|
||||
(text: string) => {
|
||||
const validChars = `[${PUNCTUATION}\\s]`
|
||||
const TypeaheadTriggerRegex = new RegExp(
|
||||
'(.*)('
|
||||
+ `[${trigger}]`
|
||||
+ `((?:${validChars}){0,${maxLength}})`
|
||||
+ ')$',
|
||||
)
|
||||
const TypeaheadTriggerRegex = new RegExp('(.*)(' + `[${trigger}]` + `((?:${validChars}){0,${maxLength}})` + ')$')
|
||||
const match = TypeaheadTriggerRegex.exec(text)
|
||||
if (match !== null) {
|
||||
const maybeLeadingWhitespace = match[1]
|
||||
@@ -174,12 +145,12 @@ export function useBasicTypeaheadTriggerMatch(
|
||||
return {
|
||||
leadOffset: match.index + maybeLeadingWhitespace.length,
|
||||
matchingString,
|
||||
replaceableString: match[2],
|
||||
replaceableString: match[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
[maxLength, minLength, trigger],
|
||||
[maxLength, minLength, trigger]
|
||||
)
|
||||
}
|
||||
|
||||
+50
-58
@@ -8,7 +8,7 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from '../../types'
|
||||
import { INSERT_CONTEXT_BLOCK_COMMAND } from '../context-block'
|
||||
import { INSERT_HISTORY_BLOCK_COMMAND } from '../history-block'
|
||||
@@ -32,32 +32,35 @@ import { $t } from '@common/locales'
|
||||
export const usePromptOptions = (
|
||||
contextBlock?: ContextBlockType,
|
||||
queryBlock?: QueryBlockType,
|
||||
historyBlock?: HistoryBlockType,
|
||||
historyBlock?: HistoryBlockType
|
||||
) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const promptOptions: PickerBlockMenuOption[] = []
|
||||
if (contextBlock?.show) {
|
||||
promptOptions.push(new PickerBlockMenuOption({
|
||||
key: $t('上下文'),
|
||||
group: 'prompt context',
|
||||
render: ({ isSelected, onSelect, onSetHighlight }) => {
|
||||
return <PromptMenuItem
|
||||
title={$t('上下文')}
|
||||
icon={<></>}
|
||||
// icon={<File05 className='w-4 h-4 text-[#6938EF]' />}
|
||||
disabled={!contextBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
/>
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!contextBlock?.selectable)
|
||||
return
|
||||
editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}))
|
||||
promptOptions.push(
|
||||
new PickerBlockMenuOption({
|
||||
key: $t('上下文'),
|
||||
group: 'prompt context',
|
||||
render: ({ isSelected, onSelect, onSetHighlight }) => {
|
||||
return (
|
||||
<PromptMenuItem
|
||||
title={$t('上下文')}
|
||||
icon={<></>}
|
||||
// icon={<File05 className='w-4 h-4 text-[#6938EF]' />}
|
||||
disabled={!contextBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
/>
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!contextBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (queryBlock?.show) {
|
||||
@@ -79,11 +82,10 @@ export const usePromptOptions = (
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!queryBlock?.selectable)
|
||||
return
|
||||
if (!queryBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,8 +100,7 @@ export const usePromptOptions = (
|
||||
title={$t('会话历史')}
|
||||
icon={<></>}
|
||||
// icon={<MessageClockCircle className='w-4 h-4 text-[#DD2590]' />}
|
||||
disabled={!historyBlock.selectable
|
||||
}
|
||||
disabled={!historyBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
@@ -107,11 +108,10 @@ export const usePromptOptions = (
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!historyBlock?.selectable)
|
||||
return
|
||||
if (!historyBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
return promptOptions
|
||||
@@ -119,16 +119,15 @@ export const usePromptOptions = (
|
||||
|
||||
export const useVariableOptions = (
|
||||
variableBlock?: VariableBlockType,
|
||||
queryString?: string,
|
||||
queryString?: string
|
||||
): PickerBlockMenuOption[] => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!variableBlock?.variables)
|
||||
return []
|
||||
if (!variableBlock?.variables) return []
|
||||
|
||||
const baseOptions = (variableBlock.variables).map((item) => {
|
||||
const baseOptions = variableBlock.variables.map((item) => {
|
||||
return new PickerBlockMenuOption({
|
||||
key: item.value,
|
||||
group: 'prompt variable',
|
||||
@@ -147,15 +146,14 @@ export const useVariableOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.value}}}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!queryString)
|
||||
return baseOptions
|
||||
if (!queryString) return baseOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
|
||||
return baseOptions.filter(option => regex.test(option.key))
|
||||
return baseOptions.filter((option) => regex.test(option.key))
|
||||
}, [editor, queryString, variableBlock])
|
||||
|
||||
const addOption = useMemo(() => {
|
||||
@@ -182,7 +180,7 @@ export const useVariableOptions = (
|
||||
$insertNodes([prefixNode, suffixNode])
|
||||
prefixNode.select()
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [editor, t])
|
||||
|
||||
@@ -191,17 +189,13 @@ export const useVariableOptions = (
|
||||
}, [options, addOption, variableBlock?.show])
|
||||
}
|
||||
|
||||
export const useExternalToolOptions = (
|
||||
externalToolBlockType?: ExternalToolBlockType,
|
||||
queryString?: string,
|
||||
) => {
|
||||
export const useExternalToolOptions = (externalToolBlockType?: ExternalToolBlockType, queryString?: string) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!externalToolBlockType?.externalTools)
|
||||
return []
|
||||
const baseToolOptions = (externalToolBlockType.externalTools).map((item) => {
|
||||
if (!externalToolBlockType?.externalTools) return []
|
||||
const baseToolOptions = externalToolBlockType.externalTools.map((item) => {
|
||||
return new PickerBlockMenuOption({
|
||||
key: item.name,
|
||||
group: 'external tool',
|
||||
@@ -217,7 +211,7 @@ export const useExternalToolOptions = (
|
||||
// background={item.icon_background}
|
||||
// />
|
||||
// }
|
||||
extraElement={<div className='text-xs text-gray-400'>{item.variableName}</div>}
|
||||
extraElement={<div className="text-xs text-gray-400">{item.variableName}</div>}
|
||||
queryString={queryString}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
@@ -227,15 +221,14 @@ export const useExternalToolOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.variableName}}}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!queryString)
|
||||
return baseToolOptions
|
||||
if (!queryString) return baseToolOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
|
||||
return baseToolOptions.filter(option => regex.test(option.key))
|
||||
return baseToolOptions.filter((option) => regex.test(option.key))
|
||||
}, [editor, queryString, externalToolBlockType])
|
||||
|
||||
const addOption = useMemo(() => {
|
||||
@@ -257,7 +250,7 @@ export const useExternalToolOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
externalToolBlockType?.onAddExternalTool?.()
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [externalToolBlockType, t])
|
||||
|
||||
@@ -273,14 +266,13 @@ export const useOptions = (
|
||||
variableBlock?: VariableBlockType,
|
||||
externalToolBlockType?: ExternalToolBlockType,
|
||||
workflowVariableBlockType?: WorkflowVariableBlockType,
|
||||
queryString?: string,
|
||||
queryString?: string
|
||||
) => {
|
||||
const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock)
|
||||
const variableOptions = useVariableOptions(variableBlock, queryString)
|
||||
const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString)
|
||||
const workflowVariableOptions = useMemo(() => {
|
||||
if (!workflowVariableBlockType?.show)
|
||||
return []
|
||||
if (!workflowVariableBlockType?.show) return []
|
||||
|
||||
return workflowVariableBlockType.variables || []
|
||||
}, [workflowVariableBlockType])
|
||||
@@ -288,7 +280,7 @@ export const useOptions = (
|
||||
return useMemo(() => {
|
||||
return {
|
||||
workflowVariableOptions,
|
||||
allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions],
|
||||
allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions]
|
||||
}
|
||||
}, [promptOptions, variableOptions, externalToolOptions, workflowVariableOptions])
|
||||
}
|
||||
|
||||
+79
-98
@@ -1,16 +1,6 @@
|
||||
import {
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Fragment, memo, useCallback, useState } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useFloating,
|
||||
} from '@floating-ui/react'
|
||||
import { flip, offset, shift, useFloating } from '@floating-ui/react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
@@ -21,7 +11,7 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from '../../types'
|
||||
import { useBasicTypeaheadTriggerMatch } from '../../hooks'
|
||||
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
|
||||
@@ -48,7 +38,7 @@ const ComponentPicker = ({
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
}: ComponentPickerProps) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const { refs, floatingStyles, isPositioned } = useFloating({
|
||||
@@ -56,15 +46,15 @@ const ComponentPicker = ({
|
||||
middleware: [
|
||||
offset(0), // fix hide cursor
|
||||
shift({
|
||||
padding: 8,
|
||||
padding: 8
|
||||
}),
|
||||
flip(),
|
||||
],
|
||||
flip()
|
||||
]
|
||||
})
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, {
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
maxLength: 0
|
||||
})
|
||||
|
||||
const [queryString, setQueryString] = useState<string | null>(null)
|
||||
@@ -74,123 +64,114 @@ const ComponentPicker = ({
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${v.payload}}}`)
|
||||
})
|
||||
|
||||
const {
|
||||
allFlattenOptions,
|
||||
workflowVariableOptions,
|
||||
} = useOptions(
|
||||
const { allFlattenOptions, workflowVariableOptions } = useOptions(
|
||||
contextBlock,
|
||||
queryBlock,
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
)
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
(
|
||||
selectedOption: PickerBlockMenuOption,
|
||||
nodeToRemove: TextNode | null,
|
||||
closeMenu: () => void,
|
||||
) => {
|
||||
(selectedOption: PickerBlockMenuOption, nodeToRemove: TextNode | null, closeMenu: () => void) => {
|
||||
editor.update(() => {
|
||||
if (nodeToRemove && selectedOption?.key)
|
||||
nodeToRemove.remove()
|
||||
if (nodeToRemove && selectedOption?.key) nodeToRemove.remove()
|
||||
|
||||
selectedOption.onSelectMenuOption()
|
||||
closeMenu()
|
||||
})
|
||||
},
|
||||
[editor],
|
||||
[editor]
|
||||
)
|
||||
|
||||
const handleSelectWorkflowVariable = useCallback((variables: string[]) => {
|
||||
editor.update(() => {
|
||||
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
|
||||
if (needRemove)
|
||||
needRemove.remove()
|
||||
})
|
||||
const handleSelectWorkflowVariable = useCallback(
|
||||
(variables: string[]) => {
|
||||
editor.update(() => {
|
||||
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
|
||||
if (needRemove) needRemove.remove()
|
||||
})
|
||||
|
||||
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
|
||||
else
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
|
||||
}, [editor, checkForTriggerMatch, triggerString])
|
||||
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
|
||||
else editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
|
||||
},
|
||||
[editor, checkForTriggerMatch, triggerString]
|
||||
)
|
||||
|
||||
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>((
|
||||
anchorElementRef,
|
||||
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
|
||||
) => {
|
||||
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show)))
|
||||
return null
|
||||
refs.setReference(anchorElementRef.current)
|
||||
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>(
|
||||
(anchorElementRef, { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) => {
|
||||
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) return null
|
||||
refs.setReference(anchorElementRef.current)
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
ReactDOM.createPortal(
|
||||
return (
|
||||
<>
|
||||
{ReactDOM.createPortal(
|
||||
// The `LexicalMenu` will try to calculate the position of the floating menu based on the first child.
|
||||
// Since we use floating ui, we need to wrap it with a div to prevent the position calculation being affected.
|
||||
// See https://github.com/facebook/lexical/blob/ac97dfa9e14a73ea2d6934ff566282d7f758e8bb/packages/lexical-react/src/shared/LexicalMenu.ts#L493
|
||||
<div className='w-0 h-0'>
|
||||
<div className="w-0 h-0">
|
||||
<div
|
||||
className='p-1 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto overflow-x-hidden'
|
||||
className="p-1 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto overflow-x-hidden"
|
||||
style={{
|
||||
...floatingStyles,
|
||||
visibility: isPositioned ? 'visible' : 'hidden',
|
||||
maxHeight: 'calc(1 / 3 * 100vh)',
|
||||
maxHeight: 'calc(1 / 3 * 100vh)'
|
||||
}}
|
||||
ref={refs.setFloating}
|
||||
>
|
||||
{
|
||||
options.map((option, index) => (
|
||||
<Fragment key={option.key}>
|
||||
{
|
||||
// Divider
|
||||
index !== 0 && options.at(index - 1)?.group !== option.group && (
|
||||
<div className='h-px bg-gray-100 my-1 w-screen -translate-x-1'></div>
|
||||
)
|
||||
{options.map((option, index) => (
|
||||
<Fragment key={option.key}>
|
||||
{
|
||||
// Divider
|
||||
index !== 0 && options.at(index - 1)?.group !== option.group && (
|
||||
<div className="h-px bg-gray-100 my-1 w-screen -translate-x-1"></div>
|
||||
)
|
||||
}
|
||||
{option.renderMenuOption({
|
||||
queryString,
|
||||
isSelected: selectedIndex === index,
|
||||
onSelect: () => {
|
||||
selectOptionAndCleanUp(option)
|
||||
},
|
||||
onSetHighlight: () => {
|
||||
setHighlightedIndex(index)
|
||||
}
|
||||
{option.renderMenuOption({
|
||||
queryString,
|
||||
isSelected: selectedIndex === index,
|
||||
onSelect: () => {
|
||||
selectOptionAndCleanUp(option)
|
||||
},
|
||||
onSetHighlight: () => {
|
||||
setHighlightedIndex(index)
|
||||
},
|
||||
})}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
{
|
||||
(!!options.length) && (
|
||||
<div className='h-px bg-gray-100 my-1 w-screen -translate-x-1'></div>
|
||||
)
|
||||
}
|
||||
<div className='p-1'>
|
||||
{/* <VarReferenceVars
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
{workflowVariableBlock?.show && (
|
||||
<>
|
||||
{!!options.length && <div className="h-px bg-gray-100 my-1 w-screen -translate-x-1"></div>}
|
||||
<div className="p-1">
|
||||
{/* <VarReferenceVars
|
||||
hideSearch
|
||||
vars={workflowVariableOptions}
|
||||
onChange={(variables: string[]) => {
|
||||
handleSelectWorkflowVariable(variables)
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
anchorElementRef.current,
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable])
|
||||
anchorElementRef.current
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
[
|
||||
allFlattenOptions.length,
|
||||
workflowVariableBlock?.show,
|
||||
refs,
|
||||
isPositioned,
|
||||
floatingStyles,
|
||||
queryString,
|
||||
workflowVariableOptions,
|
||||
handleSelectWorkflowVariable
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
@@ -202,7 +183,7 @@ const ComponentPicker = ({
|
||||
//
|
||||
// We no need the position function of the `LexicalTypeaheadMenuPlugin`,
|
||||
// so the reference anchor should be positioned based on the range of the trigger string, and the menu will be positioned by the floating ui.
|
||||
anchorClassName='z-[999999] translate-y-[calc(-100%-3px)]'
|
||||
anchorClassName="z-[999999] translate-y-[calc(-100%-3px)]"
|
||||
menuRenderFn={renderMenu}
|
||||
triggerFn={checkForTriggerMatch}
|
||||
/>
|
||||
|
||||
+4
-2
@@ -20,12 +20,14 @@ export class PickerBlockMenuOption extends MenuOption {
|
||||
group?: string
|
||||
onSelect?: () => void
|
||||
render: (menuRenderProps: MenuOptionRenderProps) => JSX.Element
|
||||
},
|
||||
}
|
||||
) {
|
||||
super(data.key)
|
||||
this.group = data.group
|
||||
}
|
||||
|
||||
public onSelectMenuOption = () => this.data.onSelect?.()
|
||||
public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => <Fragment key={this.data.key}>{this.data.render(menuRenderProps)}</Fragment>
|
||||
public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => (
|
||||
<Fragment key={this.data.key}>{this.data.render(menuRenderProps)}</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
+22
-29
@@ -9,37 +9,30 @@ type PromptMenuItemMenuItemProps = {
|
||||
onMouseEnter: () => void
|
||||
setRefElement?: (element: HTMLDivElement) => void
|
||||
}
|
||||
export const PromptMenuItem = memo(({
|
||||
icon,
|
||||
title,
|
||||
disabled,
|
||||
isSelected,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement,
|
||||
}: PromptMenuItemMenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
export const PromptMenuItem = memo(
|
||||
({ icon, title, disabled, isSelected, onClick, onMouseEnter, setRefElement }: PromptMenuItemMenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-3 h-6 cursor-pointer hover:bg-gray-50 rounded-md
|
||||
${isSelected && !disabled && '!bg-gray-50'}
|
||||
${disabled ? 'cursor-not-allowed opacity-30' : 'hover:bg-gray-50 cursor-pointer'}
|
||||
`}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={() => {
|
||||
if (disabled)
|
||||
return
|
||||
onMouseEnter()
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled)
|
||||
return
|
||||
onClick()
|
||||
}}>
|
||||
{icon}
|
||||
<div className='ml-1 text-[13px] text-gray-900'>{title}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={() => {
|
||||
if (disabled) return
|
||||
onMouseEnter()
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled) return
|
||||
onClick()
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<div className="ml-1 text-[13px] text-gray-900">{title}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
PromptMenuItem.displayName = 'PromptMenuItem'
|
||||
|
||||
+40
-39
@@ -10,51 +10,52 @@ type VariableMenuItemProps = {
|
||||
onMouseEnter: () => void
|
||||
setRefElement?: (element: HTMLDivElement) => void
|
||||
}
|
||||
export const VariableMenuItem = memo(({
|
||||
title,
|
||||
icon,
|
||||
extraElement,
|
||||
isSelected,
|
||||
queryString,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement,
|
||||
}: VariableMenuItemProps) => {
|
||||
let before = title
|
||||
let middle = ''
|
||||
let after = ''
|
||||
export const VariableMenuItem = memo(
|
||||
({
|
||||
title,
|
||||
icon,
|
||||
extraElement,
|
||||
isSelected,
|
||||
queryString,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement
|
||||
}: VariableMenuItemProps) => {
|
||||
let before = title
|
||||
let middle = ''
|
||||
let after = ''
|
||||
|
||||
if (queryString) {
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
const match = regex.exec(title)
|
||||
if (queryString) {
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
const match = regex.exec(title)
|
||||
|
||||
if (match) {
|
||||
before = title.substring(0, match.index)
|
||||
middle = match[0]
|
||||
after = title.substring(match.index + match[0].length)
|
||||
if (match) {
|
||||
before = title.substring(0, match.index)
|
||||
middle = match[0]
|
||||
after = title.substring(match.index + match[0].length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-3 h-6 rounded-md hover:bg-[#EBEEF2] cursor-pointer
|
||||
${isSelected && 'bg-[#EBEEF2]'}
|
||||
`}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onClick={onClick}>
|
||||
<div className='mr-2'>
|
||||
{icon}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="mr-2">{icon}</div>
|
||||
<div className="grow text-[13px] text-gray-900 truncate" title={title}>
|
||||
{before}
|
||||
<span className="text-[#2970FF]">{middle}</span>
|
||||
{after}
|
||||
</div>
|
||||
{extraElement}
|
||||
</div>
|
||||
<div className='grow text-[13px] text-gray-900 truncate' title={title}>
|
||||
{before}
|
||||
<span className='text-[#2970FF]'>{middle}</span>
|
||||
{after}
|
||||
</div>
|
||||
{extraElement}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
VariableMenuItem.displayName = 'VariableMenuItem'
|
||||
|
||||
+8
-8
@@ -1,6 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// import {
|
||||
// RiAddLine,
|
||||
// } from '@remixicon/react'
|
||||
@@ -28,7 +27,7 @@ const ContextBlockComponent: FC<ContextBlockComponentProps> = ({
|
||||
nodeKey,
|
||||
datasets = [],
|
||||
onAddContext,
|
||||
canNotAddContext,
|
||||
canNotAddContext
|
||||
}) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_CONTEXT_BLOCK_COMMAND)
|
||||
const [triggerRef, open, setOpen] = useTrigger()
|
||||
@@ -36,19 +35,20 @@ const ContextBlockComponent: FC<ContextBlockComponentProps> = ({
|
||||
const [localDatasets, setLocalDatasets] = useState<Dataset[]>(datasets)
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v?.type === UPDATE_DATASETS_EVENT_EMITTER)
|
||||
setLocalDatasets(v.payload)
|
||||
if (v?.type === UPDATE_DATASETS_EVENT_EMITTER) setLocalDatasets(v.payload)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
group inline-flex items-center pl-1 pr-0.5 h-6 border border-transparent bg-[#F4F3FF] text-[#6938EF] rounded-[5px] hover:bg-[#EBE9FE]
|
||||
${open ? 'bg-[#EBE9FE]' : 'bg-[#F4F3FF]'}
|
||||
${isSelected && '!border-[#9B8AFB]'}
|
||||
`} ref={ref}>
|
||||
`}
|
||||
ref={ref}
|
||||
>
|
||||
{/* <File05 className='mr-1 w-[14px] h-[14px]' /> */}
|
||||
<div className='mr-1 text-xs font-medium'>{$t('上下文')}</div>
|
||||
|
||||
<div className="mr-1 text-xs font-medium">{$t('上下文')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-16
@@ -1,18 +1,11 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import { CONTEXT_PLACEHOLDER_TEXT } from '../../constants'
|
||||
import type { ContextBlockType } from '../../types'
|
||||
import {
|
||||
$createContextBlockNode,
|
||||
ContextBlockNode,
|
||||
} from './node'
|
||||
import { $createContextBlockNode, ContextBlockNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
|
||||
const REGEX = new RegExp(CONTEXT_PLACEHOLDER_TEXT)
|
||||
@@ -21,7 +14,7 @@ const ContextBlockReplacementBlock = ({
|
||||
datasets = [],
|
||||
onAddContext = () => {},
|
||||
onInsert,
|
||||
canNotAddContext,
|
||||
canNotAddContext
|
||||
}: ContextBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -31,29 +24,29 @@ const ContextBlockReplacementBlock = ({
|
||||
}, [editor])
|
||||
|
||||
const createContextBlockNode = useCallback((): ContextBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
return $applyNodeReplacement($createContextBlockNode(datasets, onAddContext, canNotAddContext))
|
||||
}, [datasets, onAddContext, onInsert, canNotAddContext])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + CONTEXT_PLACEHOLDER_TEXT.length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createContextBlockNode)),
|
||||
editor.registerNodeTransform(CustomTextNode, (textNode) =>
|
||||
decoratorTransform(textNode, getMatch, createContextBlockNode)
|
||||
)
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
+33
-49
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { ContextBlockType } from '../../types'
|
||||
import {
|
||||
$createContextBlockNode,
|
||||
ContextBlockNode,
|
||||
} from './node'
|
||||
import { $createContextBlockNode, ContextBlockNode } from './node'
|
||||
|
||||
export const INSERT_CONTEXT_BLOCK_COMMAND = createCommand('INSERT_CONTEXT_BLOCK_COMMAND')
|
||||
export const DELETE_CONTEXT_BLOCK_COMMAND = createCommand('DELETE_CONTEXT_BLOCK_COMMAND')
|
||||
@@ -24,49 +14,43 @@ export type Dataset = {
|
||||
type: string
|
||||
}
|
||||
|
||||
const ContextBlock = memo(({
|
||||
datasets = [],
|
||||
onAddContext = () => {},
|
||||
onInsert,
|
||||
onDelete,
|
||||
canNotAddContext,
|
||||
}: ContextBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const ContextBlock = memo(
|
||||
({ datasets = [], onAddContext = () => {}, onInsert, onDelete, canNotAddContext }: ContextBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([ContextBlockNode]))
|
||||
throw new Error('ContextBlockPlugin: ContextBlock not registered on editor')
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([ContextBlockNode]))
|
||||
throw new Error('ContextBlockPlugin: ContextBlock not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
const contextBlockNode = $createContextBlockNode(datasets, onAddContext, canNotAddContext)
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
const contextBlockNode = $createContextBlockNode(datasets, onAddContext, canNotAddContext)
|
||||
|
||||
$insertNodes([contextBlockNode])
|
||||
$insertNodes([contextBlockNode])
|
||||
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_CONTEXT_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
)
|
||||
}, [editor, datasets, onAddContext, onInsert, onDelete, canNotAddContext])
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, datasets, onAddContext, onInsert, onDelete, canNotAddContext])
|
||||
|
||||
return null
|
||||
})
|
||||
return null
|
||||
}
|
||||
)
|
||||
ContextBlock.displayName = 'ContextBlock'
|
||||
|
||||
export { ContextBlock }
|
||||
|
||||
+17
-7
@@ -3,7 +3,11 @@ import { DecoratorNode } from 'lexical'
|
||||
import ContextBlockComponent from './component'
|
||||
import type { Dataset } from './index'
|
||||
|
||||
export type SerializedNode = SerializedLexicalNode & { datasets: Dataset[]; onAddContext: () => void; canNotAddContext: boolean }
|
||||
export type SerializedNode = SerializedLexicalNode & {
|
||||
datasets: Dataset[]
|
||||
onAddContext: () => void
|
||||
canNotAddContext: boolean
|
||||
}
|
||||
|
||||
export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
__datasets: Dataset[]
|
||||
@@ -70,7 +74,11 @@ export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedNode): ContextBlockNode {
|
||||
const node = $createContextBlockNode(serializedNode.datasets, serializedNode.onAddContext, serializedNode.canNotAddContext)
|
||||
const node = $createContextBlockNode(
|
||||
serializedNode.datasets,
|
||||
serializedNode.onAddContext,
|
||||
serializedNode.canNotAddContext
|
||||
)
|
||||
|
||||
return node
|
||||
}
|
||||
@@ -81,7 +89,7 @@ export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
version: 1,
|
||||
datasets: this.getDatasets(),
|
||||
onAddContext: this.getOnAddContext(),
|
||||
canNotAddContext: this.getCanNotAddContext(),
|
||||
canNotAddContext: this.getCanNotAddContext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,12 +97,14 @@ export class ContextBlockNode extends DecoratorNode<JSX.Element> {
|
||||
return '{{#context#}}'
|
||||
}
|
||||
}
|
||||
export function $createContextBlockNode(datasets: Dataset[], onAddContext: () => void, canNotAddContext?: boolean): ContextBlockNode {
|
||||
export function $createContextBlockNode(
|
||||
datasets: Dataset[],
|
||||
onAddContext: () => void,
|
||||
canNotAddContext?: boolean
|
||||
): ContextBlockNode {
|
||||
return new ContextBlockNode(datasets, onAddContext, undefined, canNotAddContext)
|
||||
}
|
||||
|
||||
export function $isContextBlockNode(
|
||||
node: ContextBlockNode | LexicalNode | null | undefined,
|
||||
): boolean {
|
||||
export function $isContextBlockNode(node: ContextBlockNode | LexicalNode | null | undefined): boolean {
|
||||
return node instanceof ContextBlockNode
|
||||
}
|
||||
|
||||
+2
-3
@@ -37,13 +37,12 @@ export class CustomTextNode extends TextNode {
|
||||
style: this.getStyle(),
|
||||
text: this.getTextContent(),
|
||||
type: 'custom-text',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
isSimpleText() {
|
||||
return (
|
||||
(this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0)
|
||||
return (this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -1,6 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// import {
|
||||
// RiMoreFill,
|
||||
// } from '@remixicon/react'
|
||||
@@ -26,7 +25,7 @@ type HistoryBlockComponentProps = {
|
||||
const HistoryBlockComponent: FC<HistoryBlockComponentProps> = ({
|
||||
nodeKey,
|
||||
roleName = { user: '', assistant: '' },
|
||||
onEditRole,
|
||||
onEditRole
|
||||
}) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_HISTORY_BLOCK_COMMAND)
|
||||
const [triggerRef, open, setOpen] = useTrigger()
|
||||
@@ -34,18 +33,20 @@ const HistoryBlockComponent: FC<HistoryBlockComponentProps> = ({
|
||||
const [localRoleName, setLocalRoleName] = useState<RoleName>(roleName)
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v?.type === UPDATE_HISTORY_EVENT_EMITTER)
|
||||
setLocalRoleName(v.payload)
|
||||
if (v?.type === UPDATE_HISTORY_EVENT_EMITTER) setLocalRoleName(v.payload)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
<div
|
||||
className={`
|
||||
group inline-flex items-center pl-1 pr-0.5 h-6 border border-transparent text-[#DD2590] rounded-[5px] hover:bg-[#FCE7F6]
|
||||
${open ? 'bg-[#FCE7F6]' : 'bg-[#FDF2FA]'}
|
||||
${isSelected && '!border-[#F670C7]'}
|
||||
`} ref={ref}>
|
||||
`}
|
||||
ref={ref}
|
||||
>
|
||||
{/* <MessageClockCircle className='mr-1 w-[14px] h-[14px]' /> */}
|
||||
<div className='mr-1 text-xs font-medium'>{$t('会话历史')}</div>
|
||||
<div className="mr-1 text-xs font-medium">{$t('会话历史')}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+9
-15
@@ -1,17 +1,11 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import { HISTORY_PLACEHOLDER_TEXT } from '../../constants'
|
||||
import type { HistoryBlockType } from '../../types'
|
||||
import {
|
||||
$createHistoryBlockNode,
|
||||
HistoryBlockNode,
|
||||
} from './node'
|
||||
import { $createHistoryBlockNode, HistoryBlockNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
|
||||
const REGEX = new RegExp(HISTORY_PLACEHOLDER_TEXT)
|
||||
@@ -19,7 +13,7 @@ const REGEX = new RegExp(HISTORY_PLACEHOLDER_TEXT)
|
||||
const HistoryBlockReplacementBlock = ({
|
||||
history = { user: '', assistant: '' },
|
||||
onEditRole = () => {},
|
||||
onInsert,
|
||||
onInsert
|
||||
}: HistoryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -29,29 +23,29 @@ const HistoryBlockReplacementBlock = ({
|
||||
}, [editor])
|
||||
|
||||
const createHistoryBlockNode = useCallback((): HistoryBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
return $applyNodeReplacement($createHistoryBlockNode(history, onEditRole))
|
||||
}, [history, onEditRole, onInsert])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + HISTORY_PLACEHOLDER_TEXT.length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createHistoryBlockNode)),
|
||||
editor.registerNodeTransform(CustomTextNode, (textNode) =>
|
||||
decoratorTransform(textNode, getMatch, createHistoryBlockNode)
|
||||
)
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
+33
-48
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { HistoryBlockType } from '../../types'
|
||||
import {
|
||||
$createHistoryBlockNode,
|
||||
HistoryBlockNode,
|
||||
} from './node'
|
||||
import { $createHistoryBlockNode, HistoryBlockNode } from './node'
|
||||
|
||||
export const INSERT_HISTORY_BLOCK_COMMAND = createCommand('INSERT_HISTORY_BLOCK_COMMAND')
|
||||
export const DELETE_HISTORY_BLOCK_COMMAND = createCommand('DELETE_HISTORY_BLOCK_COMMAND')
|
||||
@@ -30,48 +20,43 @@ export type HistoryBlockProps = {
|
||||
onDelete?: () => void
|
||||
}
|
||||
|
||||
const HistoryBlock = memo(({
|
||||
history = { user: '', assistant: '' },
|
||||
onEditRole = () => {},
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: HistoryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const HistoryBlock = memo(
|
||||
({ history = { user: '', assistant: '' }, onEditRole = () => {}, onInsert, onDelete }: HistoryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HistoryBlockNode]))
|
||||
throw new Error('HistoryBlockPlugin: HistoryBlock not registered on editor')
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([HistoryBlockNode]))
|
||||
throw new Error('HistoryBlockPlugin: HistoryBlock not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
const historyBlockNode = $createHistoryBlockNode(history, onEditRole)
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
INSERT_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
const historyBlockNode = $createHistoryBlockNode(history, onEditRole)
|
||||
|
||||
$insertNodes([historyBlockNode])
|
||||
$insertNodes([historyBlockNode])
|
||||
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_HISTORY_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
)
|
||||
}, [editor, history, onEditRole, onInsert, onDelete])
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, history, onEditRole, onInsert, onDelete])
|
||||
|
||||
return null
|
||||
})
|
||||
return null
|
||||
}
|
||||
)
|
||||
HistoryBlock.displayName = 'HistoryBlock'
|
||||
|
||||
export { HistoryBlock }
|
||||
|
||||
+3
-9
@@ -40,11 +40,7 @@ export class HistoryBlockNode extends DecoratorNode<JSX.Element> {
|
||||
|
||||
decorate(): JSX.Element {
|
||||
return (
|
||||
<HistoryBlockComponent
|
||||
nodeKey={this.getKey()}
|
||||
roleName={this.getRoleName()}
|
||||
onEditRole={this.getOnEditRole()}
|
||||
/>
|
||||
<HistoryBlockComponent nodeKey={this.getKey()} roleName={this.getRoleName()} onEditRole={this.getOnEditRole()} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +67,7 @@ export class HistoryBlockNode extends DecoratorNode<JSX.Element> {
|
||||
type: 'history-block',
|
||||
version: 1,
|
||||
roleName: this.getRoleName(),
|
||||
onEditRole: this.getOnEditRole,
|
||||
onEditRole: this.getOnEditRole
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +79,6 @@ export function $createHistoryBlockNode(roleName: RoleName, onEditRole: () => vo
|
||||
return new HistoryBlockNode(roleName, onEditRole)
|
||||
}
|
||||
|
||||
export function $isHistoryBlockNode(
|
||||
node: HistoryBlockNode | LexicalNode | null | undefined,
|
||||
): node is HistoryBlockNode {
|
||||
export function $isHistoryBlockNode(node: HistoryBlockNode | LexicalNode | null | undefined): node is HistoryBlockNode {
|
||||
return node instanceof HistoryBlockNode
|
||||
}
|
||||
|
||||
+8
-18
@@ -1,11 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import {
|
||||
BLUR_COMMAND,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
FOCUS_COMMAND,
|
||||
KEY_ESCAPE_COMMAND,
|
||||
} from 'lexical'
|
||||
import { BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { CLEAR_HIDE_MENU_TIMEOUT } from './workflow-variable-block'
|
||||
@@ -14,10 +9,7 @@ type OnBlurBlockProps = {
|
||||
onBlur?: () => void
|
||||
onFocus?: () => void
|
||||
}
|
||||
const OnBlurBlock: FC<OnBlurBlockProps> = ({
|
||||
onBlur,
|
||||
onFocus,
|
||||
}) => {
|
||||
const OnBlurBlock: FC<OnBlurBlockProps> = ({ onBlur, onFocus }) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const ref = useRef<any>(null)
|
||||
@@ -33,7 +25,7 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
|
||||
}
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
BLUR_COMMAND,
|
||||
@@ -42,22 +34,20 @@ const OnBlurBlock: FC<OnBlurBlockProps> = ({
|
||||
editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' }))
|
||||
}, 200)
|
||||
|
||||
if (onBlur)
|
||||
onBlur()
|
||||
if (onBlur) onBlur()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
FOCUS_COMMAND,
|
||||
() => {
|
||||
if (onFocus)
|
||||
onFocus()
|
||||
if (onFocus) onFocus()
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, onBlur, onFocus])
|
||||
|
||||
|
||||
+3
-11
@@ -1,18 +1,10 @@
|
||||
import { $t } from '@common/locales'
|
||||
import { memo } from 'react'
|
||||
|
||||
const Placeholder = ({
|
||||
compact,
|
||||
value
|
||||
}: {
|
||||
compact?: boolean
|
||||
value?: string
|
||||
className?: string
|
||||
}) => {
|
||||
|
||||
const Placeholder = ({ compact, value }: { compact?: boolean; value?: string; className?: string }) => {
|
||||
return (
|
||||
<div
|
||||
className={`absolute top-0 left-0 h-full w-full text-sm text-[#BBB] select-none pointer-events-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'}`}
|
||||
<div
|
||||
className={`absolute top-0 left-0 h-full w-full text-sm text-[#BBB] select-none pointer-events-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'}`}
|
||||
>
|
||||
{value || $t('AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。')}
|
||||
</div>
|
||||
|
||||
+4
-7
@@ -1,5 +1,4 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelectOrDelete } from '../../hooks'
|
||||
import { DELETE_QUERY_BLOCK_COMMAND } from './index'
|
||||
import { $t } from '@common/locales'
|
||||
@@ -9,9 +8,7 @@ type QueryBlockComponentProps = {
|
||||
nodeKey: string
|
||||
}
|
||||
|
||||
const QueryBlockComponent: FC<QueryBlockComponentProps> = ({
|
||||
nodeKey,
|
||||
}) => {
|
||||
const QueryBlockComponent: FC<QueryBlockComponentProps> = ({ nodeKey }) => {
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_QUERY_BLOCK_COMMAND)
|
||||
|
||||
return (
|
||||
@@ -23,9 +20,9 @@ const QueryBlockComponent: FC<QueryBlockComponentProps> = ({
|
||||
ref={ref}
|
||||
>
|
||||
{/* <UserEdit02 className='mr-1 w-[14px] h-[14px] text-[#FD853A]' /> */}
|
||||
<div className='text-xs font-medium text-[#EC4A0A] opacity-60'>{'{{'}</div>
|
||||
<div className='text-xs font-medium text-[#EC4A0A]'>{$t('查询内容')}</div>
|
||||
<div className='text-xs font-medium text-[#EC4A0A] opacity-60'>{'}}'}</div>
|
||||
<div className="text-xs font-medium text-[#EC4A0A] opacity-60">{'{{'}</div>
|
||||
<div className="text-xs font-medium text-[#EC4A0A]">{$t('查询内容')}</div>
|
||||
<div className="text-xs font-medium text-[#EC4A0A] opacity-60">{'}}'}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-26
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { QueryBlockType } from '../../types'
|
||||
import {
|
||||
$createQueryBlockNode,
|
||||
QueryBlockNode,
|
||||
} from './node'
|
||||
import { $createQueryBlockNode, QueryBlockNode } from './node'
|
||||
|
||||
export const INSERT_QUERY_BLOCK_COMMAND = createCommand('INSERT_QUERY_BLOCK_COMMAND')
|
||||
export const DELETE_QUERY_BLOCK_COMMAND = createCommand('DELETE_QUERY_BLOCK_COMMAND')
|
||||
@@ -22,15 +12,11 @@ export type QueryBlockProps = {
|
||||
onInsert?: () => void
|
||||
onDelete?: () => void
|
||||
}
|
||||
const QueryBlock = memo(({
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: QueryBlockType) => {
|
||||
const QueryBlock = memo(({ onInsert, onDelete }: QueryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([QueryBlockNode]))
|
||||
throw new Error('QueryBlockPlugin: QueryBlock not registered on editor')
|
||||
if (!editor.hasNodes([QueryBlockNode])) throw new Error('QueryBlockPlugin: QueryBlock not registered on editor')
|
||||
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
@@ -39,23 +25,21 @@ const QueryBlock = memo(({
|
||||
const contextBlockNode = $createQueryBlockNode()
|
||||
|
||||
$insertNodes([contextBlockNode])
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_QUERY_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, onInsert, onDelete])
|
||||
|
||||
|
||||
+2
-4
@@ -40,7 +40,7 @@ export class QueryBlockNode extends DecoratorNode<JSX.Element> {
|
||||
exportJSON(): SerializedNode {
|
||||
return {
|
||||
type: 'query-block',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +52,6 @@ export function $createQueryBlockNode(): QueryBlockNode {
|
||||
return new QueryBlockNode()
|
||||
}
|
||||
|
||||
export function $isQueryBlockNode(
|
||||
node: QueryBlockNode | LexicalNode | null | undefined,
|
||||
): node is QueryBlockNode {
|
||||
export function $isQueryBlockNode(node: QueryBlockNode | LexicalNode | null | undefined): node is QueryBlockNode {
|
||||
return node instanceof QueryBlockNode
|
||||
}
|
||||
|
||||
+9
-18
@@ -1,25 +1,16 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { decoratorTransform } from '../../utils'
|
||||
import { QUERY_PLACEHOLDER_TEXT } from '../../constants'
|
||||
import type { QueryBlockType } from '../../types'
|
||||
import {
|
||||
$createQueryBlockNode,
|
||||
QueryBlockNode,
|
||||
} from './node'
|
||||
import { $createQueryBlockNode, QueryBlockNode } from './node'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
|
||||
const REGEX = new RegExp(QUERY_PLACEHOLDER_TEXT)
|
||||
|
||||
const QueryBlockReplacementBlock = ({
|
||||
onInsert,
|
||||
}: QueryBlockType) => {
|
||||
const QueryBlockReplacementBlock = ({ onInsert }: QueryBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -28,29 +19,29 @@ const QueryBlockReplacementBlock = ({
|
||||
}, [editor])
|
||||
|
||||
const createQueryBlockNode = useCallback((): QueryBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
return $applyNodeReplacement($createQueryBlockNode())
|
||||
}, [onInsert])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + QUERY_PLACEHOLDER_TEXT.length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createQueryBlockNode)),
|
||||
editor.registerNodeTransform(CustomTextNode, (textNode) =>
|
||||
decoratorTransform(textNode, getMatch, createQueryBlockNode)
|
||||
)
|
||||
)
|
||||
}, [])
|
||||
|
||||
|
||||
+1
-3
@@ -11,9 +11,7 @@ export const PROMPT_EDITOR_INSERT_QUICKLY = 'PROMPT_EDITOR_INSERT_QUICKLY'
|
||||
type UpdateBlockProps = {
|
||||
instanceId?: string
|
||||
}
|
||||
const UpdateBlock = ({
|
||||
instanceId,
|
||||
}: UpdateBlockProps) => {
|
||||
const UpdateBlock = ({ instanceId }: UpdateBlockProps) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
|
||||
+4
-8
@@ -1,9 +1,5 @@
|
||||
import { useEffect } from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { CustomTextNode } from '../custom-text/node'
|
||||
@@ -24,7 +20,7 @@ const VariableBlock = () => {
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
INSERT_VARIABLE_VALUE_BLOCK_COMMAND,
|
||||
@@ -34,8 +30,8 @@ const VariableBlock = () => {
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
|
||||
+5
-12
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { useLexicalTextEntity } from '../../hooks'
|
||||
import {
|
||||
$createVariableValueBlockNode,
|
||||
VariableValueBlockNode,
|
||||
} from './node'
|
||||
import { $createVariableValueBlockNode, VariableValueBlockNode } from './node'
|
||||
import { getHashtagRegexString } from './utils'
|
||||
|
||||
const REGEX = new RegExp(getHashtagRegexString(), 'i')
|
||||
@@ -28,22 +22,21 @@ const VariableValueBlock = () => {
|
||||
const getVariableValueMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const hashtagLength = matchArr[0].length
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + hashtagLength
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
useLexicalTextEntity<VariableValueBlockNode>(
|
||||
getVariableValueMatch,
|
||||
VariableValueBlockNode,
|
||||
createVariableValueBlockNode,
|
||||
createVariableValueBlockNode
|
||||
)
|
||||
|
||||
return null
|
||||
|
||||
+13
-15
@@ -1,13 +1,5 @@
|
||||
import type {
|
||||
EditorConfig,
|
||||
LexicalNode,
|
||||
NodeKey,
|
||||
SerializedTextNode,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$applyNodeReplacement,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import type { EditorConfig, LexicalNode, NodeKey, SerializedTextNode } from 'lexical'
|
||||
import { $applyNodeReplacement, TextNode } from 'lexical'
|
||||
|
||||
export class VariableValueBlockNode extends TextNode {
|
||||
static getType(): string {
|
||||
@@ -24,7 +16,15 @@ export class VariableValueBlockNode extends TextNode {
|
||||
|
||||
createDOM(config: EditorConfig): HTMLElement {
|
||||
const element = super.createDOM(config)
|
||||
element.classList.add('inline-flex', 'items-center', 'px-0.5', 'h-[22px]', 'text-[#155EEF]', 'rounded-[5px]', 'align-middle')
|
||||
element.classList.add(
|
||||
'inline-flex',
|
||||
'items-center',
|
||||
'px-0.5',
|
||||
'h-[22px]',
|
||||
'text-[#155EEF]',
|
||||
'rounded-[5px]',
|
||||
'align-middle'
|
||||
)
|
||||
return element
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export class VariableValueBlockNode extends TextNode {
|
||||
style: this.getStyle(),
|
||||
text: this.getTextContent(),
|
||||
type: 'variable-value-block',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,6 @@ export function $createVariableValueBlockNode(text = ''): VariableValueBlockNode
|
||||
return $applyNodeReplacement(new VariableValueBlockNode(text))
|
||||
}
|
||||
|
||||
export function $isVariableValueNodeBlock(
|
||||
node: LexicalNode | null | undefined,
|
||||
): node is VariableValueBlockNode {
|
||||
export function $isVariableValueNodeBlock(node: LexicalNode | null | undefined): node is VariableValueBlockNode {
|
||||
return node instanceof VariableValueBlockNode
|
||||
}
|
||||
|
||||
+8
-18
@@ -1,12 +1,6 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { memo, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
} from 'lexical'
|
||||
import { COMMAND_PRIORITY_EDITOR } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
// import {
|
||||
@@ -15,10 +9,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
|
||||
import { useSelectOrDelete } from '../../hooks'
|
||||
import type { WorkflowNodesMap } from './node'
|
||||
import { WorkflowVariableBlockNode } from './node'
|
||||
import {
|
||||
DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND,
|
||||
UPDATE_WORKFLOW_NODES_MAP,
|
||||
} from './index'
|
||||
import { DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, UPDATE_WORKFLOW_NODES_MAP } from './index'
|
||||
// import cn from '@/utils/classnames'
|
||||
// import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
// import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others'
|
||||
@@ -36,7 +27,7 @@ type WorkflowVariableBlockComponentProps = {
|
||||
const WorkflowVariableBlockComponent = ({
|
||||
nodeKey,
|
||||
variables,
|
||||
workflowNodesMap = {},
|
||||
workflowNodesMap = {}
|
||||
}: WorkflowVariableBlockComponentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -66,14 +57,14 @@ const WorkflowVariableBlockComponent = ({
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor])
|
||||
|
||||
const Item = (
|
||||
<div
|
||||
className={`mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none ${isSelected ? 'border-[#84ADFF] bg-[#F5F8FF]' : 'border-black/5 bg-white'}` }
|
||||
className={`mx-0.5 relative group/wrap flex items-center h-[18px] pl-0.5 pr-[3px] rounded-[5px] border select-none ${isSelected ? 'border-[#84ADFF] bg-[#F5F8FF]' : 'border-black/5 bg-white'}`}
|
||||
ref={ref}
|
||||
>
|
||||
{/* {!isEnv && !isChatVar && (
|
||||
@@ -93,7 +84,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
<Line3 className='mr-0.5 text-gray-300'></Line3>
|
||||
</div>
|
||||
)} */}
|
||||
<div className='flex items-center text-primary-600'>
|
||||
<div className="flex items-center text-primary-600">
|
||||
{/* {!isEnv && !isChatVar && <Variable02 className='shrink-0 w-3.5 h-3.5' />}
|
||||
{isEnv && <Env className='shrink-0 w-3.5 h-3.5 text-util-colors-violet-violet-600' />}
|
||||
{isChatVar && <BubbleX className='w-3.5 h-3.5 text-util-colors-teal-teal-700' />}
|
||||
@@ -107,7 +98,6 @@ const WorkflowVariableBlockComponent = ({
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
return Item
|
||||
}
|
||||
|
||||
|
||||
+9
-25
@@ -1,19 +1,9 @@
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import {
|
||||
$insertNodes,
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
createCommand,
|
||||
} from 'lexical'
|
||||
import { memo, useEffect } from 'react'
|
||||
import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import type { WorkflowVariableBlockType } from '../../types'
|
||||
import {
|
||||
$createWorkflowVariableBlockNode,
|
||||
WorkflowVariableBlockNode,
|
||||
} from './node'
|
||||
import { $createWorkflowVariableBlockNode, WorkflowVariableBlockNode } from './node'
|
||||
// import type { Node } from '@/app/components/workflow/types'
|
||||
|
||||
export const INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND')
|
||||
@@ -26,11 +16,7 @@ export type WorkflowVariableBlockProps = {
|
||||
onInsert?: () => void
|
||||
onDelete?: () => void
|
||||
}
|
||||
const WorkflowVariableBlock = memo(({
|
||||
workflowNodesMap,
|
||||
onInsert,
|
||||
onDelete,
|
||||
}: WorkflowVariableBlockType) => {
|
||||
const WorkflowVariableBlock = memo(({ workflowNodesMap, onInsert, onDelete }: WorkflowVariableBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -51,23 +37,21 @@ const WorkflowVariableBlock = memo(({
|
||||
const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap)
|
||||
|
||||
$insertNodes([workflowVariableBlockNode])
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
if (onInsert) onInsert()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
),
|
||||
editor.registerCommand(
|
||||
DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND,
|
||||
() => {
|
||||
if (onDelete)
|
||||
onDelete()
|
||||
if (onDelete) onDelete()
|
||||
|
||||
return true
|
||||
},
|
||||
COMMAND_PRIORITY_EDITOR,
|
||||
),
|
||||
COMMAND_PRIORITY_EDITOR
|
||||
)
|
||||
)
|
||||
}, [editor, onInsert, onDelete, workflowNodesMap])
|
||||
|
||||
|
||||
+6
-3
@@ -63,7 +63,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode<JSX.Element> {
|
||||
type: 'workflow-variable-block',
|
||||
version: 1,
|
||||
variables: this.getVariables(),
|
||||
workflowNodesMap: this.getWorkflowNodesMap(),
|
||||
workflowNodesMap: this.getWorkflowNodesMap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,12 +81,15 @@ export class WorkflowVariableBlockNode extends DecoratorNode<JSX.Element> {
|
||||
return `{{#${this.getVariables().join('.')}#}}`
|
||||
}
|
||||
}
|
||||
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap): WorkflowVariableBlockNode {
|
||||
export function $createWorkflowVariableBlockNode(
|
||||
variables: string[],
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
): WorkflowVariableBlockNode {
|
||||
return new WorkflowVariableBlockNode(variables, workflowNodesMap)
|
||||
}
|
||||
|
||||
export function $isWorkflowVariableBlockNode(
|
||||
node: WorkflowVariableBlockNode | LexicalNode | null | undefined,
|
||||
node: WorkflowVariableBlockNode | LexicalNode | null | undefined
|
||||
): node is WorkflowVariableBlockNode {
|
||||
return node instanceof WorkflowVariableBlockNode
|
||||
}
|
||||
|
||||
+19
-24
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from 'react'
|
||||
import { memo, useCallback, useEffect } from 'react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import { $applyNodeReplacement } from 'lexical'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
@@ -16,10 +12,7 @@ import { WorkflowVariableBlockNode } from './index'
|
||||
|
||||
export const REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}/gi
|
||||
|
||||
const WorkflowVariableBlockReplacementBlock = ({
|
||||
workflowNodesMap,
|
||||
onInsert,
|
||||
}: WorkflowVariableBlockType) => {
|
||||
const WorkflowVariableBlockReplacementBlock = ({ workflowNodesMap, onInsert }: WorkflowVariableBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -27,37 +20,39 @@ const WorkflowVariableBlockReplacementBlock = ({
|
||||
throw new Error('WorkflowVariableBlockNodePlugin: WorkflowVariableBlockNode not registered on editor')
|
||||
}, [editor])
|
||||
|
||||
const createWorkflowVariableBlockNode = useCallback((textNode: TextNode): WorkflowVariableBlockNode => {
|
||||
if (onInsert)
|
||||
onInsert()
|
||||
const createWorkflowVariableBlockNode = useCallback(
|
||||
(textNode: TextNode): WorkflowVariableBlockNode => {
|
||||
if (onInsert) onInsert()
|
||||
|
||||
const nodePathString = textNode.getTextContent().slice(3, -3)
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap))
|
||||
}, [onInsert, workflowNodesMap])
|
||||
const nodePathString = textNode.getTextContent().slice(3, -3)
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap))
|
||||
},
|
||||
[onInsert, workflowNodesMap]
|
||||
)
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
const matchArr = REGEX.exec(text)
|
||||
|
||||
if (matchArr === null)
|
||||
return null
|
||||
if (matchArr === null) return null
|
||||
|
||||
const startOffset = matchArr.index
|
||||
const endOffset = startOffset + matchArr[0].length
|
||||
return {
|
||||
end: endOffset,
|
||||
start: startOffset,
|
||||
start: startOffset
|
||||
}
|
||||
}, [])
|
||||
|
||||
const transformListener = useCallback((textNode: any) => {
|
||||
return decoratorTransform(textNode, getMatch, createWorkflowVariableBlockNode)
|
||||
}, [createWorkflowVariableBlockNode, getMatch])
|
||||
const transformListener = useCallback(
|
||||
(textNode: any) => {
|
||||
return decoratorTransform(textNode, getMatch, createWorkflowVariableBlockNode)
|
||||
},
|
||||
[createWorkflowVariableBlockNode, getMatch]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
REGEX.lastIndex = 0
|
||||
return mergeRegister(
|
||||
editor.registerNodeTransform(CustomTextNode, transformListener),
|
||||
)
|
||||
return mergeRegister(editor.registerNodeTransform(CustomTextNode, transformListener))
|
||||
}, [])
|
||||
|
||||
return null
|
||||
|
||||
+22
-21
@@ -20,7 +20,7 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
onHeightChange,
|
||||
children,
|
||||
footer,
|
||||
hideResize,
|
||||
hideResize
|
||||
}) => {
|
||||
const [clientY, setClientY] = useState(0)
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
@@ -38,19 +38,20 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
document.body.style.userSelect = prevUserSelectStyle
|
||||
}, [prevUserSelectStyle])
|
||||
|
||||
const { run: didHandleResize } = useDebounceFn((e) => {
|
||||
if (!isResizing)
|
||||
return
|
||||
const { run: didHandleResize } = useDebounceFn(
|
||||
(e) => {
|
||||
if (!isResizing) return
|
||||
|
||||
const offset = e.clientY - clientY
|
||||
let newHeight = height + offset
|
||||
setClientY(e.clientY)
|
||||
if (newHeight < minHeight)
|
||||
newHeight = minHeight
|
||||
onHeightChange(newHeight)
|
||||
}, {
|
||||
wait: 0,
|
||||
})
|
||||
const offset = e.clientY - clientY
|
||||
let newHeight = height + offset
|
||||
setClientY(e.clientY)
|
||||
if (newHeight < minHeight) newHeight = minHeight
|
||||
onHeightChange(newHeight)
|
||||
},
|
||||
{
|
||||
wait: 0
|
||||
}
|
||||
)
|
||||
|
||||
const handleResize = useCallback(didHandleResize, [isResizing, height, minHeight, clientY])
|
||||
|
||||
@@ -69,12 +70,11 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
}, [handleStopResize])
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative rounded ant-input-outlined'
|
||||
>
|
||||
<div className={`${className} overflow-y-auto`}
|
||||
<div className="relative rounded ant-input-outlined">
|
||||
<div
|
||||
className={`${className} overflow-y-auto`}
|
||||
style={{
|
||||
height,
|
||||
height
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -83,9 +83,10 @@ const PromptEditorHeightResizeWrap: FC<Props> = ({
|
||||
{footer}
|
||||
{!hideResize && (
|
||||
<div
|
||||
className='absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize'
|
||||
onMouseDown={handleStartResize}>
|
||||
<div className='w-5 h-[3px] rounded-sm bg-gray-300'></div>
|
||||
className="absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize"
|
||||
onMouseDown={handleStartResize}
|
||||
>
|
||||
<div className="w-5 h-[3px] rounded-sm bg-gray-300"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { RoleName } from './plugins/history-block/index'
|
||||
// Node,
|
||||
// } from '@/app/components/workflow/types'
|
||||
|
||||
|
||||
export type NodeOutPutVar = {
|
||||
nodeId: string
|
||||
title: string
|
||||
@@ -12,7 +11,6 @@ export type NodeOutPutVar = {
|
||||
isStartNode?: boolean
|
||||
}
|
||||
|
||||
|
||||
export type Option = {
|
||||
value: string
|
||||
name: string
|
||||
|
||||
@@ -1,45 +1,34 @@
|
||||
import { $isAtNodeEnd } from '@lexical/selection'
|
||||
import type {
|
||||
ElementNode,
|
||||
Klass,
|
||||
LexicalEditor,
|
||||
LexicalNode,
|
||||
RangeSelection,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$createTextNode,
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
$isTextNode,
|
||||
} from 'lexical'
|
||||
import type { ElementNode, Klass, LexicalEditor, LexicalNode, RangeSelection, TextNode } from 'lexical'
|
||||
import { $createTextNode, $getSelection, $isRangeSelection, $isTextNode } from 'lexical'
|
||||
import type { EntityMatch } from '@lexical/text'
|
||||
import { CustomTextNode } from './plugins/custom-text/node'
|
||||
import type { MenuTextMatch } from './types'
|
||||
import { CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT, MAX_VAR_KEY_LENGTH } from './constants'
|
||||
import {
|
||||
CONTEXT_PLACEHOLDER_TEXT,
|
||||
HISTORY_PLACEHOLDER_TEXT,
|
||||
QUERY_PLACEHOLDER_TEXT,
|
||||
PRE_PROMPT_PLACEHOLDER_TEXT,
|
||||
MAX_VAR_KEY_LENGTH
|
||||
} from './constants'
|
||||
|
||||
export function getSelectedNode(
|
||||
selection: RangeSelection,
|
||||
): TextNode | ElementNode {
|
||||
export function getSelectedNode(selection: RangeSelection): TextNode | ElementNode {
|
||||
const anchor = selection.anchor
|
||||
const focus = selection.focus
|
||||
const anchorNode = selection.anchor.getNode()
|
||||
const focusNode = selection.focus.getNode()
|
||||
if (anchorNode === focusNode)
|
||||
return anchorNode
|
||||
if (anchorNode === focusNode) return anchorNode
|
||||
|
||||
const isBackward = selection.isBackward()
|
||||
if (isBackward)
|
||||
return $isAtNodeEnd(focus) ? anchorNode : focusNode
|
||||
else
|
||||
return $isAtNodeEnd(anchor) ? anchorNode : focusNode
|
||||
if (isBackward) return $isAtNodeEnd(focus) ? anchorNode : focusNode
|
||||
else return $isAtNodeEnd(anchor) ? anchorNode : focusNode
|
||||
}
|
||||
|
||||
export function registerLexicalTextEntity<T extends TextNode>(
|
||||
editor: LexicalEditor,
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
targetNode: Klass<T>,
|
||||
createNode: (textNode: TextNode) => T,
|
||||
createNode: (textNode: TextNode) => T
|
||||
) {
|
||||
const isTargetNode = (node: LexicalNode | null | undefined): node is T => {
|
||||
return node instanceof targetNode
|
||||
@@ -56,8 +45,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
}
|
||||
|
||||
const textNodeTransform = (node: TextNode) => {
|
||||
if (!node.isSimpleText())
|
||||
return
|
||||
if (!node.isSimpleText()) return
|
||||
|
||||
const prevSibling = node.getPreviousSibling()
|
||||
let text = node.getTextContent()
|
||||
@@ -73,8 +61,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
if (prevMatch === null || getMode(prevSibling) !== 0) {
|
||||
replaceWithSimpleText(prevSibling)
|
||||
return
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const diff = prevMatch.end - previousText.length
|
||||
|
||||
if (diff > 0) {
|
||||
@@ -85,8 +72,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
|
||||
if (diff === text.length) {
|
||||
node.remove()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const remainingText = text.slice(diff)
|
||||
node.setTextContent(remainingText)
|
||||
}
|
||||
@@ -94,8 +80,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prevMatch === null || prevMatch.start < previousText.length) {
|
||||
} else if (prevMatch === null || prevMatch.start < previousText.length) {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -113,44 +98,34 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
const nextMatch = getMatch(nextText)
|
||||
|
||||
if (nextMatch === null) {
|
||||
if (isTargetNode(nextSibling))
|
||||
replaceWithSimpleText(nextSibling)
|
||||
else
|
||||
nextSibling.markDirty()
|
||||
if (isTargetNode(nextSibling)) replaceWithSimpleText(nextSibling)
|
||||
else nextSibling.markDirty()
|
||||
|
||||
return
|
||||
}
|
||||
else if (nextMatch.start !== 0) {
|
||||
} else if (nextMatch.start !== 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const nextMatch = getMatch(nextText)
|
||||
|
||||
if (nextMatch !== null && nextMatch.start === 0)
|
||||
return
|
||||
if (nextMatch !== null && nextMatch.start === 0) return
|
||||
}
|
||||
|
||||
if (match === null)
|
||||
return
|
||||
if (match === null) return
|
||||
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity())
|
||||
continue
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) continue
|
||||
|
||||
let nodeToReplace
|
||||
|
||||
if (match.start === 0)
|
||||
[nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else
|
||||
[, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
if (match.start === 0) [nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
|
||||
const replacementNode = createNode(nodeToReplace)
|
||||
replacementNode.setFormat(nodeToReplace.getFormat())
|
||||
nodeToReplace.replace(replacementNode)
|
||||
|
||||
if (currentNode == null)
|
||||
return
|
||||
if (currentNode == null) return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,8 +156,7 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) {
|
||||
replaceWithSimpleText(nextSibling) // This may have already been converted in the previous block
|
||||
|
||||
if (isTargetNode(node))
|
||||
replaceWithSimpleText(node)
|
||||
if (isTargetNode(node)) replaceWithSimpleText(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,10 +168,9 @@ export function registerLexicalTextEntity<T extends TextNode>(
|
||||
export const decoratorTransform = (
|
||||
node: CustomTextNode,
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
createNode: (textNode: TextNode) => LexicalNode,
|
||||
createNode: (textNode: TextNode) => LexicalNode
|
||||
) => {
|
||||
if (!node.isSimpleText())
|
||||
return
|
||||
if (!node.isSimpleText()) return
|
||||
|
||||
const prevSibling = node.getPreviousSibling()
|
||||
let text = node.getTextContent()
|
||||
@@ -219,79 +192,56 @@ export const decoratorTransform = (
|
||||
if (nextMatch === null) {
|
||||
nextSibling.markDirty()
|
||||
return
|
||||
}
|
||||
else if (nextMatch.start !== 0) {
|
||||
} else if (nextMatch.start !== 0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const nextMatch = getMatch(nextText)
|
||||
|
||||
if (nextMatch !== null && nextMatch.start === 0)
|
||||
return
|
||||
if (nextMatch !== null && nextMatch.start === 0) return
|
||||
}
|
||||
|
||||
if (match === null)
|
||||
return
|
||||
if (match === null) return
|
||||
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity())
|
||||
continue
|
||||
if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) continue
|
||||
|
||||
let nodeToReplace
|
||||
|
||||
if (match.start === 0)
|
||||
[nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else
|
||||
[, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
if (match.start === 0) [nodeToReplace, currentNode] = currentNode.splitText(match.end)
|
||||
else [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end)
|
||||
|
||||
const replacementNode = createNode(nodeToReplace)
|
||||
nodeToReplace.replace(replacementNode)
|
||||
|
||||
if (currentNode == null)
|
||||
return
|
||||
if (currentNode == null) return
|
||||
}
|
||||
}
|
||||
|
||||
function getFullMatchOffset(
|
||||
documentText: string,
|
||||
entryText: string,
|
||||
offset: number,
|
||||
): number {
|
||||
function getFullMatchOffset(documentText: string, entryText: string, offset: number): number {
|
||||
let triggerOffset = offset
|
||||
for (let i = triggerOffset; i <= entryText.length; i++) {
|
||||
if (documentText.substr(-i) === entryText.substr(0, i))
|
||||
triggerOffset = i
|
||||
if (documentText.substr(-i) === entryText.substr(0, i)) triggerOffset = i
|
||||
}
|
||||
return triggerOffset
|
||||
}
|
||||
|
||||
export function $splitNodeContainingQuery(match: MenuTextMatch): TextNode | null {
|
||||
const selection = $getSelection()
|
||||
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
||||
return null
|
||||
if (!$isRangeSelection(selection) || !selection.isCollapsed()) return null
|
||||
const anchor = selection.anchor
|
||||
if (anchor.type !== 'text')
|
||||
return null
|
||||
if (anchor.type !== 'text') return null
|
||||
const anchorNode = anchor.getNode()
|
||||
if (!anchorNode.isSimpleText())
|
||||
return null
|
||||
if (!anchorNode.isSimpleText()) return null
|
||||
const selectionOffset = anchor.offset
|
||||
const textContent = anchorNode.getTextContent().slice(0, selectionOffset)
|
||||
const characterOffset = match.replaceableString.length
|
||||
const queryOffset = getFullMatchOffset(
|
||||
textContent,
|
||||
match.matchingString,
|
||||
characterOffset,
|
||||
)
|
||||
const queryOffset = getFullMatchOffset(textContent, match.matchingString, characterOffset)
|
||||
const startOffset = selectionOffset - queryOffset
|
||||
if (startOffset < 0)
|
||||
return null
|
||||
if (startOffset < 0) return null
|
||||
let newNode
|
||||
if (startOffset === 0)
|
||||
[newNode] = anchorNode.splitText(selectionOffset)
|
||||
else
|
||||
[, newNode] = anchorNode.splitText(startOffset, selectionOffset)
|
||||
if (startOffset === 0) [newNode] = anchorNode.splitText(selectionOffset)
|
||||
else [, newNode] = anchorNode.splitText(startOffset, selectionOffset)
|
||||
|
||||
return newNode
|
||||
}
|
||||
@@ -303,49 +253,57 @@ export function textToEditorState(text: string) {
|
||||
root: {
|
||||
children: paragraph.map((p) => {
|
||||
return {
|
||||
children: [{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: p,
|
||||
type: 'custom-text',
|
||||
version: 1,
|
||||
}],
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: p,
|
||||
type: 'custom-text',
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1,
|
||||
version: 1
|
||||
}
|
||||
}),
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'root',
|
||||
version: 1,
|
||||
},
|
||||
version: 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const varRegex = /\{\{(.+?)\}\}/g
|
||||
export const getVars = (value: string) => {
|
||||
if (!value)
|
||||
return []
|
||||
if (!value) return []
|
||||
|
||||
const keys = value.match(varRegex)?.filter((item) => {
|
||||
return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item)
|
||||
}).map((item) => {
|
||||
return item.replace('{{', '').replace('}}', '')
|
||||
}).filter(key => key.length <= MAX_VAR_KEY_LENGTH) || []
|
||||
const keys =
|
||||
value
|
||||
.match(varRegex)
|
||||
?.filter((item) => {
|
||||
return ![
|
||||
CONTEXT_PLACEHOLDER_TEXT,
|
||||
HISTORY_PLACEHOLDER_TEXT,
|
||||
QUERY_PLACEHOLDER_TEXT,
|
||||
PRE_PROMPT_PLACEHOLDER_TEXT
|
||||
].includes(item)
|
||||
})
|
||||
.map((item) => {
|
||||
return item.replace('{{', '').replace('}}', '')
|
||||
})
|
||||
.filter((key) => key.length <= MAX_VAR_KEY_LENGTH) || []
|
||||
const keyObj: Record<string, boolean> = {}
|
||||
// remove duplicate keys
|
||||
const res: string[] = []
|
||||
keys.forEach((key) => {
|
||||
if (keyObj[key])
|
||||
return
|
||||
if (keyObj[key]) return
|
||||
|
||||
keyObj[key] = true
|
||||
res.push(key)
|
||||
|
||||
+3
-11
@@ -1,14 +1,6 @@
|
||||
import { $t } from "@common/locales"
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export type PARAM_TYPE =
|
||||
| 'string'
|
||||
| 'float'
|
||||
| 'integer'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'time'
|
||||
| 'datatime'
|
||||
| string
|
||||
export type PARAM_TYPE = 'string' | 'float' | 'integer' | 'boolean' | 'date' | 'time' | 'datatime' | string
|
||||
export type PARAM_KEY_REF_TYPE = {
|
||||
key: string
|
||||
type: string
|
||||
@@ -17,7 +9,7 @@ export type PARAM_KEY_REF_TYPE = {
|
||||
attribute?: string
|
||||
description?: string
|
||||
filter?: string
|
||||
arrayItemKey?:string
|
||||
arrayItemKey?: string
|
||||
}
|
||||
export type PARAM_TYPE_REF_TYPE = {
|
||||
[key: string | number]: PARAM_TYPE
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { parseFormData, parseFileValue, parseFileType, parseHeaders, parseRequestBodyToString, parseUri, payloadStr, goCodeParseFormData } from './transform'
|
||||
import {
|
||||
parseFormData,
|
||||
parseFileValue,
|
||||
parseFileType,
|
||||
parseHeaders,
|
||||
parseRequestBodyToString,
|
||||
parseUri,
|
||||
payloadStr,
|
||||
goCodeParseFormData
|
||||
} from './transform'
|
||||
// import { getJson } from '../.@common/utils/';
|
||||
import { ApiBodyType } from '@common/const/api-detail';
|
||||
import { $t } from '@common/locales';
|
||||
import { ApiBodyType } from '@common/const/api-detail'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
function sameNameToParams(params: unknown) {
|
||||
params = cloneDeep(params)
|
||||
@@ -54,9 +63,9 @@ function enrichParams(params: unknown) {
|
||||
export function generateCode(
|
||||
type: string,
|
||||
multipart: boolean,
|
||||
{ protocol, URL, headers, params, method, requestType, apiRequestParamJsonType, raw }: unknown,
|
||||
{ protocol, URL, headers, params, method, requestType, apiRequestParamJsonType, raw }: unknown
|
||||
) {
|
||||
requestType=ApiBodyType[requestType]
|
||||
requestType = ApiBodyType[requestType]
|
||||
let code: string = ''
|
||||
const indent = ' '
|
||||
let urlObj: unknown = {}
|
||||
@@ -243,9 +252,7 @@ export function generateCode(
|
||||
`${indent}"headers": {\r\n` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${indent}},\r\n` +
|
||||
`${
|
||||
multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''
|
||||
}` +
|
||||
`${multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''}` +
|
||||
`${indent}"data": data,\r\n` +
|
||||
`${indent}"crossDomain": true\r\n` +
|
||||
'})\r\n' +
|
||||
@@ -334,14 +341,10 @@ export function generateCode(
|
||||
'var requestInfo={\r\n' +
|
||||
`${indent}"method": "${method}",\r\n` +
|
||||
`${urlObj.hostname ? `${indent}"hostname": "${urlObj.hostname}",\r\n` : ''}` +
|
||||
`${
|
||||
urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''
|
||||
}` +
|
||||
`${urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''}` +
|
||||
`${
|
||||
urlObj.pathname || urlObj.search
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${
|
||||
urlObj.search || ''
|
||||
}",\r\n`
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${urlObj.search || ''}",\r\n`
|
||||
: ''
|
||||
}` +
|
||||
`${indent}"headers": {\r\n` +
|
||||
@@ -366,19 +369,11 @@ export function generateCode(
|
||||
`var http = require("${urlObj.protocol.replace(':', '')}");\r\n` +
|
||||
'var requestInfo={\r\n' +
|
||||
`${indent}"method": "${method}",\r\n` +
|
||||
`${
|
||||
urlObj.hostname
|
||||
? `${indent}"hostname": "${urlObj.hostname}",\r\n`
|
||||
: ''
|
||||
}` +
|
||||
`${
|
||||
urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''
|
||||
}` +
|
||||
`${urlObj.hostname ? `${indent}"hostname": "${urlObj.hostname}",\r\n` : ''}` +
|
||||
`${urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''}` +
|
||||
`${
|
||||
urlObj.pathname || urlObj.search
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${
|
||||
urlObj.search || ''
|
||||
}",\r\n`
|
||||
? `${indent}"path": "${urlObj.pathname || ''}${urlObj.search || ''}",\r\n`
|
||||
: ''
|
||||
}` +
|
||||
`${indent}"headers": {\r\n` +
|
||||
@@ -439,11 +434,7 @@ export function generateCode(
|
||||
}
|
||||
}
|
||||
code =
|
||||
`${
|
||||
(requestType || 'FORMDATA') === 'FORMDATA' && multipart
|
||||
? 'var fs = require("fs");\r\n'
|
||||
: ''
|
||||
}` +
|
||||
`${(requestType || 'FORMDATA') === 'FORMDATA' && multipart ? 'var fs = require("fs");\r\n' : ''}` +
|
||||
'var request = require("request");\r\n' +
|
||||
'var requestInfo={\r\n' +
|
||||
` method: "${method}",\r\n` +
|
||||
@@ -472,7 +463,7 @@ export function generateCode(
|
||||
params = sameNameToParams(params)
|
||||
let tmpOutput: unknown = ''
|
||||
params.map((val: unknown, key: number) => {
|
||||
if(val.data_type === 'file') {
|
||||
if (val.data_type === 'file') {
|
||||
tmpOutput +=
|
||||
` "${val.name}" =>array(\r\n` +
|
||||
` "type" => "${parseFileType(val.value)}",\r\n` +
|
||||
@@ -481,11 +472,9 @@ export function generateCode(
|
||||
` )${key === params.length - 1 ? '' : ','}\r\n`
|
||||
} else {
|
||||
tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r`
|
||||
|
||||
}
|
||||
})
|
||||
langTmp.paramsStr =
|
||||
'addForm(' + `${tmpOutput.length ? `array(\r\n${tmpOutput})` : ''}` + '\r\n);'
|
||||
langTmp.paramsStr = 'addForm(' + `${tmpOutput.length ? `array(\r\n${tmpOutput})` : ''}` + '\r\n);'
|
||||
langTmp.fileValue = parseFileValue(params)
|
||||
} else {
|
||||
langTmp.paramsStr = `append(new http\\QueryString(array({\r\n${parseFormData(params, {
|
||||
@@ -516,9 +505,7 @@ export function generateCode(
|
||||
const tmpOutput: unknown = []
|
||||
urlObj.searchParams.forEach((val: unknown, key: unknown) => {
|
||||
tmpOutput.push(` ${JSON.stringify(key)} => ${JSON.stringify(val)}`)
|
||||
langTmp.queryStr = `$request->setQuery(new http\\QueryString(array(\r\n${tmpOutput.join(
|
||||
',\r\n'
|
||||
)}\r\n)));`
|
||||
langTmp.queryStr = `$request->setQuery(new http\\QueryString(array(\r\n${tmpOutput.join(',\r\n')}\r\n)));`
|
||||
})
|
||||
if (multipart) {
|
||||
code =
|
||||
@@ -534,7 +521,7 @@ export function generateCode(
|
||||
'$request->setBody($body);\r\n\r\n' +
|
||||
`$request->getBody()->${langTmp.paramsStr}\r\n\r\n` +
|
||||
'$request->setHeaders(array(\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
' "Content-Type":"multipart/form-data"\r\n' +
|
||||
'));\r\n\r\n' +
|
||||
'$client->enqueue($request)->send();\r\n' +
|
||||
@@ -552,7 +539,7 @@ export function generateCode(
|
||||
'$request->setBody($body);\r\n\r\n' +
|
||||
`${langTmp.queryStr ? `${langTmp.queryStr}\r\n\r\n` : ''}` +
|
||||
'$request->setHeaders(array(\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
'));\r\n\r\n' +
|
||||
'$client->enqueue($request)->send();\r\n' +
|
||||
'$response = $client->getResponse();\r\n\r\n' +
|
||||
@@ -572,9 +559,7 @@ export function generateCode(
|
||||
let tmpOutput = ''
|
||||
params.map((val: unknown, key: number) => {
|
||||
if (val.data_type === 'file') {
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${
|
||||
key === params.length - 1 ? '' : ','
|
||||
}\r\n`
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${key === params.length - 1 ? '' : ','}\r\n`
|
||||
} else {
|
||||
tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r`
|
||||
}
|
||||
@@ -614,7 +599,7 @@ export function generateCode(
|
||||
` CURLOPT_CUSTOMREQUEST => "${method}",\r\n` +
|
||||
` CURLOPT_POSTFIELDS => ${langTmp.paramsStr},\r\n` +
|
||||
' CURLOPT_HTTPHEADER => array(\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr},\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr},\r\n` : ''}` +
|
||||
' "Content-Type:multipart/form-data"' +
|
||||
'\r\n ),\r\n' +
|
||||
'));\r\n\r\n' +
|
||||
@@ -769,36 +754,33 @@ export function generateCode(
|
||||
tmpOutput.push(`${JSON.stringify(key)} : ${JSON.stringify(val)}`)
|
||||
// langTmp.querystring = `querystring={${tmpOutput.join(',')}};`
|
||||
langTmp.querystring = `{${tmpOutput.join(',')}}`
|
||||
|
||||
})
|
||||
if(multipart) {
|
||||
code =
|
||||
'import requests \r\n\r\n' +
|
||||
'headers = {\r\n' +
|
||||
`${langTmp.headerStr}\r\n` +
|
||||
'}\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n` +
|
||||
'//获取文件,需填路径 \r\n' +
|
||||
"file_path = '' \r\n" +
|
||||
`filename = "${langTmp.fileValue}" \r\n` +
|
||||
`filetype = "${langTmp.fileType}" \r\n` +
|
||||
'data = { \r\n' +
|
||||
`${langTmp.paramsStr}\r\n` +
|
||||
'} \r\n' +
|
||||
'files = {"file": (filename, open(file_path, "rb"), filetype)} \r\n' +
|
||||
`response=requests.${method.toLowerCase()}(url, files=files, headers=headers, data=data)\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
if (multipart) {
|
||||
code =
|
||||
'import requests \r\n\r\n' +
|
||||
'headers = {\r\n' +
|
||||
`${langTmp.headerStr}\r\n` +
|
||||
'}\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n` +
|
||||
'//获取文件,需填路径 \r\n' +
|
||||
"file_path = '' \r\n" +
|
||||
`filename = "${langTmp.fileValue}" \r\n` +
|
||||
`filetype = "${langTmp.fileType}" \r\n` +
|
||||
'data = { \r\n' +
|
||||
`${langTmp.paramsStr}\r\n` +
|
||||
'} \r\n' +
|
||||
'files = {"file": (filename, open(file_path, "rb"), filetype)} \r\n' +
|
||||
`response=requests.${method.toLowerCase()}(url, files=files, headers=headers, data=data)\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
} else {
|
||||
code =
|
||||
'import requests\r\n\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n\r\n` +
|
||||
`payload = ${
|
||||
langTmp.querystring ? langTmp.querystring : langTmp.paramsStr
|
||||
}\r\n\r\n` +
|
||||
`headers = {\r\n${langTmp.headerStr}` +
|
||||
'\r\n}\r\n\r\n' +
|
||||
`response=requests.request("${method}", url, ${payloadStr(method, headers)}, headers=headers)\r\n\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
code =
|
||||
'import requests\r\n\r\n' +
|
||||
`url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n\r\n` +
|
||||
`payload = ${langTmp.querystring ? langTmp.querystring : langTmp.paramsStr}\r\n\r\n` +
|
||||
`headers = {\r\n${langTmp.headerStr}` +
|
||||
'\r\n}\r\n\r\n' +
|
||||
`response=requests.request("${method}", url, ${payloadStr(method, headers)}, headers=headers)\r\n\r\n` +
|
||||
'print(response.text)\r\n'
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -815,9 +797,7 @@ export function generateCode(
|
||||
let tmpOutput = ''
|
||||
params.map((val: unknown, key: number) => {
|
||||
if (val.data_type === 'file') {
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${
|
||||
key === params.length - 1 ? '' : ','
|
||||
}\r\n`
|
||||
tmpOutput += ` "${val.name}" => new CURLFile($file_path)${key === params.length - 1 ? '' : ','}\r\n`
|
||||
} else {
|
||||
tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r`
|
||||
}
|
||||
@@ -847,7 +827,7 @@ export function generateCode(
|
||||
`request = Net::HTTP::${method.toLowerCase().replace(/^\S/, (s: string) => {
|
||||
return s?.toUpperCase()
|
||||
})}.new(url)\r\n` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`request.body = ${langTmp.paramsStr}\r\n\r\n` +
|
||||
'response = http.request(request)\r\n' +
|
||||
'puts response.read_body'
|
||||
@@ -883,25 +863,19 @@ export function generateCode(
|
||||
}
|
||||
}
|
||||
code =
|
||||
`${
|
||||
requestType === 'JSON' ? `printf '${langTmp.paramsStr}'|` : ''
|
||||
} http ${
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' &&
|
||||
!multipart
|
||||
`${requestType === 'JSON' ? `printf '${langTmp.paramsStr}'|` : ''} http ${
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' && !multipart
|
||||
? '--form'
|
||||
: (requestType || 'JSON').toString() === 'JSON' &&
|
||||
!multipart
|
||||
? '--follow'
|
||||
: ''
|
||||
: (requestType || 'JSON').toString() === 'JSON' && !multipart
|
||||
? '--follow'
|
||||
: ''
|
||||
} ${multipart ? '--ignore-stdin --form --follow' : ''} ${method} '${
|
||||
urlObj.href
|
||||
}' ${multipart ? '\\\r' : '\\'}` +
|
||||
`${multipart ? langTmp.paramsStr : ''}\r` +
|
||||
`${langTmp.headerStr}` +
|
||||
`${
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' &&
|
||||
!multipart &&
|
||||
langTmp.paramsStr
|
||||
(requestType || 'FORMDATA').toString() === 'FORMDATA' && !multipart && langTmp.paramsStr
|
||||
? ` \\\r\n${langTmp.paramsStr}`
|
||||
: ''
|
||||
}`
|
||||
@@ -923,7 +897,6 @@ export function generateCode(
|
||||
}
|
||||
})
|
||||
langTmp.paramsStr = tmpOutput
|
||||
|
||||
} else {
|
||||
const tmpOutput = parseFormData(params, {
|
||||
format: '${name}=${value}',
|
||||
@@ -1024,7 +997,7 @@ export function generateCode(
|
||||
' if err != nil {\r\n' +
|
||||
' return nil, err\r\n' +
|
||||
' }\r\n' +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
`${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` +
|
||||
' req.Header.Add("Content-Type", writer.FormDataContentType())\r\n\r\n' +
|
||||
' client := &http.Client{}\r\n' +
|
||||
' resp, err := client.Do(req)\r\n' +
|
||||
@@ -1071,7 +1044,7 @@ export function generateCode(
|
||||
}
|
||||
default: {
|
||||
langTmp.paramsStr = requestParam ? JSON.stringify(requestParam) : ''
|
||||
code =
|
||||
code =
|
||||
'package main\r\n\r\n' +
|
||||
'import (\r\n' +
|
||||
' "bytes"\r\n' +
|
||||
@@ -1091,7 +1064,7 @@ export function generateCode(
|
||||
'func request() ([]byte, error) {\r\n' +
|
||||
` uri := "${urlObj.href}"\r\n\r\n` +
|
||||
` ${
|
||||
langTmp.paramsStr
|
||||
langTmp.paramsStr
|
||||
? ` payload := map[string]interface{}${langTmp.paramsStr}`
|
||||
: ' payload := strings.NewReader("")'
|
||||
}\r\n\r\n` +
|
||||
@@ -1116,7 +1089,7 @@ export function generateCode(
|
||||
map: stringifyHeaders
|
||||
})
|
||||
let mediaType = 'application/octet-stream'
|
||||
switch ( (requestType || 'FORMDATA').toUpperCase()) {
|
||||
switch ((requestType || 'FORMDATA').toUpperCase()) {
|
||||
case 'FORMDATA': {
|
||||
if (multipart) {
|
||||
mediaType = 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||
@@ -1154,8 +1127,8 @@ export function generateCode(
|
||||
break
|
||||
}
|
||||
}
|
||||
if(multipart) {
|
||||
code =
|
||||
if (multipart) {
|
||||
code =
|
||||
'OkHttpClient client = new OkHttpClient();\r\n\r\n' +
|
||||
'//获取文件,需填路径 \r\n' +
|
||||
'File file = new File(""); \r\n\r\n' +
|
||||
@@ -1175,11 +1148,7 @@ export function generateCode(
|
||||
code =
|
||||
'OkHttpClient client = new OkHttpClient().newBuilder().build();\r\n' +
|
||||
`MediaType mediaType = MediaType.parse("${mediaType}");\r\n` +
|
||||
`${
|
||||
method === 'GET'
|
||||
? ''
|
||||
: `RequestBody body = RequestBody.create(mediaType, ${langTmp.paramsStr});\r\n`
|
||||
}` +
|
||||
`${method === 'GET' ? '' : `RequestBody body = RequestBody.create(mediaType, ${langTmp.paramsStr});\r\n`}` +
|
||||
'Request request = new Request.Builder()\r\n' +
|
||||
` .url("${urlObj.href}")\r\n` +
|
||||
` .method("${method}",${method === 'GET' ? 'null' : 'body'})\r\n` +
|
||||
@@ -1188,7 +1157,7 @@ export function generateCode(
|
||||
'Response response = client.newCall(request).execute();\r\n' +
|
||||
'System.out.println(response.body().string());\r\n'
|
||||
}
|
||||
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1233,9 +1202,7 @@ export function generateCode(
|
||||
`${indent}"header": {\r\n` +
|
||||
`${langTmp.headerStr}\r\n` +
|
||||
`${indent}},\r\n` +
|
||||
`${
|
||||
multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''
|
||||
}` +
|
||||
`${multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''}` +
|
||||
`${langTmp.paramsStr ? `${indent}"data": data,\r\n` : ''}` +
|
||||
`${indent}"success": (response)=> {\r\n` +
|
||||
`${indent + indent}console.log(response.data)\r\n` +
|
||||
|
||||
@@ -1,218 +1,228 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Cascader } from 'antd';
|
||||
import CODE_LANG from '@common/const/code/const';
|
||||
import type { DefaultOptionType } from 'antd/es/cascader';
|
||||
import { useState } from 'react';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { paramsJsonType } from './code-snippets.type';
|
||||
import { DOMAIN_SUFIX } from './code-example.type';
|
||||
import { transfromUrlParam } from './transform';
|
||||
import { generateCode } from './generate-code';
|
||||
import {ApiDetail} from "@common/const/api-detail";
|
||||
import {Codebox} from "@common/components/postcat//api/Codebox";
|
||||
import {Collapse} from "@common/components/postcat/api/Collapse";
|
||||
import {Box} from "@mui/material";
|
||||
import { $t } from '@common/locales';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { Cascader } from 'antd'
|
||||
import CODE_LANG from '@common/const/code/const'
|
||||
import type { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { useState } from 'react'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { paramsJsonType } from './code-snippets.type'
|
||||
import { DOMAIN_SUFIX } from './code-example.type'
|
||||
import { transfromUrlParam } from './transform'
|
||||
import { generateCode } from './generate-code'
|
||||
import { ApiDetail } from '@common/const/api-detail'
|
||||
import { Codebox } from '@common/components/postcat//api/Codebox'
|
||||
import { Collapse } from '@common/components/postcat/api/Collapse'
|
||||
import { Box } from '@mui/material'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
type CodeSnippetCompoType = {
|
||||
title:string
|
||||
api:ApiDetail,
|
||||
extraTitle:unknown,
|
||||
extraContent:unknown,
|
||||
minLines:number
|
||||
title: string
|
||||
api: ApiDetail
|
||||
extraTitle: unknown
|
||||
extraContent: unknown
|
||||
minLines: number
|
||||
}
|
||||
|
||||
const file: unknown[] = []
|
||||
const env: unknown = {}
|
||||
const loading: boolean = false
|
||||
const codeMode: string = 'rust'
|
||||
const codeMens: string[] = ['reset', 'copy', 'download', 'newTab', 'search']
|
||||
const DOMAIN_REGEX: RegExp = new RegExp(
|
||||
`^(((http|ftp|https):\/\/)|)(([\\\w\\\-_]+([\\\w\\\-\\\.]*)?(\\\.(${DOMAIN_SUFIX.join(
|
||||
'|'
|
||||
)})))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(localhost))((\\\/)|(\\\?)|(:)|($))`
|
||||
)
|
||||
const file: unknown[] = []
|
||||
const env: unknown = {}
|
||||
const loading: boolean = false
|
||||
const codeMode: string = 'rust'
|
||||
const codeMens: string[] = ['reset', 'copy', 'download', 'newTab', 'search']
|
||||
const DOMAIN_REGEX: RegExp = new RegExp(
|
||||
`^(((http|ftp|https):\/\/)|)(([\\\w\\\-_]+([\\\w\\\-\\\.]*)?(\\\.(${DOMAIN_SUFIX.join(
|
||||
'|'
|
||||
)})))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(localhost))((\\\/)|(\\\?)|(:)|($))`
|
||||
)
|
||||
|
||||
let isMultipart: boolean = false
|
||||
let isMultipart: boolean = false
|
||||
|
||||
export default function CodeSnippetCompo({title,api, extraTitle, extraContent, minLines=15}: CodeSnippetCompoType) {
|
||||
const {state } = useGlobalContext()
|
||||
// const [tokenState ] = useTokenBasicInfo()
|
||||
const pretreatmentRequestInfo = (apiDoc: ApiDetail) =>{
|
||||
isMultipart = false
|
||||
const result: ApiDetail = cloneDeep(apiDoc)
|
||||
const files: unknown = file || []
|
||||
let isMuti: boolean = false
|
||||
const headers: string[] = []
|
||||
let alreadyHadContentType: boolean = false
|
||||
result.headers = []
|
||||
const originHeader = apiDoc.requestParams?.headerParams
|
||||
//处理请求头部
|
||||
originHeader?.forEach((header: unknown) => {
|
||||
// if (
|
||||
// tokenState?.selected_X_apibee_token &&
|
||||
// originHeader.length &&
|
||||
// originHeader[0].name == 'X-APISpace-Token'
|
||||
// ) {
|
||||
// originHeader[0].value = tokenState?.selected_X_apibee_token
|
||||
// }
|
||||
const { checkbox, name } = header
|
||||
if ((checkbox || !header.hasOwnProperty('checkbox')) && name) {
|
||||
headers.push(name?.toLowerCase())
|
||||
result.headers.push(header)
|
||||
if (/content-type/i.test(name)) {
|
||||
alreadyHadContentType = true
|
||||
}
|
||||
export default function CodeSnippetCompo({
|
||||
title,
|
||||
api,
|
||||
extraTitle,
|
||||
extraContent,
|
||||
minLines = 15
|
||||
}: CodeSnippetCompoType) {
|
||||
const { state } = useGlobalContext()
|
||||
// const [tokenState ] = useTokenBasicInfo()
|
||||
const pretreatmentRequestInfo = (apiDoc: ApiDetail) => {
|
||||
isMultipart = false
|
||||
const result: ApiDetail = cloneDeep(apiDoc)
|
||||
const files: unknown = file || []
|
||||
let isMuti: boolean = false
|
||||
const headers: string[] = []
|
||||
let alreadyHadContentType: boolean = false
|
||||
result.headers = []
|
||||
const originHeader = apiDoc.requestParams?.headerParams
|
||||
//处理请求头部
|
||||
originHeader?.forEach((header: unknown) => {
|
||||
// if (
|
||||
// tokenState?.selected_X_apibee_token &&
|
||||
// originHeader.length &&
|
||||
// originHeader[0].name == 'X-APISpace-Token'
|
||||
// ) {
|
||||
// originHeader[0].value = tokenState?.selected_X_apibee_token
|
||||
// }
|
||||
const { checkbox, name } = header
|
||||
if ((checkbox || !header.hasOwnProperty('checkbox')) && name) {
|
||||
headers.push(name?.toLowerCase())
|
||||
result.headers.push(header)
|
||||
if (/content-type/i.test(name)) {
|
||||
alreadyHadContentType = true
|
||||
}
|
||||
})
|
||||
const query: unknown = {}
|
||||
}
|
||||
})
|
||||
const query: unknown = {}
|
||||
|
||||
apiDoc.requestParams?.queryParams?.forEach((query: unknown) => {
|
||||
const { checkbox, name } = query
|
||||
if ((checkbox || !query.hasOwnProperty('checkbox')) && name) {
|
||||
query[name] = query?.paramAttr.example || ''
|
||||
apiDoc.requestParams?.queryParams?.forEach((query: unknown) => {
|
||||
const { checkbox, name } = query
|
||||
if ((checkbox || !query.hasOwnProperty('checkbox')) && name) {
|
||||
query[name] = query?.paramAttr.example || ''
|
||||
}
|
||||
})
|
||||
result.URL = transfromUrlParam(result.uri, query)
|
||||
|
||||
//处理 restful 参数
|
||||
apiDoc.requestParams?.restParams?.forEach((rest: unknown) => {
|
||||
if ((rest.checkbox || !rest.hasOwnProperty('checkbox')) && rest.name && rest.paramAttr.example) {
|
||||
if (eval(`/:${rest.name}/`).test(result.URL.trim())) {
|
||||
result.URL = result.URL.replaceAll(`:${rest.name}`, rest.paramAttr.example)
|
||||
} else if (
|
||||
result.URL.trim().indexOf(`{{${rest.name}}}`) == -1 &&
|
||||
result.URL.trim().indexOf(`{${rest.name}}`) > -1
|
||||
) {
|
||||
result.URL = result.URL.replaceAll(`{${rest.name}}`, rest.paramAttr.example)
|
||||
}
|
||||
})
|
||||
result.URL = transfromUrlParam(result.uri, query)
|
||||
}
|
||||
})
|
||||
|
||||
//处理 restful 参数
|
||||
apiDoc.requestParams?.restParams?.forEach((rest: unknown) => {
|
||||
if ((rest.checkbox || !rest.hasOwnProperty('checkbox')) && rest.name && rest.paramAttr.example) {
|
||||
if (eval(`/:${rest.name}/`).test(result.URL.trim())) {
|
||||
result.URL = result.URL.replaceAll(`:${rest.name}`, rest.paramAttr.example )
|
||||
} else if (
|
||||
result.URL.trim().indexOf(`{{${rest.name}}}`) == -1 &&
|
||||
result.URL.trim().indexOf(`{${rest.name}}`) > -1
|
||||
) {
|
||||
result.URL = result.URL.replaceAll(`{${rest.name}}`, rest.paramAttr.example)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
result.params = []
|
||||
//为请求参数 中的header、reset、body、query 添加 value 和 valueQuery 的值
|
||||
switch (result.requestParams?.bodyParams?.[0]?.contentType) {
|
||||
case 0: {
|
||||
result.requestParams?.bodyParams?.forEach((body: unknown, key: unknown) => {
|
||||
if ((body.checkbox || !body.hasOwnProperty('checkbox')) && body.name) {
|
||||
if (paramsJsonType[body.dataType] == 'string' && body.paramAttr.example) {
|
||||
isMuti = true
|
||||
body.files = files[key] || []
|
||||
}
|
||||
result.params.push(body)
|
||||
result.params = []
|
||||
//为请求参数 中的header、reset、body、query 添加 value 和 valueQuery 的值
|
||||
switch (result.requestParams?.bodyParams?.[0]?.contentType) {
|
||||
case 0: {
|
||||
result.requestParams?.bodyParams?.forEach((body: unknown, key: unknown) => {
|
||||
if ((body.checkbox || !body.hasOwnProperty('checkbox')) && body.name) {
|
||||
if (paramsJsonType[body.dataType] == 'string' && body.paramAttr.example) {
|
||||
isMuti = true
|
||||
body.files = files[key] || []
|
||||
}
|
||||
result.params.push(body)
|
||||
}
|
||||
})
|
||||
if (!alreadyHadContentType) {
|
||||
if (isMuti) {
|
||||
isMultipart = true
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
|
||||
checkbox: true
|
||||
})
|
||||
} else {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/json',
|
||||
checkbox: true
|
||||
})
|
||||
if (!alreadyHadContentType) {
|
||||
if (isMuti) {
|
||||
isMultipart = true
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
|
||||
checkbox: true
|
||||
})
|
||||
} else {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/x-www-form-urlencoded',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/json',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/xml',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
case 3: {
|
||||
result.params = apiDoc.requestParams?.bodyParams
|
||||
if (!alreadyHadContentType) {
|
||||
result.headers.push({
|
||||
name: 'Content-Type',
|
||||
value: 'application/xml',
|
||||
checkbox: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
result.requestType = result.requestParams?.bodyParams?.[0]?.contentType || 0
|
||||
return result
|
||||
}
|
||||
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [lang, setLang] = useState<number[]>([20])
|
||||
result.requestType = result.requestParams?.bodyParams?.[0]?.contentType || 0
|
||||
return result
|
||||
}
|
||||
|
||||
let tempCode = ''
|
||||
const getCode = (language: number | string) => {
|
||||
if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) {
|
||||
tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例')
|
||||
setCode(tempCode)
|
||||
return
|
||||
}
|
||||
tempCode = generateCode(
|
||||
language.toString(),
|
||||
isMultipart,
|
||||
pretreatmentRequestInfo(cloneDeep(api))
|
||||
)
|
||||
const [code, setCode] = useState<string>('')
|
||||
const [lang, setLang] = useState<number[]>([20])
|
||||
|
||||
let tempCode = ''
|
||||
const getCode = (language: number | string) => {
|
||||
if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) {
|
||||
tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例')
|
||||
setCode(tempCode)
|
||||
return
|
||||
}
|
||||
tempCode = generateCode(language.toString(), isMultipart, pretreatmentRequestInfo(cloneDeep(api)))
|
||||
setCode(tempCode)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!Object.keys(api).length) return
|
||||
getCode(lang[lang.length -1 ])
|
||||
}, [api])
|
||||
useEffect(() => {
|
||||
if (!Object.keys(api).length) return
|
||||
getCode(lang[lang.length - 1])
|
||||
}, [api])
|
||||
|
||||
const onChange = (value: number[], record: DefaultOptionType[]) => {
|
||||
const num = value[value.length - 1]
|
||||
setLang(value)
|
||||
if (!Object.keys(api).length) return
|
||||
getCode(num)
|
||||
}
|
||||
|
||||
const onChange = (value: number[],record:DefaultOptionType[]) => {
|
||||
const num = value[value.length - 1]
|
||||
setLang(value)
|
||||
if(!Object.keys(api).length) return
|
||||
getCode(num)
|
||||
};
|
||||
const filter = (inputValue: string, path: DefaultOptionType[]) =>
|
||||
path.some((option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1)
|
||||
|
||||
const filter = (inputValue: string, path: DefaultOptionType[]) =>
|
||||
path.some(
|
||||
(option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
|
||||
);
|
||||
const [placeholderTxt, setPlaceholderTxt] = useState($t('搜索编程语言...'))
|
||||
const [selectItemTxt, setSelectItemTxt] = useState('')
|
||||
|
||||
const [placeholderTxt, setPlaceholderTxt] = useState($t('搜索编程语言...'))
|
||||
const [selectItemTxt, setSelectItemTxt ] = useState('')
|
||||
|
||||
const codeLangOptions = useMemo(()=>CODE_LANG.map(x=>({...x, label:$t(x.label as string)})),[state.language])
|
||||
return (
|
||||
|
||||
<Collapse title={title}>
|
||||
<Box width="100%">
|
||||
<>
|
||||
<Codebox extraContent={<><span className="ml-[12px]">{$t('编程语言')}:</span><Cascader
|
||||
const codeLangOptions = useMemo(
|
||||
() => CODE_LANG.map((x) => ({ ...x, label: $t(x.label as string) })),
|
||||
[state.language]
|
||||
)
|
||||
return (
|
||||
<Collapse title={title}>
|
||||
<Box width="100%">
|
||||
<>
|
||||
<Codebox
|
||||
extraContent={
|
||||
<>
|
||||
<span className="ml-[12px]">{$t('编程语言')}:</span>
|
||||
<Cascader
|
||||
options={codeLangOptions}
|
||||
onChange={(value,record) => onChange(value as unknown as number[],record)}
|
||||
onChange={(value, record) => onChange(value as unknown as number[], record)}
|
||||
placeholder={placeholderTxt}
|
||||
value={lang} // 当前的值
|
||||
showSearch={{ filter }}
|
||||
size="small"
|
||||
allowClear={false}
|
||||
// onDropdownVisibleChange={value => openChange(value)}
|
||||
/></>}
|
||||
language={'javascript'} value={code} readOnly={true} height={'250px'} width={'100%'}/>
|
||||
</>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
language={'javascript'}
|
||||
value={code}
|
||||
readOnly={true}
|
||||
height={'250px'}
|
||||
width={'100%'}
|
||||
/>
|
||||
</>
|
||||
</Box>
|
||||
</Collapse>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { PARAM_KEY_REF_TYPE, PARAM_TYPE_REF_TYPE } from './code-snippets.type';
|
||||
import { tranformJson, tranformXml } from './util';
|
||||
import { PARAM_KEY_REF_TYPE, PARAM_TYPE_REF_TYPE } from './code-snippets.type'
|
||||
import { tranformJson, tranformXml } from './util'
|
||||
type LANG_TYPE = 'Java' | 'HTTP' | 'shellHttpie' | 'go' | 'NodeJSNative'
|
||||
type PARAM_HEADER_TYPE = { name: string; value: string }
|
||||
type PARSE_OPTS_TYPE = {
|
||||
@@ -30,201 +30,199 @@ export const paramsJsonType: unknown = {
|
||||
NULL: 'null'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 拼接地址栏query参数,返回url
|
||||
* @param {string} url 地址栏url
|
||||
* @param {Object} query 地址栏query对象
|
||||
*/
|
||||
export function transfromUrlParam(url: string, query: { [key: string]: string }) {
|
||||
const querys: string[] = []
|
||||
Object.entries(query).forEach((item: string[]) => {
|
||||
querys.push(`${item[0]}=${item[1]}`)
|
||||
})
|
||||
if (!querys.length) return url
|
||||
return `${url}${url.includes('?') ? '&' : '?'}${querys.join('&')}`
|
||||
}
|
||||
export function parseUri(protocol: string, url: string) {
|
||||
if (!/((http:\/\/)|(https:\/\/))/.test(url)) {
|
||||
url = (protocol == 'HTTPS' ? 'https://' : 'http://') + url
|
||||
}
|
||||
return url
|
||||
/**
|
||||
* @description 拼接地址栏query参数,返回url
|
||||
* @param {string} url 地址栏url
|
||||
* @param {Object} query 地址栏query对象
|
||||
*/
|
||||
export function transfromUrlParam(url: string, query: { [key: string]: string }) {
|
||||
const querys: string[] = []
|
||||
Object.entries(query).forEach((item: string[]) => {
|
||||
querys.push(`${item[0]}=${item[1]}`)
|
||||
})
|
||||
if (!querys.length) return url
|
||||
return `${url}${url.includes('?') ? '&' : '?'}${querys.join('&')}`
|
||||
}
|
||||
export function parseUri(protocol: string, url: string) {
|
||||
if (!/((http:\/\/)|(https:\/\/))/.test(url)) {
|
||||
url = (protocol == 'HTTPS' ? 'https://' : 'http://') + url
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 处理FormData格式请求参数
|
||||
* @param {Object} options {format:生成Formdata格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} params 待拼接数组
|
||||
*/
|
||||
export function parseFormData(params: unknown, { map, init, format, separator, langType, hasFileParams }: PARSE_OPTS_TYPE = {}) {
|
||||
if (map) params = cloneDeep(params)
|
||||
if (init) params = init(params)
|
||||
if (format) {
|
||||
//x-www
|
||||
const result: unknown = []
|
||||
params.map((val: unknown) => {
|
||||
if (map) val = map(val)
|
||||
result.push(format.replace('${name}', val.name).replace('${value}', val.value))
|
||||
})
|
||||
return result.join(separator || '&')
|
||||
}
|
||||
//multipart
|
||||
let result: string = ''
|
||||
const boundary: string = 'WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||
params.forEach((val: unknown) => {
|
||||
/**
|
||||
* @description 处理FormData格式请求参数
|
||||
* @param {Object} options {format:生成Formdata格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} params 待拼接数组
|
||||
*/
|
||||
export function parseFormData(
|
||||
params: unknown,
|
||||
{ map, init, format, separator, langType, hasFileParams }: PARSE_OPTS_TYPE = {}
|
||||
) {
|
||||
if (map) params = cloneDeep(params)
|
||||
if (init) params = init(params)
|
||||
if (format) {
|
||||
//x-www
|
||||
const result: unknown = []
|
||||
params.map((val: unknown) => {
|
||||
if (map) val = map(val)
|
||||
if (val.files) {
|
||||
if (val.files.length) {
|
||||
result += `------${boundary}\r\n`
|
||||
val.files.map((childVal: unknown) => {
|
||||
result += `content-disposition: form-data; name="${val.name}"; filename="${val.value}"\r\n`
|
||||
if (typeof childVal === 'string') {
|
||||
result += `Content-Type: ${((childVal.match(/data:(.*);/) || [])[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
} else {
|
||||
result += `Content-Type: ${(childVal.dataUrl.match(/data:(.*);/)[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
}
|
||||
result += '\r\n'
|
||||
})
|
||||
}
|
||||
} else {
|
||||
result += `------${boundary}\r\n`
|
||||
result += `content-disposition: form-data; name="${val.name}"\r\n`
|
||||
result += '\r\n'
|
||||
result += `${val.value}\r\n`
|
||||
}
|
||||
result.push(format.replace('${name}', val.name).replace('${value}', val.value))
|
||||
})
|
||||
result += `------${boundary}--`
|
||||
return result
|
||||
return result.join(separator || '&')
|
||||
}
|
||||
export function parseFileValue (params: unknown[]) {
|
||||
const tmp = {
|
||||
output: ''
|
||||
}
|
||||
if (params && params.length) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
if (params[i].data_type === 'file') {
|
||||
tmp.output = params[i].value || ''
|
||||
break
|
||||
}
|
||||
//multipart
|
||||
let result: string = ''
|
||||
const boundary: string = 'WebKitFormBoundary7MA4YWxkTrZu0gW'
|
||||
params.forEach((val: unknown) => {
|
||||
if (map) val = map(val)
|
||||
if (val.files) {
|
||||
if (val.files.length) {
|
||||
result += `------${boundary}\r\n`
|
||||
val.files.map((childVal: unknown) => {
|
||||
result += `content-disposition: form-data; name="${val.name}"; filename="${val.value}"\r\n`
|
||||
if (typeof childVal === 'string') {
|
||||
result += `Content-Type: ${((childVal.match(/data:(.*);/) || [])[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
} else {
|
||||
result += `Content-Type: ${(childVal.dataUrl.match(/data:(.*);/)[0] || '')
|
||||
.replace(/^data:/, '')
|
||||
.replace(/;$/, '')}\r\n`
|
||||
}
|
||||
result += '\r\n'
|
||||
})
|
||||
}
|
||||
return tmp.output
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function payloadStr (method: string, headers: unknown[]) {
|
||||
let tmpStr = ''
|
||||
if (method === 'GET') {
|
||||
tmpStr = 'params=payload'
|
||||
} else {
|
||||
headers.forEach((item) => {
|
||||
if (item.name === 'Content-Type') {
|
||||
switch (item.description || item.value) {
|
||||
case 'application/x-www-form-urlencoded':
|
||||
tmpStr = 'data=payload'
|
||||
break
|
||||
case 'application/json':
|
||||
tmpStr = 'data=json.dumps(payload)'
|
||||
break
|
||||
default: {
|
||||
tmpStr = 'params=payload'
|
||||
}
|
||||
result += `------${boundary}\r\n`
|
||||
result += `content-disposition: form-data; name="${val.name}"\r\n`
|
||||
result += '\r\n'
|
||||
result += `${val.value}\r\n`
|
||||
}
|
||||
})
|
||||
result += `------${boundary}--`
|
||||
return result
|
||||
}
|
||||
export function parseFileValue(params: unknown[]) {
|
||||
const tmp = {
|
||||
output: ''
|
||||
}
|
||||
if (params && params.length) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
if (params[i].data_type === 'file') {
|
||||
tmp.output = params[i].value || ''
|
||||
break
|
||||
}
|
||||
}
|
||||
return tmp.output
|
||||
}
|
||||
}
|
||||
|
||||
export function payloadStr(method: string, headers: unknown[]) {
|
||||
let tmpStr = ''
|
||||
if (method === 'GET') {
|
||||
tmpStr = 'params=payload'
|
||||
} else {
|
||||
headers.forEach((item) => {
|
||||
if (item.name === 'Content-Type') {
|
||||
switch (item.description || item.value) {
|
||||
case 'application/x-www-form-urlencoded':
|
||||
tmpStr = 'data=payload'
|
||||
break
|
||||
case 'application/json':
|
||||
tmpStr = 'data=json.dumps(payload)'
|
||||
break
|
||||
default: {
|
||||
tmpStr = 'params=payload'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return tmpStr
|
||||
}
|
||||
|
||||
export function goCodeParseFormData(params: unknown[]) {
|
||||
if (!params.length) return
|
||||
let output = ''
|
||||
params.forEach((item) => {
|
||||
output += `payload.Set("${item.name}", "${item.value}")\r\n `
|
||||
})
|
||||
return output
|
||||
}
|
||||
export function parseFileType (fileValue: string) {
|
||||
const isPng = fileValue.endsWith('.png')
|
||||
const isJpeg = fileValue.endsWith('.jpeg')
|
||||
const isJpg = fileValue.endsWith('.jpg')
|
||||
let result = 'image/jpeg'
|
||||
if (isPng) {
|
||||
result = 'image/png'
|
||||
} else if (isJpg) {
|
||||
result = 'image/jpeg'
|
||||
} else if (isJpeg) {
|
||||
result = 'image/jpeg'
|
||||
}
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* @description 处理请求头格式
|
||||
* @param {Object} options {format:生成请求头格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} headers 待拼接数组
|
||||
*/
|
||||
export function parseHeaders(
|
||||
headers: PARAM_HEADER_TYPE[],
|
||||
{ map, filter, format, separator }: PARSE_OPTS_TYPE = {}
|
||||
) {
|
||||
const result = []
|
||||
if (map) {
|
||||
headers = cloneDeep(headers)
|
||||
}
|
||||
for (const key in headers) {
|
||||
let val = headers[key]
|
||||
if (map) {
|
||||
val = map(val)
|
||||
}
|
||||
if (filter) {
|
||||
if (filter(val)) {
|
||||
result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value))
|
||||
}
|
||||
} else {
|
||||
})
|
||||
}
|
||||
return tmpStr
|
||||
}
|
||||
|
||||
export function goCodeParseFormData(params: unknown[]) {
|
||||
if (!params.length) return
|
||||
let output = ''
|
||||
params.forEach((item) => {
|
||||
output += `payload.Set("${item.name}", "${item.value}")\r\n `
|
||||
})
|
||||
return output
|
||||
}
|
||||
export function parseFileType(fileValue: string) {
|
||||
const isPng = fileValue.endsWith('.png')
|
||||
const isJpeg = fileValue.endsWith('.jpeg')
|
||||
const isJpg = fileValue.endsWith('.jpg')
|
||||
let result = 'image/jpeg'
|
||||
if (isPng) {
|
||||
result = 'image/png'
|
||||
} else if (isJpg) {
|
||||
result = 'image/jpeg'
|
||||
} else if (isJpeg) {
|
||||
result = 'image/jpeg'
|
||||
}
|
||||
return result
|
||||
}
|
||||
/**
|
||||
* @description 处理请求头格式
|
||||
* @param {Object} options {format:生成请求头格式[option],separator:组合字符串的分割符[option]}
|
||||
* @param {Array} headers 待拼接数组
|
||||
*/
|
||||
export function parseHeaders(headers: PARAM_HEADER_TYPE[], { map, filter, format, separator }: PARSE_OPTS_TYPE = {}) {
|
||||
const result = []
|
||||
if (map) {
|
||||
headers = cloneDeep(headers)
|
||||
}
|
||||
for (const key in headers) {
|
||||
let val = headers[key]
|
||||
if (map) {
|
||||
val = map(val)
|
||||
}
|
||||
if (filter) {
|
||||
if (filter(val)) {
|
||||
result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value))
|
||||
}
|
||||
} else {
|
||||
result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value))
|
||||
}
|
||||
return result.join(separator || '')
|
||||
}
|
||||
const keyRefs: PARAM_KEY_REF_TYPE = {
|
||||
key: 'name',
|
||||
type: 'data_type',
|
||||
value: 'value',
|
||||
childKey: 'child_list',
|
||||
arrayItemKey: 'isArrItem'
|
||||
}
|
||||
return result.join(separator || '')
|
||||
}
|
||||
const keyRefs: PARAM_KEY_REF_TYPE = {
|
||||
key: 'name',
|
||||
type: 'data_type',
|
||||
value: 'value',
|
||||
childKey: 'child_list',
|
||||
arrayItemKey: 'isArrItem'
|
||||
}
|
||||
|
||||
const typeRefs: PARAM_TYPE_REF_TYPE = paramsJsonType
|
||||
const typeRefs: PARAM_TYPE_REF_TYPE = paramsJsonType
|
||||
|
||||
export const parseRequestBodyToString = ({ requestType, params, apiRequestParamJsonType, raw }: unknown) => {
|
||||
let result: string = ''
|
||||
switch ((requestType || 'FORAMDATA').toString()) {
|
||||
case 'RAW': {
|
||||
//raw
|
||||
// todo
|
||||
result = raw
|
||||
break
|
||||
}
|
||||
case 'JSON': {
|
||||
//json
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformJson(params, keyRefs, typeRefs)
|
||||
if (apiRequestParamJsonType === 'ARRAY') {
|
||||
//array
|
||||
result = `[${result}]`
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'XML': {
|
||||
//xml
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformXml(params, keyRefs, typeRefs)
|
||||
break
|
||||
}
|
||||
export const parseRequestBodyToString = ({ requestType, params, apiRequestParamJsonType, raw }: unknown) => {
|
||||
let result: string = ''
|
||||
switch ((requestType || 'FORAMDATA').toString()) {
|
||||
case 'RAW': {
|
||||
//raw
|
||||
// todo
|
||||
result = raw
|
||||
break
|
||||
}
|
||||
return result
|
||||
}
|
||||
case 'JSON': {
|
||||
//json
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformJson(params, keyRefs, typeRefs)
|
||||
if (apiRequestParamJsonType === 'ARRAY') {
|
||||
//array
|
||||
result = `[${result}]`
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'XML': {
|
||||
//xml
|
||||
if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name'
|
||||
result = tranformXml(params, keyRefs, typeRefs)
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import {Random} from 'mockjs'
|
||||
import { PARAM_KEY_REF_TYPE, PARAM_LIST_TYPE, PARAM_LIS_ITEM_TYPE, PARAM_TYPE, PARAM_TYPE_REF_TYPE } from "./code-snippets.type"
|
||||
import { Random } from 'mockjs'
|
||||
import {
|
||||
PARAM_KEY_REF_TYPE,
|
||||
PARAM_LIST_TYPE,
|
||||
PARAM_LIS_ITEM_TYPE,
|
||||
PARAM_TYPE,
|
||||
PARAM_TYPE_REF_TYPE
|
||||
} from './code-snippets.type'
|
||||
const DEFAULT_PARAM_KEY_REF: PARAM_KEY_REF_TYPE = {
|
||||
key: 'key',
|
||||
type: 'type',
|
||||
value: 'value'
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自定义列表转换为 xml
|
||||
* @param list 列表
|
||||
* @param keyRefs 关键词映射
|
||||
* @param random 是否随机值
|
||||
* @returns xml 字符串
|
||||
*/
|
||||
export function tranformXml(
|
||||
/**
|
||||
* 将自定义列表转换为 xml
|
||||
* @param list 列表
|
||||
* @param keyRefs 关键词映射
|
||||
* @param random 是否随机值
|
||||
* @returns xml 字符串
|
||||
*/
|
||||
export function tranformXml(
|
||||
list: PARAM_LIST_TYPE,
|
||||
keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF,
|
||||
typeRefs: PARAM_TYPE_REF_TYPE = {},
|
||||
@@ -74,40 +80,40 @@ export function tranformJson(
|
||||
return `{${result.join(',')}}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自定义列表转换为地址栏参数
|
||||
* @param list 列表
|
||||
* @param keyRefs 关键词映射
|
||||
* @param random 是否随机值
|
||||
* @returns key-value 结构字符串
|
||||
*/
|
||||
export function tranformUrlParam(
|
||||
list: PARAM_LIST_TYPE,
|
||||
keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF,
|
||||
typeRefs: PARAM_TYPE_REF_TYPE = {},
|
||||
random: boolean = false
|
||||
) {
|
||||
const { key, value, filter, type } = keyRefs
|
||||
const result: string[] = []
|
||||
list.forEach((item: unknown) => {
|
||||
if (filter && item[filter]) return
|
||||
const tab: string = item[key]
|
||||
if (!tab) return
|
||||
const itemType: PARAM_TYPE = typeRefs[item[type]]
|
||||
/**
|
||||
* 将自定义列表转换为地址栏参数
|
||||
* @param list 列表
|
||||
* @param keyRefs 关键词映射
|
||||
* @param random 是否随机值
|
||||
* @returns key-value 结构字符串
|
||||
*/
|
||||
export function tranformUrlParam(
|
||||
list: PARAM_LIST_TYPE,
|
||||
keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF,
|
||||
typeRefs: PARAM_TYPE_REF_TYPE = {},
|
||||
random: boolean = false
|
||||
) {
|
||||
const { key, value, filter, type } = keyRefs
|
||||
const result: string[] = []
|
||||
list.forEach((item: unknown) => {
|
||||
if (filter && item[filter]) return
|
||||
const tab: string = item[key]
|
||||
if (!tab) return
|
||||
const itemType: PARAM_TYPE = typeRefs[item[type]]
|
||||
|
||||
const text: string = random === true ? getRandomDataByType(itemType) : item[value]
|
||||
result.push(`${tab}=${text}`)
|
||||
})
|
||||
return result.join('&') //分隔符为&
|
||||
}
|
||||
/**
|
||||
* 将自定义列表转换为 key-value 结构
|
||||
* @param list 列表
|
||||
* @param keyRefs 关键词映射
|
||||
* @param random 是否随机值
|
||||
* @returns
|
||||
*/
|
||||
export function tranformKeyValue(
|
||||
const text: string = random === true ? getRandomDataByType(itemType) : item[value]
|
||||
result.push(`${tab}=${text}`)
|
||||
})
|
||||
return result.join('&') //分隔符为&
|
||||
}
|
||||
/**
|
||||
* 将自定义列表转换为 key-value 结构
|
||||
* @param list 列表
|
||||
* @param keyRefs 关键词映射
|
||||
* @param random 是否随机值
|
||||
* @returns
|
||||
*/
|
||||
export function tranformKeyValue(
|
||||
list: PARAM_LIST_TYPE,
|
||||
keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF,
|
||||
typeRefs: PARAM_TYPE_REF_TYPE = {},
|
||||
|
||||
@@ -1,112 +1,147 @@
|
||||
import {useState, useEffect, useImperativeHandle} from 'react';
|
||||
import {AutoComplete, Empty, Tabs} from 'antd';
|
||||
import { ResultListType} from "@common/const/api-detail";
|
||||
import {Collapse} from "@common/components/postcat/api/Collapse";
|
||||
import {Box} from "@mui/material";
|
||||
import {Codebox} from "@common/components/postcat/api/Codebox";
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { $t } from '@common/locales';
|
||||
import { useState, useEffect, useImperativeHandle } from 'react'
|
||||
import { AutoComplete, Empty, Tabs } from 'antd'
|
||||
import { ResultListType } from '@common/const/api-detail'
|
||||
import { Collapse } from '@common/components/postcat/api/Collapse'
|
||||
import { Box } from '@mui/material'
|
||||
import { Codebox } from '@common/components/postcat/api/Codebox'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export interface ResponseExampleCompoEditorApi {
|
||||
getData: () => ResultListType[] | []
|
||||
getData: () => ResultListType[] | []
|
||||
}
|
||||
|
||||
const DEFAULT_RESULT_LIST = [
|
||||
{id:'success',name:$t('成功示例'),httpCode:'200',content:''},
|
||||
{id:'failed',name:$t('失败示例'),httpCode:'200',content:''},
|
||||
{ id: 'success', name: $t('成功示例'), httpCode: '200', content: '' },
|
||||
{ id: 'failed', name: $t('失败示例'), httpCode: '200', content: '' }
|
||||
]
|
||||
|
||||
export const HTTP_STATUS_CODE = ['200', '403', '404', '410', '422', '500', '502', '503', '504']
|
||||
|
||||
export const CONTENT_TYPE_TYPE = [
|
||||
'application/json',
|
||||
'application/x-www-form-urlencoded',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'multipart/form-data',
|
||||
'text/asp',
|
||||
'text/css',
|
||||
'text/html',
|
||||
'text/html; charset=UTF-8',
|
||||
'text/plain',
|
||||
'text/xml'
|
||||
'application/json',
|
||||
'application/x-www-form-urlencoded',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'multipart/form-data',
|
||||
'text/asp',
|
||||
'text/css',
|
||||
'text/html',
|
||||
'text/html; charset=UTF-8',
|
||||
'text/plain',
|
||||
'text/xml'
|
||||
]
|
||||
|
||||
export function ResponseExampleCompo ({ editorRef,title,detail,mode='view' }: {editorRef?: React.RefObject<ResponseExampleCompoEditorApi>,title:string, detail:resultList[]}) {
|
||||
const [resultDemos, setResultDemos] = useState<unknown>([]);
|
||||
export function ResponseExampleCompo({
|
||||
editorRef,
|
||||
title,
|
||||
detail,
|
||||
mode = 'view'
|
||||
}: {
|
||||
editorRef?: React.RefObject<ResponseExampleCompoEditorApi>
|
||||
title: string
|
||||
detail: resultList[]
|
||||
}) {
|
||||
const [resultDemos, setResultDemos] = useState<unknown>([])
|
||||
|
||||
useImperativeHandle(editorRef, () => ({
|
||||
getData: () => {
|
||||
return resultDemos||[]
|
||||
}
|
||||
}))
|
||||
useImperativeHandle(editorRef, () => ({
|
||||
getData: () => {
|
||||
return resultDemos || []
|
||||
}
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if(mode === 'view'){
|
||||
setResultDemos(detail);
|
||||
}else{
|
||||
setResultDemos(detail?.length > 0 ? detail: cloneDeep(DEFAULT_RESULT_LIST))
|
||||
}
|
||||
}, [detail]);
|
||||
|
||||
const updateResultList = (id:string, type:'httpCode' | 'httpContentType'|'content',value:string) => {
|
||||
setResultDemos((prevList)=>{
|
||||
for(let i = 0 ; i < prevList.length; i++){
|
||||
if(prevList[i].id === id){
|
||||
prevList[i][type] = value
|
||||
return prevList
|
||||
}
|
||||
}
|
||||
})
|
||||
if (mode === 'view') {
|
||||
setResultDemos(detail)
|
||||
} else {
|
||||
setResultDemos(detail?.length > 0 ? detail : cloneDeep(DEFAULT_RESULT_LIST))
|
||||
}
|
||||
}, [detail])
|
||||
|
||||
const updateResultList = (id: string, type: 'httpCode' | 'httpContentType' | 'content', value: string) => {
|
||||
setResultDemos((prevList) => {
|
||||
for (let i = 0; i < prevList.length; i++) {
|
||||
if (prevList[i].id === id) {
|
||||
prevList[i][type] = value
|
||||
return prevList
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse title={title}>
|
||||
<Box width="100%">
|
||||
<Collapse title={title}>
|
||||
<Box width="100%">
|
||||
<Tabs className=" small-tabs" defaultActiveKey={resultDemos?.[0]?.id}>
|
||||
{resultDemos && resultDemos?.map((item:ResultListType) => (
|
||||
<Tabs.TabPane key={item.id} tab={item.name}>
|
||||
<div >
|
||||
<div className="ml-[8px] mb-[8px] flex">
|
||||
{mode === 'view' ?
|
||||
item.content ? <span className="mr-btnbase py-[5px] px-btnbase text-DEFAULT bg-[#f7f8fa] rounded border-[1px] border-solid border-DEFAULT"> HTTP Status Code: {item.httpCode}</span>:''
|
||||
: <AutoComplete
|
||||
className="mr-btnbase rounded "
|
||||
options={HTTP_STATUS_CODE.map((code)=>({label:code, value:code}))}
|
||||
style={{ width: 200 }}
|
||||
value={item.httpCode}
|
||||
status={item.httpCode ? '' : 'error'}
|
||||
onSelect={(value)=>updateResultList(item.id,'httpCode',value)}
|
||||
placeholder={$t("HTTP 状态码")}
|
||||
/>
|
||||
}
|
||||
{mode === 'view' ?
|
||||
item.content ? <span className="mr-btnbase py-[5px] px-btnbase text-DEFAULT bg-[#f7f8fa] rounded border-[1px] border-solid border-DEFAULT">Content-Type: {item.httpContentType || 'text/html;charset=UTF-8'}</span>:''
|
||||
: <AutoComplete
|
||||
options={CONTENT_TYPE_TYPE.map((type)=>({label:type, value:type}))}
|
||||
style={{ width: 200 }}
|
||||
value={item.httpContentType || 'text/html;charset=UTF-8'}
|
||||
onSelect={(value)=>updateResultList(item.id,'httpContentType',value)}
|
||||
placeholder={$t("默认 text/html;charset=UTF-8")}
|
||||
/>}
|
||||
{resultDemos &&
|
||||
resultDemos?.map((item: ResultListType) => (
|
||||
<Tabs.TabPane key={item.id} tab={item.name}>
|
||||
<div>
|
||||
<div className="ml-[8px] mb-[8px] flex">
|
||||
{mode === 'view' ? (
|
||||
item.content ? (
|
||||
<span className="mr-btnbase py-[5px] px-btnbase text-DEFAULT bg-[#f7f8fa] rounded border-[1px] border-solid border-DEFAULT">
|
||||
{' '}
|
||||
HTTP Status Code: {item.httpCode}
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
) : (
|
||||
<AutoComplete
|
||||
className="mr-btnbase rounded "
|
||||
options={HTTP_STATUS_CODE.map((code) => ({ label: code, value: code }))}
|
||||
style={{ width: 200 }}
|
||||
value={item.httpCode}
|
||||
status={item.httpCode ? '' : 'error'}
|
||||
onSelect={(value) => updateResultList(item.id, 'httpCode', value)}
|
||||
placeholder={$t('HTTP 状态码')}
|
||||
/>
|
||||
)}
|
||||
{mode === 'view' ? (
|
||||
item.content ? (
|
||||
<span className="mr-btnbase py-[5px] px-btnbase text-DEFAULT bg-[#f7f8fa] rounded border-[1px] border-solid border-DEFAULT">
|
||||
Content-Type: {item.httpContentType || 'text/html;charset=UTF-8'}
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
) : (
|
||||
<AutoComplete
|
||||
options={CONTENT_TYPE_TYPE.map((type) => ({ label: type, value: type }))}
|
||||
style={{ width: 200 }}
|
||||
value={item.httpContentType || 'text/html;charset=UTF-8'}
|
||||
onSelect={(value) => updateResultList(item.id, 'httpContentType', value)}
|
||||
placeholder={$t('默认 text/html;charset=UTF-8')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{mode === 'view' ? (
|
||||
<>
|
||||
{item.content ? (
|
||||
<pre className="border-[1px] border-solid border-BORDER p-[6px] rounded w-auto min-h-[130px] max-h-[500px] overflow-auto mt-[0px]">
|
||||
{item.content}
|
||||
</pre>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={$t('暂未填写示例')} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Codebox
|
||||
value={item.content}
|
||||
language="json"
|
||||
width="100%"
|
||||
height={'250px'}
|
||||
onChange={(value) => updateResultList(item.id, 'content', value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{mode === 'view' ?
|
||||
<>
|
||||
{ item.content ?
|
||||
<pre className="border-[1px] border-solid border-BORDER p-[6px] rounded w-auto min-h-[130px] max-h-[500px] overflow-auto mt-[0px]">{item.content}</pre>
|
||||
:
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={$t("暂未填写示例")}/>
|
||||
}</>
|
||||
: <>
|
||||
<Codebox value={item.content} language='json' width="100%" height={'250px'} onChange={(value)=>updateResultList(item.id,'content',value)}/>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
))}
|
||||
</Tabs.TabPane>
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Collapse>
|
||||
);
|
||||
</Box>
|
||||
</Collapse>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,259 +1,297 @@
|
||||
import { Collapse } from './api/Collapse'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { Select, Input, Space } from 'antd'
|
||||
import { Box, Stack, ThemeProvider, createTheme } from '@mui/material'
|
||||
import { ApiResponseEditor, ApiResponseEditorApi } from './api/ApiManager/components/ApiResponseEditor'
|
||||
import { ApiRequestEditor, ApiRequestEditorApi } from './api/ApiManager/components/ApiRequestEditor'
|
||||
import { ResponseExampleCompo, ResponseExampleCompoEditorApi } from '@common/components/apispace/response-example'
|
||||
import { ResultListType } from '@common/const/api-detail'
|
||||
import { SystemApiDetail, SystemInsideApiProxyHandle } from '@core/const/system/type'
|
||||
import SystemInsideApiProxy from '@core/pages/system/api/SystemInsideApiProxy'
|
||||
import ApiMatch from './api/ApiPreview/components/ApiMatch'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PLACEHOLDER } from '@common/const/const'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
import {Collapse} from "./api/Collapse";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||
import {Select,Input,Space} from "antd";
|
||||
import { Box, Stack, ThemeProvider, createTheme } from "@mui/material"
|
||||
import {ApiResponseEditor, ApiResponseEditorApi} from "./api/ApiManager/components/ApiResponseEditor";
|
||||
import {ApiRequestEditor, ApiRequestEditorApi} from "./api/ApiManager/components/ApiRequestEditor";
|
||||
import {ResponseExampleCompo, ResponseExampleCompoEditorApi} from "@common/components/apispace/response-example";
|
||||
import {ResultListType} from "@common/const/api-detail";
|
||||
import { SystemApiDetail, SystemInsideApiProxyHandle } from "@core/const/system/type";
|
||||
import SystemInsideApiProxy from "@core/pages/system/api/SystemInsideApiProxy";
|
||||
import ApiMatch from "./api/ApiPreview/components/ApiMatch";
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import { PLACEHOLDER } from "@common/const/const";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
const PROTOCOL_LIST = ['HTTP','HTTPS']
|
||||
const HTTP_METHOD_LIST = ['POST','GET','PUT', 'DELETE','HEAD','OPTIONS','PATCH']
|
||||
export interface ApiEditApi{
|
||||
getData:()=>(Promise<{apiInfo:Partial<SystemApiDetail>}|string|boolean> | undefined)
|
||||
const PROTOCOL_LIST = ['HTTP', 'HTTPS']
|
||||
const HTTP_METHOD_LIST = ['POST', 'GET', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH']
|
||||
export interface ApiEditApi {
|
||||
getData: () => Promise<{ apiInfo: Partial<SystemApiDetail> } | string | boolean> | undefined
|
||||
}
|
||||
|
||||
interface DescriptionHandle{
|
||||
getData:()=>string
|
||||
interface DescriptionHandle {
|
||||
getData: () => string
|
||||
}
|
||||
interface ApiNameProps{
|
||||
apiInfo:SystemApiDetail
|
||||
interface ApiNameProps {
|
||||
apiInfo: SystemApiDetail
|
||||
}
|
||||
|
||||
interface ApiNameHandle{
|
||||
getData:()=>string
|
||||
interface ApiNameHandle {
|
||||
getData: () => string
|
||||
}
|
||||
|
||||
export const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#3D46F2', // 自定义主色调
|
||||
},
|
||||
text: {
|
||||
primary: '#333', // 主要文字颜色
|
||||
secondary: '#333', // 次要文字颜色
|
||||
},
|
||||
// 添加其他颜色配置,如错误色、背景色等
|
||||
error: {
|
||||
main: '#d32f2f',
|
||||
},
|
||||
background: {
|
||||
paper: '#fff',
|
||||
default: '#f7f8fa',
|
||||
},
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#3D46F2' // 自定义主色调
|
||||
},
|
||||
transitions:{
|
||||
create:()=>'none'
|
||||
text: {
|
||||
primary: '#333', // 主要文字颜色
|
||||
secondary: '#333' // 次要文字颜色
|
||||
},
|
||||
components:{
|
||||
MuiInput: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&::placeholder': {
|
||||
color: '#BBB', // 设置 placeholder 的颜色
|
||||
},
|
||||
'&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': {
|
||||
borderColor: '#3D46F2', // 设置 hover 时的边框颜色
|
||||
borderWidth: '1px', // 设置边框粗细
|
||||
},
|
||||
'&.Mui-focused': {
|
||||
borderColor: '#3D46F2', // 设置选中时的边框颜色
|
||||
borderWidth: '1px', // 设置边框粗细
|
||||
},
|
||||
},
|
||||
// 添加其他颜色配置,如错误色、背景色等
|
||||
error: {
|
||||
main: '#d32f2f'
|
||||
},
|
||||
background: {
|
||||
paper: '#fff',
|
||||
default: '#f7f8fa'
|
||||
}
|
||||
},
|
||||
transitions: {
|
||||
create: () => 'none'
|
||||
},
|
||||
components: {
|
||||
MuiInput: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&::placeholder': {
|
||||
color: '#BBB' // 设置 placeholder 的颜色
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&::placeholder': {
|
||||
color: '#BBB', // 设置 placeholder 的颜色
|
||||
},
|
||||
'&:hover .MuiOutlinedInput-notchedOutline':{
|
||||
borderColor: '#3D46F2', // 设置选中时的边框颜色
|
||||
borderWidth: '1px', // 设置边框粗细
|
||||
}
|
||||
'&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': {
|
||||
borderColor: '#3D46F2', // 设置 hover 时的边框颜色
|
||||
borderWidth: '1px' // 设置边框粗细
|
||||
},
|
||||
'&.Mui-focused': {
|
||||
borderColor: '#3D46F2', // 设置选中时的边框颜色
|
||||
borderWidth: '1px' // 设置边框粗细
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTextField: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&::placeholder': {
|
||||
color: '#BBB' // 设置 placeholder 的颜色
|
||||
},
|
||||
'&:hover .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#3D46F2', // 设置选中时的边框颜色
|
||||
borderWidth: '1px' // 设置边框粗细
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiCheckbox: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent' // 设置 hover 时的背景色为透明
|
||||
},
|
||||
'&:hover:before': {
|
||||
backgroundColor: 'transparent' // 确保不透明度也为透明
|
||||
},
|
||||
transition: 'none' // 取消过渡效果
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&': {
|
||||
marginLeft: '0px',
|
||||
padding: '3px 12px',
|
||||
borderRadius: '4px'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiSelect: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px'
|
||||
},
|
||||
'&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error) .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px'
|
||||
},
|
||||
'&.Mui-focused': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px'
|
||||
},
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiMenu: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'.MuiMenuItem-root:hover': {
|
||||
backgroundColor: '#EBEEF2'
|
||||
},
|
||||
'.MuiMenuItem-root.Mui-selected': {
|
||||
backgroundColor: '#EBEEF2'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputLabel: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: '#BBB' // 设置 label 的颜色为灰色
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default function ApiEdit({
|
||||
apiInfo,
|
||||
editorRef,
|
||||
loaded,
|
||||
serviceId,
|
||||
teamId
|
||||
}: {
|
||||
apiInfo: SystemApiDetail
|
||||
editorRef?: React.RefObject<ApiEditApi>
|
||||
loaded: boolean
|
||||
serviceId: string
|
||||
teamId: string
|
||||
}) {
|
||||
const requestRef = useRef<ApiRequestEditorApi>(null)
|
||||
const responseRef = useRef<ApiResponseEditorApi>(null)
|
||||
const resultListRef = useRef<ResponseExampleCompoEditorApi>(null)
|
||||
const protocolOptionList = PROTOCOL_LIST.map((x) => ({ label: x, value: x }))
|
||||
const methodOptionList = HTTP_METHOD_LIST.map((x) => ({ label: x, value: x }))
|
||||
const [apiName, setApiName] = useState<string>('')
|
||||
const [resultList, setResultList] = useState<ResultListType[]>([])
|
||||
const proxyRef = useRef<SystemInsideApiProxyHandle>(null)
|
||||
const descriptionRef = useRef<DescriptionHandle>(null)
|
||||
const apiNameRef = useRef<ApiNameHandle>(null)
|
||||
|
||||
useImperativeHandle(editorRef, () => ({
|
||||
getData: () => {
|
||||
return proxyRef.current
|
||||
?.validate()
|
||||
.then((res) => {
|
||||
const name = apiNameRef.current?.getData()
|
||||
if (!name) return Promise.reject($t('请填写接口名称'))
|
||||
const newData: { apiInfo: Partial<SystemApiDetail> } = {
|
||||
apiInfo: {
|
||||
info: {
|
||||
name,
|
||||
description: descriptionRef.current?.getData()
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiCheckbox: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent', // 设置 hover 时的背景色为透明
|
||||
},
|
||||
'&:hover:before': {
|
||||
backgroundColor: 'transparent', // 确保不透明度也为透明
|
||||
},
|
||||
transition: 'none', // 取消过渡效果
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiButton:{
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&':{
|
||||
marginLeft:'0px',
|
||||
padding:'3px 12px',
|
||||
borderRadius:'4px'
|
||||
}
|
||||
proxy: res,
|
||||
doc: {
|
||||
...apiInfo?.doc,
|
||||
requestParams: requestRef.current!.getData()!,
|
||||
responseList: responseRef.current!.getData()!,
|
||||
resultList: resultListRef.current!.getData()!
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiSelect: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px',
|
||||
},
|
||||
'&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error) .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px',
|
||||
},
|
||||
'&.Mui-focused': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px',
|
||||
},
|
||||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||||
borderColor: '#3D46F2',
|
||||
borderWidth: '1px',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiMenu:{
|
||||
styleOverrides: {
|
||||
root:{
|
||||
'.MuiMenuItem-root:hover':{
|
||||
backgroundColor: '#EBEEF2',
|
||||
},
|
||||
'.MuiMenuItem-root.Mui-selected':{
|
||||
backgroundColor: '#EBEEF2',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiInputLabel: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: '#BBB', // 设置 label 的颜色为灰色
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return Promise.resolve(newData)
|
||||
})
|
||||
.catch((errInfo) => Promise.reject(errInfo))
|
||||
}
|
||||
});
|
||||
|
||||
export default function ApiEdit({apiInfo,editorRef,loaded,serviceId, teamId}:{apiInfo:SystemApiDetail,editorRef?:React.RefObject<ApiEditApi>,loaded:boolean,serviceId:string, teamId:string}){
|
||||
const requestRef = useRef<ApiRequestEditorApi>(null)
|
||||
const responseRef = useRef<ApiResponseEditorApi>(null)
|
||||
const resultListRef = useRef<ResponseExampleCompoEditorApi>(null)
|
||||
const protocolOptionList = PROTOCOL_LIST.map((x)=>({label:x,value:x}))
|
||||
const methodOptionList = HTTP_METHOD_LIST.map((x)=>({label:x,value:x}))
|
||||
const [apiName,setApiName]=useState<string>('')
|
||||
const [resultList,setResultList] = useState<ResultListType[]>([])
|
||||
const proxyRef = useRef<SystemInsideApiProxyHandle>(null)
|
||||
const descriptionRef = useRef<DescriptionHandle>(null)
|
||||
const apiNameRef = useRef<ApiNameHandle>(null)
|
||||
|
||||
useImperativeHandle(editorRef, () => ({
|
||||
getData: () => {
|
||||
return proxyRef.current?.validate().then((res)=>{
|
||||
const name = apiNameRef.current?.getData()
|
||||
if(!name) return Promise.reject($t('请填写接口名称'))
|
||||
const newData :{apiInfo:Partial<SystemApiDetail>}= {
|
||||
apiInfo:{
|
||||
info:{
|
||||
name,
|
||||
description:descriptionRef.current?.getData(),
|
||||
},
|
||||
proxy:res,
|
||||
doc:{
|
||||
...apiInfo?.doc,
|
||||
requestParams:requestRef.current!.getData()!,
|
||||
responseList:responseRef.current!.getData()!,
|
||||
resultList:resultListRef.current!.getData()!
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve(newData)
|
||||
}).catch((errInfo)=>Promise.reject(errInfo))
|
||||
}
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if (!apiInfo || Object.keys(apiInfo).length === 0) return
|
||||
setApiName(apiInfo.name!)
|
||||
setResultList(apiInfo?.doc?.resultList || [])
|
||||
}, [apiInfo])
|
||||
|
||||
const Description = forwardRef<DescriptionHandle, { initDescription: string | undefined }>((props, ref) => {
|
||||
const { initDescription } = props
|
||||
const [description, setDescription] = useState<string>(initDescription || '')
|
||||
useImperativeHandle(ref, () => ({
|
||||
getData: () => description
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
if(!apiInfo || Object.keys(apiInfo).length === 0) return
|
||||
setApiName(apiInfo.name!)
|
||||
setResultList(apiInfo?.doc?.resultList || [])
|
||||
}, [apiInfo]);
|
||||
|
||||
const Description = forwardRef<DescriptionHandle,{initDescription:string|undefined}>((props,ref)=>{
|
||||
|
||||
const { initDescription } = props
|
||||
const [description, setDescription] = useState<string>(initDescription||'')
|
||||
useImperativeHandle(ref, ()=>({
|
||||
getData:()=>description
|
||||
}))
|
||||
return (
|
||||
<Input.TextArea className="w-full border-none" value={description} onChange={(e)=>setDescription(e.target.value)} placeholder={$t(PLACEHOLDER.input)}/>
|
||||
)
|
||||
})
|
||||
|
||||
const ApiName = forwardRef<ApiNameHandle,ApiNameProps>((props,ref)=>{
|
||||
const {apiInfo} = props
|
||||
const [apiName, setApiName] = useState<string>(apiInfo?.name || '')
|
||||
useImperativeHandle(ref, ()=>({
|
||||
getData:()=>apiName
|
||||
}))
|
||||
return (
|
||||
<>
|
||||
<Space.Compact className="w-full mb-btnybase">
|
||||
<Select className="w-[15%] min-w-[100px]" value={apiInfo?.protocol || 'HTTP'} disabled={true} options={protocolOptionList} />
|
||||
<Select className="w-[15%] min-w-[100px]" value={apiInfo?.method} disabled={true} options={methodOptionList} />
|
||||
<Input className="w-[70%]" value={apiInfo?.path} disabled={true} />
|
||||
</Space.Compact>
|
||||
<Input value={apiName} onChange={(e)=>setApiName(e.target.value) } status={apiName ? '' : 'error'}/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
return(
|
||||
<>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack direction="column" spacing={3}>
|
||||
<ApiName apiInfo={apiInfo} ref={apiNameRef}/>
|
||||
<Collapse key="description" title={$t('详细说明')}>
|
||||
<Description initDescription={apiInfo?.description} ref={descriptionRef}/>
|
||||
</Collapse>
|
||||
{
|
||||
apiInfo?.match && apiInfo.match?.length > 0 &&
|
||||
<ApiMatch title={$t('高级匹配')} rows={apiInfo?.match.map((x)=>{x.id = uuidv4();return x})} />
|
||||
}
|
||||
|
||||
<Collapse title={$t('转发配置')} key="proxy" >
|
||||
<SystemInsideApiProxy className="m-[12px] px-[12px]" initProxyValue={apiInfo?.proxy} serviceId={serviceId!} ref={proxyRef} />
|
||||
</Collapse>
|
||||
|
||||
<Collapse title={$t('请求参数')} key="request" >
|
||||
<ApiRequestEditor editorRef={requestRef} apiInfo={apiInfo?.doc} loaded={loaded} />
|
||||
</Collapse>
|
||||
<Collapse title={$t('返回值')} key="response">
|
||||
<ApiResponseEditor editorRef={responseRef} apiInfo={apiInfo?.doc} loaded={loaded}/>
|
||||
</Collapse>
|
||||
<ResponseExampleCompo editorRef={resultListRef} mode='edit' title={$t('返回示例')} detail={resultList}/>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
return (
|
||||
<Input.TextArea
|
||||
className="w-full border-none"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
const ApiName = forwardRef<ApiNameHandle, ApiNameProps>((props, ref) => {
|
||||
const { apiInfo } = props
|
||||
const [apiName, setApiName] = useState<string>(apiInfo?.name || '')
|
||||
useImperativeHandle(ref, () => ({
|
||||
getData: () => apiName
|
||||
}))
|
||||
return (
|
||||
<>
|
||||
<Space.Compact className="w-full mb-btnybase">
|
||||
<Select
|
||||
className="w-[15%] min-w-[100px]"
|
||||
value={apiInfo?.protocol || 'HTTP'}
|
||||
disabled={true}
|
||||
options={protocolOptionList}
|
||||
/>
|
||||
<Select
|
||||
className="w-[15%] min-w-[100px]"
|
||||
value={apiInfo?.method}
|
||||
disabled={true}
|
||||
options={methodOptionList}
|
||||
/>
|
||||
<Input className="w-[70%]" value={apiInfo?.path} disabled={true} />
|
||||
</Space.Compact>
|
||||
<Input value={apiName} onChange={(e) => setApiName(e.target.value)} status={apiName ? '' : 'error'} />
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Box>
|
||||
<Box>
|
||||
<Stack direction="column" spacing={3}>
|
||||
<ApiName apiInfo={apiInfo} ref={apiNameRef} />
|
||||
<Collapse key="description" title={$t('详细说明')}>
|
||||
<Description initDescription={apiInfo?.description} ref={descriptionRef} />
|
||||
</Collapse>
|
||||
{apiInfo?.match && apiInfo.match?.length > 0 && (
|
||||
<ApiMatch
|
||||
title={$t('高级匹配')}
|
||||
rows={apiInfo?.match.map((x) => {
|
||||
x.id = uuidv4()
|
||||
return x
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Collapse title={$t('转发配置')} key="proxy">
|
||||
<SystemInsideApiProxy
|
||||
className="m-[12px] px-[12px]"
|
||||
initProxyValue={apiInfo?.proxy}
|
||||
serviceId={serviceId!}
|
||||
ref={proxyRef}
|
||||
/>
|
||||
</Collapse>
|
||||
|
||||
<Collapse title={$t('请求参数')} key="request">
|
||||
<ApiRequestEditor editorRef={requestRef} apiInfo={apiInfo?.doc} loaded={loaded} />
|
||||
</Collapse>
|
||||
<Collapse title={$t('返回值')} key="response">
|
||||
<ApiResponseEditor editorRef={responseRef} apiInfo={apiInfo?.doc} loaded={loaded} />
|
||||
</Collapse>
|
||||
<ResponseExampleCompo editorRef={resultListRef} mode="edit" title={$t('返回示例')} detail={resultList} />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,159 +1,169 @@
|
||||
import {useCallback, useEffect, useState} from "react";
|
||||
import Search from "antd/es/input/Search";
|
||||
import {Button, Space, Tooltip} from "antd";
|
||||
import CodeSnippetCompo from "@common/components/apispace/code-snippet";
|
||||
import {ApiDetail} from "@common/const/api-detail";
|
||||
import {flattenTree} from "@common/utils/postcat.tsx";
|
||||
import MessageBodyComponent, {RenderMessageBody} from "./api/ApiPreview/components/MessageBody";
|
||||
import HeaderFields from "./api/ApiPreview/components/HeaderFields";
|
||||
import {ResponseExampleCompo} from "@common/components/apispace/response-example";
|
||||
import {MoreSetting} from "./api/MoreSetting";
|
||||
import {
|
||||
useMoreSettingHiddenConfig
|
||||
} from "./api/ApiManager/components/MessageDataGrid/hooks/useMoreSettingHiddenConfig.ts";
|
||||
import {MessageType} from "./api/ApiManager/components/MessageDataGrid";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { ThemeProvider } from "@mui/material";
|
||||
import { theme } from "./ApiEdit.tsx";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Search from 'antd/es/input/Search'
|
||||
import { Button, Space, Tooltip } from 'antd'
|
||||
import CodeSnippetCompo from '@common/components/apispace/code-snippet'
|
||||
import { ApiDetail } from '@common/const/api-detail'
|
||||
import { flattenTree } from '@common/utils/postcat.tsx'
|
||||
import MessageBodyComponent, { RenderMessageBody } from './api/ApiPreview/components/MessageBody'
|
||||
import HeaderFields from './api/ApiPreview/components/HeaderFields'
|
||||
import { ResponseExampleCompo } from '@common/components/apispace/response-example'
|
||||
import { MoreSetting } from './api/MoreSetting'
|
||||
import { useMoreSettingHiddenConfig } from './api/ApiManager/components/MessageDataGrid/hooks/useMoreSettingHiddenConfig.ts'
|
||||
import { MessageType } from './api/ApiManager/components/MessageDataGrid'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { ThemeProvider } from '@mui/material'
|
||||
import { theme } from './ApiEdit.tsx'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
|
||||
export const SearchBtn = ({entity}:{entity:unknown})=>{
|
||||
return (
|
||||
<Tooltip >
|
||||
<span className="text-disabled">{$t('测试 API')}</span>
|
||||
</Tooltip>
|
||||
)
|
||||
export const SearchBtn = ({ entity }: { entity: unknown }) => {
|
||||
return (
|
||||
<Tooltip>
|
||||
<span className="text-disabled">{$t('测试 API')}</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ApiPreview(props:{testClick?:()=>void, entity:ApiDetail}){
|
||||
const {testClick,entity} = props
|
||||
const {requestParams,responseList,resultList} = entity
|
||||
const [requestBodyList, setRequestBodyList] = useState<RenderMessageBody[]>([])
|
||||
const [responseBodyList, setResponseBodyList] = useState<RenderMessageBody[]>([])
|
||||
const [currentMoreSettingParam, setCurrentMoreSettingParam] = useState<RenderMessageBody | null>(null)
|
||||
export default function ApiPreview(props: { testClick?: () => void; entity: ApiDetail }) {
|
||||
const { testClick, entity } = props
|
||||
const { requestParams, responseList, resultList } = entity
|
||||
const [requestBodyList, setRequestBodyList] = useState<RenderMessageBody[]>([])
|
||||
const [responseBodyList, setResponseBodyList] = useState<RenderMessageBody[]>([])
|
||||
const [currentMoreSettingParam, setCurrentMoreSettingParam] = useState<RenderMessageBody | null>(null)
|
||||
|
||||
// const responseData = responseList?.[0]
|
||||
// const responseParams = responseData?.responseParams?.headerParams
|
||||
// const responseData = responseList?.[0]
|
||||
// const responseParams = responseData?.responseParams?.headerParams
|
||||
|
||||
useEffect(() => {
|
||||
// setTimeout(()=>{
|
||||
// const element = document.querySelectorAll('.MuiDataGrid-main');
|
||||
// if(element?.length > 0){
|
||||
// for(const x of element){
|
||||
// x.childNodes[x.childNodes.length - 1 ].textContent === 'MUI X Missing license key' ? x.childNodes[x.childNodes.length - 1 ].textContent = '' :null
|
||||
// }
|
||||
// }
|
||||
// },500)
|
||||
useEffect(() => {
|
||||
// setTimeout(()=>{
|
||||
// const element = document.querySelectorAll('.MuiDataGrid-main');
|
||||
// if(element?.length > 0){
|
||||
// for(const x of element){
|
||||
// x.childNodes[x.childNodes.length - 1 ].textContent === 'MUI X Missing license key' ? x.childNodes[x.childNodes.length - 1 ].textContent = '' :null
|
||||
// }
|
||||
// }
|
||||
// },500)
|
||||
|
||||
setRequestBodyList(
|
||||
flattenTree(requestParams?.bodyParams || [], 'childList', 'name') as unknown as RenderMessageBody[]
|
||||
)
|
||||
setResponseBodyList(
|
||||
flattenTree(
|
||||
responseList?.[0]?.responseParams?.bodyParams || [],
|
||||
'childList',
|
||||
'name'
|
||||
) as unknown as RenderMessageBody[]
|
||||
)
|
||||
}, [requestParams, responseList, resultList])
|
||||
|
||||
setRequestBodyList(
|
||||
flattenTree(requestParams?.bodyParams || [], 'childList', 'name') as unknown as RenderMessageBody[]
|
||||
)
|
||||
setResponseBodyList(
|
||||
flattenTree(
|
||||
responseList?.[0]?.responseParams?.bodyParams || [],
|
||||
'childList',
|
||||
'name'
|
||||
) as unknown as RenderMessageBody[]
|
||||
)
|
||||
}, [requestParams,responseList,resultList]);
|
||||
const handleCloseMoreSetting = useCallback(() => {
|
||||
setCurrentMoreSettingParam(null)
|
||||
}, [])
|
||||
|
||||
const moreSettingHiddenConfig = useMoreSettingHiddenConfig({
|
||||
param: currentMoreSettingParam as unknown as RenderMessageBody,
|
||||
// TODO:
|
||||
messageType: 'Header' as MessageType,
|
||||
readOnly: true
|
||||
})
|
||||
|
||||
const handleCloseMoreSetting = useCallback(() => {
|
||||
setCurrentMoreSettingParam(null)
|
||||
}, [])
|
||||
const handleTest = () => {
|
||||
// testClick && testClick()
|
||||
}
|
||||
|
||||
|
||||
const moreSettingHiddenConfig = useMoreSettingHiddenConfig({
|
||||
param: currentMoreSettingParam as unknown as RenderMessageBody,
|
||||
// TODO:
|
||||
messageType: 'Header' as MessageType,
|
||||
readOnly: true
|
||||
})
|
||||
|
||||
const handleTest = () => {
|
||||
// testClick && testClick()
|
||||
};
|
||||
|
||||
return (<>
|
||||
<ThemeProvider theme={theme}>
|
||||
|
||||
{testClick &&
|
||||
<Space direction="vertical" className="mb-btnybase w-full mt-btnybase">
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={theme}>
|
||||
{testClick && (
|
||||
<Space direction="vertical" className="mb-btnybase w-full mt-btnybase">
|
||||
<Search
|
||||
readOnly
|
||||
addonBefore={entity?.method}
|
||||
value={entity?.uri}
|
||||
// enterButton={<SearchBtn entity={entity}/>}
|
||||
onSearch={handleTest}
|
||||
readOnly
|
||||
addonBefore={entity?.method}
|
||||
value={entity?.uri}
|
||||
// enterButton={<SearchBtn entity={entity}/>}
|
||||
onSearch={handleTest}
|
||||
/>
|
||||
</Space>}
|
||||
</Space>
|
||||
)}
|
||||
|
||||
{
|
||||
requestParams?.headerParams?.length > 0 &&
|
||||
<HeaderFields title={$t('请求 Header')} rows={requestParams?.headerParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam} />
|
||||
}
|
||||
{requestParams?.headerParams?.length > 0 && (
|
||||
<HeaderFields
|
||||
title={$t('请求 Header')}
|
||||
rows={requestParams?.headerParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
)}
|
||||
|
||||
{requestBodyList?.length > 0 &&
|
||||
<MessageBodyComponent
|
||||
title={$t("请求 Body")}
|
||||
rows={requestBodyList}
|
||||
contentType={requestParams?.bodyParams[0]?.contentType}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
}
|
||||
{requestBodyList?.length > 0 && (
|
||||
<MessageBodyComponent
|
||||
title={$t('请求 Body')}
|
||||
rows={requestBodyList}
|
||||
contentType={requestParams?.bodyParams[0]?.contentType}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
)}
|
||||
|
||||
{
|
||||
requestParams?.queryParams?.length > 0 &&
|
||||
<HeaderFields title={$t('Query 参数')} rows={requestParams?.queryParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam} />
|
||||
}
|
||||
{requestParams?.queryParams?.length > 0 && (
|
||||
<HeaderFields
|
||||
title={$t('Query 参数')}
|
||||
rows={requestParams?.queryParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
)}
|
||||
|
||||
{
|
||||
requestParams?.restParams?.length > 0 &&
|
||||
<HeaderFields title={$t('Rest 参数')} rows={requestParams?.restParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}/>
|
||||
}
|
||||
{requestParams?.restParams?.length > 0 && (
|
||||
<HeaderFields
|
||||
title={$t('Rest 参数')}
|
||||
rows={requestParams?.restParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/*<h3 className="text-lg mb-btnybase font-normal flex items-center">请求示例代码</h3>*/}
|
||||
<CodeSnippetCompo
|
||||
title={$t('请求示例代码')}
|
||||
api={entity}
|
||||
extraContent={ testClick ? <div className="ml-5">
|
||||
<Tooltip >
|
||||
<WithPermission access="" >
|
||||
<Button type='primary' onClick={handleTest} size='small' className='w-[114px]'>{$t('测试 API')}</Button>
|
||||
</WithPermission>
|
||||
title={$t('请求示例代码')}
|
||||
api={entity}
|
||||
extraContent={
|
||||
testClick ? (
|
||||
<div className="ml-5">
|
||||
<Tooltip>
|
||||
<WithPermission access="">
|
||||
<Button type="primary" onClick={handleTest} size="small" className="w-[114px]">
|
||||
{$t('测试 API')}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
</Tooltip>
|
||||
</div> : undefined }
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
{resultList?.length > 0 && <ResponseExampleCompo title={$t('响应示例')} detail={resultList}/>}
|
||||
{resultList?.length > 0 && <ResponseExampleCompo title={$t('响应示例')} detail={resultList} />}
|
||||
|
||||
{
|
||||
responseList?.[0]?.responseParams?.headerParams?.length > 0 &&
|
||||
<HeaderFields title={$t('响应 Header')} rows={ responseList?.[0]?.responseParams?.headerParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam} />
|
||||
}
|
||||
{responseList?.[0]?.responseParams?.headerParams?.length > 0 && (
|
||||
<HeaderFields
|
||||
title={$t('响应 Header')}
|
||||
rows={responseList?.[0]?.responseParams?.headerParams}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
)}
|
||||
|
||||
{responseBodyList?.length > 0 &&
|
||||
<MessageBodyComponent
|
||||
title={$t("响应 Body")}
|
||||
rows={responseBodyList}
|
||||
contentType={responseList?.[0]?.contentType}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
}
|
||||
{responseBodyList?.length > 0 && (
|
||||
<MessageBodyComponent
|
||||
title={$t('响应 Body')}
|
||||
rows={responseBodyList}
|
||||
contentType={responseList?.[0]?.contentType}
|
||||
onMoreSettingChange={setCurrentMoreSettingParam}
|
||||
/>
|
||||
)}
|
||||
|
||||
<MoreSetting
|
||||
readOnly={true}
|
||||
open={Boolean(currentMoreSettingParam)}
|
||||
onClose={handleCloseMoreSetting}
|
||||
hiddenConfig={moreSettingHiddenConfig}
|
||||
param={currentMoreSettingParam as unknown as RenderMessageBody}
|
||||
readOnly={true}
|
||||
open={Boolean(currentMoreSettingParam)}
|
||||
onClose={handleCloseMoreSetting}
|
||||
hiddenConfig={moreSettingHiddenConfig}
|
||||
param={currentMoreSettingParam as unknown as RenderMessageBody}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</>)
|
||||
</ThemeProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,287 +1,289 @@
|
||||
import { Box, Button, LinearProgress, Stack } from '@mui/material'
|
||||
import { Box, LinearProgress, Stack } from '@mui/material'
|
||||
import { Allotment } from 'allotment'
|
||||
import { memo, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { useAutoAnimate } from '@formkit/auto-animate/react'
|
||||
import {HTTPMethod} from "./api/RequestMethod";
|
||||
import { HTTPMethod } from './api/RequestMethod'
|
||||
import {
|
||||
ApiBodyType,
|
||||
ApiDetail,
|
||||
BodyParamsType,
|
||||
HeaderParamsType,
|
||||
QueryParamsType, ResponseList, RestParamsType,
|
||||
TestApiBodyType
|
||||
} from "@common/const/api-detail";
|
||||
import {extractBraceContent, mapContentTypeToApiBodyType, syncUrlAndQuery} from "@common/utils/postcat.tsx";
|
||||
import {generateRow} from "./api/ApiManager/components/MessageDataGrid/constants.ts";
|
||||
import {RawParams} from "./api/ApiManager/components/ApiMessageBody";
|
||||
import {ParseCurlResult} from "@common/utils/curl.ts";
|
||||
import {ApiRequestTester, ApiRequestTesterApi} from "./api/ApiTest/components/ApiRequestTester";
|
||||
import {TestResponse} from "@common/hooks/useTest.ts";
|
||||
import {getDefaultApiInfo} from "./api/ApiManager/constants.ts";
|
||||
import {UriInput} from "./api/ApiManager/components/UriInput";
|
||||
import {TestControl} from "./api/ApiTest/components/TestControl";
|
||||
ApiBodyType,
|
||||
ApiDetail,
|
||||
BodyParamsType,
|
||||
HeaderParamsType,
|
||||
QueryParamsType,
|
||||
ResponseList,
|
||||
RestParamsType,
|
||||
TestApiBodyType
|
||||
} from '@common/const/api-detail'
|
||||
import { extractBraceContent, mapContentTypeToApiBodyType, syncUrlAndQuery } from '@common/utils/postcat.tsx'
|
||||
import { generateRow } from './api/ApiManager/components/MessageDataGrid/constants.ts'
|
||||
import { RawParams } from './api/ApiManager/components/ApiMessageBody'
|
||||
import { ParseCurlResult } from '@common/utils/curl.ts'
|
||||
import { ApiRequestTester, ApiRequestTesterApi } from './api/ApiTest/components/ApiRequestTester'
|
||||
import { TestResponse } from '@common/hooks/useTest.ts'
|
||||
import { getDefaultApiInfo } from './api/ApiManager/constants.ts'
|
||||
import { UriInput } from './api/ApiManager/components/UriInput'
|
||||
import { TestControl } from './api/ApiTest/components/TestControl'
|
||||
const Tester = memo(ApiRequestTester)
|
||||
import 'allotment/dist/style.css'
|
||||
import {ApiResponse} from "./api/ApiTest/components/ApiResponse";
|
||||
import { ApiResponse } from './api/ApiTest/components/ApiResponse'
|
||||
|
||||
type SafeAny = unknown
|
||||
export interface ApiTestApiRef {
|
||||
getTestMeta: () => SafeAny
|
||||
getTestMeta: () => SafeAny
|
||||
}
|
||||
|
||||
export default function ApiTest({ apiRef, apiInfo,loaded = true}: { apiRef?: React.RefObject<ApiTestApiRef> ,apiInfo:ApiDetail,loaded?:boolean}) {
|
||||
const [uri, setUri] = useState<string>('')
|
||||
const [httpMethod, setHttpMethod] = useState<HTTPMethod>(HTTPMethod.POST)
|
||||
const [testResponse, setTestResponse] = useState<TestResponse | null>(null)
|
||||
// const { apiInfo, loaded } = useContext<Partial<ApiTabContextProps>>(ApiTabContext)
|
||||
const testerApiRef = useRef<ApiRequestTesterApi>(null)
|
||||
const [parent] = useAutoAnimate()
|
||||
// const testApiInfo:ApiDetail = apiInfo
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
const [cancel,setCancel] = useState<boolean>()
|
||||
export default function ApiTest({
|
||||
apiRef,
|
||||
apiInfo,
|
||||
loaded = true
|
||||
}: {
|
||||
apiRef?: React.RefObject<ApiTestApiRef>
|
||||
apiInfo: ApiDetail
|
||||
loaded?: boolean
|
||||
}) {
|
||||
const [uri, setUri] = useState<string>('')
|
||||
const [httpMethod, setHttpMethod] = useState<HTTPMethod>(HTTPMethod.POST)
|
||||
const [testResponse, setTestResponse] = useState<TestResponse | null>(null)
|
||||
// const { apiInfo, loaded } = useContext<Partial<ApiTabContextProps>>(ApiTabContext)
|
||||
const testerApiRef = useRef<ApiRequestTesterApi>(null)
|
||||
const [parent] = useAutoAnimate()
|
||||
// const testApiInfo:ApiDetail = apiInfo
|
||||
const [isLoading, setIsLoading] = useState<boolean>()
|
||||
const [cancel, setCancel] = useState<boolean>()
|
||||
|
||||
// useEffect(() => {
|
||||
// if (testApiInfo) {
|
||||
// const data: SafeAny = testApiInfo
|
||||
// const responseResult = {
|
||||
// report: {
|
||||
// general: {
|
||||
// downloadRate: data.downloadRate,
|
||||
// downloadSize: data.downloadSize,
|
||||
// redirectTimes: data.redirectTimes,
|
||||
// time: data.time,
|
||||
// timingSummary: data.timingSummary
|
||||
// },
|
||||
// request: {
|
||||
// headers: testApiInfo.requestParams.headerParams?.map((item) => ({
|
||||
// value: item.paramAttr.example,
|
||||
// key: item.name
|
||||
// })),
|
||||
// requestType: data.request.contentType,
|
||||
// body: testApiInfo.requestParams.bodyParams,
|
||||
// uri: testApiInfo.uri
|
||||
// },
|
||||
// response: {
|
||||
// headers: data.headers.map((item: SafeAny) => ({ key: item.name, value: item.value })),
|
||||
// body: data.body,
|
||||
// contentType: data.contentType,
|
||||
// httpCode: data.statusCode,
|
||||
// responseType: data.responseType
|
||||
// }
|
||||
// }
|
||||
// } as unknown as TestResponse
|
||||
// setTestResponse(responseResult)
|
||||
// setHttpMethod(testApiInfo.method)
|
||||
// testerApiRef.current?.updateHeaderDataGrid((testApiInfo.requestParams.headerParams as HeaderParamsType[]) || [])
|
||||
// const apiBodyType: TestApiBodyType = testApiInfo.requestParams.bodyParams[0]?.contentType as TestApiBodyType
|
||||
// const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded'
|
||||
// testerApiRef.current?.updateRequestBody({
|
||||
// apiBodyType,
|
||||
// contentType: contentType,
|
||||
// data:
|
||||
// apiBodyType === ApiBodyType.Raw
|
||||
// ? testApiInfo.requestParams.bodyParams?.[0]?.binaryRawData
|
||||
// : testApiInfo.requestParams.bodyParams
|
||||
// })
|
||||
// setTimeout(() => {
|
||||
// setUri(testApiInfo.uri)
|
||||
// testerApiRef.current?.updateQueryDataGrid([])
|
||||
// testerApiRef.current?.updateRestDataGrid([])
|
||||
// }, 0)
|
||||
// // updateTestApiInfo(null)
|
||||
// }
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// }, [testApiInfo])
|
||||
|
||||
useEffect(() => {
|
||||
if (apiInfo && loaded) {
|
||||
setUri(apiInfo.uri)
|
||||
setHttpMethod(apiInfo.method)
|
||||
testerApiRef.current?.updateHeaderDataGrid((apiInfo.requestParams.headerParams as HeaderParamsType[]) || [])
|
||||
const apiBodyType: TestApiBodyType = apiInfo.requestParams?.bodyParams[0]?.contentType as TestApiBodyType
|
||||
const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded'
|
||||
testerApiRef.current?.updateRequestBody({
|
||||
apiBodyType,
|
||||
contentType: contentType,
|
||||
data:
|
||||
apiBodyType === ApiBodyType.Raw
|
||||
? apiInfo.requestParams?.bodyParams?.[0]?.binaryRawData
|
||||
: apiInfo.requestParams?.bodyParams
|
||||
})
|
||||
}
|
||||
}, [apiInfo, loaded])
|
||||
|
||||
// useEffect(() => {
|
||||
// if (testApiInfo) {
|
||||
// const data: SafeAny = testApiInfo
|
||||
// const responseResult = {
|
||||
// report: {
|
||||
// general: {
|
||||
// downloadRate: data.downloadRate,
|
||||
// downloadSize: data.downloadSize,
|
||||
// redirectTimes: data.redirectTimes,
|
||||
// time: data.time,
|
||||
// timingSummary: data.timingSummary
|
||||
// },
|
||||
// request: {
|
||||
// headers: testApiInfo.requestParams.headerParams?.map((item) => ({
|
||||
// value: item.paramAttr.example,
|
||||
// key: item.name
|
||||
// })),
|
||||
// requestType: data.request.contentType,
|
||||
// body: testApiInfo.requestParams.bodyParams,
|
||||
// uri: testApiInfo.uri
|
||||
// },
|
||||
// response: {
|
||||
// headers: data.headers.map((item: SafeAny) => ({ key: item.name, value: item.value })),
|
||||
// body: data.body,
|
||||
// contentType: data.contentType,
|
||||
// httpCode: data.statusCode,
|
||||
// responseType: data.responseType
|
||||
// }
|
||||
// }
|
||||
// } as unknown as TestResponse
|
||||
// setTestResponse(responseResult)
|
||||
// setHttpMethod(testApiInfo.method)
|
||||
// testerApiRef.current?.updateHeaderDataGrid((testApiInfo.requestParams.headerParams as HeaderParamsType[]) || [])
|
||||
// const apiBodyType: TestApiBodyType = testApiInfo.requestParams.bodyParams[0]?.contentType as TestApiBodyType
|
||||
// const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded'
|
||||
// testerApiRef.current?.updateRequestBody({
|
||||
// apiBodyType,
|
||||
// contentType: contentType,
|
||||
// data:
|
||||
// apiBodyType === ApiBodyType.Raw
|
||||
// ? testApiInfo.requestParams.bodyParams?.[0]?.binaryRawData
|
||||
// : testApiInfo.requestParams.bodyParams
|
||||
// })
|
||||
// setTimeout(() => {
|
||||
// setUri(testApiInfo.uri)
|
||||
// testerApiRef.current?.updateQueryDataGrid([])
|
||||
// testerApiRef.current?.updateRestDataGrid([])
|
||||
// }, 0)
|
||||
// // updateTestApiInfo(null)
|
||||
useImperativeHandle(apiRef, () => ({
|
||||
getTestMeta: () => {
|
||||
const { rest, query, headers, body } = testerApiRef.current?.getEditMeta() || {}
|
||||
return {
|
||||
uri,
|
||||
restParams: rest || [],
|
||||
headersParams: (headers as HeaderParamsType[]) || [],
|
||||
bodyParams: (body?.data as BodyParamsType[]) || [],
|
||||
queryParams: query || [],
|
||||
method: httpMethod,
|
||||
requestType: body!.apiBodyType
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const handleTest = async () => {
|
||||
const { rest, headers, body } = testerApiRef.current?.getEditMeta() || {}
|
||||
// const response = await test(
|
||||
// { apiId, workspaceId, projectId },
|
||||
// {
|
||||
// uri,
|
||||
// restParams: rest || [],
|
||||
// headersParams: (headers as HeaderParamsType[]) || [],
|
||||
// bodyParams: (body?.data as BodyParamsType[]) || [],
|
||||
// method: httpMethod,
|
||||
// requestType: body!.apiBodyType
|
||||
// }
|
||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// }, [testApiInfo])
|
||||
// )
|
||||
// const response = {data:{}}
|
||||
// if (response.data?.report?.request) {
|
||||
// response.data.report.request.uri = uri
|
||||
// }
|
||||
// setTestResponse(response.data)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (apiInfo && loaded) {
|
||||
setUri(apiInfo.uri)
|
||||
setHttpMethod(apiInfo.method)
|
||||
testerApiRef.current?.updateHeaderDataGrid((apiInfo.requestParams.headerParams as HeaderParamsType[]) || [])
|
||||
const apiBodyType: TestApiBodyType = apiInfo.requestParams?.bodyParams[0]?.contentType as TestApiBodyType
|
||||
const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded'
|
||||
testerApiRef.current?.updateRequestBody({
|
||||
apiBodyType,
|
||||
contentType: contentType,
|
||||
data:
|
||||
apiBodyType === ApiBodyType.Raw
|
||||
? apiInfo.requestParams?.bodyParams?.[0]?.binaryRawData
|
||||
: apiInfo.requestParams?.bodyParams
|
||||
})
|
||||
}
|
||||
}, [apiInfo, loaded])
|
||||
|
||||
useImperativeHandle(apiRef, () => ({
|
||||
getTestMeta: () => {
|
||||
const {
|
||||
rest,
|
||||
query,
|
||||
headers,
|
||||
body,
|
||||
} = testerApiRef.current?.getEditMeta() || {}
|
||||
return {
|
||||
uri,
|
||||
restParams: rest || [],
|
||||
headersParams: (headers as HeaderParamsType[]) || [],
|
||||
bodyParams: (body?.data as BodyParamsType[]) || [],
|
||||
queryParams: query || [],
|
||||
method: httpMethod,
|
||||
requestType: body!.apiBodyType
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const handleTest = async () => {
|
||||
const { rest, headers, body} = testerApiRef.current?.getEditMeta() || {}
|
||||
// const response = await test(
|
||||
// { apiId, workspaceId, projectId },
|
||||
// {
|
||||
// uri,
|
||||
// restParams: rest || [],
|
||||
// headersParams: (headers as HeaderParamsType[]) || [],
|
||||
// bodyParams: (body?.data as BodyParamsType[]) || [],
|
||||
// method: httpMethod,
|
||||
// requestType: body!.apiBodyType
|
||||
// }
|
||||
// )
|
||||
// const response = {data:{}}
|
||||
// if (response.data?.report?.request) {
|
||||
// response.data.report.request.uri = uri
|
||||
// }
|
||||
// setTestResponse(response.data)
|
||||
const handleQueryChange = useCallback((queryList: QueryParamsType[]) => {
|
||||
/** Can't use new URL due to potential non-standard URLs; reverting to pre-refactor code temporarily. */
|
||||
const result = syncUrlAndQuery(uri, queryList as SafeAny, {
|
||||
nowOperate: 'query',
|
||||
method: 'replace'
|
||||
})
|
||||
if (result?.url) {
|
||||
setUri(result.url)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const handleQueryChange = useCallback((queryList: QueryParamsType[]) => {
|
||||
/** Can't use new URL due to potential non-standard URLs; reverting to pre-refactor code temporarily. */
|
||||
const result = syncUrlAndQuery(uri, queryList as SafeAny, {
|
||||
nowOperate: 'query',
|
||||
method: 'replace'
|
||||
const handleUriChange = (uri: string) => {
|
||||
setUri(uri)
|
||||
// if (activeTab?.path === quickTestRoute.path) {
|
||||
// updateTab({ ...activeTab, name: uri || 'New Request', method: httpMethod } as TabRouteObject)
|
||||
// }
|
||||
if (uri) {
|
||||
const restResult = extractBraceContent(uri)
|
||||
const queryResult = syncUrlAndQuery(uri, [])
|
||||
queryResult?.query?.length && testerApiRef.current?.updateQueryDataGrid(queryResult.query)
|
||||
restResult?.length &&
|
||||
testerApiRef.current?.updateRestDataGrid(restResult.map((rest) => ({ name: rest })) as RestParamsType[])
|
||||
}
|
||||
}
|
||||
|
||||
const handleHttpMethodChange = (method: HTTPMethod) => {
|
||||
setHttpMethod(method)
|
||||
// if (activeTab?.path === quickTestRoute.path) {
|
||||
// updateTab({ ...activeTab, name: uri || 'New Request', method } as TabRouteObject)
|
||||
// }
|
||||
}
|
||||
|
||||
/** Execute this logic only during 'Quicktest' run. */
|
||||
const handleSaveApi = () => {
|
||||
const { rest, query, headers, body, preScript = '', postScript = '' } = testerApiRef.current?.getEditMeta() || {}
|
||||
const newApiInfo = getDefaultApiInfo()
|
||||
const contentType = mapContentTypeToApiBodyType(body?.contentType ?? 'text/plain')
|
||||
newApiInfo.uri = uri
|
||||
newApiInfo.name = 'New Request'
|
||||
newApiInfo.apiAttrInfo.requestMethod = httpMethod
|
||||
newApiInfo.apiAttrInfo.contentType = contentType
|
||||
newApiInfo.requestParams = {
|
||||
bodyParams: body?.data,
|
||||
headerParams: headers,
|
||||
queryParams: query,
|
||||
restParams: rest
|
||||
} as SafeAny
|
||||
if (testResponse) {
|
||||
const response = testResponse.report.response
|
||||
const responseHeader = response?.headers.map((header) => {
|
||||
return generateRow({
|
||||
name: header.key,
|
||||
paramAttr: {
|
||||
example: header.value
|
||||
}
|
||||
})
|
||||
if (result?.url) {
|
||||
setUri(result.url)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const handleUriChange = (uri: string) => {
|
||||
setUri(uri)
|
||||
// if (activeTab?.path === quickTestRoute.path) {
|
||||
// updateTab({ ...activeTab, name: uri || 'New Request', method: httpMethod } as TabRouteObject)
|
||||
// }
|
||||
if (uri) {
|
||||
const restResult = extractBraceContent(uri)
|
||||
const queryResult = syncUrlAndQuery(uri, [])
|
||||
queryResult?.query?.length && testerApiRef.current?.updateQueryDataGrid(queryResult.query)
|
||||
restResult?.length &&
|
||||
testerApiRef.current?.updateRestDataGrid(restResult.map((rest) => ({ name: rest })) as RestParamsType[])
|
||||
})
|
||||
const responseList = [
|
||||
{
|
||||
// TODO: response JSON?
|
||||
contentType: ApiBodyType.Raw,
|
||||
responseParams: {
|
||||
bodyParams: [RawParams(response?.body || '')],
|
||||
headerParams: responseHeader || [],
|
||||
queryParams: [],
|
||||
restParams: []
|
||||
}
|
||||
}
|
||||
]
|
||||
newApiInfo.responseList = responseList as unknown as ResponseList[]
|
||||
}
|
||||
}
|
||||
|
||||
const handleHttpMethodChange = (method: HTTPMethod) => {
|
||||
setHttpMethod(method)
|
||||
// if (activeTab?.path === quickTestRoute.path) {
|
||||
// updateTab({ ...activeTab, name: uri || 'New Request', method } as TabRouteObject)
|
||||
// }
|
||||
}
|
||||
const handleCURLParse = (cURLResult: ParseCurlResult) => {
|
||||
setHttpMethod(HTTPMethod[cURLResult.method as keyof typeof HTTPMethod])
|
||||
/** cURLResult.body */
|
||||
const headers = Object.keys(cURLResult.headers).map((key) => ({
|
||||
name: key,
|
||||
paramAttr: {
|
||||
example: cURLResult.headers[key]
|
||||
}
|
||||
}))
|
||||
testerApiRef.current?.updateHeaderDataGrid((headers as HeaderParamsType[]) || [])
|
||||
testerApiRef.current?.updateRequestBodyWithCurlInfo(cURLResult)
|
||||
setTimeout(() => {
|
||||
setUri(cURLResult.url)
|
||||
testerApiRef.current?.updateQueryDataGrid([])
|
||||
testerApiRef.current?.updateRestDataGrid([])
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/** Execute this logic only during 'Quicktest' run. */
|
||||
const handleSaveApi = () => {
|
||||
const { rest, query, headers, body, preScript = '', postScript = '' } = testerApiRef.current?.getEditMeta() || {}
|
||||
const newApiInfo = getDefaultApiInfo()
|
||||
const contentType = mapContentTypeToApiBodyType(body?.contentType ?? 'text/plain')
|
||||
newApiInfo.uri = uri
|
||||
newApiInfo.name = 'New Request'
|
||||
newApiInfo.apiAttrInfo.requestMethod = httpMethod
|
||||
newApiInfo.apiAttrInfo.contentType = contentType
|
||||
newApiInfo.requestParams = {
|
||||
bodyParams: body?.data,
|
||||
headerParams: headers,
|
||||
queryParams: query,
|
||||
restParams: rest
|
||||
} as SafeAny
|
||||
if (testResponse) {
|
||||
const response = testResponse.report.response
|
||||
const responseHeader = response?.headers.map((header) => {
|
||||
return generateRow({
|
||||
name: header.key,
|
||||
paramAttr: {
|
||||
example: header.value
|
||||
}
|
||||
})
|
||||
})
|
||||
const responseList = [
|
||||
{
|
||||
// TODO: response JSON?
|
||||
contentType: ApiBodyType.Raw,
|
||||
responseParams: {
|
||||
bodyParams: [RawParams(response?.body || '')],
|
||||
headerParams: responseHeader || [],
|
||||
queryParams: [],
|
||||
restParams: []
|
||||
}
|
||||
}
|
||||
]
|
||||
newApiInfo.responseList = responseList as unknown as ResponseList[]
|
||||
}
|
||||
}
|
||||
|
||||
const handleCURLParse = (cURLResult: ParseCurlResult) => {
|
||||
setHttpMethod(HTTPMethod[cURLResult.method as keyof typeof HTTPMethod])
|
||||
/** cURLResult.body */
|
||||
const headers = Object.keys(cURLResult.headers).map((key) => ({
|
||||
name: key,
|
||||
paramAttr: {
|
||||
example: cURLResult.headers[key]
|
||||
}
|
||||
}))
|
||||
testerApiRef.current?.updateHeaderDataGrid((headers as HeaderParamsType[]) || [])
|
||||
testerApiRef.current?.updateRequestBodyWithCurlInfo(cURLResult)
|
||||
setTimeout(() => {
|
||||
setUri(cURLResult.url)
|
||||
testerApiRef.current?.updateQueryDataGrid([])
|
||||
testerApiRef.current?.updateRestDataGrid([])
|
||||
}, 0)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Box height="100%" width="calc(100% - 300px)" boxSizing="border-box">
|
||||
<Stack direction="column" spacing={1} height="100%">
|
||||
<Box height="100%" width="100%">
|
||||
<Allotment vertical>
|
||||
<Allotment.Pane snap>
|
||||
<Box p={2} height="100%" display="flex" boxSizing="border-box" flexDirection="column">
|
||||
<Box height="40px">
|
||||
<Box display="flex" gap={2}>
|
||||
<UriInput
|
||||
inputValue={uri}
|
||||
onInputChange={handleUriChange}
|
||||
selectValue={httpMethod}
|
||||
onSelectChange={handleHttpMethodChange}
|
||||
onCURLPaste={handleCURLParse}
|
||||
onTest={handleTest}
|
||||
/>
|
||||
<Box flexShrink={0} display="flex" gap={2}>
|
||||
<TestControl loading={isLoading} onTest={handleTest} onAbort={cancel} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box height="calc(100% - 40px)">
|
||||
<Tester apiRef={testerApiRef} apiInfo={apiInfo} onQueryChange={handleQueryChange} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Allotment.Pane>
|
||||
<Allotment.Pane preferredSize={300} snap minSize={180}>
|
||||
<Box ref={parent} height="100%" width="100%">
|
||||
{isLoading ? <LinearProgress /> : null}
|
||||
<ApiResponse data={testResponse} />
|
||||
</Box>
|
||||
</Allotment.Pane>
|
||||
</Allotment>
|
||||
return (
|
||||
<Box height="100%" width="calc(100% - 300px)" boxSizing="border-box">
|
||||
<Stack direction="column" spacing={1} height="100%">
|
||||
<Box height="100%" width="100%">
|
||||
<Allotment vertical>
|
||||
<Allotment.Pane snap>
|
||||
<Box p={2} height="100%" display="flex" boxSizing="border-box" flexDirection="column">
|
||||
<Box height="40px">
|
||||
<Box display="flex" gap={2}>
|
||||
<UriInput
|
||||
inputValue={uri}
|
||||
onInputChange={handleUriChange}
|
||||
selectValue={httpMethod}
|
||||
onSelectChange={handleHttpMethodChange}
|
||||
onCURLPaste={handleCURLParse}
|
||||
onTest={handleTest}
|
||||
/>
|
||||
<Box flexShrink={0} display="flex" gap={2}>
|
||||
<TestControl loading={isLoading} onTest={handleTest} onAbort={cancel} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Box height="calc(100% - 40px)">
|
||||
<Tester apiRef={testerApiRef} apiInfo={apiInfo} onQueryChange={handleQueryChange} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Allotment.Pane>
|
||||
<Allotment.Pane preferredSize={300} snap minSize={180}>
|
||||
<Box ref={parent} height="100%" width="100%">
|
||||
{isLoading ? <LinearProgress /> : null}
|
||||
<ApiResponse data={testResponse} />
|
||||
</Box>
|
||||
</Allotment.Pane>
|
||||
</Allotment>
|
||||
</Box>
|
||||
)
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { $t } from '@common/locales';
|
||||
import { $t } from '@common/locales'
|
||||
import { TextField } from '@mui/material'
|
||||
import { SyntheticEvent } from 'react'
|
||||
|
||||
export function RequestBodyBinary({ value, onChange }: { value: string; onChange: (value: string) => void }) {
|
||||
return (
|
||||
<TextField
|
||||
label={$t("Binary")}
|
||||
label={$t('Binary')}
|
||||
multiline
|
||||
rows={4}
|
||||
value={value}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import {Codebox} from "../../../../Codebox";
|
||||
import { Codebox } from '../../../../Codebox'
|
||||
|
||||
interface RequestBodyRawProps {
|
||||
value: string
|
||||
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
|
||||
import { ApiBodyType, ApiParamsType } from "@common/const/api-detail"
|
||||
import { ApiBodyType, ApiParamsType } from '@common/const/api-detail'
|
||||
|
||||
export type ApiBodyTypeLabel = 'Form-Data' | 'JSON' | 'XML' | 'Raw' | 'Binary'
|
||||
|
||||
|
||||
+30
-22
@@ -1,11 +1,20 @@
|
||||
import {Box, FormControl, FormControlLabel, MenuItem, Radio, RadioGroup, Select, SelectChangeEvent} from '@mui/material'
|
||||
import {ApiBodyTypeOption} from './constants'
|
||||
import {ChangeEvent, SetStateAction, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'
|
||||
import {RequestBodyRaw} from './components/Raw'
|
||||
import {RequestBodyBinary} from './components/Binary'
|
||||
import {ApiBodyType, BodyParamsType} from "@common/const/api-detail";
|
||||
import {generateId} from "@common/utils/postcat.tsx";
|
||||
import {MessageDataGrid, MessageDataGridApi} from "../MessageDataGrid";
|
||||
import {
|
||||
Box,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
MenuItem,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
SelectChangeEvent
|
||||
} from '@mui/material'
|
||||
import { ApiBodyTypeOption } from './constants'
|
||||
import { ChangeEvent, SetStateAction, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { RequestBodyRaw } from './components/Raw'
|
||||
import { RequestBodyBinary } from './components/Binary'
|
||||
import { ApiBodyType, BodyParamsType } from '@common/const/api-detail'
|
||||
import { generateId } from '@common/utils/postcat.tsx'
|
||||
import { MessageDataGrid, MessageDataGridApi } from '../MessageDataGrid'
|
||||
|
||||
export interface ApiMessageBodyApi {
|
||||
getBodyMeta: () => {
|
||||
@@ -43,7 +52,7 @@ export function RawParams(value: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBodyParamsTypeProps) {
|
||||
export function ApiMessageBody({ apiInfo = null, loaded, mode, bodyApiRef }: ApiBodyParamsTypeProps) {
|
||||
const [apiBodyTypeValue, setApiBodyTypeValue] = useState<ApiBodyType>(ApiBodyType.JSON)
|
||||
|
||||
const [apiFormData, setApiFormData] = useState<BodyParamsType[] | null>([])
|
||||
@@ -67,7 +76,9 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo
|
||||
}[apiBodyTypeValue as ApiBodyType.JSON | ApiBodyType.FormData | ApiBodyType.XML]
|
||||
bodyParams.push(...(targetRef.current?.getEditMeta() as BodyParamsType[]))
|
||||
} else if ([ApiBodyType.Raw, ApiBodyType.Binary].includes(apiBodyTypeValue)) {
|
||||
bodyParams.push(RawParams(apiBodyTypeValue === ApiBodyType.Raw ? apiRaw : apiBinary) as unknown as BodyParamsType)
|
||||
bodyParams.push(
|
||||
RawParams(apiBodyTypeValue === ApiBodyType.Raw ? apiRaw : apiBinary) as unknown as BodyParamsType
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -86,7 +97,7 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo
|
||||
// }
|
||||
// }
|
||||
// },500)
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (loaded && (apiInfo || apiInfo === null)) {
|
||||
@@ -193,14 +204,12 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo
|
||||
{
|
||||
key: 'Raw',
|
||||
value: ApiBodyType.Raw,
|
||||
element: <RequestBodyRaw value={apiRaw} onChange={setApiRaw}
|
||||
loaded={loaded} />
|
||||
element: <RequestBodyRaw value={apiRaw} onChange={setApiRaw} loaded={loaded} />
|
||||
},
|
||||
{
|
||||
key: 'Binary',
|
||||
value: ApiBodyType.Binary,
|
||||
element: <RequestBodyBinary value={apiBinary} onChange={setApiBinary}
|
||||
loaded={loaded}/>
|
||||
element: <RequestBodyBinary value={apiBinary} onChange={setApiBinary} loaded={loaded} />
|
||||
}
|
||||
].filter((type) => type) as ApiBodyTypeOption[]
|
||||
}, [apiBinary, apiFormData, apiJson, apiRaw, apiXml, mode])
|
||||
@@ -214,23 +223,23 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo
|
||||
}
|
||||
|
||||
return (
|
||||
<Box >
|
||||
<Box sx={{display:'flex',alignItems:'CENTER',paddingY:'4px'}}>
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'CENTER', paddingY: '4px' }}>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
row
|
||||
name="api-body-type-radio-buttons-group"
|
||||
value={apiBodyTypeValue}
|
||||
onChange={handleApiBodyTypeValueChange}
|
||||
sx={{ height: '30px' ,paddingLeft:'8px',marginLeft:'8px',fontSize:'12px'}}
|
||||
sx={{ height: '30px', paddingLeft: '8px', marginLeft: '8px', fontSize: '12px' }}
|
||||
>
|
||||
{apiBodyTypeList.map((apiBodyType) => (
|
||||
<FormControlLabel
|
||||
sx={{ paddingRight:'10px' ,'& .MuiFormControlLabel-label': { fontSize: '12px' }, }}
|
||||
sx={{ paddingRight: '10px', '& .MuiFormControlLabel-label': { fontSize: '12px' } }}
|
||||
key={apiBodyType.value}
|
||||
value={apiBodyType.value}
|
||||
checked={apiBodyType.value === apiBodyTypeValue}
|
||||
control={<Radio sx={{ height: '30px',width:'22px',marginRight:'4px',color:'#EDEDED' }} />}
|
||||
control={<Radio sx={{ height: '30px', width: '22px', marginRight: '4px', color: '#EDEDED' }} />}
|
||||
label={apiBodyType.key}
|
||||
/>
|
||||
))}
|
||||
@@ -243,8 +252,7 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo
|
||||
onChange={handleJsonTypeChange}
|
||||
sx={{
|
||||
height: '30px',
|
||||
borderColor:'#EDEDED'
|
||||
|
||||
borderColor: '#EDEDED'
|
||||
}}
|
||||
>
|
||||
<MenuItem value={ApiBodyType.JSON}>Object</MenuItem>
|
||||
|
||||
+11
-16
@@ -1,20 +1,19 @@
|
||||
import { Box, useTheme } from '@mui/material'
|
||||
import {ApiBodyType, BodyParamsType} from "@common/const/api-detail";
|
||||
import {RenderMessageBody} from "@common/components/postcat/api/apiManager/components/MessageDataGrid";
|
||||
import { BodyParamsType } from '@common/const/api-detail'
|
||||
import { RenderMessageBody } from '@common/components/postcat/api/apiManager/components/MessageDataGrid'
|
||||
export interface ApiProxyEditorApi {
|
||||
getEditMeta: () => Partial<BodyParamsType>[]
|
||||
}
|
||||
|
||||
|
||||
interface ApiProxyEditorProps<T = unknown> {
|
||||
// onChange?: (rows: T[]) => void
|
||||
// initialRows?: T[] | null
|
||||
// onDirty?: () => void
|
||||
// onChange?: (rows: T[]) => void
|
||||
// initialRows?: T[] | null
|
||||
// onDirty?: () => void
|
||||
loading?: boolean
|
||||
// messageType?: MessageType
|
||||
// contentType: ContentType
|
||||
// isMoreSettingReadOnly?: boolean
|
||||
// apiRef?: RefObject<ApiProxyEditorApi>
|
||||
// messageType?: MessageType
|
||||
// contentType: ContentType
|
||||
// isMoreSettingReadOnly?: boolean
|
||||
// apiRef?: RefObject<ApiProxyEditorApi>
|
||||
}
|
||||
|
||||
export function ApiProxyEditor(props: ApiProxyEditorProps<RenderMessageBody>) {
|
||||
@@ -22,7 +21,7 @@ export function ApiProxyEditor(props: ApiProxyEditorProps<RenderMessageBody>) {
|
||||
// onChange,
|
||||
// initialRows,
|
||||
// onDirty,
|
||||
loading = false,
|
||||
loading = false
|
||||
// contentType,
|
||||
// messageType,
|
||||
// isMoreSettingReadOnly,
|
||||
@@ -31,7 +30,6 @@ export function ApiProxyEditor(props: ApiProxyEditorProps<RenderMessageBody>) {
|
||||
} = props
|
||||
const theme = useTheme()
|
||||
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -39,10 +37,7 @@ export function ApiProxyEditor(props: ApiProxyEditorProps<RenderMessageBody>) {
|
||||
borderRadius: `${theme.shape.borderRadius}px`
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
|
||||
</Box>
|
||||
|
||||
<Box></Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
+33
-19
@@ -1,16 +1,17 @@
|
||||
import { Box, Grow, Tab, Tabs, Typography, useTheme } from '@mui/material'
|
||||
import {ReactNode, SyntheticEvent, useEffect, useImperativeHandle, useRef, useState} from 'react'
|
||||
import { ReactNode, SyntheticEvent, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import {
|
||||
ApiBodyType, ApiDetail,
|
||||
ApiBodyType,
|
||||
ApiDetail,
|
||||
BodyParamsType,
|
||||
HeaderParamsType,
|
||||
QueryParamsType,
|
||||
RestParamsType
|
||||
} from "@common/const/api-detail";
|
||||
import {MessageDataGrid, MessageDataGridApi} from "../MessageDataGrid";
|
||||
import {Indicator} from "../../../../Indicator";
|
||||
import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody';
|
||||
import { $t } from '@common/locales';
|
||||
} from '@common/const/api-detail'
|
||||
import { MessageDataGrid, MessageDataGridApi } from '../MessageDataGrid'
|
||||
import { Indicator } from '../../../../Indicator'
|
||||
import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export interface ApiRequestEditorApi {
|
||||
getData: () => {
|
||||
@@ -30,21 +31,31 @@ interface ApiRequestEditorTab {
|
||||
dirty: boolean
|
||||
}
|
||||
|
||||
export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?: React.RefObject<ApiRequestEditorApi> ,apiInfo:ApiDetail,loaded:boolean}) {
|
||||
const [apiHeaders, setApiHeaders] = useState<HeaderParamsType[] >([])
|
||||
const [apiQuery, setApiQuery] = useState<QueryParamsType[] >([])
|
||||
const [apiRest, setApiRest] = useState<RestParamsType[] >([])
|
||||
export function ApiRequestEditor({
|
||||
editorRef,
|
||||
apiInfo = null,
|
||||
loaded
|
||||
}: {
|
||||
editorRef?: React.RefObject<ApiRequestEditorApi>
|
||||
apiInfo: ApiDetail
|
||||
loaded: boolean
|
||||
}) {
|
||||
const [apiHeaders, setApiHeaders] = useState<HeaderParamsType[]>([])
|
||||
const [apiQuery, setApiQuery] = useState<QueryParamsType[]>([])
|
||||
const [apiRest, setApiRest] = useState<RestParamsType[]>([])
|
||||
|
||||
const headersRef = useRef<MessageDataGridApi>(null)
|
||||
const bodyRef = useRef<ApiMessageBodyApi>(null)
|
||||
const queryRef = useRef<MessageDataGridApi>(null)
|
||||
const restRef = useRef<MessageDataGridApi>(null)
|
||||
|
||||
const [innerLoaded,setInnerLoaded] = useState<boolean>(false)
|
||||
const [innerLoaded, setInnerLoaded] = useState<boolean>(false)
|
||||
useImperativeHandle(editorRef, () => ({
|
||||
getData: () => {
|
||||
return {
|
||||
bodyParams: bodyRef.current?.getBodyMeta()?.bodyParams.map((x)=>({...x,contentType:bodyRef.current?.getBodyMeta().contentType})),
|
||||
bodyParams: bodyRef.current
|
||||
?.getBodyMeta()
|
||||
?.bodyParams.map((x) => ({ ...x, contentType: bodyRef.current?.getBodyMeta().contentType })),
|
||||
headerParams: (headersRef.current?.getEditMeta() as HeaderParamsType[]) || [],
|
||||
queryParams: (queryRef.current?.getEditMeta() as QueryParamsType[]) || [],
|
||||
restParams: (restRef.current?.getEditMeta() as RestParamsType[]) || []
|
||||
@@ -77,8 +88,8 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
|
||||
dirty: false
|
||||
},
|
||||
{
|
||||
label:$t('请求体'),
|
||||
element: <ApiMessageBody bodyApiRef={bodyRef} mode="request" apiInfo={apiInfo} loaded={innerLoaded}/>,
|
||||
label: $t('请求体'),
|
||||
element: <ApiMessageBody bodyApiRef={bodyRef} mode="request" apiInfo={apiInfo} loaded={innerLoaded} />,
|
||||
dirty: false
|
||||
},
|
||||
{
|
||||
@@ -123,12 +134,15 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
|
||||
const tabHeight = '30px'
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
borderColor: 'divider' }}>
|
||||
<Box
|
||||
sx={{
|
||||
borderColor: 'divider'
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={tabValue}
|
||||
onChange={handleChange}
|
||||
aria-label={$t("api request editor")}
|
||||
aria-label={$t('api request editor')}
|
||||
sx={{
|
||||
minHeight: tabHeight,
|
||||
height: tabHeight,
|
||||
@@ -144,7 +158,7 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
|
||||
value={tab.label}
|
||||
label={
|
||||
<Box key={tab.label} display="flex" alignItems="center" pr={tab.dirty ? 0.5 : 0}>
|
||||
<Typography sx={{fontSize:'14px'}}>{tab.label}</Typography>
|
||||
<Typography sx={{ fontSize: '14px' }}>{tab.label}</Typography>
|
||||
<Grow in={tab.dirty}>
|
||||
<Box>
|
||||
<Indicator
|
||||
|
||||
+34
-24
@@ -1,11 +1,11 @@
|
||||
import { Box, Grow, Tab, Tabs, Typography, useTheme } from '@mui/material'
|
||||
import { ReactNode, SyntheticEvent, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { ReactNode, SyntheticEvent, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||
import { MessageDataGrid, MessageDataGridApi } from '../MessageDataGrid'
|
||||
import {ApiBodyType, BodyParamsType, HeaderParamsType} from "@common/const/api-detail";
|
||||
import {Indicator} from "../../../../Indicator";
|
||||
import { v4 as uuidv4} from 'uuid'
|
||||
import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody';
|
||||
import { $t } from '@common/locales';
|
||||
import { ApiBodyType, BodyParamsType, HeaderParamsType } from '@common/const/api-detail'
|
||||
import { Indicator } from '../../../../Indicator'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
interface ApiRequestEditorTab {
|
||||
label: string
|
||||
@@ -23,7 +23,13 @@ export interface ApiResponseEditorApi {
|
||||
}
|
||||
}
|
||||
|
||||
export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRef?: React.RefObject<ApiResponseEditorApi> }) {
|
||||
export function ApiResponseEditor({
|
||||
editorRef,
|
||||
apiInfo = null,
|
||||
loaded
|
||||
}: {
|
||||
editorRef?: React.RefObject<ApiResponseEditorApi>
|
||||
}) {
|
||||
const [apiHeaders, setApiHeaders] = useState<HeaderParamsType[] | null>([])
|
||||
const [innerLoaded, setInnerLoaded] = useState<boolean>(false)
|
||||
const headersRef = useRef<MessageDataGridApi>(null)
|
||||
@@ -40,16 +46,17 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
|
||||
getData: () => {
|
||||
const bodyData = bodyRef.current?.getBodyMeta()
|
||||
const uuid = uuidv4()
|
||||
return ([{
|
||||
id:uuid,
|
||||
responseUuid:uuid,
|
||||
httpCode:bodyData?.contentType,
|
||||
responseParams:{
|
||||
bodyParams: bodyData?.bodyParams,
|
||||
headerParams: (headersRef.current?.getEditMeta() as HeaderParamsType[]) || []
|
||||
return [
|
||||
{
|
||||
id: uuid,
|
||||
responseUuid: uuid,
|
||||
httpCode: bodyData?.contentType,
|
||||
responseParams: {
|
||||
bodyParams: bodyData?.bodyParams,
|
||||
headerParams: (headersRef.current?.getEditMeta() as HeaderParamsType[]) || []
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
]
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -70,8 +77,7 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
|
||||
},
|
||||
{
|
||||
label: $t('返回值'),
|
||||
element: <ApiMessageBody bodyApiRef={bodyRef} mode="response" apiInfo={apiInfo}
|
||||
loaded={innerLoaded} />,
|
||||
element: <ApiMessageBody bodyApiRef={bodyRef} mode="response" apiInfo={apiInfo} loaded={innerLoaded} />,
|
||||
dirty: false
|
||||
}
|
||||
]
|
||||
@@ -88,17 +94,21 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
|
||||
const tabHeight = '30px'
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
// borderBottom: 1,
|
||||
borderColor: 'divider' }}>
|
||||
<Box
|
||||
sx={{
|
||||
// borderBottom: 1,
|
||||
borderColor: 'divider'
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={tabValue}
|
||||
onChange={handleChange}
|
||||
aria-label={$t("api request editor")}
|
||||
aria-label={$t('api request editor')}
|
||||
sx={{
|
||||
minHeight: tabHeight,
|
||||
height: tabHeight,
|
||||
borderBottom: 1, borderColor: 'divider',
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
'& .MuiTabs-flexContainer': {
|
||||
minHeight: tabHeight,
|
||||
height: tabHeight
|
||||
@@ -111,7 +121,7 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
|
||||
value={tab.label}
|
||||
label={
|
||||
<Box key={tab.label} display="flex" alignItems="center" pr={tab.dirty ? 0.5 : 0}>
|
||||
<Typography sx={{fontSize:'14px'}}>{tab.label}</Typography>
|
||||
<Typography sx={{ fontSize: '14px' }}>{tab.label}</Typography>
|
||||
<Grow in={tab.dirty}>
|
||||
<Box>
|
||||
<Indicator
|
||||
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
|
||||
import { Box, SxProps, TextFieldProps, Theme } from '@mui/material'
|
||||
import { HTMLAttributes, KeyboardEvent } from 'react'
|
||||
|
||||
|
||||
+15
-13
@@ -1,17 +1,19 @@
|
||||
|
||||
import {generateId} from "@common/utils/postcat.tsx";
|
||||
import { generateId } from '@common/utils/postcat.tsx'
|
||||
|
||||
type SafeAny = unknown
|
||||
export function generateRow(data: SafeAny = {}) {
|
||||
return Object.assign({
|
||||
id: generateId(),
|
||||
name: '',
|
||||
dataType: null,
|
||||
isRequired: 1,
|
||||
description: '',
|
||||
paramAttr: {
|
||||
example: ''
|
||||
return Object.assign(
|
||||
{
|
||||
id: generateId(),
|
||||
name: '',
|
||||
dataType: null,
|
||||
isRequired: 1,
|
||||
description: '',
|
||||
paramAttr: {
|
||||
example: ''
|
||||
},
|
||||
childList: []
|
||||
},
|
||||
childList: []
|
||||
}, data)
|
||||
}
|
||||
data
|
||||
)
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import {MessageType, RenderMessageBody} from "../index.tsx";
|
||||
import {isNil} from "@common/utils/postcat.tsx";
|
||||
import {ApiParamsType} from "@common/const/api-detail";
|
||||
import { MessageType, RenderMessageBody } from '../index.tsx'
|
||||
import { isNil } from '@common/utils/postcat.tsx'
|
||||
import { ApiParamsType } from '@common/const/api-detail'
|
||||
|
||||
interface UseMoreSettingHiddenConfigProps {
|
||||
param: RenderMessageBody
|
||||
|
||||
+31
-32
@@ -22,26 +22,26 @@ import {
|
||||
} from 'react'
|
||||
import { ApiParamsTypeOptions } from '../ApiMessageBody/constants'
|
||||
import { RequestHeaders } from '../ApiRequestEditor/components/constants'
|
||||
import {ApiParamsType, BodyParamsType, ParamAttrType, commonTableSx} from "@common/const/api-detail";
|
||||
import { ApiParamsType, BodyParamsType, ParamAttrType, commonTableSx } from '@common/const/api-detail'
|
||||
import {
|
||||
determineCheckState,
|
||||
flattenTree,
|
||||
generateId,
|
||||
getActionColWidth,
|
||||
isNil,
|
||||
traverse
|
||||
} from "@common/utils/postcat.tsx";
|
||||
import {MoreSetting} from "../../../MoreSetting";
|
||||
determineCheckState,
|
||||
flattenTree,
|
||||
generateId,
|
||||
getActionColWidth,
|
||||
isNil,
|
||||
traverse
|
||||
} from '@common/utils/postcat.tsx'
|
||||
import { MoreSetting } from '../../../MoreSetting'
|
||||
import {
|
||||
AutoCompleteOption,
|
||||
DataGridAutoCompleteProps,
|
||||
DataGridTextFieldProps,
|
||||
EditableDataGridSx
|
||||
} from "../EditableDataGrid";
|
||||
import {collapseTableSx} from "../../../PreviewTable";
|
||||
import {IconButton} from "../../../IconButton";
|
||||
import {Icon} from "../../../Icon";
|
||||
import {useMoreSettingHiddenConfig} from "./hooks/useMoreSettingHiddenConfig.ts";
|
||||
AutoCompleteOption,
|
||||
DataGridAutoCompleteProps,
|
||||
DataGridTextFieldProps,
|
||||
EditableDataGridSx
|
||||
} from '../EditableDataGrid'
|
||||
import { collapseTableSx } from '../../../PreviewTable'
|
||||
import { IconButton } from '../../../IconButton'
|
||||
import { Icon } from '../../../Icon'
|
||||
import { useMoreSettingHiddenConfig } from './hooks/useMoreSettingHiddenConfig.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
|
||||
export interface RenderMessageBody extends BodyParamsType {
|
||||
@@ -156,7 +156,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
|
||||
useEffect(() => {
|
||||
if (initialRows && loaded && !innerLoaded) {
|
||||
let updateRows = [...initialRows,EmptyRow()]
|
||||
let updateRows = [...initialRows, EmptyRow()]
|
||||
if (!updateRows?.length && contentType !== 'XML') {
|
||||
updateRows = [EmptyRow()]
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
onChange?.(updateRows)
|
||||
setInnerLoaded(true)
|
||||
}
|
||||
}, [EmptyRow, contentType, initialRows, loaded,innerLoaded, onChange, updateSelectAll])
|
||||
}, [EmptyRow, contentType, initialRows, loaded, innerLoaded, onChange, updateSelectAll])
|
||||
|
||||
useEffect(() => {
|
||||
const neoRenderRows = flattenTree(
|
||||
@@ -251,15 +251,13 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
|
||||
const getActions = useCallback(
|
||||
(params: GridRowParams<RenderMessageBody>) => {
|
||||
const actions = [
|
||||
<IconButton title={$t("更多设置")} name="more" onClick={() => handleOpenMoreSetting(params)} />
|
||||
]
|
||||
const actions = [<IconButton title={$t('更多设置')} name="more" onClick={() => handleOpenMoreSetting(params)} />]
|
||||
const isXML = contentType === 'XML'
|
||||
const isRoot = params.row.__globalIndex__ === 0
|
||||
if (['JSON', 'XML'].includes(contentType)) {
|
||||
actions.unshift(
|
||||
<IconButton
|
||||
title={$t("添加子参数")}
|
||||
title={$t('添加子参数')}
|
||||
name="add"
|
||||
onClick={() => {
|
||||
const newRow = EmptyRow()
|
||||
@@ -289,7 +287,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
if (!(isXML && isRoot)) {
|
||||
actions.unshift(
|
||||
<IconButton
|
||||
title={$t("向下添加行")}
|
||||
title={$t('向下添加行')}
|
||||
name="down-small"
|
||||
onClick={() => {
|
||||
const newRow = EmptyRow()
|
||||
@@ -302,11 +300,11 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
}
|
||||
}
|
||||
if (renderRows.length > 1) {
|
||||
actions.push(<IconButton title={$t("删除")} name="delete" onClick={() => handleRowDelete(params)} />)
|
||||
actions.push(<IconButton title={$t('删除')} name="delete" onClick={() => handleRowDelete(params)} />)
|
||||
}
|
||||
return actions
|
||||
},
|
||||
|
||||
|
||||
[EmptyRow, tableApiRef, contentType, handleOpenMoreSetting, handleRowDelete, renderRows.length, rows]
|
||||
)
|
||||
|
||||
@@ -315,7 +313,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
field: 'name',
|
||||
headerName: $t('标签'),
|
||||
editable: true,
|
||||
sortable:false,
|
||||
sortable: false,
|
||||
renderEditCell: (params) => {
|
||||
const options = RequestHeaders.map((option) => option.key)
|
||||
return (
|
||||
@@ -331,7 +329,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
const rowIndex = params.row.__globalIndex__ as number
|
||||
if (renderRows.length === rowIndex + 1) {
|
||||
const newRow = EmptyRow()
|
||||
setRows((preRows)=>[...preRows,newRow])
|
||||
setRows((preRows) => [...preRows, newRow])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -386,7 +384,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
const newRow = EmptyRow()
|
||||
const currentLevelChildrenList = params.row.parent?.childList ?? rows
|
||||
currentLevelChildrenList?.splice((params.row.__levelIndex__ || 0) + 1, 0, newRow)
|
||||
setRows((preRows)=>[...preRows])
|
||||
setRows((preRows) => [...preRows])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -444,7 +442,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
indeterminate={selectAll === 'indeterminate'}
|
||||
onChange={handleSelectAllChange}
|
||||
/>
|
||||
<Typography sx={{fontSize:'14px'}}>{$t('必需')}</Typography>
|
||||
<Typography sx={{ fontSize: '14px' }}>{$t('必需')}</Typography>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
@@ -516,7 +514,8 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
|
||||
paddingRight: theme.spacing(1)
|
||||
}
|
||||
}}
|
||||
placeholder={$t('示例')} />
|
||||
placeholder={$t('示例')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
+7
-9
@@ -1,9 +1,9 @@
|
||||
import { InputAdornment, TextField, Select, MenuItem, Divider, SelectChangeEvent, Typography, Box } from '@mui/material'
|
||||
import { InputAdornment, TextField, Select, MenuItem, Divider, SelectChangeEvent } from '@mui/material'
|
||||
import { SyntheticEvent } from 'react'
|
||||
import {ParseCurlResult} from "@common/const/api-detail";
|
||||
import {HTTPMethod, RequestMethod} from "../../../RequestMethod";
|
||||
import {ParseCurl} from "@common/utils/curl.ts";
|
||||
import { $t } from '@common/locales';
|
||||
import { ParseCurlResult } from '@common/const/api-detail'
|
||||
import { HTTPMethod, RequestMethod } from '../../../RequestMethod'
|
||||
import { ParseCurl } from '@common/utils/curl.ts'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
interface UriInputProps {
|
||||
inputValue?: string
|
||||
@@ -22,8 +22,6 @@ export function UriInput({
|
||||
onCURLPaste,
|
||||
onTest
|
||||
}: UriInputProps) {
|
||||
|
||||
|
||||
const handleSelectChange = (event: SelectChangeEvent<HTTPMethod>) => {
|
||||
onSelectChange?.(event.target.value as HTTPMethod)
|
||||
}
|
||||
@@ -52,14 +50,14 @@ export function UriInput({
|
||||
return (
|
||||
<TextField
|
||||
fullWidth
|
||||
placeholder={$t("输入 URL 或 cURL")}
|
||||
placeholder={$t('输入 URL 或 cURL')}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
sx={{
|
||||
input: {
|
||||
lineHeight: '40px',
|
||||
fontSize: '16px',
|
||||
padding:'8.5px 14px 8.5px 0'
|
||||
padding: '8.5px 14px 8.5px 0'
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import {generateId} from "@common/utils/postcat.tsx";
|
||||
import { generateId } from '@common/utils/postcat.tsx'
|
||||
import {
|
||||
ApiBodyType,
|
||||
BodyParamsType,
|
||||
HeaderParamsType,
|
||||
QueryParamsType, ResponseList,
|
||||
QueryParamsType,
|
||||
ResponseList,
|
||||
RestParamsType
|
||||
} from "@common/const/api-detail";
|
||||
import {Protocol} from "../RequestMethod";
|
||||
} from '@common/const/api-detail'
|
||||
import { Protocol } from '../RequestMethod'
|
||||
|
||||
type SafeAny = unknown
|
||||
type SafeAny = unknown
|
||||
type Timestamp = number
|
||||
|
||||
declare interface HttpRequestMessage {
|
||||
@@ -68,4 +69,4 @@ export function getDefaultApiInfo(): ApiRequest {
|
||||
},
|
||||
responseList: [{}]
|
||||
} as unknown as ApiRequest
|
||||
}
|
||||
}
|
||||
|
||||
+10
-7
@@ -1,8 +1,7 @@
|
||||
|
||||
import { Box, Chip, Stack, Typography, Skeleton } from '@mui/material'
|
||||
import {HTTPMethod, Protocol,RequestMethod} from "../../../RequestMethod";
|
||||
import {Clipboard} from "../../../Clipboard"
|
||||
import { $t } from '@common/locales';
|
||||
import { HTTPMethod, Protocol, RequestMethod } from '../../../RequestMethod'
|
||||
import { Clipboard } from '../../../Clipboard'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
interface ApiBasicInfoDisplayProps {
|
||||
apiName: string
|
||||
@@ -39,15 +38,19 @@ export default function ApiBasicInfoDisplay(props: Partial<ApiBasicInfoDisplayPr
|
||||
<Box display="flex">
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Chip
|
||||
label={$t("HTTP")}
|
||||
label={$t('HTTP')}
|
||||
sx={{
|
||||
height:'22px',
|
||||
height: '22px',
|
||||
borderRadius: '4px',
|
||||
color: '#fff',
|
||||
backgroundColor: '#067ddb'
|
||||
}}
|
||||
/>
|
||||
<RequestMethod variant="filled" protocol={protocol ?? Protocol.HTTP} method={method ?? 'GET' as (keyof typeof HTTPMethod)} />
|
||||
<RequestMethod
|
||||
variant="filled"
|
||||
protocol={protocol ?? Protocol.HTTP}
|
||||
method={method ?? ('GET' as keyof typeof HTTPMethod)}
|
||||
/>
|
||||
<Typography>
|
||||
{/*{selectedEnv ? selectedEnv.hostUri : ''}*/}
|
||||
{uri}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user