mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
Compare commits
493 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d8e579a10 | |||
| 095c09c8c0 | |||
| 3482d5416c | |||
| d8cb4a0c94 | |||
| 59acfa7a47 | |||
| 2eb2e690d1 | |||
| 7e7be7f040 | |||
| 0187fd16b2 | |||
| ba0bdb5e99 | |||
| 9d3e4f07bf | |||
| bd81d7584d | |||
| 9577339e14 | |||
| 5c292ef1cb | |||
| 4f3de85068 | |||
| 07a25c9643 | |||
| 8f60426b4c | |||
| 37f87615bd | |||
| 3f96de660b | |||
| e86999770f | |||
| a8bb0c24ec | |||
| 6ba2a08b62 | |||
| d232269416 | |||
| 9d2208e14d | |||
| 8d69d45d1d | |||
| a6105cfc3c | |||
| 0aa5ffd2c2 | |||
| 968f5b986f | |||
| 014a7e0362 | |||
| a92baf09d9 | |||
| 46e2edbe13 | |||
| 5aba86965e | |||
| 5924208aaa | |||
| 526390816b | |||
| d7e28c9704 | |||
| b0dacbda0d | |||
| d5abde2593 | |||
| bc3290de3b | |||
| 7f438bf776 | |||
| 13cfe24b2f | |||
| 9cf1cd99c2 | |||
| f27abbd454 | |||
| 6a7a11a811 | |||
| 599ee6b9b8 | |||
| 09b98c6c0d | |||
| 13eac21609 | |||
| d0d9e2a9a8 | |||
| b047c93965 | |||
| 9cc6696340 | |||
| 150a0264c5 | |||
| 558a2d8aad | |||
| fa327114f7 | |||
| 4f7dee570a | |||
| 7a70a6ce01 | |||
| 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 | |||
| 76872b3a97 | |||
| d253d68a12 | |||
| 4de0d29f30 | |||
| 0f0480db63 | |||
| aa40b62e0a | |||
| 68309ac582 | |||
| 522183eb4d | |||
| 4f6fee4d73 | |||
| c5fecf73c8 | |||
| ee8e93d1e8 | |||
| 9d0fa5ea4a | |||
| b0b9affbe7 | |||
| 1d1c7c78e8 | |||
| d7f5b87e70 | |||
| 1048666972 | |||
| 217f5d61c7 | |||
| 2f1677f581 | |||
| afd7ea6a1c | |||
| 7155e14e64 | |||
| 68e5b49f46 | |||
| c01e95c716 | |||
| ffa7e5130e | |||
| 7ce33bfd4c | |||
| 0c64fa9986 | |||
| 07e8b347e1 | |||
| 38a402adef | |||
| 7ab2e29101 | |||
| b96796793b | |||
| baa073b05d | |||
| c944f9de52 | |||
| 5567a65e4b | |||
| c6c775f436 | |||
| c0c43be6b9 | |||
| 9b6074318a | |||
| ada121015d | |||
| 9d7b6c4bc6 | |||
| c262fc4f4d | |||
| 335317bd86 | |||
| 396464c573 | |||
| d93c76ca36 | |||
| 60ad6007e5 | |||
| ceee520102 | |||
| a053557000 | |||
| e7c14b0817 | |||
| 727fd0bd36 | |||
| 4ba2b0c9ab | |||
| b3672b8c66 | |||
| b33dda4a32 | |||
| 450e565718 | |||
| 00bd72c1e4 | |||
| 81da1f45f1 | |||
| fd3d6433e2 | |||
| 9592a0ddda | |||
| 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@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
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@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: frontend-package
|
||||
path: frontend/dist
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: download frontend release
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
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,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="800px" height="800px" viewBox="0 0 512 512" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.st0{fill:#000000;}
|
||||
]]>
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M485.016,220.691c12.422-8.422,22.188-57.266,7.797-87.016c-26.859,28.125-80.109,49.859-142,72.922
|
||||
c-64.656,24.078-71.531,41.859-94.813,42.172c-23.297-0.313-30.156-18.094-94.813-42.172c-61.891-23.063-115.141-44.797-142-72.922
|
||||
c-14.406,29.75-4.625,78.594,7.781,87.016C13.484,214.332,0,210.051,0,210.051s3.813,68.172,33.25,106.563
|
||||
c24.953,32.547,86.984,72.906,145.828,58.828c62.219-14.875,62.344-41.406,76.922-41.266c14.563-0.141,14.688,26.391,76.906,41.266
|
||||
c58.844,14.078,120.875-26.281,145.844-58.828C508.156,278.223,512,210.051,512,210.051S498.5,214.332,485.016,220.691z
|
||||
M131.438,318.848c-25.109-11.641-46.063-37.422-43.188-73.891c12.813,5.078,48.844,9.406,69.094,17.281
|
||||
c23,8.953,46.031,23.984,48.922,41.25C205.313,317.879,170.75,337.066,131.438,318.848z M380.563,318.848
|
||||
c-39.344,18.219-73.875-0.969-74.828-15.359c2.891-17.266,25.891-32.297,48.922-41.25c20.25-7.875,56.281-12.203,69.094-17.281
|
||||
C426.625,281.426,405.656,307.207,380.563,318.848z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="_x32_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="800px" height="800px" viewBox="0 0 512 512" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.st0{fill:#000000;}
|
||||
]]>
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M485.016,220.691c12.422-8.422,22.188-57.266,7.797-87.016c-26.859,28.125-80.109,49.859-142,72.922
|
||||
c-64.656,24.078-71.531,41.859-94.813,42.172c-23.297-0.313-30.156-18.094-94.813-42.172c-61.891-23.063-115.141-44.797-142-72.922
|
||||
c-14.406,29.75-4.625,78.594,7.781,87.016C13.484,214.332,0,210.051,0,210.051s3.813,68.172,33.25,106.563
|
||||
c24.953,32.547,86.984,72.906,145.828,58.828c62.219-14.875,62.344-41.406,76.922-41.266c14.563-0.141,14.688,26.391,76.906,41.266
|
||||
c58.844,14.078,120.875-26.281,145.844-58.828C508.156,278.223,512,210.051,512,210.051S498.5,214.332,485.016,220.691z
|
||||
M131.438,318.848c-25.109-11.641-46.063-37.422-43.188-73.891c12.813,5.078,48.844,9.406,69.094,17.281
|
||||
c23,8.953,46.031,23.984,48.922,41.25C205.313,317.879,170.75,337.066,131.438,318.848z M380.563,318.848
|
||||
c-39.344,18.219-73.875-0.969-74.828-15.359c2.891-17.266,25.891-32.297,48.922-41.25c20.25-7.875,56.281-12.203,69.094-17.281
|
||||
C426.625,281.426,405.656,307.207,380.563,318.848z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,30 @@
|
||||
provider: fakegpt
|
||||
label:
|
||||
en_US: FakeGPT
|
||||
description:
|
||||
en_US: FakeGPT is a fake model provider that does not actually connect to any model service. It is useful for testing and development purposes.
|
||||
icon_small:
|
||||
en_US: icon_s_en.svg
|
||||
icon_large:
|
||||
en_US: icon_l_en.svg
|
||||
background: "#FCFDFF"
|
||||
help:
|
||||
title:
|
||||
en_US: Get your Access Details from Google
|
||||
url:
|
||||
en_US: https://apipark.com
|
||||
supported_model_types:
|
||||
- llm
|
||||
- text-embedding
|
||||
configurate_methods:
|
||||
- predefined-model
|
||||
provider_credential_schema:
|
||||
credential_form_schemas:
|
||||
- variable: apikey
|
||||
label:
|
||||
en_US: API Key
|
||||
type: secret-input
|
||||
required: true
|
||||
placeholder:
|
||||
en_US: Enter your API key
|
||||
address: https://apipark.com
|
||||
@@ -0,0 +1,51 @@
|
||||
model: fakegpt-1.0
|
||||
label:
|
||||
en_US: FakeGPT 1.0
|
||||
model_type: llm
|
||||
features:
|
||||
- agent-thought
|
||||
- vision
|
||||
model_properties:
|
||||
mode: chat
|
||||
context_size: 200000
|
||||
parameter_rules:
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
type: int
|
||||
default: 4096
|
||||
min: 1
|
||||
max: 4096
|
||||
help:
|
||||
zh_Hans: 停止前生成的最大令牌数。
|
||||
en_US: The maximum number of tokens to generate before stopping.
|
||||
- name: temperature
|
||||
use_template: temperature
|
||||
required: false
|
||||
type: float
|
||||
default: 1
|
||||
min: 0.0
|
||||
max: 1.0
|
||||
help:
|
||||
zh_Hans: 生成内容的随机性。
|
||||
en_US: The amount of randomness injected into the response.
|
||||
- name: top_p
|
||||
required: false
|
||||
type: float
|
||||
default: 0.999
|
||||
min: 0.000
|
||||
max: 1.000
|
||||
help:
|
||||
zh_Hans: 在核采样中,按概率递减顺序计算每个后续标记的所有选项的累积分布,并在达到 top_p 指定的特定概率时将其切断。您应该更改温度或top_p,但不能同时更改两者。
|
||||
en_US: In nucleus sampling, computes the cumulative distribution over all the options for each subsequent token in decreasing probability order and cuts it off once it reaches a particular probability specified by top_p. You should alter either temperature or top_p, but not both.
|
||||
- name: top_k
|
||||
required: false
|
||||
type: int
|
||||
default: 0
|
||||
min: 0
|
||||
# tip docs from aws has error, max value is 500
|
||||
max: 500
|
||||
help:
|
||||
zh_Hans: 对于每个后续标记,仅从前 K 个选项中进行采样。使用 top_k 删除长尾低概率响应。
|
||||
en_US: Only sample from the top K options for each subsequent token. Use top_k to remove long tail low probability responses.
|
||||
|
||||
@@ -69,15 +69,6 @@ provider_credential_schema:
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的 API Key
|
||||
en_US: Enter your API Key
|
||||
- variable: openai_organization
|
||||
label:
|
||||
zh_Hans: 组织 ID
|
||||
en_US: Organization
|
||||
type: text-input
|
||||
required: false
|
||||
placeholder:
|
||||
zh_Hans: 在此输入您的组织 ID
|
||||
en_US: Enter your Organization ID
|
||||
- variable: openai_api_base
|
||||
label:
|
||||
zh_Hans: API Base
|
||||
|
||||
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 2048
|
||||
|
||||
@@ -23,7 +23,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ parameter_rules:
|
||||
use_template: presence_penalty
|
||||
- name: frequency_penalty
|
||||
use_template: frequency_penalty
|
||||
- name: max_output_tokens
|
||||
- name: max_tokens
|
||||
use_template: max_tokens
|
||||
required: true
|
||||
default: 8192
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+164
-35
@@ -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) {
|
||||
@@ -151,8 +236,9 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
|
||||
Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}",
|
||||
}
|
||||
aiModel := &ai_api_dto.AiModel{
|
||||
Id: m.ID(),
|
||||
Config: m.DefaultConfig(),
|
||||
Id: m.ID(),
|
||||
Config: m.DefaultConfig(),
|
||||
Provider: *input.Provider,
|
||||
}
|
||||
name := "Demo Translation API"
|
||||
description := "A demo that shows you how to use a prompt to create a Translation API."
|
||||
@@ -211,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.",
|
||||
})
|
||||
@@ -227,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) {
|
||||
@@ -278,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)
|
||||
}
|
||||
@@ -326,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{})
|
||||
})
|
||||
}
|
||||
+32
-10
@@ -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",
|
||||
@@ -319,12 +340,12 @@ func (i *imlInitController) OnInit() {
|
||||
}
|
||||
func (i *imlInitController) createAIService(ctx context.Context, teamID string, input *service_dto.CreateService) error {
|
||||
|
||||
providerId := "openai"
|
||||
err := i.providerModule.UpdateProviderConfig(ctx, "openai", &ai_dto.UpdateConfig{
|
||||
Config: "{\n \"openai_api_base\": \"API Base\",\n \"openai_api_key\": \"API Key\",\n \"openai_organization\": \"Organization\"\n}",
|
||||
providerId := "fakegpt"
|
||||
err := i.providerModule.UpdateProviderConfig(ctx, providerId, &ai_dto.UpdateConfig{
|
||||
Config: "{\n \"apikey\": \"xxx\" \n}",
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update openai config error: %v", err)
|
||||
return fmt.Errorf("update %s config error: %v", providerId, err)
|
||||
}
|
||||
input.Provider = &providerId
|
||||
if input.Id == "" {
|
||||
@@ -381,8 +402,9 @@ func (i *imlInitController) createAIService(ctx context.Context, teamID string,
|
||||
Prompt: "You need to translate {{source_lang}} into {{target_lang}}, and the following is the content that needs to be translated.\n---\n{{text}}",
|
||||
}
|
||||
aiModel := &ai_api_dto.AiModel{
|
||||
Id: m.ID(),
|
||||
Config: m.DefaultConfig(),
|
||||
Id: m.ID(),
|
||||
Config: m.DefaultConfig(),
|
||||
Provider: providerId,
|
||||
}
|
||||
name := "Demo Translation API"
|
||||
description := "A demo that shows you how to use a prompt to create a Translation API."
|
||||
@@ -415,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,
|
||||
},
|
||||
}
|
||||
@@ -435,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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,5 @@ packages/core/public/tinymce/
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
|
||||
/pnpm-lock.yaml
|
||||
|
||||
@@ -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!
|
||||
+12
-5
@@ -1,10 +1,17 @@
|
||||
# 部署
|
||||
|
||||
## 安装依赖
|
||||
建议使用pnpm
|
||||
`npm install -g pnpm`
|
||||
使用pnpm安装依赖
|
||||
`pnpm install`
|
||||
建议使用 pnpm
|
||||
```
|
||||
npm install -g pnpm
|
||||
```
|
||||
|
||||
使用pnpm安装依赖
|
||||
```
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## 编译
|
||||
`pnpm run build`
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
/*
|
||||
* @Date: 2024-05-10 14:19:56
|
||||
* @LastEditors: maggieyyy
|
||||
* @LastEditTime: 2024-05-10 15:55:29
|
||||
* @FilePath: \frontend\jest.config.js
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
roots: ['<rootDir>/packages'],
|
||||
testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'],
|
||||
@@ -15,4 +8,4 @@ module.exports = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
/*
|
||||
* @Date: 2024-05-10 14:22:41
|
||||
* @LastEditors: maggieyyy
|
||||
* @LastEditTime: 2024-05-10 15:49:31
|
||||
* @FilePath: \frontend\jest.setup.js
|
||||
*/
|
||||
// import '@testing-library/jest-dom/extend-expect';
|
||||
// import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent"
|
||||
|
||||
}
|
||||
@@ -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,26 +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 - 企业API数据开放平台</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,15 +0,0 @@
|
||||
/*
|
||||
* @Date: 2023-11-27 17:31:54
|
||||
* @LastEditors: maggieyyy
|
||||
* @LastEditTime: 2024-06-05 10:42:18
|
||||
* @FilePath: \frontend\packages\core\postcss.config.js
|
||||
*/
|
||||
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,22 +0,0 @@
|
||||
/*
|
||||
* @Date: 2024-06-05 09:35:25
|
||||
* @LastEditors: maggieyyy
|
||||
* @LastEditTime: 2024-06-05 10:50:12
|
||||
* @FilePath: \frontend\packages\core\start-vite.js
|
||||
*/
|
||||
// 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
@@ -1,9 +1,4 @@
|
||||
/*
|
||||
* @Date: 2023-11-27 17:31:54
|
||||
* @LastEditors: maggieyyy
|
||||
* @LastEditTime: 2023-11-29 15:49:05
|
||||
* @FilePath: \applatform\frontend\packages\core\postcss.config.js
|
||||
*/
|
||||
|
||||
export default {
|
||||
plugins: {
|
||||
'postcss-import': {},
|
||||
|
||||
@@ -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,290 +1,291 @@
|
||||
import {
|
||||
ConfigProvider,
|
||||
Dropdown,
|
||||
MenuProps,
|
||||
App,
|
||||
Button} from 'antd';
|
||||
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 {Outlet, useLocation, useNavigate} from "react-router-dom";
|
||||
import { useEffect, useMemo, useState} from "react";
|
||||
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx';
|
||||
import { PERMISSION_DEFINITION } from '@common/const/permissions.ts';
|
||||
import {
|
||||
ProConfigProvider,
|
||||
ProLayout,
|
||||
} from '@ant-design/pro-components';
|
||||
import { 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 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} = 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.workspace.api_market.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.dashboard.run_view.view'),
|
||||
APP_MODE === 'pro' ? getNavItem(<a >{$t('系统拓扑图')}</a>, 'systemrunning','/systemrunning',<ProjectFilled />,undefined,undefined,'system.dashboard.systemrunning.view') : null,
|
||||
],undefined,'system.dashboard.run_view.view'),
|
||||
|
||||
getNavItem($t('系统设置'), 'operationCenter','/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.devops.cluster.view'),
|
||||
getNavItem(<a>{$t('AI 模型')}</a>, 'aisetting','/aisetting',<Icon icon="hugeicons:ai-network" width="18" height="18"/>,undefined,undefined,'system.devops.cluster.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.organization.member.view'),
|
||||
getNavItem(<a>{$t('角色')}</a>, 'role','/role',<Icon icon="ic:baseline-verified-user" width="18" height="18"/>,undefined,undefined,'system.organization.role.view'),
|
||||
],undefined,''),
|
||||
getNavItem($t('集成'), 'maintenanceCenter','/datasourcing', null, [
|
||||
getNavItem(<a>{$t('数据源')}</a>, 'datasourcing','/datasourcing',<Icon icon="ic:baseline-monitor-heart" width="18" height="18"/>,undefined,undefined,'system.devops.data_source.view'),
|
||||
getNavItem(<a>{$t('证书')}</a>, 'cert','/cert',<Icon icon="ic:baseline-security" width="18" height="18"/>,undefined,undefined,'system.devops.ssl_certificate.view'),
|
||||
getNavItem(<a>{$t('日志')}</a>, 'logsettings','/logsettings',<Icon icon="ic:baseline-sticky-note-2" width="18" height="18"/>,undefined,undefined,'system.devops.log_configuration.view'),
|
||||
APP_MODE === 'pro' ? getNavItem(<a>{$t('资源')}</a>, 'resourcesettings','/resourcesettings',null,undefined,undefined,'system.partition.self.view'):null,
|
||||
APP_MODE === 'pro' ? getNavItem(<a>{$t('Open API')}</a>, 'openapi','/openapi',null,undefined,undefined,'system.openapi.self.view'):null,
|
||||
]),
|
||||
]),
|
||||
],[state.language,accessInit])
|
||||
useEffect(() => {
|
||||
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 { dispatch,resetAccess,getGlobalAccessData} = useGlobalContext()
|
||||
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'] = [
|
||||
{
|
||||
key: '2',
|
||||
label: (
|
||||
<Button key="changePsw" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={()=>navigator('/userProfile/changepsw')}>
|
||||
{$t('账号设置')}
|
||||
</Button>)
|
||||
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()
|
||||
}, [])
|
||||
|
||||
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: '3',
|
||||
label: (
|
||||
<Button key="logout" type="text" className="flex items-center p-0 bg-transparent border-none " onClick={logOut}>
|
||||
{$t('退出登录')}
|
||||
</Button>)
|
||||
},
|
||||
];
|
||||
key: '3',
|
||||
label: (
|
||||
<Button
|
||||
key="logout"
|
||||
type="text"
|
||||
className="flex items-center p-0 bg-transparent border-none"
|
||||
onClick={logOut}
|
||||
>
|
||||
{$t('退出登录')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
].filter(Boolean),
|
||||
[userInfo]
|
||||
)
|
||||
|
||||
const actionRender = useMemo(() => {
|
||||
return [
|
||||
<LanguageSetting />,
|
||||
<Button
|
||||
className=" text-[#ffffffb3] hover:text-[#fff] border-none"
|
||||
type="default"
|
||||
ghost
|
||||
onClick={() => {
|
||||
window.open('https://docs.apipark.com', '_blank')
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center gap-[8px]">
|
||||
{' '}
|
||||
<Icon icon="ic:baseline-help" width="14" height="14" />
|
||||
{$t('文档')}
|
||||
</span>
|
||||
</Button>,
|
||||
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || [])
|
||||
]
|
||||
}, [pluginSlotHub.getSlot('basicLayoutAfterBtns')])
|
||||
|
||||
|
||||
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,93 @@
|
||||
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>
|
||||
</>
|
||||
)
|
||||
})
|
||||
+399
-219
@@ -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>
|
||||
{/*
|
||||
</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,143 +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('块')}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
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('鉴权名称')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user