mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fed865872e | |||
| fd270f2c02 | |||
| f2635bf457 | |||
| 4e0813d49e | |||
| 1f8e2bf6c3 | |||
| b58a031a69 | |||
| d96f11d968 | |||
| b22b160aad | |||
| debf19b059 | |||
| 0f8c14971e | |||
| fdd4ce3c19 | |||
| 9317f20917 | |||
| 4228cf45f5 | |||
| 1bedad903b | |||
| e281282514 | |||
| 298789e0e2 | |||
| 007e4bdcfb | |||
| a7378492ef | |||
| 4ad23acfb6 | |||
| 5dfa1170ba | |||
| a4d9b0202b | |||
| 3a23ec2ab3 | |||
| 0dcea3f03a | |||
| 851c54b6af | |||
| bf78926b31 | |||
| 2aada85662 | |||
| 7d569a49cb | |||
| 6d69a06935 | |||
| 77aa0080d9 | |||
| 2ef746bf37 | |||
| 809959289c | |||
| de74542e78 | |||
| 2ae0cb8b51 | |||
| cea880ad5c | |||
| f570c8b174 | |||
| 6f4a95b0da | |||
| 2817974672 | |||
| 1f8ad0fda3 | |||
| 189a1c8fa3 | |||
| 35de0c003c | |||
| 7ccaa8972c | |||
| 9c8418ab40 | |||
| c9a2a56dd9 | |||
| 31cb70ed02 | |||
| 1fc2113c9a | |||
| 8c07b63dec | |||
| 13ec24d95e | |||
| fd5357a736 | |||
| b349f8b156 | |||
| 30d446aa16 | |||
| a5e19c2705 | |||
| d7ac23235a | |||
| 9e95144e86 | |||
| 999957ef56 | |||
| 4a523c611d | |||
| 438d0a7fa2 | |||
| 045a8abcfb | |||
| f5a141daf0 | |||
| d77025cb9e | |||
| 167d2e5c1f | |||
| 36e72169b8 | |||
| 0d67eab7f7 | |||
| c7bfbe6b9f | |||
| 577f846545 | |||
| 05f6071119 | |||
| b57c80a5ae | |||
| f3a00814a5 | |||
| d40e88c945 | |||
| 7671bb3a8c | |||
| 6bc93f8176 | |||
| c5cee7c6de | |||
| 118b847995 | |||
| b5a701d560 | |||
| b84910f7d5 | |||
| 92230b6dda | |||
| 1d2edf8ca7 | |||
| 8012ac42fe | |||
| 16d401ffa6 | |||
| 75569097b2 | |||
| 61c0f94538 | |||
| 9378188b59 | |||
| fa49bf7fc0 | |||
| ee5edd1abc | |||
| 1f43f1ad66 | |||
| d7d12d7ac0 | |||
| 26493be5ee | |||
| 7e399cc181 | |||
| c1a1d16ca8 | |||
| d6d0c9f914 | |||
| 9ece54b099 | |||
| 09130df02d | |||
| 428cbf4781 | |||
| 1be6f4e814 | |||
| 11df2331cd | |||
| 5ebda41e96 | |||
| ec1eeb6368 | |||
| 49efc0e4df | |||
| 49ef8d54db | |||
| 276b88274f | |||
| 5c239a2c53 | |||
| b6161813cb | |||
| eeeb9a3815 | |||
| 924e6c8a2e | |||
| b4d2071ba7 | |||
| 3c7c6e6c41 | |||
| 6c4704de1a |
@@ -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"
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
/config.yml
|
||||
/build/
|
||||
/apipark
|
||||
/aoplatform
|
||||
|
||||
@@ -1,156 +1,161 @@
|
||||

|
||||

|
||||
|
||||
<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.
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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"}]
|
||||
@@ -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}]
|
||||
@@ -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
@@ -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}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"id":"eef18cfb-140b-4e78-b9eb-ad669d898110","name":"Demo Team","description":""}]
|
||||
@@ -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":""}}]
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -49,9 +49,7 @@ func getFileSystem(dir string) http.FileSystem {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return http.FS(fDir)
|
||||
|
||||
}
|
||||
|
||||
type Frontend struct {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
+90
-129
@@ -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>)
|
||||
})
|
||||
+15
-33
@@ -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>}
|
||||
|
||||
|
||||
+3
-20
@@ -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>
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+6
-22
@@ -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>
|
||||
|
||||
+55
-20
@@ -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 }}
|
||||
|
||||
+50
-43
@@ -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}
|
||||
|
||||
+2
-1
@@ -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}
|
||||
|
||||
+6
-6
@@ -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,
|
||||
|
||||
+4
-3
@@ -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,
|
||||
|
||||
+15
-15
@@ -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('示例')} />
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
+2
-1
@@ -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
-1
@@ -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',
|
||||
|
||||
+5
-4
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
+10
-9
@@ -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
-4
@@ -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)}
|
||||
/>
|
||||
|
||||
+1
-1
@@ -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}
|
||||
|
||||
+7
-6
@@ -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,
|
||||
|
||||
+5
-4
@@ -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)}
|
||||
/>
|
||||
|
||||
+7
-6
@@ -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>
|
||||
|
||||
+7
-7
@@ -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'
|
||||
}
|
||||
]
|
||||
+7
-6
@@ -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
|
||||
}
|
||||
|
||||
+4
-3
@@ -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
|
||||
}
|
||||
|
||||
+8
-7
@@ -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 || []} />
|
||||
|
||||
+4
-3
@@ -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>
|
||||
|
||||
+5
-4
@@ -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,
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
+5
-4
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
+6
-5
@@ -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
Reference in New Issue
Block a user