Compare commits

...

106 Commits

Author SHA1 Message Date
Dot.L fed865872e Merge pull request #34 from Dot-Liu/main
修复服务获取API列表是获取到别的服务的API
2024-09-02 21:20:31 +08:00
Liujian fd270f2c02 修复服务获取API列表是获取到别的服务的API 2024-09-02 21:14:30 +08:00
Dot.L f2635bf457 Merge pull request #33 from Dot-Liu/main
初始化时当角色存在时,更新角色权限信息
2024-09-02 19:57:03 +08:00
Liujian 4e0813d49e 初始化时当角色存在时,更新角色权限信息 2024-09-02 19:56:04 +08:00
Dot.L 1f8e2bf6c3 Merge pull request #32 from Dot-Liu/main
修复报错:undefined: pm3.CreateRouterSimple
2024-09-02 19:32:46 +08:00
Liujian b58a031a69 修复报错:undefined: pm3.CreateRouterSimple 2024-09-02 19:32:02 +08:00
Dot.L d96f11d968 Merge pull request #31 from Dot-Liu/main
修复openapiv2保存失败的问题
2024-09-02 19:09:11 +08:00
Liujian b22b160aad 修复openapiv2保存失败的问题 2024-09-02 19:07:57 +08:00
Liujian debf19b059 Merge remote-tracking branch 'github-pro/main' into main-github 2024-09-02 19:03:23 +08:00
Dot.L 0f8c14971e Merge pull request #30 from Dot-Liu/main
1.1.0-beta后端代码提交
2024-09-02 18:28:45 +08:00
Liujian fdd4ce3c19 修复更新集群时配置不重新初始化的问题 2024-09-02 18:27:27 +08:00
Liujian 9317f20917 Merge branch 'main' into main-github 2024-09-02 18:00:18 +08:00
maggieyyy 4228cf45f5 Merge pull request #29 from maggieyyy/main
fix:change route & permission btn & datasource access
2024-09-02 17:53:56 +08:00
maggieyyy 1bedad903b fix:change route & permission btn & datasource access 2024-09-02 17:09:44 +08:00
rolea liu e281282514 Merge pull request #28 from rolealiu/main
update translate
2024-09-02 14:25:55 +08:00
HaoZhen Liu 298789e0e2 add english readme 2024-09-02 14:25:39 +08:00
HaoZhen Liu 007e4bdcfb add english readme 2024-09-02 14:21:56 +08:00
rolea liu a7378492ef Merge pull request #27 from rolealiu/main
update translate
2024-09-02 14:21:47 +08:00
rolea liu 4ad23acfb6 Merge pull request #26 from rolealiu/main
update translation
2024-09-02 14:18:15 +08:00
rolea liu 5dfa1170ba Merge branch 'APIParkLab:main' into main 2024-09-02 14:17:43 +08:00
HaoZhen Liu a4d9b0202b add english readme 2024-09-02 14:18:19 +08:00
maggieyyy 3a23ec2ab3 Merge pull request #25 from maggieyyy/main
fix: router match bugs
2024-09-02 13:43:54 +08:00
maggieyyy 0dcea3f03a Merge branch 'main' of github.com:maggieyyy/APIPark into mainGithub 2024-09-02 13:42:44 +08:00
maggieyyy 851c54b6af fix: match type is empty 2024-09-02 13:40:50 +08:00
Liujian bf78926b31 修复路由详情没有返回协议的问题 2024-09-02 11:24:46 +08:00
Liujian 2aada85662 修复上游保存失败问题 2024-09-02 10:20:06 +08:00
Liujian 7d569a49cb 修复发布报错 2024-09-02 10:02:33 +08:00
Liujian 6d69a06935 发布自测完毕 2024-09-01 23:55:45 +08:00
maggieyyy 77aa0080d9 fix:publish status & api doc 2024-08-30 19:38:59 +08:00
Liujian 2ef746bf37 发布功能完成 2024-08-30 19:35:18 +08:00
maggieyyy 809959289c Merge pull request #24 from maggieyyy/main
feat:openapi ui
2024-08-30 19:23:17 +08:00
maggieyyy de74542e78 feat:openapi ui 2024-08-30 19:22:38 +08:00
Liujian 2ae0cb8b51 发布初始提交 2024-08-30 18:33:35 +08:00
Liujian cea880ad5c 路由、权限相关接口完成 2024-08-30 16:48:43 +08:00
Liujian f570c8b174 api文档完成 2024-08-30 09:55:56 +08:00
maggieyyy 6f4a95b0da Merge pull request #23 from maggieyyy/main
fix: change build config
2024-08-28 18:01:49 +08:00
maggieyyy 2817974672 Merge branch 'APIParkLab:main' into main 2024-08-28 18:01:19 +08:00
maggieyyy 1f8ad0fda3 Merge branch 'main' of github.com:maggieyyy/APIPark into mainGithub 2024-08-28 18:00:50 +08:00
maggieyyy 189a1c8fa3 fix:change vite build config 2024-08-28 18:00:30 +08:00
maggieyyy 35de0c003c Merge pull request #22 from maggieyyy/main
fix:intelligent plugin & browser lang & check monitor config
2024-08-28 14:09:32 +08:00
maggieyyy 7ccaa8972c Merge branch 'APIParkLab:main' into main 2024-08-28 14:08:29 +08:00
maggieyyy 9c8418ab40 fix:intelligent plugin & browser lang & check monitor config 2024-08-28 14:07:55 +08:00
maggieyyy c9a2a56dd9 Merge pull request #21 from maggieyyy/main
fix:intelligent i18next
2024-08-27 10:47:15 +08:00
maggieyyy 31cb70ed02 fix:intelligent i18next 2024-08-27 10:45:27 +08:00
rolea liu 1fc2113c9a Merge pull request #20 from rolealiu/main
add english readme
2024-08-26 17:48:37 +08:00
HaoZhen Liu 8c07b63dec add english readme 2024-08-26 17:48:45 +08:00
rolea liu 13ec24d95e Merge pull request #19 from rolealiu/main
add english readme
2024-08-26 17:46:17 +08:00
HaoZhen Liu fd5357a736 Merge remote-tracking branch 'upstream/main' 2024-08-26 17:46:12 +08:00
HaoZhen Liu b349f8b156 add english readme 2024-08-26 17:40:48 +08:00
HaoZhen Liu 30d446aa16 add english readme 2024-08-26 17:33:30 +08:00
HaoZhen Liu a5e19c2705 add english readme 2024-08-26 17:29:18 +08:00
HaoZhen Liu d7ac23235a add english readme 2024-08-26 17:27:43 +08:00
rolea liu 9e95144e86 Merge pull request #18 from rolealiu/main
Improve translation
2024-08-26 17:18:10 +08:00
rolea liu 999957ef56 Merge branch 'APIParkLab:main' into main 2024-08-26 17:16:00 +08:00
HaoZhen Liu 4a523c611d Improve translation 2024-08-26 17:05:26 +08:00
HaoZhen Liu 438d0a7fa2 Improve translation 2024-08-26 16:17:57 +08:00
HaoZhen Liu 045a8abcfb Improve translation 2024-08-26 16:14:11 +08:00
HaoZhen Liu f5a141daf0 Improve translation 2024-08-26 16:12:40 +08:00
HaoZhen Liu d77025cb9e Improve translation 2024-08-26 16:07:07 +08:00
rolea liu 167d2e5c1f Merge pull request #17 from rolealiu/main
Improve translation
2024-08-26 15:39:38 +08:00
HaoZhen Liu 36e72169b8 Merge remote-tracking branch 'upstream/main' 2024-08-26 15:37:26 +08:00
HaoZhen Liu 0d67eab7f7 Improve translation 2024-08-26 15:19:45 +08:00
rolea liu c7bfbe6b9f Merge pull request #16 from rolealiu/main
Improve translation
2024-08-26 15:09:43 +08:00
HaoZhen Liu 577f846545 Improve translation 2024-08-26 15:09:03 +08:00
maggieyyy 05f6071119 Merge pull request #15 from maggieyyy/main
fix:i18next
2024-08-26 14:44:21 +08:00
maggieyyy b57c80a5ae fix:i18next 2024-08-26 14:42:34 +08:00
Liujian f3a00814a5 新增检测是否开启监控接口 2024-08-26 11:13:11 +08:00
Dot.L d40e88c945 Merge pull request #13 from Dot-Liu/main
监控后端完成
2024-08-23 19:51:38 +08:00
Liujian 7671bb3a8c Merge remote-tracking branch 'github-pro/main' into main-github 2024-08-23 19:49:28 +08:00
maggieyyy 6bc93f8176 Merge pull request #12 from maggieyyy/main
fix:login with language setting
2024-08-23 19:45:53 +08:00
maggieyyy c5cee7c6de fix:login with language setting 2024-08-23 19:44:14 +08:00
maggieyyy 118b847995 Merge pull request #11 from maggieyyy/main
fix:i18next
2024-08-23 19:19:30 +08:00
maggieyyy b5a701d560 Merge branch 'main' of github.com:maggieyyy/APIPark 2024-08-23 19:17:38 +08:00
maggieyyy b84910f7d5 fix:i18next 2024-08-23 19:16:21 +08:00
Liujian 92230b6dda Merge branch 'main' into main-github 2024-08-23 19:14:37 +08:00
Liujian 1d2edf8ca7 Merge remote-tracking branch 'github-pro/main' into main-github 2024-08-23 18:59:49 +08:00
Liujian 8012ac42fe 修改依赖 2024-08-23 18:58:13 +08:00
maggieyyy 16d401ffa6 Merge pull request #10 from maggieyyy/main
fix:i18next
2024-08-23 18:51:48 +08:00
maggieyyy 75569097b2 merge 2024-08-23 18:18:45 +08:00
maggieyyy 61c0f94538 fix: i18n bugs 2024-08-23 18:16:48 +08:00
rolea liu 9378188b59 Merge pull request #9 from rolealiu/main
Translate chinese to english
2024-08-23 16:36:51 +08:00
HaoZhen Liu fa49bf7fc0 translate chinese to english 2024-08-23 16:33:26 +08:00
HaoZhen Liu ee5edd1abc translate chinese to english 2024-08-23 16:32:42 +08:00
Dot.L 1f43f1ad66 Merge pull request #8 from maggieyyy/main
feat: guide
2024-08-23 15:43:23 +08:00
maggieyyy d7d12d7ac0 Merge branch 'main' of github.com:maggieyyy/APIPark 2024-08-23 15:41:41 +08:00
maggieyyy 26493be5ee feat:guide 2024-08-23 15:41:26 +08:00
Dot.L 7e399cc181 Merge pull request #7 from maggieyyy/main
fix:access &  monitor style
2024-08-23 10:41:48 +08:00
maggieyyy c1a1d16ca8 fix:access & monitor style 2024-08-22 18:30:20 +08:00
Liujian d6d0c9f914 修复监控获取数据失败问题 2024-08-22 18:08:18 +08:00
Liujian 9ece54b099 监控接口完成 2024-08-22 11:24:41 +08:00
Liujian 09130df02d 修改go-common和ap-account依赖 2024-08-22 09:37:30 +08:00
Liujian 428cbf4781 监控部分迁移 2024-08-21 23:42:15 +08:00
Dot.L 1be6f4e814 Merge pull request #6 from maggieyyy/main
dashboard && tour && guest_login
2024-08-21 18:39:45 +08:00
maggieyyy 11df2331cd Merge branch 'APIParkLab:main' into main 2024-08-21 18:11:26 +08:00
maggieyyy 5ebda41e96 feat:dashboard && tour && guest_login 2024-08-21 18:09:11 +08:00
Dot.L ec1eeb6368 Merge pull request #5 from maggieyyy/main
feat:多语言
2024-08-20 18:51:06 +08:00
maggieyyy 49efc0e4df feat:多语言 2024-08-20 18:49:14 +08:00
Liujian 49ef8d54db Merge remote-tracking branch 'github/main' 2024-08-20 14:32:59 +08:00
Liujian 276b88274f 七牛云登录 2024-08-20 11:38:01 +08:00
Liujian 5c239a2c53 新增登录操作 2024-08-20 11:23:17 +08:00
Liujian b6161813cb github action新增变量QINIU_BUCKET 2024-08-20 11:02:45 +08:00
Liujian eeeb9a3815 修改qshell版本号为2.9.0 2024-08-20 10:55:18 +08:00
Liujian 924e6c8a2e 修改七牛云脚本命令 2024-08-20 10:33:24 +08:00
Liujian b4d2071ba7 修改七牛云上传依赖 2024-08-20 10:23:11 +08:00
Liujian 3c7c6e6c41 修改github action脚本 2024-08-20 10:05:54 +08:00
Liujian 6c4704de1a 新增上传到七牛云操作 2024-08-20 10:01:51 +08:00
343 changed files with 13560 additions and 7464 deletions
+12 -2
View File
@@ -81,6 +81,16 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: build
run: cd scripts && ./docker_publish.sh ${{ secrets.DOCKER_USERNAME }} "backend"
- name: Setup qshell
uses: foxundermoon/setup-qshell@v5
with:
version: '2.9.0'
- name: build
env:
AccessKey: ${{ secrets.AK }}
SecretKey: ${{ secrets.SK }}
QINIU_BUCKET: ${{ secrets.QINIU_BUCKET }}
QINIU_NAME: ${{ secrets.QINIU_NAME }}
run: cd scripts && ./docker_publish.sh ${{ secrets.DOCKER_USERNAME }} "backend" "upload_qiniu"
+1
View File
@@ -3,3 +3,4 @@
/config.yml
/build/
/apipark
/aoplatform
+104 -99
View File
@@ -1,156 +1,161 @@
![image](https://github.com/user-attachments/assets/96e36db5-2733-49c8-8e1e-ecbcc60a3943)
![image]( https://github.com/user-attachments/assets/96e36db5-2733-49c8-8e1e-ecbcc60a3943 )
<p align="center">
English
|
<a href="/readme/readme-zh-cn.md">简体中文</a>
</p>
APIPark 是全球首个开源企业级 API 开放平台,帮助组织快速构建企业内部 API 门户/市场,享受极致的转发性能、API 可观测性、服务治理、多租户管理、订阅审批流程等诸多好处。
APIPark is the world's first open-source enterprise API open platform, helping organizations quickly build internal API portals/marketplaces and enjoy ultimate forwarding performance, API observability, service governance, multi tenant management, subscription approval processes, and many other benefits.
<br>
# ✨ 快速开始
APIPark 致力于为全球企业提供一站式 API 开放与接入产品,打造新一代 API 资产治理标准。APIPark 使用 Apache 2.0 协议开源。
# ✨ Quick Start
APIPark is committed to providing one-stop API open and access products for global enterprises, and creating a new generation of API asset governance standards. APIPark is open sourced using the Apache 2.0 protocol.
APIPark 致力于解决企业在 API 管理中面临的几大关键挑战:
- 复杂的 API 调用关系:简化了复杂系统架构中的 API 交互。
- 数据使用跟踪:提供全面的 API 使用监控和报告。
- 合规管理:确保 API 符合组织和法规标准。
- 故障检测和排查:简化系统问题的识别和解决。
- 量化数据资产价值:提升数据资产的可见性和估值。
APIPark is committed to addressing several key challenges that enterprises face in API management:
- Complex API call relationships: Simplifies API interactions in complex system architectures.
- Data usage tracking: Provides comprehensive API usage monitoring and reporting.
- Compliance management: Ensure that APIs comply with organizational and regulatory standards.
- Fault detection and troubleshooting: Simplify the identification and resolution of system issues.
- Quantifying the Value of Data Assets: Enhancing the Visibility and Valuation of Data Assets.
<br>
😍APIPark 部署非常简单,仅需一句命令行即可在 5 分钟内部署好你的 API 资产开放平台。
😍 APIPark deployment is very simple, just one command line can deploy your API open platform in 5 minutes.
```
curl -sSO https://download.apipark.com/install/quick-start.sh; bash quick-start.sh
curl -sSO https://download.apipark.com/install/quick-start.sh ; bash quick-start.sh
```
<br>
# 🔥 特性
# 🔥 characteristic
<table>
<tr>
<th>
集中管理与展示企业内部的所有 API 服务
</th>
<th>
覆盖 API 从设计、发布、运行、下线的全过程
</th>
<tr>
<th>
Centralize management and display of all API services within the enterprise
</th>
<th>
Covering the entire process of API design, release, operation, and deployment
</th>
</tr>
</tr>
<tr>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/%E9%A1%B5%E9%9D%A2-1.png" />
API 服务广场是 APIPark 的核心功能之一,旨在解决企业内部 API 分散、管理混乱的问题。通过 API 服务广场,企业可以将所有的 API 服务集中展示在一个统一的平台上,使得不同部门和团队能够轻松找到并使用所需的 API 服务。
</td>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Life-Cycle.png" />
API 全生命周期管理功能帮助企业规范 API 的管理流程,管理 API 的流量转发和负载均衡,并管理所有 API 对外发布的版本。提升 API 的质量和可维护性。通过这个功能,企业可以实现 API 的高效开发和稳定运营,从而支持业务的快速发展和创新。
</td>
</tr>
<tr>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/%E9%A1%B5%E9%9D%A2-1.png" />
API Service Square is one of the core functions of APIPark, aimed at solving the problem of scattered and chaotic management of APIs within enterprises. Through API Service Square, enterprises can display all API services on a unified platform, making it easy for different departments and teams to find and use the required API services.
</td>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Life-Cycle.png" />
The API lifecycle management function helps enterprises standardize the API management process, manage API traffic forwarding and load balancing, and manage all API versions released to the public. Improve the quality and maintainability of APIs. Through this feature, enterprises can achieve efficient API development and stable operation, thereby supporting rapid business development and innovation.
</td>
</tr>
<tr>
<th>
管理多个租户,确保数据隔离和安全
</th>
<th>
API资源需要先申请并等待审核通过才能调用
</th>
<th>
Manage multiple tenants to ensure data isolation and security
</th>
<th>
API resources need to be applied for and approved before they can be called
</th>
</tr>
<tr>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Multi-tenant.png" />
多租户管理功能为企业提供了在同一平台上管理多个租户的能力,每个租户可以拥有独立的资源、用户和权限设置,确保数据和操作的隔离,帮助提升资源利用效率和管理便捷性。
</td>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Application.png" />
APIPark 对所有 API 资源提供流程审批功能,避免违规或避开平台来调用API,调用方需要先申请API资源,并等待服务方审核通过后才能正式调用API。
</td>
</tr>
</tr>
<tr>
<th>
通过详细的调用日志,帮助排查API在任意时刻的访问情况
</th>
<th>
丰富的统计报表*
</th>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Multi-tenant.png" />
The multi tenant management function provides enterprises with the ability to manage multiple tenants on the same platform. Each tenant can have independent resource, user, and permission settings, ensuring the isolation of data and operations, and helping to improve resource utilization efficiency and management convenience.
</td>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Application.png" />
APIPark provides a process approval function for all API resources to avoid violating regulations or bypassing the platform to call APIs. The caller needs to first apply for API resources and wait for the service provider's approval before officially calling the API.
</td>
</tr>
</tr>
<tr>
<th>
Assist in troubleshooting API access at any given time through detailed call logs
</th>
<th>
Rich statistical reports*
</th>
<tr>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Chart-1.png" />
API 调用日志功能为企业提供了全面的日志记录能力,详细记录每一次 API 调用的所有相关信息。通过这些日志,企业可以快速追踪和排查 API 调用中的问题,确保系统的稳定运行和数据安全。
</td>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Chart.png" />
通过对历史调用数据的分析,APIPark 能够展示 API 长期的调用趋势和性能变化,帮助企业在问题发生前进行预防性维护。
</td>
</tr>
</tr>
<tr>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Chart-1.png" />
The API call log function provides comprehensive logging capabilities for enterprises, detailing all relevant information of each API call. Through these logs, enterprises can quickly track and troubleshoot issues in API calls, ensuring stable system operation and data security.
</td>
<td width="50%">
<img src="https://apipark.com/wp-content/uploads/2024/08/Chart.png" />
By analyzing historical call data, APIPark can display the long-term call trends and performance changes of APIs, helping enterprises to conduct preventive maintenance before problems occur.
</td>
</tr>
</table>
<br>
# 🚩适用场景
## 提升运营效率
- 快速构建内部 API 门户。
- 高效管理和调用 API。
- 减少复杂的系统间调用关系。
# 🚩 Applicable scenarios
## Improve operational efficiency
- Quickly build an internal API portal.
- Efficient management and API calling.
- Reduce complex inter system call relationships.
## 确保合规与安全
- 强大的服务治理和合规管理功能。
- 精细化管理应用调用的权限。
- 确保 API 调用的安全性和合规性,降低企业风险。
## Ensure compliance and safety
- Powerful service governance and compliance management capabilities.
- Refine the management of application call permissions.
- Ensure the security and compliance of API calls to reduce enterprise risks.
## 简化系统故障排查
- 利用监控和故障诊断工具快速发现和解决问题。
- 减少停机时间,提高系统稳定性。
## Simplify system troubleshooting
- Utilize monitoring and fault diagnosis tools to quickly identify and solve problems.
- Reduce downtime and improve system stability.
## 多租户管理和灵活订阅
- 支持多租户管理,满足不同业务单元需求。
- 灵活的订阅和审批流程简化 API 的使用和管理。
## Multi tenant management and flexible subscriptions
- Support multi tenant management to meet the needs of different business units.
- Flexible subscription and approval processes simplify the use and management of APIs.
## 增强 API 可观测性
- 实时监控和追踪 API 使用情况。
- 全面掌握数据流动,提升数据使用透明度。
## Enhance API observability
- Real time monitoring and tracking of API usage.
- Fully grasp the flow of data and enhance the transparency of data usage.
## 提升数据资产价值
- 量化和分析 API 使用情况,更好地评估和提升数据资产价值。
- 为决策提供数据支持。
## Enhance the value of data assets
- Quantify and analyze API usage to better evaluate and enhance the value of data assets.
- Provide data support for decision-making.
<br>
# 🚀一键部署
APIPark 部署非常简单,仅需一句命令行即可在 5 分钟内部署好你的 API 资产开放平台。
# 🚀 One click deployment
APIPark deployment is very simple, just one command line can deploy your API asset open platform in 5 minutes.
```
curl -sSO https://download.apipark.com/install/quick-start.sh; bash quick-start.sh
curl -sSO https://download.apipark.com/install/quick-start.sh ; bash quick-start.sh
```
<br>
## 📕文档
访问 [APIPark文档](https://docs.apipark.com/docs/install) 获取详细的安装指南、API 参考和使用说明。
## 📕 file
Visit [APIPark Document] https://docs.apipark.com/docs/install Get detailed installation guides, API references, and usage instructions.
<br>
## 🧾许可证
APIPark 使用 Apache 2.0 许可证。更多详情请查看 LICENSE 文件。
## 🧾 licence
APIPark uses the Apache 2.0 license. For more details, please refer to the LICENSE document.
<br>
## 💌联系我们
对于企业级功能和专业技术支持,请联系售前专家进行个性化演示、定制方案和获取报价。
## 💌 contact us
For enterprise level functionality and professional technical support, please contact pre-sales experts for personalized demonstrations, customized solutions, and pricing.
- 网站: https://apipark.com
- 电子邮件: dev@apipark.com
- Website: https://apipark.com
- Email: dev@apipark.com
<br>
感谢您选择 APIPark,下一代 API 资产治理平台。
Thank you for choosing APIPark, the next-generation API Open platform.
-43
View File
@@ -1,43 +0,0 @@
package api
import (
"reflect"
"github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire"
api_dto "github.com/APIParkLab/APIPark/module/api/dto"
)
type IAPIController interface {
// Detail 获取API详情
Detail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDetail, error)
// SimpleDetail 获取API简要详情
SimpleDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiSimpleDetail, error)
// Search 获取API列表
Search(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiItem, error)
// SimpleSearch 获取API简要列表
SimpleSearch(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiSimpleItem, error)
//SimpleList(ctx *gin.Context, serviceId string) ([]*api_dto.ApiSimpleItem, error)
// Create 创建API
Create(ctx *gin.Context, serviceId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error)
// Edit 编辑API
Edit(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.EditApi) (*api_dto.ApiSimpleDetail, error)
// Delete 删除API
Delete(ctx *gin.Context, serviceId string, apiId string) error
// Copy 复制API
Copy(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error)
// ApiDocDetail 获取API文档详情
ApiDocDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDocDetail, error)
// ApiProxyDetail 获取API代理详情
ApiProxyDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiProxyDetail, error)
// Prefix 获取API前缀
Prefix(ctx *gin.Context, serviceId string) (string, bool, error)
}
func init() {
autowire.Auto[IAPIController](func() reflect.Value {
return reflect.ValueOf(new(imlAPIController))
})
}
-65
View File
@@ -1,65 +0,0 @@
package api
import (
"github.com/APIParkLab/APIPark/module/api"
api_dto "github.com/APIParkLab/APIPark/module/api/dto"
"github.com/gin-gonic/gin"
)
var _ IAPIController = (*imlAPIController)(nil)
type imlAPIController struct {
module api.IApiModule `autowired:""`
}
//func (i *imlAPIController) SimpleList(ctx *gin.Context, serviceId string) ([]*api_dto.ApiSimpleItem, error) {
// return i.module.SimpleList(ctx, serviceId)
//}
func (i *imlAPIController) Detail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDetail, error) {
return i.module.Detail(ctx, serviceId, apiId)
}
func (i *imlAPIController) SimpleDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiSimpleDetail, error) {
return i.module.SimpleDetail(ctx, serviceId, apiId)
}
func (i *imlAPIController) Search(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiItem, error) {
return i.module.Search(ctx, keyword, serviceId)
}
func (i *imlAPIController) SimpleSearch(ctx *gin.Context, keyword string, serviceId string) ([]*api_dto.ApiSimpleItem, error) {
return i.module.SimpleSearch(ctx, keyword, serviceId)
}
func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error) {
return i.module.Create(ctx, serviceId, dto)
}
func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.EditApi) (*api_dto.ApiSimpleDetail, error) {
return i.module.Edit(ctx, serviceId, apiId, dto)
}
func (i *imlAPIController) Delete(ctx *gin.Context, serviceId string, apiId string) error {
return i.module.Delete(ctx, serviceId, apiId)
}
func (i *imlAPIController) Copy(ctx *gin.Context, serviceId string, apiId string, dto *api_dto.CreateApi) (*api_dto.ApiSimpleDetail, error) {
return i.module.Copy(ctx, serviceId, apiId, dto)
}
func (i *imlAPIController) ApiDocDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiDocDetail, error) {
return i.module.ApiDocDetail(ctx, serviceId, apiId)
}
func (i *imlAPIController) ApiProxyDetail(ctx *gin.Context, serviceId string, apiId string) (*api_dto.ApiProxyDetail, error) {
return i.module.ApiProxyDetail(ctx, serviceId, apiId)
}
func (i *imlAPIController) Prefix(ctx *gin.Context, serviceId string) (string, bool, error) {
prefix, err := i.module.Prefix(ctx, serviceId)
if err != nil {
return "", false, err
}
return prefix, true, nil
}
+81
View File
@@ -0,0 +1,81 @@
package monitor
import (
"fmt"
"time"
"github.com/APIParkLab/APIPark/module/monitor"
monitor_dto "github.com/APIParkLab/APIPark/module/monitor/dto"
"github.com/gin-gonic/gin"
)
var (
_ IMonitorStatisticController = (*imlMonitorStatisticController)(nil)
)
type imlMonitorStatisticController struct {
module monitor.IMonitorStatisticModule `autowired:""`
}
func (i *imlMonitorStatisticController) OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error) {
trend, timeInterval, err := i.module.MessageTrend(ctx, input)
if err != nil {
return nil, nil, nil, "", err
}
return trend.Dates, trend.ReqMessage, trend.RespMessage, timeInterval, nil
}
func (i *imlMonitorStatisticController) OverviewInvokeTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []int64, []int64, []int64, []int64, []float64, []float64, string, error) {
trend, timeInterval, err := i.module.InvokeTrend(ctx, input)
if err != nil {
return nil, nil, nil, nil, nil, nil, nil, "", err
}
return trend.Date, trend.RequestTotal, trend.ProxyTotal, trend.Status4XX, trend.Status5XX, trend.RequestRate, trend.ProxyRate, timeInterval, nil
}
func (i *imlMonitorStatisticController) Summary(ctx *gin.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonSummaryOutput, *monitor_dto.MonSummaryOutput, error) {
requestSummary, err := i.module.RequestSummary(ctx, input)
if err != nil {
return nil, nil, err
}
proxySummary, err := i.module.ProxySummary(ctx, input)
if err != nil {
return nil, nil, err
}
return requestSummary, proxySummary, nil
}
func (i *imlMonitorStatisticController) Top10(ctx *gin.Context, input *monitor_dto.Top10Input) (interface{}, error) {
switch input.DataType {
case monitor_dto.DataTypeApi:
return i.module.TopAPIStatistics(ctx, 10, input.CommonInput)
case monitor_dto.DataTypeProvider:
return i.module.TopProviderStatistics(ctx, 10, input.CommonInput)
case monitor_dto.DataTypeSubscriber:
return i.module.TopSubscriberStatistics(ctx, 10, input.CommonInput)
default:
return nil, fmt.Errorf("unsupported data type: %s", input.DataType)
}
}
var (
_ IMonitorConfigController = (*imlMonitorConfig)(nil)
)
type imlMonitorConfig struct {
module monitor.IMonitorConfigModule `autowired:""`
}
func (p *imlMonitorConfig) SaveMonitorConfig(ctx *gin.Context, cfg *monitor_dto.SaveMonitorConfig) (*monitor_dto.MonitorConfig, error) {
return p.module.SaveMonitorConfig(ctx, cfg)
}
func (p *imlMonitorConfig) GetMonitorConfig(ctx *gin.Context) (*monitor_dto.MonitorConfig, error) {
return p.module.GetMonitorConfig(ctx)
}
func (p *imlMonitorConfig) GetMonitorCluster(ctx *gin.Context) ([]*monitor_dto.MonitorCluster, error) {
return p.module.GetMonitorCluster(ctx)
}
+36
View File
@@ -0,0 +1,36 @@
package monitor
import (
"reflect"
"time"
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
monitor_dto "github.com/APIParkLab/APIPark/module/monitor/dto"
)
type IMonitorStatisticController interface {
Top10(ctx *gin.Context, input *monitor_dto.Top10Input) (interface{}, error)
Summary(ctx *gin.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonSummaryOutput, *monitor_dto.MonSummaryOutput, error)
OverviewInvokeTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []int64, []int64, []int64, []int64, []float64, []float64, string, error)
OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error)
//Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error)
}
type IMonitorConfigController interface {
SaveMonitorConfig(ctx *gin.Context, cfg *monitor_dto.SaveMonitorConfig) (*monitor_dto.MonitorConfig, error)
GetMonitorConfig(ctx *gin.Context) (*monitor_dto.MonitorConfig, error)
GetMonitorCluster(ctx *gin.Context) ([]*monitor_dto.MonitorCluster, error)
}
func init() {
autowire.Auto[IMonitorStatisticController](func() reflect.Value {
return reflect.ValueOf(new(imlMonitorStatisticController))
})
autowire.Auto[IMonitorConfigController](func() reflect.Value {
return reflect.ValueOf(new(imlMonitorConfig))
})
}
+7 -7
View File
@@ -2,7 +2,7 @@ package publish
import (
"strconv"
"github.com/APIParkLab/APIPark/module/publish"
"github.com/APIParkLab/APIPark/module/publish/dto"
"github.com/APIParkLab/APIPark/module/release"
@@ -39,11 +39,11 @@ func (c *imlPublishController) ReleaseDo(ctx *gin.Context, serviceId string, inp
c.releaseModule.Delete(ctx, serviceId, newReleaseId)
return nil, err
}
err = c.publishModule.Publish(ctx, serviceId, apply.Id)
if err != nil {
c.releaseModule.Delete(ctx, serviceId, newReleaseId)
return nil, err
}
//err = c.publishModule.Publish(ctx, serviceId, apply.Id)
//if err != nil {
// c.releaseModule.Delete(ctx, serviceId, newReleaseId)
// return nil, err
//}
err = c.publishModule.Publish(ctx, serviceId, apply.Id)
if err != nil {
c.releaseModule.Delete(ctx, serviceId, newReleaseId)
@@ -123,7 +123,7 @@ func (c *imlPublishController) ListPage(ctx *gin.Context, serviceId string, page
if err != nil {
return nil, 0, 0, 0, err
}
return list, pageNum, pageSizeNum, total, nil
}
+79
View File
@@ -0,0 +1,79 @@
package router
import (
api_doc "github.com/APIParkLab/APIPark/module/api-doc"
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"github.com/APIParkLab/APIPark/module/router"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
"github.com/gin-gonic/gin"
"io"
)
var _ IRouterController = (*imlAPIController)(nil)
type imlAPIController struct {
module router.IRouterModule `autowired:""`
}
func (i *imlAPIController) Detail(ctx *gin.Context, serviceId string, apiId string) (*router_dto.Detail, error) {
return i.module.Detail(ctx, serviceId, apiId)
}
func (i *imlAPIController) Search(ctx *gin.Context, keyword string, serviceId string) ([]*router_dto.Item, error) {
return i.module.Search(ctx, keyword, serviceId)
}
func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, dto *router_dto.Create) (*router_dto.SimpleDetail, error) {
return i.module.Create(ctx, serviceId, dto)
}
func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, dto *router_dto.Edit) (*router_dto.SimpleDetail, error) {
return i.module.Edit(ctx, serviceId, apiId, dto)
}
func (i *imlAPIController) Delete(ctx *gin.Context, serviceId string, apiId string) error {
return i.module.Delete(ctx, serviceId, apiId)
}
func (i *imlAPIController) Prefix(ctx *gin.Context, serviceId string) (string, bool, error) {
prefix, err := i.module.Prefix(ctx, serviceId)
if err != nil {
return "", false, err
}
return prefix, true, nil
}
var _ IAPIDocController = (*imlAPIDocController)(nil)
type imlAPIDocController struct {
module api_doc.IAPIDocModule `autowired:""`
}
func (i *imlAPIDocController) UpdateDoc(ctx *gin.Context, serviceId string, input *api_doc_dto.UpdateDoc) (*api_doc_dto.ApiDocDetail, error) {
return i.module.UpdateDoc(ctx, serviceId, input)
}
func (i *imlAPIDocController) GetDoc(ctx *gin.Context, serviceId string) (*api_doc_dto.ApiDocDetail, error) {
return i.module.GetDoc(ctx, serviceId)
}
func (i *imlAPIDocController) UploadDoc(ctx *gin.Context, serviceId string) (*api_doc_dto.ApiDocDetail, error) {
// 获取文件内容
fileHeader, err := ctx.FormFile("doc")
if err != nil {
return nil, err
}
file, err := fileHeader.Open()
if err != nil {
return nil, err
}
content, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return i.module.UpdateDoc(ctx, serviceId, &api_doc_dto.UpdateDoc{
Content: string(content),
})
}
+46
View File
@@ -0,0 +1,46 @@
package router
import (
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
"reflect"
"github.com/gin-gonic/gin"
"github.com/eolinker/go-common/autowire"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
)
type IRouterController interface {
// Detail 获取API详情
Detail(ctx *gin.Context, serviceId string, apiId string) (*router_dto.Detail, error)
// Search 获取API列表
Search(ctx *gin.Context, keyword string, serviceId string) ([]*router_dto.Item, error)
// Create 创建API
Create(ctx *gin.Context, serviceId string, dto *router_dto.Create) (*router_dto.SimpleDetail, error)
// Edit 编辑API
Edit(ctx *gin.Context, serviceId string, apiId string, dto *router_dto.Edit) (*router_dto.SimpleDetail, error)
// Delete 删除API
Delete(ctx *gin.Context, serviceId string, apiId string) error
// Prefix 获取API前缀
Prefix(ctx *gin.Context, serviceId string) (string, bool, error)
}
type IAPIDocController interface {
// UpdateDoc 更新API文档
UpdateDoc(ctx *gin.Context, serviceId string, input *api_doc_dto.UpdateDoc) (*api_doc_dto.ApiDocDetail, error)
// GetDoc 获取API文档
GetDoc(ctx *gin.Context, serviceId string) (*api_doc_dto.ApiDocDetail, error)
UploadDoc(ctx *gin.Context, serviceId string) (*api_doc_dto.ApiDocDetail, error)
}
func init() {
autowire.Auto[IRouterController](func() reflect.Value {
return reflect.ValueOf(new(imlAPIController))
})
autowire.Auto[IAPIDocController](func() reflect.Value {
return reflect.ValueOf(new(imlAPIDocController))
})
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
[{"id":"358caa9f-a079-44dd-8b28-c1d065777b6c","name":"Demo App","description":"Create the App to apply services","team":"eef18cfb-140b-4e78-b9eb-ad669d898110"}]
+1
View File
@@ -0,0 +1 @@
[{"service":"2d8beb9c-121f-4f6c-b860-50c02520e6f3","application":"358caa9f-a079-44dd-8b28-c1d065777b6c","reason":""}]
@@ -0,0 +1 @@
[{"application":"358caa9f-a079-44dd-8b28-c1d065777b6c","id":"9179f789-869b-40a5-8e45-d0c85c640f29","name":"Demo Auth Key","driver":"apikey","position":"Header","token_name":"Authorization","config":{"apikey":"da66c011-39ba-44eb-a318-3f1407644543","label":null},"expire_time":0,"hide_credential":false}]
+1
View File
@@ -0,0 +1 @@
[{"id":"16793ef0-cdb7-4c80-84eb-9a1a0c02113b","name":"AI","parent":"","sort":0},{"id":"542f3032-2b33-417a-88ee-44dbde7eec68","name":"Cloud Service","parent":"","sort":0},{"id":"6887358d-bcf5-4ebf-b945-6f882c2cde10","name":"Data Analysis","parent":"","sort":0},{"id":"706b75cf-855c-4940-9ce6-05055c95c581","name":"Development","parent":"","sort":0},{"id":"29417527-0245-4db4-8cd8-a9a02deeeda3","name":"IoT","parent":"","sort":0}]
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
[{"id":"13a1d172-6e67-4581-a95b-61ea4c54ebbb","service":"2d8beb9c-121f-4f6c-b860-50c02520e6f3","subscriber":"358caa9f-a079-44dd-8b28-c1d065777b6c","team":"","applier":"","from":1}]
+1
View File
@@ -0,0 +1 @@
[{"id":"eef18cfb-140b-4e78-b9eb-ad669d898110","name":"Demo Team","description":""}]
+1
View File
@@ -0,0 +1 @@
[{"id":"2d8beb9c-121f-4f6c-b860-50c02520e6f3","name":"","service":"2d8beb9c-121f-4f6c-b860-50c02520e6f3","driver":"static","balance":"round-robin","timeout":3,"retry":3,"remark":"","limit_peer_second":10000,"proxy_headers":[],"scheme":"HTTPS","pass_host":"pass","upstream_host":"","nodes":[{"address":"demoapi.apipark.com","weight":999}],"discover":{"discover":"","service":""}}]
+167
View File
@@ -0,0 +1,167 @@
package system
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
"github.com/APIParkLab/APIPark/module/catalogue"
"github.com/APIParkLab/APIPark/module/router"
"github.com/APIParkLab/APIPark/module/service"
"github.com/APIParkLab/APIPark/module/subscribe"
"github.com/APIParkLab/APIPark/module/team"
"github.com/APIParkLab/APIPark/module/upstream"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
var _ IExportConfigController = (*imlExportConfigController)(nil)
type imlExportConfigController struct {
teamModule team.ITeamExportModule `autowired:""`
serviceModule service.IExportServiceModule `autowired:""`
appModule service.IExportAppModule `autowired:""`
routerModule router.IExportRouterModule `autowired:""`
upstreamModule upstream.IExportUpstreamModule `autowired:""`
applicationAuthorizationModule application_authorization.IExportAuthorizationModule `autowired:""`
catalogueModule catalogue.IExportCatalogueModule `autowired:""`
subscribeModule subscribe.IExportSubscribeModule `autowired:""`
applyModule subscribe.IExportSubscribeApprovalModule `autowired:""`
}
type ExportFile struct {
Driver string `json:"driver"`
Data interface{} `json:"data"`
}
func (e *ExportFile) Byte() []byte {
b, _ := json.Marshal(e.Data)
return b
}
func zipFile(files []*ExportFile) (*bytes.Buffer, error) {
// 创建一个缓冲区用于存储 ZIP 文件内容
buf := new(bytes.Buffer)
zipWriter := zip.NewWriter(buf)
now := time.Now()
// 将文件写入 ZIP
for _, file := range files {
header := &zip.FileHeader{
Name: fmt.Sprintf("%s.json", file.Driver),
Method: zip.Deflate,
Modified: now,
}
f, err := zipWriter.CreateHeader(header)
if err != nil {
return nil, fmt.Errorf("failed to create zip file: %v", err)
}
_, err = f.Write(file.Byte())
if err != nil {
return nil, fmt.Errorf("failed to write to zip file: %v", err)
}
}
// 关闭 ZIP writer,完成压缩过程
err := zipWriter.Close()
if err != nil {
return nil, fmt.Errorf("failed to close zip writer: %v", err)
}
return buf, nil
}
func (i *imlExportConfigController) ExportAll(ctx *gin.Context) error {
files, err := i.appendFiles(ctx)
if err != nil {
return err
}
buf, err := zipFile(files)
if err != nil {
}
ctx.DataFromReader(http.StatusOK, int64(buf.Len()), "application/zip", buf, map[string]string{
"Content-Disposition": "attachment; filename=\"export.zip\"",
})
return nil
}
func (i *imlExportConfigController) appendFiles(ctx *gin.Context) ([]*ExportFile, error) {
type exportConfig struct {
exportFunc func(ctx *gin.Context) (interface{}, error)
driver string
}
exports := []exportConfig{
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.teamModule.ExportAll(ctx)
},
driver: "team",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.serviceModule.ExportAll(ctx)
},
driver: "service",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.appModule.ExportAll(ctx)
},
driver: "app",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.routerModule.ExportAll(ctx)
},
driver: "api",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.upstreamModule.ExportAll(ctx)
},
driver: "upstream",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.applicationAuthorizationModule.ExportAll(ctx)
},
driver: "authorization",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.catalogueModule.ExportAll(ctx)
},
driver: "catalogue",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.subscribeModule.ExportAll(ctx)
},
driver: "subscribe",
},
{
exportFunc: func(ctx *gin.Context) (interface{}, error) {
return i.applyModule.ExportAll(ctx)
},
driver: "apply",
},
}
files := make([]*ExportFile, 0, len(exports))
for _, config := range exports {
data, err := config.exportFunc(ctx)
if err != nil {
return nil, fmt.Errorf("[%s] failed to export data: %v", config.driver, err)
}
files = append(files, &ExportFile{
Driver: config.driver,
Data: data,
})
}
return files, nil
}
+454
View File
@@ -0,0 +1,454 @@
package system
import (
"context"
"embed"
"encoding/json"
"errors"
"fmt"
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
"github.com/APIParkLab/APIPark/module/catalogue"
catalogue_dto "github.com/APIParkLab/APIPark/module/catalogue/dto"
"github.com/APIParkLab/APIPark/module/publish"
"github.com/APIParkLab/APIPark/module/publish/dto"
"github.com/APIParkLab/APIPark/module/release"
dto2 "github.com/APIParkLab/APIPark/module/release/dto"
"github.com/APIParkLab/APIPark/module/router"
router_dto "github.com/APIParkLab/APIPark/module/router/dto"
"github.com/APIParkLab/APIPark/module/service"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/APIParkLab/APIPark/module/subscribe"
subscribe_dto "github.com/APIParkLab/APIPark/module/subscribe/dto"
"github.com/APIParkLab/APIPark/module/team"
team_dto "github.com/APIParkLab/APIPark/module/team/dto"
"github.com/APIParkLab/APIPark/module/upstream"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/eolinker/go-common/store"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
var (
//go:embed config/*.json
importConfigs embed.FS
)
func unmarshal[T any](name string) ([]*T, error) {
data, err := importConfigs.ReadFile(fmt.Sprintf("config/%s.json", name))
if err != nil {
return nil, fmt.Errorf("fail to read file(%s): %v", name, err)
}
t := make([]*T, 0)
err = json.Unmarshal(data, &t)
return t, err
}
var (
_ IImportConfigController = (*imlImportConfigController)(nil)
)
type imlImportConfigController struct {
teamModule team.ITeamModule `autowired:""`
serviceModule service.IServiceModule `autowired:""`
appModule service.IAppModule `autowired:""`
apiModule router.IRouterModule `autowired:""`
upstreamModule upstream.IUpstreamModule `autowired:""`
applicationAuthorizationModule application_authorization.IAuthorizationModule `autowired:""`
catalogueModule catalogue.ICatalogueModule `autowired:""`
subscribeModule subscribe.ISubscribeModule `autowired:""`
applyModule subscribe.ISubscribeApprovalModule `autowired:""`
publishModule publish.IPublishModule `autowired:""`
releaseModule release.IReleaseModule `autowired:""`
transaction store.ITransaction `autowired:""`
}
func (i *imlImportConfigController) ImportAll(ctx *gin.Context) error {
return i.transaction.Transaction(ctx, func(transCtx context.Context) error {
err := i.importTeams(transCtx)
if err != nil {
return err
}
err = i.importCatalogues(transCtx)
if err != nil {
return err
}
err = i.importServices(transCtx)
if err != nil {
return err
}
err = i.importApplications(transCtx)
if err != nil {
return err
}
err = i.importApplicationAuth(transCtx)
if err != nil {
return err
}
err = i.importApis(transCtx)
if err != nil {
return err
}
err = i.importUpstreams(transCtx)
if err != nil {
return err
}
err = i.publish(transCtx)
if err != nil {
return err
}
err = i.importSubscribers(transCtx)
if err != nil {
return err
}
return nil
})
}
func (i *imlImportConfigController) importTeams(ctx context.Context) error {
data, err := unmarshal[team_dto.ExportTeam]("team")
if err != nil {
return err
}
for _, d := range data {
// 判断是否存在,如果存在,则更新
_, err = i.teamModule.GetTeam(ctx, d.Id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
_, err = i.teamModule.Create(ctx, &team_dto.CreateTeam{
Id: d.Id,
Name: d.Name,
Description: d.Description,
})
if err != nil {
return fmt.Errorf("create team(%s) error: %v", d.Id, err)
}
continue
}
_, err = i.teamModule.Edit(ctx, d.Id, &team_dto.EditTeam{
Name: &d.Name,
Description: &d.Description,
})
if err != nil {
return fmt.Errorf("update team(%s) error: %v", d.Id, err)
}
}
return nil
}
func (i *imlImportConfigController) importServices(ctx context.Context) error {
data, err := unmarshal[service_dto.ExportService]("service")
if err != nil {
return err
}
for _, d := range data {
// 判断是否存在,如果存在,则更新
_, err = i.serviceModule.Get(ctx, d.Id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
_, err = i.serviceModule.Create(ctx, d.Team, &service_dto.CreateService{
Id: d.Id,
Name: d.Name,
Prefix: d.Prefix,
Description: d.Description,
ServiceType: d.ServiceType,
Logo: d.Logo,
Tags: d.Tags,
Catalogue: d.Catalogue,
})
if err != nil {
return fmt.Errorf("create service(%s) error: %v", d.Id, err)
}
} else {
_, err = i.serviceModule.Edit(ctx, d.Id, &service_dto.EditService{
Name: &d.Name,
Description: &d.Description,
ServiceType: &d.ServiceType,
Catalogue: &d.Catalogue,
Logo: &d.Logo,
Tags: &d.Tags,
})
if err != nil {
return fmt.Errorf("update service(%s) error: %v", d.Id, err)
}
}
err = i.serviceModule.SaveServiceDoc(ctx, d.Id, &service_dto.SaveServiceDoc{Doc: d.Doc})
if err != nil {
return fmt.Errorf("save service(%s) doc error: %v", d.Id, err)
}
}
return nil
}
func (i *imlImportConfigController) importApplications(ctx context.Context) error {
data, err := unmarshal[service_dto.ExportApp]("app")
if err != nil {
return err
}
for _, d := range data {
// 判断是否存在,如果存在,则更新
_, err = i.appModule.GetApp(ctx, d.Id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
_, err = i.appModule.CreateApp(ctx, d.Team, &service_dto.CreateApp{
Id: d.Id,
Name: d.Name,
Description: d.Description,
})
if err != nil {
return fmt.Errorf("create app(%s) error: %v", d.Id, err)
}
continue
}
_, err = i.appModule.UpdateApp(ctx, d.Id, &service_dto.UpdateApp{
Name: &d.Name,
Description: &d.Description,
})
if err != nil {
return fmt.Errorf("update app(%s) error: %v", d.Id, err)
}
}
return nil
}
func (i *imlImportConfigController) importApis(ctx context.Context) error {
data, err := unmarshal[router_dto.Export]("api")
if err != nil {
return err
}
for _, d := range data {
var proxy *router_dto.InputProxy
if d.Proxy != nil {
proxy = &router_dto.InputProxy{
Path: d.Proxy.Path,
Timeout: d.Proxy.Timeout,
Retry: d.Proxy.Retry,
Headers: d.Proxy.Headers,
Extends: d.Proxy.Extends,
Plugins: d.Proxy.Plugins,
}
}
// 判断是否存在,如果存在,则更新
_, err = i.apiModule.Detail(ctx, d.Service, d.Id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
_, err = i.apiModule.Create(ctx, d.Service, &router_dto.Create{
Id: d.Id,
Path: d.Path,
Methods: d.Method,
Description: d.Description,
MatchRules: d.MatchRules,
Proxy: proxy,
})
if err != nil {
return fmt.Errorf("create api(%s) error: %v", d.Id, err)
}
continue
}
info := &router_dto.Edit{
Proxy: proxy,
//Doc: &d.Doc,
}
_, err = i.apiModule.Edit(ctx, d.Service, d.Id, info)
if err != nil {
return fmt.Errorf("update api(%s) error: %v", d.Id, err)
}
}
return nil
}
func (i *imlImportConfigController) importCatalogues(ctx context.Context) error {
data, err := unmarshal[catalogue_dto.ExportCatalogue]("catalogue")
if err != nil {
return err
}
for _, d := range data {
_, err = i.catalogueModule.Get(ctx, d.Id)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
err = i.catalogueModule.Create(ctx, &catalogue_dto.CreateCatalogue{
Id: d.Id,
Name: d.Name,
Parent: &d.Parent,
Sort: &d.Sort,
})
if err != nil {
return fmt.Errorf("create catalogue(%s) error: %v", d.Id, err)
}
continue
}
err = i.catalogueModule.Edit(ctx, d.Id, &catalogue_dto.EditCatalogue{
Name: &d.Name,
Parent: &d.Parent,
Sort: &d.Sort,
})
if err != nil {
return fmt.Errorf("update catalogue(%s) error: %v", d.Id, err)
}
}
return nil
}
func (i *imlImportConfigController) importUpstreams(ctx context.Context) error {
data, err := unmarshal[upstream_dto.ExportUpstream]("upstream")
if err != nil {
return err
}
for _, d := range data {
_, err = i.upstreamModule.Save(ctx, d.Service, d.Upstream)
if err != nil {
return fmt.Errorf("update upstream(%s) error: %v", d.Service, err)
}
}
return nil
}
func (i *imlImportConfigController) importApplicationAuth(ctx context.Context) error {
data, err := unmarshal[application_authorization_dto.ExportAuthorization]("authorization")
if err != nil {
return err
}
for _, d := range data {
_, err := i.applicationAuthorizationModule.Info(ctx, d.Application, d.UUID)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
_, err = i.applicationAuthorizationModule.AddAuthorization(ctx, d.Application, &application_authorization_dto.CreateAuthorization{
UUID: d.UUID,
Name: d.Name,
Driver: d.Driver,
Position: d.Position,
TokenName: d.TokenName,
ExpireTime: d.ExpireTime,
Config: d.Config,
HideCredential: d.HideCredential,
})
if err != nil {
return fmt.Errorf("create authorization(%s) error: %v", d.UUID, err)
}
continue
}
_, err = i.applicationAuthorizationModule.EditAuthorization(ctx, d.Application, d.UUID, &application_authorization_dto.EditAuthorization{
Name: &d.Name,
Position: &d.Position,
TokenName: &d.TokenName,
ExpireTime: &d.ExpireTime,
Config: &d.Config,
HideCredential: &d.HideCredential,
})
if err != nil {
return fmt.Errorf("update authorization(%s) error: %v", d.UUID, err)
}
}
return nil
}
func (i *imlImportConfigController) publish(ctx context.Context) error {
data, err := unmarshal[service_dto.ExportService]("service")
if err != nil {
return err
}
for _, d := range data {
serviceId := d.Id
newReleaseId, err := i.releaseModule.Create(ctx, serviceId, &dto2.CreateInput{
Version: "v1",
Remark: "demo release",
})
if err != nil {
continue
}
apply, err := i.publishModule.Apply(ctx, serviceId, &dto.ApplyInput{
Release: newReleaseId,
Remark: "发布申请",
})
if err != nil {
return err
}
err = i.publishModule.Accept(ctx, serviceId, apply.Id, "")
if err != nil {
i.releaseModule.Delete(ctx, serviceId, newReleaseId)
return err
}
err = i.publishModule.Publish(ctx, serviceId, apply.Id)
if err != nil {
i.releaseModule.Delete(ctx, serviceId, newReleaseId)
return err
}
err = i.publishModule.Publish(ctx, serviceId, apply.Id)
if err != nil {
i.releaseModule.Delete(ctx, serviceId, newReleaseId)
return err
}
}
return nil
}
func (i *imlImportConfigController) importSubscribers(ctx context.Context) error {
applyData, err := unmarshal[subscribe_dto.ExportApproval]("apply")
if err != nil {
return err
}
for _, d := range applyData {
err = i.catalogueModule.Subscribe(ctx, &catalogue_dto.SubscribeService{
Service: d.Service,
Applications: []string{
d.Application,
},
Reason: d.Reason,
})
if err != nil {
return fmt.Errorf("application(%s) subscribe service(%s) error: %v", d.Application, d.Service, err)
}
}
data, err := unmarshal[subscribe_dto.ExportSubscriber]("subscribe")
if err != nil {
return err
}
for _, d := range data {
err = i.subscribeModule.ExistSubscriber(ctx, d.Service, d.Subscriber)
if err == nil {
continue
}
err = i.subscribeModule.AddSubscriber(ctx, d.Service, &subscribe_dto.AddSubscriber{
Application: d.Subscriber,
})
if err != nil {
return fmt.Errorf("update subscriber(%s) error: %v", d.Id, err)
}
}
return nil
}
+25
View File
@@ -0,0 +1,25 @@
package system
import (
"github.com/eolinker/go-common/autowire"
"github.com/gin-gonic/gin"
"reflect"
)
type IExportConfigController interface {
ExportAll(ctx *gin.Context) error
}
type IImportConfigController interface {
ImportAll(ctx *gin.Context) error
}
func init() {
autowire.Auto[IExportConfigController](func() reflect.Value {
return reflect.ValueOf(new(imlExportConfigController))
})
autowire.Auto[IImportConfigController](func() reflect.Value {
return reflect.ValueOf(new(imlImportConfigController))
})
}
+2 -2
View File
@@ -22,6 +22,6 @@ func (i *imlUpstreamController) Get(ctx *gin.Context, serviceId string) (upstrea
return i.upstreamModule.Get(ctx, serviceId)
}
func (i *imlUpstreamController) Save(ctx *gin.Context, serviceId string, upstream *upstream_dto.UpstreamConfig) (upstream_dto.UpstreamConfig, error) {
return i.upstreamModule.Save(ctx, serviceId, *upstream)
func (i *imlUpstreamController) Save(ctx *gin.Context, serviceId string, upstream *upstream_dto.Upstream) (upstream_dto.UpstreamConfig, error) {
return i.upstreamModule.Save(ctx, serviceId, upstream)
}
+3 -3
View File
@@ -2,16 +2,16 @@ package upstream
import (
"reflect"
"github.com/eolinker/go-common/autowire"
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
"github.com/gin-gonic/gin"
)
type IUpstreamController interface {
Get(ctx *gin.Context, serviceId string) (upstream_dto.UpstreamConfig, error)
Save(ctx *gin.Context, serviceId string, upstream *upstream_dto.UpstreamConfig) (upstream_dto.UpstreamConfig, error)
Save(ctx *gin.Context, serviceId string, upstream *upstream_dto.Upstream) (upstream_dto.UpstreamConfig, error)
}
func init() {
+1 -1
View File
@@ -2,7 +2,7 @@
## 代码同步
packages目录下,部分子项目为企业版独有,不要同步到开源版:
packages/businessEntry, packages/dashboard, packages/openApi, packages/systemRunning, README.pro.md
packages/businessEntry, packages/openApi, packages/systemRunning, README.pro.md
## 安装依赖
建议使用pnpm
-2
View File
@@ -49,9 +49,7 @@ func getFileSystem(dir string) http.FileSystem {
if err != nil {
panic(err)
}
return http.FS(fDir)
}
type Frontend struct {
+68
View File
@@ -0,0 +1,68 @@
const fs = require('fs');
const { crc32 } = require('crc');
const systemLanguage = {
zh_CN: 'zh-CN',
en_GB: 'en-GB'
};
// 读取已经存在在en.json文件的词条
let existData = {};
try {
console.log('Current working directory:', process.cwd());
const existJsonData = fs.readFileSync('packages/common/src/locales/scan/en-GB.json');
existData = JSON.parse(existJsonData);
} catch (error) {
if (error.code === 'ENOENT') {
console.warn('File not found: packages/common/src/locales/scan/en-GB.json. Creating an empty file.');
fs.writeFileSync('packages/common/src/locales/scan/en-GB.json', JSON.stringify({}));
} else {
throw error;
}
}
const keyList = Object.keys(existData);
module.exports = {
input: [
'packages/*/src/**/*.{js,jsx,tsx,ts}',
// 不需要扫描的文件加!
'!packages/*/src/locales/**',
'!**/node_modules/**'
],
output: 'packages/common/src/locales/scan', // 输出目录
options: {
debug: true,
func: false,
trans: false,
lngs: [systemLanguage.zh_CN, systemLanguage.en_GB],
defaultLng: systemLanguage.zh_CN,
resource: {
loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key)
jsonIndent: 2,
lineEnding: '\n'
},
removeUnusedKeys: true,
nsSeparator: false, // namespace separator
keySeparator: false, // key separator
interpolation: {
prefix: '{{',
suffix: '}}'
}
},
// 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换.
transform: function (file, enc, done) {
// 自己通过该函数来加工key或value
const { parser } = this;
const content = fs.readFileSync(file.path, enc);
parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
options.defaultValue = key;
const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
// 如果词条不存在,则写入
if (!keyList.includes(hashKey)) {
parser.set(hashKey, options);
}
});
done();
}
};
+9 -2
View File
@@ -14,7 +14,8 @@
"serve:remotes": "lerna run serve --scope=remote --parallel",
"dev": "lerna run dev --scope=core --stream",
"dev:pro": "lerna run dev --scope=business-entry --stream",
"stop": "kill-port --port 5000,5001"
"stop": "kill-port --port 5000",
"scan": "i18next-scanner --config i18next-scanner.config.js"
},
"keywords": [],
"author": "",
@@ -28,17 +29,22 @@
"@types/uuid": "^9.0.7",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.16",
"crc": "^4.3.2",
"dayjs": "^1.11.10",
"dompurify": "^3.1.6",
"i18next": "^23.12.2",
"i18next-browser-languagedetector": "^8.0.0",
"js-base64": "^3.7.5",
"moment": "^2.29.4",
"postcss": "^8.4.31",
"postcss-import": "^16.1.0",
"postcss-nesting": "^12.1.5",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-dom": "^18.2.0",
"react-i18next": "^15.0.1",
"react-joyride": "^2.8.2",
"react-router-dom": "^6.20.0",
"swagger-ui-react": "^5.17.14",
"tailwindcss": "^3.3.5",
"uuid": "^9.0.1",
"vite-tsconfig-paths": "^4.3.2"
@@ -64,6 +70,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"file-saver": "^2.0.5",
"i18next-scanner": "^4.5.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
@@ -11,7 +11,6 @@ import {FC,lazy} from 'react';
import { TeamProvider } from '@core/contexts/TeamContext.tsx';
import SystemOutlet from '@core/pages/system/SystemOutlet.tsx';
import { DashboardProvider } from '@core/contexts/DashboardContext.tsx';
import { PartitionProvider } from '@core/contexts/PartitionContext.tsx';
import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx';
type RouteConfig = {
@@ -133,7 +132,12 @@ const PUBLIC_ROUTES:RouteConfig[] = [
{
path:'api',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiList.tsx')),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')),
},
{
path:'router',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')),
},
{
path:'upstream',
@@ -182,6 +186,11 @@ const PUBLIC_ROUTES:RouteConfig[] = [
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublish.tsx')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
},
{
path:'*',
key: uuidv4(),
@@ -202,6 +211,10 @@ const PUBLIC_ROUTES:RouteConfig[] = [
]
}
]
},{
path:'datasourcing',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')),
},
{
path:'cluster',
@@ -338,23 +351,13 @@ const PUBLIC_ROUTES:RouteConfig[] = [
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@openApi/pages/OpenApiList.tsx')),
key:uuidv4(),
},
{
path:'logretrieval',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logRetrieval/LogRetrieval.tsx')),
key:uuidv4(),
},
{
path:'auditlog',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/auditLog/AuditLog.tsx')),
key:uuidv4(),
},
{
path:'assets',
component:<p></p>,
key:uuidv4()
},
APP_MODE === 'pro' &&{
path:'dashboard',
path:'analytics',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
key:uuidv4(),
children:[
@@ -394,7 +397,7 @@ const PUBLIC_ROUTES:RouteConfig[] = [
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
@@ -405,10 +408,20 @@ const PUBLIC_ROUTES:RouteConfig[] = [
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
},
{
path:'userProfile/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/UserProfile.tsx')),
key:uuidv4(),
children:[{
path:'changepsw',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/ChangePsw.tsx')),
key:uuidv4()
}]
}
]
},
@@ -433,7 +446,7 @@ const generateRoutes = (routerConfig: RouteConfig[]) => {
const LazyComponent = route.lazy as React.ExoticComponent<unknown>;
routeElement = (
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-[calc(100%-20px)]' active /></div>}>
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active /></div>}>
{route.provider ? (
createElement(route.provider, {}, <LazyComponent />)
) : (
@@ -28,6 +28,6 @@
"@businessEntry/*": ["./src/*"],
},
},
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TransferTable.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/Navigation.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/GroupTree.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../common/src/components/aoplatform/ScrollableSection.tsx", "../common/src/utils/postcat.tsx", "../common/src/utils/curl.ts", "../common/src/components/aoplatform/ResetPsw.tsx", "../common/src/components/aoplatform/SubscribeApprovalModalContent.tsx", "src/components/aoplatform/RenderRoutes.tsx", "../common/src/components/aoplatform/PublishApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePage.tsx", "../common/src/const/type.ts", "../common/src/components/aoplatform/intelligent-plugin", "../common/src/const/domain"],
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/Navigation.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../common/src/components/aoplatform/ScrollableSection.tsx", "../common/src/utils/postcat.tsx", "../common/src/utils/curl.ts", "../common/src/components/aoplatform/ResetPsw.tsx", "../common/src/components/aoplatform/SubscribeApprovalModalContent.tsx", "src/components/aoplatform/RenderRoutes.tsx", "../common/src/components/aoplatform/PublishApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePage.tsx", "../common/src/const/type.ts", "../common/src/components/aoplatform/intelligent-plugin", "../common/src/const/domain"],
"references": [{ "path": "./tsconfig.node.json" }]
}
@@ -0,0 +1 @@
<svg width="130" height="80" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="52.348%" y1="74.611%" x2="52.348%" y2="-17.635%" id="a"><stop stop-color="#DEDEDE" stop-opacity="0" offset="0%"/><stop stop-color="#A9A9A9" stop-opacity=".3" offset="100%"/></linearGradient><linearGradient x1="44.79%" y1="100%" x2="44.79%" y2="0%" id="b"><stop stop-color="#FFF" stop-opacity="0" offset="0%"/><stop stop-color="#96A1C5" stop-opacity=".373" offset="100%"/></linearGradient><linearGradient x1="50%" y1="100%" x2="50%" y2="-19.675%" id="c"><stop stop-color="#FFF" stop-opacity="0" offset="0%"/><stop stop-color="#919191" stop-opacity=".15" offset="100%"/></linearGradient><linearGradient x1="50%" y1="0%" x2="50%" y2="44.95%" id="d"><stop stop-color="#5389F5" offset="0%"/><stop stop-color="#416FDC" offset="100%"/></linearGradient><linearGradient x1="63.345%" y1="100%" x2="63.345%" y2="-5.316%" id="e"><stop stop-color="#DCE9FF" offset="0%"/><stop stop-color="#B6CFFF" offset="100%"/></linearGradient><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="f"><stop stop-color="#7CA5F7" offset="0%"/><stop stop-color="#C4D6FC" offset="100%"/></linearGradient></defs><g transform="translate(-1.866 .364)" fill="none" fill-rule="evenodd"><path d="M27.94 14.864c1.326-4.192 2.56-6.802 3.7-7.831 3.157-2.848 7.522-1.298 8.45-1.076 3.26.782 2.2-4.364 4.997-5.41 1.864-.697 3.397.155 4.6 2.556C50.752.863 52.375-.163 54.556.02c3.272.277 4.417 11.328 8.913 8.909 4.497-2.42 10.01-2.973 12.365.623.509.778.704-.429 4.166-4.55C83.462.88 86.914-.936 93.996 1.464c3.22 1.09 5.868 4.045 7.947 8.864 0 6.878 5.06 10.95 15.178 12.213 15.179 1.895 3.397 18.214-15.178 22.993-18.576 4.78-61.343 7.36-84.551-4.716C1.92 32.769 5.436 24.117 27.939 14.864z" fill="url(#a)" opacity=".8"/><ellipse fill="url(#b)" cx="66" cy="69.166" rx="27.987" ry="6.478"/><path d="M113.25 77.249c-21.043 5.278-92.87-.759-100.515-3.516-3.721-1.343-7.075-3.868-10.061-7.576a2.822 2.822 0 0 1 2.198-4.593h125.514c2.605 6.938-3.107 12.166-17.136 15.685z" fill="url(#c)" opacity=".675"/><g fill-rule="nonzero"><path d="M43.396 12.098L33.825.906a2.434 2.434 0 0 0-1.837-.86h-20.58c-.706 0-1.377.324-1.837.86L0 12.098v6.144h43.396v-6.144z" fill="url(#d)" transform="translate(44.08 39.707)"/><path d="M40.684 18.468L32.307 8.72a2.136 2.136 0 0 0-1.622-.725H12.711c-.617 0-1.22.256-1.622.725l-8.377 9.748v5.354h37.972v-5.354z" fill="url(#e)" transform="translate(44.08 39.707)"/><path d="M43.396 25.283c0 .853-.384 1.62-.99 2.134l-.123.1a2.758 2.758 0 0 1-1.67.56H2.784c-.342 0-.669-.062-.971-.176l-.15-.06A2.802 2.802 0 0 1 0 25.282V12.165h10.529c1.163 0 2.1.957 2.1 2.118v.015c0 1.162.948 2.099 2.111 2.099h13.916a2.113 2.113 0 0 0 2.111-2.107c0-1.166.938-2.125 2.1-2.125h10.53z" fill="url(#f)" transform="translate(44.08 39.707)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -0,0 +1,39 @@
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
}
}
}
return(
<SwaggerUI
spec={spec}
supportedSubmitMethods={[]}
customComponents={{Header:()=>null}}
layout="OperationsLayout"
plugins={[OperationsLayoutPlugin ]} />
)
}
@@ -6,23 +6,26 @@ import {
Button} from 'antd';
import Logo from '@common/assets/layout-logo.png';
import AvatarPic from '@common/assets/default-avatar.png'
import { routerKeyMap, TOTAL_MENU_ITEMS } from "./Navigation";
import {Outlet, useLocation, useNavigate} from "react-router-dom";
import {useEffect, useMemo, useRef, useState} from "react";
import { useEffect, useMemo, useState} from "react";
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx';
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts';
import {
ProConfigProvider,
ProLayout,
} from '@ant-design/pro-components';
import { UserProfile } from './UserProfile.tsx';
import { ResetPsw, ResetPswHandle } from './ResetPsw.tsx';
import { BasicResponse, STATUS_CODE } from '@common/const/const.ts';
import { UserInfoType, UserProfileHandle } from '@common/const/type.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 { QuestionCircleOutlined } from '@ant-design/icons';
import { Icon } from '@iconify/react/dist/iconify.js';
import { ProjectFilled } from '@ant-design/icons';
import { getNavItem } from '@common/utils/navigation';
import { Icon } from '@iconify/react';
import { $t } from '@common/locales';
import LanguageSetting from './LanguageSetting';
const APP_MODE = import.meta.env.VITE_APP_MODE;
export type MenuItem = Required<MenuProps>['items'][number];
const themeToken = {
bgLayout:'#17163E;',
header: {
@@ -38,10 +41,47 @@ const themeToken = {
const navigator = useNavigate()
const location = useLocation()
const currentUrl = location.pathname
const { accessData,checkPermission} = useGlobalContext()
const { state,accessData,checkPermission,accessInit} = useGlobalContext()
const [pathname, setPathname] = useState(currentUrl);
const mainPage = project === 'core' ?'/service/list':'/serviceHub/list'
const TOTAL_MENU_ITEMS:MenuProps['items'] = useMemo(() => [
getNavItem($t('工作空间'), 'workspace','/guide',<Icon icon="ic:baseline-space-dashboard" width="18" height="18"/>, [
getNavItem($t('我的'), 'my','/guide',null,[
getNavItem(<a>{$t('首页')}</a>, 'guide','/guide',<Icon icon="ic:baseline-home" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a>{$t('应用')}</a>, 'tenantManagement','/tenantManagement',<Icon icon="ic:baseline-apps" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a>{$t('服务')}</a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a>{$t('团队')}</a>, 'team','/team',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,''),
],undefined,''),
]),
getNavItem(<a>{$t('API 市场')}</a>, 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'),
getNavItem($t('仪表盘'), 'mainPage', APP_MODE === 'pro' ? '/analytics' : '/analytics/total',<Icon icon="ic:baseline-bar-chart" width="18" height="18"/>,[
getNavItem(<a >{$t('运行视图')}</a>, 'analytics',APP_MODE === 'pro' ? '/analytics' : '/analytics/total' ,<ProjectFilled />,undefined,undefined,'system.dashboard.run_view.view'),
APP_MODE === 'pro' ? getNavItem(<a >{$t('系统拓扑图')}</a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,'system.dashboard.systemrunning.view') : null,
],undefined,'system.dashboard.run_view.view'),
getNavItem($t('系统设置'), 'operationCenter','/member',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
getNavItem($t('组织'), 'organization','/member',null,[
getNavItem(<a>{$t('成员')}</a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.organization.member.view'),
getNavItem(<a>{$t('角色')}</a>, 'role','/role',<Icon icon="ic:baseline-verified-user" width="18" height="18"/>,undefined,undefined,'system.organization.role.view'),
],undefined,''),
getNavItem($t('API 市场'), 'serviceHubSetting','/servicecategories',null,[
getNavItem(<a>{$t('服务分类管理')}</a>, 'servicecategories','/servicecategories',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
],undefined,'system.api_market.service_classification.view'),
getNavItem($t('运维与集成'), 'maintenanceCenter','/cluster', null, [
getNavItem(<a>{$t('集群')}</a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem(<a>{$t('数据源')}</a>, 'datasourcing','/datasourcing',<Icon icon="ic:baseline-monitor-heart" width="18" height="18"/>,undefined,undefined,'system.devops.data_source.view'),
getNavItem(<a>{$t('证书')}</a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.devops.ssl_certificate.view'),
getNavItem(<a>{$t('日志')}</a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.devops.log_configuration.view'),
APP_MODE === 'pro' ? getNavItem(<a>{$t('资源')}</a>, 'resourcesettings','/resourcesettings',null,undefined,undefined,'system.partition.self.view'):null,
APP_MODE === 'pro' ? getNavItem(<a>{$t('Open API')}</a>, 'openapi','/openapi',null,undefined,undefined,'system.openapi.self.view'):null,
]),
]),
],[state.language,accessInit])
useEffect(() => {
if(currentUrl === '/'){
navigator(mainPage)
@@ -82,13 +122,14 @@ const themeToken = {
const res = [...TOTAL_MENU_ITEMS]!.filter(x => x).map((x: any) => (x.routes ? { ...x, routes: filterMenu(x.routes) } : x));
// 返回处理后的数据
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]);
}, [accessData, state.language]);
const { modal,message } = App.useApp()
const { message } = App.useApp()
const { dispatch,resetAccess,getGlobalAccessData} = useGlobalContext()
const [userInfo,setUserInfo] = useState<UserInfoType>()
const resetPswRef = useRef<ResetPswHandle>(null)
const userProfileRef = useRef<UserProfileHandle>(null)
const {fetchData} = useFetch()
const navigate = useNavigate();
@@ -100,7 +141,7 @@ const themeToken = {
setUserInfo(data.profile)
dispatch({type:'UPDATE_USERDATA',userData:data.profile})
}else{
message.error(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
@@ -116,10 +157,10 @@ const themeToken = {
if(code === STATUS_CODE.SUCCESS){
dispatch({type:'LOGOUT'})
resetAccess()
message.success(msg || '退出成功,将跳转至登录页')
// message.success(msg || $t(RESPONSE_TIPS.logoutSuccess))
navigate('/login')
}else{
message.error(msg ||'操作失败')
message.error(msg ||$t(RESPONSE_TIPS.error))
}
})
}
@@ -129,49 +170,19 @@ const themeToken = {
key: '2',
label: (
<Button key="changePsw" type="text" className="border-none p-0 flex items-center bg-transparent " onClick={()=>navigator('/userProfile/changepsw')}>
{$t('账号设置')}
</Button>)
},
{
key: '3',
label: (
<Button key="logout" type="text" className="border-none p-0 flex items-center bg-transparent " onClick={logOut}>
退
{$t('退出登录')}
</Button>)
},
];
const openModal = (type:'userSetting'|'resetPsw')=>{
let title:string = ''
let content:string|React.ReactNode = ''
switch (type){
case 'userSetting':
title='用户设置'
content=<UserProfile ref={userProfileRef} entity={userInfo}/>
break;
case 'resetPsw':
title='重置密码'
content=<ResetPsw ref={resetPswRef} entity={userInfo} />
break;
}
modal.confirm({
title,
content,
onOk:()=>{
switch (type){
case 'userSetting':
return userProfileRef.current?.save().then((res)=>{if(res === true) getUserInfo()})
case 'resetPsw':
return resetPswRef.current?.save().then((res)=>{if(res === true) logOut()})
}
},
width:600,
okText:'确认',
cancelText:'取消',
closable:true,
icon:<></>,
})
}
return(
<div
@@ -182,97 +193,99 @@ const themeToken = {
}}
>
<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
}}
>
<div className='avatar-dom'>{dom}
</div>
</Dropdown>
);
},
}}
actionsRender={(props) => {
if (props.isMobile) return [];
if (typeof window === 'undefined') return [];
return [
<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"/></span>
</Button>
];
}}
headerTitleRender={() => (
<div className="w-[192px] flex items-center">
<img
className="h-[20px] cursor-pointer "
src={Logo}
onClick={()=> navigator(mainPage)}
/>
</div>
)}
logo={Logo}
pageTitleRender={()=>'APIPark - 企业API数据开放平台'}
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 || '');
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body;
}}
>
{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>
<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 [
<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>
];
}}
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 - 企业API数据开放平台')}
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>
)
@@ -1,6 +1,7 @@
import { FC } from 'react';
import { Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { $t } from '@common/locales';
interface DataType {
httpStatusCode: string;
@@ -11,17 +12,17 @@ interface DataType {
const columns: ColumnsType<DataType> = [
{
title: 'HTTP 状态码',
title:$t('HTTP 状态码'),
dataIndex: 'httpStatusCode',
key: 'httpStatusCode',
},
{
title: '系统状态码',
title:$t('系统状态码'),
dataIndex: 'systemStatusCode',
key: 'systemStatusCode',
},
{
title: '描述',
title: $t('描述'),
dataIndex: 'description',
key: 'description',
ellipsis:true
@@ -1,8 +0,0 @@
import { DatePicker } from 'antd';
import type { Moment } from 'moment';
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
const MyDatePicker = DatePicker.generatePicker<Moment>(momentGenerateConfig);
export default MyDatePicker;
@@ -2,6 +2,7 @@
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
@@ -17,7 +18,7 @@ export type DrawerWithFooterProps = DrawerProps & {
cancelBtnTitle?:string
}
export function DrawerWithFooter(props:DrawerWithFooterProps){
const {children,title,placement='right',onClose,onSubmit,submitDisabled = false,okBtnTitle='提交',cancelBtnTitle,open,submitAccess,showLastStep,onLastStep,notAutoClose,showOkBtn=true,extraBtn} = props
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)
@@ -41,12 +42,12 @@ export function DrawerWithFooter(props:DrawerWithFooterProps){
<Space className="flex flex-row-reverse" style={{}}>
{showOkBtn && <WithPermission access={submitAccess}>
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
{okBtnTitle}
{ okBtnTitle}
</Button>
</WithPermission>}
{ showLastStep && <Button onClick={onLastStep ?? onClose}></Button>}
{ showLastStep && <Button onClick={onLastStep ?? onClose}> { $t('上一步')}</Button>}
{ extraBtn }
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? '取消':'关闭')}</Button>
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消'):$t('关闭'))}</Button>
</Space>
}
onClose={onClose}
@@ -1,11 +1,13 @@
import { EditableProTable, ProColumns } from "@ant-design/pro-components";
import { Button } from "antd";
import { useState, useEffect } from "react";
import { EditableProTable } from "@ant-design/pro-components";
import { useState, useEffect, useMemo } from "react";
import { v4 as uuidv4} from 'uuid';
import WithPermission from "./WithPermission";
import { PageProColumns } from "./PageList";
import TableBtnWithPermission from "./TableBtnWithPermission";
import { $t } from "@common/locales";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
interface EditableTableProps<T> {
configFields: ProColumns<T>[];
configFields: PageProColumns<T>[];
value?: T[]; // 外部传入的值
className?: string;
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
@@ -23,10 +25,8 @@ const EditableTable = <T extends { _id: string }>({
className,
extendsId,
}: EditableTableProps<T>) => {
// const [form] = Form.useForm<FormInstance>();
// const [isModalVisible, setIsModalVisible] = useState(false);
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
// const [editingConfig, setEditingConfig] = useState<T | null>(null);
const {state} = useGlobalContext()
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
value?.map((item) => item._id) || ['1234']
@@ -40,10 +40,12 @@ const EditableTable = <T extends { _id: string }>({
return value
}
const translatedColumns = useMemo(()=>configFields.map((x)=>({...x, title:$t(x.title as string)})),[state.language,configFields])
return (
<EditableProTable<T>
className={className}
columns={configFields}
columns={translatedColumns}
rowKey="_id"
value={configurations as T[]}
size="small"
@@ -54,43 +56,36 @@ const EditableTable = <T extends { _id: string }>({
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
actionRender: (row, config) => {
return [
<WithPermission access="" key="addPermission" ><Button type="text" className="h-[22px] border-none p-0 flex items-center bg-transparent "
key="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]))
}}
>
</Button></WithPermission>,
(config.index !== configurations.length - 1 )&& <WithPermission access=""><Button type="text" className="h-[22px] border-none p-0 flex items-center bg-transparent "
key="edit"
onClick={() => {
setConfigurations((prev)=>{
const tmpPreData = [...prev];
tmpPreData.splice(Number(config.index), 1);
onChange?.(tmpPreData);
return tmpPreData});
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
}}
>
</Button></WithPermission>,
<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="增加"/>,
(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) => {
@@ -1,16 +1,21 @@
import {useEffect, useState} from 'react';
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';
export interface ConfigField<T> {
title: string;
key: keyof T;
component: React.ReactNode;
renderText?: (value: unknown, record: T) => React.ReactNode;
renderText?: (value: unknown, record: T) => string;
required?: boolean;
ellipsis?:boolean
unRender?:(form:FormInstance)=>boolean
}
interface EditableTableWithModalProps<T> {
@@ -34,6 +39,8 @@ const EditableTableWithModal = <T extends { _id?: string }>({
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) {
@@ -81,50 +88,57 @@ const EditableTableWithModal = <T extends { _id?: string }>({
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []);
}, [value]);
const columns: ColumnsType<T> = configFields.map(({ title, key, renderText }) => ({
title,
dataIndex: key as string,
key: key as string,
render: renderText ? (value, record) => renderText(value, record) : undefined,
ellipsis:true
}));
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='编辑'/>
<Divider key="div1" type="vertical" />
<TableBtnWithPermission key="delete" disabled={disabled} btnType="delete" onClick={()=>{handleDelete(record._id || '')}} btnTitle='删除'/>
</div>
</>
),
}] )
],[state.language, disabled, configFields])
!disabled && columns.push({
title: '操作',
key: 'action',
width:117,
render: (_: unknown, record: T) => (
<>
<div className="flex items-center">
<Button key="edit" disabled={disabled} onClick={()=>{showModal(record)}} className={`h-[22px] border-none p-0 flex items-center bg-transparent`}></Button>
<Divider key="div1" type="vertical" />
<Button key="delete" disabled={disabled} onClick={()=>{handleDelete(record._id || '')}} className={`h-[22px] border-none p-0 flex items-center bg-transparent`} ></Button>
</div>
</>
),
});
const formItems = configFields.map(({ title,key, component, required }) => {
return (
<Form.Item
label={title as string}
name={key as string}
rules={[{ required, message: `必填项`}]}
>
{component}
</Form.Item>
)
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()}></Button>}
{!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 ? '编辑配置' : '添加配置'}
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
open={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
@@ -135,6 +149,9 @@ const EditableTableWithModal = <T extends { _id?: string }>({
<WithPermission access=""><Form form={form} name="editableTableWithModal"
layout="vertical"
scrollToFirstError
onFieldsChange={(()=>{
setFormsValue(form.getFieldsValue())
})}
// labelCol={{ span: 7 }}
// wrapperCol={{ span: 17}}
autoComplete="off">
@@ -1,105 +0,0 @@
import DirectoryTree from "antd/es/tree/DirectoryTree";
import { DataNode, DirectoryTreeProps } from "antd/lib/tree";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import TreeWithMore from "@common/components/aoplatform/TreeWithMore";
import { SearchOutlined } from "@ant-design/icons";
import { Input, Button, MenuProps } from "antd";
import { debounce } from "lodash-es";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { v4 as uuidv4 } from 'uuid'
type T = unknown
export interface GroupTreeProps extends DirectoryTreeProps{
groupData?:(DataNode & T )[]
addBtnName?:React.ReactNode
addBtnAccess?:string
treeNameSuffixKey?:string
dropdownMenu?:(data:(DataNode & T )) => MenuProps['items']
withMore?:boolean
onEditGroup:(type:'rename'|'addChild'|'addPeer', entity:DataNode & T, val:string) => Promise<boolean>|undefined
placeholder?:string
}
export interface GroupTreeHandle {
startEdit:(id:string)=>void;
startAdd:(type:'peer',entity?:DataNode & T)=>void
}
const GroupTree = forwardRef<GroupTreeHandle,GroupTreeProps>((props, ref)=>{
const {groupData,selectedKeys,onSelect,addBtnName,addBtnAccess,treeNameSuffixKey,dropdownMenu,onEditGroup,placeholder="输入以搜索"} = props
const [treeData, setTreeData] = useState<DataNode[]>([])
const [searchWord, setSearchWord] = useState<string>('')
const [editingId, setEditingId] = useState<string>('')
const [addStatus, setAddStatus] = useState<boolean>(false)
useImperativeHandle(ref, ()=>({
startEdit:setEditingId,
startAdd:handlerAction
}))
const handlerAction = (type:'peer')=>{
if(type === 'peer'){
setAddStatus(true)
setEditingId(uuidv4())
}
}
const getTreeData = (rawData?:DataNode[])=>{
const loop = (data: DataNode[]): DataNode[] =>{
const newData = [...data,...(addStatus? [{title:'',key:editingId,id:editingId}]:[])]
return newData.map((item) => {
const strTitle = item.title 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 >
{beforeStr}
<span className="text-theme">{searchWord}</span>
{afterStr} {treeNameSuffixKey && <span>({item?.[treeNameSuffixKey as keyof DataNode] as string ?? 0})</span>}
</span>) : (
<span className='w-[100%] truncate'>{strTitle}{treeNameSuffixKey && <span>({item?.[treeNameSuffixKey as keyof DataNode] as string?? 0})</span>}</span>
)
return {
title:<TreeWithMore dropdownMenu={dropdownMenu?.(item)} onBlur={()=>{setAddStatus(false);setEditingId('')}} editable editingId={editingId} entity={item} afterEdit={(val)=>onEditGroup?.(addStatus && editingId === item.key ? 'addPeer':'rename',item, val)?.then((res)=>{res && setEditingId('') ;res && setAddStatus(false) ; return res})}>{title}</TreeWithMore>,
key: item.key,
id:item.key
};
})
};
return rawData ? loop(rawData) :[];
}
const onSearchWordChange = (e:string)=>{
setSearchWord(e || '')
}
useEffect(()=>{
const n = getTreeData(groupData)
setTreeData(n)
},[groupData,editingId,searchWord])
return (
<>
<Input className="w-[calc(100%-24px)] mx-btnbase my-btnybase" onChange={(e) => debounce(onSearchWordChange, 100)(e.target.value)}
allowClear placeholder={placeholder}
prefix={ <SearchOutlined className="cursor-pointer" />}/>
<div className="max-h-[calc(100%-140px)] overflow-y-auto">
<DirectoryTree
icon={<></>}
blockNode={true}
treeData={treeData}
selectedKeys={selectedKeys}
onSelect={onSelect}
/>
</div>
{addBtnName && <WithPermission access={addBtnAccess}><Button className="h-[22px] mt-[20px] mb-[16px] bottom-[0px] sticky border-none p-0 flex items-center bg-transparent text-theme ml-[10px] hover:text-A_HOVER" key='add' onClick={()=>handlerAction('peer')} >{addBtnName}</Button></WithPermission>}
</>
)
})
export default GroupTree
@@ -4,27 +4,29 @@ 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";
class InsidePageProps {
showBanner?:boolean = true
pageTitle:string = ''
pageTitle:string| React.ReactNode = ''
tagList?:Array<{label:string|ReactNode}> = []
children:React.ReactNode
showBtn?:boolean = false
btnTitle?:string = ''
description?:string = ''
description?:string | React.ReactNode= ''
onBtnClick?:()=>void
backUrl?:string = '/'
btnAccess?:string
showBorder?:boolean = true
className?:string = ''
contentClassName?:string=''
headerClassName?:string=''
/** 整个页面滚动 */
scrollPage?:boolean = true
}
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',scrollPage=true})=>{
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',headerClassName='',scrollPage=true})=>{
const navigate = useNavigate();
const goBack = () => {
@@ -33,10 +35,10 @@ const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showB
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' : ''}`}>
{ showBanner && <div className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-b-[1px] border-solid border-BORDER' : ''} ${headerClassName}`}>
<div className="mb-[30px]">
{backUrl &&<div className="text-[18px] leading-[25px] mb-[12px]">
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" /></Button>
<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 ">
@@ -0,0 +1,59 @@
import { Dropdown, Row, Col, Button } from 'antd';
import i18n from '@common/locales';
import { $t } from '@common/locales';
import { memo, useEffect, useMemo } from 'react';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { Icon } from '@iconify/react/dist/iconify.js';
const LanguageSetting = ({mode = 'light'}:{mode?:'dark'|'light'}) => {
const { dispatch,state} = useGlobalContext()
const items = [
{
key: 'cn',
label: <Button key="cn" type="text" className="border-none p-0 flex items-center bg-transparent ">
{$t('简体')}
</Button>,
title: $t('简体'),
},
{
key: 'en',
label:<Button key="en" type="text" className="border-none p-0 flex items-center bg-transparent ">
{$t('英文')}
</Button>,
title:$t('英文')
}
];
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){
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang.startsWith('cn') ? 'cn' : 'en' });
}else{
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang.startsWith('zh') ? 'cn' : 'en' });
}
},[
])
return (
<Dropdown
trigger={['hover']}
menu={{
items,
style:{minWidth:'80px'},
onClick: (e) => {
const { key } = e;
dispatch({ type: 'UPDATE_LANGUAGE', language: key });
i18n.changeLanguage(key);
}
}}
>
<Button className={`border-none ${mode==='dark' ? "text-[#333] hover:text-[#333333b3]" : "text-[#ffffffb3] hover:text-[#fff] "}`} type="default" ghost >
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-language" width="14" height="14"/>{langLabel}</span>
</Button>
</Dropdown>
);
};
export default memo(LanguageSetting);
@@ -2,12 +2,27 @@
import { GetProp, TransferProps, TreeDataNode, theme, Transfer, Tree, Spin } from "antd";
import { DataNode, TreeProps } from "antd/es/tree";
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { TransferTableHandle, TransferTableProps } from "./TransferTable";
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons";
import { debounce } from "lodash-es";
import { ColumnsType } from "antd/es/table";
type TransferItem = GetProp<TransferProps, 'dataSource'>[number];
export type TransferTableProps<T> = {
request?:(k?:string)=>Promise<{data:T[],success:boolean}>
columns: ColumnsType<T>
primaryKey:string
onSelect:(selectedData:T[])=>void
tableType?:'member'|'api'
disabledData:string[]
searchPlaceholder?:string
}
export type TransferTableHandle<T> = {
selectedData: () => T[];
selectedRowKeys: () => React.Key[];
}
interface TreeTransferProps {
dataSource: TreeDataNode[];
targetKeys: TransferProps['targetKeys'];
@@ -1,20 +0,0 @@
import { useEffect } from "react";
import { editor } from "monaco-editor";
import useInitializeMonaco from "@common/hooks/useInitializeMonaco";
import { Editor, useMonaco } from '@monaco-editor/react'
export type MonacoEditorRefType = editor.IStandaloneCodeEditor;
const MonacoEditorWrapper: React.FC = (props) => {
useInitializeMonaco();
const monacoInstance = useMonaco();
useEffect(() => {
if (monacoInstance) {
// 在这里你可以访问并配置Monaco实例
}
}, [monacoInstance]);
return <Editor {...props} />;
};
export default MonacoEditorWrapper;
@@ -1,100 +0,0 @@
import {FC, useEffect, useMemo, useState} from 'react';
import type { MenuProps } from 'antd';
import { Menu } from 'antd';
import { useLocation, useNavigate} from "react-router-dom";
import { getNavItem } from '@common/utils/navigation';
import { PERMISSION_DEFINITION } from '@common/const/permissions';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { ProjectFilled } from '@ant-design/icons';
import { Icon } from '@iconify/react';
export type MenuItem = Required<MenuProps>['items'][number];
const APP_MODE = import.meta.env.VITE_APP_MODE;
// avoid changing route within ths same category
export const routerKeyMap = new Map<string, string[]|string>([
['workspace',['tenantManagement','service','team','serviceHub']],
['my',['tenantManagement','service','team']],
['mainPage',['dashboard','systemrunning']],
['operationCenter',['member','user','role','servicecategories']],
['organization',['member','user','role']],
['serviceHubSetting',['servicecategories']],
['maintenanceCenter',['partition','logsettings','resourcesettings','openapi']
]])
export const TOTAL_MENU_ITEMS: MenuProps['items'] = [
getNavItem('工作空间', 'workspace','/tenantManagement',<Icon icon="ic:baseline-space-dashboard" width="18" height="18"/>, [
getNavItem('我的', 'my','/tenantManagement',null,[
getNavItem(<a></a>, 'tenantManagement','/tenantManagement',<Icon icon="ic:baseline-apps" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a></a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,''),
getNavItem(<a></a>, 'team','/team',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,''),
],undefined,''),
getNavItem(<a>API </a>, 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.workspace.api_market.view'),
]),
APP_MODE === 'pro' ? getNavItem('仪表盘', 'mainPage', '/dashboard',<Icon icon="ic:baseline-bar-chart" width="18" height="18"/>,[
getNavItem(<a ></a>, 'dashboard','/dashboard',<ProjectFilled />,undefined,undefined,''),
getNavItem(<a ></a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,''),
]):null,
getNavItem('系统设置', 'operationCenter','/member',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
getNavItem('组织', 'organization','/member',null,[
getNavItem(<a></a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.organization.member.view'),
getNavItem(<a></a>, 'role','/role',<Icon icon="ic:baseline-verified-user" width="18" height="18"/>,undefined,undefined,'system.organization.role.view'),
],undefined,''),
getNavItem('API 市场', 'serviceHubSetting','/servicecategories',null,[
getNavItem(<a></a>, 'servicecategories','/servicecategories',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
],undefined,'system.api_market.service_classification.view'),
getNavItem('运维与集成', 'maintenanceCenter','/cluster', null, [
getNavItem(<a></a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.view'),
getNavItem(<a></a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.devops.ssl_certificate.view'),
getNavItem(<a></a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.devops.log_configuration.view'),
APP_MODE === 'pro' ? getNavItem(<a></a>, 'resourcesettings','/resourcesettings',null,undefined,undefined,'system.partition.self.view'):null,
APP_MODE === 'pro' ? getNavItem(<a>Open API</a>, 'openapi','/openapi',null,undefined,undefined,'system.openapi.self.view'):null,
]),
]),
];
const Navigation: FC = () => {
const location = useLocation()
const [selectedKeys, setSelectedKeys] = useState<string>('')
const currentUrl = location.pathname
const navigateTo = useNavigate()
const { accessData,checkPermission} = useGlobalContext()
const onClick: MenuProps['onClick'] = (e) => {
if(location.pathname.split('/')[1] === e.key) return
const newUrl = routerKeyMap.get(e.key)
newUrl && navigateTo(newUrl)
};
const menuData = useMemo(()=>{
const filterMenu = (menu:Array<{[k:string]:unknown}>)=>{
return menu.filter(x=> x && (x.access ? checkPermission(x.access as keyof typeof PERMISSION_DEFINITION[0]): true))
}
return TOTAL_MENU_ITEMS!.filter(x=>x).map((x)=> ( x.children ? {...x, children:filterMenu(x.children)} : x))?.filter(x=> x.key === 'service' || (x.children && x.children?.length > 0))
},[accessData])
useEffect(() => {
setSelectedKeys(currentUrl.split('/')[1] === 'template' ? currentUrl.split('/')[2] : currentUrl.split('/')[1])
}, [currentUrl]);
return (
<Menu
onClick={onClick}
theme="dark"
style={{height:'100%' }}
selectedKeys={[selectedKeys]}
defaultOpenKeys={['mainPage','dataAssets','operationCenter','maintenanceCenter']}
mode="inline"
items={[...menuData]}
/>
);
};
export default Navigation;
@@ -15,9 +15,11 @@ import { useGlobalContext } from '../../contexts/GlobalStateContext';
import { PERMISSION_DEFINITION } from '@common/const/permissions';
import { withMinimumDelay } from '@common/utils/ux';
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T , ValueType> & {btnNums? : number}
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
id?:string
columns: ProColumns<T,'text'>[]
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
@@ -49,23 +51,28 @@ interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<Acti
}
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 {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} = useGlobalContext()
const {accessData,checkPermission,accessInit,state} = useGlobalContext()
const [minTableWidth, setMinTableWidth] = useState<number>(0)
// 使用useImperativeHandle来自定义暴露给父组件的实例值
useImperativeHandle(ref, () => actionRef.current!);
useEffect(()=>{
actionRef?.current?.reload?.()
},[state.language])
const lastAccess = useMemo(()=>{
if(!tableClickAccess) return true
return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0])
},[allowTableClick, accessData])
},[allowTableClick, accessData,accessInit])
useEffect(()=>{
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
@@ -79,7 +86,6 @@ const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChild
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);
console.log(minTableWidth,res.width )
height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight));
}
};
@@ -114,7 +120,6 @@ const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChild
let width:number = 0
const res = columns?.map(
(x, index)=>{
width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100))
const sorter = localStorage.getItem(`${id}_sorter`)
const filters = localStorage.getItem(`${id}_filters`)
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
@@ -129,6 +134,11 @@ const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChild
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
@@ -1,112 +1,23 @@
import {App, Col, Form, Input, Row, Table, Tooltip} from "antd";
import {forwardRef, useEffect, useImperativeHandle} from "react";
import {PublishApprovalInfoType, PublishVersionTableListItem} from "@common/const/approval/type.tsx";
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, STATUS_CODE} from "@common/const/const.ts";
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR, VALIDATE_MESSAGE} 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 { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline.tsx";
import { $t } from "@common/locales";
import { 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";
enum ChangeTypeEnum {
'new' = '新增',
'update' = '变更',
'delete' = '删除',
'none' = '无变更',
'error' = '缺失字段'
}
const statusColorClass = {
new: 'text-[#138913]', // 使用 Tailwind 的 Arbitrary Properties
update: 'text-[#03a9f4]',
delete: 'text-[#ff3b30]',
none: 'text-[var(--MAIN_TEXT)]', // 假设你也有一个“none”的状态
};
const apiColumns = [
{
title:'API 名称',
dataIndex:'name',
ellipsis:true
},
{
title:'请求方式',
dataIndex:'method',
ellipsis:true
},
{
title:'路径',
dataIndex:'path',
ellipsis:true
},
{
title:'类型',
dataIndex:'change',
render:(_,entity)=>(
<Tooltip placement="top" title={entity.change === 'error' ?`该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}>
<span className={`${statusColorClass[entity.change as keyof typeof statusColorClass]} truncate block`}>
{ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-'}
{entity.change === 'error' ?` 该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}
</span>
</Tooltip>)
}
]
const upstreamColumns = [
{
title:'上游类型',
dataIndex:'type',
ellipsis:true,
// filters: true,
// onFilter: true,
// valueType: 'select',
// filterSearch: true,
valueEnum:{
'static':{
text:'静态上游'
},
// 'dynamic':{
// text:'动态上游'
// }
}
},
{
title:'地址',
dataIndex:'addr',
render:(text:string[])=>(<>{text.join(',')}</>),
ellipsis:true
},
{
title:'类型',
dataIndex:'change',
render:(_,entity)=>(
<Tooltip placement="top" title={entity.change === 'error' ?`该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}>
<span className={`${statusColorClass[entity.change as keyof typeof statusColorClass]} truncate block`}>{ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-'}
{entity.change === 'error' ?` 该 API 缺失 ${entity.proxyStatus == 1 && '转发信息,'} ${entity.docStatus == 1 && '文档信息,'} ${entity.upstreamStatus == 1 && '上游信息,'}请先补充`:''}</span>
</Tooltip>)
}
]
type PublishApprovalModalProps = {
type:'approval'|'view'|'add'|'publish'|'online'
data:PublishApprovalInfoType | PublishApprovalInfoType &{id?:string} | PublishVersionTableListItem
insideSystem?:boolean
serviceId:string
teamId:string
clusterPublishStatus?:SystemInsidePublishOnlineItems[]
}
export type PublishApprovalModalHandle = {
save:(operate:'pass'|'refuse') =>Promise<boolean|string>
publish:(notSave?:boolean)=>Promise<boolean|string|Record<string, unknown>>
online:()=>Promise<boolean|string>
}
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle,PublishApprovalModalProps>((props, ref) => {
const { message } = App.useApp()
const { type,data,insideSystem = false,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'){
@@ -115,19 +26,19 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
return form.validateFields().then((value)=>{
if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){
form.setFields([{
name:'opinion',errors:['选择拒绝时,审批意见为必填']
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
}])
form.scrollToField('opinion')
return Promise.reject('未填写审核意见')
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
return Promise.resolve(true)
}else{
message.error(msg || '操作失败')
return Promise.reject(msg || '操作失败')
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)})
@@ -141,11 +52,11 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(response)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
@@ -158,11 +69,11 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
@@ -180,26 +91,75 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
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.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])
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])
return (
<>
{!insideSystem && <>
<Row className="my-mbase">
<Col className="text-left" span={4}><span ></span></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 ></span></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 ></span></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 ></span></Col>
<Col className="text-left" span={4}><span >{$t('申请时间')}</span></Col>
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
</Row>
</> }
@@ -220,54 +180,54 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
insideSystem &&
<>
<Form.Item
label="版本号"
label={$t("版本号")}
name="version"
rules={[{required: true, message: '必填项',whitespace:true }]}
rules={[{required: true,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder="请输入" />
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item
label="版本说明"
label={$t("版本说明")}
name="versionRemark"
>
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder="请输入" />
<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 >API </span></Row>
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('路由列表')}</span></Row>
<Row className="mb-mbase ">
<Table
columns={apiColumns}
columns={translatedRouteColumns}
bordered={true}
rowKey="id"
size="small"
dataSource={data.diffs?.apis || []}
dataSource={data.diffs?.routers || []}
pagination={false}
/></Row>
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span ></span></Row>
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('上游列表')}</span></Row>
<Row className="mb-mbase ">
<Table
bordered={true}
columns={upstreamColumns}
columns={translatedUpstreamColumns}
size="small"
rowKey="id"
dataSource={data.diffs?.upstreams || []}
pagination={false}
/></Row>
<Form.Item
label="备注"
label={$t("备注")}
name="remark"
>
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder="请输入" />
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
{/*
{type !== 'add' && type !== 'publish' && <Form.Item
label="审批意见"
label={$t("审批意见"
name="opinion"
extra="选择拒绝时,审批意见为必填"
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder="请输入" onChange={()=>{ form.setFields([
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} onChange={()=>{ form.setFields([
{
name: 'opinion',
errors: [], // 设置为空数组来移除错误信息
@@ -275,11 +235,12 @@ 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>线</span></Row>
{['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={[...SYSTEM_PUBLISH_ONLINE_COLUMNS]}
columns={[...translatedPublishColumns]}
size="small"
rowKey="id"
dataSource={data.clusterPublishStatus || []}
@@ -1,134 +0,0 @@
import { Form, Input} from "antd";
import {forwardRef, useEffect, useImperativeHandle} from "react";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { UserInfoType } from "@common/const/type.ts";
type FieldType = {
userName:string
old:string
password:string
confirm:string
}
type ResetPswProps = {
entity?:UserInfoType
}
export type ResetPswHandle = {
save:()=>Promise<boolean|string>
}
export const ResetPsw = forwardRef<ResetPswHandle,ResetPswProps>((props,ref)=>{
const [form] = Form.useForm();
const save:()=>Promise<boolean | string> = ()=>{
return new Promise((resolve)=>{
// form.validateFields().then((value)=>{
// fetchData<BasicResponse<null>>(url,{method,eoBody:(value), eoTransformKeys:['departmentIds']}).then(response=>{
// const {code,msg} = response
// if(code === STATUS_CODE.SUCCESS){
// message.success(msg || '操作成功!')
resolve(true)
// }else{
// message.error(msg || '操作失败')
// reject(msg || '操作失败')
// }
// })
// }).catch((errorInfo)=> reject(errorInfo))
})
}
const getPswStrength = (value: string) => {
const pswRegNum: RegExp = /[0-9]/
const pswRegLowercase: RegExp = /[a-z]/
const pswRegUppercase: RegExp = /[A-Z]/
const pswRegSymbol: RegExp = /!@#$%^&*`~()-+=/
let strength: number = 0
if (pswRegNum.test(value)) {
strength++
}
if (pswRegLowercase.test(value)) {
strength++
}
if (pswRegUppercase.test(value)) {
strength++
}
if (pswRegSymbol.test(value)) {
strength++
}
return strength
}
useImperativeHandle(ref, ()=>({
save
})
)
useEffect(() => {
// form.setFieldsValue({id:entity!.id})
}, []);
return (<WithPermission access="">
<Form
labelAlign='left'
layout='vertical'
form={form}
scrollToFirstError
className="mx-auto mt-mbase ml-mbase"
name="resetPsw"
// labelCol={{ span: 8 }}
// wrapperCol={{ span: 10}}
autoComplete="off"
>
<Form.Item<FieldType>
label="账号"
name="userName"
hidden
rules={[{ required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="账号" disabled={true}/>
</Form.Item>
<Form.Item<FieldType>
label="旧密码"
name="old"
rules={[{ required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入6-32位字符"/>
</Form.Item>
<Form.Item<FieldType>
label="新密码"
name="password"
hidden
// eslint-disable-next-line @typescript-eslint/no-unused-vars
rules={[{ required: true, message: '必填项',whitespace:true }, ({ getFieldValue }) => ({
validator(_, value) {
if (!value || getPswStrength(value)>1) {
return Promise.resolve();
}
return Promise.reject(new Error('密码强度:弱,建议使用英文、数字、特殊字符组合'));
},
})]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入6-32位字符"/>
</Form.Item>
<Form.Item<FieldType>
label="确认新密码"
name="confirm"
dependencies={['password']}
rules={[{ required: true, message: '必填项',whitespace:true }, ({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('新密码与确认新密码不一致'));
},
})]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入6-32位字符"/>
</Form.Item>
</Form>
</WithPermission>)
})
@@ -1,9 +1,11 @@
import {App, Checkbox, Col, Form, Input, Row} from "antd";
import {App, Col, Form, Input, Row} from "antd";
import { forwardRef, useEffect, useImperativeHandle} from "react";
import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx";
import {BasicResponse, STATUS_CODE} from "@common/const/const.ts";
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'
@@ -22,26 +24,6 @@ type FieldType = {
opinion?:string;
};
const list = [
{
title:'申请方应用',key:'application'
},
{
title:'申请方所属团队',key:'applyTeam'
},
{
title:'申请人',key:'applier'
},
{
title:'申请时间',key:'applyTime'
},
{
title:'申请服务',key:'service'
},
{
title:'服务所属团队',key:'team'
}
]
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle,SubscribeApprovalModalProps>((props, ref) => {
const { message } = App.useApp()
const {data, type,inSystem=false, teamId, serviceId} = props
@@ -57,20 +39,20 @@ export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHa
form.validateFields().then((value)=>{
if(operate === 'refuse' && form.getFieldValue('opinion') === ''){
form.setFields([{
name:'opinion',errors:['必填项']
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
}])
form.scrollToField('opinion')
reject('未填写审核意见')
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
@@ -88,9 +70,9 @@ export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHa
return (
<div className="my-btnybase">{
list?.map((x)=>(
SubscribeApprovalList?.map((x)=>(
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
<Col className="text-left" span={6}>{x.title}</Col>
<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>
))
@@ -109,17 +91,17 @@ export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHa
>
<Form.Item<FieldType>
label="申请原因"
label={$t("申请原因")}
name="reason"
>
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
</Form.Item>
<Form.Item<FieldType>
label="审核意见"
label={$t("审核意见")}
name="opinion"
extra="选择拒绝时,审批意见为必填"
extra={$t(FORM_ERROR_TIPS.refuseOpinion)}
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder="请输入" onChange={()=>{ form.setFields([
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} onChange={()=>{ form.setFields([
{
name: 'opinion',
errors: [], // 设置为空数组来移除错误信息
@@ -1,40 +1,68 @@
import { Button, Tooltip } from "antd"
import { useState, useMemo, useEffect } from "react"
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"
type TableBtnWithPermissionProps = {
btnTitle:string
access: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'
}
// 表格操作栏按钮,受权限控制
const TableBtnWithPermission = ({btnTitle, access, tooltip, disabled, navigateTo, onClick,className}:TableBtnWithPermissionProps) => {
const TableBtnWithPermission = ({btnTitle, access, tooltip, disabled, navigateTo, onClick,className,btnType}:TableBtnWithPermissionProps) => {
const [btnAccess, setBtnAccess] = useState<boolean>(false)
const {accessData,checkPermission} = useGlobalContext()
const {accessData,checkPermission,accessInit} = useGlobalContext()
const navigate = useNavigate()
const lastAccess = useMemo(()=>{
if(!accessInit) return false
if(!access) return true
return checkPermission(access)
},[access, accessData])
},[access, accessData,checkPermission,accessInit])
useEffect(()=>{
access ? setBtnAccess(lastAccess) : setBtnAccess(true)
},[])
},[access, lastAccess])
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
navigateTo ? navigate(navigateTo) : onClick?.()
}, [navigateTo, navigate, onClick])
return (<>{
!btnAccess || (disabled&&tooltip) ?
<Tooltip placement="top" title={tooltip ?? `暂无${btnTitle}权限,请联系管理员分配。`}>
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key="view" >{btnTitle}</Button>
<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>
:
<Button type="text" disabled={disabled} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className} `} key="view" onClick={(e)=>{e.stopPropagation();navigateTo ? navigate(navigateTo) :onClick?.() }}>{btnTitle}</Button>
<Tooltip placement="top" title={$t(btnTitle)}>
<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>
}</>
);
@@ -10,11 +10,11 @@ export interface TagWithPermission extends TagProps{
export default function TagWithPermission(props:TagWithPermission){
const {access,onClose} = props
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
const {accessData,checkPermission} = useGlobalContext()
const {accessData,checkPermission,accessInit} = useGlobalContext()
const lastAccess = useMemo(()=>{
if(!access) return true
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
},[access, accessData,checkPermission])
},[access, accessData,checkPermission,accessInit])
useEffect(()=>{
access ? setEditAccess(lastAccess) : setEditAccess(true)
@@ -1,16 +0,0 @@
import {forwardRef} from 'react';
import type { PickerProps } from 'antd/es/date-picker/generatePicker';
import type { Moment } from 'moment';
import DatePicker from './DatePicker';
export interface TimePickerProps extends Omit<PickerProps<Moment>, 'picker'> {}
const TimePicker = forwardRef<unknown, TimePickerProps>((props, ref) => (
<DatePicker {...props} picker="time" mode={undefined} ref={ref} />
));
TimePicker.displayName = 'TimePicker';
export default TimePicker;
@@ -4,6 +4,7 @@ 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;
@@ -94,12 +95,12 @@ const disabledDate: RangePickerProps['disabledDate'] = (current) => {
return (
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
{!hideTitle && <label className={`whitespace-nowrap `}></label>}
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
<Radio.Button value="hour">1</Radio.Button>
<Radio.Button value="day">24</Radio.Button>
<Radio.Button value="threeDays">3</Radio.Button>
<Radio.Button className="rounded-e-none" value="sevenDays">7</Radio.Button>
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
<Radio.Button className="rounded-e-none" value="sevenDays">{$t('近7天')}</Radio.Button>
</Radio.Group>
<DatePicker.RangePicker
value={datePickerValue}
@@ -0,0 +1,18 @@
import JoyRide, { Props } from 'react-joyride'
const Tour = (props: Partial<Props>) => {
return (
<>
<JoyRide
continuous={true}
showSkipButton={false}
showProgress={true}
scrollToFirstStep
scrollOffset={400}
{...props}
/>
</>
)
}
export default Tour
@@ -1,28 +0,0 @@
.transfer-table-member,
.transfer-table-api{
:global .ant-table-wrapper .ant-table-thead >tr>th,
:global .ant-table-wrapper .ant-table-thead >tr>td{
font-weight: normal;
background-color: var(--MAIN_BG);
border:none;
}
:global .ant-table-wrapper .ant-table-tbody-virtual .ant-table-cell{
border:none;
}
:global .rc-virtual-list-scrollbar.rc-virtual-list-scrollbar-horizontal{
display: none;
}
:global .ant-table-wrapper .ant-table-tbody .ant-table-row > .ant-table-cell-row-hover{
background: #EBEEF2;
}
:global .ant-table-wrapper .ant-table-tbody .ant-table-row.ant-table-row-selected > .ant-table-cell-row-hover{
background: #EBEEF2;
}
:global .ant-table-thead >tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before{
display: none;
}
}
@@ -1,193 +0,0 @@
import { Input,Table} from "antd";
import {forwardRef, KeyboardEventHandler, Ref, useCallback, useEffect, useImperativeHandle, useRef, useState} from "react";
import styles from './TransferTable.module.css'
import {CloseOutlined, SearchOutlined} from "@ant-design/icons";
import {debounce} from "lodash-es";
import {ColumnsType} from "antd/es/table";
export type TransferTableProps<T> = {
request?:(k?:string)=>Promise<{data:T[],success:boolean}>
columns: ColumnsType<T>
primaryKey:string
onSelect:(selectedData:T[])=>void
tableType?:'member'|'api'
disabledData:string[]
searchPlaceholder?:string
}
export type TransferTableHandle<T> = {
selectedData: () => T[];
selectedRowKeys: () => React.Key[];
}
const TransferTable = 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 tblRef: Parameters<typeof Table>[0]['ref'] = useRef(null);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>(disabledData);
const [selectedData, setSelectedData] = useState<Array<T>>([])
const [dataSource, setDataSource] = useState<T[]>([])
const [searchWord, ] = useState<string>('')
const [loading, setLoading] = useState<boolean>(false)
const parentRef = useRef<HTMLDivElement>(null);
const [tableHeight, setTableHeight] = useState(window.innerHeight * 80 / 100 );
const [tableShow, setTableShow] = useState(false);
useImperativeHandle(ref, () =>({
selectedData: () => selectedData,
selectedRowKeys: () => selectedRowKeys,}))
const handlerLeftTableClick = (record:T & {[k:string]:string})=>{
if(disabledData.indexOf(record[primaryKey||'id' as string] )!== -1) return
const tmpSelectedRowKeys = [...selectedRowKeys];
if (tmpSelectedRowKeys.indexOf(record[primaryKey||'id' as string]) !== -1) {
tmpSelectedRowKeys.splice(tmpSelectedRowKeys.indexOf(record[primaryKey||'id' as string]), 1);
} else {
tmpSelectedRowKeys.push(record[primaryKey||'id' as string]);
}
setSelectedRowKeys(tmpSelectedRowKeys);
let tmpSelectedData = [...selectedData]
if(tmpSelectedData.filter((x: T)=>x[primaryKey || 'id'] === record[primaryKey||'id' as string]).length > 0){
tmpSelectedData = tmpSelectedData.filter((x: T)=>x[primaryKey || 'id'] !== record[primaryKey||'id' as string])
}else{
tmpSelectedData.push(record)
}
setSelectedData(tmpSelectedData)
}
// const handlerRightTableClick = (record:T & {[k:string]:string})=>{
// const tmpSelectedRowKeys = [...selectedRowKeys];
// if (tmpSelectedRowKeys.indexOf(record[primaryKey||'id' as string]) >= 0) {
// tmpSelectedRowKeys.splice(tmpSelectedRowKeys.indexOf(record[primaryKey||'id' as string]), 1);
// }
// setSelectedRowKeys(tmpSelectedRowKeys);
// let tmpSelectedData = [...selectedData]
// tmpSelectedData = tmpSelectedData.filter((x: {[k:string]:string})=>x[primaryKey || 'id'] !== record[primaryKey||'id' as string])
// setSelectedData(tmpSelectedData)
// }
const onSelectChange = (newSelectedRowKeys: React.Key[], selectedRow:T[]) => {
setSelectedRowKeys(newSelectedRowKeys);
setSelectedData(selectedRow.filter((x:T)=>disabledData.indexOf(x[primaryKey || 'id'] as string) === -1))
};
const removeItem = (item:T )=>{
setSelectedRowKeys(selectedRowKeys.filter((x)=>{return x!==item[primaryKey || 'id']}))
setSelectedData((prevData)=>prevData.filter((x:T)=>(x[primaryKey || 'id'] !== item[primaryKey || 'id'])))
}
useEffect(() => {
onSelect && onSelect(selectedData)
}, [selectedData]);
const operations = [
{
title: '操作',
key: 'option',
width: 40,
valueType: 'option',
render: (_: React.ReactNode, entity: T) => [
<CloseOutlined className="p-0 w-full h-full hover:bg-bar-theme" onClick={()=>removeItem(entity as T)}/>
],
}
]
const onSearchWordChange = (e: KeyboardEventHandler<HTMLInputElement>)=>{
getDataSource(e.target.value)
}
const getDataSource = (curSearchWord?:string)=>{
setLoading(true)
request && request(curSearchWord ?? searchWord).then((res)=>{
const {data,success} = res
setDataSource(success? data : [])
}).finally(()=>{setLoading(false)})
}
const debouncedSearch = useCallback(
debounce(onSearchWordChange, 600),[]
)
useEffect(() => {
getDataSource()
const handleResize = () => {
if (parentRef.current) {
const res = parentRef.current.getBoundingClientRect();
setTableHeight(res.height - 32 - 12 * 2 -42 -2)
setTimeout(()=>setTableShow(true),100)
// setTableWidth(res.width / 2 - 20 - 2 - 40)
// const height = res.height -( noTop ? 0 :52) - (dragSortKey ? 0 : 53) - 40;// 减去顶部按钮、底部分页、表头高度
// height && setTableHeight(height);
}
};
const debouncedHandleResize = debounce(handleResize, 200);
// 创建一个 ResizeObserver 来监听高度变化
const resizeObserver = new ResizeObserver(debouncedHandleResize);
// 开始监听
if (parentRef.current ) {
resizeObserver.observe(parentRef.current);
}
// 清理函数
return () => {
resizeObserver.disconnect();
};
}, []);
return (
<div ref={parentRef} className={styles['transfer-table-'+`${tableType === 'member'? 'member':'api'}`] + ` flex flex-1 w-[550px] min-h-[404px] overflow-hidden`}>
<div className="flex flex-col border-[1px] border-solid border-BORDER w-[calc(50%-10px)]">
<Input className=" m-btnbase w-[calc(100%-24px)]" onChange={ debouncedSearch} onPressEnter={onSearchWordChange} allowClear placeholder={searchPlaceholder || "请输入"} prefix={<SearchOutlined className="cursor-pointer" onClick={()=>{onSearchWordChange}}/>} />
{tableShow && <Table
columns={columns}
virtual
size="small"
scroll={{ x:100 ,y:tableHeight}}
rowKey={primaryKey || 'id'}
dataSource={dataSource}
pagination={false}
loading={loading}
ref={tblRef}
rowSelection={
{
selectedRowKeys,
onChange: onSelectChange,
columnWidth: 40,
getCheckboxProps: (record: {[k:string]:string}) => ({
disabled: disabledData.length > 0 && disabledData?.indexOf(record[primaryKey || 'id'] as string) !== -1, // Column configuration not to be checked
name: record[primaryKey || 'id'],
}),
}
}
onRow={(record) => ({
onClick: () => {
handlerLeftTableClick(record);
}
})}
/>}
</div>
<div className= {`flex flex-col w-[calc(50%-10px)] ml-[20px] border-[1px] border-solid border-BORDER`}>
<div className="leading-[32px] mt-btnybase mb-[54px]">
<span className="ml-[20px]">{tableType === 'member' ? '成员' : ' API'} <span className="mr-[4px]">({selectedData.length})</span></span>
</div>
<Table
virtual
scroll={{x:200,y:tableHeight}}
size="small"
columns={[...columns?.map((col)=>({...col,className:(col.className || ' ') + 'pl-[20px]'})),...operations]}
showHeader={false}
rowKey={primaryKey}
dataSource={selectedData}
pagination={false}
ref={tblRef}
loading={loading}
/>
</div>
</div>);
})
export default TransferTable
@@ -0,0 +1,13 @@
import { $t } from "@common/locales"
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 */
export const TranslateWord = ()=>{
return (
<>
{$t('上传文件')}
{$t('替换文件')}
{$t('是否放行')}
{$t('监控')}
</>
)
}
@@ -1,120 +0,0 @@
import {App, Avatar, Dropdown, MenuProps} from "antd";
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
import {FC, useEffect, useRef, useState} from "react";
import {ResetPsw, ResetPswHandle} from "@common/components/aoplatform/ResetPsw.tsx";
import {useFetch} from "@common/hooks/http.ts";
import {BasicResponse, STATUS_CODE} from "@common/const/const.ts";
import {useNavigate} from "react-router-dom";
import { UserInfoType, UserProfileHandle } from "@common/const/type.ts";
import { UserProfile } from "./UserProfile.tsx";
import AvatarPic from '@common/assets/avatar_default.svg'
const UserAvatar: FC = () => {
const { modal,message } = App.useApp()
const { dispatch,resetAccess,getGlobalAccessData} = useGlobalContext()
const [userInfo,setUserInfo] = useState<UserInfoType>()
const resetPswRef = useRef<ResetPswHandle>(null)
const userProfileRef = useRef<UserProfileHandle>(null)
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 || '操作失败')
}
})
}
useEffect(() => {
getUserInfo()
getGlobalAccessData()
}, []);
const logOut = ()=>{
fetchData<BasicResponse<null>>('account/logout',{method:'GET'}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
dispatch({type:'LOGOUT'})
resetAccess()
message.success(msg || '退出成功,将跳转至登录页')
navigate('/login')
}else{
message.error(msg ||'操作失败')
}
})
}
const items: MenuProps['items'] = [
// {
// key: '1',
// label: (
// <a target="_blank" rel="noopener noreferrer" onClick={()=>openModal('userSetting')}>
// 用户设置
// </a>
// ),
// },
// {
// key: '2',
// label: (
// <a target="_blank" rel="noopener noreferrer" onClick={()=>openModal('resetPsw')}>
// 修改密码
// </a>
// ),
// },
{
key: '3',
label: (
<a className="block leading-[32px]" target="_blank" rel="noopener noreferrer" onClick={logOut}>
退
</a>
),
},
];
const openModal = (type:'userSetting'|'resetPsw')=>{
let title:string = ''
let content:string|React.ReactNode = ''
switch (type){
case 'userSetting':
title='用户设置'
content=<UserProfile ref={userProfileRef} entity={userInfo}/>
break;
case 'resetPsw':
title='重置密码'
content=<ResetPsw ref={resetPswRef} entity={userInfo} />
break;
}
modal.confirm({
title,
content,
onOk:()=>{
switch (type){
case 'userSetting':
return userProfileRef.current?.save().then((res)=>{if(res === true) getUserInfo()})
case 'resetPsw':
return resetPswRef.current?.save().then((res)=>{if(res === true) logOut()})
}
},
width:600,
okText:'确认',
cancelText:'取消',
closable:true,
icon:<></>,
})
}
return (
<Dropdown menu={{ items }}>
<span className="flex items-center"><Avatar className="mx-[6px]" src={AvatarPic || userInfo?.avatar}></Avatar><span>{userInfo?.username||'unknown'}</span></span>
</Dropdown>
)
}
export default UserAvatar
@@ -1,152 +0,0 @@
import {App, Form, Input, Upload, UploadFile, UploadProps} from "antd";
import {forwardRef, useEffect, useImperativeHandle, useState} from "react";
import {useFetch} from "@common/hooks/http.ts";
import {RcFile, UploadChangeParam} from "antd/es/upload";
import {LoadingOutlined} from "@ant-design/icons";
import {BasicResponse, STATUS_CODE} from "@common/const/const.ts";
import { UserInfoType, UserProfileHandle, UserProfileProps } from "@common/const/type";
import { getImgBase64 } from "@common/utils/dataTransfer";
import { Icon } from "@iconify/react/dist/iconify.js";
export const UserProfile = forwardRef<UserProfileHandle,UserProfileProps>((props,ref)=>{
const { message } = App.useApp()
const [form] = Form.useForm();
const {entity,} = props
const {fetchData} = useFetch()
const [imageBase64, setImageBase64] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [imageUrl, setImageUrl] = useState<string>();
const save:()=>Promise<boolean | string> = ()=>{
return new Promise((resolve, reject)=>{
form.validateFields().then((value)=>{
fetchData<BasicResponse<null>>('account/profile',{method:'PUT',eoBody:value}).then(response=>{
const {code,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || '操作成功!')
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo)=> reject(errorInfo))
})
}
useImperativeHandle(ref, ()=>({
save
})
)
const handleChange: UploadProps['onChange'] = (info: UploadChangeParam<UploadFile>) => {
if (info.file.status === 'uploading') {
setLoading(true);
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
getImgBase64(info.file.originFileObj as RcFile, (url) => {
setLoading(false);
setImageUrl(url);
});
}
if (info.fileList.length === 0) {
// 如果文件被移除,清除 logo 字段
form.setFieldValue( "avatar", null );
}
};
const uploadButton = (
<div>
<div className="h-[68px] w-[68px] border-[1px] border-dashed border-BORDER flex items-center justify-center rounded bg-bar-theme cursor-pointer" style={{ marginTop: 8 }}>
{loading ? <LoadingOutlined /> : <Icon icon="ic:baseline-add" width="18" height="18" className='mr-[2px]'/>}</div>
</div>
);
const beforeUpload = (file: RcFile) => {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
setImageBase64(e.target?.result as string);
form.setFieldValue("avatar",e.target?.result)
};
reader.readAsDataURL(file);
return false;
};
useEffect(() => {
form.setFieldsValue(entity)
}, []);
const normFile = (e: unknown) => {
if (Array.isArray(e)) {
return e;
}
return( e as {fileList:unknown} )?.fileList;
};
return (<>
<Form
labelAlign='left'
layout='vertical'
scrollToFirstError
form={form}
className="mx-auto mt-mbase "
name="userProfile"
// labelCol={{ span: 8 }}
// wrapperCol={{ span: 10}}
autoComplete="off"
>
<Form.Item<UserInfoType>
label="账号"
name="username"
rules={[{ required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="账号" disabled={true}/>
</Form.Item>
<Form.Item<UserInfoType>
label="昵称"
name="nickname"
rules={[{ required: true, message: '必填项',whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入"/>
</Form.Item>
<Form.Item<UserInfoType>
label="头像"
name="avatar"
valuePropName="fileList" getValueFromEvent={normFile}
>
<Upload
listType="picture"
beforeUpload={beforeUpload}
onChange={handleChange}
showUploadList={false}
maxCount={1}
>
{imageBase64 ? <img src={imageBase64} alt="Logo" style={{ maxWidth: '200px'}} /> : uploadButton}
</Upload>
</Form.Item>
<Form.Item<UserInfoType>
label="邮箱"
name="email"
rules={[{ required: true, message: '必填项' ,whitespace:true },{type:'email',message: '输入的不是有效邮箱格式'}]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入"/>
</Form.Item>
<Form.Item<UserInfoType>
label="手机号码"
name="phone"
rules={[{pattern:/^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$/, message:'输入的不是有效手机号码',warningOnly: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder="请输入"/>
</Form.Item>
</Form>
</>)
})
@@ -1,8 +1,10 @@
import { Tooltip } from "antd";
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";
type WithPermissionProps = {
access?:string | string[]
@@ -15,17 +17,16 @@ type WithPermissionProps = {
const WithPermission = ({access, tooltip, children,disabled, showDisabled = true}:WithPermissionProps) => {
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
const {accessData,checkPermission} = useGlobalContext()
const {accessData,checkPermission,accessInit} = useGlobalContext()
const lastAccess = useMemo(()=>{
if(!access) return true
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
},[access, accessData])
},[access, accessData,checkPermission,accessInit])
useEffect(()=>{
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
access && setEditAccess(lastAccess)
console.log('editAccess',editAccess, children,children?.type?.displayName,showDisabled, children?.type?.displayName !== 'Button' && showDisabled)
},[lastAccess,disabled])
@@ -36,7 +37,7 @@ const WithPermission = ({access, tooltip, children,disabled, showDisabled = true
{editAccess && disabled && <Tooltip title={tooltip}>
{ cloneElement(children, {disabled:true})}
</Tooltip>}
{!editAccess && (children?.type?.displayName !== 'Button' && showDisabled ) && <Tooltip title={tooltip ?? "暂无操作权限,请联系管理员分配。"}>
{!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled ) && <Tooltip title={tooltip ?? $t("暂无操作权限,请联系管理员分配。")}>
{ cloneElement(children, {disabled:true})}
</Tooltip>}
@@ -2,6 +2,7 @@
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
@@ -135,27 +136,9 @@ export const ArrayItemBlankComponent = forwardRef(
{index !== resList.length - 1 && (
<div style={{ display: 'inline-block' }}>
{n[dataFormat[0].key] && (
<a
className="array_item_addition ant-btn-text anticon"
onClick={() => addLine(index)}
>
<span>
<svg className="iconpark-icon">
<use href="#add-circle"></use>
</svg>
</span>
</a>
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
)}
<a
className="array_item_addition ant-btn-text anticon"
onClick={() => removeLine(index)}
>
<span>
<svg className="iconpark-icon">
<use href="#reduce-one"></use>
</svg>
</span>
</a>
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
</div>
)}
</div>
@@ -39,6 +39,7 @@ import {
} from '@formily/antd-v5'
import { CustomCodeboxComponent } from './CustomCodeboxComponent.tsx'
import { SimpleMapComponent } from './SimpleMapComponent.tsx'
import { $t } from '@common/locales/index.ts'
const SchemaField = createSchemaField({
components: {
@@ -96,7 +97,7 @@ export const CustomDialogComponent = forwardRef(
className="ant-formily-array-base-config"
onClick={() => {
const dialog = FormDialog(
editPage ? `编辑${title || ''}` : `添加${title || ''}`,
editPage ? $t('编辑(0)',[title||'']) : $t('添加(0)',[title||'']),
() => {
return (
<FormLayout
@@ -1,13 +1,15 @@
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 = '请输入Key',
placeholderValue = '请输入Value'
placeholderKey = $t('请输入Key'),
placeholderValue = $t('请输入Value')
} = props
const [kvList, setKvList] = useState(
@@ -88,27 +90,9 @@ export const SimpleMapComponent = forwardRef(
{index !== kvList.length - 1 && (
<div style={{ display: 'inline-block' }}>
{n.key && (
<a
className="arrayItemAddition ant-btn-text anticon"
onClick={() => addLine(index)}
>
<span>
<svg className="iconpark-icon">
<use href="#add-circle"></use>
</svg>
</span>
</a>
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
)}
<a
className="arrayItemAddition ant-btn-text anticon"
onClick={() => removeLine(index)}
>
<span>
<svg className="iconpark-icon">
<use href="#reduce-one"></use>
</svg>
</span>
</a>
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
</div>
)}
</div>
@@ -1,4 +1,4 @@
import {forwardRef, useEffect, useImperativeHandle, useState} from "react";
import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react";
import { action } from '@formily/reactive'
import {
FormItem,
@@ -43,18 +43,19 @@ import {CustomDialogComponent} from "@common/components/aoplatform/formily2-cust
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, STATUS_CODE} from "@common/const/const.ts";
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {useFetch} from "@common/hooks/http.ts";
import {App} from "antd";
import { config } from "process";
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}.*`)
@@ -66,10 +67,39 @@ export const DynamicRender = (props) => {
}
}, [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={renderSchema}
schema={translatedRenderSchema}
onlyRenderProperties
/>
)
@@ -143,6 +173,11 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
const {fetchData} = useFetch()
const form = createForm({ validateFirst: type === 'edit' })
form.setInitialValues(initFormValue || {})
const { state } = useGlobalContext()
useEffect(()=>{
setValidateLanguage(state.language === 'cn' ? 'zh-CN' : 'en-US')
},[state.language])
const pluginEditSchema = {
type: 'object',
@@ -158,7 +193,7 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
properties: {
id: {
type: 'string',
title: 'ID',
title: $t('ID'),
required: true,
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
'x-decorator': 'FormItem',
@@ -169,13 +204,13 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
},
'x-component': 'Input',
'x-component-props': {
placeholder: '支持字母开头、英文数字中横线下划线组合',
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet),
},
'x-disabled': type === 'edit'
},
title: {
type: 'string',
title: '名称',
title: $t('名称'),
required: true,
'x-decorator': 'FormItem',
'x-decorator-props': {
@@ -185,12 +220,12 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
},
'x-component': 'Input',
'x-component-props': {
placeholder: '请输入名称',
placeholder: $t(PLACEHOLDER.input),
}
},
driver: {
type: 'string',
title: 'Driver',
title: $t('Driver'),
required: true,
'x-decorator': 'FormItem',
'x-decorator-props': {
@@ -207,7 +242,7 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
},
description: {
type: 'string',
title: '描述',
title: $t('描述'),
'x-decorator': 'FormItem',
'x-decorator-props': {
labelCol:4,
@@ -216,7 +251,7 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
},
'x-component': 'Input.TextArea',
'x-component-props': {
placeholder: '请输入描述',
placeholder: $t(PLACEHOLDER.input),
}
},
config: {
@@ -238,11 +273,11 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> reject(errorInfo))
}).catch((errorInfo:unknown)=> reject(errorInfo))
@@ -262,8 +297,8 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
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 || '操作失败')
reject(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
})
@@ -282,7 +317,7 @@ export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle
}
return (
<div className="pl-[12px]">
<FormProvider form={form}>
<FormProvider form={form} >
<SchemaField
schema={pluginEditSchema}
scope={{ useAsyncDataSource, getSkillData, form }}
@@ -1,19 +1,21 @@
import PageList from "@common/components/aoplatform/PageList.tsx";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
import {App, Divider, Spin} from "antd";
import {useEffect, useRef, useState} from "react";
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, ProColumns} from "@ant-design/pro-components";
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, STATUS_CODE} from "@common/const/const.ts";
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";
type DynamicTableField = {
name: string,
@@ -95,7 +97,7 @@ export default function IntelligentPluginList(){
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([]);
const [tableHttpReload, setTableHttpReload] = useState(true);
const [columns,setColumns] = useState<ProColumns<DynamicTableItem>[] >([])
const [columns,setColumns] = useState<DynamicTableField[] >([])
const {fetchData} = useFetch()
const pageListRef = useRef<ActionType>(null);
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
@@ -105,6 +107,7 @@ export default function IntelligentPluginList(){
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
const location = useLocation().pathname
const {accessPrefix} = useOutletContext<{accessPrefix:string}>()
const {state} = useGlobalContext()
const getIntelligentPluginTableList=(params:ParamsType & {
@@ -130,20 +133,7 @@ export default function IntelligentPluginList(){
message.destroy();
if(res.code === STATUS_CODE.SUCCESS){
getConfig(res.data)
setColumns(res.data.basic.fields.map((field:DynamicTableField, index:number)=>({
title: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:x, value:x}}),
render:(_: unknown, entity: { [x: string]: string; })=> {
return <span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>{(entity[field.name] as string)}</span>
},
}:{}),
})))
setColumns(res.data.basic.fields)
setTableListDataSource(res.data.list);
return ({ data: res.data.list, success: true,total:res.data.total });
}else{
@@ -154,12 +144,29 @@ export default function IntelligentPluginList(){
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') ? '资源配置': '日志配置'},
{title:location.includes('resourcesettings') ? $t('资源'): $t('日志')},
{
title
}
@@ -178,23 +185,23 @@ export default function IntelligentPluginList(){
setRenderSchema(resp.data.render)
return Promise.resolve(resp.data.render)
}
return Promise.reject(resp.msg || '操作失败')
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
})
}
const operation:ProColumns<DynamicTableItem>[] =[
const operation:PageProColumns<DynamicTableItem>[] =[
{
title: '操作',
title: COLUMNS_TITLE.operate,
key: 'option',
width: 150,
fixed:'right',
valueType: 'option',
btnNums:3,
render: (_: React.ReactNode, entity: DynamicTableItem) => [
<TableBtnWithPermission access={`${accessPrefix}.publish`} key="publish" onClick={()=>{openModal('publish',entity)}} btnTitle={entity.status === '已发布' ? '下线' : '上线'}/>,
<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" onClick={()=>{openDrawer('edit',entity)}} btnTitle="查看"/>,
<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" onClick={()=>{openModal('delete',entity)}} btnTitle="删除"/>,
<TableBtnWithPermission access={`${accessPrefix}.delete`} key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle={$t("删除")}/>,
],
}
]
@@ -213,11 +220,11 @@ export default function IntelligentPluginList(){
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
resolve(true)
}else{
message.error(msg || '操作失败')
reject(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
reject(msg || $t(RESPONSE_TIPS.error))
}
})
})
@@ -239,7 +246,7 @@ export default function IntelligentPluginList(){
}
setCurDetail(data.info)
}else{
message.error(msg || '操作失败')
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>setDrawerLoading(false))
break;
@@ -254,25 +261,25 @@ export default function IntelligentPluginList(){
let content:string|React.ReactNode = ''
switch (type){
case 'publish':{
message.loading('正在操作')
await fetchData<BasicResponse<DynamicPublish>>(`dynamic/${moduleId}/${entity!.status === '已发布' ? 'offline':'online'}`, {
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 || '操作成功!')
message.success(msg || $t(RESPONSE_TIPS.success))
return Promise.resolve(true)
} else {
message.error(msg || '操作失败')
return Promise.reject(msg || '操作失败')
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><span className="text-status_fail"></span></span>
content=<span>{$t(DELETE_TIPS.default)}</span>
break;
}
@@ -287,11 +294,11 @@ export default function IntelligentPluginList(){
}
},
width: type === 'delete'? 600 : 900,
okText:'确认',
okText:$t('确认'),
okButtonProps:{
disabled:false
},
cancelText:'取消',
cancelText:$t('取消'),
closable:true,
icon:<></>,
footer:(_, { OkBtn, CancelBtn }) =>{
@@ -314,10 +321,10 @@ export default function IntelligentPluginList(){
return (<>
<PageList
ref={pageListRef}
columns = {[...columns,...operation]}
columns = {[...translatedCol,...operation]}
request={(params)=>getIntelligentPluginTableList(params)}
addNewBtnTitle={`添加${pluginName}`}
searchPlaceholder={`搜索${pluginName}名称`}
addNewBtnTitle={$t('添加(0)',[$t(pluginName)])}
searchPlaceholder={$t('搜索(0)名称',[$t(pluginName)])}
onChange={() => {
setTableHttpReload(false)
}}
@@ -326,7 +333,7 @@ export default function IntelligentPluginList(){
onSearchWordChange={(e)=>{setSearchWord(e.target.value);setTableHttpReload(true);setTableHttpReload(true)}}
/>
<DrawerWithFooter title={`${drawerType === 'add' ? '添加' : '编辑'}${pluginName }`} open={drawerOpen} onClose={()=>{setCurDetail(undefined);setDrawerOpen(false)}} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} submitAccess=''>
<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!}
@@ -1,3 +1,5 @@
import { $t } from "@common/locales"
export type PARAM_TYPE =
| 'string'
| 'float'
@@ -113,7 +115,7 @@ export const CODE_SNIPPETS: CODE_LANGUAGE_SNIPPETS_TYPE[] = [
]
},
{
label: '微信小程序',
label: $t('微信小程序'),
value: 21,
isLeaf: true
},
@@ -2,6 +2,7 @@ import { cloneDeep } from 'lodash-es'
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';
function sameNameToParams(params: unknown) {
params = cloneDeep(params)
@@ -522,7 +523,7 @@ export function generateCode(
if (multipart) {
code =
'<?php\r\n' +
'//获取文件,需填路径 \r\n' +
`//${$t('获取文件,需填路径')} \r\n` +
'$file_path = "";\r\n' +
`$file_name = "${langTmp.fileValue}";\r\n` +
'$client = new http\\Client;\r\n' +
@@ -599,7 +600,7 @@ export function generateCode(
if (multipart) {
code =
'<?php\r\n\r\n' +
'//获取文件,需填路径 \r\n' +
`//${$t('获取文件,需填路径')} \r\n` +
'$file_path = ""; \r\n\r\n' +
'$curl = curl_init();\r\n\r\n' +
'curl_setopt_array($curl, array(\r\n' +
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useMemo } from 'react';
import { Cascader } from 'antd';
import CODE_LANG from '@common/const/code/const';
import type { DefaultOptionType } from 'antd/es/cascader';
@@ -12,6 +12,8 @@ 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
@@ -35,6 +37,7 @@ type CodeSnippetCompoType = {
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
@@ -156,7 +159,7 @@ type CodeSnippetCompoType = {
let tempCode = ''
const getCode = (language: number | string) => {
if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) {
tempCode = '暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例'
tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例')
setCode(tempCode)
return
}
@@ -186,16 +189,17 @@ type CodeSnippetCompoType = {
(option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1,
);
const [placeholderTxt, setPlaceholderTxt] = 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]"></span><Cascader
options={CODE_LANG}
<Codebox extraContent={<><span className="ml-[12px]">{$t('编程语言')}</span><Cascader
options={codeLangOptions}
onChange={(value,record) => onChange(value as unknown as number[],record)}
placeholder={placeholderTxt}
value={lang} // 当前的值
@@ -1,4 +1,4 @@
import * as Mock from 'mockjs'
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',
@@ -127,5 +127,5 @@ export function tranformJson(
return result.join('\n') //分隔符会换行
}
export function getRandomDataByType(type: PARAM_TYPE) {
return Mock.Random[Object.keys(Mock.Random).includes(type) ? type : 'string'](0, 5)
return Random[Object.keys(Random).includes(type) ? type : 'string'](0, 5)
}
@@ -5,14 +5,15 @@ 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[] | []
}
const DEFAULT_RESULT_LIST = [
{id:'success',name:'成功示例',httpCode:'200',content:''},
{id:'failed',name:'失败示例',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']
@@ -76,7 +77,7 @@ export function ResponseExampleCompo ({ editorRef,title,detail,mode='view' }: {e
value={item.httpCode}
status={item.httpCode ? '' : 'error'}
onSelect={(value)=>updateResultList(item.id,'httpCode',value)}
placeholder="HTTP 状态码"
placeholder={$t("HTTP 状态码")}
/>
}
{mode === 'view' ?
@@ -86,7 +87,7 @@ export function ResponseExampleCompo ({ editorRef,title,detail,mode='view' }: {e
style={{ width: 200 }}
value={item.httpContentType || 'text/html;charset=UTF-8'}
onSelect={(value)=>updateResultList(item.id,'httpContentType',value)}
placeholder="默认 text/html;charset=UTF-8"
placeholder={$t("默认 text/html;charset=UTF-8")}
/>}
</div>
{mode === 'view' ?
@@ -94,7 +95,7 @@ export function ResponseExampleCompo ({ editorRef,title,detail,mode='view' }: {e
{ 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="暂未填写示例"/>
<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)}/>
@@ -11,6 +11,8 @@ import { SystemApiDetail, SystemInsideApiProxyHandle } from "@core/const/system/
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']
@@ -165,7 +167,7 @@ export default function ApiEdit({apiInfo,editorRef,loaded,serviceId, teamId}:{ap
getData: () => {
return proxyRef.current?.validate().then((res)=>{
const name = apiNameRef.current?.getData()
if(!name) return Promise.reject('请填写接口名称')
if(!name) return Promise.reject($t('请填写接口名称'))
const newData :{apiInfo:Partial<SystemApiDetail>}= {
apiInfo:{
info:{
@@ -200,7 +202,7 @@ export default function ApiEdit({apiInfo,editorRef,loaded,serviceId, teamId}:{ap
getData:()=>description
}))
return (
<Input.TextArea className="w-full border-none" value={description} onChange={(e)=>setDescription(e.target.value)} placeholder="请输入"/>
<Input.TextArea className="w-full border-none" value={description} onChange={(e)=>setDescription(e.target.value)} placeholder={$t(PLACEHOLDER.input)}/>
)
})
@@ -229,25 +231,25 @@ export default function ApiEdit({apiInfo,editorRef,loaded,serviceId, teamId}:{ap
<Box>
<Stack direction="column" spacing={3}>
<ApiName apiInfo={apiInfo} ref={apiNameRef}/>
<Collapse key="description" title='详细说明'>
<Collapse key="description" title={$t('详细说明')}>
<Description initDescription={apiInfo?.description} ref={descriptionRef}/>
</Collapse>
{
apiInfo?.match && apiInfo.match?.length > 0 &&
<ApiMatch title='高级匹配' rows={apiInfo?.match.map((x)=>{x.id = uuidv4();return x})} />
<ApiMatch title={$t('高级匹配')} rows={apiInfo?.match.map((x)=>{x.id = uuidv4();return x})} />
}
<Collapse title='转发配置' key="proxy" >
<Collapse title={$t('转发配置')} key="proxy" >
<SystemInsideApiProxy className="m-[12px] px-[12px]" initProxyValue={apiInfo?.proxy} serviceId={serviceId!} ref={proxyRef} />
</Collapse>
<Collapse title='请求参数' key="request" >
<Collapse title={$t('请求参数')} key="request" >
<ApiRequestEditor editorRef={requestRef} apiInfo={apiInfo?.doc} loaded={loaded} />
</Collapse>
<Collapse title='返回值' key="response">
<Collapse title={$t('返回值')} key="response">
<ApiResponseEditor editorRef={responseRef} apiInfo={apiInfo?.doc} loaded={loaded}/>
</Collapse>
<ResponseExampleCompo editorRef={resultListRef} mode='edit' title='返回示例' detail={resultList}/>
<ResponseExampleCompo editorRef={resultListRef} mode='edit' title={$t('返回示例')} detail={resultList}/>
</Stack>
</Box>
</Box>
@@ -15,11 +15,12 @@ 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"> API</span>
<span className="text-disabled">{$t('测试 API')}</span>
</Tooltip>
)
}
@@ -90,13 +91,13 @@ export default function ApiPreview(props:{testClick?:()=>void, entity:ApiDetail}
{
requestParams?.headerParams?.length > 0 &&
<HeaderFields title='请求 Header' rows={requestParams?.headerParams}
<HeaderFields title={$t('请求 Header')} rows={requestParams?.headerParams}
onMoreSettingChange={setCurrentMoreSettingParam} />
}
{requestBodyList?.length > 0 &&
<MessageBodyComponent
title="请求 Body"
title={$t("请求 Body")}
rows={requestBodyList}
contentType={requestParams?.bodyParams[0]?.contentType}
onMoreSettingChange={setCurrentMoreSettingParam}
@@ -105,40 +106,40 @@ export default function ApiPreview(props:{testClick?:()=>void, entity:ApiDetail}
{
requestParams?.queryParams?.length > 0 &&
<HeaderFields title='Query 参数' rows={requestParams?.queryParams}
<HeaderFields title={$t('Query 参数')} rows={requestParams?.queryParams}
onMoreSettingChange={setCurrentMoreSettingParam} />
}
{
requestParams?.restParams?.length > 0 &&
<HeaderFields title='Rest 参数' rows={requestParams?.restParams}
<HeaderFields title={$t('Rest 参数')} rows={requestParams?.restParams}
onMoreSettingChange={setCurrentMoreSettingParam}/>
}
{/*<h3 className="text-lg mb-btnybase font-normal flex items-center">请求示例代码</h3>*/}
<CodeSnippetCompo
title='请求示例代码'
title={$t('请求示例代码')}
api={entity}
extraContent={ testClick ? <div className="ml-5">
<Tooltip >
<WithPermission access="" >
<Button type='primary' onClick={handleTest} size='small' className='w-[114px]'> API</Button>
<Button type='primary' onClick={handleTest} size='small' className='w-[114px]'>{$t('测试 API')}</Button>
</WithPermission>
</Tooltip>
</div> : undefined }
/>
{resultList?.length > 0 && <ResponseExampleCompo title='响应示例' detail={resultList}/>}
{resultList?.length > 0 && <ResponseExampleCompo title={$t('响应示例')} detail={resultList}/>}
{
responseList?.[0]?.responseParams?.headerParams?.length > 0 &&
<HeaderFields title='响应 Header' rows={ responseList?.[0]?.responseParams?.headerParams}
<HeaderFields title={$t('响应 Header')} rows={ responseList?.[0]?.responseParams?.headerParams}
onMoreSettingChange={setCurrentMoreSettingParam} />
}
{responseBodyList?.length > 0 &&
<MessageBodyComponent
title="响应 Body"
title={$t("响应 Body")}
rows={responseBodyList}
contentType={responseList?.[0]?.contentType}
onMoreSettingChange={setCurrentMoreSettingParam}
@@ -1,10 +1,11 @@
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="Binary"
label={$t("Binary")}
multiline
rows={4}
value={value}
@@ -10,6 +10,7 @@ import {
import {MessageDataGrid, MessageDataGridApi} from "../MessageDataGrid";
import {Indicator} from "../../../../Indicator";
import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody';
import { $t } from '@common/locales';
export interface ApiRequestEditorApi {
getData: () => {
@@ -62,7 +63,7 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
const tabs: ApiRequestEditorTab[] = [
{
label: '请求头部',
label: $t('请求头部'),
element: (
<MessageDataGrid
apiRef={headersRef}
@@ -76,12 +77,12 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
dirty: false
},
{
label:'请求体',
label:$t('请求体'),
element: <ApiMessageBody bodyApiRef={bodyRef} mode="request" apiInfo={apiInfo} loaded={innerLoaded}/>,
dirty: false
},
{
label: 'Query 参数',
label: $t('Query 参数'),
element: (
<MessageDataGrid
apiRef={queryRef}
@@ -95,7 +96,7 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
dirty: false
},
{
label: 'REST 参数',
label: $t('REST 参数'),
element: (
<MessageDataGrid
apiRef={restRef}
@@ -123,12 +124,11 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?
return (
<Box sx={{
// borderBottom: 1,
borderColor: 'divider' }}>
<Tabs
value={tabValue}
onChange={handleChange}
aria-label="api request editor"
aria-label={$t("api request editor")}
sx={{
minHeight: tabHeight,
height: tabHeight,
@@ -5,6 +5,7 @@ import {ApiBodyType, BodyParamsType, HeaderParamsType} from "@common/const/api-d
import {Indicator} from "../../../../Indicator";
import { v4 as uuidv4} from 'uuid'
import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody';
import { $t } from '@common/locales';
interface ApiRequestEditorTab {
label: string
@@ -54,7 +55,7 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
const tabs: ApiRequestEditorTab[] = [
{
label: '返回头部',
label: $t('返回头部'),
element: (
<MessageDataGrid
apiRef={headersRef}
@@ -68,7 +69,7 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
dirty: false
},
{
label: '返回值',
label: $t('返回值'),
element: <ApiMessageBody bodyApiRef={bodyRef} mode="response" apiInfo={apiInfo}
loaded={innerLoaded} />,
dirty: false
@@ -93,7 +94,7 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe
<Tabs
value={tabValue}
onChange={handleChange}
aria-label="api request editor"
aria-label={$t("api request editor")}
sx={{
minHeight: tabHeight,
height: tabHeight,
@@ -42,6 +42,7 @@ 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 {
path?: string[]
@@ -251,14 +252,14 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
const getActions = useCallback(
(params: GridRowParams<RenderMessageBody>) => {
const actions = [
<IconButton title="更多设置" name="more" onClick={() => handleOpenMoreSetting(params)} />
<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="添加子参数"
title={$t("添加子参数")}
name="add"
onClick={() => {
const newRow = EmptyRow()
@@ -288,7 +289,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
if (!(isXML && isRoot)) {
actions.unshift(
<IconButton
title="向下添加行"
title={$t("向下添加行")}
name="down-small"
onClick={() => {
const newRow = EmptyRow()
@@ -301,7 +302,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
}
}
if (renderRows.length > 1) {
actions.push(<IconButton title="删除" name="delete" onClick={() => handleRowDelete(params)} />)
actions.push(<IconButton title={$t("删除")} name="delete" onClick={() => handleRowDelete(params)} />)
}
return actions
},
@@ -312,7 +313,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
const columns: (GridColDef<RenderMessageBody> | false)[] = [
messageType === 'Header' && {
field: 'name',
headerName: '标签',
headerName: $t('标签'),
editable: true,
sortable:false,
renderEditCell: (params) => {
@@ -340,7 +341,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
},
messageType !== 'Header' && {
field: 'name',
headerName: '参数名',
headerName: $t('参数名'),
width: 200,
editable: true,
sortable: false,
@@ -373,7 +374,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
<TextField
fullWidth
value={params.value}
placeholder='参数名'
placeholder={$t('参数名')}
onChange={(e) => {
const newValue = e.target.value as string
const rowIndex = params.row.__globalIndex__
@@ -395,7 +396,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
},
messageType === 'Body' && {
field: 'dataType',
headerName: '类型',
headerName: $t('类型'),
sortable: false,
width: 120,
type: 'singleSelect',
@@ -429,7 +430,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
},
{
field: 'isRequired',
headerName: '必需',
headerName: $t('必需'),
headerAlign: 'left',
sortable: false,
type: 'boolean',
@@ -443,7 +444,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
indeterminate={selectAll === 'indeterminate'}
onChange={handleSelectAllChange}
/>
<Typography sx={{fontSize:'14px'}}></Typography>
<Typography sx={{fontSize:'14px'}}>{$t('必需')}</Typography>
</Box>
)
},
@@ -463,7 +464,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
},
{
field: 'description',
headerName: '描述',
headerName: $t('描述'),
sortable: false,
flex: 1,
minWidth: 200,
@@ -484,7 +485,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
paddingRight: theme.spacing(1)
}
}}
placeholder='描述'
placeholder={$t('描述')}
/>
)
}
@@ -492,7 +493,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
{
field: 'paramAttr',
sortable: false,
headerName: '示例',
headerName: $t('示例'),
flex: 1,
minWidth: 200,
editable: true,
@@ -515,8 +516,7 @@ export function MessageDataGrid(props: MessageDataGridProps<RenderMessageBody>)
paddingRight: theme.spacing(1)
}
}}
placeholder='示例'
/>
placeholder={$t('示例')} />
)
}
},
@@ -3,6 +3,7 @@ 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';
interface UriInputProps {
inputValue?: string
@@ -51,7 +52,7 @@ export function UriInput({
return (
<TextField
fullWidth
placeholder="输入 URL 或 cURL"
placeholder={$t("输入 URL 或 cURL")}
value={inputValue}
onChange={handleInputChange}
sx={{
@@ -2,6 +2,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';
interface ApiBasicInfoDisplayProps {
apiName: string
@@ -38,7 +39,7 @@ export default function ApiBasicInfoDisplay(props: Partial<ApiBasicInfoDisplayPr
<Box display="flex">
<Stack direction="row" spacing={1} alignItems="center">
<Chip
label="HTTP"
label={$t("HTTP")}
sx={{
height:'22px',
borderRadius: '4px',
@@ -6,6 +6,7 @@ import {collapseTableSx, previewTableHoverSx} from "../../../PreviewTable";
import {Collapse} from "../../../Collapse";
import { MatchPositionEnum, MatchTypeEnum } from "@core/const/system/const";
import { MatchItem } from "@common/const/type";
import { $t } from "@common/locales";
interface ApiMatchProps {
rows?: MatchItem[]
@@ -29,25 +30,25 @@ export default function ApiMatch({ rows = [], title, loading = false }: ApiMatch
const columns: GridColDef<MatchItem>[] = [
{
field: 'key',
headerName: '参数名',
headerName: $t('参数名'),
hideable: false,
width:200
},
{
field: 'position',
headerName: '参数位置',
headerName: $t('参数位置'),
valueGetter: (params) => MatchPositionEnum[params.row.position],
width:160
},
{
field: 'matchType',
headerName: '匹配类型',
headerName: $t('匹配类型'),
valueGetter: (params) => MatchTypeEnum[params.row.matchType],
width:160
},
{
field: 'pattern',
headerName: '参数值',
headerName: $t('参数值'),
flex:1
}
]
@@ -6,6 +6,7 @@ import { SystemApiProxyType, ProxyHeaderItem } from "@core/const/system/type"
import { previewTableHoverSx, collapseTableSx } from "../../../PreviewTable"
import { RenderMessageBody } from "../MessageBody"
import { Collapse } from "../../../Collapse"
import { $t } from "@common/locales"
interface HeaderFieldsProps {
proxyInfo:SystemApiProxyType
@@ -30,19 +31,19 @@ export default function ApiProxy({ proxyInfo, title, loading = false, onMoreSett
const columns: GridColDef<ProxyHeaderItem>[] = [
{
field: 'key',
headerName: '参数名',
headerName: $t('参数名'),
width: 200,
hideable: false
},
{
field: 'optType',
headerName: '操作类型',
valueGetter: (params) => params.row.optType === 'ADD'?'新增或修改':'删除',
headerName: $t('操作类型'),
valueGetter: (params) => params.row.optType === 'ADD'?$t('新增或修改'):$t('删除'),
width: 200
},
{
field: 'value',
headerName: '匹配参数值',
headerName: $t('匹配参数值'),
flex: 1
},
]
@@ -51,31 +52,31 @@ export default function ApiProxy({ proxyInfo, title, loading = false, onMoreSett
return [
{
key: 'path',
label: '转发上游路径',
label: $t('转发上游路径'),
children: proxyInfo?.path,
style: {paddingBottom: '10px'},
},
{
key: 'timeout',
label: '请求超时时间',
label: $t('请求超时时间'),
children: proxyInfo?.timeout,
style: {paddingBottom: '10px'},
},
// {
// key: 'upstream',
// label: '绑定上游服务',
// label: $t('绑定上游服务',
// children: proxyInfo?.upstream.name,
// style: {paddingBottom: '10px'},
// },
{
key: 'retry',
label: '重试时间',
label: $t('重试时间'),
children: proxyInfo?.retry,
style: {paddingBottom: '10px'},
},
...(proxyInfo.headers.length > 0 ? [{
key: 'headers',
label: '转发上游请求头',
label: $t('转发上游请求头'),
children: '',
style: {paddingBottom: '10px'},
}]:[])
@@ -5,6 +5,7 @@ import { RenderMessageBody } from '../MessageBody'
import {HeaderParamsType} from "@common/const/api-detail";
import {collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx} from "../../../PreviewTable";
import {Collapse} from "../../../Collapse";
import { $t } from "@common/locales";
interface HeaderFieldsProps {
rows?: HeaderParamsType[]
@@ -29,13 +30,13 @@ export default function HeaderFields({ rows = [], title, loading = false, onMore
const columns: GridColDef<HeaderParamsType>[] = [
{
field: 'name',
headerName: '标签',
headerName: $t('标签'),
width: 200,
hideable: false
},
{
field: 'isRequired',
headerName: '必需',
headerName: $t('必需'),
sortable: false,
valueGetter: (params) => Boolean(params.row.isRequired),
type: 'boolean',
@@ -43,7 +44,7 @@ export default function HeaderFields({ rows = [], title, loading = false, onMore
},
{
field: 'description',
headerName: '描述',
headerName: $t('描述'),
flex: 1
},
{
@@ -57,7 +58,7 @@ export default function HeaderFields({ rows = [], title, loading = false, onMore
getActions: (params) => [
<PreviewGridActionsCellItem
icon="more"
label="More"
label={$t("More")}
key="more"
onClick={() => onMoreSettingChange?.(params.row as unknown as RenderMessageBody)}
/>
@@ -3,7 +3,7 @@ import { TextField } from '@mui/material'
export function PreviewBodyBinary({ value }: { value: string;}) {
return (
<TextField
label="Binary"
label={$t("Binary")}
multiline
disabled={true}
rows={4}
@@ -6,6 +6,7 @@ import {collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx} from "
import {Collapse} from "../../../Collapse";
import { PreviewBodyBinary } from './components/Binary';
import { PreviewBodyRaw } from './components/Raw';
import { $t } from '@common/locales';
export interface RenderMessageBody extends BodyParamsType {
path: string[]
@@ -50,13 +51,13 @@ export default function MessageBodyComponent({
const columns: GridColDef<RenderMessageBody>[] = [
{
field: 'dataType',
headerName: '类型',
headerName: $t('类型'),
valueGetter: (params) => ApiParamsType[params.row.dataType as ApiParamsType],
width: 80
},
{
field: 'isRequired',
headerName: '必需',
headerName: $t('必需'),
sortable: false,
valueGetter: (params) => Boolean(params.row.isRequired),
type: 'boolean',
@@ -64,12 +65,12 @@ export default function MessageBodyComponent({
},
{
field: 'description',
headerName: '描述',
headerName: $t('描述'),
flex: 1
},
{
field: 'paramAttr',
headerName: '示例',
headerName:$t('示例'),
valueGetter: (params) => params.row.paramAttr?.example,
flex: 1
},
@@ -83,7 +84,7 @@ export default function MessageBodyComponent({
getActions: (params) => [
<PreviewGridActionsCellItem
icon="more"
label="More"
label={$t("More")}
key="more"
onClick={() => onMoreSettingChange?.(params.row)}
/>
@@ -105,7 +106,7 @@ export default function MessageBodyComponent({
rowHeight={40}
pagination={false}
groupingColDef={{
headerName: '参数名',
headerName: $t('参数名'),
sortable:true,
width: 200,
hideable: false,
@@ -6,6 +6,7 @@ import { RenderMessageBody } from '../MessageBody'
import {QueryParamsType} from "@common/const/api-detail";
import {collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx} from "../../../PreviewTable";
import {Collapse} from "../../../Collapse";
import { $t } from '@common/locales';
interface QueryFieldsProps {
rows?: QueryParamsType[]
@@ -30,13 +31,13 @@ export default function QueryFields({ rows = [], title, loading = false, onMoreS
const columns: GridColDef<QueryParamsType>[] = [
{
field: 'name',
headerName: '参数名',
headerName: $t('参数名'),
width: 200,
hideable: false
},
{
field: 'isRequired',
headerName: '必需',
headerName: $t('必需'),
sortable: false,
valueGetter: (params) => Boolean(params.row.isRequired),
type: 'boolean',
@@ -44,7 +45,7 @@ export default function QueryFields({ rows = [], title, loading = false, onMoreS
},
{
field: 'description',
headerName: '描述',
headerName: $t('描述'),
flex: 1
},
{
@@ -58,7 +59,7 @@ export default function QueryFields({ rows = [], title, loading = false, onMoreS
getActions: (params) => [
<PreviewGridActionsCellItem
icon="more"
label="More"
label={$t("More")}
key="more"
onClick={() => onMoreSettingChange?.(params.row as unknown as RenderMessageBody)}
/>
@@ -4,6 +4,7 @@ import { IconButton } from '../../../../IconButton'
import {BaseDialog} from "../../../../Dialog";
import {Icon} from "../../../../Icon";
import {Codebox} from "../../../../Codebox";
import { $t } from '@common/locales';
export type ImportMessageChangeType = 'replace-all' | 'insert-end' | 'replace-changed'
@@ -65,7 +66,7 @@ export function ImportMessage({ type, onChange }: ImportMessageDialogProps) {
return (
<>
<IconButton name="import" sx={{ height: '30px' }} onClick={() => setOpen(true)} variant="outlined">
{$t('导入')}
</IconButton>
<BaseDialog open={open} onClose={() => setOpen(false)} actionRender={null} title={`Import ${type}`}>
<DialogContent sx={{ paddingTop: 0, minWidth: '800px' }}>
@@ -74,7 +75,7 @@ export function ImportMessage({ type, onChange }: ImportMessageDialogProps) {
<Paper elevation={0} sx={{ padding: 2, bgcolor: theme.palette.grey[200] }}>
<Typography component="div" sx={{ display: 'flex', alignItems: 'center' }}>
<Icon name="attention" mx={0} px={0} sx={{ display: 'inline-flex', marginRight: 0.5 }} />
{$t('导入格式')}
</Typography>
<Typography variant="body2" component="div">
<pre>{example}</pre>
@@ -86,16 +87,16 @@ export function ImportMessage({ type, onChange }: ImportMessageDialogProps) {
</DialogContent>
<Box display="flex" p={3} pt={0} gap={2} justifyContent="flex-end">
<Button variant="outlined" onClick={() => setOpen(false)}>
{$t('取消')}
</Button>
<Button variant="outlined" onClick={() => handleChange('replace-all')}>
{$t('全量替换')}
</Button>
<Button variant="outlined" onClick={() => handleChange('insert-end')}>
{$t('在末端插入')}
</Button>
<Button variant="contained" onClick={() => handleChange('replace-changed')}>
{$t('增量更新')}
</Button>
</Box>
</BaseDialog>
@@ -9,23 +9,23 @@ export const MimeTypes: {
value: ContentType
}[] = [
{
title: 'Text',
title:'Text',
value: 'text/plain'
},
{
title: 'JSON',
title:'JSON',
value: 'application/json'
},
{
title: 'XML',
title:'XML',
value: 'application/xml'
},
{
title: 'HTML',
title:'HTML',
value: 'text/html'
},
{
title: 'JavaScript',
title:'JavaScript',
value: 'application/javascript'
}
]
@@ -35,11 +35,11 @@ export const FormContentTypes: {
value: ContentType
}[] = [
{
title: 'x-www-form-urlencoded',
title:'x-www-form-urlencoded',
value: 'application/x-www-form-urlencoded'
},
{
title: 'multipart/form-data',
title:'multipart/form-data',
value: 'multipart/form-data'
}
]
@@ -17,6 +17,7 @@ import { ImportMessage, ImportMessageChangeType, ImportMessageOption } from './I
import {ApiBodyType, ApiDetail, ParseCurlResult, TestApiBodyType} from "@common/const/api-detail";
import {TestMessageDataGrid, TestMessageDataGridApi} from "../TestMessageDataGrid";
import {Indicator} from "../../../../Indicator";
import { $t } from '@common/locales'
export interface ApiRequestTesterApi {
getEditMeta: () => {
@@ -113,13 +114,13 @@ export function ApiRequestTester({ apiRef, onQueryChange ,apiInfo, loaded=true}:
)
const handleImportChange = (changeType: ImportMessageChangeType, data: ImportMessageOption[]) => {
tabValue === '请求头' && headersApiRef.current?.importData(changeType, data)
tabValue === 'Query 参数' && queryApiRef.current?.importData(changeType, data)
tabValue === $t('请求头') && headersApiRef.current?.importData(changeType, data)
tabValue === $t('Query 参数') && queryApiRef.current?.importData(changeType, data)
}
const tabs = [
{
label: '请求头',
label: $t('请求头'),
element: (
<TestMessageDataGrid
apiRef={headersApiRef}
@@ -131,12 +132,12 @@ export function ApiRequestTester({ apiRef, onQueryChange ,apiInfo, loaded=true}:
dirty: false
},
{
label: '请求体',
label: $t('请求体'),
element: <TestBody bodyApiRef={bodyApiRef} onContentTypeChange={handleContentTypeChange} />,
dirty: false
},
{
label: 'Query 参数',
label: $t('Query 参数'),
element: (
<TestMessageDataGrid
apiRef={queryApiRef}
@@ -150,7 +151,7 @@ export function ApiRequestTester({ apiRef, onQueryChange ,apiInfo, loaded=true}:
dirty: false
},
{
label: 'Rest 参数',
label: $t('Rest 参数'),
element: <TestMessageDataGrid apiRef={restApiRef} initialRows={apiRest} messageType="REST" />,
dirty: false
}
@@ -1,6 +1,7 @@
import { Box, Chip, Typography } from '@mui/material'
import {IconButton} from "../../../../IconButton";
import {byteToString} from "@common/utils/postcat.tsx";
import { $t } from '@common/locales';
interface ResponseIndicatorProps {
statusCode: number
@@ -14,12 +15,12 @@ export function ResponseIndicator({ statusCode, size, time, onDownload }: Partia
<Box gap={2} px={1} display="flex" alignItems="center">
<Chip label={<Typography>{statusCode}</Typography>} />
<Typography>
: {byteToString(size || 0)}
{$t('大小')}: {byteToString(size || 0)}
</Typography>
<Typography>
: {time} ms
{$t('时间')}: {time} ms
</Typography>
<IconButton name="download" title='另存为文件' onClick={onDownload} />
<IconButton name="download" title={$t('另存为文件')} onClick={onDownload} />
</Box>
) : null
}
@@ -6,6 +6,7 @@ import { HeaderPreview } from './components/HeaderPreview'
import { ResponseIndicator } from './components/ResponseIndicator'
import {TestResponse} from "@common/hooks/useTest.ts";
import {downloadFile} from "@common/utils/download.ts";
import { $t } from '@common/locales'
type TabType = 'Response' | 'Response Headers' | 'Body' | 'Request Headers'
@@ -16,12 +17,12 @@ interface ApiResponseProps {
export function ApiResponse({ data }: ApiResponseProps) {
const tabHeight = 30
const theme = useTheme()
const [tabValue, setTabValue] = useState<TabType>('Response')
const handleTabValueChange = (_evt: SyntheticEvent, value: TabType): void => {
const handleTabValueChange = useCallback((_evt: SyntheticEvent, value: TabType): void => {
setTabValue(value)
}
},[])
const response = data?.report.response
const handleDownload = useCallback(() => {
@@ -42,7 +43,7 @@ export function ApiResponse({ data }: ApiResponseProps) {
const response = data?.report.response
return [
{
title: '响应',
title: $t('响应'),
name: 'Response',
hidden: false,
element: (
@@ -57,19 +58,19 @@ export function ApiResponse({ data }: ApiResponseProps) {
)
},
{
title: '响应头',
title: $t('响应头'),
name: 'Response Headers',
hidden: !response?.headers.length,
element: <HeaderPreview data={response?.headers || []} />
},
{
title:'正文',
title:$t('正文'),
name: 'Body',
hidden: !request?.body.length,
element: <Body data={request?.body} />
},
{
title: '请求头',
title: $t('请求头'),
name: 'Request Headers',
hidden: !request?.headers.length,
element: <HeaderPreview data={request?.headers || []} />
@@ -1,4 +1,5 @@
// import { TabRouteObject, useTabStore } from '@/stores/tab'
import { $t } from '@common/locales'
import { Button, Typography } from '@mui/material'
import { TouchRippleActions } from '@mui/material/ButtonBase/TouchRipple'
import { useEffect, useRef, useState } from 'react'
@@ -66,15 +67,15 @@ export function TestControl({ onTest, onAbort, loading }: TestControlProps) {
<>
{!loading ? (
<Button touchRippleRef={testRippleRef} variant="contained" onClick={onTest}>
(Enter)
{$t('发送(Enter)')}
</Button>
) : (
<Button variant="outlined" color="warning" onClick={onAbort}>
<Typography></Typography>
<Typography>{$t('中止')}</Typography>
{loadingTime ? (
<Typography sx={{ paddingLeft: 1 }}>
{' '}
({loadingTime} {'秒'}){' '}
({loadingTime} {$t('秒')}){' '}
</Typography>
) : null}
</Button>
@@ -27,6 +27,7 @@ import {Icon} from "../../../Icon";
import {ApiParamsTypeOptions} from "../../../ApiManager/components/ApiMessageBody/constants.ts";
import {UploadButton} from "../../../UploadButton";
import {isNil} from "lodash-es";
import { $t } from '@common/locales/index.ts';
type SafeAny = unknown
export interface RenderBodyParamsType extends BodyParamsType {
@@ -171,7 +172,7 @@ export function TestMessageDataGrid(props: TestMessageDataGridProps<BodyParamsTy
const columns: (GridColDef<RenderBodyParamsType> | false)[] = [
messageType === 'Headers' && {
field: 'name',
headerName: '标签',
headerName: $t('标签'),
editable: true,
sortable: false,
renderEditCell: (params) => {
@@ -214,7 +215,7 @@ export function TestMessageDataGrid(props: TestMessageDataGridProps<BodyParamsTy
},
messageType !== 'Headers' && {
field: 'name',
headerName:'参数名',
headerName:$t('参数名'),
width: 200,
editable: true,
sortable: false,
@@ -244,7 +245,7 @@ export function TestMessageDataGrid(props: TestMessageDataGridProps<BodyParamsTy
},
messageType === 'Body' && {
field: 'dataType',
headerName: '类型',
headerName: $t('类型'),
sortable: false,
width: 120,
type: 'singleSelect',
@@ -286,7 +287,7 @@ export function TestMessageDataGrid(props: TestMessageDataGridProps<BodyParamsTy
{
field: 'paramAttr',
sortable: false,
headerName: '参数值',
headerName: $t('参数值'),
flex: 1,
minWidth: 200,
editable: true,
@@ -4,6 +4,7 @@ import type { ReactNode } from 'react'
import { Box } from '@mui/material'
import { IconButton } from '../IconButton'
import useCopyToClipboard from "@common/hooks/copy.ts";
import { $t } from '@common/locales';
export interface ClipboardProps {
text: string
@@ -14,7 +15,7 @@ export interface ClipboardProps {
export function Clipboard(props: ClipboardProps): JSX.Element {
const { text, children, onError, onSuccess } = props
const DefaultText = '复制'
const DefaultText = $t('复制')
const [buttonTitle, setButtonTitle] = useState(DefaultText)
const { copyToClipboard } = useCopyToClipboard();
const handleCopy = (): void => {
@@ -5,6 +5,8 @@ import { Editor, useMonaco } from '@monaco-editor/react'
import { type editor as MonacoEditor } from 'monaco-editor'
import { IconButton } from '../IconButton'
import { message } from 'antd'
import { $t } from '@common/locales'
import { RESPONSE_TIPS } from '@common/const/const'
export interface CodeboxApiRef {
insertCode: (value: string) => void
@@ -20,8 +22,10 @@ interface CodeboxProps {
height?: string | null
readOnly?: boolean
apiRef?: RefObject<CodeboxApiRef>
language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'
language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml'
extraContent?:React.ReactNode
sx?:Record<string,unknown>
editorTheme?:'vs' | 'vs-dark' | 'hc-black'
}
export const Codebox = memo((props: CodeboxProps) => {
@@ -35,7 +39,8 @@ export const Codebox = memo((props: CodeboxProps) => {
apiRef,
readOnly = false,
language = 'plaintext',
extraContent
extraContent,
editorTheme = 'vs'
} = props
const [code, setCode] = useState<string>(``)
@@ -126,7 +131,7 @@ export const Codebox = memo((props: CodeboxProps) => {
const copyCode = async (): Promise<void> => {
if (editorRef.current) {
await navigator.clipboard.writeText(editorRef.current.getValue())
message.success('复制成功')
message.success($t(RESPONSE_TIPS.copySuccess))
}
}
@@ -151,7 +156,8 @@ export const Codebox = memo((props: CodeboxProps) => {
sx={{
// border: `1px solid ${theme.palette.divider}`,
height: '100%',
width: '100%'
width: '100%',
...props.sx
}}
>
{enableToolbar ? (<>
@@ -166,16 +172,16 @@ export const Codebox = memo((props: CodeboxProps) => {
{extraContent}
<IconButton name="code" onClick={formatCode} sx={{color:'#333',transition:'none','&.MuiButtonBase-root:hover':{background:'transparent',color:'#3D46F2',transition:'none'}}}>
{$t('格式化')}
</IconButton>
<IconButton name="copy" onClick={copyCode} sx={{color:'#333',transition:'none','&.MuiButtonBase-root:hover':{background:'transparent',color:'#3D46F2',transition:'none'}}}>
{$t('复制')}
</IconButton>
<IconButton name="search" onClick={searchInCode} sx={{color:'#333',transition:'none','&.MuiButtonBase-root:hover':{background:'transparent',color:'#3D46F2',transition:'none'}}}>
{$t('搜索')}
</IconButton>
{!readOnly &&<IconButton name="file-text" onClick={replaceInCode} sx={{color:'#333',transition:'none','&.MuiButtonBase-root:hover':{background:'transparent',color:'#3D46F2',transition:'none'}}}>
{$t('替代')}
</IconButton>}
</Box></>
) : null}
@@ -187,6 +193,7 @@ export const Codebox = memo((props: CodeboxProps) => {
value={isControlled ? controlledValue : code}
options={{ ...defaultOptions, ...options }}
onChange={handleEditorChange}
theme={editorTheme}
/>
</Box>
)
@@ -7,6 +7,7 @@ import { type ReactNode } from 'react'
import type { LoadingButtonProps } from '@mui/lab'
import { LoadingButton } from '@mui/lab'
import {renderComponent} from "@common/utils/postcat.tsx";
import { $t } from '@common/locales'
export interface BaseDialogProps extends DialogActionProps {
open: boolean
@@ -56,9 +57,8 @@ interface DialogActionProps {
export function DialogActions(props: DialogActionProps): JSX.Element {
const CancelText = '取消'
const ConfirmText = '确定'
const CancelText = $t('取消')
const ConfirmText = $t('确定')
const {
onClose,
confirmBtn,
@@ -1,5 +1,6 @@
import { Box, Typography, useTheme } from '@mui/material'
import {Codebox} from "../../Codebox";
import { $t } from '@common/locales';
interface ExampleProps {
code: string
@@ -29,7 +30,7 @@ export function Example({ code, onChange, readOnly = false }: ExampleProps) {
}}
>
<Typography fontSize={14} px={'12px'}>
{$t('示例')}
</Typography>
</Box>
<Codebox
@@ -1,6 +1,7 @@
import { ChangeEvent, useEffect, useState } from 'react'
import { FormControl, TextField, Box } from '@mui/material'
import { $t } from '@common/locales';
interface ParamLimitProps {
min: number | null
@@ -17,15 +18,15 @@ export function ParamLimit({ min, max, onChange, minLabel = 'Minimum', maxLabel
const validate = (minVal: number, maxVal: number) => {
if (isNaN(minVal) || minVal < 0) {
return `The ${minLabel} must not be negative.`
return $t('The (0) must not be negative.', [minLabel])
}
if (isNaN(maxVal) || maxVal < 0) {
return `The ${maxLabel} must not be negative.`
return $t('The (0) must not be negative.',[maxLabel])
}
if (minVal > maxVal) {
return `The ${maxLabel} must be greater than or equal to the ${minLabel}.`
return $t('The (0) must be greater than or equal to the (1).',[maxLabel, minLabel])
}
return null
@@ -2,6 +2,7 @@ import { Box } from '@mui/material'
import { DataGridPro, GridColDef, useGridApiRef } from '@mui/x-data-grid-pro'
import { useEffect, useMemo } from 'react'
import {previewTableHoverSx } from '../../PreviewTable'
import { $t } from '@common/locales'
interface ParamPreviewProps {
name?: string
@@ -48,17 +49,17 @@ export function ParamPreview(props: ParamPreviewProps) {
const columns: GridColDef[] = [
{
field: 'name',
headerName: '参数名',
headerName: $t('参数名'),
width: 120
},
{
field: 'type',
headerName: '类型',
headerName: $t('类型'),
width: 120
},
{
field: 'required',
headerName: '必需',
headerName: $t('必需'),
sortable: false,
valueGetter: (params) => Boolean(params.row.isRequired),
type: 'boolean',
@@ -66,7 +67,7 @@ export function ParamPreview(props: ParamPreviewProps) {
},
{
field: 'description',
headerName: '描述',
headerName: $t('描述'),
flex: 1
}
]
@@ -13,6 +13,7 @@ import {IconButton} from "../../IconButton";
import {flattenTree, generateId, getActionColWidth} from "@common/utils/postcat.tsx";
import {EditableDataGridSx} from "../../ApiManager/components/EditableDataGrid";
import { commonTableSx } from '@common/const/api-detail/index.ts';
import { $t } from '@common/locales';
export interface ValueEnum {
value: string
@@ -97,7 +98,7 @@ export function ValueEnum({ data, apiRef,readOnly = false }: ValueEnumProps) {
const columns: GridColDef<Row>[] = [
{
field: 'value',
headerName: '值枚举',
headerName: $t('值枚举'),
type: 'string',
sortable: false,
flex: 1,
@@ -109,7 +110,7 @@ export function ValueEnum({ data, apiRef,readOnly = false }: ValueEnumProps) {
<TextField
fullWidth
value={params.value}
placeholder='枚举'
placeholder={$t('枚举')}
sx={{
input: {
paddingLeft: `${theme.spacing(1)} !important`,
@@ -132,7 +133,7 @@ export function ValueEnum({ data, apiRef,readOnly = false }: ValueEnumProps) {
},
{
field: 'description',
headerName: '描述',
headerName: $t('描述'),
type: 'string',
sortable: false,
flex: 1,
@@ -154,7 +155,7 @@ export function ValueEnum({ data, apiRef,readOnly = false }: ValueEnumProps) {
paddingRight: `${theme.spacing(1)} !important`
}
}}
placeholder='示例'
placeholder={$t('示例')}
/>
)
}
@@ -170,7 +171,7 @@ export function ValueEnum({ data, apiRef,readOnly = false }: ValueEnumProps) {
if (renderRows.length <= 1) return []
return [
<IconButton
title='删除'
title={$t('删除')}
name="delete"
onClick={() => {
handleRowDelete(params)
@@ -9,6 +9,7 @@ import { ParamLimit } from './components/ParamLimit'
import {ParamAttrType} from "@common/const/api-detail";
import { BaseDialog} from "../Dialog/base-dialog.tsx";
import {ApiParamsTypeOptions} from "../ApiManager/components/ApiMessageBody/constants.ts";
import { $t } from '@common/locales/index.ts'
interface MoreSettingProps {
open: boolean
@@ -77,7 +78,7 @@ export function MoreSetting({ open, readOnly,onClose, param, onChange, hiddenCon
}
return (
<BaseDialog open={open} onClose={onClose} title='更多设置' onConfirm={handleConfirm}>
<BaseDialog open={open} onClose={onClose} title={$t('更多设置')} onConfirm={handleConfirm}>
<Box px={2} pb={2} width={880}>
<Stack spacing={2}>
<ParamPreview
@@ -90,8 +91,8 @@ export function MoreSetting({ open, readOnly,onClose, param, onChange, hiddenCon
<ParamLimit
min={param?.paramAttr?.minLength ?? 0}
max={param?.paramAttr?.maxLength ?? 0}
minLabel='最小长度'
maxLabel='最大长度'
minLabel={$t('最小长度')}
maxLabel={$t('最大长度')}
onChange={handleParamLengthChange}
/>
) : null}
@@ -99,8 +100,8 @@ export function MoreSetting({ open, readOnly,onClose, param, onChange, hiddenCon
<ParamLimit
min={param?.paramAttr?.minValue ?? 0}
max={param?.paramAttr?.maxValue ?? 0}
minLabel='最小值'
maxLabel='最大值'
minLabel={$t('最小值')}
maxLabel={$t('最大值')}
onChange={handleParamValueChange}
/>
) : null}
@@ -4,6 +4,7 @@ import { useDropzone } from 'react-dropzone'
import { useAutoAnimate } from '@formkit/auto-animate/react'
import { Icon } from '../Icon'
import { IconButton } from '../IconButton'
import { $t } from '@common/locales'
export interface UploadProps {
value?: File | null
@@ -48,7 +49,7 @@ export function Upload({ value, onChange }: UploadProps): JSX.Element {
<Box>
<Icon size="30px" name="link-cloud" />
</Box>
<Typography>{'将文件拖拽至此处上传,或点击选择文件上传'}</Typography>
<Typography>{$t('将文件拖拽至此处上传,或点击选择文件上传')}</Typography>
</Paper>
{value ? (
<Box>
@@ -2,6 +2,7 @@ import { Box, Button, Typography } from '@mui/material'
import type { ChangeEvent } from 'react'
import { useRef } from 'react'
import {file2Base64} from "@common/utils/postcat.tsx";
import { $t } from '@common/locales';
interface UploadButtonProps {
value?:
@@ -42,9 +43,9 @@ export function UploadButton({ value, onChange }: UploadButtonProps): JSX.Elemen
<Box display="flex" alignItems="center">
<input multiple type="file" ref={fileInputRef} style={{ display: 'none' }} onChange={handleFileChange} />
<Button variant="outlined" color="primary" onClick={handleButtonClick}>
Upload Files
{$t('Upload Files')}
</Button>
{value?.length ? <Typography sx={{ marginLeft: 1 }}>Files Selected: {value.length}</Typography> : null}
{value?.length ? <Typography sx={{ marginLeft: 1 }}>{$t('Files Selected')}: {value.length}</Typography> : null}
</Box>
)
}

Some files were not shown because too many files have changed in this diff Show More