mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
498 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b03c56315 | |||
| eaecc5c80a | |||
| ed8c2f286c | |||
| 7b2356f8f3 | |||
| 6ddd2f2389 | |||
| 4e98b09fa4 | |||
| e786393523 | |||
| 4a2995b533 | |||
| 1495451901 | |||
| d4ef5a7516 | |||
| 044e31dd8a | |||
| bd33dff2f3 | |||
| edf30ac61f | |||
| c67964045d | |||
| 92a6f777ed | |||
| 12ed7aafee | |||
| 3becd8a0a7 | |||
| 818436c946 | |||
| d0813e8595 | |||
| 3c0140f3b8 | |||
| 8d415fa273 | |||
| f8fad4caf4 | |||
| 3b54c03027 | |||
| a5f46a930f | |||
| 6157a9d1fa | |||
| f910fc84e5 | |||
| 9cb09905f9 | |||
| eeb2fbcad6 | |||
| 400faf92c0 | |||
| fb023a039b | |||
| 95b5d848f7 | |||
| ded5e064e6 | |||
| 7ea50ec380 | |||
| 901bef1463 | |||
| 8d44d796b4 | |||
| 5a10ad478e | |||
| fd6680d615 | |||
| e03cdfc42b | |||
| 945d53fcfd | |||
| ac7045b724 | |||
| c907bdc4a5 | |||
| 733ed9ac2f | |||
| 1d8e579a10 | |||
| 567cac9c95 | |||
| 095c09c8c0 | |||
| e9c949822d | |||
| 3482d5416c | |||
| d8cb4a0c94 | |||
| 59acfa7a47 | |||
| 2eb2e690d1 | |||
| f7801261c3 | |||
| 7e7be7f040 | |||
| 0187fd16b2 | |||
| ba0bdb5e99 | |||
| 9d3e4f07bf | |||
| bd81d7584d | |||
| 9577339e14 | |||
| 5c292ef1cb | |||
| 4f3de85068 | |||
| 07a25c9643 | |||
| 8f60426b4c | |||
| 37f87615bd | |||
| 3f96de660b | |||
| e86999770f | |||
| a8bb0c24ec | |||
| 6ba2a08b62 | |||
| d232269416 | |||
| 9d2208e14d | |||
| 8d69d45d1d | |||
| b0c37918b5 | |||
| d5af1c8da3 | |||
| a6105cfc3c | |||
| 0aa5ffd2c2 | |||
| 968f5b986f | |||
| 014a7e0362 | |||
| a92baf09d9 | |||
| 46e2edbe13 | |||
| 5aba86965e | |||
| 5924208aaa | |||
| 526390816b | |||
| d7e28c9704 | |||
| 7c827804f4 | |||
| b0dacbda0d | |||
| d5abde2593 | |||
| bc3290de3b | |||
| 7f438bf776 | |||
| 13cfe24b2f | |||
| 9cf1cd99c2 | |||
| f5cfd77550 | |||
| f27abbd454 | |||
| 6a7a11a811 | |||
| 4a8f5152b3 | |||
| 599ee6b9b8 | |||
| 83ac747cb1 | |||
| 09b98c6c0d | |||
| 13eac21609 | |||
| d0d9e2a9a8 | |||
| b047c93965 | |||
| d5eedd1dd2 | |||
| 9cc6696340 | |||
| 86758383c4 | |||
| 150a0264c5 | |||
| 558a2d8aad | |||
| fa327114f7 | |||
| 6ce3e0bfac | |||
| 4f7dee570a | |||
| e4eadf863e | |||
| 7a70a6ce01 | |||
| ca328e784c | |||
| 9871e252bc | |||
| d40eb6c4e1 | |||
| a7b0e6d0bf | |||
| f4f546e654 | |||
| 1fcbb3ecbc | |||
| 98e3cc973b | |||
| 220ab53ef2 | |||
| 9ba70063d2 | |||
| 076277d0a9 | |||
| bcd2ba1ec9 | |||
| 5827afd09c | |||
| 4a3e49f4e3 | |||
| 95d24aca41 | |||
| 345e37bd81 | |||
| 91b2fabf10 | |||
| bae1803157 | |||
| 911d16de31 | |||
| 1efe924221 | |||
| 0230235427 | |||
| 58e737ee28 | |||
| 78c98f121d | |||
| 2e3b86741b | |||
| fcd9869caa | |||
| f60579b735 | |||
| 6574f36c73 | |||
| 246ce245b9 | |||
| fa0a211db9 | |||
| 1c536df3c8 | |||
| 82fa1b5b1c | |||
| dacce748a1 | |||
| b59088c598 | |||
| 26dbce9dbf | |||
| 9b6d07dc4c | |||
| 00e21c8000 | |||
| 8c5f5326d5 | |||
| 3ebafcbc03 | |||
| 9777859f42 | |||
| 54f76d6576 | |||
| 3bb8293478 | |||
| 16e899cab7 | |||
| b4b9469284 | |||
| e0896864c2 | |||
| c7b16e0ea9 | |||
| f6d6920cfb | |||
| 8b4059a249 | |||
| 94b19e7589 | |||
| a090bb7caa | |||
| 101e97dfec | |||
| 31054a2df7 | |||
| 58231eb19c | |||
| 8f8b5d2684 | |||
| ca5e497dbb | |||
| 13aabcacd2 | |||
| 3afd0bb609 | |||
| e250a8b57f | |||
| 051fa7647d | |||
| 9c4c794d0f | |||
| 9211a28675 | |||
| 8d660ec7c0 | |||
| ed8109fc30 | |||
| 37fc63def8 | |||
| 8c1a8f67d8 | |||
| 94ed7581bb | |||
| 4a84a69fe7 | |||
| 1ebf6c9319 | |||
| d0fc353d0b | |||
| 977919fdb1 | |||
| ae2e37cedb | |||
| 02e5394924 | |||
| cdc9bb73bb | |||
| 3a2c0c744c | |||
| ffeb76f608 | |||
| 3748cb39b2 | |||
| 543ea52bb3 | |||
| 2ad508ec60 | |||
| f9501d6f60 | |||
| 6bbdf9600d | |||
| c554c010c5 | |||
| 075c976d19 | |||
| 6da49e78ee | |||
| 59ce2e0623 | |||
| 09b2a7f1a4 | |||
| bd466ac420 | |||
| 96183eb5df | |||
| 21164859cf | |||
| 9b50fe68c9 | |||
| d8576e4dc6 | |||
| 9fc23ad4be | |||
| 6c997c0b51 | |||
| 9b10421882 | |||
| e5bc98cac0 | |||
| e98f320f41 | |||
| d0ef35fb92 | |||
| 8c512f3163 | |||
| 8cc0fc9987 | |||
| 604200e1db | |||
| 2a1581acdb | |||
| 39e3198821 | |||
| b4c2b3614b | |||
| 5ea0cc7838 | |||
| a9b7fc525a | |||
| b05874fb0a | |||
| 5fdd142a17 | |||
| 243e1da716 | |||
| 772217258c | |||
| 57cc9c9db7 | |||
| e6f6560f3a | |||
| cbce30b4d7 | |||
| 23c8a84b4c | |||
| a521bff1f1 | |||
| 986784b128 | |||
| 238c2b8cd3 | |||
| c6a418e00c | |||
| e59ea1f84d | |||
| a1acde5df0 | |||
| 3ec61165ca | |||
| cc823a18d4 | |||
| b6aa865a67 | |||
| 86f83d995b | |||
| e314e09fdb | |||
| 4aa4238943 | |||
| 5d638b8bf4 | |||
| 5027d817c5 | |||
| 40b92330eb | |||
| b990447226 | |||
| 482019f514 | |||
| 2027af3a3f | |||
| c1a55385d3 | |||
| 8be47c99dd | |||
| 10d11f5b99 | |||
| 3db2c66f12 | |||
| e8614c0072 | |||
| 11b4c43845 | |||
| 36fec4ddbe | |||
| bb64039a0d | |||
| 7b43b0b300 | |||
| fd270c80f5 | |||
| 12b5801b1b | |||
| 23a6b38a7d | |||
| 296118470c | |||
| 6d4dccc6b7 | |||
| e449f86c01 | |||
| a1bdc048a7 | |||
| b5be78416f | |||
| 89d1fe2c49 | |||
| 0a1b08157d | |||
| 61503f4146 | |||
| d36c66371f | |||
| 34b259562d | |||
| b7bb409e96 | |||
| 517007c941 | |||
| 4c685a9ec6 | |||
| 1aca2099de | |||
| a93e5b4ff8 | |||
| 85d25bebe2 | |||
| 9fa43ccc00 | |||
| c2a11050dd | |||
| 080bfc3a44 | |||
| f6956ddeca | |||
| 9f56fa5e14 | |||
| ccc39b95de | |||
| 9a2782e54b | |||
| 22455e2301 | |||
| 8ed2c84b68 | |||
| ccd2a209e2 | |||
| baf8ed4830 | |||
| dedb586daf | |||
| 21cd823791 | |||
| c8ab65ef1b | |||
| f1c16fd992 | |||
| 52035341f6 | |||
| aa62d44717 | |||
| a072d1fc8d | |||
| 38a00570d0 | |||
| 43283b9da3 | |||
| ef82cdbed6 | |||
| 2bafe6f31f | |||
| 3eb4f98fd8 | |||
| bb5acad033 | |||
| b8308a446b | |||
| c86f99ce45 | |||
| 952c519e45 | |||
| 07d97fa0bf | |||
| b0defedf04 | |||
| edc2fccdeb | |||
| a75b8a3f13 | |||
| 5aab5f7913 | |||
| 9ab7989c8b | |||
| e3e11d740a | |||
| 4bae2edc49 | |||
| 4eaa47ca25 | |||
| e01f596525 | |||
| 912e8d0d04 | |||
| 570c80af91 | |||
| 7aa0ec0d67 | |||
| 2fea6cb622 | |||
| 2195ff900f | |||
| 836c7699b8 | |||
| 72ed6c814e | |||
| c33b070509 | |||
| de12d5686c | |||
| c55a8ac805 | |||
| df3626f3f0 | |||
| 28bef97faa | |||
| 9897b6e9dc | |||
| 9af6963901 | |||
| c2d3ebecda | |||
| ba543311fc | |||
| 87b8dda97b | |||
| e7facf5686 | |||
| 4dd57837c5 | |||
| d4ebc68e30 | |||
| 1f8e089e51 | |||
| ce6f463fe8 | |||
| a37fe1d794 | |||
| 818d1ec6bf | |||
| fc4a5f7e28 | |||
| 0e3568b584 | |||
| 3617e4fe29 | |||
| f2fddc1727 | |||
| 3b7204f1a6 | |||
| 6646bb1e56 | |||
| c27533f802 | |||
| 613a47c181 | |||
| 90138a142b | |||
| 4bf8db4898 | |||
| 4f887f7204 | |||
| 14e17ccf2c | |||
| 5975670b8c | |||
| dbc4bc3343 | |||
| ada7635703 | |||
| 943a77f718 | |||
| ef02c11efa | |||
| 8c166dae9b | |||
| b23da78c26 | |||
| 93ac7310e8 | |||
| 9376acc456 | |||
| b70a1f9a51 | |||
| ac90a134b4 | |||
| 96bd1cf9f6 | |||
| 960e37a81a | |||
| 044bd550c9 | |||
| 0a9a903d1b | |||
| 84d7606e12 | |||
| febb64b8bb | |||
| 932e433c46 | |||
| b7307cd36d | |||
| 7a0f3efd83 | |||
| 28af1f691c | |||
| 796bc7bc15 | |||
| 8f06073783 | |||
| 7d6251b191 | |||
| e13fff633e | |||
| d984be4b85 | |||
| 213bdbd9d5 | |||
| 6a59d27b84 | |||
| ad45ab2e82 | |||
| 8982a63283 | |||
| 1b1515a8bd | |||
| df50e13db0 | |||
| 89d91c14c9 | |||
| 3a57c609f7 | |||
| 2cd331ec50 | |||
| 3fa02ec65c | |||
| f33f1965b4 | |||
| a70ecea02b | |||
| 8e68eb35f3 | |||
| 2893331ff5 | |||
| 1a3d14cdd6 | |||
| dce9a7addb | |||
| f3e7487482 | |||
| 86c39237dc | |||
| b5ad739b93 | |||
| 0b7f0405d5 | |||
| 1ab56708a5 | |||
| 522489c9e9 | |||
| a9eb2a790f | |||
| a092ed1108 | |||
| 503515281d | |||
| 2326d4dfb5 | |||
| 42e8030cf7 | |||
| 17b4ede566 | |||
| bbc3fea848 | |||
| 82e46b872b | |||
| 0f6a091c73 | |||
| 4e87adb4b3 | |||
| 320a2b6cf8 | |||
| b216556867 | |||
| 7fbb98a2a9 | |||
| 461a8edbea | |||
| cdef179bed | |||
| a067388d79 | |||
| 2d5e541593 | |||
| be40186ad3 | |||
| c9ae05b22e | |||
| 2b874fe59f | |||
| 9da5e5d6c0 | |||
| a6bfce2a5f | |||
| 8cbeabe917 | |||
| 1db354077f | |||
| e31d41a276 | |||
| 21d2abf716 | |||
| 3a86a88870 | |||
| 6117a840e1 | |||
| 0e20987fb8 | |||
| 65e7cab772 | |||
| 935f2ac766 | |||
| 3e12d7eb9c | |||
| d6095269b7 | |||
| 0c415b4e32 | |||
| bc1819b368 | |||
| a9be016c87 | |||
| 11da5e9d26 | |||
| 2c6c194821 | |||
| aedd8b4cc6 | |||
| fa2607e9b8 | |||
| 3783e5ee5d | |||
| fd56b8ffed | |||
| 07e288be16 | |||
| 46143c3fe0 | |||
| 83e3cc85f2 | |||
| a4b50ae60a | |||
| a30d0c37eb | |||
| 64fdf59905 | |||
| 1aa3f2fb05 | |||
| 68e8cb72d3 | |||
| a86d3cd65a | |||
| a4b4cbf60f | |||
| 2dcb7ebd74 | |||
| ad6b64ca74 | |||
| d27a2b8cf3 | |||
| 8fa1985feb | |||
| 451efb8d3e | |||
| d6bf3139ff | |||
| 309b9ea937 | |||
| 554bff38c6 | |||
| d5e6062ec9 | |||
| bcb68d552f | |||
| 3de87723ae | |||
| 535d70ac5a | |||
| d588d43aa1 | |||
| f455cecb54 | |||
| b88c0a9305 | |||
| 9030cff8ba | |||
| 98f73f799a | |||
| d2e428ada8 | |||
| 74156ec84c | |||
| f9e6bc92d7 | |||
| 9884586cc9 | |||
| ac717a6efb | |||
| 2b7c1ded15 | |||
| daac27712d | |||
| 1080f33282 | |||
| cc5c0a0a89 | |||
| 85971447e1 | |||
| 256e8ef275 | |||
| c07e54ec03 | |||
| 70f834e1cf | |||
| c320c6f2a2 | |||
| 256eb60df8 | |||
| 5f03973bf2 | |||
| ab2a0d8ae2 | |||
| 7e48402591 | |||
| aa85f6fcd1 | |||
| cd441ccfe7 | |||
| 47f6519006 | |||
| 9679376cb2 | |||
| efa97c3bbc | |||
| 087e598be0 | |||
| 31aa8243ee | |||
| d3d05ef539 | |||
| d6062ea4e7 | |||
| 7949748951 | |||
| 94f7392060 | |||
| 0d737bad57 | |||
| 1225db50c9 | |||
| e5d85bb3df | |||
| 0f09e5c236 | |||
| 0f0204b647 | |||
| 94421d2622 | |||
| 44f0b70461 | |||
| f58768237b | |||
| 588cf839e3 | |||
| aa40b62e0a | |||
| b0b9affbe7 | |||
| bf2aefe2da | |||
| 540a31f237 | |||
| 0505045c81 | |||
| 1a18d79d94 |
@@ -25,7 +25,7 @@ jobs:
|
||||
echo "Build frontend..."
|
||||
cd ./frontend && pnpm run build
|
||||
- name: upload frontend release
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: Checkout #Checkout代码
|
||||
uses: actions/checkout@v3
|
||||
- name: download frontend release
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: download frontend release
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@
|
||||
/config.yml
|
||||
/build/
|
||||
/apipark
|
||||
/aoplatform
|
||||
.gitlab-ci.yml
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ variables:
|
||||
PATH: /opt/go-1.21/go/bin/:/opt/node/node/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
|
||||
GOROOT: /opt/go-1.21/go
|
||||
GOPROXY: https://goproxy.cn
|
||||
VERSION: $CI_COMMIT_SHORT_SHA
|
||||
VERSION: $CI_COMMIT_SHORT_SHA
|
||||
APP: apipark
|
||||
APP_PRE: ${APP}_${VERSION}
|
||||
BUILD_DIR: ${APP}-build
|
||||
|
||||
@@ -197,7 +197,7 @@ To achieve this goal, we plan to add new features to APIPark, including:
|
||||
<br>
|
||||
|
||||
# 📕 Documentation
|
||||
Visit [APIPark Documentation](https://docs.apipark.com/docs/install) for detailed installation guides, API references, and usage instructions.
|
||||
Visit [APIPark Documentation](https://docs.apipark.com/docs/deploy) for detailed installation guides, API references, and usage instructions.
|
||||
|
||||
<br>
|
||||
|
||||
@@ -210,7 +210,7 @@ APIPark uses the Apache 2.0 License. For more details, please refer to the LICEN
|
||||
For enterprise-level features and professional technical support, contact our pre-sales experts for personalized demos, customized solutions, and pricing.
|
||||
|
||||
- Website: https://apipark.com
|
||||
- Email: dev@apipark.com
|
||||
- Email: contact@apipark.com
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
@@ -40,7 +40,14 @@ func (c *Config) Check(cfg string) error {
|
||||
}
|
||||
|
||||
func (c *Config) GenConfig(target string, origin string) (string, error) {
|
||||
if target == "" {
|
||||
target = "{}"
|
||||
}
|
||||
if origin == "" {
|
||||
origin = "{}"
|
||||
}
|
||||
var targetData map[string]interface{}
|
||||
|
||||
err := json.Unmarshal([]byte(target), &targetData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/config.yml
|
||||
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
nsq "github.com/nsqio/go-nsq"
|
||||
|
||||
"github.com/eolinker/go-common/cftool"
|
||||
|
||||
_ "github.com/eolinker/go-common/store/store_mysql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
var (
|
||||
version string
|
||||
confPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&confPath, "c", "config.yml", "`config` file path for server ")
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `yaml:"port"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 1. 连接 MySQL 数据库
|
||||
cftool.Register[ServerConfig](fmt.Sprintf("root:%s", confPath))
|
||||
cftool.ReadFile(confPath)
|
||||
|
||||
handler := &NSQHandler{}
|
||||
autowire.Autowired(handler)
|
||||
err := autowire.CheckComplete()
|
||||
if err != nil {
|
||||
log.Fatal("check autowired:", err)
|
||||
return
|
||||
}
|
||||
// 2. 创建 NSQ 消费者
|
||||
config := nsq.NewConfig()
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get hostname: %v", err)
|
||||
return
|
||||
}
|
||||
nsqConfig := handler.nsqConfig
|
||||
consumer, err := nsq.NewConsumer(fmt.Sprintf("%s_ai_event", nsqConfig.TopicPrefix), hostname, config)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create NSQ consumer: %v", err)
|
||||
}
|
||||
|
||||
consumer.AddHandler(handler)
|
||||
|
||||
// 4. 连接到 NSQ
|
||||
//nsqAddress := "172.18.166.219:9150" // NSQ 地址
|
||||
err = consumer.ConnectToNSQD(nsqConfig.Addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to NSQ: %v", err)
|
||||
}
|
||||
log.Println("Connected to NSQ")
|
||||
|
||||
// 5. 捕获系统信号,优雅关闭
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigChan
|
||||
|
||||
// 优雅停止消费者
|
||||
consumer.Stop()
|
||||
<-consumer.StopChan
|
||||
log.Println("NSQ Consumer stopped")
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eolinker/go-common/cftool"
|
||||
|
||||
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
|
||||
|
||||
"github.com/eolinker/go-common/store"
|
||||
|
||||
"github.com/APIParkLab/APIPark/service/ai"
|
||||
|
||||
ai_key "github.com/APIParkLab/APIPark/service/ai-key"
|
||||
|
||||
nsq "github.com/nsqio/go-nsq"
|
||||
|
||||
ai_api "github.com/APIParkLab/APIPark/service/ai-api"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cftool.Register[NSQConfig]("nsq")
|
||||
}
|
||||
|
||||
type NSQConfig struct {
|
||||
Addr string `json:"addr" yaml:"addr"`
|
||||
TopicPrefix string `json:"topic_prefix" yaml:"topic_prefix"`
|
||||
}
|
||||
|
||||
// 定义 NSQ 消息结构
|
||||
type AIProviderStatus struct {
|
||||
Provider string `json:"provider"`
|
||||
Model string `json:"model"`
|
||||
Key string `json:"key"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type AIInfo struct {
|
||||
Model string `json:"ai_model"`
|
||||
Cost interface{} `json:"ai_model_cost"`
|
||||
InputToken interface{} `json:"ai_model_input_token"`
|
||||
OutputToken interface{} `json:"ai_model_output_token"`
|
||||
TotalToken interface{} `json:"ai_model_total_token"`
|
||||
Provider string `json:"ai_provider"`
|
||||
ProviderStats []AIProviderStatus `json:"ai_provider_statuses"`
|
||||
}
|
||||
|
||||
type NSQMessage struct {
|
||||
AI AIInfo `json:"ai"`
|
||||
API string `json:"api"`
|
||||
Provider string `json:"provider"`
|
||||
RequestID string `json:"request_id"`
|
||||
TimeISO8601 string `json:"time_iso8601"`
|
||||
}
|
||||
|
||||
// NSQHandler 处理 NSQ 消息并写入 MySQL
|
||||
type NSQHandler struct {
|
||||
apiUseService ai_api.IAPIUseService `autowired:""`
|
||||
aiKeyService ai_key.IKeyService `autowired:""`
|
||||
aiService ai.IProviderService `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
nsqConfig *NSQConfig `autowired:""`
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func convertInt(value interface{}) int {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return v
|
||||
case float64:
|
||||
return int(v)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func genAIKey(key string, provider string) string {
|
||||
keys := strings.Split(key, "@")
|
||||
return strings.TrimSuffix(keys[0], fmt.Sprintf("-%s", provider))
|
||||
}
|
||||
|
||||
// HandleMessage 处理从 NSQ 读取的消息
|
||||
func (h *NSQHandler) HandleMessage(message *nsq.Message) error {
|
||||
log.Printf("Received message: %s", string(message.Body))
|
||||
|
||||
// 解析消息为结构体
|
||||
var data NSQMessage
|
||||
err := json.Unmarshal(message.Body, &data)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal message: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将时间字符串转换为 time.Time
|
||||
timestamp, err := time.Parse(time.RFC3339, data.TimeISO8601)
|
||||
if err != nil {
|
||||
log.Printf("Failed to parse timestamp: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
day := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), 0, 0, 0, 0, timestamp.Location())
|
||||
hour := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), 0, 0, 0, timestamp.Location())
|
||||
minute := time.Date(timestamp.Year(), timestamp.Month(), timestamp.Day(), timestamp.Hour(), timestamp.Minute(), 0, 0, timestamp.Location())
|
||||
return h.transaction.Transaction(context.Background(), func(ctx context.Context) error {
|
||||
finalStatus := &AIProviderStatus{}
|
||||
for _, s := range data.AI.ProviderStats {
|
||||
status := ToKeyStatus(s.Status).Int()
|
||||
key := genAIKey(s.Key, s.Provider)
|
||||
err = h.aiKeyService.Save(ctx, key, &ai_key.Edit{
|
||||
Status: &status,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to save AI key: %v", err)
|
||||
return nil
|
||||
}
|
||||
if s.Provider != data.AI.Provider {
|
||||
|
||||
pStatus := ai_dto.ProviderAbnormal.Int()
|
||||
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
|
||||
Status: &pStatus,
|
||||
})
|
||||
} else {
|
||||
pStatus := ai_dto.ProviderEnabled.Int()
|
||||
err = h.aiService.Save(ctx, s.Provider, &ai.SetProvider{
|
||||
Status: &pStatus,
|
||||
})
|
||||
}
|
||||
finalStatus = &s
|
||||
}
|
||||
if finalStatus != nil {
|
||||
//keys := strings.Split(finalStatus.Key, "@")
|
||||
key := genAIKey(finalStatus.Key, finalStatus.Provider)
|
||||
err = h.aiKeyService.IncrUseToken(ctx, key, convertInt(data.AI.TotalToken))
|
||||
if err != nil {
|
||||
log.Printf("Failed to increment AI key token: %v", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// 调用 AI API 接口
|
||||
err = h.apiUseService.Incr(context.Background(), &ai_api.IncrAPIUse{
|
||||
API: data.API,
|
||||
Service: data.Provider,
|
||||
Provider: data.AI.Provider,
|
||||
Model: data.AI.Model,
|
||||
Day: day.Unix(),
|
||||
Hour: hour.Unix(),
|
||||
Minute: minute.Unix(),
|
||||
InputToken: convertInt(data.AI.InputToken),
|
||||
OutputToken: convertInt(data.AI.OutputToken),
|
||||
TotalToken: convertInt(data.AI.TotalToken),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("Failed to call AI API: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Message processed and saved to MySQL: %+v", data)
|
||||
return nil
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
|
||||
|
||||
var (
|
||||
StatusNormal = "normal"
|
||||
StatusInvalidRequest = "invalid request"
|
||||
StatusQuotaExhausted = "quota exhausted"
|
||||
StatusExpired = "expired"
|
||||
StatusExceeded = "exceeded"
|
||||
StatusInvalid = "invalid"
|
||||
StatusTimeout = "timeout"
|
||||
)
|
||||
|
||||
func ToKeyStatus(status string) ai_key_dto.KeyStatus {
|
||||
switch status {
|
||||
case StatusNormal:
|
||||
return ai_key_dto.KeyNormal
|
||||
case StatusInvalidRequest:
|
||||
return ai_key_dto.KeyNormal
|
||||
case StatusQuotaExhausted:
|
||||
return ai_key_dto.KeyExceed
|
||||
case StatusExpired:
|
||||
return ai_key_dto.KeyExpired
|
||||
case StatusExceeded:
|
||||
return ai_key_dto.KeyNormal
|
||||
case StatusInvalid:
|
||||
return ai_key_dto.KeyError
|
||||
case StatusTimeout:
|
||||
return ai_key_dto.KeyError
|
||||
default:
|
||||
return ai_key_dto.KeyNormal
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package ai_api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/APIParkLab/APIPark/model/plugin_model"
|
||||
@@ -27,7 +26,7 @@ type imlAPIController struct {
|
||||
}
|
||||
|
||||
func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_api_dto.CreateAPI) (*ai_api_dto.API, error) {
|
||||
info, err := i.serviceModule.Get(ctx, serviceId)
|
||||
_, err := i.serviceModule.Get(ctx, serviceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -52,7 +51,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
|
||||
plugins["ai_formatter"] = api.PluginSetting{
|
||||
Config: plugin_model.ConfigType{
|
||||
"model": input.AiModel.Id,
|
||||
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
|
||||
"provider": input.AiModel.Provider,
|
||||
"config": input.AiModel.Config,
|
||||
},
|
||||
}
|
||||
@@ -73,8 +72,8 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
|
||||
Retry: input.Retry,
|
||||
Plugins: plugins,
|
||||
},
|
||||
Upstream: info.Provider.Id,
|
||||
Disable: false,
|
||||
//Upstream: input.AiModel.Provider,
|
||||
Disable: false,
|
||||
})
|
||||
|
||||
return err
|
||||
@@ -86,7 +85,7 @@ func (i *imlAPIController) Create(ctx *gin.Context, serviceId string, input *ai_
|
||||
}
|
||||
|
||||
func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string, input *ai_api_dto.EditAPI) (*ai_api_dto.API, error) {
|
||||
info, err := i.serviceModule.Get(ctx, serviceId)
|
||||
_, err := i.serviceModule.Get(ctx, serviceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -101,16 +100,16 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
|
||||
Retry: apiInfo.Proxy.Retry,
|
||||
Plugins: apiInfo.Proxy.Plugins,
|
||||
}
|
||||
var upstream *string
|
||||
//var upstream *string
|
||||
if input.AiModel != nil {
|
||||
proxy.Plugins["ai_formatter"] = api.PluginSetting{
|
||||
Config: plugin_model.ConfigType{
|
||||
"model": input.AiModel.Id,
|
||||
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
|
||||
"provider": input.AiModel.Provider,
|
||||
"config": input.AiModel.Config,
|
||||
},
|
||||
}
|
||||
upstream = &info.Provider.Id
|
||||
//upstream = &input.AiModel.Provider
|
||||
}
|
||||
|
||||
if input.AiPrompt != nil {
|
||||
@@ -128,7 +127,7 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
|
||||
Path: input.Path,
|
||||
Disable: input.Disable,
|
||||
Methods: &apiInfo.Methods,
|
||||
Upstream: upstream,
|
||||
//Upstream: upstream,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package ai_key
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type IKeyController interface {
|
||||
Create(ctx *gin.Context, providerId string, input *ai_key_dto.Create) error
|
||||
Edit(ctx *gin.Context, providerId string, id string, input *ai_key_dto.Edit) error
|
||||
Delete(ctx *gin.Context, providerId string, id string) error
|
||||
Get(ctx *gin.Context, providerId string, id string) (*ai_key_dto.Key, error)
|
||||
List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error)
|
||||
Enable(ctx *gin.Context, providerId string, id string) error
|
||||
Disable(ctx *gin.Context, providerId string, id string) error
|
||||
Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error
|
||||
}
|
||||
|
||||
func init() {
|
||||
autowire.Auto[IKeyController](func() reflect.Value {
|
||||
return reflect.ValueOf(new(imlAIKeyController))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package ai_key
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
ai_key "github.com/APIParkLab/APIPark/module/ai-key"
|
||||
ai_key_dto "github.com/APIParkLab/APIPark/module/ai-key/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var _ IKeyController = &imlAIKeyController{}
|
||||
|
||||
type imlAIKeyController struct {
|
||||
module ai_key.IKeyModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Enable(ctx *gin.Context, providerId string, id string) error {
|
||||
return i.module.UpdateKeyStatus(ctx, providerId, id, true)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Disable(ctx *gin.Context, providerId string, id string) error {
|
||||
return i.module.UpdateKeyStatus(ctx, providerId, id, false)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Create(ctx *gin.Context, providerId string, input *ai_key_dto.Create) error {
|
||||
return i.module.Create(ctx, providerId, input)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Edit(ctx *gin.Context, providerId string, id string, input *ai_key_dto.Edit) error {
|
||||
return i.module.Edit(ctx, providerId, id, input)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Delete(ctx *gin.Context, providerId string, id string) error {
|
||||
return i.module.Delete(ctx, providerId, id)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Get(ctx *gin.Context, providerId string, id string) (*ai_key_dto.Key, error) {
|
||||
return i.module.Get(ctx, providerId, id)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) List(ctx *gin.Context, providerId string, keyword string, page string, pageSize string, statuses string) ([]*ai_key_dto.Item, int64, error) {
|
||||
p, err := strconv.Atoi(page)
|
||||
if err != nil {
|
||||
if page != "" {
|
||||
return nil, 0, err
|
||||
}
|
||||
p = 1
|
||||
}
|
||||
ps, err := strconv.Atoi(pageSize)
|
||||
if err != nil {
|
||||
if pageSize != "" {
|
||||
return nil, 0, err
|
||||
}
|
||||
ps = 20
|
||||
}
|
||||
ss := make([]string, 0)
|
||||
if statuses != "" {
|
||||
err = json.Unmarshal([]byte(statuses), &ss)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
return i.module.List(ctx, providerId, keyword, p, ps, ss)
|
||||
}
|
||||
|
||||
func (i *imlAIKeyController) Sort(ctx *gin.Context, providerId string, input *ai_key_dto.Sort) error {
|
||||
return i.module.Sort(ctx, providerId, input)
|
||||
}
|
||||
@@ -1,25 +1,37 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/gin-gonic/gin"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type IProviderController interface {
|
||||
Providers(ctx *gin.Context) ([]*ai_dto.ProviderItem, error)
|
||||
ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error)
|
||||
UnConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ProviderItem, error)
|
||||
SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error)
|
||||
SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error)
|
||||
Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error)
|
||||
SimpleProvider(ctx *gin.Context, id string) (*ai_dto.SimpleProvider, error)
|
||||
LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error)
|
||||
Enable(ctx *gin.Context, id string) error
|
||||
Disable(ctx *gin.Context, id string) error
|
||||
UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error
|
||||
UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error
|
||||
Sort(ctx *gin.Context, input *ai_dto.Sort) error
|
||||
}
|
||||
|
||||
type IStatisticController interface {
|
||||
APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
autowire.Auto[IProviderController](func() reflect.Value {
|
||||
return reflect.ValueOf(&imlProviderController{})
|
||||
})
|
||||
autowire.Auto[IStatisticController](func() reflect.Value {
|
||||
return reflect.ValueOf(&imlStatisticController{})
|
||||
})
|
||||
}
|
||||
|
||||
+72
-5
@@ -1,6 +1,9 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/APIParkLab/APIPark/module/ai"
|
||||
ai_dto "github.com/APIParkLab/APIPark/module/ai/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -14,28 +17,46 @@ type imlProviderController struct {
|
||||
module ai.IProviderModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlProviderController) Sort(ctx *gin.Context, input *ai_dto.Sort) error {
|
||||
return i.module.Sort(ctx, input)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) ConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ConfiguredProviderItem, *ai_dto.BackupProvider, error) {
|
||||
return i.module.ConfiguredProviders(ctx)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) UnConfiguredProviders(ctx *gin.Context) ([]*ai_dto.ProviderItem, error) {
|
||||
return i.module.UnConfiguredProviders(ctx)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) SimpleProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, error) {
|
||||
return i.module.SimpleProviders(ctx)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) Providers(ctx *gin.Context) ([]*ai_dto.ProviderItem, error) {
|
||||
return i.module.Providers(ctx)
|
||||
func (i *imlProviderController) SimpleConfiguredProviders(ctx *gin.Context) ([]*ai_dto.SimpleProviderItem, *ai_dto.BackupProvider, error) {
|
||||
return i.module.SimpleConfiguredProviders(ctx)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) Provider(ctx *gin.Context, id string) (*ai_dto.Provider, error) {
|
||||
return i.module.Provider(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) SimpleProvider(ctx *gin.Context, id string) (*ai_dto.SimpleProvider, error) {
|
||||
return i.module.SimpleProvider(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) LLMs(ctx *gin.Context, driver string) ([]*ai_dto.LLMItem, *ai_dto.ProviderItem, error) {
|
||||
return i.module.LLMs(ctx, driver)
|
||||
}
|
||||
|
||||
func (i *imlProviderController) Enable(ctx *gin.Context, id string) error {
|
||||
return i.module.UpdateProviderStatus(ctx, id, true)
|
||||
//return i.module.UpdateProviderStatus(ctx, id, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *imlProviderController) Disable(ctx *gin.Context, id string) error {
|
||||
return i.module.UpdateProviderStatus(ctx, id, false)
|
||||
//return i.module.UpdateProviderStatus(ctx, id, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string, input *ai_dto.UpdateConfig) error {
|
||||
@@ -43,5 +64,51 @@ func (i *imlProviderController) UpdateProviderConfig(ctx *gin.Context, id string
|
||||
}
|
||||
|
||||
func (i *imlProviderController) UpdateProviderDefaultLLM(ctx *gin.Context, id string, input *ai_dto.UpdateLLM) error {
|
||||
return i.module.UpdateProviderDefaultLLM(ctx, id, input)
|
||||
//return i.module.UpdateProviderDefaultLLM(ctx, id, input)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ IStatisticController = (*imlStatisticController)(nil)
|
||||
|
||||
type imlStatisticController struct {
|
||||
module ai.IAIAPIModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlStatisticController) APIs(ctx *gin.Context, keyword string, providerId string, start string, end string, page string, pageSize string, sortCondition string, asc string, models string, services string) ([]*ai_dto.APIItem, *ai_dto.Condition, int64, error) {
|
||||
s, err := strconv.ParseInt(start, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
e, err := strconv.ParseInt(end, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
|
||||
p, err := strconv.Atoi(page)
|
||||
if err != nil {
|
||||
if page != "" {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
p = 1
|
||||
}
|
||||
|
||||
ps, err := strconv.Atoi(pageSize)
|
||||
if err != nil {
|
||||
if pageSize != "" {
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
ps = 20
|
||||
}
|
||||
ms := make([]string, 0)
|
||||
if models != "" {
|
||||
json.Unmarshal([]byte(models), &ms)
|
||||
ms = append(ms, models)
|
||||
}
|
||||
ss := make([]string, 0)
|
||||
if services != "" {
|
||||
json.Unmarshal([]byte(services), &ss)
|
||||
ss = append(ss, services)
|
||||
}
|
||||
return i.module.APIs(ctx, keyword, providerId, s, e, p, ps, sortCondition, asc == "true", ms, ss)
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ func (p *imlCluster) Check(ctx *gin.Context, input *cluster_dto.CheckCluster) ([
|
||||
// return id, nil
|
||||
//}
|
||||
//
|
||||
//func (p *imlCluster) Search(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) {
|
||||
// return p.module.Search(ctx, keyword)
|
||||
//func (p *imlCluster) SearchByDriver(ctx *gin.Context, keyword string) ([]*parition_dto.Item, error) {
|
||||
// return p.module.SearchByDriver(ctx, keyword)
|
||||
//}
|
||||
//
|
||||
//func (p *imlCluster) Simple(ctx *gin.Context) ([]*parition_dto.Simple, error) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
log_dto "github.com/APIParkLab/APIPark/module/log/dto"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ILogController interface {
|
||||
Save(ctx *gin.Context, driver string, input *log_dto.Save) error
|
||||
Get(ctx *gin.Context, driver string) (*log_dto.LogSource, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
logController := &imlLogController{}
|
||||
autowire.Auto[ILogController](func() reflect.Value {
|
||||
return reflect.ValueOf(logController)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"github.com/APIParkLab/APIPark/module/log"
|
||||
log_dto "github.com/APIParkLab/APIPark/module/log/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type imlLogController struct {
|
||||
module log.ILogModule `autowired:""`
|
||||
}
|
||||
|
||||
func (c *imlLogController) Save(ctx *gin.Context, driver string, input *log_dto.Save) error {
|
||||
return c.module.Save(ctx, driver, input)
|
||||
}
|
||||
|
||||
func (c *imlLogController) Get(ctx *gin.Context, driver string) (*log_dto.LogSource, error) {
|
||||
return c.module.Get(ctx, driver)
|
||||
}
|
||||
@@ -17,6 +17,70 @@ type imlMonitorStatisticController struct {
|
||||
module monitor.IMonitorStatisticModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlMonitorStatisticController) Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) {
|
||||
switch dataType {
|
||||
case monitor_dto.DataTypeApi:
|
||||
return i.module.ApiStatistics(ctx, input)
|
||||
case monitor_dto.DataTypeProvider:
|
||||
return i.module.ProviderStatistics(ctx, input)
|
||||
case monitor_dto.DataTypeSubscriber:
|
||||
return i.module.SubscriberStatistics(ctx, input)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported data type: %s", dataType)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *imlMonitorStatisticController) InvokeTrend(ctx *gin.Context, dataType string, id string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
|
||||
switch dataType {
|
||||
case monitor_dto.DataTypeApi:
|
||||
return i.module.APITrend(ctx, id, input)
|
||||
case monitor_dto.DataTypeProvider:
|
||||
return i.module.ProviderTrend(ctx, id, input)
|
||||
case monitor_dto.DataTypeSubscriber:
|
||||
return i.module.SubscriberTrend(ctx, id, input)
|
||||
default:
|
||||
return nil, "", fmt.Errorf("unsupported data type: %s", dataType)
|
||||
}
|
||||
}
|
||||
|
||||
func (i *imlMonitorStatisticController) InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) {
|
||||
if dataType == monitor_dto.DataTypeApi && typ == monitor_dto.DataTypeSubscriber || dataType == monitor_dto.DataTypeSubscriber && typ == monitor_dto.DataTypeApi {
|
||||
return i.module.InvokeTrendWithSubscriberAndApi(ctx, api, subscriber, input)
|
||||
} else if dataType == monitor_dto.DataTypeApi && typ == monitor_dto.DataTypeProvider || dataType == monitor_dto.DataTypeProvider && typ == monitor_dto.DataTypeApi {
|
||||
return i.module.InvokeTrendWithProviderAndApi(ctx, provider, api, input)
|
||||
}
|
||||
return nil, "", fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
|
||||
}
|
||||
|
||||
func (i *imlMonitorStatisticController) StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error) {
|
||||
switch dataType {
|
||||
case monitor_dto.DataTypeApi:
|
||||
switch typ {
|
||||
case monitor_dto.DataTypeProvider:
|
||||
return i.module.ProviderStatisticsOnApi(ctx, id, input)
|
||||
case monitor_dto.DataTypeSubscriber:
|
||||
return i.module.SubscriberStatisticsOnApi(ctx, id, input)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
|
||||
}
|
||||
case monitor_dto.DataTypeProvider:
|
||||
switch typ {
|
||||
case monitor_dto.DataTypeApi:
|
||||
return i.module.ApiStatisticsOnProvider(ctx, id, input)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
|
||||
}
|
||||
case monitor_dto.DataTypeSubscriber:
|
||||
switch typ {
|
||||
case monitor_dto.DataTypeApi:
|
||||
return i.module.ApiStatisticsOnSubscriber(ctx, id, input)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported data type: %s", dataType)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -16,7 +16,12 @@ type IMonitorStatisticController interface {
|
||||
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)
|
||||
Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error)
|
||||
|
||||
InvokeTrend(ctx *gin.Context, dataType string, id string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
|
||||
|
||||
InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
|
||||
StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error)
|
||||
}
|
||||
|
||||
type IMonitorConfigController interface {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
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)
|
||||
@@ -15,6 +16,10 @@ type imlAPIController struct {
|
||||
module router.IRouterModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAPIController) Simple(ctx *gin.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error) {
|
||||
return i.module.SimpleAPIs(ctx, input)
|
||||
}
|
||||
|
||||
func (i *imlAPIController) Detail(ctx *gin.Context, serviceId string, apiId string) (*router_dto.Detail, error) {
|
||||
return i.module.Detail(ctx, serviceId, apiId)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
|
||||
"reflect"
|
||||
|
||||
api_doc_dto "github.com/APIParkLab/APIPark/module/api-doc/dto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
@@ -24,6 +25,7 @@ type IRouterController interface {
|
||||
Delete(ctx *gin.Context, serviceId string, apiId string) error
|
||||
// Prefix 获取API前缀
|
||||
Prefix(ctx *gin.Context, serviceId string) (string, bool, error)
|
||||
Simple(ctx *gin.Context, input *router_dto.InputSimpleAPI) ([]*router_dto.SimpleItem, error)
|
||||
}
|
||||
|
||||
type IAPIDocController interface {
|
||||
|
||||
+161
-33
@@ -5,6 +5,19 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eolinker/go-common/pm3"
|
||||
|
||||
"github.com/APIParkLab/APIPark/module/system"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
|
||||
api_doc "github.com/APIParkLab/APIPark/module/api-doc"
|
||||
|
||||
upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto"
|
||||
|
||||
"github.com/eolinker/eosc/log"
|
||||
|
||||
application_authorization "github.com/APIParkLab/APIPark/module/application-authorization"
|
||||
application_authorization_dto "github.com/APIParkLab/APIPark/module/application-authorization/dto"
|
||||
@@ -22,7 +35,6 @@ import (
|
||||
"github.com/APIParkLab/APIPark/module/service"
|
||||
service_dto "github.com/APIParkLab/APIPark/module/service/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"
|
||||
"github.com/google/uuid"
|
||||
@@ -39,29 +51,102 @@ type imlServiceController struct {
|
||||
docModule service.IServiceDocModule `autowired:""`
|
||||
aiAPIModule ai_api.IAPIModule `autowired:""`
|
||||
routerModule router.IRouterModule `autowired:""`
|
||||
apiDocModule api_doc.IAPIDocModule `autowired:""`
|
||||
providerModule ai.IProviderModule `autowired:""`
|
||||
upstreamModule upstream.IUpstreamModule `autowired:""`
|
||||
settingModule system.ISettingModule `autowired:""`
|
||||
transaction store.ITransaction `autowired:""`
|
||||
}
|
||||
|
||||
func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream {
|
||||
return &upstream_dto.Upstream{
|
||||
Type: "http",
|
||||
Balance: "round-robin",
|
||||
Timeout: 300000,
|
||||
Retry: 0,
|
||||
Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider),
|
||||
LimitPeerSecond: 0,
|
||||
ProxyHeaders: nil,
|
||||
Scheme: uri.Scheme(),
|
||||
PassHost: "node",
|
||||
Nodes: []*upstream_dto.NodeConfig{
|
||||
{
|
||||
Address: uri.Host(),
|
||||
Weight: 100,
|
||||
},
|
||||
},
|
||||
var (
|
||||
loader = openapi3.NewLoader()
|
||||
)
|
||||
|
||||
func (i *imlServiceController) swagger(ctx *gin.Context, id string) (*openapi3.T, error) {
|
||||
doc, err := i.apiDocModule.GetDoc(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmp, err := loader.LoadFromData([]byte(doc.Content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := i.settingModule.Get(ctx)
|
||||
|
||||
tmp.AddServer(&openapi3.Server{
|
||||
URL: cfg.InvokeAddress,
|
||||
})
|
||||
return tmp, nil
|
||||
}
|
||||
|
||||
func (i *imlServiceController) ExportSwagger(ctx *gin.Context) {
|
||||
id, has := ctx.Params.Get("id")
|
||||
if !has {
|
||||
ctx.JSON(200, &pm3.Response{
|
||||
Code: -1,
|
||||
Success: "fail",
|
||||
Message: fmt.Sprintf("id is required"),
|
||||
})
|
||||
return
|
||||
}
|
||||
s, err := i.module.Get(ctx, id)
|
||||
if err != nil {
|
||||
ctx.JSON(200, &pm3.Response{
|
||||
Code: -1,
|
||||
Success: "fail",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
tmp, err := i.swagger(ctx, id)
|
||||
if err != nil {
|
||||
ctx.JSON(200, &pm3.Response{
|
||||
Code: -1,
|
||||
Success: "fail",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
data, _ := tmp.MarshalJSON()
|
||||
ctx.Status(200)
|
||||
// 设置响应头
|
||||
ctx.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.json", strings.Replace(s.Name, " ", "_", -1)))
|
||||
ctx.Header("Content-Type", "application/octet-stream")
|
||||
ctx.Header("Content-Transfer-Encoding", "binary")
|
||||
ctx.Writer.Write(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (i *imlServiceController) Swagger(ctx *gin.Context) {
|
||||
id, has := ctx.Params.Get("id")
|
||||
if !has {
|
||||
ctx.JSON(200, &pm3.Response{
|
||||
Code: -1,
|
||||
Success: "fail",
|
||||
Message: fmt.Sprintf("id is required"),
|
||||
})
|
||||
return
|
||||
}
|
||||
tmp, err := i.swagger(ctx, id)
|
||||
if err != nil {
|
||||
ctx.JSON(200, &pm3.Response{
|
||||
Code: -1,
|
||||
Success: "fail",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ctx.JSON(200, tmp)
|
||||
return
|
||||
}
|
||||
|
||||
func (i *imlServiceController) Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) {
|
||||
return i.module.Simple(ctx)
|
||||
}
|
||||
|
||||
func (i *imlServiceController) MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) {
|
||||
return i.module.MySimple(ctx)
|
||||
}
|
||||
|
||||
func (i *imlServiceController) editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
|
||||
@@ -212,10 +297,7 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//_, err = i.upstreamModule.Save(ctx, info.Id, newAIUpstream(info.Id, *input.Provider, p.URI()))
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
return i.docModule.SaveServiceDoc(ctx, info.Id, &service_dto.SaveServiceDoc{
|
||||
Doc: "The Translation API allows developers to translate text from one language to another. It supports multiple languages and enables easy integration of high-quality translation features into applications. With simple API requests, you can quickly translate content into different target languages.",
|
||||
})
|
||||
@@ -228,27 +310,49 @@ func (i *imlServiceController) SearchMyServices(ctx *gin.Context, teamId string,
|
||||
return i.module.SearchMyServices(ctx, teamId, keyword)
|
||||
}
|
||||
|
||||
//func (i *imlServiceController) Simple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
|
||||
// return i.module.Simple(ctx, keyword)
|
||||
//}
|
||||
//
|
||||
//func (i *imlServiceController) MySimple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) {
|
||||
// return i.module.MySimple(ctx, keyword)
|
||||
//}
|
||||
|
||||
func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Service, error) {
|
||||
now := time.Now()
|
||||
defer func() {
|
||||
log.Infof("get service %s cost %d ms", id, time.Since(now).Milliseconds())
|
||||
}()
|
||||
return i.module.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) {
|
||||
return i.module.Search(ctx, teamID, keyword)
|
||||
func (i *imlServiceController) Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error) {
|
||||
return i.module.Search(ctx, teamIDs, keyword)
|
||||
}
|
||||
|
||||
func (i *imlServiceController) Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) {
|
||||
if input.Kind == "ai" {
|
||||
return i.createAIService(ctx, teamID, input)
|
||||
}
|
||||
return i.module.Create(ctx, teamID, input)
|
||||
var err error
|
||||
var info *service_dto.Service
|
||||
err = i.transaction.Transaction(ctx, func(txCtx context.Context) error {
|
||||
info, err = i.module.Create(txCtx, teamID, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("/%s/", strings.Trim(input.Prefix, "/"))
|
||||
_, err = i.routerModule.Create(txCtx, info.Id, &router_dto.Create{
|
||||
Id: uuid.New().String(),
|
||||
Name: "",
|
||||
Path: path + "*",
|
||||
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
|
||||
Description: "auto create by create service",
|
||||
Protocols: []string{"http", "https"},
|
||||
MatchRules: nil,
|
||||
Upstream: "",
|
||||
Proxy: &router_dto.InputProxy{
|
||||
Path: path,
|
||||
Timeout: 30000,
|
||||
Retry: 0,
|
||||
},
|
||||
Disable: false,
|
||||
})
|
||||
return err
|
||||
})
|
||||
return info, err
|
||||
}
|
||||
|
||||
func (i *imlServiceController) Edit(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) {
|
||||
@@ -279,6 +383,10 @@ type imlAppController struct {
|
||||
authModule application_authorization.IAuthorizationModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlAppController) SearchCanSubscribe(ctx *gin.Context, serviceId string) ([]*service_dto.SimpleAppItem, error) {
|
||||
return i.module.SearchCanSubscribe(ctx, serviceId)
|
||||
}
|
||||
|
||||
func (i *imlAppController) Search(ctx *gin.Context, teamId string, keyword string) ([]*service_dto.AppItem, error) {
|
||||
return i.module.Search(ctx, teamId, keyword)
|
||||
}
|
||||
@@ -327,3 +435,23 @@ func (i *imlAppController) GetApp(ctx *gin.Context, appId string) (*service_dto.
|
||||
func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error {
|
||||
return i.module.DeleteApp(ctx, appId)
|
||||
}
|
||||
|
||||
func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream {
|
||||
return &upstream_dto.Upstream{
|
||||
Type: "http",
|
||||
Balance: "round-robin",
|
||||
Timeout: 300000,
|
||||
Retry: 0,
|
||||
Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider),
|
||||
LimitPeerSecond: 0,
|
||||
ProxyHeaders: nil,
|
||||
Scheme: uri.Scheme(),
|
||||
PassHost: "node",
|
||||
Nodes: []*upstream_dto.NodeConfig{
|
||||
{
|
||||
Address: uri.Host(),
|
||||
Weight: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type IServiceController interface {
|
||||
Get(ctx *gin.Context, id string) (*service_dto.Service, error)
|
||||
// SearchMyServices 搜索服务
|
||||
SearchMyServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
|
||||
Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
|
||||
Search(ctx *gin.Context, teamIDs string, keyword string) ([]*service_dto.ServiceItem, error)
|
||||
// Create 创建
|
||||
Create(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error)
|
||||
// Edit 编辑
|
||||
@@ -24,12 +24,11 @@ type IServiceController interface {
|
||||
Delete(ctx *gin.Context, id string) error
|
||||
ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error)
|
||||
SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error
|
||||
Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error)
|
||||
MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error)
|
||||
|
||||
//createAIService(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error)
|
||||
//editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error)
|
||||
//DeleteAIService(ctx *gin.Context, id string) error
|
||||
//SearchMyAIServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
|
||||
//SearchAIServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error)
|
||||
Swagger(ctx *gin.Context)
|
||||
ExportSwagger(ctx *gin.Context)
|
||||
}
|
||||
|
||||
type IAppController interface {
|
||||
@@ -42,6 +41,7 @@ type IAppController interface {
|
||||
// SimpleApps 获取简易项目列表
|
||||
SimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
|
||||
MySimpleApps(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
|
||||
SearchCanSubscribe(ctx *gin.Context, keyword string) ([]*service_dto.SimpleAppItem, error)
|
||||
GetApp(ctx *gin.Context, appId string) (*service_dto.App, error)
|
||||
DeleteApp(ctx *gin.Context, appId string) error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eolinker/go-common/utils"
|
||||
|
||||
strategy_filter "github.com/APIParkLab/APIPark/strategy-filter"
|
||||
|
||||
"github.com/APIParkLab/APIPark/module/service"
|
||||
"github.com/APIParkLab/APIPark/module/strategy"
|
||||
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var _ IStrategyController = (*imlStrategyController)(nil)
|
||||
|
||||
type imlStrategyController struct {
|
||||
strategyModule strategy.IStrategyModule `autowired:""`
|
||||
serviceModule service.IServiceModule `autowired:""`
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) Restore(ctx *gin.Context, id string) error {
|
||||
return i.strategyModule.Restore(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) DeleteServiceStrategy(ctx *gin.Context, serviceId string, id string) error {
|
||||
return i.strategyModule.DeleteServiceStrategy(ctx, serviceId, id)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) ToPublish(ctx *gin.Context, driver string) ([]*strategy_dto.ToPublishItem, string, string, bool, error) {
|
||||
list, err := i.strategyModule.ToPublish(ctx, driver)
|
||||
if err != nil {
|
||||
return nil, "", "", false, err
|
||||
}
|
||||
data, _ := json.Marshal(list)
|
||||
source := base64.StdEncoding.EncodeToString(data)
|
||||
return list, source, time.Now().Format("20060102150405") + "-release", len(list) > 0, nil
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) FilterGlobalRemote(ctx *gin.Context, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) {
|
||||
f, has := strategy_filter.RemoteFilter(name)
|
||||
if !has {
|
||||
return nil, nil, 0, "", "", fmt.Errorf("filter not found: %s", name)
|
||||
}
|
||||
scopeAllow := false
|
||||
for _, s := range f.Scopes() {
|
||||
if s == strategy_filter.ScopeGlobal {
|
||||
scopeAllow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !scopeAllow {
|
||||
return nil, nil, 0, "", "", fmt.Errorf("scope not allowed: %s", name)
|
||||
}
|
||||
|
||||
list, total, err := f.RemoteList(ctx, "", nil, -1, -1)
|
||||
if err != nil {
|
||||
return nil, nil, 0, "", "", err
|
||||
}
|
||||
return utils.SliceToSlice(f.Titles(), func(l strategy_filter.OptionTitle) *strategy_dto.Title {
|
||||
return &strategy_dto.Title{
|
||||
Field: l.Field,
|
||||
Title: l.Title,
|
||||
}
|
||||
}), list, total, f.Key(), f.Key(), nil
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) FilterServiceRemote(ctx *gin.Context, serviceId string, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) {
|
||||
f, has := strategy_filter.RemoteFilter(name)
|
||||
if !has {
|
||||
return nil, nil, 0, "", "", fmt.Errorf("filter not found: %s", name)
|
||||
}
|
||||
scopeAllow := false
|
||||
for _, s := range f.Scopes() {
|
||||
if s == strategy_filter.ScopeService {
|
||||
scopeAllow = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !scopeAllow {
|
||||
return nil, nil, 0, "", "", fmt.Errorf("scope not allowed: %s", name)
|
||||
}
|
||||
list, total, err := f.RemoteList(ctx, "", map[string]interface{}{"service": serviceId}, -1, -1)
|
||||
if err != nil {
|
||||
return nil, nil, 0, "", "", err
|
||||
}
|
||||
return utils.SliceToSlice(f.Titles(), func(l strategy_filter.OptionTitle) *strategy_dto.Title {
|
||||
return &strategy_dto.Title{
|
||||
Field: l.Field,
|
||||
Title: l.Title,
|
||||
}
|
||||
}), list, total, f.Key(), "list", nil
|
||||
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) filterOptions(ctx *gin.Context, scope string) ([]*strategy_dto.FilterOption, error) {
|
||||
m, has := strategy_filter.Options(scope)
|
||||
if !has {
|
||||
return nil, fmt.Errorf("scope not found: %s", scope)
|
||||
}
|
||||
|
||||
list := utils.MapToSlice(m, func(key string, value *strategy_filter.Option) *strategy_dto.FilterOption {
|
||||
pattern := ""
|
||||
if value.Pattern != nil {
|
||||
pattern = value.Pattern.String()
|
||||
}
|
||||
return &strategy_dto.FilterOption{
|
||||
Name: value.Name,
|
||||
Title: value.Title,
|
||||
Type: value.Type,
|
||||
Pattern: pattern,
|
||||
Options: value.Options,
|
||||
}
|
||||
})
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].Name < list[j].Name
|
||||
})
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) FilterServiceOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) {
|
||||
return i.filterOptions(ctx, strategy_filter.ScopeService)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) FilterGlobalOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) {
|
||||
return i.filterOptions(ctx, strategy_filter.ScopeGlobal)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) GetStrategy(ctx *gin.Context, id string) (*strategy_dto.Strategy, error) {
|
||||
return i.strategyModule.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) search(ctx *gin.Context, keyword string, scope strategy_dto.Scope, target string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) {
|
||||
p, err := strconv.Atoi(page)
|
||||
if err != nil {
|
||||
if page != "" {
|
||||
return nil, 0, fmt.Errorf("page error: %s", err)
|
||||
}
|
||||
p = 1
|
||||
}
|
||||
ps, err := strconv.Atoi(pageSize)
|
||||
if err != nil {
|
||||
if pageSize != "" {
|
||||
return nil, 0, fmt.Errorf("page size error: %s", err)
|
||||
}
|
||||
ps = 20
|
||||
}
|
||||
ss := make([]string, 0)
|
||||
json.Unmarshal([]byte(sort), &ss)
|
||||
fs := make([]string, 0)
|
||||
json.Unmarshal([]byte(filters), &fs)
|
||||
list, total, err := i.strategyModule.Search(ctx, keyword, driver, scope, target, p, ps, fs, ss...)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) GlobalStrategyList(ctx *gin.Context, keyword string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) {
|
||||
|
||||
return i.search(ctx, keyword, strategy_dto.ToScope(strategy_dto.ScopeGlobal), "", driver, page, pageSize, order, sort, filters)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) CreateGlobalStrategy(ctx *gin.Context, driver string, input *strategy_dto.Create) error {
|
||||
input.Driver = driver
|
||||
input.Scope = strategy_dto.ToScope(strategy_dto.ScopeGlobal)
|
||||
|
||||
return i.strategyModule.Create(ctx, input)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) PublishGlobalStrategy(ctx *gin.Context, driver string) error {
|
||||
return i.strategyModule.Publish(ctx, driver, strategy_dto.ScopeGlobal, "")
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) ServiceStrategyList(ctx *gin.Context, keyword string, serviceId string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) {
|
||||
|
||||
return i.search(ctx, keyword, strategy_dto.ToScope(strategy_dto.ScopeService), serviceId, driver, page, pageSize, order, sort, filters)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) CreateServiceStrategy(ctx *gin.Context, serviceId string, driver string, input *strategy_dto.Create) error {
|
||||
_, err := i.serviceModule.Get(ctx, serviceId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create service strategy error: %s", err)
|
||||
}
|
||||
input.Driver = driver
|
||||
input.Scope = strategy_dto.ToScope(strategy_dto.ScopeService)
|
||||
input.Target = serviceId
|
||||
|
||||
return i.strategyModule.Create(ctx, input)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) EditStrategy(ctx *gin.Context, id string, input *strategy_dto.Edit) error {
|
||||
return i.strategyModule.Edit(ctx, id, input)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) EnableStrategy(ctx *gin.Context, id string) error {
|
||||
return i.strategyModule.Enable(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) DisableStrategy(ctx *gin.Context, id string) error {
|
||||
return i.strategyModule.Disable(ctx, id)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) DeleteStrategy(ctx *gin.Context, id string) error {
|
||||
return i.strategyModule.Delete(ctx, id)
|
||||
}
|
||||
|
||||
func genTime(t string, defaultValue time.Time) (time.Time, error) {
|
||||
if t == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
s, err := strconv.ParseInt(t, 10, 64)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Unix(s, 0), nil
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) GetStrategyLogs(ctx *gin.Context, keyword string, strategyId string, start string, end string, limit string, offset string) ([]*strategy_dto.LogItem, int64, error) {
|
||||
now := time.Now()
|
||||
|
||||
s, err := genTime(start, now.Add(-time.Hour*24*30))
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("start time error: %s", err)
|
||||
}
|
||||
e, err := genTime(end, now)
|
||||
if err != nil {
|
||||
|
||||
return nil, 0, fmt.Errorf("end time error: %s", err)
|
||||
}
|
||||
if s.After(e) {
|
||||
return nil, 0, fmt.Errorf("start time must be less than end time")
|
||||
}
|
||||
l, err := strconv.ParseInt(limit, 10, 64)
|
||||
if err != nil && limit != "" {
|
||||
|
||||
return nil, 0, err
|
||||
}
|
||||
o, err := strconv.ParseInt(offset, 10, 64)
|
||||
if err != nil && offset != "" {
|
||||
return nil, 0, err
|
||||
}
|
||||
if l < 1 {
|
||||
l = 15
|
||||
}
|
||||
if o < 1 {
|
||||
o = 1
|
||||
}
|
||||
return i.strategyModule.GetStrategyLogs(ctx, keyword, strategyId, s, e, l, o)
|
||||
}
|
||||
|
||||
func (i *imlStrategyController) LogInfo(ctx *gin.Context, id string) (*strategy_dto.LogInfo, error) {
|
||||
|
||||
return i.strategyModule.StrategyLogInfo(ctx, id)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto"
|
||||
"github.com/eolinker/go-common/autowire"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type IStrategyController interface {
|
||||
GlobalStrategyList(ctx *gin.Context, keyword string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error)
|
||||
CreateGlobalStrategy(ctx *gin.Context, driver string, input *strategy_dto.Create) error
|
||||
PublishGlobalStrategy(ctx *gin.Context, driver string) error
|
||||
|
||||
ServiceStrategyList(ctx *gin.Context, keyword string, serviceId string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error)
|
||||
CreateServiceStrategy(ctx *gin.Context, serviceId string, driver string, input *strategy_dto.Create) error
|
||||
|
||||
EditStrategy(ctx *gin.Context, id string, input *strategy_dto.Edit) error
|
||||
GetStrategy(ctx *gin.Context, id string) (*strategy_dto.Strategy, error)
|
||||
EnableStrategy(ctx *gin.Context, id string) error
|
||||
DisableStrategy(ctx *gin.Context, id string) error
|
||||
|
||||
DeleteStrategy(ctx *gin.Context, id string) error
|
||||
DeleteServiceStrategy(ctx *gin.Context, serviceId string, id string) error
|
||||
|
||||
Restore(ctx *gin.Context, id string) error
|
||||
|
||||
FilterGlobalOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error)
|
||||
FilterServiceOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error)
|
||||
|
||||
FilterGlobalRemote(ctx *gin.Context, name string) ([]*strategy_dto.Title, []any, int64, string, string, error)
|
||||
FilterServiceRemote(ctx *gin.Context, serviceId string, name string) ([]*strategy_dto.Title, []any, int64, string, string, error)
|
||||
|
||||
ToPublish(ctx *gin.Context, driver string) ([]*strategy_dto.ToPublishItem, string, string, bool, error)
|
||||
|
||||
GetStrategyLogs(ctx *gin.Context, keyword string, strategyId string, start string, end string, limit string, offset string) ([]*strategy_dto.LogItem, int64, error)
|
||||
LogInfo(ctx *gin.Context, id string) (*strategy_dto.LogInfo, error)
|
||||
}
|
||||
|
||||
type IStrategyCommonController interface {
|
||||
}
|
||||
|
||||
func init() {
|
||||
autowire.Auto[IStrategyController](func() reflect.Value {
|
||||
return reflect.ValueOf(&imlStrategyController{})
|
||||
})
|
||||
}
|
||||
@@ -265,7 +265,8 @@ func (i *imlInitController) OnInit() {
|
||||
return fmt.Errorf("create default team error: %v", err)
|
||||
}
|
||||
// 创建Rest服务
|
||||
_, err = i.serviceModule.Create(ctx, info.Id, &service_dto.CreateService{
|
||||
restPath := "/rest-demo"
|
||||
serviceInfo, err := i.serviceModule.Create(ctx, info.Id, &service_dto.CreateService{
|
||||
Name: "REST Demo Service",
|
||||
Prefix: "/rest-demo",
|
||||
Description: "Auto created By APIPark",
|
||||
@@ -277,6 +278,26 @@ func (i *imlInitController) OnInit() {
|
||||
if err != nil {
|
||||
return fmt.Errorf("create default service error: %v", err)
|
||||
}
|
||||
path := fmt.Sprintf("/%s/", strings.Trim(restPath, "/"))
|
||||
_, err = i.routerModule.Create(ctx, serviceInfo.Id, &router_dto.Create{
|
||||
Id: uuid.NewString(),
|
||||
Name: "",
|
||||
Path: path + "*",
|
||||
Methods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodOptions},
|
||||
Description: "auto create by create service",
|
||||
Protocols: []string{"http", "https"},
|
||||
MatchRules: nil,
|
||||
Upstream: "",
|
||||
Proxy: &router_dto.InputProxy{
|
||||
Path: path,
|
||||
Timeout: 30000,
|
||||
Retry: 0,
|
||||
},
|
||||
Disable: false,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("create default router error: %v", err)
|
||||
}
|
||||
// 创建AI服务
|
||||
err = i.createAIService(ctx, info.Id, &service_dto.CreateService{
|
||||
Name: "AI Demo Service",
|
||||
@@ -416,7 +437,7 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
|
||||
plugins["ai_formatter"] = api.PluginSetting{
|
||||
Config: plugin_model.ConfigType{
|
||||
"model": aiModel.Id,
|
||||
"provider": fmt.Sprintf("%s@ai-provider", info.Provider.Id),
|
||||
"provider": info.Provider.Id,
|
||||
"config": aiModel.Config,
|
||||
},
|
||||
}
|
||||
@@ -436,8 +457,8 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
|
||||
Retry: retry,
|
||||
Plugins: plugins,
|
||||
},
|
||||
Disable: false,
|
||||
Upstream: info.Provider.Id,
|
||||
Disable: false,
|
||||
//Upstream: info.Provider.Id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
.next
|
||||
*.d.ts
|
||||
*.js
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
},
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
|
||||
"rules": {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"warn",
|
||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"useTabs": false,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "always",
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"bracketLine": true
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
Create detailed components with these requirements:
|
||||
1. Use 'use client' directive for client-side components
|
||||
2. Style with Tailwind CSS utility classes for responsive design
|
||||
3. Use React Router for navigation
|
||||
4. Use Ant Design for UI components
|
||||
5. Use iconify React for icons (from @iconify/react package). Do NOT use other UI libraries unless requested
|
||||
6. Use local photos from public folder where appropriate, only valid URLs you know exist
|
||||
7. Create root layout.tsx page that wraps necessary navigation items to all pages
|
||||
8. MUST implement the navigation elements items in their rightful place i.e. Left sidebar, Top header
|
||||
9. Accurately implement necessary grid layouts
|
||||
10. Follow proper import practices:
|
||||
- Use @/ path aliases
|
||||
- Keep component imports organized
|
||||
- Update current packages/core/src/pages/Root.tsx with new comprehensive code
|
||||
- Don't forget root route (page.tsx) handling
|
||||
- You MUST complete the entire prompt before stopping
|
||||
11. Table component should use `import PageList from "@common/components/aoplatform/PageList.tsx"`
|
||||
12. PageList component MUST use addNewBtnTitle for add button, NOT toolBarRender. Example:
|
||||
<PageList
|
||||
id="global_team"
|
||||
className="pl-btnbase"
|
||||
ref={pageListRef}
|
||||
columns = {[...columns]}
|
||||
request = {()=>getTeamList()}
|
||||
showPagination={false}
|
||||
addNewBtnTitle={$t('添加团队')}
|
||||
addNewBtnAccess = "system.organization.team.add"
|
||||
searchPlaceholder={$t("输入名称、ID、负责人查找团队")}
|
||||
onAddNewBtnClick={()=>{openModal('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value)}}
|
||||
onRowClick={(row:TeamTableListItem)=>(navigate(`../inside/${row.id}/setting`))}
|
||||
/>
|
||||
13. use `const { fetchData } = useFetch()` to fetch http data,such as
|
||||
```tsx
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
```
|
||||
14. can't not import new package!
|
||||
@@ -1,17 +0,0 @@
|
||||
# 部署
|
||||
|
||||
## 代码同步
|
||||
packages目录下,部分子项目为企业版独有,不要同步到开源版:
|
||||
packages/businessEntry, packages/openApi, packages/systemRunning, README.pro.md
|
||||
|
||||
## 安装依赖
|
||||
建议使用pnpm
|
||||
`npm install -g pnpm`
|
||||
使用pnpm安装依赖
|
||||
`pnpm install`
|
||||
|
||||
## 编译
|
||||
### 开源版本
|
||||
`pnpm run build`
|
||||
### 企业版本
|
||||
`pnpm run build:pro`
|
||||
@@ -6,7 +6,7 @@ const systemLanguage = {
|
||||
en_US: 'en-US',
|
||||
zh_CN: 'zh-CN',
|
||||
ja_JP: 'ja-JP',
|
||||
zh_TW: 'zh-TW'
|
||||
zh_TW: 'zh-TW',
|
||||
};
|
||||
const localesDir = 'packages/common/src/locales/scan';
|
||||
const newJsonDir = 'packages/common/src/locales/scan/newJson';
|
||||
@@ -19,7 +19,7 @@ fs.readdirSync(localesDir).forEach(file => {
|
||||
const lang = path.basename(file, '.json');
|
||||
const filePath = path.join(localesDir, file);
|
||||
try {
|
||||
console.log('Current working directory:', process.cwd(),filePath);
|
||||
console.log('Current working directory:', process.cwd(), filePath);
|
||||
const existJsonData = fs.readFileSync(filePath);
|
||||
existData[lang] = JSON.parse(existJsonData);
|
||||
} catch (error) {
|
||||
@@ -36,20 +36,18 @@ fs.readdirSync(localesDir).forEach(file => {
|
||||
|
||||
const keyList = Object.keys(existData);
|
||||
|
||||
|
||||
// 清空 newJson 目录下的所有语言文件
|
||||
Object.values(systemLanguage).forEach(lng => {
|
||||
const newJsonPath = path.join(newJsonDir, `${lng}.json`);
|
||||
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
|
||||
fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
input: [
|
||||
'packages/*/src/**/*.{js,jsx,tsx,ts}',
|
||||
// 不需要扫描的文件加!
|
||||
'!packages/*/src/locales/**',
|
||||
'!**/node_modules/**'
|
||||
'!**/node_modules/**',
|
||||
],
|
||||
output: 'packages/common/src/locales/scan', // 输出目录
|
||||
options: {
|
||||
@@ -62,15 +60,15 @@ module.exports = {
|
||||
loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录)
|
||||
savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key)
|
||||
jsonIndent: 2,
|
||||
lineEnding: '\n'
|
||||
lineEnding: '\n',
|
||||
},
|
||||
removeUnusedKeys: true,
|
||||
nsSeparator: false, // namespace separator
|
||||
keySeparator: false, // key separator
|
||||
interpolation: {
|
||||
prefix: '{{',
|
||||
suffix: '}}'
|
||||
}
|
||||
suffix: '}}',
|
||||
},
|
||||
},
|
||||
// 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换.
|
||||
transform: function (file, enc, done) {
|
||||
@@ -80,11 +78,10 @@ module.exports = {
|
||||
parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => {
|
||||
options.defaultValue = key;
|
||||
const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式
|
||||
keyHashMap[key] = hashKey;
|
||||
|
||||
keyHashMap[key] = hashKey;
|
||||
|
||||
// 遍历每种语言,逐个语言检查翻译是否存在
|
||||
keyList.forEach((lng) => {
|
||||
keyList.forEach(lng => {
|
||||
const langData = existData[lng] || {};
|
||||
|
||||
// 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中
|
||||
@@ -116,13 +113,12 @@ module.exports = {
|
||||
});
|
||||
done();
|
||||
},
|
||||
flush: function(done) {
|
||||
flush: function (done) {
|
||||
// 将 keyHashMap 写入文件
|
||||
fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2));
|
||||
|
||||
|
||||
// 遍历每种语言,处理旧字段
|
||||
keyList.forEach((lng) => {
|
||||
keyList.forEach(lng => {
|
||||
const localeFilePath = path.join(localesDir, `${lng}.json`);
|
||||
const oldJsonPath = path.join(oldJsonDir, `${lng}.json`);
|
||||
const langData = existData[lng] || {};
|
||||
@@ -132,7 +128,7 @@ module.exports = {
|
||||
// 将不存在于 keyHashMap 中的键移动到 oldJson 文件中
|
||||
Object.keys(langData).forEach(hashKey => {
|
||||
if (!Object.values(keyHashMap).includes(hashKey)) {
|
||||
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
|
||||
oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,5 +138,5 @@ module.exports = {
|
||||
}
|
||||
});
|
||||
done();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
|
||||
"build:pro": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=business-entry --stream --verbose ",
|
||||
"serve": "lerna run preview --parallel",
|
||||
"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",
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js"
|
||||
"scan": "i18next-scanner --config i18next-scanner.config.js",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
|
||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -67,8 +67,12 @@
|
||||
"antd": "^5.19.4",
|
||||
"babel-jest": "^29.7.0",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.2",
|
||||
"eslint-plugin-react": "7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next-scanner": "^4.5.0",
|
||||
"jest": "^29.7.0",
|
||||
@@ -80,6 +84,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"prettier": "^3.1.1",
|
||||
"react-test-renderer": "^18.3.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.2.2",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
|
||||
// .env.pro
|
||||
VITE_APP_MODE=pro
|
||||
VITE_APP_TITLE=My Production App
|
||||
VITE_API_BASE_URL=https://api.production.example.com
|
||||
@@ -1,18 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs','public','code-snippet','ace-editor'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
public/tinymce
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# `businessEntry`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const businessEntry = require('businessEntry');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const businessEntry = require('..');
|
||||
const assert = require('assert').strict;
|
||||
|
||||
assert.strictEqual(businessEntry(), 'Hello from businessEntry');
|
||||
console.info('businessEntry tests passed');
|
||||
@@ -1,15 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/frontend/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>APIPark</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script src="/frontend/iconpark_eolink.js"></script>
|
||||
<script src="/frontend/iconpark_apinto.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"name": "business-entry",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": " vite --port 5000 --strictPort",
|
||||
"build": "vite build ",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview --port 5000 --strictPort",
|
||||
"serve": "vite preview --port 5000 --strictPort"
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
'tailwindcss/nesting': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
},
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 31 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,158 +0,0 @@
|
||||
import '@core/App.css'
|
||||
import { ConfigProvider } from 'antd';
|
||||
import RenderRoutes from '@businessEntry/components/aoplatform/RenderRoutes';
|
||||
import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import { StyleProvider } from '@ant-design/cssinjs';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import useInitializeMonaco from "@common/hooks/useInitializeMonaco";
|
||||
import ThemeSwitcher from '@common/components/aoplatform/ThemeSwitcher'
|
||||
|
||||
const antdComponentThemeToken = {
|
||||
token: {
|
||||
// Seed Token,影响范围大
|
||||
colorPrimary: '#3D46F2',
|
||||
colorLink:'#3D46F2',
|
||||
colorBorder:'#ededed',
|
||||
colorText:'#333',
|
||||
borderRadius: 4,
|
||||
// 派生变量,影响范围小
|
||||
colorBgContainer: '#fff',
|
||||
colorPrimaryBg:'#EBEEF2',
|
||||
colorTextQuaternary:'#BBB',
|
||||
colorTextTertiary:'#999'
|
||||
},
|
||||
components:{
|
||||
// 派生变量,影响范围小
|
||||
Input:{
|
||||
activeShadow:'none'
|
||||
},
|
||||
Select:{
|
||||
activeShadow:'none'
|
||||
},
|
||||
Checkbox:{
|
||||
activeShadow:'none'
|
||||
},
|
||||
Cascader:{
|
||||
activeShadow:'none',
|
||||
optionSelectedBg:'#EBEEF2',
|
||||
optionHoverBg:'#EBEEF2'
|
||||
},
|
||||
Layout: {
|
||||
bodyBg: '#17163E',
|
||||
headerBg: 'transparent',
|
||||
headerColor: '#333',
|
||||
headerPadding: '10 20px',
|
||||
lightSiderBg: 'transparent',
|
||||
siderBg: 'transparent',
|
||||
},
|
||||
Breadcrumb:{
|
||||
itemColor:'#666',
|
||||
linkColor:'#666',
|
||||
lastItemColor:'#333',
|
||||
},
|
||||
Table:{
|
||||
headerBorderRadius:0,
|
||||
headerSplitColor:'#ededed',
|
||||
borderColor:'#ededed',
|
||||
cellPaddingBlockMD:'10px',
|
||||
cellPaddingInlineMD:'12px',
|
||||
cellPaddingBlockSM:'8px',
|
||||
cellPaddingInlineSM:'12px',
|
||||
headerFilterHoverBg:'#EBEEF2',
|
||||
headerSortActiveBg:'#F7F8FA',
|
||||
headerSortHoverBg:'#F7F8FA',
|
||||
fixedHeaderSortActiveBg:'#F7F8FA',
|
||||
headerBg:'#F7F8FA',
|
||||
rowHoverBg:'#EBEEF2'
|
||||
|
||||
},
|
||||
Segmented:{
|
||||
itemColor:'#333',
|
||||
itemSelectedColor:'#333',
|
||||
trackBg:'#f7f8fa',
|
||||
trackPadding:0,
|
||||
// itemHoverColor:'#EBEEF2',
|
||||
itemActiveBg:'#EBEEF2',
|
||||
itemHoverBg:'#EBEEF2',
|
||||
itemSelectedBg:'#EBEEF2',
|
||||
},
|
||||
Tree:{
|
||||
// titleHeight:30,
|
||||
// fontSize:12,
|
||||
directoryNodeSelectedBg:'#EBEEF2',
|
||||
directoryNodeSelectedColor:'#333',
|
||||
nodeSelectedBg:'#EBEEF2',
|
||||
nodeHoverBg:'#EBEEF2'
|
||||
},
|
||||
Collapse:{
|
||||
headerBg:'#f7f8fa',
|
||||
headerPadding:"12px",
|
||||
contentPadding:"0 10px 12px 10px"
|
||||
},
|
||||
Button:{
|
||||
// paddingInline:8,
|
||||
dangerShadow:'none',
|
||||
defaultShadow:'none',
|
||||
primaryShadow:'none'
|
||||
},
|
||||
Tabs:{
|
||||
cardBg:'#EBEEF2',
|
||||
cardHeight:42,
|
||||
horizontalItemGutter:8,
|
||||
horizontalItemPaddingSM:'12px 8px 8px 8px',
|
||||
horizontalItemPadding:'12px 8px 8px 8px',
|
||||
},
|
||||
Menu:{
|
||||
// itemBg:'#F7F8FA',
|
||||
// subMenuItemBg:'#F7F8FA',
|
||||
// itemMarginBlock:0,
|
||||
// activeBarBorderWidth:0,
|
||||
// itemSelectedColor:'#333',
|
||||
// itemSelectedBg:'#EBEEF2',
|
||||
// itemHoverBg:'#EBEEF2'
|
||||
// itemHeight:'72px',
|
||||
// darkItemBg:'transparent',
|
||||
// itemBg:'transparent',
|
||||
// itemSelectedBg:'transparent',
|
||||
// darkItemSelectedBg:'transparent',
|
||||
// subMenuItemBg:'transparent',
|
||||
// itemActiveBg:'transparent',
|
||||
// darkSubMenuItemBg:'transparent',
|
||||
// activeBarHeight:'2px',
|
||||
// activeBarBorderWidth:2
|
||||
},
|
||||
List:{
|
||||
itemPadding:'8px 0'
|
||||
},
|
||||
Form:{
|
||||
itemMarginBottom:10,
|
||||
|
||||
},
|
||||
Alert:{
|
||||
defaultPadding:'12px 16px'
|
||||
},
|
||||
Tag:{
|
||||
defaultBg:"#f7f8fa"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function App() {
|
||||
useInitializeMonaco()
|
||||
|
||||
return (
|
||||
<StyleProvider hashPriority={"high"}>
|
||||
<ConfigProvider
|
||||
locale={zhCN}
|
||||
wave={{disabled:true}}
|
||||
theme={antdComponentThemeToken}>
|
||||
<ThemeSwitcher />
|
||||
<BreadcrumbProvider>
|
||||
<RenderRoutes />
|
||||
</BreadcrumbProvider>
|
||||
</ConfigProvider>
|
||||
</StyleProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -1,484 +0,0 @@
|
||||
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
|
||||
import Login from "@core/pages/Login.tsx"
|
||||
import BasicLayout from '@common/components/aoplatform/BasicLayout';
|
||||
import {createElement, ReactElement,ReactNode,Suspense} from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import {App, Skeleton} from "antd";
|
||||
import ApprovalPage from "@core/pages/approval/ApprovalPage.tsx";
|
||||
import {SystemProvider} from "@core/contexts/SystemContext.tsx";
|
||||
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
|
||||
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 { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx';
|
||||
|
||||
type RouteConfig = {
|
||||
path:string
|
||||
component?:ReactElement
|
||||
children?:(RouteConfig|false)[]
|
||||
key:string
|
||||
provider?:FC<{ children: ReactNode; }>
|
||||
lazy?:unknown
|
||||
}
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE;
|
||||
export type RouterParams = {
|
||||
teamId:string
|
||||
apiId:string
|
||||
serviceId:string
|
||||
clusterId:string;
|
||||
memberGroupId:string
|
||||
userGroupId:string
|
||||
pluginName:string
|
||||
moduleId:string
|
||||
accessType:'project'|'team'|'service'
|
||||
categoryId:string
|
||||
tagId:string
|
||||
dashboardType:string
|
||||
dashboardDetailId:string
|
||||
topologyId:string
|
||||
appId:string
|
||||
roleType:string
|
||||
roleId:string
|
||||
}
|
||||
|
||||
const PUBLIC_ROUTES:RouteConfig[] = [
|
||||
{
|
||||
path:'/',
|
||||
component:<Login/>,
|
||||
key: uuidv4(),
|
||||
},
|
||||
{
|
||||
path:'/login',
|
||||
component:<Login/>,
|
||||
key: uuidv4()
|
||||
},
|
||||
{
|
||||
path:'/',
|
||||
component:<ProtectedRoute/>,
|
||||
key: uuidv4(),
|
||||
children:[
|
||||
{
|
||||
path:'approval/*',
|
||||
component:<ApprovalPage />,
|
||||
key:uuidv4()
|
||||
},
|
||||
{
|
||||
path:'team',
|
||||
component:<Outlet/>,
|
||||
key: uuidv4(),
|
||||
provider: TeamProvider,
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
key: uuidv4(),
|
||||
component: <Navigate to="list" />
|
||||
},
|
||||
{
|
||||
path:'list',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamList.tsx'))
|
||||
},
|
||||
{
|
||||
path:'inside/:teamId',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsidePage.tsx')),
|
||||
key: uuidv4(),
|
||||
children:[
|
||||
{
|
||||
path:'member',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsideMember.tsx')),
|
||||
},
|
||||
{
|
||||
path:'setting',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamConfig.tsx')),
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'service',
|
||||
component:<SystemOutlet />,
|
||||
key: uuidv4(),
|
||||
provider: SystemProvider,
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
key:uuidv4(),
|
||||
component:<Navigate to="list" />
|
||||
},
|
||||
{
|
||||
path:'list',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
|
||||
},
|
||||
{
|
||||
path:'list/:teamId',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
|
||||
},
|
||||
{
|
||||
path:':teamId',
|
||||
component:<Outlet/>,
|
||||
key: uuidv4(),
|
||||
children:[
|
||||
{
|
||||
path:'inside/:serviceId',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
|
||||
children:[
|
||||
{
|
||||
path:'api',
|
||||
key: uuidv4(),
|
||||
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',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/upstream/SystemInsideUpstreamContent.tsx')),
|
||||
},
|
||||
{
|
||||
path:'document',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideDocument.tsx')),
|
||||
},
|
||||
{
|
||||
path:'subscriber',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideSubscriber.tsx')),
|
||||
children:[
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'approval',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApproval.tsx')),
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
|
||||
},
|
||||
{
|
||||
path:'*',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'topology',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemTopology.tsx')),
|
||||
key: uuidv4(),
|
||||
children:[
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'publish',
|
||||
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(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'setting',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
|
||||
children:[
|
||||
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},{
|
||||
path:'datasourcing',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')),
|
||||
},
|
||||
{
|
||||
path:'cluster',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCluster.tsx')),
|
||||
},
|
||||
{
|
||||
path:'cert',
|
||||
key: uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCert.tsx')),
|
||||
},
|
||||
{
|
||||
path:'serviceHub',
|
||||
component:<Outlet />,
|
||||
key:uuidv4(),
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
key: uuidv4(),
|
||||
component: <Navigate to="list" />
|
||||
},
|
||||
{
|
||||
path:'list',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')),
|
||||
},
|
||||
{
|
||||
path:'detail/:serviceId',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')),
|
||||
}]
|
||||
},
|
||||
{
|
||||
path:'commonsetting',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
|
||||
key:uuidv4(),
|
||||
},
|
||||
{
|
||||
path:'consumer',
|
||||
component:<Outlet />,
|
||||
provider:TenantManagementProvider,
|
||||
key:uuidv4(),
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
key:uuidv4(),
|
||||
component:<Navigate to="list" />
|
||||
},
|
||||
{
|
||||
path:':teamId/inside/:appId',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')),
|
||||
children:[
|
||||
{
|
||||
path:'service',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')),
|
||||
},
|
||||
{
|
||||
path:'authorization',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')),
|
||||
},
|
||||
{
|
||||
path:'setting',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'list',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
|
||||
},
|
||||
{
|
||||
path:'list/:teamId',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'member',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberPage.tsx')),
|
||||
children:[
|
||||
{
|
||||
path:'',
|
||||
key:uuidv4(),
|
||||
component:<Navigate to="list" />
|
||||
},
|
||||
{
|
||||
path:'list',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
|
||||
},
|
||||
{
|
||||
path:'list/:memberGroupId',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'role',
|
||||
key:uuidv4(),
|
||||
component:<Outlet></Outlet>,
|
||||
children:[
|
||||
{
|
||||
path: '',
|
||||
key: uuidv4(),
|
||||
component: <Navigate to="list" />
|
||||
},
|
||||
{
|
||||
path:'list',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleList.tsx')),
|
||||
},
|
||||
{
|
||||
path:':roleType/config',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
|
||||
},
|
||||
{
|
||||
path:':roleType/config/:roleId',
|
||||
key:uuidv4(),
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
|
||||
}
|
||||
]
|
||||
},
|
||||
APP_MODE === 'pro' &&{
|
||||
path:'openapi',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@openApi/pages/OpenApiList.tsx')),
|
||||
key:uuidv4(),
|
||||
},
|
||||
{
|
||||
path:'assets',
|
||||
component:<p>设计中</p>,
|
||||
key:uuidv4()
|
||||
},
|
||||
APP_MODE === 'pro' &&{
|
||||
path:'analytics',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
|
||||
key:uuidv4(),
|
||||
children:[
|
||||
{
|
||||
path:':dashboardType',
|
||||
component:<Outlet/>,
|
||||
key:uuidv4(),
|
||||
provider:DashboardProvider,
|
||||
children:[
|
||||
{
|
||||
path:'list',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardList.tsx')),
|
||||
key:uuidv4()
|
||||
},
|
||||
{
|
||||
path:'detail/:dashboardDetailId',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardDetail.tsx')),
|
||||
key:uuidv4()
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path:'systemrunning',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@systemRunning/pages/SystemRunning.tsx')),
|
||||
key:uuidv4()
|
||||
},
|
||||
{
|
||||
path:'template/:moduleId',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
|
||||
key:uuidv4()
|
||||
},
|
||||
{
|
||||
path:'logsettings/*',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logsettings/LogSettings.tsx')),
|
||||
key: uuidv4(),
|
||||
children:[{
|
||||
path:'template/:moduleId',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
|
||||
key:uuidv4()
|
||||
}]
|
||||
|
||||
},
|
||||
APP_MODE ==='pro' && {
|
||||
path:'resourcesettings/*',
|
||||
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/resourcesettings/ResourceSettings.tsx')),
|
||||
key: uuidv4(),
|
||||
children:[{
|
||||
path:'template/:moduleId',
|
||||
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()
|
||||
}]
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
const RenderRoutes = ()=> {
|
||||
return (
|
||||
<App className="h-full" message={{ maxCount: 1 }}>
|
||||
<Router>
|
||||
<Routes>
|
||||
{generateRoutes(PUBLIC_ROUTES)}
|
||||
</Routes>
|
||||
</Router>
|
||||
</App>
|
||||
)
|
||||
}
|
||||
|
||||
const generateRoutes = (routerConfig: RouteConfig[]) => {
|
||||
return routerConfig?.map((route: RouteConfig) => {
|
||||
let routeElement;
|
||||
if (route.lazy) {
|
||||
const LazyComponent = route.lazy as React.ExoticComponent<unknown>;
|
||||
|
||||
routeElement = (
|
||||
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active /></div>}>
|
||||
{route.provider ? (
|
||||
createElement(route.provider, {}, <LazyComponent />)
|
||||
) : (
|
||||
<LazyComponent />
|
||||
)}
|
||||
</Suspense>
|
||||
);
|
||||
} else {
|
||||
routeElement = route.provider ? (
|
||||
createElement(route.provider, {}, route.component)
|
||||
) : (
|
||||
route.component
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Route
|
||||
key={route.key}
|
||||
path={route.path}
|
||||
element={routeElement}
|
||||
>
|
||||
{route.children && generateRoutes(route.children as RouteConfig[])}
|
||||
</Route>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 保护的路由组件
|
||||
function ProtectedRoute() {
|
||||
const {state} = useGlobalContext()
|
||||
return state.isAuthenticated? <BasicLayout project="core" /> : <Navigate to="/login" />;
|
||||
}
|
||||
|
||||
export default RenderRoutes
|
||||
@@ -1,27 +0,0 @@
|
||||
import {StrictMode} from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import '@core/index.css'
|
||||
import {GlobalProvider} from "@common/contexts/GlobalStateContext.tsx";
|
||||
|
||||
async function initializeApp() {
|
||||
try {
|
||||
// 初始化行为
|
||||
// await fetchInitialConfig(); // 示例:获取初始配置
|
||||
|
||||
// 异步操作完成后,渲染React应用
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<GlobalProvider>
|
||||
<App />
|
||||
</GlobalProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Initialization failed:', error);
|
||||
// 处理初始化失败的情况,比如渲染一个错误界面
|
||||
}
|
||||
}
|
||||
|
||||
// 执行初始化
|
||||
initializeApp();
|
||||
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
// start-vite.js// start-vite.js
|
||||
import { exec } from 'child_process';
|
||||
|
||||
const viteProcess = exec('pnpm run build');
|
||||
|
||||
viteProcess.stdout.on('data', (data) => {
|
||||
console.log(data.toString());
|
||||
});
|
||||
|
||||
viteProcess.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
|
||||
viteProcess.on('close', (code) => {
|
||||
console.log(`Vite process exited with code ${code}`);
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@core/*": ["../core/src/*"],
|
||||
"@common/*": ["../common/src/*"],
|
||||
"@market/*": ["../market/src/*"],
|
||||
"@dashboard/*": ["../dashboard/src/*"],
|
||||
"@openApi/*": ["../openApi/src/*"],
|
||||
"@systemRunning/*": ["../systemRunning/src/*"],
|
||||
"@businessEntry/*": ["./src/*"],
|
||||
},
|
||||
},
|
||||
"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" }]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
|
||||
import tailwindcss from 'tailwindcss';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
|
||||
export default defineConfig({
|
||||
cacheDir: './node_modules/.vite',
|
||||
build:{
|
||||
outDir:'../../dist',
|
||||
sourcemap: false,
|
||||
chunkSizeWarningLimit: 50000,
|
||||
cacheDir: './node_modules/.vite',
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules')) {
|
||||
return id.toString().split('node_modules/')[1].split('/')[0].toString();
|
||||
}
|
||||
// 针对 pnpm 和 Monorepo 特殊处理
|
||||
if (id.includes('.pnpm')) {
|
||||
const segments = id.split(path.sep);
|
||||
const packageName = segments[segments.indexOf('.pnpm') + 1].split('@')[0];
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
tailwindcss(path.resolve(__dirname, '../common/tailwind.config.js')),
|
||||
autoprefixer
|
||||
],
|
||||
},
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
modules:{
|
||||
localsConvention:"camelCase",
|
||||
generateScopedName:"[local]_[hash:base64:2]"
|
||||
}
|
||||
},
|
||||
plugins: [react(),
|
||||
dynamicImportVars({
|
||||
include:["src"],
|
||||
exclude:[],
|
||||
warnOnError:false
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: /^~/, replacement: '' },
|
||||
{ find: '@common', replacement: path.resolve(__dirname, '../common/src') },
|
||||
{ find: '@market', replacement: path.resolve(__dirname, '../market/src') },
|
||||
{ find: '@core', replacement: path.resolve(__dirname, '../core/src') },
|
||||
{ find: '@dashboard', replacement: path.resolve(__dirname, '../dashboard/src') },
|
||||
{ find: '@openApi', replacement: path.resolve(__dirname, '../openApi/src') },
|
||||
{ find: '@systemRunning', replacement: path.resolve(__dirname, '../systemRunning/src') },
|
||||
{ find: '@businessEntry', replacement: path.resolve(__dirname, './src') },
|
||||
]
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
'/api/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8288/',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/api2/v1': {
|
||||
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
|
||||
target: 'http://172.18.166.219:8288/',
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
logLevel:'info'
|
||||
})
|
||||
-85
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<image id="icons8-server-100" y="11" width="99" height="78" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGMAAABOCAYAAAA0Cah9AAAKN0lEQVR4nO2dC3BcVRnH/9/ZPAjt7qYFLFDBF1OlgsNYWqHZx2g3m1A6aBlaQawMxREtQ30NUh2djjKOdooOAxSUEUcQR2rVCqWYF8Ukm0ZgCkpV6FiGxwyPAjbJbmibxz1/5272lc1m9+Zukt1m728mmdxz7z2P77v3nHO/850vQhIO4wmEY/kkMg/AIgDHARwBoIsQnwvA2QDeAGAoRw+W8AD4PoCDAAYBvJQQ4ACAPwDw28jzTAD/BvAagH+ZCnaUUZgwgMMAbgNwQdbV8wGsA9AF4CEAp04h3xsBfDTx98fMY0cZ+bkKwF4AZ1i49loAHQDqLOadnef7HGVMzlIADwComsI9lwK4226BjjImZ1uObmc3gE8BOAXAWQA2Afhf1jXXA/iknQIdZeTmHACXZ50xn/grATwNYAjAWwDuBdAAoC/jOgHwVTuFOsrITXNCqEneAfCdSa49BOCnWWnNdgp1lJGbD2Sl7kt8V0zGY1npiwFUT7VQRxm5qc9KzR4Xsnkn61jlyKMgjjKsMStmCkcZZYSjjDKiSkTG1SYUOuo9Ia7rAFkjwJKEXWau8zaU8YXu1gXPTmI4bUrYoCajNld6V5s79XcB42OccV+XvvDA9Scg2wU4rQIUkMkCUN0C4JpJzp+X+JlRUt2Ur3HgJwL5dQUqIg6JZ0pdh/ib4Q9FvwzBlnFnyL9RpCXr63JOIpTDkXbPvlK3reqScP9CAtuTIweBAZLX9LR7/1riupUTzwJoz1Mf04Z1c7H1raqm3ABJf6AI9fpIe33bySatGaa3q829ZbIiAuHYGdOiDAiuSB4QaHUUUTrMAXxJqnTy8coTQfmgQJyerI1S6q1KF0gpURBJTW+1nh0bjENuHHNIbrIfykIPaS53nUIuPBPKmMr6bk6CwejprOa1VHIxyVNF8CKV2hlpcT9fbN4l5Lmsop8tUJWjCZebcxPHrybS8jGhjKKU4WuMXmUIfiUQr3mctHOJobf4G6M71LD7W52dMlpqydrAdET4EIBVCY+PB/Jl0dXmZiAcWwvgx4mk75lpBYqdUIb4w9HUTaSsj7S7d1mpe0PjwGUC7BER1+RX8Z7uNu9NRQhlzmDFc9PWmLFsGasV5N7ximAPgUdBHMu49GuB5v7lFSPxIrHVTdUtGAxA0uvEhN4caau/y/zb1xxdCo2nJO5tJ0ItXwQmN8IFgzxF18a2gVya6zxFNMi9kXbvndnnfI2xqwW8bjrGPitQ8MxZXs8Pdu0SYybyt9UIUcb5qZeKiEVWencg8d0eafH8x98Y3Q3BhsQFH8+Xl1ET2yjAZmStq6TKiv+ScCAUe7Krw30wmR4M9tVr0Q8CUj3ekWPmECD0Zn/MHMz/OBOF2HuiqEaS7aewuvkpVLeM+RKNIZyP9AUj+fPSfZBCvSVHxGWMW50ZdB8brht2HxPAa6sNdlHSb+XOYJBVuib2SwqXk7I14QCXF3vKEP1c8s0QyCmDRvSOZcu4+cABGfE3RT9LzSvSDzoP5Msq0uF9uCEcrRNiucjEMYyUYVD9pbPD+0pm+oHHFh9bGeoPKVHXinAqDsf2IAxCdUXa3B1W7h+tiV6mRDZK/KHkDivKsDmbovjDMXMcWJZOQh8kvvbx4XQahw0Xl+5vqX9peiRy8uBrGtgglAfHRMMT3a2egg7RNr/AhVSy0Rwv0klYME4RY2m3VqIi7GLbHGJ+YRtQAYIHJ5wk+jR4Q3eb944StOmkpagp4f72+f/AVl4U6B0ManI5BHVK5IUTaqjl6ZbTo5Ut2qlT/Pz8h6K7gCcx9uNQBI7VtoxQ5hw+ozp57EwOU4KZvY41Y6ki5fX0Pcx2hXewiRAfTIs1vkW5sDJE8ELqpviGQs6ObWEus5WKEt/lFIfgP620VlH4+4zji/1NsRsrXZbF4tsf/bpAUtuURfhnK1mq49WDfyLwcjKBmnf7w9EtS9expgzbWdY0N7PWF45uFeD2VD2JQ2rIu9NKveNdUiAc+7Smbs9cnyD5FiCmy+MbAs6IyXiuQEiVCBeDWAWR9P5uclhTVvV0eCJWFpdS40OgKbaeWv8WIs4bMQ2Y9ihQfSlp65vSSp9hxrEQWDIPO+SHRK+IbrC6hJ0k/mYEw/3nGVB/z94OYI4lUtjLwQEYJXBEwBe1lj09He6e+IQ2U5YW3owqcxpm9Ebvz1QEyUe0wq37W72HHEHPHlW+3oGwQAWSJZL8RaTdsylbsw4zjwLVhlQpxKvzXZ5vOIooDUoyV+uEv2tpkaHyqmLloCBMuiSas4AXKl0gpUQBkrE2q5y3ooQ46xllxLR44q1oftdTPVrzCYHUEiP/7elY+NpcEVAx+JsGz6Khl7hGzO8OFFzTKM4L/fL+BRiR7WJgAxRq4lYaVMEfHuiB6M1mxIHZaHQ50hA6ei60PigKHqMmuhPwXF2omra7KXNfhgyrXoG5WzbbniUNpOrxNcVCJ6UkpwGBaoAkQn2IrLGSo21l6FreB0mFAp2A6WkI8mHTJ3amG16WuCSj16Gl5Wxb3ZRpy9KQtamiwC4X1U0jVEeVjN4sIvE906aJxahRGwH8PG9+jbELDOD8WZOpcGRYDe/L5U7kDx87BxhdQU50AHaJ8VxnW/3hmaqWLWUYEH8qogJpGFDrIu3utxNJ3/U1DjSISDIKciCfMnyhaNCAfiL/ppvpp9aofh7gRZnWhpWh984mRg+aztSSwwihKcOBUOziTG/46cRWN6V0Rtgjkf7etCISaTiUPp0/PJwoLp9tRYwVjAuDwbfnZSYp6PPzerWL1FD0sknPF4ktZWglr6bbhNP8jbHPJI/NaS6Yjl5JyMu58kiiqlwPjUW0mb0I+QTeA3FbZ+eiwcz0433zuwDuznJfGruHNMzgNkOuEUvr2Xaw1U2Z/W2tURNNzhYofNQXjt4D4l0xsBGC9yevVZp5XeE7H59vBgJYMVMNnArmloZE7NqSYOvNiA98wq3JYwHmCXCLCLZlzrAI7uvq8OwpVeNONmxPbU0PczLDCyILc+nRNSSfd8zx1inKNhVp95hvQ4DkTnOJluSbJJ8k8BXXsDvQ2el5d5bbc1JTtG2qq9XTbb4olS3G6cGx2pYRjjLKCEVk+EoJK9OONBPo+B7HMWgt6KYSMv0BR1v/yMkhByKZspRXrMjI7KZakgcE1/tWDSzJf4tDIQKh2IUEPpfWRVrGeZUhxP3Jz38RqYXCI5euPupsmrHJyub+j1C4WzBmQjd9bqlHf2Mlt7jx1dcYvV0E304mmrFtxdzVT2lVot8wjKL+MeCcR4m4NORsUVxNyKaxIDZJYfJH3e3erZa90M2QRXULY3tF0Fjpgp1m9pzpda81o/BY9kI3DWRH691rAN43m9bTOQtpht68Sw25r5xKOKQJ+/f84f4VgHwTlNWpNVwHS8Q/E4i9Shs/63piwbgYhFPaLJPNunV0vd53bFEVhxdpV8EYRBWNZpXhUjjSfcm8I2aQglyyKKgMAP8HPtmJtk2SwmQAAAAASUVORK5CYII="/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<image id="icons8-ai-cloud-100" y="16" width="100" height="68" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABECAYAAAB3TpBiAAAMt0lEQVR4nO1dC3BU1Rn+/nM3CUSpoigqml0CPrBOdUSSDVoVHd+1UxCx9VHrC8iCYn212qpFtOooWjTZhbGtD9AiPtv6qlXBqrAbQXSsDxw0u9EBIg+rYgLJ3vt1zkXCvZtNSJZ7Nxvgm2GYPefs/f9zvj33nPM/TgS9EbxZlS/Zb3/T5CAl1i4WpZ8iDCj+zwSbioDPd1eBVUuOmtDa23rXKwgpq4uWK8qJIEdCUEHKEBGUdPYdEhtFsJTk20qxzoQsbKiIfJY/rXNDwRKy/8LYoIDBi0EZA8ERXjyTxNsinCNW0eP1VZc1evFMr1FwhJTV1Y5UllxDkTMFCPghg0AawAs01dSGkRPe8UNGrigYQoKJmiME6jZATu/yl8jvIPI1yGYIFIk9RGS3bnyfFDwFizelqiZ9lKPqnqLHCTl0fs2uTaXqFgBXAGJ02JBcCch8C1wowJKi4tZPlx85ZXW7dvPmGUOHrtwj3VoyDGQYgioQ+v99OtaCJoGYaTZf98XIq5u96lsu6FFCQotmVUKZcwEJZasn8I0As5Wl5n4WXrEQMtXKSRBvVsHE3scB6kIIzhJg1w5afkTheamKyNKc5HiAHiMklIheQeIuESnOUr0CkDtKWgMPLTvm0m+9lLtpRgYuInmjCPbKrCfZIpDfJcPVd3spt6vIPyHz5hmh4NoZACa1qyObKPLHFkPdu/KoCU1+qnHwm3/utyHQeq2AV0Fkl3aqgDWpisYpOc/KHJFXQoYvnlW0Nm3OhciYzDoSr1sBdcnnR034NJ86hd6qCcIwHoNgZBad5gwIqIvzecBU+RKkZ8aatDm7HRl6pwPekqpcdUK+ydBIHj05lWxedRzA20G4ZoMIzl9jWk9i/s2+bL+zIW8zJBSPxiAyMaO4mcB5qcrqZ/KlR2coi9eeoUQ9AaCvsxnBGanKyJX50CEvhAQT0WqBRJ1lBNYr4ej6isgr+dChqwglZh5L8PksO7HxycrqB/yW7zshemtLMf/j3k1xg7KMkz6rmvCm3/JzQaiu9jhSPeckRdvGIDIqVTlxkZ+yfV1D9l84vS/EfMhFBmFZFs4vVDI0khWTXofIhXp921ymjZlCzhn47iPtdmRewldCDKN0KkQOcZfy9oaqyFN+yvUCqYqJTwNyj+tRgvK+G7+71U+5vhFSvih2IEjXQkjgjWRz4x/8kuk19gyo6wm6ZjKBycFFtcP8kunbGhKKR59ybnFt/4Ti4cmKyDK/ZPoB/cMyhf/NeO0uFGXFACzboAIfeHmI9YWQUDx2OATvugrJ25LhyO/9kOc3gononwQyJZsYbWqBoE6Af5mG8bdtPUv58soi8OuMgrUl6eI7/ZCVD5hmkbZGf5VNlJ45AjkGkGmGaS0PJqJvBBOx0dqgmYtqnhMy9J0Z2mD3c2cZgeleGwnziS9GXrZOiJu6IlKTI8DTobqB7wfroqd1V03PCWltLRrr9ndzA1s502s5+UZ9uLqGxLkk9eHweW2q136UjtWQQ4XyQjARfbb8rejeXVXX8zUkGI++LiLHthUQjyXD1ed5LacQoE35zX2MKkvxp0IZ25ETjGCjkL9Mhie9vDW1PSUkuPTe3WVjn7XandqmjPD0VEXkxcIYQv9gW7ItjiGt6wVyeKYg248vmJKqqI52poSnryzVUnKMkwzt3zDTzQvyMB49Dm2iT1ZMfDxV0XikiHUBgS+dOumADSFqQ4lopztNT2aInhlqQ3EZRa6DyJbXE/lSMhzp9sK2PeCgxbMGtJiWXm9+1q475HXJcOSubN3MiRD73VmqTrMgZwKoEmBotnYkbk2Fq2/c7ke/I5ASSsy8EeAf9P64rRVhUTA2m9uhW4ToCEKhfca4UCD9ttbesji2N9it/IbtfiBqnaTY7gekh9dXXv6JU3yX1hD9SgrGo/eJhY8EMrkrZGhTScDCG4U3PPlHqjISo+Bap2Bt2icCD2tPakZ55wgloqcA+Asgg7bWlsRqAb4FuNLSVt3wpOe96r02e5e0rB8IZTQ3DF/RuE3BB/PmGUNCq/f7NLnXCowb18lZwlsE47EHRHCp66EWrkxWVc/Y/LFjQkgJ1s28QYhbXDsnd5tFEPxdByj0SRd/4PVpfGh8xg/SUnQFKeeI4LA2scB6AC8q8v76cKRbszCYqNkHNBaI4GCQHwuLjs9XnO++i2eVlqTNJW6XBNdZLRja8OOIbZrJ7ry3F6NYDUQimZSR1BEYD4nC9GSlf5bbULz25FaoOQLsJRk6fO/JO5siZ4fi0Uc3BozxXbW4CtUEaDLsD3IIkZ4A4BZfOpEBrWPZ27FLlck3tqwnsocU4SoA9uYn6y8/WBe71yajHbgAAeNHqXBkvJ9m9MHx2FiKej5bIFs7iJxXkjZfsb2TXYGI+5mCLps1vEDDiOq3IJjrfhQn6tmDbIQMjscmtzM1265M3pSsaDwxddSEj/1UuCweO5Tgw92KfBepChilNX7q5SVocZrbPSwDik1rHDIJKVs460gLnO4s00d+S+SCZGVkWj6i+AS4EyKlrkLyGVGoDPRv6UOY+xKMaJN+RpuLtP5+6+cF7Eh7kRdc/SbPgXMN2RRVaD0Cl2dMR+vLJalw9aP5UHRIXe0BpsUznHsNO6QzHLnc0WwVgFioLvoaKDoCpP+mHomIMnXc1/h86LrNEJkN6r5u7idO1JuYthmy1jSrIfihUw4F05Ph6kfypaNJdarr8ESs1ikC2draaxhxh7tUTvVfS2+wUck/7dCizZqLFLVKyUibkOD8B/sQuMEpieB7AwzjBl+06Qhk0F3D1zrL17CA55yfBRikZ3pedc4R9q5QUOfSn9YxNiGqtPl8gQx0VlLJpLxnsYrs7vqIjHUiA8XFLe6EHYH6Wm3YvbPvFBaYGXQ3zCaElIucpQRetrdnPQ0RFuAoegfCdXSgyFBlh+ODVc4KBd7fO3vYu0BRKbfCHKgYMEa5F1KuqW9qfGlHH6x8wBDzG6cYIfoFQBztMo+IzMeoqelCUJjAKcFEbF5H9a2tKOmdV1F8DxMbnSdBihQHIDzEbWOUeM9o1x7fO76yOr+2B5gB1U85j9rkd0og5c6+KVifbHc9L1AYFvu7NZNvFEhXoj0to33u9074AotyoIsO4PMABH3chekeTZx3g++Q8u9O6ktF5PKO6wsdHOZaLgTLAgSaBGhzyRKBrpmx8wJZlApX/7YjSTpsNd1a3IsJcQQUbjIVva8EcG+9lLl1H8RObDP0+U82O8o2Q3GBIsR1h5SFjEY74QsYUL9wzw6uSY1ofE9tChp2IbyTAp+hUxUov3IKEeBJ7W9SQrhsVkIZ1Vsspr0VgxP7jMl8XVmKs/X/KgDjNac7EYI911nsEb8CQXb2ORNNraqdBzNb2bbI8Br6x07AnWdJLG0YEbEtv2p5ePwXFPcssWj1zM6FcF+LZLHT295WVExeR7Jhy9eR0mVeyvAaa0zzikxHoEXettmyvdn8/pCzgUBOKns7dnQ+FdVINQx4WMcD63gvEtNS4S8f7vQLIqRljNZB3fY/4eitmey7LcNDlNdFD7Pj3Bwgubgh3NgW42ufSrTHUEo36GTF/dqaEu8mm1eNKBRDY29H+eJZu1mmpV9LW1KqCUsMVNWPqG7zHNozJDXqog36wjBXnwVHhPruM21HH0gvMPSTGSWWaT7rIgP2GN/nJAPOMKBk08qY9qNnyL8umIieW9jdLWzo1I3Wr4r/AcjxTkX1lbWlu+z5m0zlt1jjR01N0zQudkZC2DG9xINli6Jn7bhDmjv0abyp1FggwMkZD1khpnn2h4eNa8l8uCtQTt9hS/BqZ5nOw1YKj4fisWv06l9QPS5gDE5Ez0ZA6R3ccKeWJL8WyzpNX5yWTfusAxyKR6dD5Kp2FcSLJCKpqurk9j6guUKHwirgbgjapfLpvEOSpzeEI0s6enz2X7yOfq+beX9HF1VCMCvAwD36DNMTnS448GZVlhh4vNIB6sTobOkbJJYFLPMnn46cvLwz9Tt9BQXjsetFOC37Bcf68mF5VSDPiWW+buyZXrb8oCkbsz1ne8OmFGiznJYcCcHxIE4TwQEddpN4rCRdNLEr+TNbXRPK4tETlI5DdZ5Rsks1QXxJwXqBfNN5214Koj/Fzk3ZoyvR+QS/UMBV9ZWRJ7ra4S4t0psymYqnkox0cPHxTripWAfKjNJm854PR01e352x6dauqXzxrDLTNK8FcUG3Lr3fYcB3YMkjJWbRX3NN78tpG6tNLaq0+UwdNwVCH3jKXXnYO8z4cyWBxUJ5lbBe9uIvLHgyiDodq28LhjBgDrT//BCYt4uH8wla2GiJzi+XtQG01C8PT/F2rQTwf2pROIQTkdWSAAAAAElFTkSuQmCC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100" height="100" viewBox="0 0 100 100">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
font-size: 40px;
|
||||
fill: #339af0;
|
||||
font-family: Montserrat;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<image id="icons8-api-100" width="100" height="100" xlink:href="data:img/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAKu0lEQVR4nO2dC4xU1RnH/9+ZmV3eO7vQivhqgSbWgsLOavARIU3aGlFCNZpq04cGGGhNaGNta0hraNq09qW2Vplh8ZGm1dTEZ6xaaXRtqhR2EFlK0gYfAUGJwM4sW/Y19/6bO7OD9zXLzO6du3eZ+0s2u/fc87rff8/5zj333nMEFZBI9cySyNBsnZHGSuKHWFGiDVCLfZhJzjhyKtOUFSSROrYQIusAWQHgrNDGY4fE+yJ8DuSDmWRLl1uGDkEuuP/wtMmxxt9B+A1AVPAvcyJCnZBH+gcH1u+97Yxe8wVYBGl76Og5zEeeB7Cw3k3mE10S1ZZ33jrzQKm4k4IMt4x/QnDhaWyAAMK9kT7t0u3rZ/UYdYuWKljsplzFOAHiRQj3AHKiji03BjgFlAUQXAVgijUfuSA/KfpbAKtQaiHDDnyXi894Wg1G1+24bdqHQb/kiUAi1XsmJb9JgBXm6pLUVERf1Llq5p6iAMXRlLJGwjOZg03Xh2J4RyY57YOdB5u+TOBZc6YiEqGu1hl/K5AyPLQ1cyIyFF2LjaJP2KsPKhtFF0bXEugz15CUa43fYtz0QfSPLNUnnswk49fXu+1qSSKdfQrASnMRMcFMBeTPdJQr/PdpcM2BhjQGSVbyos2JUiJTBbSe4cijqbb2owugRWafjA453tc84829N8qgW/xE6liTAIsAFTOONZIxpf1n+5pZ75/ORh8JgfzPflrXo9OqvhNPpLIPUY90UfBy6QfCbZOzue2XtX803Rm/u5Uib1PUq6X4SsnWPCLvLt7cfUsNrnVCU5UgF6ePfg6Ccka8qF+Lfc0eSMidAplpDxdIVBG/qAcjV0NVguhUI872Cug4L8AIaaSxOMoLKVGVIJlD8V0A/+Z2zpjJHNKif7KH6+R9AAdc0wC/hAjdztUr0aque6Poc//Cq9/J9XyJ4Nkmw+b0GF/afcv0rD3Jm2ub/35JuvuzGvUrqYotTHTqULJ75+r49noXwE51ggB44kbRAPy1mjTb1zS/C+Dd0VayngifdwSMUJCAEQoSMEJBAoa7UxcuTKSyN9SxXWqPcKHbOyZlRllyMwQ3T/BLDjju98NhlxUwQkECRihIwCjjQ/hnUJ6uX7P4gHBlwVfbcBeE0pVJxp84na4/aCRS2Xlufj3ssgJGKEjACAUJGKEgASMUJGCEggSMUJCAEQoSMEJBAkYoSMAIBQkYoSABIxQkYISCBIxQkIARChIwQkECRtUvW3sJ6f4lgkh1n4yUy6cclebvlm+1dauWcWshIxmxGgNXK0YpzWjS+UFguyw/DBZEUerehwRNlHERJGhGCFJ9At1CxmIow/maf2pZlpfUTZdVEifo1J0PCboovgsS1OFmUAhMCyn3n1sLAf0sq1p8v1M3G2P+fWxsmpT9FkSuS6S6Z7dtzu7TdT7w5tqW56rJL5HKbhheRXVWKTyRLn0yzyFADgPcT6BDKMbSU29X0nUtae89I0/t9wAXAdLRNzjwXQC9p0w4BsZt6mTpw5x0fDD3AkSWFQKKBpqvlFyVSHf/JLOm+a5K8mlLdV9PwU/LL0FcCP8kIAsFWA7w7tZU9+NRwYbh7+fLUhQDNwzn8ZlJDY3HAPzAi+svx7h1Wb2Due+IYJn7WflxYaGbCqDIkqoKlgI35SG7Eqnc8pGikrjUktR2XAvGz4cIV410WkPk6xXlw9G1cgFmEPozI37cKrTlzdhoyqqGcRHkoge7PwXIvJHiCPj5MRTRBSJT+iHxjmsZIhEIHrk43b1oDGV5yrj4kKjiUkefbwxxTJ6WxGJjJbpMsiVXbf55jSveWtf8njkskTp2LkRuJ/HtghAfM0UD/gDyiiCsTDROXZZy+g6RZ6yHhtEiV3hVYibZsj+zpnm9Aq8rjrxMZUEua93cM6I/8YvxmVwEltqCeiHq146IwjJOf/R0JlueBeRHzqL0r3hd1mjwXZDF7cfOE8GnraF8fdr70/9FoMcSCtqF84RcX9O9IK1L44pcXYuyqsV3QZTuaB3GuowdHRslL+A/bKcWX3LfkRle12HfejFWuLOvjNe88IFss9dlVYv/XRad/kNEdaDoyF+1hEOi2pSYZ37EWigO2IOiMTXbPbJ/+D+5CMfN4Im+puk7Cn8pecWRQPfejwzXw9EalK4cSxT6ja+CGENPp//AG6UFmOc1Ne0CYTFKrfyI2AcW5GDm0NTDtSirGvxtIW5TJURH6U9jPUcKXrOlafXaj7Smc9cAcr61GugIwuYDvgpCKMd/u069w3wspMOP6JNil3tVh0IrhZ62h4vIk16VMRb8bSG0thCS/TMamy1LxSqBw4/oHt2PFFqGyDZALBsQGGsOx473POpFGWPFt6mT4oZjmGsNlW0dt0i/OWTHwfju1jnZYyLScjJWlX4komRtIp3tLhwQDQTnFGeWeb7bNL0C7th2+7l9Lln5jn9zWUPRZbBNFQnY4YhnbHiSzr5m3luDQMJY6P/1VZ84XklRIqZnFlLo9srGJXBPJhl/vOLrqDG+CaKDSx1mEb7TtiU71x7MPPdC5KQghh8Z1BqM+5EXPK0Uce+85qY7dnqa6djwTRAp+AG7JOpRaq6RHUF6sdvyRJDijpv4fiYZfyzjRYYe4osgl6SPnK2d4vnHqZAxO3YOGTMBxmiqobfn0aD4DDu+CDIksWVqjG90GH7E2PzSvlWpG7qur5CIOmScipCaztjhwk3fBNjkzBdBxJj+GOP7aYYfmdTQaNyPvHSquDql663V8fdOFS+I+CMI7E8IeQREmrA8ubOlwRkQfNOWz7JKBJnI1FyQxe0fzYEu862h8komGd8wUrolv9k/eWj69JsKu/B8TE3mtYJE7e/U2eD2soL9uYcDw+mS6LRkBVzstvHY6UTNBVE6r7SHaZr+mntsKwJY57kg0X49dllNKhoQat9ChOdZjonsrg+bXXfbd0nsuJMX8hxrdnDsb9WgxfrtYaNBYJ3Woe24FvjQZVkflRL8Y6XDz1x/kyHIQVPqASXK0roUsNVW3m4PN1S25m3s2Vhjai5I5lD8HpDfI7CV4M96+uN3VJrWePati/oCyccAPm/sR75jTdN/zXE6k/GtIL4K8EUQDwP6tV7V3Xi5msTdJF8msEHY9Cuv8i6HtKZzSwR8w3KeuDOTjIebPtaQRCr7Qwh+bi6BkEvr/ivcoBEKEjCUUHPsWgzhlDq3S80hONVehlL5XqUGI4cchVMq+jYjZPSIyAJ74igjh8TYHLg1ndsvgrNN506A0fmZ5LQPQpt7jzGdJHpsnwCTS5mTOLAzGT9XGQ8aRPisrdQplPwm3MXQx3jNXVRKi20yi4HixGlBg6LByU2k9dmdACtaz8o9lUj1nlmzytUZRstIzMk9DYHlXsmwvUT0TTDv3daazm4R4Fa7iQj0SWHKm12AOAcAIRXAqaRcCMEX7S1jmPbMmvhqmKff+wcH1k+ONbZBcKE55nAGxn5JKyspOsQNcXtNoPQvvzfSp91eOjrpI4xHoxLTrjHmgkKb+gSxW6L6VdvXzzr5XYzFaXfeOvNA39DA5SS22H1KiHcM27bdsLVhc3PGZRtSW/vRBdTVOlKuFcE53lWnfjGGtiJ8TpT+YOeqmXvcDFHRqwdLNmdb8qLN0RlprCB6iA0l2oBx07dtddxYCaI8AP4PK/TIwF3GH5UAAAAASUVORK5CYII="/>
|
||||
<text id="REST" class="cls-1" transform="translate(20.904 48.641) scale(0.553)">REST</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
@@ -1,39 +1,37 @@
|
||||
import React from "react"
|
||||
import SwaggerUI from 'swagger-ui-react';
|
||||
import 'swagger-ui-react/swagger-ui.css';
|
||||
import React from 'react'
|
||||
import SwaggerUI from 'swagger-ui-react'
|
||||
import 'swagger-ui-react/swagger-ui.css'
|
||||
|
||||
export default function ApiDocument({spec}:{spec?:string|object}) {
|
||||
|
||||
class OperationsLayout extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
getComponent
|
||||
} = this.props
|
||||
const Operations = getComponent("operations", true)
|
||||
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
}
|
||||
}
|
||||
export default function ApiDocument({ spec }: { spec?: string | object }) {
|
||||
class OperationsLayout extends React.Component {
|
||||
render() {
|
||||
const { getComponent } = this.props
|
||||
const Operations = getComponent('operations', true)
|
||||
|
||||
return (
|
||||
<div className="swagger-ui">
|
||||
<Operations />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return(
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
supportedSubmitMethods={[]}
|
||||
customComponents={{Header:()=>null}}
|
||||
layout="OperationsLayout"
|
||||
plugins={[OperationsLayoutPlugin ]} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the plugin that provides our layout component
|
||||
const OperationsLayoutPlugin = () => {
|
||||
return {
|
||||
components: {
|
||||
OperationsLayout: OperationsLayout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SwaggerUI
|
||||
spec={spec}
|
||||
supportedSubmitMethods={[]}
|
||||
customComponents={{ Header: () => null }}
|
||||
layout="OperationsLayout"
|
||||
plugins={[OperationsLayoutPlugin]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,285 +1,295 @@
|
||||
import {
|
||||
MenuProps,
|
||||
App,
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Dropdown} from 'antd';
|
||||
import { Outlet, useLocation, useNavigate} from "react-router-dom";
|
||||
import Logo from '@common/assets/layout-logo.png';
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components'
|
||||
import AvatarPic from '@common/assets/default-avatar.png'
|
||||
import { useEffect, useMemo, useState} from "react";
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts';
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx';
|
||||
import { UserInfoType } from '@common/const/type.ts';
|
||||
import { useFetch } from '@common/hooks/http.ts';
|
||||
import { ProjectFilled } from '@ant-design/icons';
|
||||
import { getNavItem } from '@common/utils/navigation';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { $t } from '@common/locales';
|
||||
import { ProConfigProvider, ProLayout } from '@ant-design/pro-components';
|
||||
import LanguageSetting from './LanguageSetting';
|
||||
import Logo from '@common/assets/layout-logo.png'
|
||||
import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'
|
||||
import { UserInfoType } from '@common/const/type.ts'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales'
|
||||
import { transformMenuData } from '@common/utils/navigation'
|
||||
import { Icon } from '@iconify/react'
|
||||
import { App, Button, ConfigProvider, Dropdown, MenuProps } from 'antd'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
|
||||
import LanguageSetting from './LanguageSetting'
|
||||
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE;
|
||||
export type MenuItem = Required<MenuProps>['items'][number];
|
||||
const APP_MODE = import.meta.env.VITE_APP_MODE
|
||||
export type MenuItem = Required<MenuProps>['items'][number]
|
||||
|
||||
const themeToken = {
|
||||
bgLayout:'#17163E;',
|
||||
header: {
|
||||
heightLayoutHeader:72
|
||||
},
|
||||
pageContainer:{
|
||||
paddingBlockPageContainerContent:0,
|
||||
paddingInlinePageContainerContent:0,
|
||||
}
|
||||
bgLayout: '#17163E;',
|
||||
header: {
|
||||
heightLayoutHeader: 72
|
||||
},
|
||||
pageContainer: {
|
||||
paddingBlockPageContainerContent: 0,
|
||||
paddingInlinePageContainerContent: 0
|
||||
}
|
||||
}
|
||||
|
||||
function BasicLayout({project = 'core'}:{project:string}){
|
||||
const navigator = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { state,accessData,checkPermission,accessInit,dispatch,resetAccess,getGlobalAccessData} = 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/page',<Icon icon="ic:baseline-space-dashboard" width="18" height="18"/>, [
|
||||
getNavItem(<a>{$t('首页')}</a>, 'guide','/guide/page',<Icon icon="ic:baseline-home" width="18" height="18"/>,undefined,undefined,'all'),
|
||||
getNavItem(<a>{$t('服务')}</a>, 'service','/service',<Icon icon="ic:baseline-blinds-closed" width="18" height="18"/>,undefined,undefined,'all'),
|
||||
getNavItem(<a>{$t('消费者')}</a>, 'consumer','/consumer',<Icon icon="ic:baseline-apps" width="18" height="18"/>,undefined,undefined,'all'),
|
||||
getNavItem(<a>{$t('团队')}</a>, 'team','/team',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'all'),
|
||||
]),
|
||||
getNavItem($t('API 市场'), 'serviceHub','/serviceHub',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_portal.api_portal.view'),
|
||||
function BasicLayout({ project = 'core' }: { project: string }) {
|
||||
const navigator = useNavigate()
|
||||
const location = useLocation()
|
||||
const currentUrl = location.pathname
|
||||
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
|
||||
useGlobalContext()
|
||||
const [pathname, setPathname] = useState(currentUrl)
|
||||
const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
|
||||
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
|
||||
const pluginSlotHub = usePluginSlotHub()
|
||||
|
||||
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.analysis.run_view.view'),
|
||||
APP_MODE === 'pro' ? getNavItem(<a >{$t('系统拓扑图')}</a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,'system.dashboard.systemrunning.view') : null,
|
||||
],undefined,'system.analysis.run_view.view'),
|
||||
|
||||
getNavItem($t('系统设置'), 'operationCenter','/commonsetting',<Icon icon="ic:baseline-settings" width="18" height="18"/>, [
|
||||
getNavItem($t('系统'), 'serviceHubSetting','/commonsetting',null,[
|
||||
getNavItem(<a>{$t('常规')}</a>, 'commonsetting','/commonsetting',<Icon icon="ic:baseline-hub" width="18" height="18"/>,undefined,undefined,'system.api_market.service_classification.view'),
|
||||
getNavItem(<a>{$t('API 网关')}</a>, 'cluster','/cluster',<Icon icon="ic:baseline-device-hub" width="18" height="18"/>,undefined,undefined,'system.settings.api_gateway.view'),
|
||||
getNavItem(<a>{$t('AI 模型')}</a>, 'aisetting','/aisetting',<Icon icon="hugeicons:ai-network" width="18" height="18"/>,undefined,undefined,'system.settings.api_gateway.view'),
|
||||
],undefined,'system.api_market.service_classification.view'),
|
||||
getNavItem($t('用户'), 'organization','/member',null,[
|
||||
getNavItem(<a>{$t('账号')}</a>, 'member','/member',<Icon icon="ic:baseline-people-alt" width="18" height="18"/>,undefined,undefined,'system.settings.account.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('集成'), 'maintenanceCenter','/datasourcing', null, [
|
||||
getNavItem(<a>{$t('数据源')}</a>, 'datasourcing','/datasourcing',<Icon icon="ic:baseline-monitor-heart" width="18" height="18"/>,undefined,undefined,'system.settings.data_source.view'),
|
||||
getNavItem(<a>{$t('证书')}</a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.settings.ssl_certificate.view'),
|
||||
getNavItem(<a>{$t('日志')}</a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.settings.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(() => {
|
||||
const newMenu = transformMenuData(menuList)
|
||||
setMenuItems(newMenu)
|
||||
}, [menuList, state.language, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUrl === '/') {
|
||||
navigator(mainPage)
|
||||
}
|
||||
}, [currentUrl])
|
||||
|
||||
useEffect(() => {
|
||||
if(currentUrl === '/'){
|
||||
navigator(mainPage)
|
||||
}
|
||||
|
||||
}, [currentUrl]);
|
||||
const headerMenuData = useMemo(() => {
|
||||
// 判断权限
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
|
||||
const headerMenuData = useMemo(() => {
|
||||
// 判断权限
|
||||
const hasAccess = (access: unknown) => checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]);
|
||||
|
||||
// 过滤菜单项
|
||||
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
|
||||
return [...menu]
|
||||
.filter(x => x) // 过滤掉空数据
|
||||
.map((item: any) => {
|
||||
if (item.routes && item.routes.length > 0) {
|
||||
// 递归处理子菜单
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes);
|
||||
|
||||
if(filteredRoutes.length === 0){
|
||||
return false
|
||||
}
|
||||
return {...item, routes: filteredRoutes};
|
||||
}
|
||||
// 处理没有 routes 的菜单项
|
||||
if (item.access) {
|
||||
return (item.access === 'all' || hasAccess(item.access)) ? item : null;
|
||||
}
|
||||
// 过滤菜单项
|
||||
const filterMenu = (menu: Array<{ [k: string]: unknown }>) => {
|
||||
return [...menu]
|
||||
.filter((x) => x) // 过滤掉空数据
|
||||
.map((item: any) => {
|
||||
if (item.routes && item.routes.length > 0) {
|
||||
// 递归处理子菜单
|
||||
const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes)
|
||||
|
||||
// 如果没有 access 和 routes,则保留
|
||||
return item;
|
||||
})
|
||||
.filter(x => x); // 过滤掉处理后为 null 的项
|
||||
};
|
||||
|
||||
// 初始过滤操作
|
||||
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, state.language]);
|
||||
|
||||
|
||||
|
||||
|
||||
const { message } = App.useApp()
|
||||
const [userInfo,setUserInfo] = useState<UserInfoType>()
|
||||
const {fetchData} = useFetch()
|
||||
const navigate = useNavigate();
|
||||
|
||||
const getUserInfo = ()=>{
|
||||
fetchData<BasicResponse<{profile:UserInfoType}>>('account/profile',{method:'GET'})
|
||||
.then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
setUserInfo(data.profile)
|
||||
dispatch({type:'UPDATE_USERDATA',userData:data.profile})
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
if (filteredRoutes.length === 0) {
|
||||
return false
|
||||
}
|
||||
return { ...item, routes: filteredRoutes, name: $t(item.name) }
|
||||
}
|
||||
// 处理没有 routes 的菜单项
|
||||
if (item.access) {
|
||||
return item.access === 'all' || hasAccess(item.access) ? { ...item, name: $t(item.name) } : null
|
||||
}
|
||||
// 如果没有 access 和 routes,则保留
|
||||
return { ...item, name: $t(item.name) }
|
||||
})
|
||||
.filter((x) => x) // 过滤掉处理后为 null 的项
|
||||
}
|
||||
|
||||
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 || $t(RESPONSE_TIPS.logoutSuccess))
|
||||
navigate('/login')
|
||||
}else{
|
||||
message.error(msg ||$t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
// 初始过滤操作
|
||||
const res = [...(menuItems || [])]!
|
||||
.filter((x) => x)
|
||||
.map((x: any) =>
|
||||
x.routes ? { ...x, name: $t(x.name), routes: filterMenu(x.routes) } : { ...x, name: $t(x.name) }
|
||||
)
|
||||
// 返回处理后的数据
|
||||
return {
|
||||
path: '/',
|
||||
routes: res
|
||||
.map((x) => ({ ...x, routes: x.routes?.filter((x) => x.access || x.routes?.length > 0) }))
|
||||
.filter((x) => x.access || x.routes?.length > 0)
|
||||
}
|
||||
}, [accessData, state.language, menuItems])
|
||||
|
||||
const items: MenuProps['items'] = useMemo(() => [
|
||||
const { message } = App.useApp()
|
||||
const [userInfo, setUserInfo] = useState<UserInfoType>()
|
||||
const { fetchData } = useFetch()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const getUserInfo = () => {
|
||||
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' }).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
setUserInfo(data.profile)
|
||||
dispatch({ type: 'UPDATE_USERDATA', userData: data.profile })
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUserInfo()
|
||||
getGlobalAccessData()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setPathname(location.pathname)
|
||||
}, [location.pathname])
|
||||
|
||||
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 || $t(RESPONSE_TIPS.logoutSuccess))
|
||||
navigate('/login')
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const items: MenuProps['items'] = useMemo(
|
||||
() =>
|
||||
[
|
||||
userInfo?.type !== 'guest' && {
|
||||
key: '2',
|
||||
label: (
|
||||
<Button key="changePsw" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={()=>navigator('/userProfile/changepsw')}>
|
||||
{$t('账号设置')}
|
||||
</Button>)
|
||||
key: '2',
|
||||
label: (
|
||||
<Button
|
||||
key="changePsw"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={() => navigator('/userProfile/changepsw')}
|
||||
>
|
||||
{$t('账号设置')}
|
||||
</Button>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: (
|
||||
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={logOut}>
|
||||
{$t('退出登录')}
|
||||
</Button>)
|
||||
},
|
||||
].filter(Boolean), [userInfo]);
|
||||
key: '3',
|
||||
label: (
|
||||
<Button
|
||||
key="logout"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={logOut}
|
||||
>
|
||||
{$t('退出登录')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
].filter(Boolean),
|
||||
[userInfo]
|
||||
)
|
||||
|
||||
const actionRender = useMemo(() => {
|
||||
return [
|
||||
<LanguageSetting />,
|
||||
<Button
|
||||
className=" text-[#ffffffb3] hover:text-[#fff] border-none"
|
||||
type="default"
|
||||
ghost
|
||||
onClick={() => {
|
||||
window.open('https://docs.apipark.com', '_blank')
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-[8px]">
|
||||
{' '}
|
||||
<Icon icon="ic:baseline-help" width="14" height="14" />
|
||||
{$t('文档')}
|
||||
</span>
|
||||
</Button>,
|
||||
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || [])
|
||||
]
|
||||
}, [state.language, pluginSlotHub.getSlot('basicLayoutAfterBtns')])
|
||||
|
||||
|
||||
return(
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
return (
|
||||
<div
|
||||
id="test-pro-layout"
|
||||
style={{
|
||||
height: '100vh',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body
|
||||
}}
|
||||
>
|
||||
<ProConfigProvider hashed={false}>
|
||||
<ConfigProvider
|
||||
getTargetContainer={() => {
|
||||
return document.getElementById('test-pro-layout') || document.body;
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{
|
||||
pathname
|
||||
}}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{
|
||||
type: 'group',
|
||||
collapsedShowGroupTitle: true
|
||||
}}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: AvatarPic || userInfo?.avatar,
|
||||
size: 'small',
|
||||
title: userInfo?.username || 'unknown',
|
||||
render: (props, dom) => {
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items
|
||||
}}
|
||||
>
|
||||
<ProLayout
|
||||
prefixCls="apipark-layout"
|
||||
location={{
|
||||
pathname,
|
||||
}}
|
||||
siderWidth={220}
|
||||
breakpoint={'lg'}
|
||||
route={headerMenuData}
|
||||
token={themeToken}
|
||||
siderMenuType="group"
|
||||
menu={{
|
||||
type: 'group',
|
||||
collapsedShowGroupTitle: true,
|
||||
}}
|
||||
disableMobile={true}
|
||||
avatarProps={{
|
||||
src: AvatarPic || userInfo?.avatar,
|
||||
size: 'small',
|
||||
title: userInfo?.username||'unknown',
|
||||
render: (props, dom) => {
|
||||
return (
|
||||
<Dropdown
|
||||
menu={{
|
||||
items
|
||||
}}
|
||||
>
|
||||
<div className='avatar-dom'>{dom}
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
},
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return [];
|
||||
if (typeof window === 'undefined') return [];
|
||||
return [
|
||||
<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>
|
||||
)
|
||||
>
|
||||
<div className="avatar-dom">{dom}</div>
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
}}
|
||||
actionsRender={(props) => {
|
||||
if (props.isMobile) return []
|
||||
if (typeof window === 'undefined') return []
|
||||
return actionRender
|
||||
}}
|
||||
headerTitleRender={() => (
|
||||
<div className="w-[192px] flex items-center">
|
||||
<img className="h-[20px] cursor-pointer " src={Logo} onClick={() => navigator(mainPage)} />
|
||||
</div>
|
||||
)}
|
||||
logo={Logo}
|
||||
pageTitleRender={() => $t('APIPark')}
|
||||
menuFooterRender={(props) => {
|
||||
if (props?.collapsed) return undefined
|
||||
}}
|
||||
menuItemRender={(item, dom) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
// 同级目录点击无效
|
||||
if (
|
||||
item.key &&
|
||||
routerKeyMap.get(item.key) &&
|
||||
routerKeyMap.get(item.key).length > 0 &&
|
||||
routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (item.key === pathname.split('/')[1]) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.path) {
|
||||
navigator(item.path)
|
||||
}
|
||||
setPathname(item.path || '')
|
||||
}}
|
||||
>
|
||||
{dom}
|
||||
</div>
|
||||
)}
|
||||
fixSiderbar={true}
|
||||
layout="mix"
|
||||
splitMenus={true}
|
||||
collapsed={false}
|
||||
collapsedButtonRender={false}
|
||||
>
|
||||
<div
|
||||
className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${
|
||||
currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'
|
||||
}`}
|
||||
>
|
||||
<Outlet />
|
||||
</div>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
</ProConfigProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default BasicLayout
|
||||
export default BasicLayout
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { Breadcrumb } from "antd"
|
||||
import { useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {FC,useEffect} from "react";
|
||||
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { FC, useEffect } from 'react'
|
||||
|
||||
const TopBreadcrumb: FC = () => {
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {
|
||||
}, [breadcrumb]);
|
||||
return (
|
||||
<Breadcrumb items={breadcrumb} />
|
||||
)
|
||||
const { breadcrumb } = useBreadcrumb()
|
||||
useEffect(() => {}, [breadcrumb])
|
||||
return <Breadcrumb items={breadcrumb} />
|
||||
}
|
||||
|
||||
export default TopBreadcrumb
|
||||
export default TopBreadcrumb
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
import { FC } from 'react';
|
||||
import { Table } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { $t } from '@common/locales';
|
||||
import { FC } from 'react'
|
||||
import { Table } from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
interface DataType {
|
||||
httpStatusCode: string;
|
||||
systemStatusCode: string;
|
||||
description: string;
|
||||
|
||||
httpStatusCode: string
|
||||
systemStatusCode: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
title:$t('HTTP 状态码'),
|
||||
title: $t('HTTP 状态码'),
|
||||
dataIndex: 'httpStatusCode',
|
||||
key: 'httpStatusCode',
|
||||
key: 'httpStatusCode'
|
||||
},
|
||||
{
|
||||
title:$t('系统状态码'),
|
||||
title: $t('系统状态码'),
|
||||
dataIndex: 'systemStatusCode',
|
||||
key: 'systemStatusCode',
|
||||
key: 'systemStatusCode'
|
||||
},
|
||||
{
|
||||
title: $t('描述'),
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
ellipsis:true
|
||||
},
|
||||
|
||||
];
|
||||
ellipsis: true
|
||||
}
|
||||
]
|
||||
|
||||
const data: DataType[] = [
|
||||
// {
|
||||
@@ -44,12 +42,12 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '413',
|
||||
systemStatusCode: '10003',
|
||||
description: '请求频率过高',
|
||||
description: '请求频率过高'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '403',
|
||||
systemStatusCode: '10004',
|
||||
description: '请求来源非法,不在白名单中',
|
||||
description: '请求来源非法,不在白名单中'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '416',
|
||||
@@ -59,7 +57,7 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '504',
|
||||
systemStatusCode: '10006',
|
||||
description: '网关超时',
|
||||
description: '网关超时'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '504',
|
||||
@@ -69,7 +67,7 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '404',
|
||||
systemStatusCode: '10007',
|
||||
description: '接口不存在',
|
||||
description: '接口不存在'
|
||||
},
|
||||
// {
|
||||
// httpStatusCode: '416',
|
||||
@@ -84,42 +82,43 @@ const data: DataType[] = [
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10010',
|
||||
description: '无法识别请求内容,请检查请求体是否正确',
|
||||
description: '无法识别请求内容,请检查请求体是否正确'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10011',
|
||||
description: '请求头部缺少 Content-Type 字段',
|
||||
description: '请求头部缺少 Content-Type 字段'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10011',
|
||||
description: '请求头部 Content-Type 字段错误',
|
||||
description: '请求头部 Content-Type 字段错误'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10014',
|
||||
description: '批量参数超出单次批量数量的最大限制',
|
||||
description: '批量参数超出单次批量数量的最大限制'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '400',
|
||||
systemStatusCode: '10016',
|
||||
description: '参数缺少内容',
|
||||
description: '参数缺少内容'
|
||||
},
|
||||
{
|
||||
httpStatusCode: '500',
|
||||
systemStatusCode: '10017',
|
||||
description: '参数类型错误',
|
||||
},
|
||||
];
|
||||
description: '参数类型错误'
|
||||
}
|
||||
]
|
||||
|
||||
const CodePage: FC = () =>
|
||||
<Table
|
||||
const CodePage: FC = () => (
|
||||
<Table
|
||||
size="small"
|
||||
columns={columns}
|
||||
className='table-border border-b-0 rounded'
|
||||
dataSource={data?.map((item, index) => ({...item, key: index})) || []}
|
||||
columns={columns}
|
||||
className="table-border border-b-0 rounded"
|
||||
dataSource={data?.map((item, index) => ({ ...item, key: index })) || []}
|
||||
pagination={false}
|
||||
/>;
|
||||
/>
|
||||
)
|
||||
|
||||
export default CodePage;
|
||||
export default CodePage
|
||||
|
||||
@@ -1,33 +1,32 @@
|
||||
|
||||
import { useState,FC } from 'react';
|
||||
import { Tooltip, Button } from 'antd';
|
||||
import useCopyToClipboard from '@common/hooks/copy';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { useState, FC } from 'react'
|
||||
import { Tooltip, Button } from 'antd'
|
||||
import useCopyToClipboard from '@common/hooks/copy'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
type AddressItem = {
|
||||
expand?: boolean;
|
||||
[key: string]: unknown;
|
||||
expand?: boolean
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type CopyAddrListProps = {
|
||||
addrItem: AddressItem;
|
||||
onAddrItemChange?: (addrItem: AddressItem) => void;
|
||||
keyName: string;
|
||||
type CopyAddrListProps = {
|
||||
addrItem: AddressItem
|
||||
onAddrItemChange?: (addrItem: AddressItem) => void
|
||||
keyName: string
|
||||
}
|
||||
|
||||
const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyName }) => {
|
||||
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem);
|
||||
const { copyToClipboard } = useCopyToClipboard();
|
||||
const [localAddrItem, setLocalAddrItem] = useState<AddressItem>(addrItem)
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
|
||||
const toggleExpand = () => {
|
||||
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand };
|
||||
setLocalAddrItem(updatedAddrItem);
|
||||
onAddrItemChange?.(updatedAddrItem);
|
||||
};
|
||||
const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand }
|
||||
setLocalAddrItem(updatedAddrItem)
|
||||
onAddrItemChange?.(updatedAddrItem)
|
||||
}
|
||||
|
||||
const renderTooltipTitle = () => {
|
||||
// 假设keyName对应的值是一个字符串数组
|
||||
const addresses:string[] = localAddrItem[keyName] as string[]
|
||||
const addresses: string[] = localAddrItem[keyName] as string[]
|
||||
return (
|
||||
<div>
|
||||
{addresses?.map((addr, index) => (
|
||||
@@ -36,29 +35,41 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const renderAddresses = () => {
|
||||
if (!localAddrItem.expand) {
|
||||
return (
|
||||
<span className="overflow-ellipsis w-full inline-block overflow-hidden align-middle">
|
||||
<Tooltip title={renderTooltipTitle}>
|
||||
<span className='flex items-center'>
|
||||
<span className={`overflow-ellipsis inline-block overflow-hidden align-middle ${((localAddrItem[keyName] as string[]).length > 1) ? 'w-5/6' : 'w-full'}`}>
|
||||
<span className="flex items-center">
|
||||
<span
|
||||
className={`overflow-ellipsis inline-block overflow-hidden align-middle ${(localAddrItem[keyName] as string[]).length > 1 ? 'w-5/6' : 'w-full'}`}
|
||||
>
|
||||
{(localAddrItem[keyName] as string[]).join(',')}
|
||||
</span>
|
||||
{(localAddrItem[keyName] as string[]).length === 1 && (
|
||||
<Button type="primary" className="border-none ant-typography-copy text-theme hover:text-A_HOVER " ghost onClick={() => copyToClipboard((localAddrItem[keyName] as string))} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
|
||||
<Button
|
||||
type="primary"
|
||||
className="border-none ant-typography-copy text-theme hover:text-A_HOVER "
|
||||
ghost
|
||||
onClick={() => copyToClipboard(localAddrItem[keyName] as string)}
|
||||
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
{(localAddrItem[keyName] as string[]).length !== 1 && (
|
||||
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="zhankai" style={{marginTop:'4px'}}></iconpark-icon>} onClick={toggleExpand} />
|
||||
<Button
|
||||
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
|
||||
icon={<iconpark-icon name="zhankai" style={{ marginTop: '4px' }}></iconpark-icon>}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
);
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center justify-between">
|
||||
@@ -66,21 +77,28 @@ const CopyAddrList: FC<CopyAddrListProps> = ({ addrItem, onAddrItemChange, keyNa
|
||||
{(localAddrItem[keyName] as string[])?.map((addr: string, index: number) => (
|
||||
<div key={index} className="block w-full">
|
||||
<span className="leading-6">{addr}</span>
|
||||
<Button type="primary" className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER" ghost onClick={() => copyToClipboard(addr)} icon={<Icon icon="ic:baseline-file-copy" width="14" height="14"/>} size="small" />
|
||||
<Button
|
||||
type="primary"
|
||||
className="border-none bg-transparent w-[16px] h-[22px] p-[0px] ml-2 text-theme hover:text-A_HOVER"
|
||||
ghost
|
||||
onClick={() => copyToClipboard(addr)}
|
||||
icon={<Icon icon="ic:baseline-file-copy" width="14" height="14" />}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Button className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]" icon={<iconpark-icon name="shouqi-2"></iconpark-icon>} onClick={toggleExpand} />
|
||||
<Button
|
||||
className="border-none bg-transparent w-[16px] h-[22px] text-table_text p-[0px]"
|
||||
icon={<iconpark-icon name="shouqi-2"></iconpark-icon>}
|
||||
onClick={toggleExpand}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderAddresses()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
return <div>{renderAddresses()}</div>
|
||||
}
|
||||
|
||||
export default CopyAddrList;
|
||||
export default CopyAddrList
|
||||
|
||||
@@ -1,59 +1,84 @@
|
||||
|
||||
import { Button, Drawer, DrawerProps, Space } from "antd";
|
||||
import WithPermission from "./WithPermission";
|
||||
import { useEffect, useState } from "react";
|
||||
import { $t } from '@common/locales';
|
||||
import { Button, Drawer, DrawerProps, Space } from 'antd'
|
||||
import WithPermission from './WithPermission'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
export type DrawerWithFooterProps = DrawerProps & {
|
||||
onSubmit?: () => Promise<boolean|string>|undefined
|
||||
submitAccess?: string
|
||||
submitDisabled?:boolean
|
||||
onClose?:()=>void
|
||||
showLastStep?:boolean
|
||||
onLastStep?:()=>void
|
||||
notAutoClose?:boolean
|
||||
showOkBtn?:boolean
|
||||
extraBtn?:React.ReactNode
|
||||
okBtnTitle?:string
|
||||
cancelBtnTitle?:string
|
||||
onSubmit?: () => Promise<boolean | string> | undefined
|
||||
submitAccess?: string
|
||||
submitDisabled?: boolean
|
||||
onClose?: () => void
|
||||
showLastStep?: boolean
|
||||
onLastStep?: () => void
|
||||
notAutoClose?: boolean
|
||||
showOkBtn?: boolean
|
||||
extraBtn?: React.ReactNode
|
||||
okBtnTitle?: string
|
||||
cancelBtnTitle?: string
|
||||
}
|
||||
export function DrawerWithFooter(props:DrawerWithFooterProps){
|
||||
const {children,title,placement='right',onClose,onSubmit,submitDisabled = false,okBtnTitle= $t('提交'),cancelBtnTitle,open,submitAccess,showLastStep,onLastStep,notAutoClose,showOkBtn=true,extraBtn} = props
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||
const handlerSubmit = ()=>{
|
||||
setSubmitLoading(true)
|
||||
onSubmit?.()?.then(()=>{!notAutoClose && onClose?.()}).finally(()=>{setSubmitLoading(false)})
|
||||
}
|
||||
export function DrawerWithFooter(props: DrawerWithFooterProps) {
|
||||
const {
|
||||
children,
|
||||
title,
|
||||
placement = 'right',
|
||||
onClose,
|
||||
onSubmit,
|
||||
submitDisabled = false,
|
||||
okBtnTitle = $t('提交'),
|
||||
cancelBtnTitle,
|
||||
open,
|
||||
submitAccess,
|
||||
showLastStep,
|
||||
onLastStep,
|
||||
notAutoClose,
|
||||
showOkBtn = true,
|
||||
extraBtn
|
||||
} = props
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||
const handlerSubmit = () => {
|
||||
setSubmitLoading(true)
|
||||
onSubmit?.()
|
||||
?.then(() => {
|
||||
!notAutoClose && onClose?.()
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(()=>{!open && setSubmitLoading(false)},[open])
|
||||
return (<>
|
||||
<Drawer
|
||||
{...props}
|
||||
push={false}
|
||||
title={title}
|
||||
placement={placement}
|
||||
width="60%"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
classNames={
|
||||
{footer:'text-right'}
|
||||
}
|
||||
footer={
|
||||
<Space className="flex flex-row-reverse" style={{}}>
|
||||
{showOkBtn && <WithPermission access={submitAccess}>
|
||||
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
|
||||
{ okBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>}
|
||||
{ showLastStep && <Button onClick={onLastStep ?? onClose}> { $t('上一步')}</Button>}
|
||||
{ extraBtn }
|
||||
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消'):$t('关闭'))}</Button>
|
||||
</Space>
|
||||
}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
</>)
|
||||
}
|
||||
useEffect(() => {
|
||||
!open && setSubmitLoading(false)
|
||||
}, [open])
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
{...props}
|
||||
push={false}
|
||||
title={title}
|
||||
placement={placement}
|
||||
width="60%"
|
||||
destroyOnClose={true}
|
||||
maskClosable={false}
|
||||
classNames={{ footer: 'text-right' }}
|
||||
footer={
|
||||
<Space className="flex flex-row-reverse" style={{}}>
|
||||
{showOkBtn && (
|
||||
<WithPermission access={submitAccess}>
|
||||
<Button onClick={handlerSubmit} type="primary" loading={submitLoading} disabled={submitDisabled}>
|
||||
{okBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
)}
|
||||
{showLastStep && <Button onClick={onLastStep ?? onClose}> {$t('上一步')}</Button>}
|
||||
{extraBtn}
|
||||
<Button onClick={onClose}>{cancelBtnTitle ?? (showOkBtn ? $t('取消') : $t('关闭'))}</Button>
|
||||
</Space>
|
||||
}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
>
|
||||
{children}
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,91 +1,93 @@
|
||||
|
||||
import {FC } from 'react';
|
||||
import { Input, Space } from 'antd';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { FC } from 'react'
|
||||
import { Input, Space } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
|
||||
type KeyValueInput = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type DynamicKeyValueInputProps = {
|
||||
value?: KeyValueInput[];
|
||||
onChange?: (newValue: KeyValueInput[]) => void;
|
||||
};
|
||||
value?: KeyValueInput[]
|
||||
onChange?: (newValue: KeyValueInput[]) => void
|
||||
}
|
||||
|
||||
|
||||
export function transferToList (rawData:unknown):Array<{key:string, value:string}> {
|
||||
const res:Array<{key:string, value:string}> = []
|
||||
if(!rawData)
|
||||
return res
|
||||
const keys:Array<string> = Object.keys(rawData)
|
||||
export function transferToList(rawData: unknown): Array<{ key: string; value: string }> {
|
||||
const res: Array<{ key: string; value: string }> = []
|
||||
if (!rawData) return res
|
||||
const keys: Array<string> = Object.keys(rawData)
|
||||
if (keys?.length > 0) {
|
||||
for (const key of keys) {
|
||||
res.push({ key: key, value: rawData[key] })
|
||||
for (const key of keys) {
|
||||
res.push({ key: key, value: rawData[key] })
|
||||
}
|
||||
return [...res, { key: '', value: '' }]
|
||||
}
|
||||
return [...res, { key: '', value: '' }]
|
||||
}
|
||||
return [{ key: '', value: '' }]
|
||||
return [{ key: '', value: '' }]
|
||||
}
|
||||
|
||||
export function transferToMap (rawData:Array<{key:string, value:string}>):{[key:string]:string} {
|
||||
const res:{[key:string]:string} = {}
|
||||
export function transferToMap(rawData: Array<{ key: string; value: string }>): { [key: string]: string } {
|
||||
const res: { [key: string]: string } = {}
|
||||
for (const kv of rawData) {
|
||||
if (kv.key && kv.value) { res[kv.key] = kv.value }
|
||||
if (kv.key && kv.value) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({value = [{key:'',value:''}],onChange}) => {
|
||||
export const DynamicKeyValueInput: FC<DynamicKeyValueInputProps> = ({ value = [{ key: '', value: '' }], onChange }) => {
|
||||
// const [keyValuePairs, setKeyValuePairs] = useState<KeyValueInput[]>([{ key: '', value: '' }]);
|
||||
|
||||
// Define a handler for when the inputs change
|
||||
// Define a handler for when the inputs change
|
||||
const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => {
|
||||
// Create a new array with the updated value
|
||||
const newKeyValuePairs = value ? [...value] : [];
|
||||
const newKeyValuePairs = value ? [...value] : []
|
||||
if (newKeyValuePairs[index]) {
|
||||
newKeyValuePairs[index][type] = newValue;
|
||||
newKeyValuePairs[index][type] = newValue
|
||||
// If we're changing the last input and it's not empty, add a new pair
|
||||
if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) {
|
||||
newKeyValuePairs.push({ key: '', value: '' });
|
||||
newKeyValuePairs.push({ key: '', value: '' })
|
||||
}
|
||||
// Call the onChange handler if it exists
|
||||
onChange?.(newKeyValuePairs);
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const addNewPair = () => {
|
||||
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }];
|
||||
onChange?.(newKeyValuePairs);
|
||||
};
|
||||
const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }]
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
|
||||
const removePair = (index: number) => {
|
||||
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || [];
|
||||
onChange?.(newKeyValuePairs);
|
||||
};
|
||||
const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || []
|
||||
onChange?.(newKeyValuePairs)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{value && value?.map((pair, index) => (
|
||||
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Input
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
|
||||
style={{ width: 162 }} />
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
style={{ width: 162 }} />
|
||||
{index !== value.length - 1 && (
|
||||
<>
|
||||
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14"/>
|
||||
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14"/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
<>
|
||||
{value &&
|
||||
value?.map((pair, index) => (
|
||||
<Space key={index} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
|
||||
<Input
|
||||
placeholder="Key"
|
||||
value={pair.key}
|
||||
onChange={(e) => handleInputChange(index, 'key', e.target.value)}
|
||||
style={{ width: 162 }}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Value"
|
||||
value={pair.value}
|
||||
onChange={(e) => handleInputChange(index, 'value', e.target.value)}
|
||||
style={{ width: 162 }}
|
||||
/>
|
||||
{index !== value.length - 1 && (
|
||||
<>
|
||||
<Icon icon="ic:baseline-delete" onClick={() => removePair(index)} width="14" height="14" />
|
||||
<Icon icon="ic:baseline-add" onClick={addNewPair} width="14" height="14" />
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,116 +1,130 @@
|
||||
import { EditableProTable } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { PageProColumns } from "./PageList";
|
||||
import TableBtnWithPermission from "./TableBtnWithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
import { EditableProTable } from '@ant-design/pro-components'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PageProColumns } from './PageList'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
interface EditableTableProps<T> {
|
||||
configFields: PageProColumns<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
extendsId?:string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
|
||||
configFields: PageProColumns<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?: boolean
|
||||
extendsId?: string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id
|
||||
}
|
||||
|
||||
const EditableTable = <T extends { _id: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
extendsId,
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
|
||||
const {state} = useGlobalContext()
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
extendsId
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
|
||||
value?.map((item) => item._id) || ['1234']
|
||||
);
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
|
||||
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]);
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }])
|
||||
}, [value])
|
||||
|
||||
const getNotEmptyValue = (value:unknown)=>{
|
||||
return value
|
||||
}
|
||||
const getNotEmptyValue = (value: unknown) => {
|
||||
return value
|
||||
}
|
||||
|
||||
const translatedColumns = useMemo(()=>configFields.map((x)=>({...x, title:$t(x.title as string)})),[state.language,configFields])
|
||||
const translatedColumns = useMemo(
|
||||
() => configFields.map((x) => ({ ...x, title: $t(x.title as string) })),
|
||||
[state.language, configFields]
|
||||
)
|
||||
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={ {
|
||||
type: 'multiple',
|
||||
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission key="add" btnType="add" onClick={() => {
|
||||
const newId = uuidv4();
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
const newId = uuidv4()
|
||||
const lastRecord:{[k:string]:unknown} = tmpPreData[tmpPreData.length - 1];
|
||||
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if(extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach(field => {
|
||||
newRecord[field] = lastRecord[field];
|
||||
});
|
||||
}
|
||||
tmpPreData.splice(Number(config.index) + 1, 0,newRecord);
|
||||
onChange?.(getNotEmptyValue(tmpPreData));
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>([...prev,newId]))
|
||||
}}
|
||||
btnTitle="增加"/>,
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission
|
||||
key="add"
|
||||
btnType="add"
|
||||
onClick={() => {
|
||||
const newId = uuidv4()
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
const newId = uuidv4()
|
||||
const lastRecord: { [k: string]: unknown } = tmpPreData[tmpPreData.length - 1]
|
||||
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
|
||||
|
||||
(config.index !== configurations.length - 1 )&& <TableBtnWithPermission key="remove" btnType="remove" btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
tmpPreData.splice(Number(config.index), 1);
|
||||
onChange?.(tmpPreData);
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
|
||||
}}/>,,
|
||||
];
|
||||
},
|
||||
onValuesChange: (record, recordList) => {
|
||||
if(record._id === recordList[recordList.length - 1]._id){
|
||||
const newId = uuidv4()
|
||||
const lastRecord:{[k:string]:unknown} = recordList[recordList.length - 1];
|
||||
const newRecord :{[k:string]:unknown, _id:string}= { _id: newId };
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if(extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach(field => {
|
||||
newRecord[field] = lastRecord[field];
|
||||
});
|
||||
}
|
||||
|
||||
recordList = ([...recordList, newRecord as T]);
|
||||
setEditableRowKeys((prev)=>[...prev, newId])
|
||||
}
|
||||
setConfigurations(recordList);
|
||||
onChange?.(recordList);
|
||||
},
|
||||
onChange: setEditableRowKeys,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if (extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach((field) => {
|
||||
newRecord[field] = lastRecord[field]
|
||||
})
|
||||
}
|
||||
tmpPreData.splice(Number(config.index) + 1, 0, newRecord)
|
||||
onChange?.(getNotEmptyValue(tmpPreData))
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => [...prev, newId])
|
||||
}}
|
||||
btnTitle="增加"
|
||||
/>,
|
||||
|
||||
export default EditableTable;
|
||||
config.index !== configurations.length - 1 && (
|
||||
<TableBtnWithPermission
|
||||
key="remove"
|
||||
btnType="remove"
|
||||
btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
tmpPreData.splice(Number(config.index), 1)
|
||||
onChange?.(tmpPreData)
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
|
||||
}}
|
||||
/>
|
||||
),
|
||||
,
|
||||
]
|
||||
},
|
||||
onValuesChange: (record, recordList) => {
|
||||
if (record._id === recordList[recordList.length - 1]._id) {
|
||||
const newId = uuidv4()
|
||||
const lastRecord: { [k: string]: unknown } = recordList[recordList.length - 1]
|
||||
const newRecord: { [k: string]: unknown; _id: string } = { _id: newId }
|
||||
|
||||
// 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值
|
||||
if (extendsId && extendsId.length > 0) {
|
||||
extendsId.forEach((field) => {
|
||||
newRecord[field] = lastRecord[field]
|
||||
})
|
||||
}
|
||||
|
||||
recordList = [...recordList, newRecord as T]
|
||||
setEditableRowKeys((prev) => [...prev, newId])
|
||||
}
|
||||
setConfigurations(recordList)
|
||||
onChange?.(recordList)
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTable
|
||||
|
||||
@@ -1,105 +1,119 @@
|
||||
import { EditableFormInstance, EditableProTable } from "@ant-design/pro-components";
|
||||
import { useState, useEffect, useMemo, useRef, MutableRefObject } from "react";
|
||||
import { v4 as uuidv4} from 'uuid';
|
||||
import { PageProColumns } from "./PageList";
|
||||
import TableBtnWithPermission from "./TableBtnWithPermission";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { Form } from "antd";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
import { EditableFormInstance, EditableProTable } from '@ant-design/pro-components'
|
||||
import { useState, useEffect, useMemo, useRef, MutableRefObject } from 'react'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { PageProColumns } from './PageList'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { Form } from 'antd'
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
interface EditableTableProps<T> {
|
||||
configFields: PageProColumns<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
getFromRef?:(form:MutableRefObject<EditableFormInstance<T> | undefined>)=>void
|
||||
configFields: PageProColumns<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
// tableProps?: TableProps<T>;
|
||||
disabled?: boolean
|
||||
getFromRef?: (form: MutableRefObject<EditableFormInstance<T> | undefined>) => void
|
||||
}
|
||||
|
||||
const EditableTableNotAutoGen = <T extends { _id: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
getFromRef
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]);
|
||||
const {state} = useGlobalContext()
|
||||
const form =useRef<EditableFormInstance<T>>();
|
||||
const [tableForm] = Form.useForm();
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
|
||||
value?.map((item) => item._id) || ['1234']
|
||||
);
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
// tableProps,
|
||||
disabled,
|
||||
className,
|
||||
getFromRef
|
||||
}: EditableTableProps<T>) => {
|
||||
const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }])
|
||||
const { state } = useGlobalContext()
|
||||
const form = useRef<EditableFormInstance<T>>()
|
||||
const [tableForm] = Form.useForm()
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => value?.map((item) => item._id) || ['1234'])
|
||||
|
||||
useEffect(()=>{
|
||||
getFromRef?.(form)
|
||||
},[form])
|
||||
useEffect(() => {
|
||||
getFromRef?.(form)
|
||||
}, [form])
|
||||
|
||||
useEffect(() => {
|
||||
const newValue = value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]
|
||||
setConfigurations(newValue);
|
||||
setTimeout(()=>validateForm(),1000)
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
const newValue = value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }]
|
||||
setConfigurations(newValue)
|
||||
setTimeout(() => validateForm(), 1000)
|
||||
}, [value])
|
||||
|
||||
const validateForm = async ()=>{
|
||||
await tableForm.validateFields();
|
||||
}
|
||||
const validateForm = async () => {
|
||||
await tableForm.validateFields()
|
||||
}
|
||||
|
||||
const translatedColumns = useMemo(()=>configFields.map((x)=>(
|
||||
{...x,
|
||||
title:$t(x.title as string),
|
||||
formItemProps:{
|
||||
...(x. formItemProps || {}),
|
||||
rules:[...(x.formItemProps?.rules || []).map((r:Record<string, string>)=>{
|
||||
if(r.message){
|
||||
r.message = $t(r.message)
|
||||
}
|
||||
return r
|
||||
})],
|
||||
}})),[state.language,configFields])
|
||||
|
||||
const debouncedOnChange = useMemo(() => debounce((value) => {
|
||||
onChange?.(value);
|
||||
}, 500), [onChange]);
|
||||
const translatedColumns = useMemo(
|
||||
() =>
|
||||
configFields.map((x) => ({
|
||||
...x,
|
||||
title: $t(x.title as string),
|
||||
formItemProps: {
|
||||
...(x.formItemProps || {}),
|
||||
rules: [
|
||||
...(x.formItemProps?.rules || []).map((r: Record<string, string>) => {
|
||||
if (r.message) {
|
||||
r.message = $t(r.message)
|
||||
}
|
||||
return r
|
||||
})
|
||||
]
|
||||
}
|
||||
})),
|
||||
[state.language, configFields]
|
||||
)
|
||||
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
onChange={debouncedOnChange}
|
||||
controlled={true}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
editableFormRef={form}
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={ {
|
||||
type: 'multiple',
|
||||
form: tableForm,
|
||||
// errorType:'default',
|
||||
editableKeys:disabled ? [] : configurations?.map(x=>x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission key="delete" btnType="delete" btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev)=>{
|
||||
const tmpPreData = [...prev];
|
||||
tmpPreData.splice(Number(config.index), 1);
|
||||
onChange?.(tmpPreData);
|
||||
return tmpPreData});
|
||||
setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id)))
|
||||
}}/>,
|
||||
];
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
const debouncedOnChange = useMemo(
|
||||
() =>
|
||||
debounce((value) => {
|
||||
onChange?.(value)
|
||||
}, 500),
|
||||
[onChange]
|
||||
)
|
||||
|
||||
export default EditableTableNotAutoGen;
|
||||
return (
|
||||
<EditableProTable<T>
|
||||
className={className}
|
||||
columns={translatedColumns}
|
||||
onChange={debouncedOnChange}
|
||||
controlled={true}
|
||||
rowKey="_id"
|
||||
value={configurations as T[]}
|
||||
size="small"
|
||||
editableFormRef={form}
|
||||
bordered={true}
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
form: tableForm,
|
||||
// errorType:'default',
|
||||
editableKeys: disabled ? [] : configurations?.map((x) => x._id),
|
||||
actionRender: (row, config) => {
|
||||
return [
|
||||
<TableBtnWithPermission
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
btnTitle="删除"
|
||||
onClick={() => {
|
||||
setConfigurations((prev) => {
|
||||
const tmpPreData = [...prev]
|
||||
tmpPreData.splice(Number(config.index), 1)
|
||||
onChange?.(tmpPreData)
|
||||
return tmpPreData
|
||||
})
|
||||
setEditableRowKeys((prev) => prev.filter((x) => x !== config._id))
|
||||
}}
|
||||
/>
|
||||
]
|
||||
},
|
||||
onChange: setEditableRowKeys
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditableTableNotAutoGen
|
||||
|
||||
@@ -1,165 +1,195 @@
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import WithPermission from './WithPermission';
|
||||
import { $t } from '@common/locales';
|
||||
import { COLUMNS_TITLE, VALIDATE_MESSAGE } from '@common/const/const';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import TableBtnWithPermission from './TableBtnWithPermission';
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import WithPermission from './WithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { COLUMNS_TITLE } from '@common/const/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import TableBtnWithPermission from './TableBtnWithPermission'
|
||||
|
||||
export interface ConfigField<T> {
|
||||
title: string;
|
||||
key: keyof T;
|
||||
component: React.ReactNode;
|
||||
renderText?: (value: unknown, record: T) => string;
|
||||
required?: boolean;
|
||||
ellipsis?:boolean
|
||||
unRender?:(form:FormInstance)=>boolean
|
||||
title: string
|
||||
key: keyof T
|
||||
component: React.ReactNode
|
||||
renderText?: (value: unknown, record: T) => string
|
||||
required?: boolean
|
||||
ellipsis?: boolean
|
||||
unRender?: (form: FormInstance) => boolean
|
||||
}
|
||||
|
||||
interface EditableTableWithModalProps<T> {
|
||||
configFields: ConfigField<T>[];
|
||||
value?: T[]; // 外部传入的值
|
||||
className?: string;
|
||||
onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数
|
||||
tableProps?: TableProps<T>;
|
||||
disabled?:boolean
|
||||
configFields: ConfigField<T>[]
|
||||
value?: T[] // 外部传入的值
|
||||
className?: string
|
||||
onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数
|
||||
tableProps?: TableProps<T>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const EditableTableWithModal = <T extends { _id?: string }>({
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
tableProps,
|
||||
disabled,
|
||||
className
|
||||
}: EditableTableWithModalProps<T>) => {
|
||||
const [form] = Form.useForm<FormInstance>();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const [configurations, setConfigurations] = useState<T[]>(value ||[]);
|
||||
const [editingConfig, setEditingConfig] = useState<T | null>(null);
|
||||
const {state} = useGlobalContext()
|
||||
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
|
||||
configFields,
|
||||
value, // value 现在是外部传入的配置项数组
|
||||
onChange, // onChange 现在是当配置项数组变化时的回调函数
|
||||
tableProps,
|
||||
disabled,
|
||||
className
|
||||
}: EditableTableWithModalProps<T>) => {
|
||||
const [form] = Form.useForm<FormInstance>()
|
||||
const [isModalVisible, setIsModalVisible] = useState(false)
|
||||
const [configurations, setConfigurations] = useState<T[]>(value || [])
|
||||
const [editingConfig, setEditingConfig] = useState<T | null>(null)
|
||||
const { state } = useGlobalContext()
|
||||
const [formsValue, setFormsValue] = useState<FormInstance<unknown>>()
|
||||
|
||||
const showModal = (config?: T) => {
|
||||
if (config) {
|
||||
form.setFieldsValue(config as Record<string, unknown>);
|
||||
setEditingConfig(config);
|
||||
const showModal = (config?: T) => {
|
||||
if (config) {
|
||||
form.setFieldsValue(config as Record<string, unknown>)
|
||||
setEditingConfig(config)
|
||||
} else {
|
||||
form.resetFields()
|
||||
setEditingConfig(null)
|
||||
}
|
||||
setIsModalVisible(true)
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false)
|
||||
}
|
||||
|
||||
const handleDelete = (_id: string) => {
|
||||
const newConfigurations = configurations.filter((config) => config._id !== _id)
|
||||
setConfigurations(newConfigurations)
|
||||
onChange?.(newConfigurations)
|
||||
}
|
||||
|
||||
const handleOk = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
let newConfigurations = [...configurations]
|
||||
if (editingConfig && editingConfig._id) {
|
||||
newConfigurations = newConfigurations?.map((config) =>
|
||||
config._id === editingConfig._id ? { ...config, ...values } : config
|
||||
)
|
||||
} else {
|
||||
form.resetFields();
|
||||
setEditingConfig(null);
|
||||
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>
|
||||
newConfigurations.push(newConfig as T)
|
||||
}
|
||||
setIsModalVisible(true);
|
||||
};
|
||||
setConfigurations(newConfigurations)
|
||||
onChange?.(newConfigurations)
|
||||
setIsModalVisible(false)
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log('Validate Failed:', info)
|
||||
})
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [])
|
||||
}, [value])
|
||||
|
||||
const handleDelete = (_id: string) => {
|
||||
const newConfigurations = configurations.filter(config => config._id !== _id);
|
||||
setConfigurations(newConfigurations);
|
||||
onChange?.(newConfigurations);
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
form.validateFields()
|
||||
.then(values => {
|
||||
let newConfigurations = [...configurations];
|
||||
if (editingConfig && editingConfig._id) {
|
||||
newConfigurations = newConfigurations?.map(config =>
|
||||
config._id === editingConfig._id ? { ...config, ...values } : config
|
||||
);
|
||||
} else {
|
||||
const newConfig = { _id: uuidv4(), ...values } as Record<string, unknown>;
|
||||
newConfigurations.push(newConfig as T);
|
||||
}
|
||||
setConfigurations(newConfigurations);
|
||||
onChange?.(newConfigurations);
|
||||
setIsModalVisible(false);
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []);
|
||||
}, [value]);
|
||||
|
||||
const columns = useMemo(()=>[
|
||||
...configFields.map(({ title, key, renderText }) => ({
|
||||
title:$t(title),
|
||||
dataIndex: key as string,
|
||||
key: key as string,
|
||||
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
|
||||
ellipsis:true
|
||||
})),
|
||||
...(disabled ? []:[{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'action',
|
||||
btnNums:2,
|
||||
render: (_: unknown, record: T) => (
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
...configFields.map(({ title, key, renderText }) => ({
|
||||
title: $t(title),
|
||||
dataIndex: key as string,
|
||||
key: key as string,
|
||||
render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined,
|
||||
ellipsis: true
|
||||
})),
|
||||
...(disabled
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'action',
|
||||
btnNums: 2,
|
||||
render: (_: unknown, record: T) => (
|
||||
<>
|
||||
<div className="flex items-center">
|
||||
<TableBtnWithPermission key="add" disabled={disabled} btnType="edit" onClick={()=>{showModal(record)}} btnTitle='编辑'/>
|
||||
<div className="flex items-center">
|
||||
<TableBtnWithPermission
|
||||
key="add"
|
||||
disabled={disabled}
|
||||
btnType="edit"
|
||||
onClick={() => {
|
||||
showModal(record)
|
||||
}}
|
||||
btnTitle="编辑"
|
||||
/>
|
||||
<Divider key="div1" type="vertical" />
|
||||
<TableBtnWithPermission key="delete" disabled={disabled} btnType="delete" onClick={()=>{handleDelete(record._id || '')}} btnTitle='删除'/>
|
||||
</div>
|
||||
<TableBtnWithPermission
|
||||
key="delete"
|
||||
disabled={disabled}
|
||||
btnType="delete"
|
||||
onClick={() => {
|
||||
handleDelete(record._id || '')
|
||||
}}
|
||||
btnTitle="删除"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
}] )
|
||||
],[state.language, disabled, configFields])
|
||||
)
|
||||
}
|
||||
])
|
||||
],
|
||||
[state.language, disabled, configFields]
|
||||
)
|
||||
|
||||
|
||||
const formItems = useMemo(()=>{
|
||||
return configFields.map(({ title,key, component, required,unRender }) => {
|
||||
return (
|
||||
unRender && unRender(formsValue) ? null :
|
||||
<Form.Item
|
||||
label={$t(title as string)}
|
||||
name={key as string}
|
||||
rules={[{ required}]}
|
||||
>
|
||||
{component}
|
||||
</Form.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
,[formsValue])
|
||||
const formItems = useMemo(() => {
|
||||
return configFields.map(({ title, key, component, required, unRender }) => {
|
||||
return unRender && unRender(formsValue) ? null : (
|
||||
<Form.Item label={$t(title as string)} name={key as string} rules={[{ required }]}>
|
||||
{component}
|
||||
</Form.Item>
|
||||
)
|
||||
})
|
||||
}, [formsValue])
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && (
|
||||
<Button className="" disabled={disabled} onClick={() => showModal()}>
|
||||
{$t('添加配置')}
|
||||
</Button>
|
||||
)}
|
||||
{configurations.length > 0 && (
|
||||
<Table
|
||||
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`}
|
||||
{...tableProps}
|
||||
dataSource={configurations}
|
||||
size="small"
|
||||
columns={columns}
|
||||
rowKey="_id"
|
||||
pagination={false}
|
||||
/>
|
||||
)}
|
||||
<Modal
|
||||
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
|
||||
open={isModalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
maskClosable={false}
|
||||
>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
form={form}
|
||||
name="editableTableWithModal"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
onFieldsChange={() => {
|
||||
setFormsValue(form.getFieldsValue())
|
||||
}}
|
||||
// labelCol={{ span: 7 }}
|
||||
// wrapperCol={{ span: 17}}
|
||||
autoComplete="off"
|
||||
>
|
||||
{formItems}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{!disabled && <Button className="" disabled={disabled} onClick={() => showModal()}>{$t('添加配置')}</Button>}
|
||||
{configurations.length > 0 &&
|
||||
<Table
|
||||
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${className}`} {...tableProps} dataSource={configurations} size="small" columns={columns} rowKey="_id" pagination={false}/>}
|
||||
<Modal
|
||||
title={editingConfig ? $t('编辑配置') : $t('添加配置')}
|
||||
open={isModalVisible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={600}
|
||||
maskClosable={false}
|
||||
|
||||
>
|
||||
<WithPermission access=""><Form form={form} name="editableTableWithModal"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
onFieldsChange={(()=>{
|
||||
setFormsValue(form.getFieldsValue())
|
||||
})}
|
||||
// labelCol={{ span: 7 }}
|
||||
// wrapperCol={{ span: 17}}
|
||||
autoComplete="off">
|
||||
{formItems}
|
||||
</Form></WithPermission>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditableTableWithModal;
|
||||
export default EditableTableWithModal
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from 'react'
|
||||
function ErrorBoundary({ children }) {
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("error", (event) => {
|
||||
setError(event.error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h1>An error occurred</h1>
|
||||
<pre>{error.message}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('error', (event) => {
|
||||
setError(event.error)
|
||||
})
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h1>An error occurred</h1>
|
||||
<pre>{error.message}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
return children
|
||||
}
|
||||
|
||||
export default ErrorBoundary
|
||||
|
||||
@@ -1,66 +1,108 @@
|
||||
|
||||
import { Button, Tag } from "antd"
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { ArrowLeftOutlined, LeftOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales";
|
||||
|
||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { $t } from '@common/locales'
|
||||
import { Button, Tag } from 'antd'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
class InsidePageProps {
|
||||
showBanner?:boolean = true
|
||||
pageTitle:string| React.ReactNode = ''
|
||||
tagList?:Array<{label:string|ReactNode}> = []
|
||||
children:React.ReactNode
|
||||
showBtn?:boolean = false
|
||||
btnTitle?:string = ''
|
||||
description?:string | React.ReactNode= ''
|
||||
onBtnClick?:()=>void
|
||||
backUrl?:string = '/'
|
||||
btnAccess?:string
|
||||
showBorder?:boolean = true
|
||||
className?:string = ''
|
||||
contentClassName?:string=''
|
||||
headerClassName?:string=''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?:boolean = true
|
||||
customBtn?:ReactNode
|
||||
showBanner?: boolean = true
|
||||
pageTitle: string | React.ReactNode = ''
|
||||
tagList?: Array<{ label: string | ReactNode }> = []
|
||||
children: React.ReactNode
|
||||
showBtn?: boolean = false
|
||||
btnTitle?: string = ''
|
||||
description?: string | React.ReactNode = ''
|
||||
onBtnClick?: () => void
|
||||
backUrl?: string = '/'
|
||||
btnAccess?: string
|
||||
showBorder?: boolean = true
|
||||
className?: string = ''
|
||||
contentClassName?: string = ''
|
||||
headerClassName?: string = ''
|
||||
/** 整个页面滚动 */
|
||||
scrollPage?: boolean = true
|
||||
customBtn?: ReactNode
|
||||
}
|
||||
|
||||
const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',headerClassName='',scrollPage=true,customBtn})=>{
|
||||
const navigate = useNavigate();
|
||||
const InsidePage: FC<InsidePageProps> = ({
|
||||
showBanner = true,
|
||||
pageTitle,
|
||||
tagList,
|
||||
showBtn,
|
||||
btnTitle,
|
||||
btnAccess,
|
||||
description,
|
||||
children,
|
||||
onBtnClick,
|
||||
backUrl,
|
||||
showBorder = true,
|
||||
className = '',
|
||||
contentClassName = '',
|
||||
headerClassName = '',
|
||||
scrollPage = true,
|
||||
customBtn
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const goBack = () => {
|
||||
navigate(backUrl || '/');
|
||||
};
|
||||
return (
|
||||
// <div className="h-full flex flex-col flex-1 overflow-hidden bg-[#f7f8fa]">
|
||||
<div className={`h-full flex flex-col flex-1 overflow-hidden ${className}`}>
|
||||
{ showBanner && <div className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-b-[1px] border-solid border-BORDER' : ''} ${headerClassName}`}>
|
||||
<div className="mb-[30px]">
|
||||
{backUrl &&<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button>
|
||||
</div>}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT ">
|
||||
<p className="text-theme text-[26px] ">{pageTitle}</p>
|
||||
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{
|
||||
return ( <Tag key={tag.label as string} bordered={false} >{tag.label}</Tag>)
|
||||
})}
|
||||
</div>
|
||||
{showBtn && <WithPermission access={btnAccess}><Button type="primary" onClick={()=> {
|
||||
onBtnClick&&onBtnClick()
|
||||
}}>{btnTitle}</Button></WithPermission>}
|
||||
{customBtn}
|
||||
</div>
|
||||
<p >
|
||||
{description}
|
||||
</p>
|
||||
const goBack = () => {
|
||||
navigate(backUrl || '/')
|
||||
}
|
||||
return (
|
||||
<div className={`flex overflow-hidden flex-col flex-1 h-full ${className}`}>
|
||||
{showBanner && (
|
||||
<div
|
||||
className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
|
||||
>
|
||||
{!pageTitle && !description && !backUrl && !customBtn ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="mb-[30px]">
|
||||
{backUrl && (
|
||||
<div className="text-[18px] leading-[25px] mb-[12px]">
|
||||
<Button type="text" onClick={goBack}>
|
||||
<ArrowLeftOutlined className="max-h-[14px]" />
|
||||
{$t('返回')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>}
|
||||
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>{children}</div>
|
||||
)}
|
||||
<div className="flex justify-between mb-[20px] items-center ">
|
||||
<div className="flex items-center gap-TAG_LEFT">
|
||||
<div className="text-theme text-[26px] ">{pageTitle}</div>
|
||||
{tagList &&
|
||||
tagList?.length > 0 &&
|
||||
tagList?.map((tag) => {
|
||||
return (
|
||||
<Tag key={tag.label as string} bordered={false}>
|
||||
{tag.label}
|
||||
</Tag>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
{showBtn && (
|
||||
<WithPermission access={btnAccess}>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
onBtnClick && onBtnClick()
|
||||
}}
|
||||
>
|
||||
{btnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
)}
|
||||
{customBtn}
|
||||
</div>
|
||||
<div>{description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default InsidePage
|
||||
export default InsidePage
|
||||
|
||||
@@ -1,72 +1,92 @@
|
||||
import { Dropdown, Row, Col, Button } from 'antd';
|
||||
import i18n from '@common/locales';
|
||||
import { memo, useEffect, useMemo } from 'react';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
|
||||
import { Icon } from '@iconify/react/dist/iconify.js';
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import i18n from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Button, Dropdown } from 'antd'
|
||||
import { memo, useEffect, useMemo } from 'react'
|
||||
|
||||
const LanguageSetting = ({mode = 'light'}:{mode?:'dark'|'light'}) => {
|
||||
const { dispatch,state} = useGlobalContext()
|
||||
const items = [
|
||||
{
|
||||
key: 'en-US',
|
||||
label:<Button key="en" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
English
|
||||
</Button>,
|
||||
title:'English'
|
||||
},
|
||||
{
|
||||
key: 'ja-JP',
|
||||
label: <Button key="jp" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
日本語
|
||||
</Button>,
|
||||
title: '日本語',
|
||||
},
|
||||
{
|
||||
key: 'zh-TW',
|
||||
label: <Button key="tw" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
繁體中文
|
||||
</Button>,
|
||||
title: '繁體中文',
|
||||
},
|
||||
{
|
||||
key: 'zh-CN',
|
||||
label: <Button key="cn" type="text" className="border-none p-0 flex items-center bg-transparent ">
|
||||
简体中文
|
||||
</Button>,
|
||||
title: '简体中文',
|
||||
},
|
||||
];
|
||||
const LanguageItems = [
|
||||
{
|
||||
key: 'en-US',
|
||||
label: (
|
||||
<Button key="en" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
English
|
||||
</Button>
|
||||
),
|
||||
title: 'English'
|
||||
},
|
||||
{
|
||||
key: 'ja-JP',
|
||||
label: (
|
||||
<Button key="jp" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
日本語
|
||||
</Button>
|
||||
),
|
||||
title: '日本語'
|
||||
},
|
||||
{
|
||||
key: 'zh-TW',
|
||||
label: (
|
||||
<Button key="tw" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
繁體中文
|
||||
</Button>
|
||||
),
|
||||
title: '繁體中文'
|
||||
},
|
||||
{
|
||||
key: 'zh-CN',
|
||||
label: (
|
||||
<Button key="cn" type="text" className="flex items-center p-0 bg-transparent border-none">
|
||||
简体中文
|
||||
</Button>
|
||||
),
|
||||
title: '简体中文'
|
||||
}
|
||||
]
|
||||
const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => {
|
||||
const { dispatch, state } = useGlobalContext()
|
||||
|
||||
const langLabel = useMemo(()=>items.find((item) => item?.key === state.language)?.title,[state.language])
|
||||
const langLabel = useMemo(() => LanguageItems.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 });
|
||||
}else{
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang });
|
||||
useEffect(() => {
|
||||
const savedLang = i18n.language || sessionStorage.getItem('i18nextLng')
|
||||
if (savedLang && state.language !== savedLang) {
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang })
|
||||
} else if (!savedLang) {
|
||||
const browserLang = navigator.language
|
||||
const supportedLang = LanguageItems.find((item) => item.key === browserLang) ? browserLang : 'zh-CN'
|
||||
if (state.language === supportedLang) return
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: supportedLang })
|
||||
i18n.changeLanguage(supportedLang)
|
||||
}
|
||||
},[
|
||||
])
|
||||
}, [])
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['hover']}
|
||||
menu={{
|
||||
items,
|
||||
style:{minWidth:'80px'},
|
||||
items: LanguageItems,
|
||||
style: { minWidth: '80px' },
|
||||
onClick: (e) => {
|
||||
const { key } = e;
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: key });
|
||||
i18n.changeLanguage(key);
|
||||
const { key } = e
|
||||
dispatch({ type: 'UPDATE_LANGUAGE', language: key })
|
||||
i18n.changeLanguage(key)
|
||||
sessionStorage.setItem('i18nextLng', key)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Button className={`border-none ${mode==='dark' ? "text-[#333] hover:text-[#333333b3]" : "text-[#ffffffb3] hover:text-[#fff] "}`} type="default" ghost >
|
||||
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-language" width="14" height="14"/>{langLabel}</span>
|
||||
</Button>
|
||||
<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);
|
||||
|
||||
)
|
||||
}
|
||||
export default memo(LanguageSetting)
|
||||
|
||||
@@ -1,275 +1,199 @@
|
||||
|
||||
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 { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { cloneDeep, debounce } from "lodash-es";
|
||||
import { ColumnsType } from "antd/es/table";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
type TransferItem = GetProp<TransferProps, 'dataSource'>[number];
|
||||
import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from 'antd'
|
||||
import { DataNode } from 'antd/es/tree'
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
|
||||
import { ApartmentOutlined, LoadingOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import { ColumnsType } from 'antd/es/table'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
export type TransferTableProps<T> = {
|
||||
request?:(k?:string)=>Promise<{data:T[],success:boolean}>
|
||||
request?: (k?: string) => Promise<{ data: T[]; success: boolean }>
|
||||
columns: ColumnsType<T>
|
||||
primaryKey:string
|
||||
onSelect:(selectedData:T[])=>void
|
||||
tableType?:'member'|'api'
|
||||
disabledData:string[]
|
||||
searchPlaceholder?:string
|
||||
primaryKey: string
|
||||
onSelect: (selectedData: string[]) => void
|
||||
tableType?: 'member' | 'api'
|
||||
disabledData: string[]
|
||||
searchPlaceholder?: string
|
||||
}
|
||||
|
||||
export type TransferTableHandle<T> = {
|
||||
selectedData: () => T[];
|
||||
selectedRowKeys: () => React.Key[];
|
||||
selectedRowKeys: () => React.Key[]
|
||||
}
|
||||
|
||||
interface TreeTransferProps {
|
||||
dataSource: TreeDataNode[];
|
||||
targetKeys: TransferProps['targetKeys'];
|
||||
onChange: TransferProps['onChange'];
|
||||
dataSource: TreeDataNode[]
|
||||
targetKeys: TransferProps['targetKeys']
|
||||
onChange: TransferProps['onChange']
|
||||
}
|
||||
|
||||
// Customize Table Transfer
|
||||
const isChecked = (selectedKeys: React.Key[], eventKey: React.Key) =>
|
||||
selectedKeys.includes(eventKey);
|
||||
|
||||
const generateTree = (
|
||||
treeNodes: TreeDataNode[] = [],
|
||||
checkedKeys: TreeTransferProps['targetKeys'] = [],
|
||||
checkedKeys: TreeTransferProps['targetKeys'] = [],
|
||||
filterUnchecked: boolean = false,
|
||||
disabledData:string[],
|
||||
filteredItems?:Set<string>
|
||||
disabledData: string[],
|
||||
filteredItems?: Set<string>
|
||||
): TreeDataNode[] => {
|
||||
const checkedKeysSet = new Set(checkedKeys);
|
||||
const checkedKeysSet = new Set(checkedKeys)
|
||||
return treeNodes
|
||||
.map(({ children, ...props }) => {
|
||||
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems);
|
||||
const isDisabled = (!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1)
|
||||
? true
|
||||
: (filterUnchecked ? false : checkedKeysSet.has(props.id as string));
|
||||
const hasEnabledChild = childNodes.some(node => !node.disabled);
|
||||
const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems)
|
||||
const isDisabled =
|
||||
!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1
|
||||
? true
|
||||
: filterUnchecked
|
||||
? false
|
||||
: checkedKeysSet.has(props.id as string)
|
||||
const hasEnabledChild = childNodes.some((node) => !node.disabled)
|
||||
|
||||
return {
|
||||
...props,
|
||||
title: <span className="w-full truncate ml-[4px] block">{props.name}</span>,
|
||||
key: props.id,
|
||||
disabled: isDisabled && !hasEnabledChild,
|
||||
children: childNodes,
|
||||
};
|
||||
})
|
||||
.filter(node => {
|
||||
let res:boolean= true
|
||||
if(filterUnchecked){
|
||||
res =(!disabledData || disabledData.indexOf(node.key as string) === -1) && (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0) )
|
||||
children: childNodes
|
||||
}
|
||||
|
||||
if(filterUnchecked && filteredItems &&((filteredItems.size && !filteredItems.has(node.key as string))&& !(node.children && node.children.length > 0) )){
|
||||
})
|
||||
.filter((node) => {
|
||||
let res: boolean = true
|
||||
if (filterUnchecked) {
|
||||
res =
|
||||
(!disabledData || disabledData.indexOf(node.key as string) === -1) &&
|
||||
(checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0))
|
||||
}
|
||||
|
||||
if (
|
||||
filterUnchecked &&
|
||||
filteredItems &&
|
||||
filteredItems.size &&
|
||||
!filteredItems.has(node.key as string) &&
|
||||
!(node.children && node.children.length > 0)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return res
|
||||
}
|
||||
)
|
||||
};
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
const TransferTree = (props)=>{
|
||||
const { direction, token, tableHeight, dataSource, targetKeys, onItemSelect, onItemSelectAll,checkedKey,selectedKeys, filteredItems ,disabledData} = props;
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
|
||||
const MemberTransfer = forwardRef<
|
||||
TransferTableHandle<{ [k: string]: unknown }>,
|
||||
TransferTableProps<{ [k: string]: unknown }>
|
||||
>(<T extends { [k: string]: unknown }>(props: TransferTableProps<T>, ref: Ref<TransferTableHandle<T>>) => {
|
||||
const { request, columns, primaryKey, onSelect, tableType, disabledData = [], searchPlaceholder } = props
|
||||
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([])
|
||||
const [dataSource, setDataSource] = useState<DataNode[]>([])
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const { state } = useGlobalContext()
|
||||
const [expandedKeys, setExpandedKeys] = useState<string[]>([])
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
useEffect(() => {
|
||||
setTargetKeys(disabledData)
|
||||
}, [disabledData])
|
||||
|
||||
const getExpandedKeys = (newData:TreeDataNode[], expandedSet:Set<string> = new Set())=>{
|
||||
newData.forEach((item)=>{
|
||||
if(item.children && item.children.length > 0){
|
||||
expandedSet.add(item.key)
|
||||
getExpandedKeys(item.children,expandedSet)
|
||||
useImperativeHandle(ref, () => ({
|
||||
selectedRowKeys: () => targetKeys
|
||||
}))
|
||||
|
||||
const translatedDataSource = useMemo(() => {
|
||||
const loop = (data: DataNode[]): DataNode[] =>
|
||||
data?.map((item) => {
|
||||
const strTitle: string = item.name === '所有成员' ? ($t(item.name) as string) : (item.name as string)
|
||||
const index = strTitle.indexOf(searchWord)
|
||||
const beforeStr = strTitle.substring(0, index)
|
||||
const afterStr = strTitle.slice(index + searchWord.length)
|
||||
const title =
|
||||
index > -1 ? (
|
||||
<span className="w-[calc(100%-16px)] truncate" title={strTitle}>
|
||||
{beforeStr}
|
||||
<span className="text-theme">{searchWord}</span>
|
||||
{afterStr}
|
||||
</span>
|
||||
) : (
|
||||
<span className="w-[calc(100%-16px)] truncate" title={`${strTitle}`}>
|
||||
{strTitle}
|
||||
</span>
|
||||
)
|
||||
if (item.children) {
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
disableCheckbox: disabledData.indexOf(item.key as string) !== -1,
|
||||
icon: <ApartmentOutlined />,
|
||||
children: loop(item.children as T[])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...item,
|
||||
title,
|
||||
icon: <UserOutlined />,
|
||||
isLeaf: true,
|
||||
disableCheckbox: disabledData.indexOf(item.key as string) !== -1
|
||||
}
|
||||
})
|
||||
return loop(dataSource)
|
||||
}, [dataSource, state.language, searchWord])
|
||||
|
||||
const getInitExpandKeys = (data: T[], expandKeys: string[] = []) => {
|
||||
data.forEach((item) => {
|
||||
if (item.children?.length) {
|
||||
expandKeys.push(item.key as string)
|
||||
getInitExpandKeys(item.children, expandKeys)
|
||||
}
|
||||
})
|
||||
return expandedSet
|
||||
return expandKeys
|
||||
}
|
||||
|
||||
const treeData:TreeDataNode[] = useMemo(()=>{
|
||||
const filteredSet = filteredItems && filteredItems.length > 0 ? new Set(filteredItems.map((x)=>x.id)) : new Set()
|
||||
const res = dataSource && dataSource.length > 0 ? generateTree(dataSource, targetKeys,direction === 'right',disabledData,filteredSet) : []
|
||||
setExpandedKeys(Array.from(getExpandedKeys(res)))
|
||||
return res
|
||||
},[
|
||||
dataSource, targetKeys,direction ,disabledData,filteredItems
|
||||
])
|
||||
const getDataSource = () => {
|
||||
setLoading(true)
|
||||
request &&
|
||||
request()
|
||||
.then((res) => {
|
||||
const { data, success } = res
|
||||
setDataSource(success ? data : [])
|
||||
setExpandedKeys(getInitExpandKeys(success ? data : []))
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => {
|
||||
setExpandedKeys(expandedKeysValue as string[]);
|
||||
};
|
||||
useEffect(() => {
|
||||
getDataSource()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
<div style={{ padding: token.paddingXS }}>
|
||||
<Tree
|
||||
className="icon-tree"
|
||||
blockNode
|
||||
checkable
|
||||
showIcon
|
||||
checkedKeys={direction === 'left' ? Array.from(new Set([...checkedKey,...disabledData])) : selectedKeys }
|
||||
defaultExpandAll
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={onExpand}
|
||||
height={tableHeight}
|
||||
icon={(props)=> { return (props.type === 'member' ? <UserOutlined /> :<ApartmentOutlined /> )} }
|
||||
treeData={treeData}
|
||||
onCheck={(_checkedKeys, e:{checked: boolean, checkedNodes, node, event, halfCheckedKeys}) => {
|
||||
if(e.checked){
|
||||
onItemSelectAll( _checkedKeys, e.checked);
|
||||
}else{
|
||||
const checkedKeyArrFromTree = e.checkedNodes.map(node => node.key)
|
||||
onItemSelectAll((checkedKey as string[]).filter(key => checkedKeyArrFromTree.indexOf(key) === -1),e.checked)
|
||||
}
|
||||
}}
|
||||
onSelect={(_, { node: { key } }) => {
|
||||
onItemSelect(key as string, !isChecked(checkedKey, key));
|
||||
}}
|
||||
/>
|
||||
<div ref={parentRef}>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className="">
|
||||
<Input
|
||||
className="mb-[10px]"
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={(e) => setSearchWord(e.target.value)}
|
||||
value={searchWord}
|
||||
/>
|
||||
<>
|
||||
{translatedDataSource && translatedDataSource.length > 0 ? (
|
||||
<Tree
|
||||
checkable
|
||||
expandedKeys={expandedKeys}
|
||||
checkedKeys={targetKeys}
|
||||
selectable={false}
|
||||
onCheck={(e) => {
|
||||
setTargetKeys(e)
|
||||
onSelect((e as string[])?.filter((x) => disabledData.indexOf(x as string) === -1) || [])
|
||||
}}
|
||||
onExpand={setExpandedKeys}
|
||||
treeData={translatedDataSource}
|
||||
blockNode
|
||||
showIcon
|
||||
/>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</>
|
||||
</Spin>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const MemberTransfer= forwardRef<TransferTableHandle<{[k:string]:unknown}>, TransferTableProps<{[k:string]:unknown}>>(
|
||||
<T extends {[k:string]:unknown}>(props: TransferTableProps<T>, ref:Ref<TransferTableHandle<T>>) => {
|
||||
const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props
|
||||
const [tableHeight, setTableHeight] = useState(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3);
|
||||
const [targetKeys, setTargetKeys] = useState<TreeTransferProps['targetKeys']>([]);
|
||||
const [dataSource, setDataSource] = useState<DataNode[] >([])
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
useEffect(()=>{
|
||||
setTargetKeys(disabledData)
|
||||
},[disabledData])
|
||||
|
||||
useImperativeHandle(ref, () =>({
|
||||
selectedData: () => dataSource,
|
||||
selectedRowKeys: () => targetKeys,}))
|
||||
|
||||
const onChange: TreeTransferProps['onChange'] = (keys) => {
|
||||
onSelect?.(new Set(keys))
|
||||
setTargetKeys(Array.from(new Set(keys)));
|
||||
};
|
||||
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const transferDataSource: TransferItem[] = useMemo(()=>{
|
||||
function flatten(list: TreeDataNode[] = [], res:TransferItem[]) {
|
||||
list.forEach((item) => {
|
||||
res.push({...item, title:item.title === '所有成员' ? $t((item as unknown as {title:string}).title):item.title }as TransferItem);
|
||||
flatten(item.children,res);
|
||||
});
|
||||
}
|
||||
const res:TransferItem[] =[]
|
||||
flatten(dataSource,res);
|
||||
return res
|
||||
},[
|
||||
dataSource, state.language
|
||||
])
|
||||
|
||||
|
||||
const translatedDataSource = useMemo(()=>dataSource.map((item)=>({
|
||||
...item,
|
||||
name:item.name === '所有成员' ? $t((item as unknown as {name:string}).name):item.name,
|
||||
})),[dataSource, state.language])
|
||||
|
||||
|
||||
|
||||
let memo: Record<string, boolean> = {};
|
||||
|
||||
const handlerFilterOption = (inputValue: string, item: any, parentResult: boolean = false, childrenSet: Set<string> = new Set()): boolean => {
|
||||
const cacheKey = `${inputValue}_${item.key}`;
|
||||
if (memo[cacheKey]) {
|
||||
return memo[cacheKey];
|
||||
}
|
||||
|
||||
childrenSet.add(item.key);
|
||||
let result = item.title.includes(inputValue) || parentResult
|
||||
if (item.children) {
|
||||
for (const child of item.children) {
|
||||
if (handlerFilterOption(inputValue, child, result,childrenSet)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result) {
|
||||
memo[cacheKey] = result;
|
||||
childrenSet.forEach((key) => {
|
||||
memo[`${inputValue}_${key}`] = result;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const getDataSource = ()=>{
|
||||
setLoading(true)
|
||||
request && request().then((res)=>{
|
||||
const {data,success} = res
|
||||
setDataSource(success? data : [])
|
||||
}).finally(()=>{setLoading(false)})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getDataSource()
|
||||
const handleResize = () => {
|
||||
setTableHeight(window.innerHeight * 80 / 100 - 64 - 72 - 56 - 16 -3)
|
||||
};
|
||||
|
||||
const debouncedHandleResize = debounce(handleResize, 200);
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', debouncedHandleResize);
|
||||
handleResize();
|
||||
return () => {
|
||||
window.removeEventListener('resize', debouncedHandleResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={parentRef}>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} className=''>
|
||||
<Transfer
|
||||
showSearch
|
||||
onSearch={(dir)=>{
|
||||
memo = {};
|
||||
}}
|
||||
listStyle={{width:'408px'}}
|
||||
disabledData={disabledData}
|
||||
filterOption={(inputValue: string, item: any) => handlerFilterOption(inputValue, item)}
|
||||
targetKeys={targetKeys}
|
||||
dataSource={transferDataSource}
|
||||
className="tree-transfer"
|
||||
render={(item) => item.title!}
|
||||
showSelectAll={false}
|
||||
onChange={onChange}
|
||||
titles={['','']}
|
||||
>
|
||||
{({ direction, onItemSelect, selectedKeys,onItemSelectAll ,filteredItems}) => {
|
||||
const treeProps = {
|
||||
dataSource:translatedDataSource, direction, onItemSelect, selectedKeys,onItemSelectAll ,filteredItems,token,tableHeight,targetKeys,disabledData
|
||||
}
|
||||
if (direction === 'left') {
|
||||
const checkedKey = [...selectedKeys, ...targetKeys as string[]];
|
||||
return (
|
||||
<TransferTree {...treeProps} checkedKey={checkedKey} />
|
||||
);
|
||||
}
|
||||
if(direction === 'right'){
|
||||
const checkedKey = [...selectedKeys,...targetKeys as string[]];
|
||||
return (
|
||||
<TransferTree {...treeProps} checkedKey={checkedKey} />
|
||||
);
|
||||
}
|
||||
}}
|
||||
</Transfer>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
|
||||
export default MemberTransfer;
|
||||
export default MemberTransfer
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Result, Skeleton } from 'antd'
|
||||
|
||||
const NotFound: React.FC = () => {
|
||||
const [showPage, setShowPage] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => setShowPage(true), 1000)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
|
||||
{showPage ? (
|
||||
<Result className="w-full" status="404" title="404" subTitle="Sorry, the page you visited does not exist." />
|
||||
) : (
|
||||
<Skeleton active />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFound
|
||||
@@ -1,243 +1,370 @@
|
||||
|
||||
import {Button, Dropdown, Input, MenuProps, TablePaginationConfig} from 'antd';
|
||||
import {ChangeEvent, RefAttributes, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
|
||||
import type {ActionType, ParamsType, ProColumns, ProTableProps} from '@ant-design/pro-components';
|
||||
import {
|
||||
DragSortTable,
|
||||
ProTable,
|
||||
} from '@ant-design/pro-components';
|
||||
import './PageList.module.css'
|
||||
import {SearchOutlined} from "@ant-design/icons";
|
||||
import { SearchOutlined } from '@ant-design/icons'
|
||||
import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components'
|
||||
import { DragSortTable, ProTable } from '@ant-design/pro-components'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { withMinimumDelay } from '@common/utils/ux'
|
||||
import { Button, Dropdown, Input, MenuProps, TablePaginationConfig } from 'antd'
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'
|
||||
import { debounce } from 'lodash-es'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission';
|
||||
import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions';
|
||||
import { withMinimumDelay } from '@common/utils/ux';
|
||||
import {
|
||||
ChangeEvent,
|
||||
RefAttributes,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
import './PageList.module.css'
|
||||
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T , ValueType> & {btnNums? : number}
|
||||
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T, ValueType> & { btnNums?: number }
|
||||
|
||||
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
|
||||
id?:string
|
||||
columns: PageProColumns<T,'text'>[]
|
||||
request?:(params: (ParamsType & {pageSize?: number | undefined, current?: number | undefined, keyword?: string | undefined}), sorter: unknown, filter: unknown)=>Promise<{data:T[], success:boolean}>
|
||||
dropMenu?:MenuProps
|
||||
searchPlaceholder?:string
|
||||
showPagination?:boolean
|
||||
primaryKey?:string
|
||||
addNewBtnTitle?:string
|
||||
addNewBtnAccess?:string
|
||||
tableClickAccess?:string
|
||||
onAddNewBtnClick?:()=>void
|
||||
beforeSearchNode?:React.ReactNode[]
|
||||
onSearchWordChange?:(e:ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?:React.ReactNode[]
|
||||
dragSortKey?:string
|
||||
onDragSortEnd?:(beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?:string
|
||||
dataSource?:T[]
|
||||
onRowClick?:(record:T)=>void
|
||||
showColSetting?:boolean
|
||||
minVirtualHeight?:number
|
||||
besidesTableHeight?:number
|
||||
noTop?:boolean
|
||||
tableClass?:string
|
||||
tableTitleClass?:string
|
||||
addNewBtnWrapperClass?:string
|
||||
delayLoading?:boolean
|
||||
noScroll?:boolean
|
||||
interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<ActionType> {
|
||||
id?: string
|
||||
columns: PageProColumns<T, 'text'>[]
|
||||
request?: (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sorter: unknown,
|
||||
filter: unknown
|
||||
) => Promise<{ data: T[]; success: boolean }>
|
||||
dropMenu?: MenuProps
|
||||
searchPlaceholder?: string
|
||||
showPagination?: boolean
|
||||
primaryKey?: string
|
||||
addNewBtnTitle?: string
|
||||
addNewBtnAccess?: string
|
||||
tableClickAccess?: string
|
||||
onAddNewBtnClick?: () => void
|
||||
beforeSearchNode?: React.ReactNode[]
|
||||
onSearchWordChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
afterNewBtn?: React.ReactNode[]
|
||||
dragSortKey?: string
|
||||
onDragSortEnd?: (beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise<void>
|
||||
tableTitle?: string
|
||||
dataSource?: T[]
|
||||
onRowClick?: (record: T) => void
|
||||
showColSetting?: boolean
|
||||
minVirtualHeight?: number
|
||||
besidesTableHeight?: number
|
||||
noTop?: boolean
|
||||
tableClass?: string
|
||||
tableTitleClass?: string
|
||||
addNewBtnWrapperClass?: string
|
||||
delayLoading?: boolean
|
||||
noScroll?: boolean
|
||||
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
|
||||
manualReloadTable?:()=>void
|
||||
manualReloadTable?: () => void
|
||||
}
|
||||
|
||||
|
||||
|
||||
const PageList = <T extends Record<string, unknown>>(props: React.PropsWithChildren<PageListProps<T>>,ref: React.Ref<ActionType>) => {
|
||||
const {id,columns,request,dropMenu,searchPlaceholder,showPagination=true,primaryKey='id',addNewBtnTitle,addNewBtnAccess,tableClickAccess,tableClass,onAddNewBtnClick,beforeSearchNode,onSearchWordChange,manualReloadTable,afterNewBtn,dragSortKey,onDragSortEnd,tableTitle,rowSelection,onChange,dataSource,onRowClick,showColSetting=false,minVirtualHeight,noTop,addNewBtnWrapperClass = '',tableTitleClass,delayLoading = true,besidesTableHeight, noScroll} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight);
|
||||
const [tableWidth, setTableWidth] = useState<number|undefined>(undefined);
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [allowTableClick,setAllowTableClick] = useState<boolean>(false)
|
||||
const {accessData,checkPermission,accessInit,state} = useGlobalContext()
|
||||
const PageList = <T extends Record<string, unknown>>(
|
||||
props: React.PropsWithChildren<PageListProps<T>>,
|
||||
ref: React.Ref<ActionType>
|
||||
) => {
|
||||
const {
|
||||
id,
|
||||
columns,
|
||||
request,
|
||||
dropMenu,
|
||||
searchPlaceholder,
|
||||
showPagination = true,
|
||||
primaryKey = 'id',
|
||||
addNewBtnTitle,
|
||||
addNewBtnAccess,
|
||||
tableClickAccess,
|
||||
tableClass,
|
||||
onAddNewBtnClick,
|
||||
beforeSearchNode,
|
||||
onSearchWordChange,
|
||||
manualReloadTable,
|
||||
afterNewBtn,
|
||||
dragSortKey,
|
||||
onDragSortEnd,
|
||||
tableTitle,
|
||||
rowSelection,
|
||||
onChange,
|
||||
dataSource,
|
||||
onRowClick,
|
||||
showColSetting = false,
|
||||
minVirtualHeight,
|
||||
noTop,
|
||||
addNewBtnWrapperClass = '',
|
||||
tableTitleClass,
|
||||
delayLoading = true,
|
||||
besidesTableHeight,
|
||||
noScroll
|
||||
} = props
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight)
|
||||
const [tableWidth, setTableWidth] = useState<number | undefined>(undefined)
|
||||
const actionRef = useRef<ActionType>()
|
||||
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
|
||||
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
|
||||
const [minTableWidth, setMinTableWidth] = useState<number>(0)
|
||||
|
||||
// 使用useImperativeHandle来自定义暴露给父组件的实例值
|
||||
useImperativeHandle(ref, () => actionRef.current!);
|
||||
useImperativeHandle(ref, () => actionRef.current!)
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
actionRef?.current?.reload?.()
|
||||
},[state.language])
|
||||
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!tableClickAccess) return true
|
||||
return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[allowTableClick, accessData,accessInit])
|
||||
}, [state.language])
|
||||
|
||||
useEffect(()=>{
|
||||
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
|
||||
},[accessData])
|
||||
|
||||
const resizeObserverRef = useRef<ResizeObserver |null >(null);
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!tableClickAccess) return true
|
||||
return checkPermission(tableClickAccess as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [allowTableClick, accessData, accessInit])
|
||||
|
||||
useEffect(() => {
|
||||
tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true)
|
||||
}, [accessData])
|
||||
|
||||
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (parentRef.current && !noScroll) {
|
||||
const res = parentRef.current.getBoundingClientRect();
|
||||
const height = res.height - ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) +( besidesTableHeight ?? 0) + 1); // 减去顶部按钮、底部分页、表头高度
|
||||
setTableWidth(minTableWidth - 5> res.width ? minTableWidth : undefined);
|
||||
height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight));
|
||||
const res = parentRef.current.getBoundingClientRect()
|
||||
const height =
|
||||
res.height -
|
||||
((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) + (besidesTableHeight ?? 0) + 1) // 减去顶部按钮、底部分页、表头高度
|
||||
setTableWidth(minTableWidth - 5 > res.width ? minTableWidth : undefined)
|
||||
height &&
|
||||
setTableHeight(
|
||||
minVirtualHeight === undefined ? height : height > minVirtualHeight ? height : minVirtualHeight
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const debouncedHandleResize = debounce(handleResize, 200);
|
||||
const debouncedHandleResize = debounce(handleResize, 200)
|
||||
|
||||
if (!resizeObserverRef.current && !noScroll) {
|
||||
// 创建一个 ResizeObserver 来监听高度变化,只创建一次
|
||||
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize);
|
||||
resizeObserverRef.current = new ResizeObserver(debouncedHandleResize)
|
||||
// 开始监听
|
||||
if (parentRef.current && !minVirtualHeight) {
|
||||
resizeObserverRef.current.observe(parentRef.current);
|
||||
resizeObserverRef.current.observe(parentRef.current)
|
||||
}
|
||||
}
|
||||
|
||||
// 在 minTableWidth 变化时手动触发 handleResize
|
||||
handleResize();
|
||||
handleResize()
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (resizeObserverRef.current) {
|
||||
resizeObserverRef.current.disconnect();
|
||||
resizeObserverRef.current = null;
|
||||
resizeObserverRef.current.disconnect()
|
||||
resizeObserverRef.current = null
|
||||
}
|
||||
};
|
||||
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]); // 将相关依赖项作为 useEffect 的依赖项
|
||||
}
|
||||
}, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]) // 将相关依赖项作为 useEffect 的依赖项
|
||||
|
||||
|
||||
|
||||
|
||||
const newColumns = useMemo(()=>{
|
||||
let width:number = 0
|
||||
const res = columns?.map(
|
||||
(x, index)=>{
|
||||
const sorter = localStorage.getItem(`${id}_sorter`)
|
||||
const filters = localStorage.getItem(`${id}_filters`)
|
||||
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
|
||||
if(sorter && x.sorter){
|
||||
const sorterObj = JSON.parse(sorter)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
|
||||
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
|
||||
// x.showSorterTooltip = {target:'sorter-icon'}
|
||||
}
|
||||
if(filters && x.filters){
|
||||
const filtersObj = JSON.parse(filters)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex
|
||||
x.defaultFilteredValue = filtersObj?.[xName as string]
|
||||
}
|
||||
if((index === columns.length -1 || x.key === 'option') && x.btnNums){
|
||||
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
|
||||
x.width = Math.max(optionWidth, 54)
|
||||
}
|
||||
width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100))
|
||||
return x})
|
||||
setMinTableWidth(width)
|
||||
const newColumns = useMemo(() => {
|
||||
let width: number = 0
|
||||
const res = columns?.map((x, index) => {
|
||||
const sorter = localStorage.getItem(`${id}_sorter`)
|
||||
const filters = localStorage.getItem(`${id}_filters`)
|
||||
x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email')
|
||||
if (sorter && x.sorter) {
|
||||
const sorterObj = JSON.parse(sorter)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
|
||||
x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined
|
||||
// x.showSorterTooltip = {target:'sorter-icon'}
|
||||
}
|
||||
if (filters && x.filters) {
|
||||
const filtersObj = JSON.parse(filters)
|
||||
const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex
|
||||
x.defaultFilteredValue = filtersObj?.[xName as string]
|
||||
}
|
||||
if ((index === columns.length - 1 || x.key === 'option') && x.btnNums) {
|
||||
const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21
|
||||
x.width = Math.max(optionWidth, 54)
|
||||
}
|
||||
width += Number(x.width ?? (x.filters || x.sorter ? 120 : 100))
|
||||
return x
|
||||
})
|
||||
setMinTableWidth(width)
|
||||
return res
|
||||
},[columns])
|
||||
}, [columns])
|
||||
|
||||
const headerTitle = ()=>{
|
||||
const headerTitle = () => {
|
||||
return (
|
||||
<>{
|
||||
tableTitle ? <span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span> : (
|
||||
addNewBtnTitle ? <WithPermission access={addNewBtnAccess} ><Button type="primary" className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`} onClick={onAddNewBtnClick}>{addNewBtnTitle}</Button></WithPermission> : undefined
|
||||
)
|
||||
|
||||
}
|
||||
{afterNewBtn ? afterNewBtn as React.ReactNode[] :undefined}
|
||||
</>
|
||||
)
|
||||
<>
|
||||
{tableTitle ? (
|
||||
<span className={`text-[30px] leading-[42px] my-mbase pl-[20px] ${tableTitleClass}`}>{tableTitle}</span>
|
||||
) : addNewBtnTitle ? (
|
||||
<WithPermission access={addNewBtnAccess}>
|
||||
<Button
|
||||
type="primary"
|
||||
className={`mr-btnrbase my-btnbase ${addNewBtnWrapperClass}`}
|
||||
onClick={onAddNewBtnClick}
|
||||
>
|
||||
{addNewBtnTitle}
|
||||
</Button>
|
||||
</WithPermission>
|
||||
) : undefined}
|
||||
{afterNewBtn ? (afterNewBtn as React.ReactNode[]) : undefined}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const requestWithDelay = (params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined;}, sort: unknown, filter: unknown) => {
|
||||
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false? 0 : undefined);
|
||||
};
|
||||
const getTableActions = () => {
|
||||
return [
|
||||
...(beforeSearchNode ? [beforeSearchNode] : []),
|
||||
...(searchPlaceholder
|
||||
? [
|
||||
<Input
|
||||
key="search-input"
|
||||
className="my-btnbase ml-btnbase"
|
||||
onChange={onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined}
|
||||
onPressEnter={() => {
|
||||
if (manualReloadTable) {
|
||||
manualReloadTable()
|
||||
return
|
||||
}
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reset?.()
|
||||
actionRef.current.reload?.()
|
||||
}
|
||||
}}
|
||||
allowClear
|
||||
placeholder={searchPlaceholder}
|
||||
prefix={
|
||||
<SearchOutlined
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
if (actionRef.current) {
|
||||
actionRef.current.reset?.()
|
||||
actionRef.current.reload?.()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
|
||||
const requestWithDelay = (
|
||||
params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined },
|
||||
sort: unknown,
|
||||
filter: unknown
|
||||
) => {
|
||||
return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false ? 0 : undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={parentRef} className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag':''} ${tableClass ?? ''}`}style={{ height: '100%' }}>
|
||||
{dragSortKey? <DragSortTable<T>
|
||||
<div
|
||||
ref={parentRef}
|
||||
className={`eo_page_list bg-MAIN_BG ${dragSortKey ? 'eo_page_drag' : ''} ${tableClass ?? ''}`}
|
||||
style={{ height: '100%' }}
|
||||
>
|
||||
{dragSortKey ? (
|
||||
<DragSortTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
rowKey={primaryKey}
|
||||
search={false}
|
||||
pagination={false}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
request={request}
|
||||
dragSortKey={dragSortKey}
|
||||
onDragSortEnd={onDragSortEnd}
|
||||
scroll={noScroll ? undefined :{ y: tableHeight }}
|
||||
scroll={noScroll ? undefined : { y: tableHeight }}
|
||||
options={{
|
||||
reload: false,
|
||||
density: false,
|
||||
setting: false,
|
||||
setting: false
|
||||
}}
|
||||
headerTitle={
|
||||
headerTitle()
|
||||
}
|
||||
/> : <ProTable<T>
|
||||
toolbar={{
|
||||
actions: getTableActions()
|
||||
}}
|
||||
headerTitle={headerTitle()}
|
||||
/>
|
||||
) : (
|
||||
<ProTable<T>
|
||||
actionRef={actionRef}
|
||||
columns={newColumns}
|
||||
virtual
|
||||
scroll={noScroll ? undefined : {x:tableWidth,y: tableHeight }}
|
||||
scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
|
||||
size="middle"
|
||||
rowSelection={rowSelection}
|
||||
tableAlertRender={false}
|
||||
tableAlertOptionRender={false}
|
||||
request={request ? requestWithDelay : undefined}
|
||||
toolBarRender={() => [
|
||||
dropMenu ? (<Dropdown
|
||||
key="menu"
|
||||
menu={dropMenu}
|
||||
>
|
||||
<Button>
|
||||
筛选
|
||||
</Button>
|
||||
</Dropdown>):null,
|
||||
dropMenu ? (
|
||||
<Dropdown key="menu" menu={dropMenu}>
|
||||
<Button>筛选</Button>
|
||||
</Dropdown>
|
||||
) : null
|
||||
]}
|
||||
toolbar={{
|
||||
actions:[...[beforeSearchNode],...[searchPlaceholder?<Input className="my-btnbase ml-btnbase" onChange={ onSearchWordChange ? (e) => debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>manualReloadTable ? manualReloadTable():actionRef.current?.reload?.()} allowClear placeholder={searchPlaceholder} prefix={<SearchOutlined className="cursor-pointer" onClick={()=>{actionRef.current?.reload?.()}}/>}/>:null]],
|
||||
actions: getTableActions()
|
||||
}}
|
||||
options={{
|
||||
reload: false,
|
||||
density: false,
|
||||
setting: showColSetting ? {
|
||||
draggable:false,
|
||||
showListItemOption:false
|
||||
} :false,
|
||||
setting: showColSetting
|
||||
? {
|
||||
draggable: false,
|
||||
showListItemOption: false
|
||||
}
|
||||
: false
|
||||
}}
|
||||
showSorterTooltip={false}
|
||||
columnsState={{persistenceType:'localStorage',persistenceKey:id}}
|
||||
pagination={showPagination ? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size:'default'
|
||||
}:false}
|
||||
columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
|
||||
pagination={
|
||||
showPagination
|
||||
? {
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
size: 'default'
|
||||
}
|
||||
: false
|
||||
}
|
||||
onChange={(
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue | null>,
|
||||
sorter: SorterResult<T> | SorterResult<T>[],
|
||||
extra: TableCurrentDataSource<T>
|
||||
) => {
|
||||
localStorage.setItem(`${id}_filters`, JSON.stringify(filters))
|
||||
!Array.isArray(sorter) &&
|
||||
localStorage.setItem(
|
||||
`${id}_sorter`,
|
||||
JSON.stringify({ columnKey: sorter?.columnKey, order: sorter?.order })
|
||||
)
|
||||
onChange?.(pagination, filters, sorter, extra)
|
||||
}}
|
||||
rowKey={primaryKey}
|
||||
onChange={(pagination: TablePaginationConfig, filters: Record<string, FilterValue | null>, sorter: SorterResult<T> | SorterResult<T>[],extra:TableCurrentDataSource<T>) =>{
|
||||
localStorage.setItem(`${id}_filters`,JSON.stringify(filters))
|
||||
!Array.isArray(sorter) && localStorage.setItem(`${id}_sorter`,JSON.stringify({columnKey:sorter?.columnKey, order: sorter?.order}))
|
||||
onChange?.(pagination,filters,sorter,extra)}}
|
||||
dataSource={dataSource}
|
||||
search={false}
|
||||
headerTitle={
|
||||
headerTitle()
|
||||
headerTitle={headerTitle()}
|
||||
onRow={
|
||||
onRowClick && allowTableClick
|
||||
? (record) => ({
|
||||
onClick: () => {
|
||||
onRowClick(record)
|
||||
}
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
onRow={onRowClick && allowTableClick ? (record) => ({
|
||||
onClick: () => {
|
||||
onRowClick(record);
|
||||
}
|
||||
}):undefined}
|
||||
rowClassName={()=>onRowClick && allowTableClick ?"cursor-pointer":''}
|
||||
/>}
|
||||
rowClassName={() => (onRowClick && allowTableClick ? 'cursor-pointer' : '')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(PageList) as <T extends Record<string,unknown>>(props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }) => ReturnType<typeof PageList>;
|
||||
export default forwardRef(PageList) as <T extends Record<string, unknown>>(
|
||||
props: React.PropsWithChildren<PageListProps<T>> & { ref?: React.Ref<ActionType> }
|
||||
) => ReturnType<typeof PageList>
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { App, Form, Input, Row, Table } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { PolicyPublishModalHandle, PolicyPublishModalProps } from '@common/const/type'
|
||||
|
||||
export const PolicyPublishModalContent = forwardRef<PolicyPublishModalHandle, PolicyPublishModalProps>((props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { data } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const publish: () => Promise<boolean | string | Record<string, unknown>> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
const body = { ...value, source: data.source }
|
||||
fetchData<BasicResponse<null>>('strategy/global/data-masking/publish', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoTransformKeys: ['versionName']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
publish
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue(data)
|
||||
}, [data])
|
||||
|
||||
const translatedPolicyColumns = useMemo(
|
||||
() =>
|
||||
PolicyPublishColumns.map((x) => ({
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item label={$t('发布名称')} name="versionName" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={$t('描述')} name="desc">
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('策略列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedPolicyColumns}
|
||||
bordered={true}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
dataSource={data.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
{!data?.isPublish && data?.unpublishMsg && <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
|
||||
</Row>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
})
|
||||
+398
-218
@@ -1,231 +1,402 @@
|
||||
import {App, Col, Form, Input, Row, Table, Tooltip} from "antd";
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react";
|
||||
import {PublishApprovalInfoType, PublishApprovalModalHandle, PublishApprovalModalProps, PublishVersionTableListItem} from "@common/const/approval/type.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR} from "@common/const/const.tsx";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx";
|
||||
import { $t } from "@common/locales";
|
||||
import { ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline";
|
||||
import { App, Col, Form, Input, Row, Table, Tooltip } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'
|
||||
import {
|
||||
PublishApprovalInfoType,
|
||||
PublishApprovalModalHandle,
|
||||
PublishApprovalModalProps,
|
||||
PublishVersionTableListItem
|
||||
} from '@common/const/approval/type.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import {
|
||||
BasicResponse,
|
||||
FORM_ERROR_TIPS,
|
||||
PLACEHOLDER,
|
||||
RESPONSE_TIPS,
|
||||
STATUS_CODE,
|
||||
STATUS_COLOR
|
||||
} from '@common/const/const.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from '@core/const/system/const.tsx'
|
||||
import { $t } from '@common/locales'
|
||||
import {
|
||||
ApprovalPolicyColumns,
|
||||
ApprovalRouteColumns,
|
||||
ApprovalStatusColorClass,
|
||||
ApprovalUpstreamColumns,
|
||||
ChangeTypeEnum
|
||||
} from '@common/const/approval/const'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { SystemInsidePublishOnlineItems } from '@core/pages/system/publish/SystemInsidePublishOnline'
|
||||
|
||||
|
||||
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle,PublishApprovalModalProps>((props, ref) => {
|
||||
export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle, PublishApprovalModalProps>(
|
||||
(props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const { type,data,insidePage = false, serviceType = 'rest', serviceId, teamId} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const {state} = useGlobalContext()
|
||||
const { type, data, insidePage = false, serviceType = 'rest', serviceId, teamId } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
|
||||
if(type === 'view'){
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
if (type === 'view') {
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}
|
||||
])
|
||||
form.scrollToField('opinion')
|
||||
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
}
|
||||
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`, {
|
||||
method: 'PUT',
|
||||
eoBody: { comments: value.opinion },
|
||||
eoParams: { id: data!.id, project: serviceId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
return form.validateFields().then((value)=>{
|
||||
if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){
|
||||
form.setFields([{
|
||||
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}])
|
||||
form.scrollToField('opinion')
|
||||
return Promise.reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
}
|
||||
return fetchData<BasicResponse<null>>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`,{method: 'PUT',eoBody:({comments:value.opinion}), eoParams:{id:data!.id, project:serviceId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> Promise.reject(errorInfo))
|
||||
}).catch((err)=> {form.scrollToField(err.errorFields[0].name[0]); return Promise.reject(err)})
|
||||
}
|
||||
|
||||
const publish:(notSave?:boolean)=>Promise<boolean | string | Record<string, unknown>> = (notSave)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then((value)=>{
|
||||
const body = {...value, ...(type === 'publish'&&{release:data.id})}
|
||||
fetchData<BasicResponse<null>>(
|
||||
notSave ? 'service/publish/apply' : 'service/publish/release/do',{method: 'POST',eoBody:body, eoParams:{service:serviceId, team:teamId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const online:()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validateFields().then(()=>{
|
||||
fetchData<BasicResponse<null>>('service/publish/execute',{method: 'PUT', eoParams:{project:serviceId,id:(data as PublishVersionTableListItem).flowId},eoTransformKeys:['versionRemark']}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save,
|
||||
publish,
|
||||
online
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => Promise.reject(errorInfo))
|
||||
})
|
||||
.catch((err) => {
|
||||
form.scrollToField(err.errorFields[0].name[0])
|
||||
return Promise.reject(err)
|
||||
})
|
||||
}
|
||||
|
||||
const publish: (notSave?: boolean) => Promise<boolean | string | Record<string, unknown>> = (notSave) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
const body = { ...value, ...(type === 'publish' && { release: data.id }) }
|
||||
fetchData<BasicResponse<null>>(notSave ? 'service/publish/apply' : 'service/publish/release/do', {
|
||||
method: 'POST',
|
||||
eoBody: body,
|
||||
eoParams: { service: serviceId, team: teamId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(response)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
const online: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
fetchData<BasicResponse<null>>('service/publish/execute', {
|
||||
method: 'PUT',
|
||||
eoParams: { project: serviceId, id: (data as PublishVersionTableListItem).flowId },
|
||||
eoTransformKeys: ['versionRemark']
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save,
|
||||
publish,
|
||||
online
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
}, [])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(
|
||||
() =>
|
||||
ApprovalUpstreamColumns.map((x) => ({
|
||||
...x,
|
||||
...(x.dataIndex === 'type'
|
||||
? {
|
||||
valueEnum: {
|
||||
static: {
|
||||
text: $t('静态上游')
|
||||
}
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
...(x.dataIndex === 'change'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
{entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({ opinion:'',...data})
|
||||
},[])
|
||||
|
||||
const translatedUpstreamColumns = useMemo(()=>ApprovalUpstreamColumns.map((x)=>({
|
||||
...x,
|
||||
...(x.dataIndex === 'type' ? {valueEnum:{
|
||||
'static':{
|
||||
text:$t('静态上游')
|
||||
}
|
||||
}}:{}),
|
||||
...(x.dataIndex === 'change' ? {
|
||||
render:(_,entity)=>(
|
||||
<Tooltip placement="top" title={entity.change === 'error' ? $t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}</span>
|
||||
</Tooltip>)
|
||||
}:{}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language])
|
||||
|
||||
|
||||
const translatedRouteColumns = useMemo(()=>ApprovalRouteColumns.filter(x=> serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods').map((x)=>({
|
||||
...x,
|
||||
...(x.dataIndex === 'change' ? {
|
||||
render:(_,entity)=>(
|
||||
<Tooltip placement="top" title={entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}>
|
||||
<span className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}>
|
||||
{$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')}
|
||||
{entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''}
|
||||
</span>
|
||||
</Tooltip>)
|
||||
}:{}
|
||||
),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
})),[state.language, serviceType])
|
||||
|
||||
const translatedPublishColumns = useMemo(()=>SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{
|
||||
if(x.dataIndex === 'status'){
|
||||
return {...x,title:$t(x.title),
|
||||
render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{
|
||||
switch(entity.status){
|
||||
case 'done':
|
||||
return <span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
|
||||
case 'error':
|
||||
return <Tooltip title={entity.error || $t('上线失败')}><span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>{$t('失败')} {entity.error}</span></Tooltip>
|
||||
default:
|
||||
return <LoadingOutlined className="text-theme" spin />
|
||||
const translatedRouteColumns = useMemo(
|
||||
() =>
|
||||
ApprovalRouteColumns.filter((x) =>
|
||||
serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods'
|
||||
).map((x) => ({
|
||||
...x,
|
||||
...(x.dataIndex === 'change'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={
|
||||
entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''
|
||||
}
|
||||
}}
|
||||
}
|
||||
}),[state.language])
|
||||
>
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
{entity.change === 'error'
|
||||
? $t('该 API 缺失(0)(1)(2)请先补充', [
|
||||
entity.proxyStatus == 1 && $t('转发信息,'),
|
||||
entity.docStatus == 1 && $t('文档信息,'),
|
||||
entity.upstreamStatus == 1 && $t('上游信息,')
|
||||
])
|
||||
: ''}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
: {}),
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title
|
||||
})),
|
||||
[state.language, serviceType]
|
||||
)
|
||||
|
||||
const translatedPublishColumns = useMemo(
|
||||
() =>
|
||||
SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x) => {
|
||||
if (x.dataIndex === 'status') {
|
||||
return {
|
||||
...x,
|
||||
title: $t(x.title),
|
||||
render: (_: unknown, entity: SystemInsidePublishOnlineItems) => {
|
||||
switch (entity.status) {
|
||||
case 'done':
|
||||
return (
|
||||
<span className={STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]}>{$t('成功')}</span>
|
||||
)
|
||||
case 'error':
|
||||
return (
|
||||
<Tooltip title={entity.error || $t('上线失败')}>
|
||||
<span className={`${STATUS_COLOR[entity.status as keyof typeof STATUS_COLOR]} truncate block`}>
|
||||
{$t('失败')} {entity.error}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
default:
|
||||
return <LoadingOutlined className="text-theme" spin />
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
const translatedPolicyColumns = useMemo(
|
||||
() =>
|
||||
ApprovalPolicyColumns.map((x) => {
|
||||
return {
|
||||
...x,
|
||||
title: typeof x.title === 'string' ? $t(x.title) : x.title,
|
||||
...(x.dataIndex === 'status'
|
||||
? {
|
||||
render: (_, entity) => (
|
||||
<span
|
||||
className={`${ApprovalStatusColorClass[entity.change as keyof typeof ApprovalStatusColorClass]} truncate block`}
|
||||
>
|
||||
{$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}),
|
||||
[state.language]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{!insidePage && <>
|
||||
<>
|
||||
{!insidePage && (
|
||||
<>
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请系统')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请系统')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).project || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('所属团队')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('所属团队')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).team || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请人')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请人')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applier || '-'}</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="my-mbase">
|
||||
<Col className="text-left" span={4}><span >{$t('申请时间')}:</span></Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
|
||||
<Col className="text-left" span={4}>
|
||||
<span>{$t('申请时间')}:</span>
|
||||
</Col>
|
||||
<Col span={18}>{(data as PublishApprovalInfoType).applyTime || '-'}</Col>
|
||||
</Row>
|
||||
</> }
|
||||
<WithPermission access=""><Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
</>
|
||||
)}
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
className=" mx-auto"
|
||||
form={form}
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
scrollToFirstError
|
||||
name="publishApprovalModalContent"
|
||||
// labelCol={{span: 3}}
|
||||
// wrapperCol={{span: 21}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
{insidePage && (
|
||||
<>
|
||||
<Form.Item label={$t('版本号')} name="version" rules={[{ required: true, whitespace: true }]}>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
{
|
||||
insidePage &&
|
||||
<>
|
||||
<Form.Item
|
||||
label={$t("版本号")}
|
||||
name="version"
|
||||
rules={[{required: true,whitespace:true }]}
|
||||
>
|
||||
<Input className="w-INPUT_NORMAL" disabled={type !== 'add'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={$t("版本说明")}
|
||||
name="versionRemark"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('路由列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedRouteColumns}
|
||||
bordered={true}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={data.diffs?.routers || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
{
|
||||
serviceType === 'rest' && <>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('上游列表')}:</span></Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedUpstreamColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.upstreams || []}
|
||||
pagination={false}
|
||||
/></Row>
|
||||
</>
|
||||
}
|
||||
{/* <Form.Item
|
||||
<Form.Item label={$t('版本说明')} name="versionRemark">
|
||||
<Input.TextArea
|
||||
className="w-INPUT_NORMAL"
|
||||
disabled={type !== 'add' && type !== 'publish'}
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('路由列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
columns={translatedRouteColumns}
|
||||
bordered={true}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
dataSource={data.diffs?.routers || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
{serviceType === 'rest' && (
|
||||
<>
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('上游列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedUpstreamColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.upstreams || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
<Row className="mt-mbase pb-[8px] h-[32px] font-bold">
|
||||
<span>{$t('策略列表')}:</span>
|
||||
</Row>
|
||||
<Row className="mb-mbase ">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={translatedPolicyColumns}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.diffs?.strategies || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
{/* <Form.Item
|
||||
label={$t("备注")}
|
||||
name="remark"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={type !== 'add' && type !== 'publish'} placeholder={$t(PLACEHOLDER.input)} />
|
||||
</Form.Item> */}
|
||||
{/*
|
||||
{/*
|
||||
{type !== 'add' && type !== 'publish' && <Form.Item
|
||||
label={$t("审核意见"
|
||||
name="opinion"
|
||||
@@ -238,20 +409,29 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
|
||||
},
|
||||
]);}}/>
|
||||
</Form.Item>} */}
|
||||
|
||||
{['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <>
|
||||
<Row className="text-left h-[32px] mb-8px]" span={3}><span>{$t('上线情况')}:</span></Row>
|
||||
<Row span={24} className="mb-mbase">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={[...translatedPublishColumns]}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.clusterPublishStatus || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row></>}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>)
|
||||
})
|
||||
|
||||
{['error', 'done'].indexOf(data.status) !== -1 &&
|
||||
data.clusterPublishStatus &&
|
||||
data.clusterPublishStatus.length > 0 && (
|
||||
<>
|
||||
<Row className="text-left h-[32px] mb-8px]" span={3}>
|
||||
<span>{$t('上线情况')}:</span>
|
||||
</Row>
|
||||
<Row span={24} className="mb-mbase">
|
||||
<Table
|
||||
bordered={true}
|
||||
columns={[...translatedPublishColumns]}
|
||||
size="small"
|
||||
rowKey="id"
|
||||
dataSource={data.clusterPublishStatus || []}
|
||||
pagination={false}
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
|
||||
import {FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react';
|
||||
import { FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react'
|
||||
|
||||
interface ScrollableSectionProps {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ScrollableSection: FC<ScrollableSectionProps> = ({ children }) => {
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null);
|
||||
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (scrollAreaRef.current) {
|
||||
const scrollTop = scrollAreaRef.current.scrollTop;
|
||||
const scrollHeight = scrollAreaRef.current.scrollHeight;
|
||||
const clientHeight = scrollAreaRef.current.clientHeight;
|
||||
const scrollTop = scrollAreaRef.current.scrollTop
|
||||
const scrollHeight = scrollAreaRef.current.scrollHeight
|
||||
const clientHeight = scrollAreaRef.current.clientHeight
|
||||
|
||||
// 如果滚动到顶部,.content-before 应该显示阴影
|
||||
const showTopShadow = scrollTop > 0;
|
||||
// 如果滚动到底部,.content-after 应该显示阴影
|
||||
const showBottomShadow = scrollHeight - scrollTop < clientHeight;
|
||||
// 这里我们不直接更新状态,而是通过ref来设置样式
|
||||
if (showTopShadow && !showBottomShadow) {
|
||||
setElementShadow('.content-before', true);
|
||||
setElementShadow('.content-after', false);
|
||||
} else if (!showTopShadow && showBottomShadow) {
|
||||
setElementShadow('.content-before', false);
|
||||
setElementShadow('.content-after', true);
|
||||
} else {
|
||||
setElementShadow('.content-before', false);
|
||||
setElementShadow('.content-after', false);
|
||||
}
|
||||
// 如果滚动到顶部,.content-before 应该显示阴影
|
||||
const showTopShadow = scrollTop > 0
|
||||
// 如果滚动到底部,.content-after 应该显示阴影
|
||||
const showBottomShadow = scrollHeight - scrollTop < clientHeight
|
||||
// 这里我们不直接更新状态,而是通过ref来设置样式
|
||||
if (showTopShadow && !showBottomShadow) {
|
||||
setElementShadow('.content-before', true)
|
||||
setElementShadow('.content-after', false)
|
||||
} else if (!showTopShadow && showBottomShadow) {
|
||||
setElementShadow('.content-before', false)
|
||||
setElementShadow('.content-after', true)
|
||||
} else {
|
||||
setElementShadow('.content-before', false)
|
||||
setElementShadow('.content-after', false)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll);
|
||||
scrollAreaRef.current?.addEventListener('scroll', handleScroll)
|
||||
|
||||
return () => {
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
scrollAreaRef.current?.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const setElementShadow = (elementSelector: string, showShadow: boolean) => {
|
||||
const element = document.querySelector(elementSelector);
|
||||
const element = document.querySelector(elementSelector)
|
||||
if (element) {
|
||||
element.style.boxShadow = showShadow ? ( elementSelector === '.content-before' ? '0 2px 2px #0000000d':'0 -2px 2px -2px var(--border-color)') : 'none';
|
||||
element.style.boxShadow = showShadow
|
||||
? elementSelector === '.content-before'
|
||||
? '0 2px 2px #0000000d'
|
||||
: '0 -2px 2px -2px var(--border-color)'
|
||||
: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
const childrenWithRef = Children.toArray(children).map((child) => {
|
||||
if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) {
|
||||
// 将 ref 附加到具有 'scroll-area' 类名的子元素
|
||||
return cloneElement(child, { ref: scrollAreaRef });
|
||||
return cloneElement(child, { ref: scrollAreaRef })
|
||||
}
|
||||
return child;
|
||||
});
|
||||
return child
|
||||
})
|
||||
|
||||
return (
|
||||
<> {childrenWithRef}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return <> {childrenWithRef}</>
|
||||
}
|
||||
|
||||
export default ScrollableSection;
|
||||
export default ScrollableSection
|
||||
|
||||
+110
-96
@@ -1,115 +1,129 @@
|
||||
import {App, Col, Form, Input, Row} from "antd";
|
||||
import { forwardRef, useEffect, useImperativeHandle} from "react";
|
||||
import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx";
|
||||
import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import { SubscribeApprovalList } from "@common/const/approval/const";
|
||||
import { $t } from "@common/locales";
|
||||
import { App, Col, Form, Input, Row } from 'antd'
|
||||
import { forwardRef, useEffect, useImperativeHandle } from 'react'
|
||||
import { SubscribeApprovalInfoType } from '@common/const/approval/type.tsx'
|
||||
import { BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { SubscribeApprovalList } from '@common/const/approval/const'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
type SubscribeApprovalModalProps = {
|
||||
type:'approval'|'view'
|
||||
data?:SubscribeApprovalInfoType
|
||||
inSystem?:boolean
|
||||
serviceId:string
|
||||
teamId:string
|
||||
type: 'approval' | 'view'
|
||||
data?: SubscribeApprovalInfoType
|
||||
inSystem?: boolean
|
||||
serviceId: string
|
||||
teamId: string
|
||||
}
|
||||
|
||||
export type SubscribeApprovalModalHandle = {
|
||||
save:(operate:'pass'|'refuse') =>Promise<boolean|string>
|
||||
save: (operate: 'pass' | 'refuse') => Promise<boolean | string>
|
||||
}
|
||||
|
||||
type FieldType = {
|
||||
reason?:string;
|
||||
opinion?:string;
|
||||
};
|
||||
reason?: string
|
||||
opinion?: string
|
||||
}
|
||||
|
||||
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle,SubscribeApprovalModalProps>((props, ref) => {
|
||||
export const SubscribeApprovalModalContent = forwardRef<SubscribeApprovalModalHandle, SubscribeApprovalModalProps>(
|
||||
(props, ref) => {
|
||||
const { message } = App.useApp()
|
||||
const {data, type,inSystem=false, teamId, serviceId} = props
|
||||
const [form] = Form.useForm();
|
||||
const {fetchData} = useFetch()
|
||||
const { data, type, inSystem = false, teamId, serviceId } = props
|
||||
const [form] = Form.useForm()
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
const save:(operate:'pass'|'refuse')=>Promise<boolean | string> = (operate)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(type === 'view'){
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
form.validateFields().then((value)=>{
|
||||
if(operate === 'refuse' && form.getFieldValue('opinion') === ''){
|
||||
form.setFields([{
|
||||
name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}])
|
||||
form.scrollToField('opinion')
|
||||
reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
return
|
||||
const save: (operate: 'pass' | 'refuse') => Promise<boolean | string> = (operate) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (type === 'view') {
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
form
|
||||
.validateFields()
|
||||
.then((value) => {
|
||||
if (operate === 'refuse' && form.getFieldValue('opinion') === '') {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [$t(FORM_ERROR_TIPS.refuseOpinion)]
|
||||
}
|
||||
fetchData<BasicResponse<null>>(`${inSystem?'service/':''}approval/subscribe`,{method: 'POST',eoBody:({opinion:value.opinion,operate}), eoParams:(inSystem ? {apply:data!.id, team:teamId} : {id:data!.id,team:teamId})}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
})
|
||||
])
|
||||
form.scrollToField('opinion')
|
||||
reject($t(RESPONSE_TIPS.refuseOpinion))
|
||||
return
|
||||
}
|
||||
fetchData<BasicResponse<null>>(`${inSystem ? 'service/' : ''}approval/subscribe`, {
|
||||
method: 'POST',
|
||||
eoBody: { opinion: value.opinion, operate },
|
||||
eoParams: inSystem ? { apply: data!.id, team: teamId } : { id: data!.id, team: teamId }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save
|
||||
})
|
||||
)
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
useEffect(()=>{
|
||||
form.setFieldsValue({opinion:'',...data})
|
||||
},[])
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({ opinion: '', ...data })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="my-btnybase">{
|
||||
SubscribeApprovalList?.map((x)=>(
|
||||
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
|
||||
<Col className="text-left" span={6}>{$t(x.title)}:</Col>
|
||||
<Col >{(data as {[k:string]:unknown})?.[x.key]?.name || (data as {[k:string]:unknown})?.[x.key] || '-'}</Col>
|
||||
</Row>
|
||||
))
|
||||
}
|
||||
<div className="my-btnybase">
|
||||
{SubscribeApprovalList?.map((x) => (
|
||||
<Row key={x.key} className="leading-[32px] mb-btnbase mx-auto">
|
||||
<Col className="text-left" span={6}>
|
||||
{$t(x.title)}:
|
||||
</Col>
|
||||
<Col>
|
||||
{(data as { [k: string]: unknown })?.[x.key]?.name || (data as { [k: string]: unknown })?.[x.key] || '-'}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<WithPermission access="">
|
||||
<Form
|
||||
labelAlign='left'
|
||||
layout='vertical'
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="subscribeApprovalModalContent"
|
||||
// labelCol={{ span: 6}}
|
||||
// wrapperCol={{ span: 18}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
|
||||
<Form.Item<FieldType>
|
||||
label={$t("申请原因")}
|
||||
name="reason"
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType>
|
||||
label={$t("审核意见")}
|
||||
name="opinion"
|
||||
extra={$t(FORM_ERROR_TIPS.refuseOpinion)}
|
||||
>
|
||||
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} onChange={()=>{ form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [], // 设置为空数组来移除错误信息
|
||||
},
|
||||
])}} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
<Form
|
||||
labelAlign="left"
|
||||
layout="vertical"
|
||||
form={form}
|
||||
className="mx-auto "
|
||||
name="subscribeApprovalModalContent"
|
||||
// labelCol={{ span: 6}}
|
||||
// wrapperCol={{ span: 18}}
|
||||
autoComplete="off"
|
||||
disabled={type === 'view'}
|
||||
>
|
||||
<Form.Item<FieldType> label={$t('申请原因')} name="reason">
|
||||
<Input.TextArea className="w-INPUT_NORMAL" disabled={true} placeholder=" " />
|
||||
</Form.Item>
|
||||
<Form.Item<FieldType> label={$t('审核意见')} name="opinion" extra={$t(FORM_ERROR_TIPS.refuseOpinion)}>
|
||||
<Input.TextArea
|
||||
className="w-INPUT_NORMAL"
|
||||
placeholder={$t(PLACEHOLDER.input)}
|
||||
onChange={() => {
|
||||
form.setFields([
|
||||
{
|
||||
name: 'opinion',
|
||||
errors: [] // 设置为空数组来移除错误信息
|
||||
}
|
||||
])
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</WithPermission>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,71 +1,121 @@
|
||||
|
||||
import { Button, Tooltip } from "antd"
|
||||
import { useState, useMemo, useEffect, useCallback } from "react"
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions"
|
||||
import { Icon } from "@iconify/react/dist/iconify.js"
|
||||
import { $t } from "@common/locales"
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
import { Button, Tooltip } from 'antd'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
type TableBtnWithPermissionProps = {
|
||||
btnTitle:string
|
||||
access?:keyof typeof PERMISSION_DEFINITION[0],
|
||||
tooltip?:string,
|
||||
disabled?:boolean,
|
||||
navigateTo?:string,
|
||||
onClick?:(args?:unknown)=>void
|
||||
className?:string
|
||||
btnType:string
|
||||
btnTitle: 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 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',
|
||||
offline: 'ic:baseline-file-download-off',
|
||||
approval: 'ic:baseline-approval',
|
||||
stop: 'ic:baseline-stop-circle',
|
||||
online: 'ic:baseline-check-circle',
|
||||
cancel: 'ic:baseline-cancel-schedule-send',
|
||||
refresh: 'ic:baseline-refresh',
|
||||
logs: 'hugeicons:google-doc',
|
||||
disable: 'ic:baseline-pause-circle',
|
||||
enable: 'ic:baseline-play-circle'
|
||||
}
|
||||
// 表格操作栏按钮,受权限控制
|
||||
const TableBtnWithPermission = ({btnTitle, access, tooltip, disabled, navigateTo, onClick,className,btnType}:TableBtnWithPermissionProps) => {
|
||||
|
||||
const [btnAccess, setBtnAccess] = useState<boolean>(false)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const navigate = useNavigate()
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!accessInit) return false
|
||||
if(!access) return true
|
||||
return checkPermission(access)
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
const TableBtnWithPermission = ({
|
||||
btnTitle,
|
||||
access,
|
||||
tooltip,
|
||||
disabled,
|
||||
navigateTo,
|
||||
onClick,
|
||||
className,
|
||||
btnType
|
||||
}: TableBtnWithPermissionProps) => {
|
||||
const [btnAccess, setBtnAccess] = useState<boolean>(false)
|
||||
const [btnStatus, setBtnStatus] = useState<boolean>(false)
|
||||
const [closeToolTip, setCloseToolTip] = useState<boolean>(false)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
const navigate = useNavigate()
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!accessInit) return false
|
||||
if (!access) return true
|
||||
return checkPermission(access)
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
access ? setBtnAccess(lastAccess) : setBtnAccess(true)
|
||||
},[access, lastAccess])
|
||||
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 ?? $t('暂无(0)权限,请联系管理员分配。',[$t(btnTitle).toLowerCase()])}>
|
||||
<Button type="text" disabled={true} className={`h-[22px] border-none p-0 flex items-center bg-transparent ${className}`} key={btnType} icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18"/>} >{}</Button>
|
||||
</Tooltip>
|
||||
:
|
||||
<Tooltip placement="top" title={$t(btnTitle)}>
|
||||
<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>
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
setTimeout(() => {
|
||||
setBtnStatus(false)
|
||||
setCloseToolTip(true)
|
||||
})
|
||||
|
||||
}</>
|
||||
);
|
||||
navigateTo ? navigate(navigateTo) : onClick?.()
|
||||
},
|
||||
[navigateTo, navigate, onClick]
|
||||
)
|
||||
const changeTooltipStatus = (open: boolean) => {
|
||||
setBtnStatus(open)
|
||||
if (closeToolTip) {
|
||||
setBtnStatus(false)
|
||||
setCloseToolTip(false)
|
||||
}
|
||||
|
||||
export default TableBtnWithPermission
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{!btnAccess || (disabled && tooltip) ? (
|
||||
<Tooltip placement="top" title={tooltip ?? $t('暂无(0)权限,请联系管理员分配。', [$t(btnTitle).toLowerCase()])}>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={true}
|
||||
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
|
||||
key={btnType}
|
||||
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
|
||||
>
|
||||
{}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
placement="top"
|
||||
title={$t(btnTitle)}
|
||||
trigger="hover"
|
||||
open={btnStatus}
|
||||
onOpenChange={changeTooltipStatus}
|
||||
>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={disabled}
|
||||
className={`flex items-center p-0 bg-transparent border-none h-[22px] ${className}`}
|
||||
key={btnType}
|
||||
icon={<Icon icon={TableIconName[btnType as keyof typeof TableIconName]} width="18" height="18" />}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableBtnWithPermission
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import { Tag, TagProps } from 'antd'
|
||||
import { useState, useMemo, useEffect } from 'react'
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
|
||||
import { Tag, TagProps } from "antd";
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
|
||||
export interface TagWithPermission extends TagProps{
|
||||
access?:string
|
||||
export interface TagWithPermission extends TagProps {
|
||||
access?: string
|
||||
}
|
||||
export default function TagWithPermission(props:TagWithPermission){
|
||||
const {access,onClose} = props
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!access) return true
|
||||
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
export default function TagWithPermission(props: TagWithPermission) {
|
||||
const { access, onClose } = props
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!access) return true
|
||||
return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
access ? setEditAccess(lastAccess) : setEditAccess(true)
|
||||
},[lastAccess])
|
||||
|
||||
const handleTagClose = (e: React.MouseEvent<HTMLElement>)=>{
|
||||
e.preventDefault();
|
||||
if(!editAccess) return
|
||||
onClose?.(e)
|
||||
}
|
||||
useEffect(() => {
|
||||
access ? setEditAccess(lastAccess) : setEditAccess(true)
|
||||
}, [lastAccess])
|
||||
|
||||
return <Tag
|
||||
closeIcon
|
||||
{...props}
|
||||
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
|
||||
onClose={handleTagClose}>
|
||||
{props.children}
|
||||
</Tag>
|
||||
const handleTagClose = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault()
|
||||
if (!editAccess) return
|
||||
onClose?.(e)
|
||||
}
|
||||
|
||||
}
|
||||
return (
|
||||
<Tag
|
||||
closeIcon
|
||||
{...props}
|
||||
className={` rounded-SEARCH_RADIUS h-[32px] text-[14px] leading-[22px] py-[5px] px-btnbase bg-transparent mb-[8px] ${props.className}`}
|
||||
onClose={handleTagClose}
|
||||
>
|
||||
{props.children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
const ThemeSwitcher = () => {
|
||||
const [darkMode, setDarkMode] = useState(true);
|
||||
const [darkMode, setDarkMode] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
let isDarkMode = localStorage.getItem('dark-mode');
|
||||
if(isDarkMode !== undefined && isDarkMode !== null){
|
||||
setDarkMode(isDarkMode === 'true')
|
||||
}else{
|
||||
localStorage.setItem('dark-mode', (darkMode).toString());
|
||||
const isDarkMode = localStorage.getItem('dark-mode')
|
||||
if (isDarkMode !== undefined && isDarkMode !== null) {
|
||||
setDarkMode(isDarkMode === 'true')
|
||||
} else {
|
||||
localStorage.setItem('dark-mode', darkMode.toString())
|
||||
}
|
||||
}, []);
|
||||
}, [])
|
||||
|
||||
useEffect(()=>{
|
||||
document.documentElement.classList.toggle('dark', darkMode);
|
||||
},[darkMode])
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle('dark', darkMode)
|
||||
}, [darkMode])
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setDarkMode(!darkMode);
|
||||
localStorage.setItem('dark-mode', (!darkMode).toString());
|
||||
document.documentElement.classList.toggle('dark', !darkMode);
|
||||
};
|
||||
setDarkMode(!darkMode)
|
||||
localStorage.setItem('dark-mode', (!darkMode).toString())
|
||||
document.documentElement.classList.toggle('dark', !darkMode)
|
||||
}
|
||||
|
||||
return (
|
||||
// <button onClick={toggleDarkMode}>
|
||||
// {darkMode ? '切换到白天模式' : '切换到黑夜模式'}
|
||||
// </button>
|
||||
<></>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeSwitcher;
|
||||
export default ThemeSwitcher
|
||||
|
||||
@@ -1,121 +1,148 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import '../../index.css'
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import "../../index.css"
|
||||
import { $t } from '@common/locales';
|
||||
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>
|
||||
export type RangeValue = [Dayjs | null, Dayjs | null] | null
|
||||
|
||||
type RangePickerProps = GetProps<typeof DatePicker.RangePicker>;
|
||||
export type RangeValue = [Dayjs | null, Dayjs | null] | null;
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
dayjs.extend(customParseFormat)
|
||||
|
||||
export type TimeRange = {
|
||||
start:number|null
|
||||
end:number|null
|
||||
start: number | null
|
||||
end: number | null
|
||||
}
|
||||
|
||||
export type TimeRangeButton = ''| 'hour' | 'day' | 'threeDays' | 'sevenDays';
|
||||
export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
|
||||
|
||||
type TimeRangeSelectorProps = {
|
||||
initialTimeButton?:TimeRangeButton,
|
||||
initialDatePickerValue?:RangeValue
|
||||
onTimeRangeChange?:(timeRange:TimeRange) =>void
|
||||
hideTitle?:boolean
|
||||
onTimeButtonChange:(time:TimeRangeButton) =>void
|
||||
labelSize?:'small'|'default'
|
||||
initialTimeButton?: TimeRangeButton
|
||||
initialDatePickerValue?: RangeValue
|
||||
onTimeRangeChange?: (timeRange: TimeRange) => void
|
||||
hideTitle?: boolean
|
||||
onTimeButtonChange: (time: TimeRangeButton) => void
|
||||
labelSize?: 'small' | 'default'
|
||||
bindRef?: any
|
||||
hideBtns?: TimeRangeButton[]
|
||||
defaultTimeButton?: TimeRangeButton
|
||||
}
|
||||
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
|
||||
const {
|
||||
initialTimeButton,
|
||||
initialDatePickerValue,
|
||||
onTimeRangeChange,
|
||||
hideTitle,
|
||||
onTimeButtonChange,
|
||||
labelSize = 'default',
|
||||
bindRef,
|
||||
hideBtns = [],
|
||||
defaultTimeButton = 'hour'
|
||||
} = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
|
||||
useEffect(() => {
|
||||
if (bindRef) {
|
||||
bindRef({ reset })
|
||||
}
|
||||
const TimeRangeSelector = (props:TimeRangeSelectorProps) => {
|
||||
const {initialTimeButton,initialDatePickerValue,onTimeRangeChange,hideTitle,onTimeButtonChange,labelSize='default'} = props
|
||||
const [timeButton, setTimeButton] = useState(initialTimeButton || '');
|
||||
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null,null]);
|
||||
|
||||
}, [bindRef])
|
||||
// 根据选择的时间范围计算开始和结束时间
|
||||
const calculateTimeRange = (curBtn:'hour'|'day'|'threeDays'|'sevenDays') => {
|
||||
const currentSecond = new Date().getTime() // 当前毫秒数时间戳
|
||||
const currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳
|
||||
let startMin = currentMin - 60 * 60 * 1000
|
||||
const calculateTimeRange = (curBtn: TimeRangeButton) => {
|
||||
const currentSecond = Math.floor(Date.now() / 1000) // 当前秒级时间戳
|
||||
let startMin = currentSecond - 60 * 60
|
||||
switch (curBtn) {
|
||||
case 'hour': {
|
||||
startMin = currentMin - 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'day': {
|
||||
startMin = currentMin - 24 * 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'threeDays': {
|
||||
startMin =
|
||||
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
|
||||
2 * 24 * 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'sevenDays': {
|
||||
startMin =
|
||||
new Date(new Date().setHours(0, 0, 0, 0)).getTime() -
|
||||
6 * 24 * 60 * 60 * 1000
|
||||
break
|
||||
}
|
||||
case 'hour': {
|
||||
startMin = currentSecond - 60 * 60
|
||||
break
|
||||
}
|
||||
case 'day': {
|
||||
startMin = currentSecond - 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
case 'threeDays': {
|
||||
startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 2 * 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
case 'sevenDays': {
|
||||
startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 6 * 24 * 60 * 60
|
||||
break
|
||||
}
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start: startMin / 1000, end: currentMin / 1000 });
|
||||
}
|
||||
};
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start: startMin, end: currentSecond })
|
||||
}
|
||||
}
|
||||
|
||||
// 处理单选按钮的变化
|
||||
const handleRadioChange = (e:RadioChangeEvent) => {
|
||||
setTimeButton(e.target.value);
|
||||
const handleRadioChange = (e: RadioChangeEvent) => {
|
||||
setTimeButton(e.target.value)
|
||||
onTimeButtonChange?.(e.target.value)
|
||||
setDatePickerValue(null)
|
||||
calculateTimeRange(e.target.value);
|
||||
};
|
||||
calculateTimeRange(e.target.value)
|
||||
}
|
||||
const reset = () => {
|
||||
setTimeButton(defaultTimeButton)
|
||||
calculateTimeRange(defaultTimeButton)
|
||||
setDatePickerValue(null)
|
||||
}
|
||||
|
||||
// 处理日期选择器的变化
|
||||
const handleDatePickerChange = (dates: RangeValue) => {
|
||||
setTimeButton(dates ? '' : 'hour')
|
||||
onTimeButtonChange?.(dates ? '' : 'hour')
|
||||
setDatePickerValue(dates);
|
||||
setTimeButton(dates ? '' : defaultTimeButton)
|
||||
onTimeButtonChange?.(dates ? '' : defaultTimeButton)
|
||||
setDatePickerValue(dates)
|
||||
if (dates && Array.isArray(dates) && dates.length === 2) {
|
||||
const [startDate, endDate] = dates;
|
||||
const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00
|
||||
const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59
|
||||
const [startDate, endDate] = dates
|
||||
const start = startDate!.startOf('day').unix() // 开始日期的00:00:00
|
||||
const end = endDate!.endOf('day').unix() // 结束日期的23:59:59
|
||||
if (onTimeRangeChange) {
|
||||
onTimeRangeChange({ start, end });
|
||||
onTimeRangeChange({ start, end })
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!dates) {
|
||||
calculateTimeRange(defaultTimeButton)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
|
||||
// Can not select days before today and today
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf();
|
||||
};
|
||||
const disabledDate: RangePickerProps['disabledDate'] = (current) => {
|
||||
// Can not select days before today and today
|
||||
return current && current.valueOf() > dayjs().startOf('day').valueOf()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
|
||||
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}:</label>}
|
||||
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
|
||||
<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>
|
||||
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}:</label>}
|
||||
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
|
||||
{hideBtns?.length && hideBtns.includes('hour') ? null : (
|
||||
<Radio.Button value="hour">{$t('近1小时')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('day') ? null : (
|
||||
<Radio.Button value="day">{$t('近24小时')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('threeDays') ? null : (
|
||||
<Radio.Button value="threeDays">{$t('近3天')}</Radio.Button>
|
||||
)}
|
||||
{hideBtns?.length && hideBtns.includes('sevenDays') ? null : (
|
||||
<Radio.Button className="rounded-e-none" value="sevenDays">
|
||||
{$t('近7天')}
|
||||
</Radio.Button>
|
||||
)}
|
||||
</Radio.Group>
|
||||
<DatePicker.RangePicker
|
||||
value={datePickerValue}
|
||||
className="rounded-s-none ml-[-1px]"
|
||||
className="rounded-s-none ml-[-1px]"
|
||||
disabledDate={disabledDate}
|
||||
onChange={handleDatePickerChange}
|
||||
onOpenChange={(open)=>{
|
||||
if(!open && datePickerValue && datePickerValue.length > 2){
|
||||
setTimeButton('')
|
||||
onTimeButtonChange?.('')
|
||||
}
|
||||
onOpenChange={(open) => {
|
||||
if (!open && datePickerValue && datePickerValue.length > 2) {
|
||||
setTimeButton('')
|
||||
onTimeButtonChange?.('')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default TimeRangeSelector;
|
||||
export default TimeRangeSelector
|
||||
|
||||
@@ -1,45 +1,94 @@
|
||||
|
||||
import {CheckOutlined, LoadingOutlined, MoreOutlined} from "@ant-design/icons";
|
||||
import {Dropdown, Input, InputRef, MenuProps} from "antd";
|
||||
import { ReactNode, useEffect, useRef, useState} from "react";
|
||||
import { CheckOutlined, LoadingOutlined, MoreOutlined } from '@ant-design/icons'
|
||||
import { Dropdown, Input, InputRef, MenuProps } from 'antd'
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
|
||||
export type TreeWithMoreProp = {
|
||||
children:ReactNode,
|
||||
dropdownMenu:MenuProps['items']
|
||||
editable?:boolean
|
||||
editingId?:string
|
||||
afterEdit?:(val:string)=>Promise<string|boolean>
|
||||
editKey?:string
|
||||
entity?:{id:string,[k:string]:unknown | string}
|
||||
onBlur?:()=>void
|
||||
stopClick?:boolean
|
||||
children: ReactNode
|
||||
dropdownMenu: MenuProps['items']
|
||||
editable?: boolean
|
||||
editingId?: string
|
||||
afterEdit?: (val: string) => Promise<string | boolean>
|
||||
editKey?: string
|
||||
entity?: { id: string; [k: string]: unknown | string }
|
||||
onBlur?: () => void
|
||||
stopClick?: boolean
|
||||
}
|
||||
|
||||
const TreeWithMore = ({children,dropdownMenu,editable,editingId,entity,editKey='name',afterEdit,onBlur,stopClick=true}:TreeWithMoreProp)=>{
|
||||
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
const TreeWithMore = ({
|
||||
children,
|
||||
dropdownMenu,
|
||||
editable,
|
||||
editingId,
|
||||
entity,
|
||||
editKey = 'name',
|
||||
afterEdit,
|
||||
onBlur,
|
||||
stopClick = true
|
||||
}: TreeWithMoreProp) => {
|
||||
const [editValue, setEditValue] = useState<string>(entity?.[editKey] as string)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const inputRef = useRef<InputRef>(null)
|
||||
|
||||
const handleSubmit = (val:string)=>{
|
||||
if(submitting) return
|
||||
setSubmitting(true)
|
||||
afterEdit && afterEdit(val).finally(()=>setSubmitting(false))
|
||||
}
|
||||
const handleSubmit = (val: string) => {
|
||||
if (submitting) return
|
||||
setSubmitting(true)
|
||||
afterEdit && afterEdit(val).finally(() => setSubmitting(false))
|
||||
}
|
||||
|
||||
useEffect(()=>{inputRef.current?.focus()},[inputRef])
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus()
|
||||
}, [inputRef])
|
||||
|
||||
return (<>
|
||||
{
|
||||
editable && editingId && entity?.id && editingId === entity.id ? <Input ref={inputRef} value={editValue} onChange={(e)=>{setEditValue(e.target.value)}} onBlur={()=>{onBlur?.()}} onClick={(e)=>stopClick&&e?.stopPropagation()} onPressEnter={()=>{handleSubmit(editValue)}} suffix={submitting ? <LoadingOutlined />:<CheckOutlined onClick={()=>{handleSubmit(editValue)}}/>} />:
|
||||
<Dropdown menu={{items:dropdownMenu}} trigger={['contextMenu']} >
|
||||
<div className='tree-title-hover' >{children}
|
||||
<span onClick={(e)=>{ stopClick && e.stopPropagation();}}>
|
||||
<Dropdown menu={{items:dropdownMenu}} trigger={['click']} >
|
||||
<MoreOutlined className="tree-title-more" onClick={(e)=>{ stopClick && e.stopPropagation(); }} />
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
return (
|
||||
<>
|
||||
{editable && editingId && entity?.id && editingId === entity.id ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => {
|
||||
setEditValue(e.target.value)
|
||||
}}
|
||||
onBlur={() => {
|
||||
onBlur?.()
|
||||
}}
|
||||
onClick={(e) => stopClick && e?.stopPropagation()}
|
||||
onPressEnter={() => {
|
||||
handleSubmit(editValue)
|
||||
}}
|
||||
suffix={
|
||||
submitting ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
<CheckOutlined
|
||||
onClick={() => {
|
||||
handleSubmit(editValue)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Dropdown menu={{ items: dropdownMenu }} trigger={['contextMenu']}>
|
||||
<div className="tree-title-hover">
|
||||
{children}
|
||||
<span
|
||||
onClick={(e) => {
|
||||
stopClick && e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<Dropdown menu={{ items: dropdownMenu }} trigger={['click']}>
|
||||
<MoreOutlined
|
||||
className="tree-title-more"
|
||||
onClick={(e) => {
|
||||
stopClick && e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</Dropdown>
|
||||
}</>)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default TreeWithMore
|
||||
export default TreeWithMore
|
||||
|
||||
@@ -1,165 +1,190 @@
|
||||
import { $t } from "@common/locales"
|
||||
import { $t } from '@common/locales'
|
||||
|
||||
/* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/
|
||||
export const TranslateWord = ()=>{
|
||||
return (
|
||||
<>
|
||||
{$t('文件日志')}
|
||||
{$t('HTTP日志')}
|
||||
{$t('Kafka日志')}
|
||||
{$t('NSQ日志')}
|
||||
{$t('Syslog日志')}
|
||||
{$t('未分配')}
|
||||
{$t('超级管理员')}
|
||||
{$t('团队管理员')}
|
||||
{$t('运维管理员')}
|
||||
{$t('普通成员')}
|
||||
{$t('只读成员')}
|
||||
{$t('服务管理员')}
|
||||
{$t('服务开发者')}
|
||||
{$t('消费者开发者')}
|
||||
{$t('消费者管理员')}
|
||||
{$t('驱动名称')}
|
||||
{$t('请求失败数')}
|
||||
{$t('转发失败数')}
|
||||
{$t('作用范围')}
|
||||
{$t('添加条目')}
|
||||
{$t('添加地址')}
|
||||
{$t('文件名称')}
|
||||
{$t('存放目录')}
|
||||
{$t('日志分割周期')}
|
||||
{$t('过期时间')}
|
||||
{$t('单位:天')}
|
||||
{$t('输出格式')}
|
||||
{$t('格式化配置')}
|
||||
{$t('服务器地址')}
|
||||
{$t('Access日志')}
|
||||
{$t('NSQD地址列表')}
|
||||
{$t('鉴权Secret')}
|
||||
{$t('网络协议')}
|
||||
{$t('日志等级')}
|
||||
{$t('单行')}
|
||||
{$t('小时')}
|
||||
{$t('天')}
|
||||
{$t('未发布')}
|
||||
{$t('待发布')}
|
||||
{$t('单位:s,最小值:1')}
|
||||
{$t('上传文件')}
|
||||
{$t('替换文件')}
|
||||
{$t('是否放行')}
|
||||
{$t('监控')}
|
||||
{$t('必填')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
{$t('无需审核')}
|
||||
{$t('需要审核')}
|
||||
{$t('创建时间')}
|
||||
{$t('协议')}
|
||||
{$t('方法')}
|
||||
{$t('地址(IP 端口或域名)')}
|
||||
{$t('权重(0-999)')}
|
||||
{$t('带权轮询')}
|
||||
{$t('发布版本')}
|
||||
{$t('发布申请记录')}
|
||||
{$t('创建版本时间')}
|
||||
{$t('版本状态')}
|
||||
{$t('创建人')}
|
||||
{$t('审核时间')}
|
||||
{$t('申请方-消费者')}
|
||||
{$t('审核状态')}
|
||||
{$t('申请人')}
|
||||
{$t('审核人')}
|
||||
{$t('审核时间')}
|
||||
{$t('来源')}
|
||||
{$t('订阅时间')}
|
||||
{$t('请输入')}
|
||||
{$t('请选择')}
|
||||
{$t('创建者')}
|
||||
{$t('服务数量')}
|
||||
{$t('负责人')}
|
||||
{$t('姓名')}
|
||||
{$t('团队角色')}
|
||||
{$t('添加日期')}
|
||||
{$t('请求成功数')}
|
||||
{$t('转发成功数')}
|
||||
{$t('API 名称')}
|
||||
{$t('失败状态码数')}
|
||||
{$t('所属服务')}
|
||||
{$t('平均响应时间(ms)')}
|
||||
{$t('最大响应时间(ms)')}
|
||||
{$t('最小响应时间(ms)')}
|
||||
{$t('平均请求流量(KB)')}
|
||||
{$t('最大请求流量(KB)')}
|
||||
{$t('最小请求流量(KB)')}
|
||||
{$t('所有成员')}
|
||||
{$t('状态')}
|
||||
{$t('角色名称')}
|
||||
{$t('绑定域名')}
|
||||
{$t('过期日期')}
|
||||
{$t('支持字母开头、英文数字中横线下划线组合')}
|
||||
{$t('英文数字下划线任意一种,首字母必须为英文')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
|
||||
{$t('选择拒绝时,审核意见为必填')}
|
||||
{$t('操作成功')}
|
||||
{$t('操作失败')}
|
||||
{$t('正在操作')}
|
||||
{$t('正在加载数据')}
|
||||
{$t('获取数据失败')}
|
||||
{$t('登录成功')}
|
||||
{$t('退出成功,将跳转至登录页')}
|
||||
{$t('未填写审核意见')}
|
||||
{$t('复制成功')}
|
||||
{$t('复制失败,请手动复制')}
|
||||
{$t('服务所属团队')}
|
||||
{$t('在线')}
|
||||
{$t('已拒绝')}
|
||||
{$t('中止')}
|
||||
{$t('发布异常')}
|
||||
{$t('发布中')}
|
||||
{$t('申请方所属团队')}
|
||||
{$t('发布状态')}
|
||||
{$t(' 次')}
|
||||
{$t('每分钟')}
|
||||
{$t('每5分钟')}
|
||||
{$t('每小时')}
|
||||
{$t('每天')}
|
||||
{$t('每周')}
|
||||
{$t('上线结果')}
|
||||
{$t('订阅服务数量')}
|
||||
{$t('鉴权数量')}
|
||||
{$t('列表')}
|
||||
{$t('块')}
|
||||
{$t('HTTP 请求头')}
|
||||
{$t('全等匹配')}
|
||||
{$t('前缀匹配')}
|
||||
{$t('后缀匹配')}
|
||||
{$t('子串匹配')}
|
||||
{$t('非等匹配')}
|
||||
{$t('空值匹配')}
|
||||
{$t('存在匹配')}
|
||||
{$t('不存在匹配')}
|
||||
{$t('区分大小写的正则匹配')}
|
||||
{$t('不区分大小写的正则匹配')}
|
||||
{$t('任意匹配')}
|
||||
{$t('驳回')}
|
||||
{$t('已订阅')}
|
||||
{$t('取消申请')}
|
||||
{$t('透传客户端请求 Host')}
|
||||
{$t('使用上游服务 Host')}
|
||||
{$t('重写 Host')}
|
||||
{$t('动态服务发现')}
|
||||
{$t('地址')}
|
||||
{$t('新增')}
|
||||
{$t('申请方消费者')}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
export const TranslateWord = () => {
|
||||
return (
|
||||
<>
|
||||
{$t('文件日志')}
|
||||
{$t('HTTP日志')}
|
||||
{$t('Kafka日志')}
|
||||
{$t('NSQ日志')}
|
||||
{$t('Syslog日志')}
|
||||
{$t('未分配')}
|
||||
{$t('超级管理员')}
|
||||
{$t('团队管理员')}
|
||||
{$t('运维管理员')}
|
||||
{$t('普通成员')}
|
||||
{$t('只读成员')}
|
||||
{$t('服务管理员')}
|
||||
{$t('服务开发者')}
|
||||
{$t('消费者开发者')}
|
||||
{$t('消费者管理员')}
|
||||
{$t('驱动名称')}
|
||||
{$t('请求失败数')}
|
||||
{$t('转发失败数')}
|
||||
{$t('作用范围')}
|
||||
{$t('添加条目')}
|
||||
{$t('添加地址')}
|
||||
{$t('文件名称')}
|
||||
{$t('存放目录')}
|
||||
{$t('日志分割周期')}
|
||||
{$t('过期时间')}
|
||||
{$t('单位:天')}
|
||||
{$t('输出格式')}
|
||||
{$t('格式化配置')}
|
||||
{$t('服务器地址')}
|
||||
{$t('Access日志')}
|
||||
{$t('NSQD地址列表')}
|
||||
{$t('鉴权Secret')}
|
||||
{$t('网络协议')}
|
||||
{$t('日志等级')}
|
||||
{$t('单行')}
|
||||
{$t('小时')}
|
||||
{$t('天')}
|
||||
{$t('未发布')}
|
||||
{$t('待发布')}
|
||||
{$t('单位:s,最小值:1')}
|
||||
{$t('上传文件')}
|
||||
{$t('替换文件')}
|
||||
{$t('是否放行')}
|
||||
{$t('监控')}
|
||||
{$t('必填')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('上传 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('替换 OpenAPI 文档 (.json/.yaml)')}
|
||||
{$t('打开 OpenAPI YAML 编辑器')}
|
||||
{$t('无需审核:允许任何消费者调用该服务')}
|
||||
{$t('人工审核:仅允许通过人工审核的消费者调用该服务')}
|
||||
{$t('永久')}
|
||||
{$t('否')}
|
||||
{$t('是')}
|
||||
{$t('无需审核')}
|
||||
{$t('需要审核')}
|
||||
{$t('创建时间')}
|
||||
{$t('协议')}
|
||||
{$t('方法')}
|
||||
{$t('地址(IP 端口或域名)')}
|
||||
{$t('权重(0-999)')}
|
||||
{$t('带权轮询')}
|
||||
{$t('发布版本')}
|
||||
{$t('发布申请记录')}
|
||||
{$t('创建版本时间')}
|
||||
{$t('版本状态')}
|
||||
{$t('创建人')}
|
||||
{$t('审核时间')}
|
||||
{$t('申请方-消费者')}
|
||||
{$t('审核状态')}
|
||||
{$t('申请人')}
|
||||
{$t('审核人')}
|
||||
{$t('审核时间')}
|
||||
{$t('来源')}
|
||||
{$t('订阅时间')}
|
||||
{$t('请输入')}
|
||||
{$t('请选择')}
|
||||
{$t('创建者')}
|
||||
{$t('服务数量')}
|
||||
{$t('负责人')}
|
||||
{$t('姓名')}
|
||||
{$t('团队角色')}
|
||||
{$t('添加日期')}
|
||||
{$t('请求成功数')}
|
||||
{$t('转发成功数')}
|
||||
{$t('API 名称')}
|
||||
{$t('失败状态码数')}
|
||||
{$t('所属服务')}
|
||||
{$t('平均响应时间(ms)')}
|
||||
{$t('最大响应时间(ms)')}
|
||||
{$t('最小响应时间(ms)')}
|
||||
{$t('平均请求流量(KB)')}
|
||||
{$t('最大请求流量(KB)')}
|
||||
{$t('最小请求流量(KB)')}
|
||||
{$t('所有成员')}
|
||||
{$t('状态')}
|
||||
{$t('角色名称')}
|
||||
{$t('绑定域名')}
|
||||
{$t('过期日期')}
|
||||
{$t('支持字母开头、英文数字中横线下划线组合')}
|
||||
{$t('英文数字下划线任意一种,首字母必须为英文')}
|
||||
{$t('字符非法,仅支持英文')}
|
||||
{$t('无法连接集群,请检查集群地址是否正确或防火墙配置')}
|
||||
{$t('选择拒绝时,审核意见为必填')}
|
||||
{$t('操作成功')}
|
||||
{$t('操作失败')}
|
||||
{$t('正在操作')}
|
||||
{$t('正在加载数据')}
|
||||
{$t('获取数据失败')}
|
||||
{$t('登录成功')}
|
||||
{$t('退出成功,将跳转至登录页')}
|
||||
{$t('未填写审核意见')}
|
||||
{$t('复制成功')}
|
||||
{$t('复制失败,请手动复制')}
|
||||
{$t('服务所属团队')}
|
||||
{$t('在线')}
|
||||
{$t('已拒绝')}
|
||||
{$t('中止')}
|
||||
{$t('发布异常')}
|
||||
{$t('发布中')}
|
||||
{$t('申请方所属团队')}
|
||||
{$t('发布状态')}
|
||||
{$t(' 次')}
|
||||
{$t('每分钟')}
|
||||
{$t('每5分钟')}
|
||||
{$t('每小时')}
|
||||
{$t('每天')}
|
||||
{$t('每周')}
|
||||
{$t('上线结果')}
|
||||
{$t('订阅服务数量')}
|
||||
{$t('鉴权数量')}
|
||||
{$t('列表')}
|
||||
{$t('块')}
|
||||
{$t('HTTP 请求头')}
|
||||
{$t('全等匹配')}
|
||||
{$t('前缀匹配')}
|
||||
{$t('后缀匹配')}
|
||||
{$t('子串匹配')}
|
||||
{$t('非等匹配')}
|
||||
{$t('空值匹配')}
|
||||
{$t('存在匹配')}
|
||||
{$t('不存在匹配')}
|
||||
{$t('区分大小写的正则匹配')}
|
||||
{$t('不区分大小写的正则匹配')}
|
||||
{$t('任意匹配')}
|
||||
{$t('驳回')}
|
||||
{$t('已订阅')}
|
||||
{$t('取消申请')}
|
||||
{$t('透传客户端请求 Host')}
|
||||
{$t('使用上游服务 Host')}
|
||||
{$t('重写 Host')}
|
||||
{$t('动态服务发现')}
|
||||
{$t('地址')}
|
||||
{$t('新增')}
|
||||
{$t('申请方消费者')}
|
||||
{$t('策略名称')}
|
||||
{$t('优先级')}
|
||||
{$t('筛选条件')}
|
||||
{$t('处理数')}
|
||||
{$t('数据格式')}
|
||||
{$t('关键字')}
|
||||
{$t('正则表达式')}
|
||||
{$t('手机号')}
|
||||
{$t('身份证号')}
|
||||
{$t('银行卡号')}
|
||||
{$t('金额')}
|
||||
{$t('日期')}
|
||||
{$t('局部显示')}
|
||||
{$t('局部遮蔽')}
|
||||
{$t('截取')}
|
||||
{$t('替换')}
|
||||
{$t('乱序')}
|
||||
{$t('随机字符串')}
|
||||
{$t('自定义字符串')}
|
||||
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
|
||||
{$t('待更新')}
|
||||
{$t('待删除')}
|
||||
{$t('内容')}
|
||||
{$t('调用地址')}
|
||||
{$t('消费者 IP')}
|
||||
{$t('鉴权名称')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,49 +1,47 @@
|
||||
|
||||
import { Button, Tooltip, Upload } from "antd";
|
||||
import { ReactElement, cloneElement, useEffect, useMemo, useState } from "react";
|
||||
import { useGlobalContext } from "../../contexts/GlobalStateContext";
|
||||
import { PERMISSION_DEFINITION } from "@common/const/permissions";
|
||||
import { $t } from "@common/locales";
|
||||
import { last } from "lodash-es";
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions'
|
||||
import { $t } from '@common/locales'
|
||||
import { Button, Tooltip, Upload } from 'antd'
|
||||
import { ReactElement, cloneElement, useEffect, useMemo, useState } from 'react'
|
||||
import { useGlobalContext } from '../../contexts/GlobalStateContext'
|
||||
|
||||
type WithPermissionProps = {
|
||||
access?:string | string[]
|
||||
tooltip?:string
|
||||
children:ReactElement
|
||||
disabled?:boolean
|
||||
showDisabled?:boolean
|
||||
access?: string | string[]
|
||||
tooltip?: string
|
||||
children: ReactElement
|
||||
disabled?: boolean
|
||||
showDisabled?: boolean
|
||||
}
|
||||
// 权限控制的高阶组件
|
||||
const WithPermission = ({access, tooltip, children,disabled, showDisabled = true}:WithPermissionProps) => {
|
||||
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false:true)
|
||||
const {accessData,checkPermission,accessInit} = useGlobalContext()
|
||||
const WithPermission = ({ access, tooltip, children, disabled, showDisabled = true }: WithPermissionProps) => {
|
||||
const [editAccess, setEditAccess] = useState<boolean>(access ? false : true)
|
||||
const { accessData, checkPermission, accessInit } = useGlobalContext()
|
||||
|
||||
const lastAccess = useMemo(()=>{
|
||||
if(!access) return true
|
||||
return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0])
|
||||
},[access, accessData,checkPermission,accessInit])
|
||||
const lastAccess = useMemo(() => {
|
||||
if (!access) return true
|
||||
return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0])
|
||||
}, [access, accessData, checkPermission, accessInit])
|
||||
|
||||
useEffect(()=>{
|
||||
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
|
||||
access && setEditAccess(lastAccess)
|
||||
},[lastAccess,disabled])
|
||||
useEffect(() => {
|
||||
// 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture
|
||||
access && setEditAccess(lastAccess)
|
||||
}, [lastAccess, disabled])
|
||||
|
||||
return (
|
||||
<>
|
||||
{}
|
||||
{editAccess && !disabled && cloneElement(children)}
|
||||
{editAccess && disabled && <Tooltip title={tooltip}>{cloneElement(children, { disabled: true })}</Tooltip>}
|
||||
{!editAccess && children?.type !== Button && children?.type !== Upload && showDisabled && (
|
||||
<Tooltip title={tooltip ?? $t('暂无操作权限,请联系管理员分配。')}>
|
||||
{cloneElement(children, {
|
||||
disabled: true,
|
||||
onClick: (e) => e.preventDefault(),
|
||||
okButtonProps: { disabled: true }
|
||||
})}
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<>{
|
||||
}
|
||||
{editAccess && !disabled && cloneElement(children)}
|
||||
{editAccess && disabled && <Tooltip title={tooltip}>
|
||||
{ cloneElement(children, {disabled:true})}
|
||||
</Tooltip>}
|
||||
{!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled) && <Tooltip title={tooltip ?? $t("暂无操作权限,请联系管理员分配。")}>
|
||||
{ cloneElement(children, {disabled:true,okButtonProps:{disabled:true}})}
|
||||
</Tooltip>}
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default WithPermission
|
||||
export default WithPermission
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { ExoticComponent, JSXElementConstructor, useEffect, useState } from 'react'
|
||||
import { useBlocker, useLocation } from 'react-router-dom'
|
||||
import { JSX } from 'react/jsx-runtime'
|
||||
|
||||
const withRouteGuard = (
|
||||
WrappedComponent: ExoticComponent<any> | JSXElementConstructor<any>,
|
||||
{
|
||||
canActivate,
|
||||
canLoad,
|
||||
canDeactivate,
|
||||
deactivated,
|
||||
pathPrefix
|
||||
}: {
|
||||
pathPrefix?: string
|
||||
canActivate?: () => Promise<boolean>
|
||||
canLoad?: () => Promise<boolean>
|
||||
canDeactivate?: () => Promise<boolean>
|
||||
deactivated?: () => Promise<void>
|
||||
} = {}
|
||||
) => {
|
||||
return function RouteGuard(props: JSX.IntrinsicAttributes) {
|
||||
const [isActivated, setIsActivated] = useState<boolean>(false)
|
||||
const location = useLocation()
|
||||
// check canActivate
|
||||
const startLifecycle = async () => {
|
||||
if (canActivate) {
|
||||
const activateRes = await canActivate()
|
||||
setIsActivated(activateRes)
|
||||
} else {
|
||||
setIsActivated(true)
|
||||
}
|
||||
}
|
||||
|
||||
// check canDeactivate
|
||||
const handleBeforeUnload = async (event: { preventDefault: () => void; returnValue: string }) => {
|
||||
const deactivateRes = canDeactivate ? await canDeactivate() : true
|
||||
if (!deactivateRes) {
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 激活组件时的检查
|
||||
useEffect(() => {
|
||||
startLifecycle()
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
deactivated?.()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const blocker = useBlocker((tx) => {
|
||||
const currentPath = location.pathname
|
||||
const targetPath = tx.nextLocation.pathname
|
||||
|
||||
if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) {
|
||||
canDeactivate().then((res) => {
|
||||
if (res) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const checkCanLoad = async () => {
|
||||
const loadRes = await canLoad!()
|
||||
!loadRes && setIsActivated(false)
|
||||
}
|
||||
useEffect(() => {
|
||||
if (isActivated && canLoad) {
|
||||
checkCanLoad()
|
||||
}
|
||||
}, [isActivated])
|
||||
|
||||
return isActivated ? <WrappedComponent {...props} /> : null
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouteGuard
|
||||
+124
-131
@@ -1,150 +1,143 @@
|
||||
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
import { Input } from 'antd'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
export const ArrayItemBlankComponent = forwardRef(
|
||||
(props: { [k: string]: any }, ref) => {
|
||||
const { onChange, value, dataFormat } = props
|
||||
export const ArrayItemBlankComponent = forwardRef((props: { [k: string]: any }, ref) => {
|
||||
const { onChange, value, dataFormat } = props
|
||||
|
||||
const getDefaultListItem = () => {
|
||||
const defaultData: { [k: string]: unknown } = {}
|
||||
const getDefaultListItem = () => {
|
||||
const defaultData: { [k: string]: unknown } = {}
|
||||
|
||||
for (const data of dataFormat) {
|
||||
defaultData[data.key] = ''
|
||||
}
|
||||
|
||||
return [defaultData]
|
||||
for (const data of dataFormat) {
|
||||
defaultData[data.key] = ''
|
||||
}
|
||||
|
||||
const [resList, setResList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...value
|
||||
?.filter((v: string) => {
|
||||
return v
|
||||
})
|
||||
?.map((v: string) => {
|
||||
const vTmp = v
|
||||
const newValue: { [k: string]: unknown } = {}
|
||||
for (let index = 0; index < dataFormat.length; index++) {
|
||||
if (dataFormat[index]?.hideName) {
|
||||
newValue[dataFormat[index].key] = vTmp.split(' ')[index]
|
||||
} else {
|
||||
const vTmp2: string | string[] | undefined =
|
||||
vTmp.indexOf(' ') === -1
|
||||
? vTmp
|
||||
: vTmp.split(' ')[index]
|
||||
return [defaultData]
|
||||
}
|
||||
|
||||
const [resList, setResList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...value
|
||||
?.filter((v: string) => {
|
||||
return v
|
||||
})
|
||||
?.map((v: string) => {
|
||||
const vTmp = v
|
||||
const newValue: { [k: string]: unknown } = {}
|
||||
for (let index = 0; index < dataFormat.length; index++) {
|
||||
if (dataFormat[index]?.hideName) {
|
||||
newValue[dataFormat[index].key] = vTmp.split(' ')[index]
|
||||
} else {
|
||||
const vTmp2: string | string[] | undefined =
|
||||
vTmp.indexOf(' ') === -1
|
||||
? vTmp
|
||||
: vTmp.split(' ')[index]
|
||||
? vTmp.split(' ')[index].indexOf('=') === -1
|
||||
? ''
|
||||
: vTmp.split(' ')[index].split('=')
|
||||
: ''
|
||||
|
||||
if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) {
|
||||
vTmp2.shift()
|
||||
}
|
||||
newValue[dataFormat[index].key] =
|
||||
vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2
|
||||
if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) {
|
||||
vTmp2.shift()
|
||||
}
|
||||
newValue[dataFormat[index].key] = vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2
|
||||
}
|
||||
return newValue
|
||||
}),
|
||||
...getDefaultListItem()
|
||||
]
|
||||
: [...getDefaultListItem()]
|
||||
)
|
||||
}
|
||||
return newValue
|
||||
}),
|
||||
...getDefaultListItem()
|
||||
]
|
||||
: [...getDefaultListItem()]
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
const emitNewArr = () => {
|
||||
const newArr: Array<string> = []
|
||||
for (const r of resList) {
|
||||
if (r[dataFormat[0].key]) {
|
||||
newArr.push(
|
||||
dataFormat?.map((format: { key: string; hideName: boolean }) => {
|
||||
return format?.hideName
|
||||
? r[format.key]
|
||||
: `${format.key}=${r[format.key]}`
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
onChange(newArr)
|
||||
}
|
||||
|
||||
const changeInputValue = (
|
||||
newValue: string,
|
||||
index: number,
|
||||
keyName: string,
|
||||
dataFormat: unknown
|
||||
) => {
|
||||
const newArr = [...resList]
|
||||
newArr[index][keyName] = newValue
|
||||
newArr[index].status =
|
||||
(dataFormat.required && !newValue) ||
|
||||
(dataFormat.pattern && !dataFormat.pattern.test(newValue))
|
||||
? 'error'
|
||||
: ''
|
||||
setResList(newArr)
|
||||
emitNewArr()
|
||||
if (index === resList.length - 1) {
|
||||
setResList([...newArr, ...getDefaultListItem()])
|
||||
const emitNewArr = () => {
|
||||
const newArr: Array<string> = []
|
||||
for (const r of resList) {
|
||||
if (r[dataFormat[0].key]) {
|
||||
newArr.push(
|
||||
dataFormat
|
||||
?.map((format: { key: string; hideName: boolean }) => {
|
||||
return format?.hideName ? r[format.key] : `${format.key}=${r[format.key]}`
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
resList.splice(index + 1, 0, ...getDefaultListItem())
|
||||
const newKvList = [...resList]
|
||||
setResList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
resList.splice(index, 1)
|
||||
const newKvList = [...resList]
|
||||
setResList([...newKvList])
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div
|
||||
key={n + index}
|
||||
className="flex"
|
||||
style={{ marginTop: index === 0 ? '0px' : '16px' }}
|
||||
>
|
||||
{dataFormat?.map((data: unknown, index2: unknown) => {
|
||||
return (
|
||||
<Input
|
||||
key={data.key + index2}
|
||||
className="mr-[8px]"
|
||||
style={{ width: data.width }}
|
||||
value={n[data.key]}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, data.key, data)
|
||||
}}
|
||||
placeholder={data.placeholder || `请输入${data.key}`}
|
||||
status={n.status}
|
||||
type={data.type || 'text'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{index !== resList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n[dataFormat[0].key] && (
|
||||
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
|
||||
)}
|
||||
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
onChange(newArr)
|
||||
}
|
||||
)
|
||||
|
||||
const changeInputValue = (newValue: string, index: number, keyName: string, dataFormat: unknown) => {
|
||||
const newArr = [...resList]
|
||||
newArr[index][keyName] = newValue
|
||||
newArr[index].status =
|
||||
(dataFormat.required && !newValue) || (dataFormat.pattern && !dataFormat.pattern.test(newValue)) ? 'error' : ''
|
||||
setResList(newArr)
|
||||
emitNewArr()
|
||||
if (index === resList.length - 1) {
|
||||
setResList([...newArr, ...getDefaultListItem()])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
resList.splice(index + 1, 0, ...getDefaultListItem())
|
||||
const newKvList = [...resList]
|
||||
setResList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
resList.splice(index, 1)
|
||||
const newKvList = [...resList]
|
||||
setResList([...newKvList])
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div key={n + index} className="flex" style={{ marginTop: index === 0 ? '0px' : '16px' }}>
|
||||
{dataFormat?.map((data: unknown, index2: unknown) => {
|
||||
return (
|
||||
<Input
|
||||
key={data.key + index2}
|
||||
className="mr-[8px]"
|
||||
style={{ width: data.width }}
|
||||
value={n[data.key]}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, data.key, data)
|
||||
}}
|
||||
placeholder={data.placeholder || `请输入${data.key}`}
|
||||
status={n.status}
|
||||
type={data.type || 'text'}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
{index !== resList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n[dataFormat[0].key] && (
|
||||
<Icon
|
||||
icon="ic:baseline-add"
|
||||
onClick={() => addLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="ic:baseline-minus"
|
||||
onClick={() => removeLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+30
-42
@@ -1,47 +1,35 @@
|
||||
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
import { Codebox } from '@common/components/postcat/api/Codebox'
|
||||
|
||||
export const CustomCodeboxComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const {
|
||||
mode = 'yaml',
|
||||
theme = 'xcode',
|
||||
fontSize,
|
||||
height,
|
||||
width = '100%',
|
||||
onChange,
|
||||
value
|
||||
} = props
|
||||
const [code, setCode] = useState(
|
||||
mode === 'json' ? JSON.stringify(value) : value
|
||||
)
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
const handleChange = (value: string) => {
|
||||
setCode(value)
|
||||
let res = value
|
||||
if (mode === 'json') {
|
||||
try {
|
||||
res = JSON.parse(value)
|
||||
} catch {
|
||||
console.warn(' 输入的json语句格式有误')
|
||||
}
|
||||
export const CustomCodeboxComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { mode = 'yaml', theme = 'xcode', fontSize, height, width = '100%', onChange, value } = props
|
||||
const [code, setCode] = useState(mode === 'json' ? JSON.stringify(value) : value)
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
const handleChange = (value: string) => {
|
||||
setCode(value)
|
||||
let res = value
|
||||
if (mode === 'json') {
|
||||
try {
|
||||
res = JSON.parse(value)
|
||||
} catch {
|
||||
console.warn(' 输入的json语句格式有误')
|
||||
}
|
||||
onChange(res)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" mt-[4px] border-[1px] border-solid border-BORDER">
|
||||
<Codebox
|
||||
value={code}
|
||||
language={mode}
|
||||
enableToolbar={false}
|
||||
theme={theme}
|
||||
fontSize={fontSize}
|
||||
height={height ?? 500}
|
||||
width={width}
|
||||
onChange={handleChange} />
|
||||
</div>
|
||||
)
|
||||
onChange(res)
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className=" mt-[4px] border-[1px] border-solid border-BORDER">
|
||||
<Codebox
|
||||
value={code}
|
||||
language={mode}
|
||||
enableToolbar={false}
|
||||
theme={theme}
|
||||
fontSize={fontSize}
|
||||
height={height ?? 500}
|
||||
width={width}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+48
-53
@@ -1,5 +1,4 @@
|
||||
|
||||
import {forwardRef,useImperativeHandle} from 'react'
|
||||
import { forwardRef, useImperativeHandle } from 'react'
|
||||
import { createSchemaField } from '@formily/react'
|
||||
import {
|
||||
FormItem,
|
||||
@@ -82,57 +81,53 @@ const SchemaField = createSchemaField({
|
||||
}
|
||||
})
|
||||
|
||||
export const CustomDialogComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, title, value, render } = props
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
let editPage: boolean = false
|
||||
try {
|
||||
editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0
|
||||
} catch {}
|
||||
export const CustomDialogComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, title, value, render } = props
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
let editPage: boolean = false
|
||||
try {
|
||||
editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0
|
||||
} catch {}
|
||||
|
||||
return (
|
||||
<FormDialog.Portal>
|
||||
<span
|
||||
className="ant-formily-array-base-config"
|
||||
onClick={() => {
|
||||
const dialog = FormDialog(
|
||||
editPage ? $t('编辑(0)',[title||'']) : $t('添加(0)',[title||'']),
|
||||
() => {
|
||||
return (
|
||||
<FormLayout
|
||||
// labelCol={6}
|
||||
layout={'vertical'}
|
||||
scrollToFirstError
|
||||
name="CustomDialogComponent"
|
||||
// wrapperCol={10}
|
||||
form={value}>
|
||||
<SchemaField schema={JSON.parse(render)} />
|
||||
</FormLayout>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<FormDialog.Portal>
|
||||
<span
|
||||
className="ant-formily-array-base-config"
|
||||
onClick={() => {
|
||||
const dialog = FormDialog(editPage ? $t('编辑(0)', [title || '']) : $t('添加(0)', [title || '']), () => {
|
||||
return (
|
||||
<FormLayout
|
||||
// labelCol={6}
|
||||
layout={'vertical'}
|
||||
scrollToFirstError
|
||||
name="CustomDialogComponent"
|
||||
// wrapperCol={10}
|
||||
form={value}
|
||||
>
|
||||
<SchemaField schema={JSON.parse(render)} />
|
||||
</FormLayout>
|
||||
)
|
||||
dialog
|
||||
.forOpen((payload, next) => {
|
||||
next({
|
||||
initialValues: value
|
||||
})
|
||||
})
|
||||
dialog
|
||||
.forOpen((payload, next) => {
|
||||
next({
|
||||
initialValues: value
|
||||
})
|
||||
.forConfirm((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.forCancel((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.open()
|
||||
.then(onChange)
|
||||
}}
|
||||
>
|
||||
<svg style={{ width: '16px', height: '16px' }}>
|
||||
<use href="#tool"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</FormDialog.Portal>
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
.forConfirm((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.forCancel((payload, next) => {
|
||||
next(payload)
|
||||
})
|
||||
.open()
|
||||
.then(onChange)
|
||||
}}
|
||||
>
|
||||
<svg style={{ width: '16px', height: '16px' }}>
|
||||
<use href="#tool"></use>
|
||||
</svg>
|
||||
</span>
|
||||
</FormDialog.Portal>
|
||||
)
|
||||
})
|
||||
|
||||
+90
-95
@@ -1,104 +1,99 @@
|
||||
import {forwardRef, useImperativeHandle, useState} from 'react'
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react'
|
||||
|
||||
import { Input } from '@formily/antd-v5'
|
||||
import { $t } from '@common/locales'
|
||||
import { Icon } from '@iconify/react/dist/iconify.js'
|
||||
export const SimpleMapComponent = forwardRef(
|
||||
(props: { [k: string]: unknown }, ref) => {
|
||||
const {
|
||||
onChange,
|
||||
value,
|
||||
placeholderKey = $t('请输入Key'),
|
||||
placeholderValue = $t('请输入Value')
|
||||
} = props
|
||||
export const SimpleMapComponent = forwardRef((props: { [k: string]: unknown }, ref) => {
|
||||
const { onChange, value, placeholderKey = $t('请输入Key'), placeholderValue = $t('请输入Value') } = props
|
||||
|
||||
const [kvList, setKvList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...Object.keys(value)?.map((k: string) => {
|
||||
return { key: k, value: value[k] }
|
||||
}),
|
||||
{ key: '', value: '' }
|
||||
]
|
||||
: [{ key: '', value: '' }]
|
||||
)
|
||||
const [kvList, setKvList] = useState(
|
||||
value && Object.keys(value).length > 0
|
||||
? [
|
||||
...Object.keys(value)?.map((k: string) => {
|
||||
return { key: k, value: value[k] }
|
||||
}),
|
||||
{ key: '', value: '' }
|
||||
]
|
||||
: [{ key: '', value: '' }]
|
||||
)
|
||||
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
useImperativeHandle(ref, () => ({}))
|
||||
|
||||
const emitNewArr = () => {
|
||||
const res: { [k: string]: unknown } = {}
|
||||
for (const kv of kvList) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
onChange(res)
|
||||
const emitNewArr = () => {
|
||||
const res: { [k: string]: unknown } = {}
|
||||
for (const kv of kvList) {
|
||||
res[kv.key] = kv.value
|
||||
}
|
||||
|
||||
const changeInputValue = (
|
||||
newValue: string,
|
||||
index: number,
|
||||
type: 'key' | 'value'
|
||||
) => {
|
||||
const newArr = [...kvList]
|
||||
newArr[index][type] = newValue
|
||||
setKvList(newArr)
|
||||
emitNewArr()
|
||||
if (index === kvList.length - 1) {
|
||||
setKvList([...newArr, { key: '', value: '' }])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
kvList.splice(index + 1, 0, { key: '', value: '' })
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
kvList.splice(index, 1)
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kvList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div
|
||||
key={n + index}
|
||||
className="flex"
|
||||
style={{ marginTop: index === 0 ? '0px' : '16px' }}
|
||||
>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL mr-[8px]"
|
||||
style={{ width: '174px' }}
|
||||
value={n.key}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'key')
|
||||
}}
|
||||
placeholder={placeholderKey}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '164px', marginRight: '10px' }}
|
||||
value={n.value}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'value')
|
||||
}}
|
||||
placeholder={placeholderValue}
|
||||
/>
|
||||
{index !== kvList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n.key && (
|
||||
<Icon icon="ic:baseline-add" onClick={() => addLine(index as unknown as number)} width="14" height="14"/>
|
||||
)}
|
||||
<Icon icon="ic:baseline-minus" onClick={() => removeLine(index as unknown as number)} width="14" height="14"/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
onChange(res)
|
||||
}
|
||||
)
|
||||
|
||||
const changeInputValue = (newValue: string, index: number, type: 'key' | 'value') => {
|
||||
const newArr = [...kvList]
|
||||
newArr[index][type] = newValue
|
||||
setKvList(newArr)
|
||||
emitNewArr()
|
||||
if (index === kvList.length - 1) {
|
||||
setKvList([...newArr, { key: '', value: '' }])
|
||||
}
|
||||
}
|
||||
|
||||
const addLine = (index: number) => {
|
||||
kvList.splice(index + 1, 0, { key: '', value: '' })
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
const removeLine = (index: number) => {
|
||||
kvList.splice(index, 1)
|
||||
const newKvList = [...kvList]
|
||||
setKvList(newKvList)
|
||||
emitNewArr()
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kvList?.map((n: unknown, index: unknown) => {
|
||||
return (
|
||||
<div key={n + index} className="flex" style={{ marginTop: index === 0 ? '0px' : '16px' }}>
|
||||
<Input
|
||||
className="w-INPUT_NORMAL mr-[8px]"
|
||||
style={{ width: '174px' }}
|
||||
value={n.key}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'key')
|
||||
}}
|
||||
placeholder={placeholderKey}
|
||||
/>
|
||||
<Input
|
||||
style={{ width: '164px', marginRight: '10px' }}
|
||||
value={n.value}
|
||||
onChange={(e: unknown) => {
|
||||
changeInputValue(e.target.value, index, 'value')
|
||||
}}
|
||||
placeholder={placeholderValue}
|
||||
/>
|
||||
{index !== kvList.length - 1 && (
|
||||
<div style={{ display: 'inline-block' }}>
|
||||
{n.key && (
|
||||
<Icon
|
||||
icon="ic:baseline-add"
|
||||
onClick={() => addLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
icon="ic:baseline-minus"
|
||||
onClick={() => removeLine(index as unknown as number)}
|
||||
width="14"
|
||||
height="14"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
+292
-286
@@ -1,327 +1,333 @@
|
||||
import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'
|
||||
import { action } from '@formily/reactive'
|
||||
import {
|
||||
FormItem,
|
||||
Space,
|
||||
ArrayItems,
|
||||
DatePicker,
|
||||
Editable,
|
||||
FormButtonGroup,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Submit,
|
||||
Cascader,
|
||||
Form,
|
||||
FormGrid,
|
||||
FormLayout,
|
||||
Upload,
|
||||
FormItem,
|
||||
Space,
|
||||
ArrayItems,
|
||||
DatePicker,
|
||||
Editable,
|
||||
FormButtonGroup,
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
Submit,
|
||||
Cascader,
|
||||
Form,
|
||||
FormGrid,
|
||||
FormLayout,
|
||||
Upload,
|
||||
ArrayCollapse,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Checkbox,
|
||||
FormCollapse,
|
||||
FormDialog,
|
||||
FormDrawer,
|
||||
FormStep,
|
||||
FormTab,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Reset,
|
||||
SelectTable,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
ArrayCards
|
||||
} from '@formily/antd-v5'
|
||||
import { createForm } from '@formily/core'
|
||||
import { CustomCodeboxComponent } from '@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx'
|
||||
import { SimpleMapComponent } from '@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx'
|
||||
import { CustomDialogComponent } from '@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx'
|
||||
import { ArrayItemBlankComponent } from '@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { createSchemaField, FormProvider, RecursionField, useField, useForm } from '@formily/react'
|
||||
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { App } from 'antd'
|
||||
import { $t } from '@common/locales'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
|
||||
import { setValidateLanguage } from '@formily/core'
|
||||
|
||||
export const DynamicRender = (props) => {
|
||||
const { schema } = props
|
||||
const field = useField()
|
||||
const form = useForm()
|
||||
const [renderSchema, setRenderSchema] = useState({})
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
useEffect(() => {
|
||||
form.clearFormGraph(`${field.address}.*`)
|
||||
try {
|
||||
const parsedSchema = JSON.parse(schema)
|
||||
setRenderSchema(parsedSchema[form?.values?.driver])
|
||||
} catch (e) {
|
||||
console.error('渲染出错', e?.message)
|
||||
}
|
||||
}, [form.values.driver])
|
||||
|
||||
const translateSchema = (render) => {
|
||||
const res1 = {
|
||||
...render,
|
||||
...(render.title ? { title: $t(render.title) } : {}),
|
||||
...(render.description ? { description: $t(render.description) } : {}),
|
||||
...(render.label ? { label: $t(render.label) } : {}),
|
||||
...(render.properties
|
||||
? {
|
||||
properties: Object.keys(render.properties).reduce((total, cur) => {
|
||||
try {
|
||||
total[cur] = translateSchema(render.properties[cur])
|
||||
} catch (error) {
|
||||
console.error(`Error translating schema for property ${cur}:`, error)
|
||||
}
|
||||
return total
|
||||
}, {})
|
||||
}
|
||||
: {}),
|
||||
...(render.items && Array.isArray(render.items) ? { items: render.items.map((x) => translateSchema(x)) } : {}),
|
||||
...(render.items && !Array.isArray(render.items) ? { items: translateSchema(render.items) } : {}),
|
||||
...(render.additionalProperties ? { additionalProperties: translateSchema(render.additionalProperties) } : {}),
|
||||
...(render.enum ? { enum: render.enum.map((x) => ({ ...x, label: $t(x.label) })) } : {})
|
||||
}
|
||||
return res1
|
||||
}
|
||||
|
||||
const translatedRenderSchema = useMemo(() => {
|
||||
const res = renderSchema && translateSchema(renderSchema)
|
||||
return res
|
||||
}, [state.language, renderSchema])
|
||||
|
||||
return <RecursionField basePath={field.address} schema={translatedRenderSchema} onlyRenderProperties />
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigProps = {
|
||||
type: 'add' | 'edit'
|
||||
renderSchema: unknown
|
||||
tabData: DefaultOptionType[]
|
||||
moduleId: string
|
||||
driverSelectionOptions: DefaultOptionType[]
|
||||
entityId?: string
|
||||
initFormValue: { [k: string]: unknown }
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigHandle = {
|
||||
save: () => Promise<boolean | string>
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
ArrayCards,
|
||||
ArrayCollapse,
|
||||
ArrayItems,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Editable,
|
||||
Form,
|
||||
FormButtonGroup,
|
||||
FormCollapse,
|
||||
// @ts-ignore
|
||||
FormDialog,
|
||||
// @ts-ignore
|
||||
FormDrawer,
|
||||
FormGrid,
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormStep,
|
||||
FormTab,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Radio,
|
||||
Reset,
|
||||
Select,
|
||||
SelectTable,
|
||||
Space,
|
||||
Submit,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
ArrayCards
|
||||
} from '@formily/antd-v5'
|
||||
import { createForm } from '@formily/core'
|
||||
import {CustomCodeboxComponent} from "@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx";
|
||||
import {SimpleMapComponent} from "@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx";
|
||||
import {CustomDialogComponent} from "@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx";
|
||||
import {ArrayItemBlankComponent} from "@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx";
|
||||
import {DefaultOptionType} from "antd/es/cascader";
|
||||
import {createSchemaField, FormProvider, RecursionField, useField, useForm} from "@formily/react";
|
||||
import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {App, Descriptions} from "antd";
|
||||
import { $t } from "@common/locales";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
|
||||
import { setValidateLanguage } from '@formily/core'
|
||||
|
||||
export const DynamicRender = (props) => {
|
||||
const {schema} = props
|
||||
const field = useField()
|
||||
const form = useForm()
|
||||
const [renderSchema, setRenderSchema] = useState({})
|
||||
const {state} = useGlobalContext()
|
||||
|
||||
useEffect(() => {
|
||||
form.clearFormGraph(`${field.address}.*`)
|
||||
try{
|
||||
const parsedSchema = JSON.parse(schema)
|
||||
setRenderSchema(parsedSchema[form?.values?.driver])
|
||||
}catch(e){
|
||||
console.error('渲染出错',e?.message)
|
||||
}
|
||||
}, [form.values.driver])
|
||||
|
||||
|
||||
const translateSchema = (render) =>{
|
||||
const res1 = {
|
||||
...render,
|
||||
...(render.title ? {title:$t(render.title)} : {}),
|
||||
...(render.description) ? {description:$t(render.description)} : {},
|
||||
...(render.label ? {label:$t(render.label)} : {}),
|
||||
...(render.properties ? {properties: Object.keys(render.properties).reduce((total, cur) => {
|
||||
try {
|
||||
total[cur] = translateSchema(render.properties[cur]);
|
||||
} catch (error) {
|
||||
console.error(`Error translating schema for property ${cur}:`, error);
|
||||
}
|
||||
return total;
|
||||
}, {})} : {}),
|
||||
...(render.items && Array.isArray(render.items) ? {items:render.items.map(x=>translateSchema(x))} : {}),
|
||||
...(render.items && !Array.isArray(render.items) ? {items:translateSchema(render.items)} : {}),
|
||||
...(render.additionalProperties ? {additionalProperties: translateSchema(render.additionalProperties)} : {}),
|
||||
...(render.enum ? {enum: render.enum.map(x=>({...x, label:$t(x.label)}))} : {}),
|
||||
|
||||
}
|
||||
return res1
|
||||
}
|
||||
|
||||
const translatedRenderSchema = useMemo(()=>{
|
||||
const res = renderSchema && translateSchema(renderSchema)
|
||||
return res
|
||||
},[state.language,renderSchema])
|
||||
|
||||
return (
|
||||
<RecursionField
|
||||
basePath={field.address}
|
||||
schema={translatedRenderSchema}
|
||||
onlyRenderProperties
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type IntelligentPluginConfigProps = {
|
||||
type:'add'|'edit'
|
||||
renderSchema:unknown
|
||||
tabData:DefaultOptionType[]
|
||||
moduleId:string
|
||||
driverSelectionOptions:DefaultOptionType[]
|
||||
entityId?:string
|
||||
initFormValue:{[k:string]:unknown}
|
||||
}
|
||||
|
||||
export type IntelligentPluginConfigHandle = {
|
||||
save:()=>Promise<boolean | string>
|
||||
}
|
||||
|
||||
const SchemaField = createSchemaField({
|
||||
components: {
|
||||
ArrayCards,
|
||||
ArrayCollapse,
|
||||
ArrayItems,
|
||||
ArrayTable,
|
||||
ArrayTabs,
|
||||
Cascader,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Editable,
|
||||
Form,
|
||||
FormButtonGroup,
|
||||
FormCollapse,
|
||||
// @ts-ignore
|
||||
FormDialog,
|
||||
// @ts-ignore
|
||||
FormDrawer,
|
||||
FormGrid,
|
||||
FormItem,
|
||||
FormLayout,
|
||||
FormStep,
|
||||
FormTab,
|
||||
Input,
|
||||
NumberPicker,
|
||||
Password,
|
||||
PreviewText,
|
||||
Radio,
|
||||
Reset,
|
||||
Select,
|
||||
SelectTable,
|
||||
Space,
|
||||
Submit,
|
||||
Switch,
|
||||
TimePicker,
|
||||
Transfer,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
CustomCodeboxComponent,
|
||||
SimpleMapComponent,
|
||||
CustomDialogComponent,
|
||||
ArrayItemBlankComponent,
|
||||
DynamicRender
|
||||
}
|
||||
Upload,
|
||||
CustomCodeboxComponent,
|
||||
SimpleMapComponent,
|
||||
CustomDialogComponent,
|
||||
ArrayItemBlankComponent,
|
||||
DynamicRender
|
||||
}
|
||||
})
|
||||
|
||||
export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle,IntelligentPluginConfigProps>((props,ref)=>{
|
||||
const { type,renderSchema,moduleId,driverSelectionOptions,initFormValue} = props
|
||||
export const IntelligentPluginConfig = forwardRef<IntelligentPluginConfigHandle, IntelligentPluginConfigProps>(
|
||||
(props, ref) => {
|
||||
const { type, renderSchema, moduleId, driverSelectionOptions, initFormValue } = props
|
||||
const { message } = App.useApp()
|
||||
const {fetchData} = useFetch()
|
||||
const { fetchData } = useFetch()
|
||||
const form = createForm({ validateFirst: type === 'edit' })
|
||||
form.setInitialValues(initFormValue || {})
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
useEffect(()=>{
|
||||
setValidateLanguage(state.language)
|
||||
},[state.language])
|
||||
useEffect(() => {
|
||||
setValidateLanguage(state.language)
|
||||
}, [state.language])
|
||||
|
||||
const pluginEditSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
layout: 'vertical',
|
||||
type: 'object',
|
||||
properties: {
|
||||
layout: {
|
||||
type: 'void',
|
||||
'x-component': 'FormLayout',
|
||||
'x-component-props': {
|
||||
labelCol: 6,
|
||||
wrapperCol: 10,
|
||||
layout: 'vertical'
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: $t('ID'),
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
title: $t('ID'),
|
||||
required: true,
|
||||
pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet),
|
||||
},
|
||||
'x-disabled': type === 'edit'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: $t('名称'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input),
|
||||
}
|
||||
},
|
||||
driver: {
|
||||
type: 'string',
|
||||
title: $t('Driver'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
disabled: type === 'edit'
|
||||
},
|
||||
'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden',
|
||||
enum: [...driverSelectionOptions]
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
title: $t('描述'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol:4,
|
||||
wrapperCol: 20,
|
||||
labelAlign:'left'
|
||||
},
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input),
|
||||
}
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
'x-component': 'DynamicRender',
|
||||
'x-component-props': {
|
||||
schema: JSON.stringify(renderSchema),
|
||||
}
|
||||
}
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.specialStartWithAlphabet)
|
||||
},
|
||||
'x-disabled': type === 'edit'
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
title: $t('名称'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Input',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input)
|
||||
}
|
||||
},
|
||||
driver: {
|
||||
type: 'string',
|
||||
title: $t('Driver'),
|
||||
required: true,
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
disabled: type === 'edit'
|
||||
},
|
||||
'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden',
|
||||
enum: [...driverSelectionOptions]
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
title: $t('描述'),
|
||||
'x-decorator': 'FormItem',
|
||||
'x-decorator-props': {
|
||||
labelCol: 4,
|
||||
wrapperCol: 20,
|
||||
labelAlign: 'left'
|
||||
},
|
||||
'x-component': 'Input.TextArea',
|
||||
'x-component-props': {
|
||||
placeholder: $t(PLACEHOLDER.input)
|
||||
}
|
||||
},
|
||||
config: {
|
||||
type: 'object',
|
||||
'x-component': 'DynamicRender',
|
||||
'x-component-props': {
|
||||
schema: JSON.stringify(renderSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const save :()=>Promise<boolean | string> = ()=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
form.validate().then(()=>{
|
||||
fetchData<BasicResponse<null>>(type === 'add'?`dynamic/${moduleId}`:`dynamic/${moduleId}/config`,{method:type === 'add'? 'POST' : 'PUT',eoBody:form.values, eoParams:{...(type !== 'add' && {id:initFormValue.id})}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> reject(errorInfo))
|
||||
}).catch((errorInfo:unknown)=> reject(errorInfo))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, ()=>({
|
||||
save
|
||||
})
|
||||
)
|
||||
const save: () => Promise<boolean | string> = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
form
|
||||
.validate()
|
||||
.then(() => {
|
||||
fetchData<BasicResponse<null>>(type === 'add' ? `dynamic/${moduleId}` : `dynamic/${moduleId}/config`, {
|
||||
method: type === 'add' ? 'POST' : 'PUT',
|
||||
eoBody: form.values,
|
||||
eoParams: { ...(type !== 'add' && { id: initFormValue.id }) }
|
||||
})
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => reject(errorInfo))
|
||||
})
|
||||
.catch((errorInfo: unknown) => reject(errorInfo))
|
||||
})
|
||||
}
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
save
|
||||
}))
|
||||
|
||||
const getSkillData = async (skill: string) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
fetchData<BasicResponse<{[k:string]:Array<{name:string,title:string}>}>>(`api/common/provider/${skill}`,{method:'GET'}).then(response=>{
|
||||
const {code,data,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
resolve(data[skill]?.map((x:{name:string,title:string})=>{return{label:x.title, value:x.name}}) || [])
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<{ [k: string]: Array<{ name: string; title: string }> }>>(
|
||||
`api/common/provider/${skill}`,
|
||||
{ method: 'GET' }
|
||||
).then((response) => {
|
||||
const { code, data, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
resolve(
|
||||
data[skill]?.map((x: { name: string; title: string }) => {
|
||||
return { label: x.title, value: x.name }
|
||||
}) || []
|
||||
)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const useAsyncDataSource =
|
||||
(service: unknown, skill: string) => (field: unknown) => {
|
||||
field.loading = true
|
||||
service(skill).then(
|
||||
action.bound &&
|
||||
action.bound((data: unknown) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
const useAsyncDataSource = (service: unknown, skill: string) => (field: unknown) => {
|
||||
field.loading = true
|
||||
service(skill).then(
|
||||
action.bound &&
|
||||
action.bound((data: unknown) => {
|
||||
field.dataSource = data
|
||||
field.loading = false
|
||||
})
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="pl-[12px]">
|
||||
<FormProvider form={form} >
|
||||
<SchemaField
|
||||
schema={pluginEditSchema}
|
||||
scope={{ useAsyncDataSource, getSkillData, form }}
|
||||
/>
|
||||
</FormProvider>
|
||||
</div>)
|
||||
})
|
||||
<div className="pl-[12px]">
|
||||
<FormProvider form={form}>
|
||||
<SchemaField schema={pluginEditSchema} scope={{ useAsyncDataSource, getSkillData, form }} />
|
||||
</FormProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
+400
-315
@@ -1,349 +1,434 @@
|
||||
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
|
||||
import {App, Divider, Spin} from "antd";
|
||||
import {useEffect, useMemo, useRef, useState} from "react";
|
||||
import { useLocation, useOutletContext, useParams} from "react-router-dom";
|
||||
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
|
||||
import {ActionType, ParamsType} from "@ant-design/pro-components";
|
||||
import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx";
|
||||
import {DefaultOptionType} from "antd/es/cascader";
|
||||
import {IntelligentPluginConfig, IntelligentPluginConfigHandle} from "./IntelligentPluginConfig.tsx";
|
||||
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
|
||||
import {useFetch} from "@common/hooks/http.ts";
|
||||
import {EntityItem} from "@common/const/type.ts";
|
||||
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
|
||||
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx";
|
||||
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { $t } from "@common/locales/index.ts";
|
||||
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
|
||||
import { LoadingOutlined } from '@ant-design/icons'
|
||||
import { ActionType, ParamsType } from '@ant-design/pro-components'
|
||||
import { DrawerWithFooter } from '@common/components/aoplatform/DrawerWithFooter.tsx'
|
||||
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx'
|
||||
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
|
||||
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
|
||||
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
|
||||
import { EntityItem } from '@common/const/type.ts'
|
||||
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
|
||||
import { useFetch } from '@common/hooks/http.ts'
|
||||
import { $t } from '@common/locales/index.ts'
|
||||
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
|
||||
import { App, Divider, Spin } from 'antd'
|
||||
import { DefaultOptionType } from 'antd/es/cascader'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useLocation, useOutletContext, useParams } from 'react-router-dom'
|
||||
import { IntelligentPluginConfig, IntelligentPluginConfigHandle } from './IntelligentPluginConfig.tsx'
|
||||
|
||||
type DynamicTableField = {
|
||||
name: string,
|
||||
title: string,
|
||||
attr: string,
|
||||
enum: Array<string>
|
||||
type DynamicTableField = {
|
||||
name: string
|
||||
title: string
|
||||
attr: string
|
||||
enum: Array<string>
|
||||
}
|
||||
|
||||
type DynamicDriverData = {
|
||||
name:string, title:string
|
||||
type DynamicDriverData = {
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type DynamicTableConfig = {
|
||||
basic:{
|
||||
id:string,
|
||||
name: string,
|
||||
title: string,
|
||||
drivers: Array<DynamicDriverData>,
|
||||
fields: Array<DynamicTableField>,
|
||||
}
|
||||
list: Array<DynamicTableItem>,
|
||||
total:number
|
||||
basic: {
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
drivers: Array<DynamicDriverData>
|
||||
fields: Array<DynamicTableField>
|
||||
}
|
||||
list: Array<DynamicTableItem>
|
||||
total: number
|
||||
}
|
||||
|
||||
export type DynamicRender = {
|
||||
render:unknown,
|
||||
basic:{
|
||||
id:string,
|
||||
name:string,
|
||||
title:string
|
||||
}
|
||||
render: unknown
|
||||
basic: {
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
export type DynamicPublishCluster = {
|
||||
name:string,
|
||||
title:string,
|
||||
status:string,
|
||||
updater:EntityItem,
|
||||
update_time:string,
|
||||
checked?:boolean
|
||||
name: string
|
||||
title: string
|
||||
status: string
|
||||
updater: EntityItem
|
||||
update_time: string
|
||||
checked?: boolean
|
||||
}
|
||||
|
||||
export type DynamicPublishData = {
|
||||
id:string,
|
||||
name:string,
|
||||
title:string,
|
||||
description:string
|
||||
clusters:DynamicPublishCluster[]
|
||||
id: string
|
||||
name: string
|
||||
title: string
|
||||
description: string
|
||||
clusters: DynamicPublishCluster[]
|
||||
}
|
||||
|
||||
export type DynamicTableItem = {[k:string]:unknown}
|
||||
export type DynamicTableItem = { [k: string]: unknown }
|
||||
|
||||
export const StatusColorClass = {
|
||||
"已发布":'text-[#03a9f4]',
|
||||
"待发布":'text-[#46BE11]',
|
||||
"未发布":'text-[#03a9f4]'
|
||||
已发布: 'text-[#03a9f4]',
|
||||
待发布: 'text-[#46BE11]',
|
||||
未发布: 'text-[#03a9f4]'
|
||||
}
|
||||
|
||||
|
||||
export type DynamicPublish = {
|
||||
code:number,
|
||||
msg:string,
|
||||
data:{
|
||||
success:Array<string>,
|
||||
fail:Array<string>
|
||||
}
|
||||
code: number
|
||||
msg: string
|
||||
data: {
|
||||
success: Array<string>
|
||||
fail: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
export default function IntelligentPluginList(){
|
||||
const { modal,message } = App.useApp()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { moduleId } = useParams<RouterParams>();
|
||||
const [pluginName,setPluginName] = useState<string>('-')
|
||||
const [partitionOptions] = useState<DefaultOptionType[]>([{label:'default', value:'default'}])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [renderSchema ,setRenderSchema] = useState<{[k:string]:unknown}>({})
|
||||
const drawerFormRef = useRef<IntelligentPluginConfigHandle>(null);
|
||||
const [driverOptions, setDriverOptions] = useState<DefaultOptionType[]>([])
|
||||
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([]);
|
||||
export default function IntelligentPluginList() {
|
||||
const { modal, message } = App.useApp()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const { moduleId } = useParams<RouterParams>()
|
||||
const [pluginName, setPluginName] = useState<string>('-')
|
||||
const [partitionOptions] = useState<DefaultOptionType[]>([{ label: 'default', value: 'default' }])
|
||||
const { setBreadcrumb } = useBreadcrumb()
|
||||
const [renderSchema, setRenderSchema] = useState<{ [k: string]: unknown }>({})
|
||||
const drawerFormRef = useRef<IntelligentPluginConfigHandle>(null)
|
||||
const [driverOptions, setDriverOptions] = useState<DefaultOptionType[]>([])
|
||||
const [tableListDataSource, setTableListDataSource] = useState<DynamicTableItem[]>([])
|
||||
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true);
|
||||
const [columns,setColumns] = useState<DynamicTableField[] >([])
|
||||
const {fetchData} = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null);
|
||||
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
|
||||
const [curDetail,setCurDetail] = useState<{[k: string]: unknown;}|undefined>()
|
||||
const [drawerType, setDrawerType] = useState<'add'|'edit'>('add')
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
|
||||
const location = useLocation().pathname
|
||||
const {accessPrefix} = useOutletContext<{accessPrefix:string}>()
|
||||
const {state} = useGlobalContext()
|
||||
const [tableHttpReload, setTableHttpReload] = useState(true)
|
||||
const [columns, setColumns] = useState<DynamicTableField[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const [publishBtnLoading, setPublishBtnLoading] = useState<boolean>(false)
|
||||
const [curDetail, setCurDetail] = useState<{ [k: string]: unknown } | undefined>()
|
||||
const [drawerType, setDrawerType] = useState<'add' | 'edit'>('add')
|
||||
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
|
||||
const [drawerLoading, setDrawerLoading] = useState<boolean>(false)
|
||||
const location = useLocation().pathname
|
||||
const { accessPrefix } = useOutletContext<{ accessPrefix: string }>()
|
||||
const { state } = useGlobalContext()
|
||||
|
||||
|
||||
const getIntelligentPluginTableList=(params:ParamsType & {
|
||||
pageSize?: number | undefined;
|
||||
current?: number | undefined;
|
||||
keyword?: string | undefined;
|
||||
}): Promise<{ data: DynamicTableItem[], success: boolean }>=> {
|
||||
if(!tableHttpReload){
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true,
|
||||
});
|
||||
const getIntelligentPluginTableList = (
|
||||
params: ParamsType & {
|
||||
pageSize?: number | undefined
|
||||
current?: number | undefined
|
||||
keyword?: string | undefined
|
||||
}
|
||||
): Promise<{ data: DynamicTableItem[]; success: boolean }> => {
|
||||
if (!tableHttpReload) {
|
||||
setTableHttpReload(true)
|
||||
return Promise.resolve({
|
||||
data: tableListDataSource,
|
||||
success: true
|
||||
})
|
||||
}
|
||||
const query = {
|
||||
page: params.current,
|
||||
pageSize: params.pageSize,
|
||||
keyword: searchWord
|
||||
}
|
||||
return fetchData<BasicResponse<DynamicTableConfig>>(`dynamic/${moduleId}/list`, {
|
||||
method: 'GET',
|
||||
eoParams: query,
|
||||
eoTransformKeys: ['pageSize']
|
||||
})
|
||||
.then((res) => {
|
||||
message.destroy()
|
||||
if (res.code === STATUS_CODE.SUCCESS) {
|
||||
getConfig(res.data)
|
||||
setColumns(res.data.basic.fields)
|
||||
setTableListDataSource(res.data.list)
|
||||
return { data: res.data.list, success: true, total: res.data.total }
|
||||
} else {
|
||||
setTableListDataSource([])
|
||||
return { data: [], success: false }
|
||||
}
|
||||
const query = {
|
||||
page:params.current,
|
||||
pageSize:params.pageSize,
|
||||
keyword:searchWord,
|
||||
}
|
||||
return fetchData<BasicResponse<DynamicTableConfig>>(
|
||||
`dynamic/${moduleId}/list`,
|
||||
{method:'GET',eoParams:query,eoTransformKeys:['pageSize']}).then((res)=>{
|
||||
message.destroy();
|
||||
if(res.code === STATUS_CODE.SUCCESS){
|
||||
getConfig(res.data)
|
||||
setColumns(res.data.basic.fields)
|
||||
setTableListDataSource(res.data.list);
|
||||
return ({ data: res.data.list, success: true,total:res.data.total });
|
||||
}else{
|
||||
setTableListDataSource([]);
|
||||
return ({ data: [], success: false });
|
||||
}
|
||||
}).catch((e)=>{console.warn(e);
|
||||
return ({ data: [], success: false });})
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.warn(e)
|
||||
return { data: [], success: false }
|
||||
})
|
||||
}
|
||||
|
||||
const translatedCol = useMemo(()=>columns.map((field:DynamicTableField, index:number)=>({
|
||||
title: typeof field.title === 'string' ? $t(field.title as string): field.title,
|
||||
dataIndex:field.name,
|
||||
fixed:field.name === 'title' ? 'left' : undefined,
|
||||
ellipsis:true,
|
||||
width:field.name === 'title' ? 150 : undefined,
|
||||
...(field.enum?.length > 0 ?{
|
||||
onFilter: (value: string, record: { [x: string]: string | string[]; }) => record[field.name].indexOf(value) === 0,
|
||||
filters:field.enum?.map((x:string)=>{return {text:$t(x), value:x}}),
|
||||
render:(_: unknown, entity: { [x: string]: string; })=> {
|
||||
return <span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>{$t(entity[field.name] as string)}</span>
|
||||
},
|
||||
}:{}),
|
||||
})),[state.language,columns])
|
||||
|
||||
|
||||
|
||||
const getConfig = (data:DynamicTableConfig)=>{
|
||||
const {basic,list } = data
|
||||
const {title,drivers} = basic
|
||||
|
||||
setBreadcrumb([
|
||||
{title:location.includes('resourcesettings') ? $t('资源'): $t('日志')},
|
||||
{
|
||||
title
|
||||
}
|
||||
])
|
||||
|
||||
setPluginName(title)
|
||||
setDriverOptions(drivers?.map((driver:DynamicDriverData) => {
|
||||
return { label: driver.title, value: driver.name }
|
||||
}) || [])
|
||||
|
||||
}
|
||||
|
||||
const getRender = ()=>{
|
||||
return fetchData<BasicResponse<DynamicRender>>(`dynamic/${moduleId}/render`,{method:'GET'}).then((resp) => {
|
||||
if (resp.code === STATUS_CODE.SUCCESS) {
|
||||
setRenderSchema(resp.data.render)
|
||||
return Promise.resolve(resp.data.render)
|
||||
}
|
||||
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
const operation:PageProColumns<DynamicTableItem>[] =[
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
fixed:'right',
|
||||
valueType: 'option',
|
||||
btnNums:3,
|
||||
render: (_: React.ReactNode, entity: DynamicTableItem) => [
|
||||
<TableBtnWithPermission access={`${accessPrefix}.publish`} key="publish" btnType="publish" onClick={()=>{openModal('publish',entity)}} btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1"/>,
|
||||
<TableBtnWithPermission access={`${accessPrefix}.view`} key="edit" btnType="edit" onClick={()=>{openDrawer('edit',entity)}} btnTitle={$t("查看")}/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2"/>,
|
||||
<TableBtnWithPermission access={`${accessPrefix}.delete`} key="delete" btnType="delete" onClick={()=>{openModal('delete',entity)}} btnTitle={$t("删除")}/>,
|
||||
],
|
||||
}
|
||||
]
|
||||
const handleClusterChange = (e:string[])=>{
|
||||
setTableHttpReload(true)
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true); // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
};
|
||||
|
||||
const deleteInstance = (entity:DynamicTableItem)=>{
|
||||
return new Promise((resolve, reject)=>{
|
||||
fetchData<BasicResponse<null>>(`dynamic/${moduleId}/batch`,{method:'DELETE',eoParams:{ids:JSON.stringify([entity!.id])}}).then(response=>{
|
||||
const {code,msg} = response
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = async (type:'add'|'edit', entity?:DynamicTableItem)=>{
|
||||
switch (type){
|
||||
case 'add':
|
||||
setCurDetail({driver:driverOptions[0].value || '',config:{}})
|
||||
break;
|
||||
case 'edit':{
|
||||
setDrawerLoading(true)
|
||||
fetchData<BasicResponse<{info:DynamicTableItem}>>(
|
||||
`dynamic/${moduleId}/info`,
|
||||
{method:'GET',eoParams:{id:entity!.id}}).then((res)=>{
|
||||
const {code, data, msg } = res
|
||||
if(code === STATUS_CODE.SUCCESS){
|
||||
if(data.info.config){
|
||||
}
|
||||
setCurDetail(data.info)
|
||||
}else{
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).finally(()=>setDrawerLoading(false))
|
||||
break;
|
||||
}
|
||||
}
|
||||
setDrawerType(type)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
const openModal = async (type:'publish'|'delete', entity?:DynamicTableItem)=>{
|
||||
let title:string = ''
|
||||
let content:string|React.ReactNode = ''
|
||||
switch (type){
|
||||
case 'publish':{
|
||||
message.loading($t(RESPONSE_TIPS.operating))
|
||||
await fetchData<BasicResponse<DynamicPublish>>(`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline':'online'}`, {
|
||||
method: 'PUT',
|
||||
eoParams:{id:entity!.id},
|
||||
}).then(response => {
|
||||
const {code, msg} = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
}).catch((errorInfo)=> Promise.reject(errorInfo))
|
||||
message.destroy()
|
||||
return;}
|
||||
case 'delete':
|
||||
title='删除'
|
||||
content=<span>{$t(DELETE_TIPS.default)}</span>
|
||||
break;
|
||||
}
|
||||
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk:()=>{
|
||||
switch (type){ // case 'publish':
|
||||
// return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()})
|
||||
case 'delete':
|
||||
return deleteInstance(entity!).then((res)=>{if(res === true) manualReloadTable()})
|
||||
}
|
||||
},
|
||||
width: type === 'delete'? 600 : 900,
|
||||
okText:$t('确认'),
|
||||
okButtonProps:{
|
||||
disabled:false
|
||||
},
|
||||
cancelText:$t('取消'),
|
||||
closable:true,
|
||||
icon:<></>,
|
||||
footer:(_, { OkBtn, CancelBtn }) =>{
|
||||
const translatedCol = useMemo(
|
||||
() =>
|
||||
columns.map((field: DynamicTableField, index: number) => ({
|
||||
title: typeof field.title === 'string' ? $t(field.title as string) : field.title,
|
||||
dataIndex: field.name,
|
||||
fixed: field.name === 'title' ? 'left' : undefined,
|
||||
ellipsis: true,
|
||||
width: field.name === 'title' ? 150 : undefined,
|
||||
...(field.enum?.length > 0
|
||||
? {
|
||||
onFilter: (value: string, record: { [x: string]: string | string[] }) =>
|
||||
record[field.name].indexOf(value) === 0,
|
||||
filters: field.enum?.map((x: string) => {
|
||||
return { text: $t(x), value: x }
|
||||
}),
|
||||
render: (_: unknown, entity: { [x: string]: string }) => {
|
||||
return (
|
||||
<>
|
||||
<WithPermission access=""><CancelBtn/></WithPermission>
|
||||
<WithPermission access=""><OkBtn/></WithPermission>
|
||||
</>
|
||||
);
|
||||
},
|
||||
<span className={StatusColorClass[entity[field.name] as keyof typeof StatusColorClass]}>
|
||||
{$t(entity[field.name] as string)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})),
|
||||
[state.language, columns]
|
||||
)
|
||||
|
||||
const getConfig = (data: DynamicTableConfig) => {
|
||||
const { basic, list } = data
|
||||
const { title, drivers } = basic
|
||||
|
||||
setBreadcrumb([
|
||||
{ title: location.includes('resourcesettings') ? $t('资源') : $t('日志') },
|
||||
{
|
||||
title
|
||||
}
|
||||
])
|
||||
|
||||
setPluginName(title)
|
||||
setDriverOptions(
|
||||
drivers?.map((driver: DynamicDriverData) => {
|
||||
return { label: driver.title, value: driver.name }
|
||||
}) || []
|
||||
)
|
||||
}
|
||||
|
||||
const getRender = () => {
|
||||
return fetchData<BasicResponse<DynamicRender>>(`dynamic/${moduleId}/render`, { method: 'GET' }).then((resp) => {
|
||||
if (resp.code === STATUS_CODE.SUCCESS) {
|
||||
setRenderSchema(resp.data.render)
|
||||
return Promise.resolve(resp.data.render)
|
||||
}
|
||||
return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error))
|
||||
})
|
||||
}
|
||||
|
||||
const operation: PageProColumns<DynamicTableItem>[] = [
|
||||
{
|
||||
title: COLUMNS_TITLE.operate,
|
||||
key: 'option',
|
||||
fixed: 'right',
|
||||
valueType: 'option',
|
||||
btnNums: 3,
|
||||
render: (_: React.ReactNode, entity: DynamicTableItem) => [
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.publish`}
|
||||
key={entity.status === $t('已发布') ? 'offline' : 'publish'}
|
||||
btnType={entity.status === $t('已发布') ? 'offline' : 'publish'}
|
||||
onClick={() => {
|
||||
openModal('publish', entity)
|
||||
}}
|
||||
btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}
|
||||
/>,
|
||||
<Divider type="vertical" className="mx-0" key="div1" />,
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.view`}
|
||||
key="edit"
|
||||
btnType="edit"
|
||||
onClick={() => {
|
||||
openDrawer('edit', entity)
|
||||
}}
|
||||
btnTitle={$t('查看 ')}
|
||||
/>,
|
||||
<Divider type="vertical" className="mx-0" key="div2" />,
|
||||
<TableBtnWithPermission
|
||||
access={`${accessPrefix}.delete`}
|
||||
key="delete"
|
||||
btnType="delete"
|
||||
onClick={() => {
|
||||
openModal('delete', entity)
|
||||
}}
|
||||
btnTitle={$t('删除')}
|
||||
/>
|
||||
]
|
||||
}
|
||||
]
|
||||
const handleClusterChange = (e: string[]) => {
|
||||
setTableHttpReload(true)
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const manualReloadTable = () => {
|
||||
setTableHttpReload(true) // 表格数据需要从后端接口获取
|
||||
pageListRef.current?.reload()
|
||||
}
|
||||
|
||||
const deleteInstance = (entity: DynamicTableItem) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchData<BasicResponse<null>>(`dynamic/${moduleId}/batch`, {
|
||||
method: 'DELETE',
|
||||
eoParams: { ids: JSON.stringify([entity!.id]) }
|
||||
}).then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const openDrawer = async (type: 'add' | 'edit', entity?: DynamicTableItem) => {
|
||||
switch (type) {
|
||||
case 'add':
|
||||
setCurDetail({ driver: driverOptions[0].value || '', config: {} })
|
||||
break
|
||||
case 'edit': {
|
||||
setDrawerLoading(true)
|
||||
fetchData<BasicResponse<{ info: DynamicTableItem }>>(`dynamic/${moduleId}/info`, {
|
||||
method: 'GET',
|
||||
eoParams: { id: entity!.id }
|
||||
})
|
||||
.then((res) => {
|
||||
const { code, data, msg } = res
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
if (data.info.config) {
|
||||
}
|
||||
setCurDetail(data.info)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.finally(() => setDrawerLoading(false))
|
||||
break
|
||||
}
|
||||
}
|
||||
setDrawerType(type)
|
||||
setDrawerOpen(true)
|
||||
}
|
||||
|
||||
const openModal = async (type: 'publish' | 'delete', entity?: DynamicTableItem) => {
|
||||
let title: string = ''
|
||||
let content: string | React.ReactNode = ''
|
||||
switch (type) {
|
||||
case 'publish': {
|
||||
message.loading($t(RESPONSE_TIPS.operating))
|
||||
await fetchData<BasicResponse<DynamicPublish>>(
|
||||
`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline' : 'online'}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
eoParams: { id: entity!.id }
|
||||
}
|
||||
)
|
||||
.then((response) => {
|
||||
const { code, msg } = response
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
message.success(msg || $t(RESPONSE_TIPS.success))
|
||||
manualReloadTable()
|
||||
return Promise.resolve(true)
|
||||
} else {
|
||||
message.error(msg || $t(RESPONSE_TIPS.error))
|
||||
return Promise.reject(msg || $t(RESPONSE_TIPS.error))
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => Promise.reject(errorInfo))
|
||||
return
|
||||
}
|
||||
case 'delete':
|
||||
title = '删除'
|
||||
content = <span>{$t(DELETE_TIPS.default)}</span>
|
||||
break
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getRender()
|
||||
pageListRef.current?.reload()
|
||||
}, [moduleId]);
|
||||
modal.confirm({
|
||||
title,
|
||||
content,
|
||||
onOk: () => {
|
||||
switch (
|
||||
type // case 'publish':
|
||||
) {
|
||||
// return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()})
|
||||
case 'delete':
|
||||
return deleteInstance(entity!).then((res) => {
|
||||
if (res === true) manualReloadTable()
|
||||
})
|
||||
}
|
||||
},
|
||||
width: type === 'delete' ? 600 : 900,
|
||||
okText: $t('确认'),
|
||||
okButtonProps: {
|
||||
disabled: false
|
||||
},
|
||||
cancelText: $t('取消'),
|
||||
closable: true,
|
||||
icon: <></>,
|
||||
footer: (_, { OkBtn, CancelBtn }) => {
|
||||
return (
|
||||
<>
|
||||
<WithPermission access="">
|
||||
<CancelBtn />
|
||||
</WithPermission>
|
||||
<WithPermission access="">
|
||||
<OkBtn />
|
||||
</WithPermission>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getRender()
|
||||
pageListRef.current?.reload()
|
||||
}, [moduleId])
|
||||
|
||||
return (<>
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
columns = {[...translatedCol,...operation]}
|
||||
request={(params)=>getIntelligentPluginTableList(params)}
|
||||
addNewBtnTitle={$t('添加(0)',[$t(pluginName)])}
|
||||
searchPlaceholder={$t('搜索(0)名称',[$t(pluginName)])}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
addNewBtnAccess={`${accessPrefix}.add`}
|
||||
onAddNewBtnClick={()=>{openDrawer('add')}}
|
||||
onSearchWordChange={(e)=>{setSearchWord(e.target.value);setTableHttpReload(true);setTableHttpReload(true)}}
|
||||
/>
|
||||
|
||||
<DrawerWithFooter title={`${drawerType === 'add' ? $t('添加(0)',[$t(pluginName)]) : $t('编辑(0)',[$t(pluginName)])}`} open={drawerOpen} onClose={()=>{setCurDetail(undefined);setDrawerOpen(false)}} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} submitAccess=''>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin/>} spinning={drawerLoading}>
|
||||
<IntelligentPluginConfig
|
||||
ref={drawerFormRef!}
|
||||
type={drawerType}
|
||||
renderSchema={renderSchema}
|
||||
tabData={partitionOptions}
|
||||
moduleId={moduleId!}
|
||||
driverSelectionOptions={driverOptions}
|
||||
initFormValue={curDetail as { [k: string]: unknown; }} />
|
||||
</Spin>
|
||||
</DrawerWithFooter>
|
||||
</>)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PageList
|
||||
ref={pageListRef}
|
||||
columns={[...translatedCol, ...operation]}
|
||||
request={(params) => getIntelligentPluginTableList(params)}
|
||||
addNewBtnTitle={$t('添加(0)', [$t(pluginName)])}
|
||||
searchPlaceholder={$t('搜索(0)名称', [$t(pluginName)])}
|
||||
onChange={() => {
|
||||
setTableHttpReload(false)
|
||||
}}
|
||||
addNewBtnAccess={`${accessPrefix}.add`}
|
||||
onAddNewBtnClick={() => {
|
||||
openDrawer('add')
|
||||
}}
|
||||
onSearchWordChange={(e) => {
|
||||
setSearchWord(e.target.value)
|
||||
setTableHttpReload(true)
|
||||
setTableHttpReload(true)
|
||||
}}
|
||||
/>
|
||||
|
||||
<DrawerWithFooter
|
||||
title={`${drawerType === 'add' ? $t('添加(0)', [$t(pluginName)]) : $t('编辑(0)', [$t(pluginName)])}`}
|
||||
open={drawerOpen}
|
||||
onClose={() => {
|
||||
setCurDetail(undefined)
|
||||
setDrawerOpen(false)
|
||||
}}
|
||||
onSubmit={() =>
|
||||
drawerFormRef.current?.save()?.then((res) => {
|
||||
res && manualReloadTable()
|
||||
return res
|
||||
})
|
||||
}
|
||||
submitAccess=""
|
||||
>
|
||||
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={drawerLoading}>
|
||||
<IntelligentPluginConfig
|
||||
ref={drawerFormRef!}
|
||||
type={drawerType}
|
||||
renderSchema={renderSchema}
|
||||
tabData={partitionOptions}
|
||||
moduleId={moduleId!}
|
||||
driverSelectionOptions={driverOptions}
|
||||
initFormValue={curDetail as { [k: string]: unknown }}
|
||||
/>
|
||||
</Spin>
|
||||
</DrawerWithFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import type {
|
||||
EditorState,
|
||||
} from 'lexical'
|
||||
import {
|
||||
$getRoot,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import { useEffect } from 'react'
|
||||
import type { EditorState } from 'lexical'
|
||||
import { $getRoot, TextNode } from 'lexical'
|
||||
import { CodeNode } from '@lexical/code'
|
||||
import { LexicalComposer } from '@lexical/react/LexicalComposer'
|
||||
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
|
||||
@@ -19,25 +14,13 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
|
||||
// import TreeView from './plugins/tree-view'
|
||||
import Placeholder from './plugins/placeholder'
|
||||
import ComponentPickerBlock from './plugins/component-picker-block/index'
|
||||
import {
|
||||
ContextBlock,
|
||||
ContextBlockNode,
|
||||
ContextBlockReplacementBlock,
|
||||
} from './plugins/context-block/index'
|
||||
import {
|
||||
QueryBlock,
|
||||
QueryBlockNode,
|
||||
QueryBlockReplacementBlock,
|
||||
} from './plugins/query-block/index'
|
||||
import {
|
||||
HistoryBlock,
|
||||
HistoryBlockNode,
|
||||
HistoryBlockReplacementBlock,
|
||||
} from './plugins/history-block/index'
|
||||
import { ContextBlock, ContextBlockNode, ContextBlockReplacementBlock } from './plugins/context-block/index'
|
||||
import { QueryBlock, QueryBlockNode, QueryBlockReplacementBlock } from './plugins/query-block/index'
|
||||
import { HistoryBlock, HistoryBlockNode, HistoryBlockReplacementBlock } from './plugins/history-block/index'
|
||||
import {
|
||||
WorkflowVariableBlock,
|
||||
WorkflowVariableBlockNode,
|
||||
WorkflowVariableBlockReplacementBlock,
|
||||
WorkflowVariableBlockReplacementBlock
|
||||
} from './plugins/workflow-variable-block/index'
|
||||
import VariableBlock from './plugins/variable-block/index'
|
||||
import VariableValueBlock from './plugins/variable-value-block/index'
|
||||
@@ -52,13 +35,10 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from './types'
|
||||
import {
|
||||
UPDATE_DATASETS_EVENT_EMITTER,
|
||||
UPDATE_HISTORY_EVENT_EMITTER,
|
||||
} from './constants'
|
||||
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
|
||||
import { UPDATE_DATASETS_EVENT_EMITTER, UPDATE_HISTORY_EVENT_EMITTER } from './constants'
|
||||
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
|
||||
|
||||
export type PromptEditorProps = {
|
||||
instanceId?: string
|
||||
@@ -97,7 +77,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
}) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const initialConfig = {
|
||||
@@ -107,51 +87,58 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
CustomTextNode,
|
||||
{
|
||||
replace: TextNode,
|
||||
with: (node: TextNode) => new CustomTextNode(node.__text),
|
||||
with: (node: TextNode) => new CustomTextNode(node.__text)
|
||||
},
|
||||
ContextBlockNode,
|
||||
HistoryBlockNode,
|
||||
QueryBlockNode,
|
||||
WorkflowVariableBlockNode,
|
||||
VariableValueBlockNode,
|
||||
VariableValueBlockNode
|
||||
],
|
||||
editorState: textToEditorState(value || ''),
|
||||
onError: (error: Error) => {
|
||||
throw error
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleEditorChange = (editorState: EditorState) => {
|
||||
const text = editorState.read(() => {
|
||||
return $getRoot().getChildren().map(p => p.getTextContent()).join('\n')
|
||||
return $getRoot()
|
||||
.getChildren()
|
||||
.map((p) => p.getTextContent())
|
||||
.join('\n')
|
||||
})
|
||||
if (onChange)
|
||||
onChange(text)
|
||||
if (onChange) onChange(text)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_DATASETS_EVENT_EMITTER,
|
||||
payload: contextBlock?.datasets,
|
||||
payload: contextBlock?.datasets
|
||||
} as any)
|
||||
}, [eventEmitter, contextBlock?.datasets])
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_HISTORY_EVENT_EMITTER,
|
||||
payload: historyBlock?.history,
|
||||
payload: historyBlock?.history
|
||||
} as any)
|
||||
}, [eventEmitter, historyBlock?.history])
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
<div className='relative min-h-5'>
|
||||
<div className="relative min-h-5">
|
||||
<RichTextPlugin
|
||||
contentEditable={<ContentEditable className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`} style={style || {}} />}
|
||||
contentEditable={
|
||||
<ContentEditable
|
||||
className={`${className} outline-none ${compact ? 'leading-5 text-[13px]' : 'leading-6 text-sm'} text-gray-700`}
|
||||
style={style || {}}
|
||||
/>
|
||||
}
|
||||
placeholder={<Placeholder value={placeholder} className={placeholderClassName} compact={compact} />}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString='/'
|
||||
triggerString="/"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@@ -160,7 +147,7 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
/>
|
||||
<ComponentPickerBlock
|
||||
triggerString='{'
|
||||
triggerString="{"
|
||||
contextBlock={contextBlock}
|
||||
historyBlock={historyBlock}
|
||||
queryBlock={queryBlock}
|
||||
@@ -168,46 +155,36 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
externalToolBlock={externalToolBlock}
|
||||
workflowVariableBlock={workflowVariableBlock}
|
||||
/>
|
||||
{
|
||||
contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
{contextBlock?.show && (
|
||||
<>
|
||||
<ContextBlock {...contextBlock} />
|
||||
<ContextBlockReplacementBlock {...contextBlock} />
|
||||
</>
|
||||
)}
|
||||
{queryBlock?.show && (
|
||||
<>
|
||||
<QueryBlock {...queryBlock} />
|
||||
<QueryBlockReplacementBlock />
|
||||
</>
|
||||
)}
|
||||
{historyBlock?.show && (
|
||||
<>
|
||||
<HistoryBlock {...historyBlock} />
|
||||
<HistoryBlockReplacementBlock {...historyBlock} />
|
||||
</>
|
||||
)}
|
||||
{(variableBlock?.show || externalToolBlock?.show) && (
|
||||
<>
|
||||
<VariableBlock />
|
||||
<VariableValueBlock />
|
||||
</>
|
||||
)}
|
||||
{workflowVariableBlock?.show && (
|
||||
<>
|
||||
<WorkflowVariableBlock {...workflowVariableBlock} />
|
||||
<WorkflowVariableBlockReplacementBlock {...workflowVariableBlock} />
|
||||
</>
|
||||
)}
|
||||
<OnChangePlugin onChange={handleEditorChange} />
|
||||
<OnBlurBlock onBlur={onBlur} onFocus={onFocus} />
|
||||
<UpdateBlock instanceId={instanceId} />
|
||||
|
||||
+88
-76
@@ -1,82 +1,94 @@
|
||||
import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx'
|
||||
import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx'
|
||||
import { useState } from 'react'
|
||||
import { getVars } from './utils'
|
||||
import { VariableItems } from '@core/const/ai-service/type'
|
||||
|
||||
import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx';
|
||||
import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getVars } from './utils';
|
||||
import { VariableItems } from '@core/const/ai-service/type';
|
||||
const PromptEditorResizable = (props: {
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
variablesChange?: (keys: string[]) => void
|
||||
promptVariables: VariableItems[]
|
||||
disabled?: boolean
|
||||
}) => {
|
||||
const { value, onChange, variablesChange, promptVariables, disabled } = props
|
||||
const minHeight = 68
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
const [previousKeys, setPreviousKeys] = useState<string[]>([])
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
onChange?.(newTemplates)
|
||||
}
|
||||
|
||||
const PromptEditorResizable = (props:{value?:string, onChange?:(value:string)=>void, variablesChange?:(keys:string[])=>void,promptVariables:VariableItems[]}) =>{
|
||||
const {value , onChange,variablesChange,promptVariables} = props
|
||||
const minHeight = 68
|
||||
const [editorHeight, setEditorHeight] = useState(minHeight)
|
||||
const [previousKeys, setPreviousKeys] = useState<string[]>([])
|
||||
const handleChange = (newTemplates: string, keys: string[]) => {
|
||||
onChange?.(newTemplates)
|
||||
return (
|
||||
<PromptEditorHeightResizeWrap
|
||||
className="px-4 pt-2 min-h-[94px] bg-white rounded-t-xl text-sm text-gray-700"
|
||||
height={editorHeight}
|
||||
minHeight={minHeight}
|
||||
onHeightChange={setEditorHeight}
|
||||
hideResize={false}
|
||||
footer={
|
||||
<div className="pl-4 pb-2 flex bg-white rounded-b-xl">
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">
|
||||
{value?.length || 0}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
return ( <PromptEditorHeightResizeWrap
|
||||
className='px-4 pt-2 min-h-[94px] bg-white rounded-t-xl text-sm text-gray-700'
|
||||
height={editorHeight}
|
||||
minHeight={minHeight}
|
||||
onHeightChange={setEditorHeight}
|
||||
hideResize={false}
|
||||
footer={(
|
||||
<div className='pl-4 pb-2 flex bg-white rounded-b-xl'>
|
||||
<div className="h-[18px] leading-[18px] px-1 rounded-md bg-gray-100 text-xs text-gray-500">{value?.length || 0}</div>
|
||||
</div>
|
||||
>
|
||||
<>
|
||||
{value !== undefined && (
|
||||
<PromptEditor
|
||||
className="min-h-[68px]"
|
||||
compact
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: false,
|
||||
selectable: true
|
||||
// datasets: dataSets.map(item => ({
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.data_source_type,
|
||||
// })),
|
||||
// onAddContext: ()=>{console.log('?onAddContext')},
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables: promptVariables?.map((x) => ({ name: x.key, value: x.key })) || []
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: false,
|
||||
externalTools: []
|
||||
// onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: ''
|
||||
},
|
||||
onEditRole: () => {}
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: true
|
||||
}}
|
||||
onChange={(value) => {
|
||||
handleChange?.(value, [])
|
||||
}}
|
||||
onBlur={() => {
|
||||
const keys = getVars(value)
|
||||
handleChange(value, keys)
|
||||
if (keys.filter((key) => !previousKeys.includes(key)).length > 0) {
|
||||
variablesChange?.(keys)
|
||||
setPreviousKeys(keys)
|
||||
}
|
||||
}}
|
||||
editable={disabled ? false : true}
|
||||
/>
|
||||
)}
|
||||
><>
|
||||
{value !== undefined && <PromptEditor
|
||||
className='min-h-[68px]'
|
||||
compact
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: false,
|
||||
selectable: true,
|
||||
// datasets: dataSets.map(item => ({
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.data_source_type,
|
||||
// })),
|
||||
// onAddContext: ()=>{console.log('?onAddContext')},
|
||||
}}
|
||||
variableBlock={{
|
||||
show: true,
|
||||
variables:promptVariables?.map(x=>({name:x.key, value:x.key})) || [],
|
||||
}}
|
||||
externalToolBlock={{
|
||||
show: false,
|
||||
externalTools: [],
|
||||
// onAddExternalTool: handleOpenExternalDataToolModal,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: false,
|
||||
selectable: false,
|
||||
history: {
|
||||
user: '',
|
||||
assistant: '',
|
||||
},
|
||||
onEditRole: () => { },
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false,
|
||||
selectable: true,
|
||||
}}
|
||||
onChange={(value) => {
|
||||
handleChange?.(value, [])
|
||||
}}
|
||||
onBlur={() => {
|
||||
const keys = getVars(value)
|
||||
handleChange(value, keys)
|
||||
if(keys.filter(key => !previousKeys.includes(key)).length > 0){
|
||||
variablesChange?.(keys)
|
||||
setPreviousKeys(keys)
|
||||
}
|
||||
}}
|
||||
editable={true}
|
||||
/>
|
||||
}</>
|
||||
</PromptEditorHeightResizeWrap>)
|
||||
</>
|
||||
</PromptEditorHeightResizeWrap>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptEditorResizable
|
||||
export default PromptEditorResizable
|
||||
|
||||
@@ -9,40 +9,35 @@ export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-
|
||||
export const MAX_VAR_KEY_LENGTH = 30
|
||||
|
||||
export const checkHasContextBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(CONTEXT_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
export const checkHasHistoryBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(HISTORY_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
export const checkHasQueryBlock = (text: string) => {
|
||||
if (!text)
|
||||
return false
|
||||
if (!text) return false
|
||||
return text.includes(QUERY_PLACEHOLDER_TEXT)
|
||||
}
|
||||
|
||||
/*
|
||||
* {{#1711617514996.name#}} => [1711617514996, name]
|
||||
* {{#1711617514996.sys.query#}} => [sys, query]
|
||||
*/
|
||||
* {{#1711617514996.name#}} => [1711617514996, name]
|
||||
* {{#1711617514996.sys.query#}} => [sys, query]
|
||||
*/
|
||||
export const getInputVars = (text: string) => {
|
||||
if (!text)
|
||||
return []
|
||||
if (!text) return []
|
||||
|
||||
const allVars = text.match(/{{#([^#]*)#}}/g)
|
||||
if (allVars && allVars?.length > 0) {
|
||||
// {{#context#}}, {{#query#}} is not input vars
|
||||
const inputVars = allVars
|
||||
.filter(item => item.includes('.'))
|
||||
.filter((item) => item.includes('.'))
|
||||
.map((item) => {
|
||||
const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.')
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0]))
|
||||
return valueSelector.slice(1)
|
||||
if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0])) return valueSelector.slice(1)
|
||||
|
||||
return valueSelector
|
||||
})
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { Dispatch, RefObject, SetStateAction } from 'react'
|
||||
import type {
|
||||
Klass,
|
||||
LexicalCommand,
|
||||
LexicalEditor,
|
||||
TextNode,
|
||||
} from 'lexical'
|
||||
import type { Klass, LexicalCommand, LexicalEditor, TextNode } from 'lexical'
|
||||
import {
|
||||
$getNodeByKey,
|
||||
$getSelection,
|
||||
@@ -18,12 +8,10 @@ import {
|
||||
$isNodeSelection,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
KEY_BACKSPACE_COMMAND,
|
||||
KEY_DELETE_COMMAND,
|
||||
KEY_DELETE_COMMAND
|
||||
} from 'lexical'
|
||||
import type { EntityMatch } from '@lexical/text'
|
||||
import {
|
||||
mergeRegister,
|
||||
} from '@lexical/utils'
|
||||
import { mergeRegister } from '@lexical/utils'
|
||||
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { $isContextBlockNode } from './plugins/context-block/node'
|
||||
@@ -35,7 +23,10 @@ import { DELETE_QUERY_BLOCK_COMMAND } from './plugins/query-block'
|
||||
import type { CustomTextNode } from './plugins/custom-text/node'
|
||||
import { registerLexicalTextEntity } from './utils'
|
||||
|
||||
export type UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand<undefined>) => [RefObject<HTMLDivElement>, boolean]
|
||||
export type UseSelectOrDeleteHandler = (
|
||||
nodeKey: string,
|
||||
command?: LexicalCommand<undefined>
|
||||
) => [RefObject<HTMLDivElement>, boolean]
|
||||
export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand<undefined>) => {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const [editor] = useLexicalComposerContext()
|
||||
@@ -46,13 +37,11 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
const selection = $getSelection()
|
||||
const nodes = selection?.getNodes()
|
||||
if (
|
||||
!isSelected
|
||||
&& nodes?.length === 1
|
||||
&& (
|
||||
($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND)
|
||||
|| ($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND)
|
||||
|| ($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND)
|
||||
)
|
||||
!isSelected &&
|
||||
nodes?.length === 1 &&
|
||||
(($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND) ||
|
||||
($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND) ||
|
||||
($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND))
|
||||
)
|
||||
editor.dispatchCommand(command, undefined)
|
||||
|
||||
@@ -60,8 +49,7 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
event.preventDefault()
|
||||
const node = $getNodeByKey(nodeKey)
|
||||
if ($isDecoratorNode(node)) {
|
||||
if (command)
|
||||
editor.dispatchCommand(command, undefined)
|
||||
if (command) editor.dispatchCommand(command, undefined)
|
||||
|
||||
node.remove()
|
||||
return true
|
||||
@@ -70,38 +58,31 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com
|
||||
|
||||
return false
|
||||
},
|
||||
[isSelected, nodeKey, command, editor],
|
||||
[isSelected, nodeKey, command, editor]
|
||||
)
|
||||
|
||||
const handleSelect = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
}, [setSelected, clearSelection])
|
||||
const handleSelect = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
clearSelection()
|
||||
setSelected(true)
|
||||
},
|
||||
[setSelected, clearSelection]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const ele = ref.current
|
||||
|
||||
if (ele)
|
||||
ele.addEventListener('click', handleSelect)
|
||||
if (ele) ele.addEventListener('click', handleSelect)
|
||||
|
||||
return () => {
|
||||
if (ele)
|
||||
ele.removeEventListener('click', handleSelect)
|
||||
if (ele) ele.removeEventListener('click', handleSelect)
|
||||
}
|
||||
}, [handleSelect])
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
KEY_DELETE_COMMAND,
|
||||
handleDelete,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(
|
||||
KEY_BACKSPACE_COMMAND,
|
||||
handleDelete,
|
||||
COMMAND_PRIORITY_LOW,
|
||||
),
|
||||
editor.registerCommand(KEY_DELETE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW),
|
||||
editor.registerCommand(KEY_BACKSPACE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW)
|
||||
)
|
||||
}, [editor, clearSelection, handleDelete])
|
||||
|
||||
@@ -114,17 +95,15 @@ export const useTrigger: UseTriggerHandler = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const handleOpen = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setOpen(v => !v)
|
||||
setOpen((v) => !v)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const trigger = triggerRef.current
|
||||
if (trigger)
|
||||
trigger.addEventListener('click', handleOpen)
|
||||
if (trigger) trigger.addEventListener('click', handleOpen)
|
||||
|
||||
return () => {
|
||||
if (trigger)
|
||||
trigger.removeEventListener('click', handleOpen)
|
||||
if (trigger) trigger.removeEventListener('click', handleOpen)
|
||||
}
|
||||
}, [handleOpen])
|
||||
|
||||
@@ -134,7 +113,7 @@ export const useTrigger: UseTriggerHandler = () => {
|
||||
export function useLexicalTextEntity<T extends TextNode>(
|
||||
getMatch: (text: string) => null | EntityMatch,
|
||||
targetNode: Klass<T>,
|
||||
createNode: (textNode: CustomTextNode) => T,
|
||||
createNode: (textNode: CustomTextNode) => T
|
||||
) {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -148,24 +127,16 @@ export type MenuTextMatch = {
|
||||
matchingString: string
|
||||
replaceableString: string
|
||||
}
|
||||
export type TriggerFn = (
|
||||
text: string,
|
||||
editor: LexicalEditor,
|
||||
) => MenuTextMatch | null
|
||||
export type TriggerFn = (text: string, editor: LexicalEditor) => MenuTextMatch | null
|
||||
export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;'
|
||||
export function useBasicTypeaheadTriggerMatch(
|
||||
trigger: string,
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number },
|
||||
{ minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number }
|
||||
): TriggerFn {
|
||||
return useCallback(
|
||||
(text: string) => {
|
||||
const validChars = `[${PUNCTUATION}\\s]`
|
||||
const TypeaheadTriggerRegex = new RegExp(
|
||||
'(.*)('
|
||||
+ `[${trigger}]`
|
||||
+ `((?:${validChars}){0,${maxLength}})`
|
||||
+ ')$',
|
||||
)
|
||||
const TypeaheadTriggerRegex = new RegExp('(.*)(' + `[${trigger}]` + `((?:${validChars}){0,${maxLength}})` + ')$')
|
||||
const match = TypeaheadTriggerRegex.exec(text)
|
||||
if (match !== null) {
|
||||
const maybeLeadingWhitespace = match[1]
|
||||
@@ -174,12 +145,12 @@ export function useBasicTypeaheadTriggerMatch(
|
||||
return {
|
||||
leadOffset: match.index + maybeLeadingWhitespace.length,
|
||||
matchingString,
|
||||
replaceableString: match[2],
|
||||
replaceableString: match[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
[maxLength, minLength, trigger],
|
||||
[maxLength, minLength, trigger]
|
||||
)
|
||||
}
|
||||
|
||||
+50
-58
@@ -8,7 +8,7 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from '../../types'
|
||||
import { INSERT_CONTEXT_BLOCK_COMMAND } from '../context-block'
|
||||
import { INSERT_HISTORY_BLOCK_COMMAND } from '../history-block'
|
||||
@@ -32,32 +32,35 @@ import { $t } from '@common/locales'
|
||||
export const usePromptOptions = (
|
||||
contextBlock?: ContextBlockType,
|
||||
queryBlock?: QueryBlockType,
|
||||
historyBlock?: HistoryBlockType,
|
||||
historyBlock?: HistoryBlockType
|
||||
) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const promptOptions: PickerBlockMenuOption[] = []
|
||||
if (contextBlock?.show) {
|
||||
promptOptions.push(new PickerBlockMenuOption({
|
||||
key: $t('上下文'),
|
||||
group: 'prompt context',
|
||||
render: ({ isSelected, onSelect, onSetHighlight }) => {
|
||||
return <PromptMenuItem
|
||||
title={$t('上下文')}
|
||||
icon={<></>}
|
||||
// icon={<File05 className='w-4 h-4 text-[#6938EF]' />}
|
||||
disabled={!contextBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
/>
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!contextBlock?.selectable)
|
||||
return
|
||||
editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}))
|
||||
promptOptions.push(
|
||||
new PickerBlockMenuOption({
|
||||
key: $t('上下文'),
|
||||
group: 'prompt context',
|
||||
render: ({ isSelected, onSelect, onSetHighlight }) => {
|
||||
return (
|
||||
<PromptMenuItem
|
||||
title={$t('上下文')}
|
||||
icon={<></>}
|
||||
// icon={<File05 className='w-4 h-4 text-[#6938EF]' />}
|
||||
disabled={!contextBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
/>
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!contextBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (queryBlock?.show) {
|
||||
@@ -79,11 +82,10 @@ export const usePromptOptions = (
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!queryBlock?.selectable)
|
||||
return
|
||||
if (!queryBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -98,8 +100,7 @@ export const usePromptOptions = (
|
||||
title={$t('会话历史')}
|
||||
icon={<></>}
|
||||
// icon={<MessageClockCircle className='w-4 h-4 text-[#DD2590]' />}
|
||||
disabled={!historyBlock.selectable
|
||||
}
|
||||
disabled={!historyBlock.selectable}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
onMouseEnter={onSetHighlight}
|
||||
@@ -107,11 +108,10 @@ export const usePromptOptions = (
|
||||
)
|
||||
},
|
||||
onSelect: () => {
|
||||
if (!historyBlock?.selectable)
|
||||
return
|
||||
if (!historyBlock?.selectable) return
|
||||
editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined)
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
return promptOptions
|
||||
@@ -119,16 +119,15 @@ export const usePromptOptions = (
|
||||
|
||||
export const useVariableOptions = (
|
||||
variableBlock?: VariableBlockType,
|
||||
queryString?: string,
|
||||
queryString?: string
|
||||
): PickerBlockMenuOption[] => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!variableBlock?.variables)
|
||||
return []
|
||||
if (!variableBlock?.variables) return []
|
||||
|
||||
const baseOptions = (variableBlock.variables).map((item) => {
|
||||
const baseOptions = variableBlock.variables.map((item) => {
|
||||
return new PickerBlockMenuOption({
|
||||
key: item.value,
|
||||
group: 'prompt variable',
|
||||
@@ -147,15 +146,14 @@ export const useVariableOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.value}}}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!queryString)
|
||||
return baseOptions
|
||||
if (!queryString) return baseOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
|
||||
return baseOptions.filter(option => regex.test(option.key))
|
||||
return baseOptions.filter((option) => regex.test(option.key))
|
||||
}, [editor, queryString, variableBlock])
|
||||
|
||||
const addOption = useMemo(() => {
|
||||
@@ -182,7 +180,7 @@ export const useVariableOptions = (
|
||||
$insertNodes([prefixNode, suffixNode])
|
||||
prefixNode.select()
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [editor, t])
|
||||
|
||||
@@ -191,17 +189,13 @@ export const useVariableOptions = (
|
||||
}, [options, addOption, variableBlock?.show])
|
||||
}
|
||||
|
||||
export const useExternalToolOptions = (
|
||||
externalToolBlockType?: ExternalToolBlockType,
|
||||
queryString?: string,
|
||||
) => {
|
||||
export const useExternalToolOptions = (externalToolBlockType?: ExternalToolBlockType, queryString?: string) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const options = useMemo(() => {
|
||||
if (!externalToolBlockType?.externalTools)
|
||||
return []
|
||||
const baseToolOptions = (externalToolBlockType.externalTools).map((item) => {
|
||||
if (!externalToolBlockType?.externalTools) return []
|
||||
const baseToolOptions = externalToolBlockType.externalTools.map((item) => {
|
||||
return new PickerBlockMenuOption({
|
||||
key: item.name,
|
||||
group: 'external tool',
|
||||
@@ -217,7 +211,7 @@ export const useExternalToolOptions = (
|
||||
// background={item.icon_background}
|
||||
// />
|
||||
// }
|
||||
extraElement={<div className='text-xs text-gray-400'>{item.variableName}</div>}
|
||||
extraElement={<div className="text-xs text-gray-400">{item.variableName}</div>}
|
||||
queryString={queryString}
|
||||
isSelected={isSelected}
|
||||
onClick={onSelect}
|
||||
@@ -227,15 +221,14 @@ export const useExternalToolOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.variableName}}}`)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
if (!queryString)
|
||||
return baseToolOptions
|
||||
if (!queryString) return baseToolOptions
|
||||
|
||||
const regex = new RegExp(queryString, 'i')
|
||||
|
||||
return baseToolOptions.filter(option => regex.test(option.key))
|
||||
return baseToolOptions.filter((option) => regex.test(option.key))
|
||||
}, [editor, queryString, externalToolBlockType])
|
||||
|
||||
const addOption = useMemo(() => {
|
||||
@@ -257,7 +250,7 @@ export const useExternalToolOptions = (
|
||||
},
|
||||
onSelect: () => {
|
||||
externalToolBlockType?.onAddExternalTool?.()
|
||||
},
|
||||
}
|
||||
})
|
||||
}, [externalToolBlockType, t])
|
||||
|
||||
@@ -273,14 +266,13 @@ export const useOptions = (
|
||||
variableBlock?: VariableBlockType,
|
||||
externalToolBlockType?: ExternalToolBlockType,
|
||||
workflowVariableBlockType?: WorkflowVariableBlockType,
|
||||
queryString?: string,
|
||||
queryString?: string
|
||||
) => {
|
||||
const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock)
|
||||
const variableOptions = useVariableOptions(variableBlock, queryString)
|
||||
const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString)
|
||||
const workflowVariableOptions = useMemo(() => {
|
||||
if (!workflowVariableBlockType?.show)
|
||||
return []
|
||||
if (!workflowVariableBlockType?.show) return []
|
||||
|
||||
return workflowVariableBlockType.variables || []
|
||||
}, [workflowVariableBlockType])
|
||||
@@ -288,7 +280,7 @@ export const useOptions = (
|
||||
return useMemo(() => {
|
||||
return {
|
||||
workflowVariableOptions,
|
||||
allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions],
|
||||
allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions]
|
||||
}
|
||||
}, [promptOptions, variableOptions, externalToolOptions, workflowVariableOptions])
|
||||
}
|
||||
|
||||
+80
-99
@@ -1,16 +1,6 @@
|
||||
import {
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Fragment, memo, useCallback, useState } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import {
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useFloating,
|
||||
} from '@floating-ui/react'
|
||||
import { flip, offset, shift, useFloating } from '@floating-ui/react'
|
||||
import type { TextNode } from 'lexical'
|
||||
import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
@@ -21,7 +11,7 @@ import type {
|
||||
HistoryBlockType,
|
||||
QueryBlockType,
|
||||
VariableBlockType,
|
||||
WorkflowVariableBlockType,
|
||||
WorkflowVariableBlockType
|
||||
} from '../../types'
|
||||
import { useBasicTypeaheadTriggerMatch } from '../../hooks'
|
||||
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
|
||||
@@ -30,7 +20,7 @@ import { $splitNodeContainingQuery } from '../../utils'
|
||||
import { useOptions } from './hooks'
|
||||
import type { PickerBlockMenuOption } from './menu'
|
||||
// import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
|
||||
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
|
||||
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
|
||||
|
||||
type ComponentPickerProps = {
|
||||
triggerString: string
|
||||
@@ -48,7 +38,7 @@ const ComponentPicker = ({
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
}: ComponentPickerProps) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const { refs, floatingStyles, isPositioned } = useFloating({
|
||||
@@ -56,15 +46,15 @@ const ComponentPicker = ({
|
||||
middleware: [
|
||||
offset(0), // fix hide cursor
|
||||
shift({
|
||||
padding: 8,
|
||||
padding: 8
|
||||
}),
|
||||
flip(),
|
||||
],
|
||||
flip()
|
||||
]
|
||||
})
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, {
|
||||
minLength: 0,
|
||||
maxLength: 0,
|
||||
maxLength: 0
|
||||
})
|
||||
|
||||
const [queryString, setQueryString] = useState<string | null>(null)
|
||||
@@ -74,123 +64,114 @@ const ComponentPicker = ({
|
||||
editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${v.payload}}}`)
|
||||
})
|
||||
|
||||
const {
|
||||
allFlattenOptions,
|
||||
workflowVariableOptions,
|
||||
} = useOptions(
|
||||
const { allFlattenOptions, workflowVariableOptions } = useOptions(
|
||||
contextBlock,
|
||||
queryBlock,
|
||||
historyBlock,
|
||||
variableBlock,
|
||||
externalToolBlock,
|
||||
workflowVariableBlock,
|
||||
workflowVariableBlock
|
||||
)
|
||||
|
||||
const onSelectOption = useCallback(
|
||||
(
|
||||
selectedOption: PickerBlockMenuOption,
|
||||
nodeToRemove: TextNode | null,
|
||||
closeMenu: () => void,
|
||||
) => {
|
||||
(selectedOption: PickerBlockMenuOption, nodeToRemove: TextNode | null, closeMenu: () => void) => {
|
||||
editor.update(() => {
|
||||
if (nodeToRemove && selectedOption?.key)
|
||||
nodeToRemove.remove()
|
||||
if (nodeToRemove && selectedOption?.key) nodeToRemove.remove()
|
||||
|
||||
selectedOption.onSelectMenuOption()
|
||||
closeMenu()
|
||||
})
|
||||
},
|
||||
[editor],
|
||||
[editor]
|
||||
)
|
||||
|
||||
const handleSelectWorkflowVariable = useCallback((variables: string[]) => {
|
||||
editor.update(() => {
|
||||
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
|
||||
if (needRemove)
|
||||
needRemove.remove()
|
||||
})
|
||||
const handleSelectWorkflowVariable = useCallback(
|
||||
(variables: string[]) => {
|
||||
editor.update(() => {
|
||||
const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!)
|
||||
if (needRemove) needRemove.remove()
|
||||
})
|
||||
|
||||
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
|
||||
else
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
|
||||
}, [editor, checkForTriggerMatch, triggerString])
|
||||
if (variables[1] === 'sys.query' || variables[1] === 'sys.files')
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]])
|
||||
else editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables)
|
||||
},
|
||||
[editor, checkForTriggerMatch, triggerString]
|
||||
)
|
||||
|
||||
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>((
|
||||
anchorElementRef,
|
||||
{ options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },
|
||||
) => {
|
||||
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show)))
|
||||
return null
|
||||
refs.setReference(anchorElementRef.current)
|
||||
const renderMenu = useCallback<MenuRenderFn<PickerBlockMenuOption>>(
|
||||
(anchorElementRef, { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) => {
|
||||
if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) return null
|
||||
refs.setReference(anchorElementRef.current)
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
ReactDOM.createPortal(
|
||||
return (
|
||||
<>
|
||||
{ReactDOM.createPortal(
|
||||
// The `LexicalMenu` will try to calculate the position of the floating menu based on the first child.
|
||||
// Since we use floating ui, we need to wrap it with a div to prevent the position calculation being affected.
|
||||
// See https://github.com/facebook/lexical/blob/ac97dfa9e14a73ea2d6934ff566282d7f758e8bb/packages/lexical-react/src/shared/LexicalMenu.ts#L493
|
||||
<div className='w-0 h-0'>
|
||||
<div className="w-0 h-0">
|
||||
<div
|
||||
className='p-1 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto overflow-x-hidden'
|
||||
className="p-1 w-[260px] bg-white rounded-lg border-[0.5px] border-gray-200 shadow-lg overflow-y-auto overflow-x-hidden"
|
||||
style={{
|
||||
...floatingStyles,
|
||||
visibility: isPositioned ? 'visible' : 'hidden',
|
||||
maxHeight: 'calc(1 / 3 * 100vh)',
|
||||
maxHeight: 'calc(1 / 3 * 100vh)'
|
||||
}}
|
||||
ref={refs.setFloating}
|
||||
>
|
||||
{
|
||||
options.map((option, index) => (
|
||||
<Fragment key={option.key}>
|
||||
{
|
||||
// Divider
|
||||
index !== 0 && options.at(index - 1)?.group !== option.group && (
|
||||
<div className='h-px bg-gray-100 my-1 w-screen -translate-x-1'></div>
|
||||
)
|
||||
{options.map((option, index) => (
|
||||
<Fragment key={option.key}>
|
||||
{
|
||||
// Divider
|
||||
index !== 0 && options.at(index - 1)?.group !== option.group && (
|
||||
<div className="h-px bg-gray-100 my-1 w-screen -translate-x-1"></div>
|
||||
)
|
||||
}
|
||||
{option.renderMenuOption({
|
||||
queryString,
|
||||
isSelected: selectedIndex === index,
|
||||
onSelect: () => {
|
||||
selectOptionAndCleanUp(option)
|
||||
},
|
||||
onSetHighlight: () => {
|
||||
setHighlightedIndex(index)
|
||||
}
|
||||
{option.renderMenuOption({
|
||||
queryString,
|
||||
isSelected: selectedIndex === index,
|
||||
onSelect: () => {
|
||||
selectOptionAndCleanUp(option)
|
||||
},
|
||||
onSetHighlight: () => {
|
||||
setHighlightedIndex(index)
|
||||
},
|
||||
})}
|
||||
</Fragment>
|
||||
))
|
||||
}
|
||||
{
|
||||
workflowVariableBlock?.show && (
|
||||
<>
|
||||
{
|
||||
(!!options.length) && (
|
||||
<div className='h-px bg-gray-100 my-1 w-screen -translate-x-1'></div>
|
||||
)
|
||||
}
|
||||
<div className='p-1'>
|
||||
{/* <VarReferenceVars
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
{workflowVariableBlock?.show && (
|
||||
<>
|
||||
{!!options.length && <div className="h-px bg-gray-100 my-1 w-screen -translate-x-1"></div>}
|
||||
<div className="p-1">
|
||||
{/* <VarReferenceVars
|
||||
hideSearch
|
||||
vars={workflowVariableOptions}
|
||||
onChange={(variables: string[]) => {
|
||||
handleSelectWorkflowVariable(variables)
|
||||
}}
|
||||
/> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>,
|
||||
anchorElementRef.current,
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable])
|
||||
anchorElementRef.current
|
||||
)}
|
||||
</>
|
||||
)
|
||||
},
|
||||
[
|
||||
allFlattenOptions.length,
|
||||
workflowVariableBlock?.show,
|
||||
refs,
|
||||
isPositioned,
|
||||
floatingStyles,
|
||||
queryString,
|
||||
workflowVariableOptions,
|
||||
handleSelectWorkflowVariable
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<LexicalTypeaheadMenuPlugin
|
||||
@@ -202,7 +183,7 @@ const ComponentPicker = ({
|
||||
//
|
||||
// We no need the position function of the `LexicalTypeaheadMenuPlugin`,
|
||||
// so the reference anchor should be positioned based on the range of the trigger string, and the menu will be positioned by the floating ui.
|
||||
anchorClassName='z-[999999] translate-y-[calc(-100%-3px)]'
|
||||
anchorClassName="z-[999999] translate-y-[calc(-100%-3px)]"
|
||||
menuRenderFn={renderMenu}
|
||||
triggerFn={checkForTriggerMatch}
|
||||
/>
|
||||
|
||||
+4
-2
@@ -20,12 +20,14 @@ export class PickerBlockMenuOption extends MenuOption {
|
||||
group?: string
|
||||
onSelect?: () => void
|
||||
render: (menuRenderProps: MenuOptionRenderProps) => JSX.Element
|
||||
},
|
||||
}
|
||||
) {
|
||||
super(data.key)
|
||||
this.group = data.group
|
||||
}
|
||||
|
||||
public onSelectMenuOption = () => this.data.onSelect?.()
|
||||
public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => <Fragment key={this.data.key}>{this.data.render(menuRenderProps)}</Fragment>
|
||||
public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => (
|
||||
<Fragment key={this.data.key}>{this.data.render(menuRenderProps)}</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
+22
-29
@@ -9,37 +9,30 @@ type PromptMenuItemMenuItemProps = {
|
||||
onMouseEnter: () => void
|
||||
setRefElement?: (element: HTMLDivElement) => void
|
||||
}
|
||||
export const PromptMenuItem = memo(({
|
||||
icon,
|
||||
title,
|
||||
disabled,
|
||||
isSelected,
|
||||
onClick,
|
||||
onMouseEnter,
|
||||
setRefElement,
|
||||
}: PromptMenuItemMenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
export const PromptMenuItem = memo(
|
||||
({ icon, title, disabled, isSelected, onClick, onMouseEnter, setRefElement }: PromptMenuItemMenuItemProps) => {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex items-center px-3 h-6 cursor-pointer hover:bg-gray-50 rounded-md
|
||||
${isSelected && !disabled && '!bg-gray-50'}
|
||||
${disabled ? 'cursor-not-allowed opacity-30' : 'hover:bg-gray-50 cursor-pointer'}
|
||||
`}
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={() => {
|
||||
if (disabled)
|
||||
return
|
||||
onMouseEnter()
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled)
|
||||
return
|
||||
onClick()
|
||||
}}>
|
||||
{icon}
|
||||
<div className='ml-1 text-[13px] text-gray-900'>{title}</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
tabIndex={-1}
|
||||
ref={setRefElement}
|
||||
onMouseEnter={() => {
|
||||
if (disabled) return
|
||||
onMouseEnter()
|
||||
}}
|
||||
onClick={() => {
|
||||
if (disabled) return
|
||||
onClick()
|
||||
}}
|
||||
>
|
||||
{icon}
|
||||
<div className="ml-1 text-[13px] text-gray-900">{title}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
PromptMenuItem.displayName = 'PromptMenuItem'
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user